From 6ef7749e25b1e87667a91f6fe9db7fddef3424c3 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 22 Mar 2026 05:28:45 +0000 Subject: [PATCH] Rebuild Willow as a P2P Discord replacement with Bevy UI Replace the original Yew-based timeline app with a full chat platform architecture. New crate structure: - willow-transport: Protocol-versioned binary serialization with envelopes - willow-identity: Modern libp2p Ed25519 identity, Arc-based (Send+Sync), user profiles, signed message packaging - willow-messaging: Chat messages with Hybrid Logical Clock ordering, multiple content types (text, file, reaction, reply, edit, delete), pluggable message store with in-memory implementation - willow-channel: Server/channel/role/permission model with invite system - willow-network: libp2p networking layer with GossipSub, Kademlia, mDNS, Relay, and Identify protocols on tokio - willow-app: Bevy 0.14 desktop client with Discord-style layout and async network bridge via std::sync::mpsc channels All library crates have thorough documentation and tests (60 total). Removes old timeline and web crates. https://claude.ai/code/session_019s8X2ZL8SEF9KZok2e5qJV --- CLAUDE.md | 106 + Cargo.lock | 7059 ++++++++++++++++++++++++------ Cargo.toml | 59 +- PLAN.md | 115 + crates/app/Cargo.toml | 20 + crates/app/src/main.rs | 35 + crates/app/src/network_bridge.rs | 156 + crates/app/src/ui.rs | 340 ++ crates/channel/Cargo.toml | 18 + crates/channel/src/lib.rs | 730 +++ crates/identity/Cargo.toml | 22 +- crates/identity/src/lib.rs | 343 +- crates/messaging/Cargo.toml | 18 + crates/messaging/src/hlc.rs | 280 ++ crates/messaging/src/lib.rs | 329 ++ crates/messaging/src/store.rs | 238 + crates/network/Cargo.toml | 20 + crates/network/src/behaviour.rs | 31 + crates/network/src/config.rs | 38 + crates/network/src/lib.rs | 63 + crates/network/src/node.rs | 355 ++ crates/timeline/Cargo.toml | 15 - crates/timeline/src/lib.rs | 138 - crates/transport/Cargo.toml | 20 +- crates/transport/src/lib.rs | 291 +- crates/web/Cargo.toml | 11 - crates/web/index.html | 7 - crates/web/src/main.rs | 93 - 28 files changed, 9184 insertions(+), 1766 deletions(-) create mode 100644 CLAUDE.md create mode 100644 PLAN.md create mode 100644 crates/app/Cargo.toml create mode 100644 crates/app/src/main.rs create mode 100644 crates/app/src/network_bridge.rs create mode 100644 crates/app/src/ui.rs create mode 100644 crates/channel/Cargo.toml create mode 100644 crates/channel/src/lib.rs create mode 100644 crates/messaging/Cargo.toml create mode 100644 crates/messaging/src/hlc.rs create mode 100644 crates/messaging/src/lib.rs create mode 100644 crates/messaging/src/store.rs create mode 100644 crates/network/Cargo.toml create mode 100644 crates/network/src/behaviour.rs create mode 100644 crates/network/src/config.rs create mode 100644 crates/network/src/lib.rs create mode 100644 crates/network/src/node.rs delete mode 100644 crates/timeline/Cargo.toml delete mode 100644 crates/timeline/src/lib.rs delete mode 100644 crates/web/Cargo.toml delete mode 100644 crates/web/index.html delete mode 100644 crates/web/src/main.rs diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..6d0c4ff9 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,106 @@ +# CLAUDE.md — Willow Development Guide + +## Project Overview + +Willow is a P2P Discord replacement built in Rust. It uses libp2p for +networking, Bevy for the desktop UI, and Ed25519 cryptography for identity. + +## Repository Structure + +``` +crates/ +├── transport/ — Binary serialization & protocol framing (willow-transport) +├── identity/ — Ed25519 identity, message signing, profiles (willow-identity) +├── messaging/ — Chat messages, HLC ordering, message store (willow-messaging) +├── channel/ — Servers, channels, roles, permissions (willow-channel) +├── network/ — libp2p P2P networking layer (willow-network) +└── app/ — Bevy desktop UI application (willow-app) +``` + +## Build & Test + +```bash +# Check everything compiles +cargo check + +# Run all tests (60 tests across all crates) +cargo test + +# Run tests for a specific crate +cargo test -p willow-messaging + +# Build the desktop app (requires desktop environment with GPU) +cargo build -p willow-app +``` + +### System Dependencies (for Bevy app) + +On a full desktop Linux system, you'll need: +```bash +sudo apt install -y libasound2-dev libudev-dev libwayland-dev libxkbcommon-dev +``` + +The library crates (transport, identity, messaging, channel, network) compile +without any system dependencies. + +## Code Conventions + +- **Crate naming**: `willow-` in Cargo.toml, `willow_` in code +- **Thread safety**: Use `Arc` (not `Rc`) everywhere — all types must be `Send + Sync` +- **Error handling**: `thiserror` for library error types, `anyhow` for application code +- **Documentation**: Every public type and function has a doc comment. Module-level `//!` docs explain the purpose and provide examples. +- **Testing**: Every crate has unit tests. Use `#[cfg(test)] mod tests` at the bottom of each file. +- **Serialization**: All wire types derive `Serialize + Deserialize`. Round-trip tests validate compatibility. + +## Architecture Notes + +### Dependency Graph + +``` +willow-app → willow-network → willow-identity → willow-transport + → willow-channel → willow-identity + → willow-messaging → willow-identity +``` + +### Async / Sync Boundary + +- **Network layer**: Fully async (tokio). Runs on a background thread. +- **Bevy app**: Synchronous ECS. Communicates with the network via `std::sync::mpsc` channels. +- **Bridge**: `network_bridge.rs` in the app crate converts between the two worlds. + +### Message Flow + +1. User types in Bevy UI → `NetworkBridgeCommand::Publish` +2. Bridge sends to tokio task → `NetworkNode::publish()` +3. libp2p GossipSub floods to subscribed peers +4. Remote peer receives → `NetworkEvent::Message` +5. Bridge forwards to Bevy → `NetworkBridgeEvent::MessageReceived` +6. Bevy system updates `ChatState` resource → UI re-renders + +### Hybrid Logical Clocks (HLC) + +Messages are ordered using HLCs (`willow-messaging/src/hlc.rs`). Every node +maintains an `HLC` instance. Call `hlc.now()` for local events and +`hlc.receive(remote_ts)` when processing remote messages. This ensures +consistent ordering even when system clocks drift. + +## Common Tasks + +### Adding a new message type + +1. Add a variant to `Content` in `crates/messaging/src/lib.rs` +2. Add a constructor method on `Message` +3. Add tests +4. Handle the new variant in the app's network event handler + +### Adding a new permission + +1. Add a variant to `Permission` in `crates/channel/src/lib.rs` +2. Check it in the relevant server methods +3. Add tests + +### Adding a new libp2p protocol + +1. Add the protocol to `WillowBehaviour` in `crates/network/src/behaviour.rs` +2. Handle its events in `run_swarm()` in `crates/network/src/node.rs` +3. Expose relevant commands/events through `NetworkNode` and `NetworkEvent` diff --git a/Cargo.lock b/Cargo.lock index b151c30e..98f79753 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,2412 +1,6590 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] -name = "ahash" -version = "0.7.6" +name = "ab_glyph" +version = "0.2.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +checksum = "01c0457472c38ea5bd1c3b5ada5e368271cb550be7a4ca4a0b4634e9913f6cc2" dependencies = [ - "getrandom 0.2.8", - "once_cell", - "version_check", + "ab_glyph_rasterizer", + "owned_ttf_parser", ] [[package]] -name = "aho-corasick" -version = "0.7.19" +name = "ab_glyph_rasterizer" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e" -dependencies = [ - "memchr", -] +checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618" [[package]] -name = "android_system_properties" -version = "0.1.5" +name = "accesskit" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" -dependencies = [ - "libc", -] +checksum = "6cf780eb737f2d4a49ffbd512324d53ad089070f813f7be7f99dbd5123a7f448" [[package]] -name = "anyhow" -version = "1.0.66" +name = "accesskit_consumer" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6" +checksum = "3bdfa1638ddd6eb9c752def95568df8b3ad832df252e9156d2eb783b201ca8a9" +dependencies = [ + "accesskit", + "immutable-chunkmap", +] [[package]] -name = "anymap" -version = "1.0.0-beta.2" +name = "accesskit_macos" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f1f8f5a6f3d50d89e3797d7593a50f96bb2aaa20ca0cc7be1fb673232c91d72" +checksum = "c236a84ff1111defc280cee755eaa953d0b24398786851b9d28322c6d3bb1ebd" +dependencies = [ + "accesskit", + "accesskit_consumer", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "once_cell", +] [[package]] -name = "anymap2" -version = "0.13.0" +name = "accesskit_windows" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d301b3b94cb4b2f23d7917810addbbaff90738e0ca2be692bd027e70d7e0330c" +checksum = "5d7f43d24b16b3e76bef248124fbfd2493c3a9860edb5aae1010c890e826de5e" +dependencies = [ + "accesskit", + "accesskit_consumer", + "paste", + "static_assertions", + "windows 0.54.0", +] [[package]] -name = "arrayref" -version = "0.3.6" +name = "accesskit_winit" +version = "0.20.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" +checksum = "755535e6bf711a42dac28b888b884b10fc00ff4010d9d3bd871c5f5beae5aa78" +dependencies = [ + "accesskit", + "accesskit_macos", + "accesskit_windows", + "raw-window-handle", + "winit", +] [[package]] -name = "arrayvec" +name = "aead" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" - -[[package]] -name = "asn1_der" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e22d1f4b888c298a027c99dc9048015fac177587de20fc30232a057dfbe24a21" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] [[package]] -name = "async-trait" -version = "0.1.58" +name = "aes" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e805d94e6b5001b651426cf4cd446b1ab5f319d27bab5c644f61de0a804360c" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" dependencies = [ - "proc-macro2", - "quote", - "syn", + "cfg-if", + "cipher", + "cpufeatures", ] [[package]] -name = "asynchronous-codec" -version = "0.6.1" +name = "aes-gcm" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06a0daa378f5fd10634e44b0a29b2a87b890657658e072a30d6f26e57ddee182" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" dependencies = [ - "bytes", - "futures-sink", - "futures-util", - "memchr", - "pin-project-lite", + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", ] [[package]] -name = "atomic" -version = "0.5.1" +name = "ahash" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b88d82667eca772c4aa12f0f1348b3ae643424c8876448f3f7bd5787032e234c" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ - "autocfg", + "cfg-if", + "getrandom 0.3.4", + "once_cell", + "version_check", + "zerocopy", ] [[package]] -name = "autocfg" -version = "1.1.0" +name = "aho-corasick" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] [[package]] -name = "base64" -version = "0.13.1" +name = "allocator-api2" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] -name = "bincode" -version = "1.3.3" +name = "android-activity" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +checksum = "ef6978589202a00cd7e118380c448a08b6ed394c3a8df3a430d0898e3a42d046" dependencies = [ - "serde", + "android-properties", + "bitflags 2.11.0", + "cc", + "cesu8", + "jni", + "jni-sys", + "libc", + "log", + "ndk", + "ndk-context", + "ndk-sys 0.6.0+11769913", + "num_enum", + "thiserror 1.0.69", ] [[package]] -name = "bitflags" -version = "1.3.2" +name = "android-properties" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" [[package]] -name = "block-buffer" -version = "0.9.0" +name = "android_log-sys" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" -dependencies = [ - "generic-array", -] +checksum = "84521a3cf562bc62942e294181d9eef17eb38ceb8c68677bc49f144e4c3d4f8d" [[package]] -name = "block-buffer" -version = "0.10.3" +name = "android_system_properties" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" dependencies = [ - "generic-array", + "libc", ] [[package]] -name = "boolinator" -version = "2.4.0" +name = "anyhow" +version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfa8873f51c92e232f9bac4065cddef41b714152812bfc5f7672ba16d6ef8cd9" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" [[package]] -name = "bs58" -version = "0.4.0" +name = "approx" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3" +checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" +dependencies = [ + "num-traits", +] [[package]] -name = "bumpalo" -version = "3.11.1" +name = "arrayref" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" +checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" [[package]] -name = "byteorder" -version = "1.4.3" +name = "arrayvec" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] -name = "bytes" -version = "1.2.1" +name = "ash" +version = "0.37.3+1.3.251" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" +checksum = "39e9c3835d686b0a6084ab4234fcd1b07dbf6e4767dce60874b12356a25ecd4a" dependencies = [ - "serde", + "libloading 0.7.4", ] [[package]] -name = "cc" -version = "1.0.74" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581f5dba903aac52ea3feb5ec4810848460ee833876f1f9b0fdeab1f19091574" - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "chrono" -version = "0.4.22" +name = "asn1-rs" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1" +checksum = "7f6fd5ddaf0351dff5b8da21b2fb4ff8e08ddd02857f0bf69c47639106c0fff0" dependencies = [ - "iana-time-zone", - "js-sys", - "num-integer", + "asn1-rs-derive", + "asn1-rs-impl", + "displaydoc", + "nom", "num-traits", - "serde", - "time", - "wasm-bindgen", - "winapi", + "rusticata-macros", + "thiserror 1.0.69", + "time 0.3.47", ] [[package]] -name = "codespan-reporting" -version = "0.11.1" +name = "asn1-rs-derive" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +checksum = "726535892e8eae7e70657b4c8ea93d26b8553afb1ce617caee529ef96d7dee6c" dependencies = [ - "termcolor", - "unicode-width", + "proc-macro2", + "quote", + "syn 1.0.103", + "synstructure 0.12.6", ] [[package]] -name = "console_error_panic_hook" -version = "0.1.7" +name = "asn1-rs-impl" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +checksum = "2777730b2039ac0f95f093556e61b6d26cebed5393ca6f152717777cec3a42ed" dependencies = [ - "cfg-if", - "wasm-bindgen", + "proc-macro2", + "quote", + "syn 1.0.103", ] [[package]] -name = "core-foundation-sys" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" - -[[package]] -name = "core2" -version = "0.4.0" +name = "async-broadcast" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b49ba7ef1ad6107f8824dbe97de947cbaac53c44e7f9756a1fba0d37c1eec505" +checksum = "7c48ccdbf6ca6b121e0f586cbc0e73ae440e56c67c30fa0873b4e110d9c26d2b" dependencies = [ - "memchr", + "event-listener 2.5.3", + "futures-core", ] [[package]] -name = "cpufeatures" -version = "0.2.5" +name = "async-channel" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" +checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" dependencies = [ - "libc", + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", ] [[package]] -name = "crunchy" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" - -[[package]] -name = "crypto-common" -version = "0.1.6" +name = "async-executor" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a" dependencies = [ - "generic-array", - "typenum", + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "pin-project-lite", + "slab", ] [[package]] -name = "curve25519-dalek" -version = "3.2.1" +name = "async-fs" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90f9d052967f590a76e62eb387bd0bbb1b000182c3cefe5364db6b7211651bc0" +checksum = "8034a681df4aed8b8edbd7fbe472401ecf009251c8b40556b304567052e294c5" dependencies = [ - "byteorder", - "digest 0.9.0", - "rand_core 0.5.1", - "subtle", - "zeroize", + "async-lock", + "blocking", + "futures-lite", ] [[package]] -name = "cxx" -version = "1.0.80" +name = "async-io" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b7d4e43b25d3c994662706a1d4fcfc32aaa6afd287502c111b237093bb23f3a" +checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" dependencies = [ - "cc", - "cxxbridge-flags", - "cxxbridge-macro", - "link-cplusplus", + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite", + "parking", + "polling", + "rustix 1.1.4", + "slab", + "windows-sys 0.61.2", ] [[package]] -name = "cxx-build" -version = "1.0.80" +name = "async-lock" +version = "3.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84f8829ddc213e2c1368e51a2564c552b65a8cb6a28f31e576270ac81d5e5827" +checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" dependencies = [ - "cc", - "codespan-reporting", - "once_cell", - "proc-macro2", - "quote", - "scratch", - "syn", + "event-listener 5.4.1", + "event-listener-strategy", + "pin-project-lite", ] [[package]] -name = "cxxbridge-flags" -version = "1.0.80" +name = "async-task" +version = "4.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e72537424b474af1460806647c41d4b6d35d09ef7fe031c5c2fa5766047cc56a" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" [[package]] -name = "cxxbridge-macro" -version = "1.0.80" +name = "async-trait" +version = "0.1.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "309e4fb93eed90e1e14bea0da16b209f81813ba9fc7830c20ed151dd7bc0a4d7" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] -name = "darling" -version = "0.13.4" +name = "asynchronous-codec" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" +checksum = "a860072022177f903e59730004fb5dc13db9275b79bb2aef7ba8ce831956c233" dependencies = [ - "darling_core", - "darling_macro", + "bytes", + "futures-sink", + "futures-util", + "memchr", + "pin-project-lite", ] [[package]] -name = "darling_core" -version = "0.13.4" +name = "atomic-waker" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn", -] +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] -name = "darling_macro" -version = "0.13.4" +name = "attohttpc" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" +checksum = "8d9a9bf8b79a749ee0b911b91b671cc2b6c670bdbc7e3dfd537576ddc94bb2a2" dependencies = [ - "darling_core", - "quote", - "syn", + "http", + "log", + "url", ] [[package]] -name = "data-encoding" -version = "2.3.2" +name = "autocfg" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] -name = "digest" -version = "0.9.0" +name = "base-x" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" -dependencies = [ - "generic-array", -] +checksum = "4cbbc9d0964165b47557570cce6c952866c2678457aca742aafc9fb771d30270" [[package]] -name = "digest" -version = "0.10.5" +name = "base256emoji" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adfbc57365a37acbd2ebf2b64d7e69bb766e2fea813521ed536f5d0520dcf86c" +checksum = "b5e9430d9a245a77c92176e649af6e275f20839a48389859d1661e9a128d077c" dependencies = [ - "block-buffer 0.10.3", - "crypto-common", + "const-str", + "match-lookup", ] [[package]] -name = "dtoa" -version = "1.0.4" +name = "base64" +version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8a6eee2d5d0d113f015688310da018bd1d864d86bd567c8fca9c266889e1bfa" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" [[package]] -name = "ed25519" -version = "1.5.2" +name = "base64" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e9c280362032ea4203659fc489832d0204ef09f247a0506f170dafcac08c369" -dependencies = [ - "signature", -] +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] -name = "ed25519-dalek" -version = "1.0.1" +name = "base64ct" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + +[[package]] +name = "bevy" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d" +checksum = "043c9ad4b6fc4ca52d779873a8ca792a4e37842d07fce95363c9e17e36a1d8a0" dependencies = [ - "curve25519-dalek", - "ed25519", - "rand 0.7.3", - "serde", - "sha2 0.9.9", - "zeroize", + "bevy_internal", ] [[package]] -name = "either" -version = "1.8.0" +name = "bevy_a11y" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" +checksum = "ae1a976cb539d6a5a3ff579cdb78187a6bcfbffa7e8224ea28f23d8b983d9389" +dependencies = [ + "accesskit", + "bevy_app", + "bevy_derive", + "bevy_ecs", +] [[package]] -name = "fastrand" -version = "1.8.0" +name = "bevy_app" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" +checksum = "a5361d0f8a8677a5d0102cfe7321a7ecd2a8b9a4f887ce0dde1059311cf9cd42" dependencies = [ - "instant", + "bevy_derive", + "bevy_ecs", + "bevy_reflect", + "bevy_tasks", + "bevy_utils", + "console_error_panic_hook", + "downcast-rs", + "thiserror 1.0.69", + "wasm-bindgen", + "web-sys", ] [[package]] -name = "fixedbitset" -version = "0.4.2" +name = "bevy_asset" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" +checksum = "60ec5ea257e1ebd3d411f669e29acf60beb715bebc7e1f374c17f49cd3aad46c" +dependencies = [ + "async-broadcast", + "async-fs", + "async-lock", + "bevy_app", + "bevy_asset_macros", + "bevy_ecs", + "bevy_reflect", + "bevy_tasks", + "bevy_utils", + "bevy_winit", + "blake3", + "crossbeam-channel", + "downcast-rs", + "futures-io", + "futures-lite", + "js-sys", + "parking_lot 0.12.5", + "ron", + "serde", + "thiserror 1.0.69", + "uuid", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] [[package]] -name = "fnv" -version = "1.0.7" +name = "bevy_asset_macros" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +checksum = "c9eb05ce838d282f09d83380b4d6432aec7519d421dee8c75cc20e6148237e6e" +dependencies = [ + "bevy_macro_utils", + "proc-macro2", + "quote", + "syn 2.0.117", +] [[package]] -name = "form_urlencoded" -version = "1.1.0" +name = "bevy_color" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" +checksum = "04842e9d38a93f0c75ab46f7f404ea24ef57ad83dbd159e5b4b35318b02257bb" dependencies = [ - "percent-encoding", + "bevy_math", + "bevy_reflect", + "bytemuck", + "encase", + "serde", + "thiserror 1.0.69", + "wgpu-types", ] [[package]] -name = "futures" -version = "0.3.25" +name = "bevy_core" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38390104763dc37a5145a53c29c63c1290b5d316d6086ec32c293f6736051bb0" +checksum = "de706862871a1fe99ea619bff2f99d73e43ad82f19ef866a9e19a14c957c8537" dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", + "bevy_app", + "bevy_ecs", + "bevy_reflect", + "bevy_tasks", + "bevy_utils", + "uuid", ] [[package]] -name = "futures-channel" -version = "0.3.25" +name = "bevy_core_pipeline" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed" +checksum = "2f6e1e122ada4cd811442e083fb5ad3e325c59a87271d5ef57193f1c2cad7f8c" dependencies = [ - "futures-core", - "futures-sink", + "bevy_app", + "bevy_asset", + "bevy_color", + "bevy_core", + "bevy_derive", + "bevy_ecs", + "bevy_math", + "bevy_reflect", + "bevy_render", + "bevy_transform", + "bevy_utils", + "bitflags 2.11.0", + "nonmax", + "radsort", + "serde", + "smallvec", + "thiserror 1.0.69", ] [[package]] -name = "futures-core" -version = "0.3.25" +name = "bevy_derive" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" +checksum = "3fbfc33a4c6b80760bb8bf850a2cc65a1e031da62fd3ca8b552189104dc98514" +dependencies = [ + "bevy_macro_utils", + "quote", + "syn 2.0.117", +] [[package]] -name = "futures-executor" -version = "0.3.25" +name = "bevy_diagnostic" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7acc85df6714c176ab5edf386123fafe217be88c0840ec11f199441134a074e2" +checksum = "bebb154e0cc78e3bbfbfdb42fb502b14c1cd47e72f16e6d4228dfe6233ba6cbd" dependencies = [ - "futures-core", - "futures-task", - "futures-util", - "num_cpus", + "bevy_app", + "bevy_core", + "bevy_ecs", + "bevy_tasks", + "bevy_time", + "bevy_utils", + "const-fnv1a-hash", ] [[package]] -name = "futures-io" -version = "0.3.25" +name = "bevy_ecs" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb" +checksum = "9ee4222406637f3c8e3991a99788cfcde76097bf997c311f1b6297364057483f" +dependencies = [ + "arrayvec", + "bevy_ecs_macros", + "bevy_ptr", + "bevy_reflect", + "bevy_tasks", + "bevy_utils", + "bitflags 2.11.0", + "concurrent-queue", + "fixedbitset 0.5.7", + "nonmax", + "petgraph", + "serde", + "thiserror 1.0.69", +] [[package]] -name = "futures-macro" -version = "0.3.25" +name = "bevy_ecs_macros" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d" +checksum = "36b573430b67aff7bde8292257494f39343401379bfbda64035ba4918bba7b20" dependencies = [ + "bevy_macro_utils", "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] -name = "futures-sink" -version = "0.3.25" +name = "bevy_encase_derive" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9" +checksum = "d06c9693847a2a6ea61d6b86288dd4d8b6a79f05d4bf6e27b96d4f5c8d552fe4" +dependencies = [ + "bevy_macro_utils", + "encase_derive_impl", +] [[package]] -name = "futures-task" -version = "0.3.25" +name = "bevy_gizmos" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" +checksum = "dfe32af0666d8d8a7fd6eb6b5e41eceefdc6f2e5441c74b812e8f0902a9d7f52" +dependencies = [ + "bevy_app", + "bevy_asset", + "bevy_color", + "bevy_core_pipeline", + "bevy_ecs", + "bevy_gizmos_macros", + "bevy_math", + "bevy_reflect", + "bevy_render", + "bevy_sprite", + "bevy_time", + "bevy_transform", + "bevy_utils", + "bytemuck", +] [[package]] -name = "futures-timer" -version = "3.0.2" +name = "bevy_gizmos_macros" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" +checksum = "906b052f8cf3f3983f0f6df625fb10cbd9b27d44e362a327dc1ed51300d362bc" dependencies = [ - "gloo-timers", - "send_wrapper", + "bevy_macro_utils", + "proc-macro2", + "quote", + "syn 2.0.117", ] [[package]] -name = "futures-util" -version = "0.3.25" +name = "bevy_hierarchy" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" +checksum = "a88b912b37e1bc4dbb2aa40723199f74c8b06c4fbb6da0bb4585131df28ef66e" dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "pin-utils", - "slab", + "bevy_app", + "bevy_core", + "bevy_ecs", + "bevy_reflect", + "bevy_utils", + "smallvec", ] [[package]] -name = "generic-array" -version = "0.14.6" +name = "bevy_input" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" +checksum = "8dd3a54e67cc3ba17971de7b1a7e64eda84493c1e7bb6bfa11c6cf8ac124377b" dependencies = [ - "typenum", - "version_check", + "bevy_app", + "bevy_ecs", + "bevy_math", + "bevy_reflect", + "bevy_utils", + "smol_str", + "thiserror 1.0.69", ] [[package]] -name = "getrandom" -version = "0.1.16" +name = "bevy_internal" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +checksum = "45d435cac77c568f3aef65f786a5fee0e53c81950c5258182dd2c1d6cd6c4fec" dependencies = [ - "cfg-if", - "js-sys", - "libc", - "wasi 0.9.0+wasi-snapshot-preview1", - "wasm-bindgen", + "bevy_a11y", + "bevy_app", + "bevy_asset", + "bevy_color", + "bevy_core", + "bevy_core_pipeline", + "bevy_derive", + "bevy_diagnostic", + "bevy_ecs", + "bevy_gizmos", + "bevy_hierarchy", + "bevy_input", + "bevy_log", + "bevy_math", + "bevy_ptr", + "bevy_reflect", + "bevy_render", + "bevy_scene", + "bevy_sprite", + "bevy_tasks", + "bevy_text", + "bevy_time", + "bevy_transform", + "bevy_ui", + "bevy_utils", + "bevy_window", ] [[package]] -name = "getrandom" -version = "0.2.8" +name = "bevy_log" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +checksum = "67240c7596c8f0653e50fce35a60196516817449235193246599facba9002e02" dependencies = [ - "cfg-if", - "js-sys", - "libc", - "wasi 0.11.0+wasi-snapshot-preview1", - "wasm-bindgen", + "android_log-sys", + "bevy_app", + "bevy_ecs", + "bevy_utils", + "tracing-log", + "tracing-subscriber", + "tracing-wasm", ] [[package]] -name = "gloo" -version = "0.8.0" +name = "bevy_macro_utils" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a4bef6b277b3ab073253d4bca60761240cf8d6998f4bd142211957b69a61b20" +checksum = "bfc65e570012e64a21f3546df68591aaede8349e6174fb500071677f54f06630" dependencies = [ - "gloo-console", - "gloo-dialogs", - "gloo-events", - "gloo-file", - "gloo-history", - "gloo-net", - "gloo-render", - "gloo-storage", - "gloo-timers", - "gloo-utils", - "gloo-worker", + "proc-macro2", + "quote", + "syn 2.0.117", + "toml_edit", ] [[package]] -name = "gloo-console" -version = "0.2.3" +name = "bevy_math" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82b7ce3c05debe147233596904981848862b068862e9ec3e34be446077190d3f" +checksum = "5421792749dda753ab3718e77d27bfce38443daf1850b836b97530b6245a4581" dependencies = [ - "gloo-utils", - "js-sys", + "bevy_reflect", + "glam", + "rand 0.8.5", "serde", - "wasm-bindgen", - "web-sys", + "smallvec", + "thiserror 1.0.69", ] [[package]] -name = "gloo-dialogs" -version = "0.1.1" +name = "bevy_mikktspace" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67062364ac72d27f08445a46cab428188e2e224ec9e37efdba48ae8c289002e6" +checksum = "66cf695a264b043f2c4edb92dd5c742e6892180d2b30dac870012d153f8557ea" dependencies = [ - "wasm-bindgen", - "web-sys", + "glam", ] [[package]] -name = "gloo-events" -version = "0.1.2" +name = "bevy_ptr" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68b107f8abed8105e4182de63845afcc7b69c098b7852a813ea7462a320992fc" -dependencies = [ - "wasm-bindgen", - "web-sys", -] +checksum = "61baa1bdc1f4a7ac2c18217570a7cc04e1cd54d38456e91782f0371c79afe0a8" [[package]] -name = "gloo-file" -version = "0.2.3" +name = "bevy_reflect" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8d5564e570a38b43d78bdc063374a0c3098c4f0d64005b12f9bbe87e869b6d7" +checksum = "2508785a4a5809f25a237eec4fee2c91a4dbcf81324b2bbc2d6c52629e603781" dependencies = [ - "gloo-events", - "js-sys", - "wasm-bindgen", - "web-sys", + "bevy_ptr", + "bevy_reflect_derive", + "bevy_utils", + "downcast-rs", + "erased-serde", + "glam", + "serde", + "smallvec", + "smol_str", + "thiserror 1.0.69", + "uuid", ] [[package]] -name = "gloo-history" -version = "0.1.0" +name = "bevy_reflect_derive" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f81af52c0d31e86242eecefe1ed4d066deb79cfb80f9f7da0847fac417396bfe" +checksum = "967d5da1882ec3bb3675353915d3da909cafac033cbf31e58727824a1ad2a288" dependencies = [ - "gloo-events", - "gloo-utils", - "serde", - "serde-wasm-bindgen", - "serde_urlencoded", - "thiserror", - "wasm-bindgen", - "web-sys", + "bevy_macro_utils", + "proc-macro2", + "quote", + "syn 2.0.117", + "uuid", ] [[package]] -name = "gloo-net" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec897194fb9ac576c708f63d35604bc58f2a262b8cec0fabfed26f3991255f21" -dependencies = [ - "futures-channel", - "futures-core", - "futures-sink", - "gloo-utils", +name = "bevy_render" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "836cf8a513db013cbe7d55a331060088efd407e49fd5b05c8404700cd82e7619" +dependencies = [ + "async-channel", + "bevy_app", + "bevy_asset", + "bevy_color", + "bevy_core", + "bevy_derive", + "bevy_diagnostic", + "bevy_ecs", + "bevy_encase_derive", + "bevy_hierarchy", + "bevy_math", + "bevy_mikktspace", + "bevy_reflect", + "bevy_render_macros", + "bevy_tasks", + "bevy_time", + "bevy_transform", + "bevy_utils", + "bevy_window", + "bitflags 2.11.0", + "bytemuck", + "codespan-reporting", + "downcast-rs", + "encase", + "futures-lite", + "hexasphere", + "image", "js-sys", - "pin-project 1.0.12", + "naga", + "naga_oil", + "nonmax", + "send_wrapper", "serde", - "serde_json", - "thiserror", + "smallvec", + "thiserror 1.0.69", "wasm-bindgen", - "wasm-bindgen-futures", "web-sys", + "wgpu", ] [[package]] -name = "gloo-render" -version = "0.1.1" +name = "bevy_render_macros" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fd9306aef67cfd4449823aadcd14e3958e0800aa2183955a309112a84ec7764" +checksum = "cbc24e0e95061a38a7744218b9c7e52e4c08b53f1499f33480e2b749f3864432" dependencies = [ - "wasm-bindgen", - "web-sys", + "bevy_macro_utils", + "proc-macro2", + "quote", + "syn 2.0.117", ] [[package]] -name = "gloo-storage" -version = "0.2.2" +name = "bevy_scene" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d6ab60bf5dbfd6f0ed1f7843da31b41010515c745735c970e821945ca91e480" +checksum = "8ec57a72d75273bdbb6154390688fd07ba79ae9f6f99476d1937f799c736c2da" dependencies = [ - "gloo-utils", - "js-sys", + "bevy_app", + "bevy_asset", + "bevy_derive", + "bevy_ecs", + "bevy_hierarchy", + "bevy_reflect", + "bevy_render", + "bevy_transform", + "bevy_utils", "serde", - "serde_json", - "thiserror", - "wasm-bindgen", - "web-sys", + "thiserror 1.0.69", + "uuid", ] [[package]] -name = "gloo-timers" -version = "0.2.4" +name = "bevy_sprite" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fb7d06c1c8cc2a29bee7ec961009a0b2caa0793ee4900c2ffb348734ba1c8f9" +checksum = "e045b4d8cc8e7422a4c29b1eadbe224f5cc42f170b88d43e7535892fcede3840" dependencies = [ - "futures-channel", - "futures-core", - "js-sys", - "wasm-bindgen", + "bevy_app", + "bevy_asset", + "bevy_color", + "bevy_core_pipeline", + "bevy_derive", + "bevy_ecs", + "bevy_math", + "bevy_reflect", + "bevy_render", + "bevy_transform", + "bevy_utils", + "bitflags 2.11.0", + "bytemuck", + "fixedbitset 0.5.7", + "guillotiere", + "radsort", + "rectangle-pack", + "thiserror 1.0.69", ] [[package]] -name = "gloo-utils" -version = "0.1.5" +name = "bevy_tasks" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40913a05c8297adca04392f707b1e73b12ba7b8eab7244a4961580b1fd34063c" +checksum = "77865f310b1fc48fb05b7c4adbe76607ec01d0c14f8ab4caba4d714c86439946" dependencies = [ - "js-sys", - "serde", - "serde_json", - "wasm-bindgen", - "web-sys", + "async-channel", + "async-executor", + "concurrent-queue", + "futures-lite", + "wasm-bindgen-futures", ] [[package]] -name = "gloo-worker" -version = "0.2.1" +name = "bevy_text" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13471584da78061a28306d1359dd0178d8d6fc1c7c80e5e35d27260346e0516a" +checksum = "b661db828fd423fc41a4ccf43aa4d1b8e50e75057ec40453317d0d761e8ad62d" dependencies = [ - "anymap2", - "bincode", - "gloo-console", - "gloo-utils", - "js-sys", + "ab_glyph", + "bevy_app", + "bevy_asset", + "bevy_color", + "bevy_ecs", + "bevy_math", + "bevy_reflect", + "bevy_render", + "bevy_sprite", + "bevy_transform", + "bevy_utils", + "bevy_window", + "glyph_brush_layout", "serde", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", + "thiserror 1.0.69", ] [[package]] -name = "hashbrown" -version = "0.12.3" +name = "bevy_time" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +checksum = "f4e4d53ec32a1b16492396951d04de0d2d90e924bf9adcb8d1adacab5ab6c17c" dependencies = [ - "ahash", + "bevy_app", + "bevy_ecs", + "bevy_reflect", + "bevy_utils", + "crossbeam-channel", + "thiserror 1.0.69", ] [[package]] -name = "heck" -version = "0.3.3" +name = "bevy_transform" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" +checksum = "d5493dce84427d00a9266e8e4386d738a72ee8640423b62dfcecb6dfccbfe0d2" dependencies = [ - "unicode-segmentation", + "bevy_app", + "bevy_ecs", + "bevy_hierarchy", + "bevy_math", + "bevy_reflect", + "thiserror 1.0.69", ] [[package]] -name = "hermit-abi" -version = "0.1.19" +name = "bevy_ui" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +checksum = "56d2cba6603b39a3765f043212ae530e25550af168a7eec6b23b9b93c19bc5f7" dependencies = [ - "libc", + "bevy_a11y", + "bevy_app", + "bevy_asset", + "bevy_color", + "bevy_core_pipeline", + "bevy_derive", + "bevy_ecs", + "bevy_hierarchy", + "bevy_input", + "bevy_math", + "bevy_reflect", + "bevy_render", + "bevy_sprite", + "bevy_text", + "bevy_transform", + "bevy_utils", + "bevy_window", + "bytemuck", + "nonmax", + "smallvec", + "taffy", + "thiserror 1.0.69", ] [[package]] -name = "hex" -version = "0.4.3" +name = "bevy_utils" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +checksum = "ffb0ec333b5965771153bd746f92ffd8aeeb9d008a8620ffd9ed474859381a5e" +dependencies = [ + "ahash", + "bevy_utils_proc_macros", + "getrandom 0.2.17", + "hashbrown 0.14.5", + "thread_local", + "tracing", + "web-time", +] [[package]] -name = "hex_fmt" -version = "0.3.0" +name = "bevy_utils_proc_macros" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b07f60793ff0a4d9cef0f18e63b5357e06209987153a64648c972c1e5aff336f" +checksum = "38f1ab8f2f6f58439d260081d89a42b02690e5fdd64f814edc9417d33fcf2857" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] [[package]] -name = "iana-time-zone" -version = "0.1.53" +name = "bevy_window" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765" +checksum = "c89e88a20db64ea8204540afb4699295947c454738fd50293f7b32ab8be857a6" dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "wasm-bindgen", - "winapi", + "bevy_a11y", + "bevy_app", + "bevy_ecs", + "bevy_math", + "bevy_reflect", + "bevy_utils", + "raw-window-handle", + "smol_str", ] [[package]] -name = "iana-time-zone-haiku" -version = "0.1.1" +name = "bevy_winit" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" +checksum = "d0bef8ec3e4b45db943ad4d1c0bf59b09e382ce0651a706e2f33a70fa955303c" dependencies = [ - "cxx", - "cxx-build", + "accesskit_winit", + "approx", + "bevy_a11y", + "bevy_app", + "bevy_derive", + "bevy_ecs", + "bevy_hierarchy", + "bevy_input", + "bevy_log", + "bevy_math", + "bevy_reflect", + "bevy_tasks", + "bevy_utils", + "bevy_window", + "cfg-if", + "crossbeam-channel", + "raw-window-handle", + "wasm-bindgen", + "web-sys", + "winit", ] [[package]] -name = "ident_case" -version = "1.0.1" +name = "bincode" +version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" - -[[package]] -name = "identity" -version = "0.1.0" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" dependencies = [ - "libp2p", "serde", - "thiserror", - "transport", ] [[package]] -name = "idna" -version = "0.3.0" +name = "bit-set" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" dependencies = [ - "unicode-bidi", - "unicode-normalization", + "bit-vec", ] [[package]] -name = "implicit-clone" -version = "0.3.2" +name = "bit-vec" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a937e630d3907d421944abd8edb5288936f1fde83aaaf1a8c6c89bb4222f0677" -dependencies = [ - "indexmap", -] +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" [[package]] -name = "indexmap" -version = "1.9.1" +name = "bitflags" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" dependencies = [ - "autocfg", - "hashbrown", + "serde_core", ] [[package]] -name = "instant" -version = "0.1.12" +name = "blake2" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" dependencies = [ - "cfg-if", - "js-sys", - "wasm-bindgen", - "web-sys", + "digest", ] [[package]] -name = "itertools" -version = "0.10.5" +name = "blake3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +checksum = "2468ef7d57b3fb7e16b576e8377cdbde2320c60e1491e961d11da40fc4f02a2d" dependencies = [ - "either", + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", + "cpufeatures", ] [[package]] -name = "itoa" -version = "1.0.4" +name = "block" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" +checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" [[package]] -name = "js-sys" -version = "0.3.60" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" +name = "block-buffer" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block2" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" +dependencies = [ + "objc2", +] + +[[package]] +name = "blocking" +version = "1.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21" +dependencies = [ + "async-channel", + "async-task", + "futures-io", + "futures-lite", + "piper", +] + +[[package]] +name = "bs58" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "bumpalo" +version = "3.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" + +[[package]] +name = "bytemuck" +version = "1.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "byteorder-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" +dependencies = [ + "serde", +] + +[[package]] +name = "calloop" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec" +dependencies = [ + "bitflags 2.11.0", + "log", + "polling", + "rustix 0.38.44", + "slab", + "thiserror 1.0.69", +] + +[[package]] +name = "cc" +version = "1.2.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cfg_aliases" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "chacha20" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "chacha20poly1305" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" +dependencies = [ + "aead", + "chacha20", + "cipher", + "poly1305", + "zeroize", +] + +[[package]] +name = "chrono" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-integer", + "num-traits", + "serde", + "time 0.1.44", + "wasm-bindgen", + "winapi", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", + "zeroize", +] + +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width", +] + +[[package]] +name = "com" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e17887fd17353b65b1b2ef1c526c83e26cd72e74f598a8dc1bee13a48f3d9f6" +dependencies = [ + "com_macros", +] + +[[package]] +name = "com_macros" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d375883580a668c7481ea6631fc1a8863e33cc335bf56bfad8d7e6d4b04b13a5" +dependencies = [ + "com_macros_support", + "proc-macro2", + "syn 1.0.103", +] + +[[package]] +name = "com_macros_support" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad899a1087a9296d5644792d7cb72b8e34c1bec8e7d4fbc002230169a6e8710c" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.103", +] + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" dependencies = [ + "cfg-if", "wasm-bindgen", ] [[package]] -name = "lazy_static" -version = "1.4.0" +name = "const-fnv1a-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32b13ea120a812beba79e34316b3942a857c86ec1593cb34f27bb28272ce2cca" + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "const-str" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f421161cb492475f1661ddc9815a745a1c894592070661180fdec3d4872e9c3" + +[[package]] +name = "const_panic" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e262cdaac42494e3ae34c43969f9cdeb7da178bdb4b66fa6a1ea2edb4c8ae652" +dependencies = [ + "typewit", +] + +[[package]] +name = "const_soft_float" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ca1caa64ef4ed453e68bb3db612e51cf1b2f5b871337f0fcab1c8f87cc3dff" + +[[package]] +name = "constant_time_eq" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" + +[[package]] +name = "constgebra" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1aaf9b65849a68662ac6c0810c8893a765c960b907dd7cfab9c4a50bf764fbc" +dependencies = [ + "const_soft_float", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "core-graphics" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-graphics-types", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "libc", +] + +[[package]] +name = "core2" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b49ba7ef1ad6107f8824dbe97de947cbaac53c44e7f9756a1fba0d37c1eec505" +dependencies = [ + "memchr", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "typenum", +] + +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + +[[package]] +name = "cursor-icon" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f" + +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "digest", + "fiat-crypto", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "cxx" +version = "1.0.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b7d4e43b25d3c994662706a1d4fcfc32aaa6afd287502c111b237093bb23f3a" +dependencies = [ + "cc", + "cxxbridge-flags", + "cxxbridge-macro", + "link-cplusplus", +] + +[[package]] +name = "cxx-build" +version = "1.0.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84f8829ddc213e2c1368e51a2564c552b65a8cb6a28f31e576270ac81d5e5827" +dependencies = [ + "cc", + "codespan-reporting", + "once_cell", + "proc-macro2", + "quote", + "scratch", + "syn 1.0.103", +] + +[[package]] +name = "cxxbridge-flags" +version = "1.0.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e72537424b474af1460806647c41d4b6d35d09ef7fe031c5c2fa5766047cc56a" + +[[package]] +name = "cxxbridge-macro" +version = "1.0.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "309e4fb93eed90e1e14bea0da16b209f81813ba9fc7830c20ed151dd7bc0a4d7" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.103", +] + +[[package]] +name = "d3d12" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b28bfe653d79bd16c77f659305b195b82bb5ce0c0eb2a4846b82ddbd77586813" +dependencies = [ + "bitflags 2.11.0", + "libloading 0.8.9", + "winapi", +] + +[[package]] +name = "data-encoding" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" + +[[package]] +name = "data-encoding-macro" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8142a83c17aa9461d637e649271eae18bf2edd00e91f2e105df36c3c16355bdb" +dependencies = [ + "data-encoding", + "data-encoding-macro-internal", +] + +[[package]] +name = "data-encoding-macro-internal" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ab67060fc6b8ef687992d439ca0fa36e7ed17e9a0b16b25b601e8757df720de" +dependencies = [ + "data-encoding", + "syn 1.0.103", +] + +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "zeroize", +] + +[[package]] +name = "der-parser" +version = "8.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbd676fbbab537128ef0278adb5576cf363cff6aa22a7b24effe97347cfab61e" +dependencies = [ + "asn1-rs", + "displaydoc", + "nom", + "num-bigint", + "num-traits", + "rusticata-macros", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + +[[package]] +name = "dispatch" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "dlib" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab8ecd87370524b461f8557c119c405552c396ed91fc0a8eec68679eab26f94a" +dependencies = [ + "libloading 0.8.9", +] + +[[package]] +name = "document-features" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" +dependencies = [ + "litrs", +] + +[[package]] +name = "downcast-rs" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" + +[[package]] +name = "dpi" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" + +[[package]] +name = "dtoa" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8a6eee2d5d0d113f015688310da018bd1d864d86bd567c8fca9c266889e1bfa" + +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8", + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" +dependencies = [ + "curve25519-dalek", + "ed25519", + "serde", + "sha2", + "subtle", + "zeroize", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "encase" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a9299a95fa5671ddf29ecc22b00e121843a65cb9ff24911e394b4ae556baf36" +dependencies = [ + "const_panic", + "encase_derive", + "glam", + "thiserror 1.0.69", +] + +[[package]] +name = "encase_derive" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e09decb3beb1fe2db6940f598957b2e1f7df6206a804d438ff6cb2a9cddc10" +dependencies = [ + "encase_derive_impl", +] + +[[package]] +name = "encase_derive_impl" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd31dbbd9743684d339f907a87fe212cb7b51d75b9e8e74181fe363199ee9b47" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "enum-as-inner" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "erased-serde" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2add8a07dd6a8d93ff627029c51de145e12686fbc36ecb298ac22e74cf02dec" +dependencies = [ + "serde", + "serde_core", + "typeid", +] + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "euclid" +version = "0.22.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1a05365e3b1c6d1650318537c7460c6923f1abdd272ad6842baa2b509957a06" +dependencies = [ + "num-traits", +] + +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + +[[package]] +name = "event-listener" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener 5.4.1", + "pin-project-lite", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-bounded" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91f328e7fb845fc832912fb6a34f40cf6d1888c92f974d1893a54e97b5ff542e" +dependencies = [ + "futures-timer", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-executor" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" + +[[package]] +name = "futures-lite" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + +[[package]] +name = "futures-macro" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "futures-rustls" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd3cf68c183738046838e300353e4716c674dc5e56890de4826801a6622a28" +dependencies = [ + "futures-io", + "rustls", +] + +[[package]] +name = "futures-sink" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-ticker" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9763058047f713632a52e916cc7f6a4b3fc6e9fc1ff8c5b1dc49e5a89041682e" +dependencies = [ + "futures", + "futures-timer", + "instant", +] + +[[package]] +name = "futures-timer" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi 5.3.0", + "wasip2", +] + +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", + "wasip2", + "wasip3", +] + +[[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 = "gl_generator" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d" +dependencies = [ + "khronos_api", + "log", + "xml-rs", +] + +[[package]] +name = "glam" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e05e7e6723e3455f4818c7b26e855439f7546cf617ef669d1adedb8669e5cb9" +dependencies = [ + "bytemuck", + "rand 0.8.5", + "serde", +] + +[[package]] +name = "glow" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd348e04c43b32574f2de31c8bb397d96c9fcfa1371bd4ca6d8bdc464ab121b1" +dependencies = [ + "js-sys", + "slotmap", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "glutin_wgl_sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8098adac955faa2d31079b65dc48841251f69efd3ac25477903fc424362ead" +dependencies = [ + "gl_generator", +] + +[[package]] +name = "glyph_brush_layout" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b1e288bfd2f6c0313f78bf5aa538356ad481a3bb97e9b7f93220ab0066c5992" +dependencies = [ + "ab_glyph", + "approx", + "xi-unicode", +] + +[[package]] +name = "gpu-alloc" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbcd2dba93594b227a1f57ee09b8b9da8892c34d55aa332e034a228d0fe6a171" +dependencies = [ + "bitflags 2.11.0", + "gpu-alloc-types", +] + +[[package]] +name = "gpu-alloc-types" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98ff03b468aa837d70984d55f5d3f846f6ec31fe34bbb97c4f85219caeee1ca4" +dependencies = [ + "bitflags 2.11.0", +] + +[[package]] +name = "gpu-allocator" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f56f6318968d03c18e1bcf4857ff88c61157e9da8e47c5f29055d60e1228884" +dependencies = [ + "log", + "presser", + "thiserror 1.0.69", + "winapi", + "windows 0.52.0", +] + +[[package]] +name = "gpu-descriptor" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b89c83349105e3732062a895becfc71a8f921bb71ecbbdd8ff99263e3b53a0ca" +dependencies = [ + "bitflags 2.11.0", + "gpu-descriptor-types", + "hashbrown 0.15.5", +] + +[[package]] +name = "gpu-descriptor-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdf242682df893b86f33a73828fb09ca4b2d3bb6cc95249707fc684d27484b91" +dependencies = [ + "bitflags 2.11.0", +] + +[[package]] +name = "grid" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be136d9dacc2a13cc70bb6c8f902b414fb2641f8db1314637c6b7933411a8f82" + +[[package]] +name = "guillotiere" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b62d5865c036cb1393e23c50693df631d3f5d7bcca4c04fe4cc0fd592e74a782" +dependencies = [ + "euclid", + "svg_fmt", +] + +[[package]] +name = "h2" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap 2.13.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash", + "allocator-api2", + "serde", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[package]] +name = "hassle-rs" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af2a7e73e1f34c48da31fb668a907f250794837e08faa144fd24f0b8b741e890" +dependencies = [ + "bitflags 2.11.0", + "com", + "libc", + "libloading 0.8.9", + "thiserror 1.0.69", + "widestring", + "winapi", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hex_fmt" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b07f60793ff0a4d9cef0f18e63b5357e06209987153a64648c972c1e5aff336f" + +[[package]] +name = "hexasphere" +version = "12.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd6b038160f086b0a7496edae34169ae22f328793cbe2b627a5a3d8373748ec" +dependencies = [ + "constgebra", + "glam", +] + +[[package]] +name = "hexf-parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" + +[[package]] +name = "hickory-proto" +version = "0.24.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92652067c9ce6f66ce53cc38d1169daa36e6e7eb7dd3b63b5103bd9d97117248" +dependencies = [ + "async-trait", + "cfg-if", + "data-encoding", + "enum-as-inner", + "futures-channel", + "futures-io", + "futures-util", + "idna", + "ipnet", + "once_cell", + "rand 0.8.5", + "socket2 0.5.10", + "thiserror 1.0.69", + "tinyvec", + "tokio", + "tracing", + "url", +] + +[[package]] +name = "hickory-resolver" +version = "0.24.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbb117a1ca520e111743ab2f6688eddee69db4e0ea242545a604dce8a66fd22e" +dependencies = [ + "cfg-if", + "futures-util", + "hickory-proto", + "ipconfig", + "lru-cache", + "once_cell", + "parking_lot 0.12.5", + "rand 0.8.5", + "resolv-conf", + "smallvec", + "thiserror 1.0.69", + "tokio", + "tracing", +] + +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "0.14.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2 0.5.10", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "winapi", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" +dependencies = [ + "cxx", + "cxx-build", +] + +[[package]] +name = "icu_collections" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" + +[[package]] +name = "icu_properties" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" + +[[package]] +name = "icu_provider" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "if-addrs" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0a05c691e1fae256cf7013d99dad472dc52d5543322761f83ec8d47eab40d2b" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "if-watch" +version = "3.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71c02a5161c313f0cbdbadc511611893584a10a7b6153cb554bdf83ddce99ec2" +dependencies = [ + "async-io", + "core-foundation", + "fnv", + "futures", + "if-addrs", + "ipnet", + "log", + "netlink-packet-core", + "netlink-packet-route", + "netlink-proto", + "netlink-sys", + "rtnetlink", + "system-configuration", + "tokio", + "windows 0.62.2", +] + +[[package]] +name = "igd-next" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "064d90fec10d541084e7b39ead8875a5a80d9114a2b18791565253bae25f49e4" +dependencies = [ + "async-trait", + "attohttpc", + "bytes", + "futures", + "http", + "hyper", + "log", + "rand 0.8.5", + "tokio", + "url", + "xmltree", +] + +[[package]] +name = "image" +version = "0.25.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104" +dependencies = [ + "bytemuck", + "byteorder-lite", + "moxcms", + "num-traits", +] + +[[package]] +name = "immutable-chunkmap" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3e98b1520e49e252237edc238a39869da9f3241f2ec19dc788c1d24694d1e4" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "indexmap" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", +] + +[[package]] +name = "indexmap" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +dependencies = [ + "equivalent", + "hashbrown 0.16.1", + "serde", + "serde_core", +] + +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array", +] + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "ipconfig" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" +dependencies = [ + "socket2 0.5.10", + "widestring", + "windows-sys 0.48.0", + "winreg", +] + +[[package]] +name = "ipnet" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" + +[[package]] +name = "itoa" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" + +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "khronos-egl" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76" +dependencies = [ + "libc", + "libloading 0.8.9", + "pkg-config", +] + +[[package]] +name = "khronos_api" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libc" +version = "0.2.183" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" + +[[package]] +name = "libloading" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +dependencies = [ + "cfg-if", + "winapi", +] + +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link", +] + +[[package]] +name = "libp2p" +version = "0.53.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "681fb3f183edfbedd7a57d32ebe5dcdc0b9f94061185acf3c30249349cc6fc99" +dependencies = [ + "bytes", + "either", + "futures", + "futures-timer", + "getrandom 0.2.17", + "instant", + "libp2p-allow-block-list", + "libp2p-connection-limits", + "libp2p-core", + "libp2p-dns", + "libp2p-gossipsub", + "libp2p-identify", + "libp2p-identity", + "libp2p-kad", + "libp2p-mdns", + "libp2p-metrics", + "libp2p-noise", + "libp2p-quic", + "libp2p-relay", + "libp2p-swarm", + "libp2p-tcp", + "libp2p-upnp", + "libp2p-yamux", + "multiaddr", + "pin-project", + "rw-stream-sink", + "thiserror 1.0.69", +] + +[[package]] +name = "libp2p-allow-block-list" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "107b238b794cb83ab53b74ad5dcf7cca3200899b72fe662840cfb52f5b0a32e6" +dependencies = [ + "libp2p-core", + "libp2p-identity", + "libp2p-swarm", + "void", +] + +[[package]] +name = "libp2p-connection-limits" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7cd50a78ccfada14de94cbacd3ce4b0138157f376870f13d3a8422cd075b4fd" +dependencies = [ + "libp2p-core", + "libp2p-identity", + "libp2p-swarm", + "void", +] + +[[package]] +name = "libp2p-core" +version = "0.41.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5a8920cbd8540059a01950c1e5c96ea8d89eb50c51cd366fc18bdf540a6e48f" +dependencies = [ + "either", + "fnv", + "futures", + "futures-timer", + "libp2p-identity", + "multiaddr", + "multihash", + "multistream-select", + "once_cell", + "parking_lot 0.12.5", + "pin-project", + "quick-protobuf", + "rand 0.8.5", + "rw-stream-sink", + "serde", + "smallvec", + "thiserror 1.0.69", + "tracing", + "unsigned-varint 0.8.0", + "void", + "web-time", +] + +[[package]] +name = "libp2p-dns" +version = "0.41.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d17cbcf7160ff35c3e8e560de4a068fe9d6cb777ea72840e48eb76ff9576c4b6" +dependencies = [ + "async-trait", + "futures", + "hickory-resolver", + "libp2p-core", + "libp2p-identity", + "parking_lot 0.12.5", + "smallvec", + "tracing", +] + +[[package]] +name = "libp2p-gossipsub" +version = "0.46.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d665144a616dadebdc5fff186b1233488cdcd8bfb1223218ff084b6d052c94f7" +dependencies = [ + "asynchronous-codec", + "base64 0.21.7", + "byteorder", + "bytes", + "either", + "fnv", + "futures", + "futures-ticker", + "getrandom 0.2.17", + "hex_fmt", + "instant", + "libp2p-core", + "libp2p-identity", + "libp2p-swarm", + "prometheus-client", + "quick-protobuf", + "quick-protobuf-codec", + "rand 0.8.5", + "regex", + "serde", + "sha2", + "smallvec", + "tracing", + "void", +] + +[[package]] +name = "libp2p-identify" +version = "0.44.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20499a945d2f0221fdc6269b3848892c0f370d2ee3e19c7f65a29d8f860f6126" +dependencies = [ + "asynchronous-codec", + "either", + "futures", + "futures-bounded", + "futures-timer", + "libp2p-core", + "libp2p-identity", + "libp2p-swarm", + "lru", + "quick-protobuf", + "quick-protobuf-codec", + "smallvec", + "thiserror 1.0.69", + "tracing", + "void", +] + +[[package]] +name = "libp2p-identity" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0c7892c221730ba55f7196e98b0b8ba5e04b4155651736036628e9f73ed6fc3" +dependencies = [ + "bs58", + "ed25519-dalek", + "hkdf", + "multihash", + "quick-protobuf", + "rand 0.8.5", + "serde", + "sha2", + "thiserror 2.0.18", + "tracing", + "zeroize", +] + +[[package]] +name = "libp2p-kad" +version = "0.45.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cc5767727d062c4eac74dd812c998f0e488008e82cce9c33b463d38423f9ad2" +dependencies = [ + "arrayvec", + "asynchronous-codec", + "bytes", + "either", + "fnv", + "futures", + "futures-bounded", + "futures-timer", + "instant", + "libp2p-core", + "libp2p-identity", + "libp2p-swarm", + "quick-protobuf", + "quick-protobuf-codec", + "rand 0.8.5", + "serde", + "sha2", + "smallvec", + "thiserror 1.0.69", + "tracing", + "uint", + "void", +] + +[[package]] +name = "libp2p-mdns" +version = "0.45.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49007d9a339b3e1d7eeebc4d67c05dbf23d300b7d091193ec2d3f26802d7faf2" +dependencies = [ + "data-encoding", + "futures", + "hickory-proto", + "if-watch", + "libp2p-core", + "libp2p-identity", + "libp2p-swarm", + "rand 0.8.5", + "smallvec", + "socket2 0.5.10", + "tokio", + "tracing", + "void", +] + +[[package]] +name = "libp2p-metrics" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdac91ae4f291046a3b2660c039a2830c931f84df2ee227989af92f7692d3357" +dependencies = [ + "futures", + "instant", + "libp2p-core", + "libp2p-gossipsub", + "libp2p-identify", + "libp2p-identity", + "libp2p-kad", + "libp2p-relay", + "libp2p-swarm", + "pin-project", + "prometheus-client", +] + +[[package]] +name = "libp2p-noise" +version = "0.44.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecd0545ce077f6ea5434bcb76e8d0fe942693b4380aaad0d34a358c2bd05793" +dependencies = [ + "asynchronous-codec", + "bytes", + "curve25519-dalek", + "futures", + "libp2p-core", + "libp2p-identity", + "multiaddr", + "multihash", + "once_cell", + "quick-protobuf", + "rand 0.8.5", + "sha2", + "snow", + "static_assertions", + "thiserror 1.0.69", + "tracing", + "x25519-dalek", + "zeroize", +] + +[[package]] +name = "libp2p-quic" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0375cdfee57b47b313ef1f0fdb625b78aed770d33a40cf1c294a371ff5e6666" +dependencies = [ + "bytes", + "futures", + "futures-timer", + "if-watch", + "libp2p-core", + "libp2p-identity", + "libp2p-tls", + "parking_lot 0.12.5", + "quinn", + "rand 0.8.5", + "ring 0.16.20", + "rustls", + "socket2 0.5.10", + "thiserror 1.0.69", + "tokio", + "tracing", +] + +[[package]] +name = "libp2p-relay" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0aadb213ffc8e1a6f2b9c48dcf0fc07bf370f2ea4db7981813d45e50671c8d9d" +dependencies = [ + "asynchronous-codec", + "bytes", + "either", + "futures", + "futures-bounded", + "futures-timer", + "instant", + "libp2p-core", + "libp2p-identity", + "libp2p-swarm", + "quick-protobuf", + "quick-protobuf-codec", + "rand 0.8.5", + "static_assertions", + "thiserror 1.0.69", + "tracing", + "void", +] + +[[package]] +name = "libp2p-swarm" +version = "0.44.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e92532fc3c4fb292ae30c371815c9b10103718777726ea5497abc268a4761866" +dependencies = [ + "either", + "fnv", + "futures", + "futures-timer", + "instant", + "libp2p-core", + "libp2p-identity", + "libp2p-swarm-derive", + "multistream-select", + "once_cell", + "rand 0.8.5", + "smallvec", + "tokio", + "tracing", + "void", +] + +[[package]] +name = "libp2p-swarm-derive" +version = "0.34.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b644268b4acfdaa6a6100b31226ee7a36d96ab4c43287d113bfd2308607d8b6f" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "libp2p-tcp" +version = "0.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2460fc2748919adff99ecbc1aab296e4579e41f374fb164149bd2c9e529d4c" +dependencies = [ + "futures", + "futures-timer", + "if-watch", + "libc", + "libp2p-core", + "libp2p-identity", + "socket2 0.5.10", + "tokio", + "tracing", +] + +[[package]] +name = "libp2p-tls" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93ce7e3c2e7569d685d08ec795157981722ff96e9e9f9eae75df3c29d02b07a5" +dependencies = [ + "futures", + "futures-rustls", + "libp2p-core", + "libp2p-identity", + "rcgen", + "ring 0.16.20", + "rustls", + "rustls-webpki", + "thiserror 1.0.69", + "x509-parser", + "yasna", +] + +[[package]] +name = "libp2p-upnp" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b49cc89949bf0e06869297cd4fe2c132358c23fe93e76ad43950453df4da3d35" +dependencies = [ + "futures", + "futures-timer", + "igd-next", + "libp2p-core", + "libp2p-swarm", + "tokio", + "tracing", + "void", +] + +[[package]] +name = "libp2p-yamux" +version = "0.45.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd5265f6b80f94d48a3963541aad183cc598a645755d2f1805a373e41e0716b" +dependencies = [ + "either", + "futures", + "libp2p-core", + "thiserror 1.0.69", + "tracing", + "yamux 0.12.1", + "yamux 0.13.10", +] + +[[package]] +name = "libredox" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1744e39d1d6a9948f4f388969627434e31128196de472883b39f148769bfe30a" +dependencies = [ + "bitflags 2.11.0", + "libc", + "plain", + "redox_syscall 0.7.3", +] + +[[package]] +name = "link-cplusplus" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9272ab7b96c9046fbc5bc56c06c117cb639fe2d509df0c421cad82d2915cf369" +dependencies = [ + "cc", +] + +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "lru" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" +dependencies = [ + "hashbrown 0.15.5", +] + +[[package]] +name = "lru-cache" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c" +dependencies = [ + "linked-hash-map", +] + +[[package]] +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] + +[[package]] +name = "match-lookup" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "757aee279b8bdbb9f9e676796fd459e4207a1f986e87886700abf589f5abf771" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "metal" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5637e166ea14be6063a3f8ba5ccb9a4159df7d8f6d61c02fc3d480b1f90dcfcb" +dependencies = [ + "bitflags 2.11.0", + "block", + "core-graphics-types", + "foreign-types", + "log", + "objc", + "paste", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "mio" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +dependencies = [ + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys 0.61.2", +] + +[[package]] +name = "moxcms" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb85c154ba489f01b25c0d36ae69a87e4a1c73a72631fc6c0eb6dde34a73e44b" +dependencies = [ + "num-traits", + "pxfm", +] + +[[package]] +name = "multiaddr" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe6351f60b488e04c1d21bc69e56b89cb3f5e8f5d22557d6e8031bdfd79b6961" +dependencies = [ + "arrayref", + "byteorder", + "data-encoding", + "libp2p-identity", + "multibase", + "multihash", + "percent-encoding", + "serde", + "static_assertions", + "unsigned-varint 0.8.0", + "url", +] + +[[package]] +name = "multibase" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8694bb4835f452b0e3bb06dbebb1d6fc5385b6ca1caf2e55fd165c042390ec77" +dependencies = [ + "base-x", + "base256emoji", + "data-encoding", + "data-encoding-macro", +] + +[[package]] +name = "multihash" +version = "0.19.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b430e7953c29dd6a09afc29ff0bb69c6e306329ee6794700aee27b76a1aea8d" +dependencies = [ + "core2", + "serde", + "unsigned-varint 0.8.0", +] + +[[package]] +name = "multistream-select" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea0df8e5eec2298a62b326ee4f0d7fe1a6b90a09dfcf9df37b38f947a8c42f19" +dependencies = [ + "bytes", + "futures", + "log", + "pin-project", + "smallvec", + "unsigned-varint 0.7.1", +] + +[[package]] +name = "naga" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e536ae46fcab0876853bd4a632ede5df4b1c2527a58f6c5a4150fe86be858231" +dependencies = [ + "arrayvec", + "bit-set", + "bitflags 2.11.0", + "codespan-reporting", + "hexf-parse", + "indexmap 2.13.0", + "log", + "num-traits", + "pp-rs", + "rustc-hash", + "spirv", + "termcolor", + "thiserror 1.0.69", + "unicode-xid", +] + +[[package]] +name = "naga_oil" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "275d9720a7338eedac966141089232514c84d76a246a58ef501af88c5edf402f" +dependencies = [ + "bit-set", + "codespan-reporting", + "data-encoding", + "indexmap 2.13.0", + "naga", + "once_cell", + "regex", + "regex-syntax", + "rustc-hash", + "thiserror 1.0.69", + "tracing", + "unicode-ident", +] + +[[package]] +name = "ndk" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" +dependencies = [ + "bitflags 2.11.0", + "jni-sys", + "log", + "ndk-sys 0.6.0+11769913", + "num_enum", + "raw-window-handle", + "thiserror 1.0.69", +] + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "ndk-sys" +version = "0.5.0+25.2.9519653" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c196769dd60fd4f363e11d948139556a344e79d451aeb2fa2fd040738ef7691" +dependencies = [ + "jni-sys", +] + +[[package]] +name = "ndk-sys" +version = "0.6.0+11769913" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" +dependencies = [ + "jni-sys", +] + +[[package]] +name = "netlink-packet-core" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3463cbb78394cb0141e2c926b93fc2197e473394b761986eca3b9da2c63ae0f4" +dependencies = [ + "paste", +] + +[[package]] +name = "netlink-packet-route" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ce3636fa715e988114552619582b530481fd5ef176a1e5c1bf024077c2c9445" +dependencies = [ + "bitflags 2.11.0", + "libc", + "log", + "netlink-packet-core", +] + +[[package]] +name = "netlink-proto" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b65d130ee111430e47eed7896ea43ca693c387f097dd97376bffafbf25812128" +dependencies = [ + "bytes", + "futures", + "log", + "netlink-packet-core", + "netlink-sys", + "thiserror 2.0.18", +] + +[[package]] +name = "netlink-sys" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd6c30ed10fa69cc491d491b85cc971f6bdeb8e7367b7cde2ee6cc878d583fae" +dependencies = [ + "bytes", + "futures-util", + "libc", + "log", + "tokio", +] + +[[package]] +name = "nix" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" +dependencies = [ + "bitflags 2.11.0", + "cfg-if", + "cfg_aliases 0.2.1", + "libc", +] + +[[package]] +name = "nohash-hasher" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "nonmax" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "610a5acd306ec67f907abe5567859a3c693fb9886eb1f012ab8f2a47bef3db51" + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "num-bigint" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-conv" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_enum" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" +dependencies = [ + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "objc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +dependencies = [ + "malloc_buf", +] + +[[package]] +name = "objc-sys" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" + +[[package]] +name = "objc2" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804" +dependencies = [ + "objc-sys", + "objc2-encode", +] + +[[package]] +name = "objc2-app-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" +dependencies = [ + "bitflags 2.11.0", + "block2", + "libc", + "objc2", + "objc2-core-data", + "objc2-core-image", + "objc2-foundation", + "objc2-quartz-core", +] + +[[package]] +name = "objc2-cloud-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" +dependencies = [ + "bitflags 2.11.0", + "block2", + "objc2", + "objc2-core-location", + "objc2-foundation", +] + +[[package]] +name = "objc2-contacts" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889" +dependencies = [ + "block2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-data" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" +dependencies = [ + "bitflags 2.11.0", + "block2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-image" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80" +dependencies = [ + "block2", + "objc2", + "objc2-foundation", + "objc2-metal", +] + +[[package]] +name = "objc2-core-location" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781" +dependencies = [ + "block2", + "objc2", + "objc2-contacts", + "objc2-foundation", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-foundation" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" +dependencies = [ + "bitflags 2.11.0", + "block2", + "dispatch", + "libc", + "objc2", +] + +[[package]] +name = "objc2-link-presentation" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398" +dependencies = [ + "block2", + "objc2", + "objc2-app-kit", + "objc2-foundation", +] + +[[package]] +name = "objc2-metal" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" +dependencies = [ + "bitflags 2.11.0", + "block2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" +dependencies = [ + "bitflags 2.11.0", + "block2", + "objc2", + "objc2-foundation", + "objc2-metal", +] + +[[package]] +name = "objc2-symbols" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a684efe3dec1b305badae1a28f6555f6ddd3bb2c2267896782858d5a78404dc" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-ui-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" +dependencies = [ + "bitflags 2.11.0", + "block2", + "objc2", + "objc2-cloud-kit", + "objc2-core-data", + "objc2-core-image", + "objc2-core-location", + "objc2-foundation", + "objc2-link-presentation", + "objc2-quartz-core", + "objc2-symbols", + "objc2-uniform-type-identifiers", + "objc2-user-notifications", +] + +[[package]] +name = "objc2-uniform-type-identifiers" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe" +dependencies = [ + "block2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-user-notifications" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" +dependencies = [ + "bitflags 2.11.0", + "block2", + "objc2", + "objc2-core-location", + "objc2-foundation", +] + +[[package]] +name = "oid-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bedf36ffb6ba96c2eb7144ef6270557b52e54b20c0a8e1eb2ff99a6c6959bff" +dependencies = [ + "asn1-rs", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + +[[package]] +name = "orbclient" +version = "0.3.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59aed3b33578edcfa1bc96a321d590d31832b6ad55a26f0313362ce687e9abd6" +dependencies = [ + "libc", + "libredox", +] + +[[package]] +name = "owned_ttf_parser" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36820e9051aca1014ddc75770aab4d68bc1e9e632f0f5627c4086bc216fb583b" +dependencies = [ + "ttf-parser", +] + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[package]] +name = "parking_lot" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core 0.8.5", +] + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core 0.9.12", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" +dependencies = [ + "cfg-if", + "instant", + "libc", + "redox_syscall 0.2.16", + "smallvec", + "winapi", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.5.18", + "smallvec", + "windows-link", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pem" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" +dependencies = [ + "base64 0.22.1", + "serde_core", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "petgraph" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5014253a1331579ce62aa67443b4a658c5e7dd03d4bc6d302b94474888143" +dependencies = [ + "fixedbitset 0.4.2", + "indexmap 1.9.1", +] + +[[package]] +name = "pin-project" +version = "1.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1749c7ed4bcaf4c3d0a3efc28538844fb29bcdd7d2b67b2be7e20ba861ff517" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b20ed30f105399776b9c883e68e536ef602a16ae6f596d2c473591d6ad64c6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "piper" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1" +dependencies = [ + "atomic-waker", + "fastrand", + "futures-io", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "plain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" + +[[package]] +name = "polling" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi", + "pin-project-lite", + "rustix 1.1.4", + "windows-sys 0.61.2", +] + +[[package]] +name = "poly1305" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" +dependencies = [ + "cpufeatures", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "polyval" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "potential_utf" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "pp-rs" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb458bb7f6e250e6eb79d5026badc10a3ebb8f9a15d1fff0f13d17c71f4d6dee" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "presser" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa" + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn 2.0.117", +] + +[[package]] +name = "proc-macro-crate" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17d47ce914bf4de440332250b0edd23ce48c005f59fab39d3335866b114f11a" +dependencies = [ + "thiserror 1.0.69", + "toml", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "profiling" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eb8486b569e12e2c32ad3e204dbaba5e4b5b216e9367044f25f1dba42341773" + +[[package]] +name = "prometheus-client" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "504ee9ff529add891127c4827eb481bd69dc0ebc72e9a682e187db4caa60c3ca" +dependencies = [ + "dtoa", + "itoa", + "parking_lot 0.12.5", + "prometheus-client-derive-encode", +] + +[[package]] +name = "prometheus-client-derive-encode" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "440f724eba9f6996b75d63681b0a92b06947f1457076d503a4d2e2c8f56442b8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "pxfm" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5a041e753da8b807c9255f28de81879c78c876392ff2469cde94799b2896b9d" + +[[package]] +name = "quick-protobuf" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d6da84cc204722a989e01ba2f6e1e276e190f22263d0cb6ce8526fcdb0d2e1f" +dependencies = [ + "byteorder", +] + +[[package]] +name = "quick-protobuf-codec" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15a0580ab32b169745d7a39db2ba969226ca16738931be152a3209b409de2474" +dependencies = [ + "asynchronous-codec", + "bytes", + "quick-protobuf", + "thiserror 1.0.69", + "unsigned-varint 0.8.0", +] + +[[package]] +name = "quinn" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cc2c5017e4b43d5995dcea317bc46c1e09404c0a9664d2908f7f02dfe943d75" +dependencies = [ + "bytes", + "futures-io", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "thiserror 1.0.69", + "tokio", + "tracing", +] + +[[package]] +name = "quinn-proto" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "141bf7dfde2fbc246bfd3fe12f2455aa24b0fbd9af535d8c86c7bd1381ff2b1a" +dependencies = [ + "bytes", + "rand 0.8.5", + "ring 0.16.20", + "rustc-hash", + "rustls", + "slab", + "thiserror 1.0.69", + "tinyvec", + "tracing", +] + +[[package]] +name = "quinn-udp" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "055b4e778e8feb9f93c4e439f71dc2156ef13360b432b799e179a8c4cdf0b1d7" +dependencies = [ + "bytes", + "libc", + "socket2 0.5.10", + "tracing", + "windows-sys 0.48.0", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +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 = "radsort" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "019b4b213425016d7d84a153c4c73afb0946fbb4840e4eece7ba8848b9d6da22" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.5", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + +[[package]] +name = "range-alloc" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca45419789ae5a7899559e9512e58ca889e41f04f1f2445e9f4b290ceccd1d08" + +[[package]] +name = "raw-window-handle" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" + +[[package]] +name = "rcgen" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52c4f3084aa3bc7dfbba4eff4fab2a54db4324965d8872ab933565e6fbd83bc6" +dependencies = [ + "pem", + "ring 0.16.20", + "time 0.3.47", + "yasna", +] + +[[package]] +name = "rectangle-pack" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0d463f2884048e7153449a55166f91028d5b0ea53c79377099ce4e8cf0cf9bb" + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.11.0", +] + +[[package]] +name = "redox_syscall" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce70a74e890531977d37e532c34d45e9055d2409ed08ddba14529471ed0be16" +dependencies = [ + "bitflags 2.11.0", +] + +[[package]] +name = "regex" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" + +[[package]] +name = "renderdoc-sys" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" + +[[package]] +name = "resolv-conf" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e061d1b48cb8d38042de4ae0a7a6401009d6143dc80d2e2d6f31f0bdd6470c7" + +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin", + "untrusted 0.7.1", + "web-sys", + "winapi", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted 0.9.0", + "windows-sys 0.52.0", +] + +[[package]] +name = "ron" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94" +dependencies = [ + "base64 0.21.7", + "bitflags 2.11.0", + "serde", + "serde_derive", +] + +[[package]] +name = "rtnetlink" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b960d5d873a75b5be9761b1e73b146f52dddcd27bac75263f40fba686d4d7b5" +dependencies = [ + "futures-channel", + "futures-util", + "log", + "netlink-packet-core", + "netlink-packet-route", + "netlink-proto", + "netlink-sys", + "nix", + "thiserror 1.0.69", + "tokio", +] + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rusticata-macros" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" +dependencies = [ + "nom", +] + +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags 2.11.0", + "errno", + "libc", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags 2.11.0", + "errno", + "libc", + "linux-raw-sys 0.12.1", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.21.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" +dependencies = [ + "log", + "ring 0.17.14", + "rustls-webpki", + "sct", +] + +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring 0.17.14", + "untrusted 0.9.0", +] + +[[package]] +name = "rustversion" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97477e48b4cf8603ad5f7aaf897467cf42ab4218a38ef76fb14c2d6773a6d6a8" + +[[package]] +name = "rw-stream-sink" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8c9026ff5d2f23da5e45bbc283f156383001bfb09c4e44256d02c1a685fe9a1" +dependencies = [ + "futures", + "pin-project", + "static_assertions", +] + +[[package]] +name = "ryu" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" + +[[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 = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "scratch" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898" + +[[package]] +name = "sct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring 0.17.14", + "untrusted 0.9.0", +] + +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + +[[package]] +name = "send_wrapper" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "serde_json" +version = "1.0.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce777b7b150d76b9cf60d28b55f5847135a003f7d7350c6be7a773508ce7d45" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "rand_core 0.6.4", +] + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "slotmap" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdd58c3c93c3d278ca835519292445cb4b0d4dc59ccfdf7ceadaab3f8aeb4038" +dependencies = [ + "version_check", +] + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "smol_str" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead" +dependencies = [ + "serde", +] + +[[package]] +name = "snow" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "850948bee068e713b8ab860fe1adc4d109676ab4c3b621fd8147f06b261f2f85" +dependencies = [ + "aes-gcm", + "blake2", + "chacha20poly1305", + "curve25519-dalek", + "rand_core 0.6.4", + "ring 0.17.14", + "rustc_version", + "sha2", + "subtle", +] + +[[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.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "spirv" +version = "0.3.0+sdk-1.3.268.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844" +dependencies = [ + "bitflags 2.11.0", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "subtle" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" + +[[package]] +name = "svg_fmt" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0193cc4331cfd2f3d2011ef287590868599a2f33c3e69bc22c1a3d3acf9e02fb" + +[[package]] +name = "syn" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "synstructure" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.103", + "unicode-xid", +] [[package]] -name = "libc" -version = "0.2.137" +name = "synstructure" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] [[package]] -name = "libp2p" -version = "0.44.0" +name = "system-configuration" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "475ce2ac4a9727e53a519f6ee05b38abfcba8f0d39c4d24f103d184e36fd5b0f" +checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" dependencies = [ - "atomic", - "bytes", - "futures", - "futures-timer", - "getrandom 0.2.8", - "instant", - "lazy_static", - "libp2p-core", - "libp2p-gossipsub", - "libp2p-identify", - "libp2p-kad", - "libp2p-swarm", - "libp2p-swarm-derive", - "multiaddr", - "parking_lot 0.12.1", - "pin-project 1.0.12", - "rand 0.7.3", - "smallvec", + "bitflags 2.11.0", + "core-foundation", + "system-configuration-sys", ] [[package]] -name = "libp2p-core" -version = "0.32.1" +name = "system-configuration-sys" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db5b02602099fb75cb2d16f9ea860a320d6eb82ce41e95ab680912c454805cd5" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" dependencies = [ - "asn1_der", - "bs58", - "ed25519-dalek", - "either", - "fnv", - "futures", - "futures-timer", - "instant", - "lazy_static", - "log", - "multiaddr", - "multihash", - "multistream-select", - "parking_lot 0.12.1", - "pin-project 1.0.12", - "prost", - "prost-build", - "rand 0.8.5", - "ring", - "rw-stream-sink", - "serde", - "sha2 0.10.6", - "smallvec", - "thiserror", - "unsigned-varint", - "void", - "zeroize", + "core-foundation-sys", + "libc", ] [[package]] -name = "libp2p-gossipsub" -version = "0.37.0" +name = "taffy" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a90c989a7c0969c2ab63e898da9bc735e3be53fb4f376e9c045ce516bcc9f928" +checksum = "9cb893bff0f80ae17d3a57e030622a967b8dbc90e38284d9b4b1442e23873c94" dependencies = [ - "asynchronous-codec", - "base64", - "byteorder", - "bytes", - "fnv", - "futures", - "hex_fmt", - "instant", - "libp2p-core", - "libp2p-swarm", - "log", - "prometheus-client", - "prost", - "prost-build", - "rand 0.7.3", - "regex", + "arrayvec", + "grid", + "num-traits", "serde", - "sha2 0.10.6", - "smallvec", - "unsigned-varint", - "wasm-timer", + "slotmap", ] [[package]] -name = "libp2p-identify" -version = "0.35.0" +name = "termcolor" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5ef5a5b57904c7c33d6713ef918d239dc6b7553458f3475d87f8a18e9c651c8" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" dependencies = [ - "futures", - "futures-timer", - "libp2p-core", - "libp2p-swarm", - "log", - "lru", - "prost", - "prost-build", - "smallvec", + "winapi-util", ] [[package]] -name = "libp2p-kad" -version = "0.36.0" +name = "thiserror" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "564e6bd64d177446399ed835b9451a8825b07929d6daa6a94e6405592974725e" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "arrayvec", - "asynchronous-codec", - "bytes", - "either", - "fnv", - "futures", - "futures-timer", - "instant", - "libp2p-core", - "libp2p-swarm", - "log", - "prost", - "prost-build", - "rand 0.7.3", - "serde", - "sha2 0.10.6", - "smallvec", - "thiserror", - "uint", - "unsigned-varint", - "void", + "thiserror-impl 1.0.69", ] [[package]] -name = "libp2p-swarm" -version = "0.35.0" +name = "thiserror" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f0c69ad9e8f7c5fc50ad5ad9c7c8b57f33716532a2b623197f69f93e374d14c" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "either", - "fnv", - "futures", - "futures-timer", - "instant", - "libp2p-core", - "log", - "pin-project 1.0.12", - "rand 0.7.3", - "smallvec", - "thiserror", - "void", + "thiserror-impl 2.0.18", ] [[package]] -name = "libp2p-swarm-derive" -version = "0.27.2" +name = "thiserror-impl" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f693c8c68213034d472cbb93a379c63f4f307d97c06f1c41e4985de481687a5" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ + "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] -name = "link-cplusplus" -version = "1.0.7" +name = "thiserror-impl" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9272ab7b96c9046fbc5bc56c06c117cb639fe2d509df0c421cad82d2915cf369" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ - "cc", + "proc-macro2", + "quote", + "syn 2.0.117", ] [[package]] -name = "lock_api" -version = "0.4.9" +name = "thread_local" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" dependencies = [ - "autocfg", - "scopeguard", + "cfg-if", ] [[package]] -name = "log" -version = "0.4.17" +name = "time" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" dependencies = [ - "cfg-if", + "libc", + "wasi 0.10.0+wasi-snapshot-preview1", + "winapi", ] [[package]] -name = "lru" -version = "0.7.8" +name = "time" +version = "0.3.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999beba7b6e8345721bd280141ed958096a2e4abdf74f67ff4ce49b4b54e47a" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" dependencies = [ - "hashbrown", + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", ] [[package]] -name = "memchr" -version = "2.5.0" +name = "time-core" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" [[package]] -name = "multiaddr" -version = "0.14.0" +name = "time-macros" +version = "0.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c580bfdd8803cce319b047d239559a22f809094aaea4ac13902a1fdcfcd4261" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" dependencies = [ - "arrayref", - "bs58", - "byteorder", - "data-encoding", - "multihash", - "percent-encoding", - "serde", - "static_assertions", - "unsigned-varint", - "url", + "num-conv", + "time-core", ] [[package]] -name = "multihash" -version = "0.16.3" +name = "tinystr" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c346cf9999c631f002d8f977c4eaeaa0e6386f16007202308d0b3757522c2cc" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" dependencies = [ - "core2", - "digest 0.10.5", - "multihash-derive", - "serde", - "serde-big-array", - "sha2 0.10.6", - "unsigned-varint", + "displaydoc", + "zerovec", ] [[package]] -name = "multihash-derive" -version = "0.8.1" +name = "tinyvec" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d6d4752e6230d8ef7adf7bd5d8c4b1f6561c1014c5ba9a37445ccefe18aa1db" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" dependencies = [ - "proc-macro-crate", - "proc-macro-error", - "proc-macro2", - "quote", - "syn", - "synstructure", + "tinyvec_macros", ] [[package]] -name = "multimap" -version = "0.8.3" +name = "tinyvec_macros" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] -name = "multistream-select" -version = "0.11.0" +name = "tokio" +version = "1.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "363a84be6453a70e63513660f4894ef815daf88e3356bffcda9ca27d810ce83b" +checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" dependencies = [ "bytes", - "futures", - "log", - "pin-project 1.0.12", - "smallvec", - "unsigned-varint", + "libc", + "mio", + "parking_lot 0.12.5", + "pin-project-lite", + "signal-hook-registry", + "socket2 0.6.3", + "tokio-macros", + "windows-sys 0.61.2", ] [[package]] -name = "num-integer" -version = "0.1.45" +name = "tokio-macros" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c" dependencies = [ - "autocfg", - "num-traits", + "proc-macro2", + "quote", + "syn 2.0.117", ] [[package]] -name = "num-traits" -version = "0.2.15" +name = "tokio-util" +version = "0.7.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" dependencies = [ - "autocfg", + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", ] [[package]] -name = "num_cpus" -version = "1.14.0" +name = "toml" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5" +checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" dependencies = [ - "hermit-abi", - "libc", + "serde", ] [[package]] -name = "once_cell" -version = "1.16.0" +name = "toml_datetime" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" [[package]] -name = "opaque-debug" -version = "0.3.0" +name = "toml_edit" +version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap 2.13.0", + "toml_datetime", + "winnow", +] [[package]] -name = "owning_ref" -version = "0.4.1" +name = "tower-service" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ff55baddef9e4ad00f88b6c743a2a8062d4c6ade126c2a528644b8e444d52ce" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ - "stable_deref_trait", + "pin-project-lite", + "tracing-attributes", + "tracing-core", ] [[package]] -name = "parking_lot" -version = "0.11.2" +name = "tracing-attributes" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ - "instant", - "lock_api", - "parking_lot_core 0.8.5", + "proc-macro2", + "quote", + "syn 2.0.117", ] [[package]] -name = "parking_lot" -version = "0.12.1" +name = "tracing-core" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ - "lock_api", - "parking_lot_core 0.9.4", + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "tracing-wasm" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4575c663a174420fa2d78f4108ff68f65bf2fbb7dd89f33749b6e826b3626e07" +dependencies = [ + "tracing", + "tracing-subscriber", + "wasm-bindgen", ] [[package]] -name = "parking_lot_core" -version = "0.8.5" +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "ttf-parser" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31" + +[[package]] +name = "typeid" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" -dependencies = [ - "cfg-if", - "instant", - "libc", - "redox_syscall", - "smallvec", - "winapi", -] +checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" [[package]] -name = "parking_lot_core" -version = "0.9.4" +name = "typenum" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dc9e0dc2adc1c69d09143aff38d3d30c5c3f0df0dad82e6d25547af174ebec0" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-sys", -] +checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" [[package]] -name = "percent-encoding" -version = "2.2.0" +name = "typewit" +version = "1.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" +checksum = "f8c1ae7cc0fdb8b842d65d127cb981574b0d2b249b74d1c7a2986863dc134f71" [[package]] -name = "petgraph" -version = "0.6.2" +name = "uint" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6d5014253a1331579ce62aa67443b4a658c5e7dd03d4bc6d302b94474888143" +checksum = "a45526d29728d135c2900b0d30573fe3ee79fceb12ef534c7bb30e810a91b601" dependencies = [ - "fixedbitset", - "indexmap", + "byteorder", + "crunchy", + "hex", + "static_assertions", ] [[package]] -name = "pin-project" -version = "0.4.30" +name = "unicode-ident" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ef0f924a5ee7ea9cbcea77529dba45f8a9ba9f622419fe3386ca581a3ae9d5a" -dependencies = [ - "pin-project-internal 0.4.30", -] +checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" [[package]] -name = "pin-project" -version = "1.0.12" +name = "unicode-segmentation" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" -dependencies = [ - "pin-project-internal 1.0.12", -] +checksum = "0fdbf052a0783de01e944a6ce7a8cb939e295b1e7be835a1112c3b9a7f047a5a" [[package]] -name = "pin-project-internal" -version = "0.4.30" +name = "unicode-width" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "851c8d0ce9bebe43790dedfc86614c23494ac9f423dd618d3a61fc693eafe61e" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" [[package]] -name = "pin-project-internal" -version = "1.0.12" +name = "unicode-xid" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" + +[[package]] +name = "universal-hash" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" dependencies = [ - "proc-macro2", - "quote", - "syn", + "crypto-common", + "subtle", ] [[package]] -name = "pin-project-lite" -version = "0.2.9" +name = "unsigned-varint" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" +checksum = "d86a8dc7f45e4c1b0d30e43038c38f274e77af056aa5f74b93c2cf9eb3c1c836" [[package]] -name = "pin-utils" -version = "0.1.0" +name = "unsigned-varint" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +checksum = "eb066959b24b5196ae73cb057f45598450d2c5f71460e98c49b738086eff9c06" [[package]] -name = "pinned" -version = "0.1.0" +name = "untrusted" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a829027bd95e54cfe13e3e258a1ae7b645960553fb82b75ff852c29688ee595b" -dependencies = [ - "futures", - "rustversion", - "thiserror", -] +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" [[package]] -name = "ppv-lite86" -version = "0.2.17" +name = "untrusted" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] -name = "prettyplease" -version = "0.1.21" +name = "url" +version = "2.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c142c0e46b57171fe0c528bee8c5b7569e80f0c17e377cd0e30ea57dbc11bb51" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" dependencies = [ - "proc-macro2", - "syn", + "form_urlencoded", + "idna", + "percent-encoding", + "serde", ] [[package]] -name = "proc-macro-crate" -version = "1.1.3" +name = "utf8_iter" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e17d47ce914bf4de440332250b0edd23ce48c005f59fab39d3335866b114f11a" -dependencies = [ - "thiserror", - "toml", -] +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] -name = "proc-macro-error" -version = "1.0.4" +name = "uuid" +version = "1.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +checksum = "a68d3c8f01c0cfa54a75291d83601161799e4a89a39e0929f4b0354d88757a37" dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn", - "version_check", + "getrandom 0.4.2", + "js-sys", + "serde_core", + "wasm-bindgen", ] [[package]] -name = "proc-macro-error-attr" -version = "1.0.4" +name = "valuable" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", -] +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" [[package]] -name = "proc-macro2" -version = "1.0.47" +name = "version_check" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" -dependencies = [ - "unicode-ident", -] +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] -name = "prokio" -version = "0.1.0" +name = "void" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03b55e106e5791fa5a13abd13c85d6127312e8e09098059ca2bc9b03ca4cf488" -dependencies = [ - "futures", - "gloo", - "num_cpus", - "once_cell", - "pin-project 1.0.12", - "pinned", - "tokio", - "tokio-stream", - "wasm-bindgen-futures", -] +checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" [[package]] -name = "prometheus-client" -version = "0.15.1" +name = "walkdir" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9a896938cc6018c64f279888b8c7559d3725210d5db9a3a1ee6bc7188d51d34" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" dependencies = [ - "dtoa", - "itoa", - "owning_ref", - "prometheus-client-derive-text-encode", + "same-file", + "winapi-util", ] [[package]] -name = "prometheus-client-derive-text-encode" -version = "0.2.0" +name = "want" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8e12d01b9d66ad9eb4529c57666b6263fc1993cb30261d83ead658fdd932652" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" dependencies = [ - "proc-macro2", - "quote", - "syn", + "try-lock", ] [[package]] -name = "prost" -version = "0.9.0" +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "444879275cb4fd84958b1a1d5420d15e6fcf7c235fe47f053c9c2a80aceb6001" -dependencies = [ - "bytes", - "prost-derive", -] +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" [[package]] -name = "prost-build" -version = "0.9.0" +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62941722fb675d463659e49c4f3fe1fe792ff24fe5bbaa9c08cd3b98a1c354f5" -dependencies = [ - "bytes", - "heck", - "itertools", - "lazy_static", - "log", - "multimap", - "petgraph", - "prost", - "prost-types", - "regex", - "tempfile", - "which", -] +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] -name = "prost-derive" -version = "0.9.0" +name = "wasip2" +version = "1.0.2+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9cc1a3263e07e0bf68e96268f37665207b49560d98739662cdfaae215c720fe" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" dependencies = [ - "anyhow", - "itertools", - "proc-macro2", - "quote", - "syn", + "wit-bindgen", ] [[package]] -name = "prost-types" -version = "0.9.0" +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534b7a0e836e3c482d2693070f982e39e7611da9695d4d1f5a4b186b51faef0a" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" dependencies = [ - "bytes", - "prost", + "wit-bindgen", ] [[package]] -name = "quote" -version = "1.0.21" +name = "wasm-bindgen" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" +checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e" dependencies = [ - "proc-macro2", + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", ] [[package]] -name = "rand" -version = "0.7.3" +name = "wasm-bindgen-futures" +version = "0.4.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +checksum = "e9c5522b3a28661442748e09d40924dfb9ca614b21c00d3fd135720e48b67db8" dependencies = [ - "getrandom 0.1.16", - "libc", - "rand_chacha 0.2.2", - "rand_core 0.5.1", - "rand_hc", + "cfg-if", + "futures-util", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", ] [[package]] -name = "rand" -version = "0.8.5" +name = "wasm-bindgen-macro" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6" dependencies = [ - "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.4", + "quote", + "wasm-bindgen-macro-support", ] [[package]] -name = "rand_chacha" -version = "0.2.2" +name = "wasm-bindgen-macro-support" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3" dependencies = [ - "ppv-lite86", - "rand_core 0.5.1", + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.117", + "wasm-bindgen-shared", ] [[package]] -name = "rand_chacha" -version = "0.3.1" +name = "wasm-bindgen-shared" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16" dependencies = [ - "ppv-lite86", - "rand_core 0.6.4", + "unicode-ident", ] [[package]] -name = "rand_core" -version = "0.5.1" +name = "wasm-encoder" +version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" dependencies = [ - "getrandom 0.1.16", + "leb128fmt", + "wasmparser", ] [[package]] -name = "rand_core" -version = "0.6.4" +name = "wasm-metadata" +version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" dependencies = [ - "getrandom 0.2.8", + "anyhow", + "indexmap 2.13.0", + "wasm-encoder", + "wasmparser", ] [[package]] -name = "rand_hc" -version = "0.2.0" +name = "wasmparser" +version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ - "rand_core 0.5.1", + "bitflags 2.11.0", + "hashbrown 0.15.5", + "indexmap 2.13.0", + "semver", ] [[package]] -name = "redox_syscall" -version = "0.2.16" +name = "web-sys" +version = "0.3.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +checksum = "854ba17bb104abfb26ba36da9729addc7ce7f06f5c0f90f3c391f8461cca21f9" dependencies = [ - "bitflags", + "js-sys", + "wasm-bindgen", ] [[package]] -name = "regex" -version = "1.7.0" +name = "web-time" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", + "js-sys", + "wasm-bindgen", ] [[package]] -name = "regex-syntax" -version = "0.6.28" +name = "wgpu" +version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" +checksum = "90e37c7b9921b75dfd26dd973fdcbce36f13dfa6e2dc82aece584e0ed48c355c" +dependencies = [ + "arrayvec", + "cfg-if", + "cfg_aliases 0.1.1", + "document-features", + "js-sys", + "log", + "naga", + "parking_lot 0.11.2", + "profiling", + "raw-window-handle", + "smallvec", + "static_assertions", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "wgpu-core", + "wgpu-hal", + "wgpu-types", +] [[package]] -name = "remove_dir_all" -version = "0.5.3" +name = "wgpu-core" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +checksum = "d50819ab545b867d8a454d1d756b90cd5f15da1f2943334ca314af10583c9d39" dependencies = [ - "winapi", + "arrayvec", + "bit-vec", + "bitflags 2.11.0", + "cfg_aliases 0.1.1", + "codespan-reporting", + "document-features", + "indexmap 2.13.0", + "log", + "naga", + "once_cell", + "parking_lot 0.11.2", + "profiling", + "raw-window-handle", + "rustc-hash", + "smallvec", + "thiserror 1.0.69", + "web-sys", + "wgpu-hal", + "wgpu-types", ] [[package]] -name = "ring" -version = "0.16.20" +name = "wgpu-hal" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +checksum = "172e490a87295564f3fcc0f165798d87386f6231b04d4548bca458cbbfd63222" dependencies = [ - "cc", + "android_system_properties", + "arrayvec", + "ash", + "bit-set", + "bitflags 2.11.0", + "block", + "cfg_aliases 0.1.1", + "core-graphics-types", + "d3d12", + "glow", + "glutin_wgl_sys", + "gpu-alloc", + "gpu-allocator", + "gpu-descriptor", + "hassle-rs", + "js-sys", + "khronos-egl", "libc", + "libloading 0.8.9", + "log", + "metal", + "naga", + "ndk-sys 0.5.0+25.2.9519653", + "objc", "once_cell", - "spin", - "untrusted", + "parking_lot 0.11.2", + "profiling", + "range-alloc", + "raw-window-handle", + "renderdoc-sys", + "rustc-hash", + "smallvec", + "thiserror 1.0.69", + "wasm-bindgen", "web-sys", + "wgpu-types", "winapi", ] [[package]] -name = "rustversion" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97477e48b4cf8603ad5f7aaf897467cf42ab4218a38ef76fb14c2d6773a6d6a8" - -[[package]] -name = "rw-stream-sink" -version = "0.2.1" +name = "wgpu-types" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4da5fcb054c46f5a5dff833b129285a93d3f0179531735e6c866e8cc307d2020" +checksum = "1353d9a46bff7f955a680577f34c69122628cc2076e1d6f3a9be6ef00ae793ef" dependencies = [ - "futures", - "pin-project 0.4.30", - "static_assertions", + "bitflags 2.11.0", + "js-sys", + "web-sys", ] [[package]] -name = "ryu" -version = "1.0.11" +name = "widestring" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" +checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471" [[package]] -name = "scopeguard" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +name = "willow-app" +version = "0.1.0" +dependencies = [ + "anyhow", + "bevy", + "serde", + "tokio", + "tracing", + "tracing-subscriber", + "willow-channel", + "willow-identity", + "willow-messaging", + "willow-network", + "willow-transport", +] [[package]] -name = "scratch" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898" +name = "willow-channel" +version = "0.1.0" +dependencies = [ + "chrono", + "serde", + "thiserror 1.0.69", + "tokio", + "uuid", + "willow-identity", + "willow-transport", +] [[package]] -name = "send_wrapper" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f638d531eccd6e23b980caf34876660d38e265409d8e99b397ab71eb3612fad0" +name = "willow-identity" +version = "0.1.0" +dependencies = [ + "chrono", + "libp2p", + "serde", + "thiserror 1.0.69", + "tokio", + "willow-transport", +] [[package]] -name = "serde" -version = "1.0.147" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965" +name = "willow-messaging" +version = "0.1.0" dependencies = [ - "serde_derive", + "chrono", + "serde", + "thiserror 1.0.69", + "tokio", + "uuid", + "willow-identity", + "willow-transport", ] [[package]] -name = "serde-big-array" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd31f59f6fe2b0c055371bb2f16d7f0aa7d8881676c04a55b1596d1a17cd10a4" +name = "willow-network" +version = "0.1.0" dependencies = [ + "anyhow", + "libp2p", "serde", + "thiserror 1.0.69", + "tokio", + "tracing", + "tracing-subscriber", + "willow-identity", + "willow-transport", ] [[package]] -name = "serde-wasm-bindgen" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "618365e8e586c22123d692b72a7d791d5ee697817b65a218cdf12a98870af0f7" +name = "willow-transport" +version = "0.1.0" dependencies = [ - "fnv", - "js-sys", + "anyhow", + "bincode", "serde", - "wasm-bindgen", + "thiserror 1.0.69", + "tokio", ] [[package]] -name = "serde_derive" -version = "1.0.147" +name = "winapi" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f1d362ca8fc9c3e3a7484440752472d68a6caa98f1ab81d99b5dfe517cec852" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ - "proc-macro2", - "quote", - "syn", + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", ] [[package]] -name = "serde_json" -version = "1.0.87" +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce777b7b150d76b9cf60d28b55f5847135a003f7d7350c6be7a773508ce7d45" -dependencies = [ - "itoa", - "ryu", - "serde", -] +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] -name = "serde_urlencoded" -version = "0.7.1" +name = "winapi-util" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde", + "winapi", ] [[package]] -name = "sha2" -version = "0.9.9" +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" -dependencies = [ - "block-buffer 0.9.0", - "cfg-if", - "cpufeatures", - "digest 0.9.0", - "opaque-debug", -] +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] -name = "sha2" -version = "0.10.6" +name = "windows" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" +checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" dependencies = [ - "cfg-if", - "cpufeatures", - "digest 0.10.5", + "windows-core 0.52.0", + "windows-targets 0.52.6", ] [[package]] -name = "signature" -version = "1.6.4" +name = "windows" +version = "0.54.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" +checksum = "9252e5725dbed82865af151df558e754e4a3c2c30818359eb17465f1346a1b49" +dependencies = [ + "windows-core 0.54.0", + "windows-implement 0.53.0", + "windows-interface 0.53.0", + "windows-targets 0.52.6", +] [[package]] -name = "slab" -version = "0.4.7" +name = "windows" +version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" dependencies = [ - "autocfg", + "windows-collections", + "windows-core 0.62.2", + "windows-future", + "windows-numerics", ] [[package]] -name = "smallvec" -version = "1.10.0" +name = "windows-collections" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" +checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" +dependencies = [ + "windows-core 0.62.2", +] [[package]] -name = "spin" -version = "0.5.2" +name = "windows-core" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.6", +] [[package]] -name = "stable_deref_trait" -version = "1.2.0" +name = "windows-core" +version = "0.54.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +checksum = "12661b9c89351d684a50a8a643ce5f608e20243b9fb84687800163429f161d65" +dependencies = [ + "windows-result 0.1.2", + "windows-targets 0.52.6", +] [[package]] -name = "static_assertions" -version = "1.1.0" +name = "windows-core" +version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement 0.60.2", + "windows-interface 0.59.3", + "windows-link", + "windows-result 0.4.1", + "windows-strings", +] [[package]] -name = "strsim" -version = "0.10.0" +name = "windows-future" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" +dependencies = [ + "windows-core 0.62.2", + "windows-link", + "windows-threading", +] [[package]] -name = "subtle" -version = "2.4.1" +name = "windows-implement" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" +checksum = "942ac266be9249c84ca862f0a164a39533dc2f6f33dc98ec89c8da99b82ea0bd" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] [[package]] -name = "syn" -version = "1.0.103" +name = "windows-implement" +version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "unicode-ident", + "syn 2.0.117", ] [[package]] -name = "synstructure" -version = "0.12.6" +name = "windows-interface" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" +checksum = "da33557140a288fae4e1d5f8873aaf9eb6613a9cf82c3e070223ff177f598b60" dependencies = [ "proc-macro2", "quote", - "syn", - "unicode-xid", + "syn 2.0.117", ] [[package]] -name = "tempfile" -version = "3.3.0" +name = "windows-interface" +version = "0.59.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ - "cfg-if", - "fastrand", - "libc", - "redox_syscall", - "remove_dir_all", - "winapi", + "proc-macro2", + "quote", + "syn 2.0.117", ] [[package]] -name = "termcolor" -version = "1.1.3" +name = "windows-link" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" -dependencies = [ - "winapi-util", -] +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] -name = "thiserror" -version = "1.0.37" +name = "windows-numerics" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" +checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" dependencies = [ - "thiserror-impl", + "windows-core 0.62.2", + "windows-link", ] [[package]] -name = "thiserror-impl" -version = "1.0.37" +name = "windows-result" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" +checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" dependencies = [ - "proc-macro2", - "quote", - "syn", + "windows-targets 0.52.6", ] [[package]] -name = "time" -version = "0.1.44" +name = "windows-result" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" dependencies = [ - "libc", - "wasi 0.10.0+wasi-snapshot-preview1", - "winapi", + "windows-link", ] [[package]] -name = "timeline" -version = "0.1.0" +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" dependencies = [ - "chrono", - "identity", - "uuid", - "yew", - "yewdux", - "yewdux-input", + "windows-link", ] [[package]] -name = "tinyvec" -version = "1.6.0" +name = "windows-sys" +version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" dependencies = [ - "tinyvec_macros", + "windows-targets 0.42.2", ] [[package]] -name = "tinyvec_macros" -version = "0.1.0" +name = "windows-sys" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] [[package]] -name = "tokio" -version = "1.21.2" +name = "windows-sys" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9e03c497dc955702ba729190dc4aac6f2a0ce97f913e5b1b5912fc5039d9099" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "autocfg", - "pin-project-lite", + "windows-targets 0.52.6", ] [[package]] -name = "tokio-stream" -version = "0.1.11" +name = "windows-sys" +version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d660770404473ccd7bc9f8b28494a811bc18542b915c0855c51e8f419d5223ce" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "futures-core", - "pin-project-lite", - "tokio", + "windows-targets 0.52.6", ] [[package]] -name = "toml" -version = "0.5.9" +name = "windows-sys" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "serde", + "windows-link", ] [[package]] -name = "tracing" -version = "0.1.37" +name = "windows-targets" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" dependencies = [ - "cfg-if", - "pin-project-lite", - "tracing-attributes", - "tracing-core", + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", ] [[package]] -name = "tracing-attributes" -version = "0.1.23" +name = "windows-targets" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ - "proc-macro2", - "quote", - "syn", + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", ] [[package]] -name = "tracing-core" -version = "0.1.30" +name = "windows-targets" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "once_cell", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] [[package]] -name = "transport" -version = "0.1.0" +name = "windows-threading" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" dependencies = [ - "anyhow", - "bincode", - "serde", + "windows-link", ] [[package]] -name = "typenum" -version = "1.15.0" +name = "windows_aarch64_gnullvm" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" [[package]] -name = "uint" -version = "0.9.4" +name = "windows_aarch64_gnullvm" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a45526d29728d135c2900b0d30573fe3ee79fceb12ef534c7bb30e810a91b601" -dependencies = [ - "byteorder", - "crunchy", - "hex", - "static_assertions", -] +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] -name = "unicode-bidi" -version = "0.3.8" +name = "windows_aarch64_msvc" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] -name = "unicode-ident" -version = "1.0.5" +name = "windows_i686_gnu" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" [[package]] -name = "unicode-normalization" -version = "0.1.22" +name = "windows_i686_gnu" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" -dependencies = [ - "tinyvec", -] +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] -name = "unicode-segmentation" -version = "1.10.0" +name = "windows_i686_gnu" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fdbf052a0783de01e944a6ce7a8cb939e295b1e7be835a1112c3b9a7f047a5a" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] -name = "unicode-width" -version = "0.1.10" +name = "windows_i686_gnullvm" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] -name = "unicode-xid" -version = "0.2.4" +name = "windows_i686_msvc" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" [[package]] -name = "unsigned-varint" -version = "0.7.1" +name = "windows_i686_msvc" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d86a8dc7f45e4c1b0d30e43038c38f274e77af056aa5f74b93c2cf9eb3c1c836" -dependencies = [ - "asynchronous-codec", - "bytes", -] +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] -name = "untrusted" -version = "0.7.1" +name = "windows_i686_msvc" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] -name = "url" -version = "2.3.1" +name = "windows_x86_64_gnu" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", -] +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" [[package]] -name = "uuid" -version = "1.2.1" +name = "windows_x86_64_gnu" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "feb41e78f93363bb2df8b0e86a2ca30eed7806ea16ea0c790d757cf93f79be83" -dependencies = [ - "getrandom 0.2.8", -] +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] -name = "version_check" -version = "0.9.4" +name = "windows_x86_64_gnu" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] -name = "void" -version = "1.0.2" +name = "windows_x86_64_gnullvm" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" [[package]] -name = "wasi" -version = "0.9.0+wasi-snapshot-preview1" +name = "windows_x86_64_gnullvm" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] -name = "wasi" -version = "0.10.0+wasi-snapshot-preview1" +name = "windows_x86_64_gnullvm" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +name = "windows_x86_64_msvc" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" [[package]] -name = "wasm-bindgen" -version = "0.2.83" +name = "windows_x86_64_msvc" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" -dependencies = [ - "cfg-if", - "serde", - "serde_json", - "wasm-bindgen-macro", -] +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] -name = "wasm-bindgen-backend" -version = "0.2.83" +name = "windows_x86_64_msvc" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" -dependencies = [ - "bumpalo", - "log", - "once_cell", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] -name = "wasm-bindgen-futures" -version = "0.4.33" +name = "winit" +version = "0.30.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23639446165ca5a5de86ae1d8896b737ae80319560fbaa4c2887b7da6e7ebd7d" +checksum = "a6755fa58a9f8350bd1e472d4c3fcc25f824ec358933bba33306d0b63df5978d" dependencies = [ - "cfg-if", + "android-activity", + "atomic-waker", + "bitflags 2.11.0", + "block2", + "calloop", + "cfg_aliases 0.2.1", + "concurrent-queue", + "core-foundation", + "core-graphics", + "cursor-icon", + "dpi", "js-sys", + "libc", + "ndk", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "objc2-ui-kit", + "orbclient", + "pin-project", + "raw-window-handle", + "redox_syscall 0.4.1", + "rustix 0.38.44", + "smol_str", + "tracing", + "unicode-segmentation", "wasm-bindgen", + "wasm-bindgen-futures", "web-sys", + "web-time", + "windows-sys 0.52.0", + "xkbcommon-dl", ] [[package]] -name = "wasm-bindgen-macro" -version = "0.2.83" +name = "winnow" +version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" dependencies = [ - "quote", - "wasm-bindgen-macro-support", + "memchr", ] [[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.83" +name = "winreg" +version = "0.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" dependencies = [ - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-backend", - "wasm-bindgen-shared", + "cfg-if", + "windows-sys 0.48.0", ] [[package]] -name = "wasm-bindgen-shared" -version = "0.2.83" +name = "wit-bindgen" +version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] [[package]] -name = "wasm-timer" -version = "0.2.5" +name = "wit-bindgen-core" +version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be0ecb0db480561e9a7642b5d3e4187c128914e58aa84330b9493e3eb68c5e7f" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" dependencies = [ - "futures", - "js-sys", - "parking_lot 0.11.2", - "pin-utils", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", + "anyhow", + "heck 0.5.0", + "wit-parser", ] [[package]] -name = "web" -version = "0.1.0" +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" dependencies = [ - "yew", - "yewdux", - "yewdux-input", + "anyhow", + "heck 0.5.0", + "indexmap 2.13.0", + "prettyplease", + "syn 2.0.117", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", ] [[package]] -name = "web-sys" -version = "0.3.60" +name = "wit-bindgen-rust-macro" +version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" dependencies = [ - "js-sys", - "wasm-bindgen", + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn 2.0.117", + "wit-bindgen-core", + "wit-bindgen-rust", ] [[package]] -name = "which" -version = "4.3.0" +name = "wit-component" +version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c831fbbee9e129a8cf93e7747a82da9d95ba8e16621cae60ec2cdc849bacb7b" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" dependencies = [ - "either", - "libc", - "once_cell", + "anyhow", + "bitflags 2.11.0", + "indexmap 2.13.0", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", ] [[package]] -name = "winapi" -version = "0.3.9" +name = "wit-parser" +version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", + "anyhow", + "id-arena", + "indexmap 2.13.0", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", ] [[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" +name = "writeable" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" [[package]] -name = "winapi-util" -version = "0.1.5" +name = "x25519-dalek" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" dependencies = [ - "winapi", + "curve25519-dalek", + "rand_core 0.6.4", + "serde", + "zeroize", ] [[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" +name = "x509-parser" +version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +checksum = "7069fba5b66b9193bd2c5d3d4ff12b839118f6bcbef5328efafafb5395cf63da" +dependencies = [ + "asn1-rs", + "data-encoding", + "der-parser", + "lazy_static", + "nom", + "oid-registry", + "rusticata-macros", + "thiserror 1.0.69", + "time 0.3.47", +] [[package]] -name = "windows-sys" -version = "0.42.0" +name = "xi-unicode" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" -dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", -] +checksum = "a67300977d3dc3f8034dae89778f502b6ba20b269527b3223ba59c0cf393bb8a" [[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.0" +name = "xkbcommon-dl" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" +checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5" +dependencies = [ + "bitflags 2.11.0", + "dlib", + "log", + "once_cell", + "xkeysym", +] [[package]] -name = "windows_aarch64_msvc" -version = "0.42.0" +name = "xkeysym" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" +checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" [[package]] -name = "windows_i686_gnu" -version = "0.42.0" +name = "xml-rs" +version = "0.8.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" +checksum = "3ae8337f8a065cfc972643663ea4279e04e7256de865aa66fe25cec5fb912d3f" [[package]] -name = "windows_i686_msvc" -version = "0.42.0" +name = "xmltree" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" +checksum = "d7d8a75eaf6557bb84a65ace8609883db44a29951042ada9b393151532e41fcb" +dependencies = [ + "xml-rs", +] [[package]] -name = "windows_x86_64_gnu" -version = "0.42.0" +name = "yamux" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" +checksum = "9ed0164ae619f2dc144909a9f082187ebb5893693d8c0196e8085283ccd4b776" +dependencies = [ + "futures", + "log", + "nohash-hasher", + "parking_lot 0.12.5", + "pin-project", + "rand 0.8.5", + "static_assertions", +] [[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.0" +name = "yamux" +version = "0.13.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" +checksum = "1991f6690292030e31b0144d73f5e8368936c58e45e7068254f7138b23b00672" +dependencies = [ + "futures", + "log", + "nohash-hasher", + "parking_lot 0.12.5", + "pin-project", + "rand 0.9.2", + "static_assertions", + "web-time", +] [[package]] -name = "windows_x86_64_msvc" -version = "0.42.0" +name = "yasna" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" +checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" +dependencies = [ + "time 0.3.47", +] [[package]] -name = "yew" -version = "0.19.3" -source = "git+https://github.com/yewstack/yew.git#812c65c54cdd8f6dfaca490361400d4c3d1cbecb" +name = "yoke" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" dependencies = [ - "console_error_panic_hook", - "futures", - "gloo", - "implicit-clone", - "indexmap", - "js-sys", - "prokio", - "rustversion", - "serde", - "slab", - "thiserror", - "tokio", - "tracing", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "yew-macro", + "stable_deref_trait", + "yoke-derive", + "zerofrom", ] [[package]] -name = "yew-macro" -version = "0.19.3" -source = "git+https://github.com/yewstack/yew.git#812c65c54cdd8f6dfaca490361400d4c3d1cbecb" +name = "yoke-derive" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ - "boolinator", - "once_cell", - "prettyplease", - "proc-macro-error", "proc-macro2", "quote", - "syn", + "syn 2.0.117", + "synstructure 0.13.2", ] [[package]] -name = "yewdux" -version = "0.8.2" -source = "git+https://github.com/intendednull/yewdux.git#31e5ba8d309ddbe7832106638e79035ad63289cb" +name = "zerocopy" +version = "0.8.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efbb2a062be311f2ba113ce66f697a4dc589f85e78a4aea276200804cea0ed87" dependencies = [ - "anymap", - "async-trait", - "log", - "serde", - "serde_json", - "slab", - "thiserror", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "yew", - "yewdux-macros", + "zerocopy-derive", ] [[package]] -name = "yewdux-input" -version = "0.1.0" -source = "git+https://github.com/intendednull/yewdux.git#31e5ba8d309ddbe7832106638e79035ad63289cb" +name = "zerocopy-derive" +version = "0.8.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e8bc7269b54418e7aeeef514aa68f8690b8c0489a06b0136e5f57c4c5ccab89" dependencies = [ - "chrono", - "wasm-bindgen", - "web-sys", - "yew", - "yewdux", + "proc-macro2", + "quote", + "syn 2.0.117", ] [[package]] -name = "yewdux-macros" -version = "0.8.2" -source = "git+https://github.com/intendednull/yewdux.git#31e5ba8d309ddbe7832106638e79035ad63289cb" +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ - "darling", - "proc-macro-error", "proc-macro2", "quote", - "syn", + "syn 2.0.117", + "synstructure 0.13.2", ] [[package]] name = "zeroize" -version = "1.3.0" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4756f7db3f7b5574938c3eb1c117038b8e07f95ee6718c0efad4ac21508f1efd" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" dependencies = [ "zeroize_derive", ] @@ -2419,6 +6597,39 @@ checksum = "3f8f187641dad4f680d25c4bfc4225b418165984179f26ca76ec4fb6441d3a17" dependencies = [ "proc-macro2", "quote", - "syn", - "synstructure", + "syn 1.0.103", + "synstructure 0.12.6", +] + +[[package]] +name = "zerotrie" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", ] diff --git a/Cargo.toml b/Cargo.toml index fa10a8c7..bbda4a6c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,60 @@ [workspace] members = ["crates/*"] +resolver = "2" -# [patch."https://github.com/intendednull/yewdux.git"] -# yewdux = { path = "../yewdux/crates/yewdux" } +[workspace.package] +edition = "2021" +version = "0.1.0" +license = "MIT" + +[workspace.dependencies] +# Serialization +serde = { version = "1", features = ["derive", "rc"] } +bincode = "1.3" + +# Error handling +anyhow = "1" +thiserror = "1" + +# Async runtime +tokio = { version = "1", features = ["full"] } + +# Crypto & Networking +libp2p = { version = "0.53", features = [ + "tokio", + "gossipsub", + "kad", + "mdns", + "noise", + "yamux", + "tcp", + "dns", + "relay", + "identify", + "macros", + "ed25519", + "serde", +] } + +# Time & IDs +chrono = { version = "0.4", features = ["serde"] } +uuid = { version = "1", features = ["v4", "serde"] } + +# Game engine / UI +# NOTE: On a full desktop, re-enable "x11", "wayland", and "bevy_winit". +# These are disabled here because CI/headless environments lack the system +# libraries (libwayland-dev, libxkbcommon-dev, etc.). +bevy = { version = "0.14", default-features = false, features = [ + "bevy_asset", + "bevy_core_pipeline", + "bevy_render", + "bevy_sprite", + "bevy_text", + "bevy_ui", + "default_font", + "multi_threaded", +] } + +# Logging +tracing = "0.1" +tracing-subscriber = { version = "0.3", features = ["env-filter"] } diff --git a/PLAN.md b/PLAN.md new file mode 100644 index 00000000..d1b276c1 --- /dev/null +++ b/PLAN.md @@ -0,0 +1,115 @@ +# Willow — P2P Chat Platform + +A decentralized Discord replacement for friend groups. No central servers, no +accounts, no middlemen. Built with Rust, libp2p, and Bevy. + +## Vision + +A native desktop app where you and your friends can: +- Text chat with channels, threads, reactions, and emoji +- Voice call, video call, and screen share +- Share files peer-to-peer +- Create servers and channels with role-based permissions +- Discover each other on the local network or via bootstrap nodes +- Own your data — everything is end-to-end encrypted + +## Architecture + +``` +┌─────────────────────────────────────────────────┐ +│ Bevy App (willow-app) │ +│ Sidebar │ Chat │ Voice/Video │ Files │ Emoji │ +├─────────────────────────────────────────────────┤ +│ Application Layer │ +│ willow-channel │ willow-messaging │ +├─────────────────────────────────────────────────┤ +│ Media Layer (future) │ +│ WebRTC voice/video │ File chunking │ +├─────────────────────────────────────────────────┤ +│ Network Layer │ +│ willow-network (libp2p + tokio) │ +│ GossipSub │ Kademlia │ mDNS │ Relay │ Noise │ +├─────────────────────────────────────────────────┤ +│ Foundation │ +│ willow-identity │ willow-transport │ +│ Ed25519 signing │ Protocol framing │ +└─────────────────────────────────────────────────┘ +``` + +## Crates + +| Crate | Purpose | Status | +|-------|---------|--------| +| `willow-transport` | Binary serialization, protocol versioning, message framing | ✅ Done | +| `willow-identity` | Ed25519 keypairs, signed messages, user profiles | ✅ Done | +| `willow-messaging` | Chat messages, HLC ordering, message store | ✅ Done | +| `willow-channel` | Servers, channels, roles, permissions, invites | ✅ Done | +| `willow-network` | libp2p networking (GossipSub, Kademlia, mDNS, Relay) | ✅ Done | +| `willow-app` | Bevy-based native desktop UI | ✅ Scaffold done | + +## Roadmap + +### Phase 1 — Foundation (COMPLETE) +- [x] Transport layer with versioned envelopes +- [x] Cryptographic identity with Ed25519 signing +- [x] Message model with Hybrid Logical Clock ordering +- [x] Channel/server/role/permission model +- [x] libp2p networking with GossipSub, Kademlia, mDNS +- [x] Bevy app scaffold with sidebar + chat layout + +### Phase 2 — Working Chat +- [ ] Wire up network ↔ messaging: publish/receive messages over GossipSub +- [ ] Text input in Bevy UI (keyboard capture, text editing) +- [ ] Message rendering (scrollable list, timestamps, author names) +- [ ] Channel switching in the sidebar +- [ ] Server creation and invite flow in the UI +- [ ] Persist messages to disk (SQLite or sled) +- [ ] Profile editing (display name, avatar) + +### Phase 3 — File Sharing +- [ ] `willow-files` crate — content-addressed chunked file storage +- [ ] File transfer protocol over libp2p streams +- [ ] Drag-and-drop file upload in Bevy +- [ ] Inline image/video previews +- [ ] Peers seed files they've downloaded (BitTorrent-style) + +### Phase 4 — Voice & Video +- [ ] `willow-media` crate — WebRTC-like media transport +- [ ] Voice channels with Opus audio +- [ ] Video with VP8/VP9 +- [ ] Screen sharing +- [ ] Mesh topology for small groups (≤8 peers) +- [ ] Push-to-talk and voice activity detection + +### Phase 5 — Polish +- [ ] Custom emoji (shared as files, referenced by shortcode) +- [ ] Emoji reactions on messages +- [ ] Message search +- [ ] Notification system +- [ ] Themes and UI customization +- [ ] End-to-end encryption with per-channel group keys +- [ ] Optional relay node for offline message delivery + +## Key Technical Decisions + +| Decision | Choice | Rationale | +|----------|--------|-----------| +| Language | Rust | Memory safety, performance, async ecosystem | +| UI Framework | Bevy | GPU-accelerated, ECS state management, native + WASM | +| Networking | libp2p | Battle-tested P2P stack, NAT traversal, encryption | +| Async Runtime | tokio | Standard for Rust async, full-featured | +| Message Ordering | Hybrid Logical Clocks | Consistent ordering without central coordination | +| State Sync | CRDTs (future) | Convergent state across disconnected peers | +| Serialization | bincode | Fast, compact binary format | +| Crypto | Ed25519 | Fast signing, small keys, well-audited | + +## Honest Tradeoffs + +- **NAT traversal is hard** — most home networks need relay nodes. Budget for + one $5/mo VPS running a Willow relay. +- **Offline delivery** — pure P2P means no one receives your message if they're + offline. The relay node doubles as a mailbox. +- **Group calls** — mesh topology works for ~5-8 people. Larger groups would + need an SFU, which reintroduces a server. +- **Bevy UI** — powerful but immature for traditional GUI. Text input, scroll + views, and accessibility need custom work. diff --git a/crates/app/Cargo.toml b/crates/app/Cargo.toml new file mode 100644 index 00000000..2218da70 --- /dev/null +++ b/crates/app/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "willow-app" +edition.workspace = true +version.workspace = true +license.workspace = true +description = "Bevy-based desktop UI for the Willow P2P chat application" + +[dependencies] +willow-identity = { path = "../identity" } +willow-messaging = { path = "../messaging" } +willow-channel = { path = "../channel" } +willow-network = { path = "../network" } +willow-transport = { path = "../transport" } + +bevy = { workspace = true } +tokio = { workspace = true } +anyhow = { workspace = true } +serde = { workspace = true } +tracing = { workspace = true } +tracing-subscriber = { workspace = true } diff --git a/crates/app/src/main.rs b/crates/app/src/main.rs new file mode 100644 index 00000000..308aa817 --- /dev/null +++ b/crates/app/src/main.rs @@ -0,0 +1,35 @@ +//! # Willow App +//! +//! A Bevy-powered desktop client for the Willow P2P chat network. +//! +//! ## Architecture +//! +//! The app uses Bevy's ECS (Entity Component System) to manage all UI state. +//! Network I/O runs on a separate tokio runtime and communicates with the Bevy +//! world through event channels. +//! +//! ### Plugin structure +//! +//! - [`NetworkPlugin`] — bridges the tokio networking layer to Bevy events. +//! - [`ChatPlugin`] — manages message state, input, and chat rendering. +//! - [`UiPlugin`] — top-level layout: sidebar, channel list, message area. + +mod network_bridge; +mod ui; + +use bevy::prelude::*; + +fn main() { + App::new() + .add_plugins(DefaultPlugins.set(WindowPlugin { + primary_window: Some(Window { + title: "Willow".to_string(), + resolution: (1280.0, 720.0).into(), + ..default() + }), + ..default() + })) + .add_plugins(network_bridge::NetworkPlugin) + .add_plugins(ui::UiPlugin) + .run(); +} diff --git a/crates/app/src/network_bridge.rs b/crates/app/src/network_bridge.rs new file mode 100644 index 00000000..cbe7b5e0 --- /dev/null +++ b/crates/app/src/network_bridge.rs @@ -0,0 +1,156 @@ +//! # Network Bridge +//! +//! Bridges the async tokio-based [`willow_network`] layer into Bevy's +//! synchronous ECS world. +//! +//! ## How it works +//! +//! 1. On startup, we spawn a tokio runtime on a background thread and start a +//! [`NetworkNode`](willow_network::NetworkNode). +//! 2. Network events flow from the node into a crossbeam-style receiver that a +//! Bevy system polls each frame. +//! 3. Outbound commands (publish, subscribe) go from Bevy systems to the node +//! handle, which is stored as a Bevy resource. + +use bevy::prelude::*; +use std::sync::{mpsc as std_mpsc, Arc, Mutex}; + +use willow_identity::Identity; + +/// Bevy resource holding the local user's identity. +#[derive(Resource)] +pub struct LocalIdentity(pub Identity); + +/// Bevy resource for receiving network events on the main thread. +/// +/// Wrapped in `Arc>` to satisfy Bevy's `Send + Sync` requirement on +/// resources. The mutex is only locked briefly each frame to drain events. +#[derive(Resource, Clone)] +pub struct NetworkEventReceiver(pub Arc>>); + +/// Bevy resource for sending commands to the network task. +#[derive(Resource, Clone)] +pub struct NetworkCommandSender(pub std_mpsc::Sender); + +/// Events flowing from the network into Bevy. +#[derive(Debug, Clone, Event)] +pub enum NetworkBridgeEvent { + /// A chat message arrived on a topic. + MessageReceived { + topic: String, + data: Vec, + source: Option, + }, + /// A peer connected. + PeerConnected(String), + /// A peer disconnected. + PeerDisconnected(String), + /// We are now listening on an address. + Listening(String), +} + +/// Commands flowing from Bevy to the network. +#[derive(Debug, Clone)] +pub enum NetworkBridgeCommand { + /// Subscribe to a gossipsub topic. + Subscribe(String), + /// Publish data to a gossipsub topic. + Publish { topic: String, data: Vec }, +} + +/// Bevy plugin that sets up the network bridge. +pub struct NetworkPlugin; + +impl Plugin for NetworkPlugin { + fn build(&self, app: &mut App) { + let identity = Identity::generate(); + info!(peer_id = %identity.peer_id(), "generated local identity"); + + let (event_tx, event_rx) = std_mpsc::channel(); + let (cmd_tx, cmd_rx) = std_mpsc::channel(); + + // Spawn the tokio runtime + network node on a background thread. + let net_identity = identity.clone(); + std::thread::spawn(move || { + let rt = tokio::runtime::Runtime::new().expect("failed to create tokio runtime"); + rt.block_on(async move { + match run_network(net_identity, event_tx, cmd_rx).await { + Ok(()) => info!("network task exited cleanly"), + Err(e) => error!("network task failed: {e}"), + } + }); + }); + + app.insert_resource(LocalIdentity(identity)) + .insert_resource(NetworkEventReceiver(Arc::new(Mutex::new(event_rx)))) + .insert_resource(NetworkCommandSender(cmd_tx)) + .add_event::() + .add_systems(Update, poll_network_events); + } +} + +/// System that drains the network event channel each frame. +fn poll_network_events( + receiver: Res, + mut events: EventWriter, +) { + let Ok(rx) = receiver.0.lock() else { return }; + while let Ok(event) = rx.try_recv() { + events.send(event); + } +} + +/// Run the network node on the tokio runtime (called from background thread). +async fn run_network( + identity: Identity, + event_tx: std_mpsc::Sender, + cmd_rx: std_mpsc::Receiver, +) -> anyhow::Result<()> { + use willow_network::{NetworkConfig, NetworkEvent, NetworkNode}; + + let config = NetworkConfig::default(); + let (node, mut events) = NetworkNode::start(identity, config).await?; + + loop { + tokio::select! { + event = events.recv() => { + let Some(event) = event else { break }; + let bridge_event = match event { + NetworkEvent::Message { topic, data, source } => { + NetworkBridgeEvent::MessageReceived { + topic, + data, + source: source.map(|p| p.to_string()), + } + } + NetworkEvent::PeerConnected(peer) => { + NetworkBridgeEvent::PeerConnected(peer.to_string()) + } + NetworkEvent::PeerDisconnected(peer) => { + NetworkBridgeEvent::PeerDisconnected(peer.to_string()) + } + NetworkEvent::Listening(addr) => { + NetworkBridgeEvent::Listening(addr.to_string()) + } + _ => continue, + }; + let _ = event_tx.send(bridge_event); + } + + _ = tokio::time::sleep(std::time::Duration::from_millis(16)) => { + while let Ok(cmd) = cmd_rx.try_recv() { + match cmd { + NetworkBridgeCommand::Subscribe(topic) => { + node.subscribe(&topic)?; + } + NetworkBridgeCommand::Publish { topic, data } => { + node.publish(&topic, data)?; + } + } + } + } + } + } + + Ok(()) +} diff --git a/crates/app/src/ui.rs b/crates/app/src/ui.rs new file mode 100644 index 00000000..800397dd --- /dev/null +++ b/crates/app/src/ui.rs @@ -0,0 +1,340 @@ +//! # UI Module +//! +//! Top-level Bevy UI layout for the Willow chat client. +//! +//! ## Layout (Bevy 0.14) +//! +//! ```text +//! ┌──────────┬──────────────────────────────────┐ +//! │ │ #general │ +//! │ Servers │ │ +//! │ │ Alice: hey everyone! │ +//! │ ──────── │ Bob: what's up? │ +//! │ #general │ │ +//! │ #random │ │ +//! │ #voice │ │ +//! │ │ ┌──────────────────────────────┐ │ +//! │ │ │ Type a message... │ │ +//! │ │ └──────────────────────────────┘ │ +//! └──────────┴──────────────────────────────────┘ +//! ``` + +use bevy::prelude::*; + +use crate::network_bridge::{LocalIdentity, NetworkBridgeEvent}; + +/// Plugin for all UI systems and resources. +pub struct UiPlugin; + +impl Plugin for UiPlugin { + fn build(&self, app: &mut App) { + app.insert_resource(ChatState::default()) + .add_systems(Startup, setup_ui) + .add_systems(Update, (handle_network_events, update_peer_count)); + } +} + +// ───── Resources ───────────────────────────────────────────────────────────── + +/// Holds the current chat state visible to the UI. +#[derive(Resource, Default)] +pub struct ChatState { + /// Messages in the currently active channel. + pub messages: Vec, + /// Name of the current channel. + pub current_channel: String, + /// Connected peers. + pub peers: Vec, +} + +/// A rendered chat message. +#[derive(Debug, Clone)] +pub struct ChatMessage { + pub author: String, + pub body: String, +} + +// ───── Components ──────────────────────────────────────────────────────────── + +/// Marker for the message list container. +#[derive(Component)] +struct MessageList; + +/// Marker for the channel name header. +#[derive(Component)] +struct ChannelHeader; + +/// Marker for the peer count display. +#[derive(Component)] +struct PeerCount; + +// ───── Systems ─────────────────────────────────────────────────────────────── + +/// Build the initial UI layout. +fn setup_ui(mut commands: Commands, identity: Res) { + // Camera + commands.spawn(Camera2dBundle::default()); + + let peer_id_str = format!("{}", identity.0.peer_id()); + let peer_display = if peer_id_str.len() > 12 { + format!("{}...", &peer_id_str[..12]) + } else { + peer_id_str + }; + + // Root container — fills the window, horizontal flex. + commands + .spawn(NodeBundle { + style: Style { + width: Val::Percent(100.0), + height: Val::Percent(100.0), + flex_direction: FlexDirection::Row, + ..default() + }, + ..default() + }) + .with_children(|root| { + // ── Left sidebar ── + root.spawn(NodeBundle { + style: Style { + width: Val::Px(220.0), + height: Val::Percent(100.0), + flex_direction: FlexDirection::Column, + padding: UiRect::all(Val::Px(12.0)), + ..default() + }, + background_color: Color::srgb(0.15, 0.15, 0.18).into(), + ..default() + }) + .with_children(|sidebar| { + // App title. + sidebar.spawn(TextBundle::from_section( + "Willow", + TextStyle { + font_size: 24.0, + color: Color::srgb(0.9, 0.9, 0.9), + ..default() + }, + )); + + // Peer ID. + sidebar.spawn(TextBundle::from_section( + format!("You: {peer_display}"), + TextStyle { + font_size: 11.0, + color: Color::srgb(0.5, 0.5, 0.5), + ..default() + }, + )); + + // Spacer. + sidebar.spawn(NodeBundle { + style: Style { + height: Val::Px(20.0), + ..default() + }, + ..default() + }); + + // Channel list header. + sidebar.spawn(TextBundle::from_section( + "CHANNELS", + TextStyle { + font_size: 11.0, + color: Color::srgb(0.5, 0.5, 0.55), + ..default() + }, + )); + + // Channel entries. + for name in ["# general", "# random", "# voice"] { + sidebar.spawn(TextBundle { + text: Text::from_section( + name, + TextStyle { + font_size: 15.0, + color: Color::srgb(0.7, 0.7, 0.7), + ..default() + }, + ), + style: Style { + margin: UiRect::top(Val::Px(6.0)), + ..default() + }, + ..default() + }); + } + + // Flexible spacer. + sidebar.spawn(NodeBundle { + style: Style { + flex_grow: 1.0, + ..default() + }, + ..default() + }); + + // Peer count. + sidebar.spawn(( + TextBundle::from_section( + "0 peers connected", + TextStyle { + font_size: 11.0, + color: Color::srgb(0.4, 0.8, 0.4), + ..default() + }, + ), + PeerCount, + )); + }); + + // ── Main content area ── + root.spawn(NodeBundle { + style: Style { + flex_grow: 1.0, + height: Val::Percent(100.0), + flex_direction: FlexDirection::Column, + ..default() + }, + background_color: Color::srgb(0.2, 0.2, 0.22).into(), + ..default() + }) + .with_children(|main| { + // Channel header bar. + main.spawn(NodeBundle { + style: Style { + width: Val::Percent(100.0), + height: Val::Px(48.0), + padding: UiRect::horizontal(Val::Px(16.0)), + align_items: AlignItems::Center, + border: UiRect::bottom(Val::Px(1.0)), + ..default() + }, + border_color: Color::srgb(0.15, 0.15, 0.18).into(), + ..default() + }) + .with_children(|header| { + header.spawn(( + TextBundle::from_section( + "# general", + TextStyle { + font_size: 18.0, + color: Color::srgb(0.9, 0.9, 0.9), + ..default() + }, + ), + ChannelHeader, + )); + }); + + // Message area. + main.spawn(( + NodeBundle { + style: Style { + flex_grow: 1.0, + width: Val::Percent(100.0), + flex_direction: FlexDirection::Column, + padding: UiRect::all(Val::Px(16.0)), + overflow: Overflow::clip_y(), + ..default() + }, + ..default() + }, + MessageList, + )) + .with_children(|messages| { + messages.spawn(TextBundle::from_section( + "Welcome to Willow! This is a P2P chat — no servers, no middlemen.", + TextStyle { + font_size: 14.0, + color: Color::srgb(0.5, 0.5, 0.55), + ..default() + }, + )); + }); + + // Input area. + main.spawn(NodeBundle { + style: Style { + width: Val::Percent(100.0), + height: Val::Px(56.0), + padding: UiRect::all(Val::Px(12.0)), + ..default() + }, + background_color: Color::srgb(0.17, 0.17, 0.19).into(), + ..default() + }) + .with_children(|input_area| { + input_area + .spawn(NodeBundle { + style: Style { + width: Val::Percent(100.0), + height: Val::Percent(100.0), + padding: UiRect::horizontal(Val::Px(12.0)), + align_items: AlignItems::Center, + ..default() + }, + background_color: Color::srgb(0.25, 0.25, 0.28).into(), + ..default() + }) + .with_children(|input| { + input.spawn(TextBundle::from_section( + "Type a message... (input coming soon)", + TextStyle { + font_size: 14.0, + color: Color::srgb(0.45, 0.45, 0.48), + ..default() + }, + )); + }); + }); + }); + }); +} + +/// Process incoming network events and update the chat state. +fn handle_network_events( + mut events: EventReader, + mut state: ResMut, +) { + for event in events.read() { + match event { + NetworkBridgeEvent::MessageReceived { data, source, .. } => { + if let Ok(body) = String::from_utf8(data.clone()) { + state.messages.push(ChatMessage { + author: source + .as_ref() + .map(|s| { + if s.len() > 12 { + format!("{}...", &s[..12]) + } else { + s.clone() + } + }) + .unwrap_or_else(|| "unknown".into()), + body, + }); + } + } + NetworkBridgeEvent::PeerConnected(peer) => { + if !state.peers.contains(peer) { + state.peers.push(peer.clone()); + } + } + NetworkBridgeEvent::PeerDisconnected(peer) => { + state.peers.retain(|p| p != peer); + } + _ => {} + } + } +} + +/// Update the peer count label when state changes. +fn update_peer_count(state: Res, mut query: Query<&mut Text, With>) { + if !state.is_changed() { + return; + } + for mut text in &mut query { + text.sections[0].value = format!("{} peer(s) connected", state.peers.len()); + } +} diff --git a/crates/channel/Cargo.toml b/crates/channel/Cargo.toml new file mode 100644 index 00000000..f3cc07bd --- /dev/null +++ b/crates/channel/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "willow-channel" +edition.workspace = true +version.workspace = true +license.workspace = true +description = "Servers, channels, roles, and permissions for Willow" + +[dependencies] +willow-identity = { path = "../identity" } +willow-transport = { path = "../transport" } + +serde = { workspace = true } +chrono = { workspace = true } +uuid = { workspace = true } +thiserror = { workspace = true } + +[dev-dependencies] +tokio = { workspace = true } diff --git a/crates/channel/src/lib.rs b/crates/channel/src/lib.rs new file mode 100644 index 00000000..79017c3c --- /dev/null +++ b/crates/channel/src/lib.rs @@ -0,0 +1,730 @@ +//! # Willow Channel +//! +//! Servers, channels, roles, and permissions for the Willow P2P network. +//! +//! ## Data model +//! +//! Willow borrows Discord's organisational hierarchy: +//! +//! - A **[`Server`]** is a named community owned by a peer. It contains +//! channels and members. +//! - A **[`Channel`]** is a named conversation space inside a server. Channels +//! can be text or voice. +//! - A **[`Role`]** groups a set of [`Permission`]s under a name. +//! - **[`Member`]** tracks a peer's membership in a server, including which +//! roles they hold. +//! +//! ## Invite system +//! +//! Servers are private by default. New members join via an [`Invite`] — a +//! signed token that grants the bearer permission to join. Invites can be +//! one-time-use or have an expiration date. +//! +//! ## Examples +//! +//! ``` +//! use willow_channel::{Server, ChannelKind}; +//! use willow_identity::Identity; +//! +//! let owner = Identity::generate(); +//! let mut server = Server::new("My Server", owner.peer_id()); +//! +//! server.create_channel("general", ChannelKind::Text).unwrap(); +//! server.create_channel("voice-chat", ChannelKind::Voice).unwrap(); +//! +//! assert_eq!(server.channels().len(), 2); +//! ``` + +use std::collections::{HashMap, HashSet}; + +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; +use uuid::Uuid; + +use willow_identity::PeerId; + +// ───── IDs ─────────────────────────────────────────────────────────────────── + +/// Unique identifier for a server. +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub struct ServerId(pub Uuid); + +impl ServerId { + pub fn new() -> Self { + Self(Uuid::new_v4()) + } +} + +impl Default for ServerId { + fn default() -> Self { + Self::new() + } +} + +impl std::fmt::Display for ServerId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} + +/// Unique identifier for a channel within a server. +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub struct ChannelId(pub Uuid); + +impl ChannelId { + pub fn new() -> Self { + Self(Uuid::new_v4()) + } +} + +impl Default for ChannelId { + fn default() -> Self { + Self::new() + } +} + +impl std::fmt::Display for ChannelId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} + +/// Unique identifier for a role. +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub struct RoleId(pub Uuid); + +impl RoleId { + pub fn new() -> Self { + Self(Uuid::new_v4()) + } +} + +impl Default for RoleId { + fn default() -> Self { + Self::new() + } +} + +impl std::fmt::Display for RoleId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} + +/// Unique identifier for an invite. +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub struct InviteId(pub Uuid); + +impl InviteId { + pub fn new() -> Self { + Self(Uuid::new_v4()) + } +} + +impl Default for InviteId { + fn default() -> Self { + Self::new() + } +} + +// ───── Errors ──────────────────────────────────────────────────────────────── + +/// Errors produced by channel operations. +#[derive(Debug, thiserror::Error)] +pub enum ChannelError { + /// The caller does not have the required permission. + #[error("permission denied: {0} requires {1:?}")] + PermissionDenied(PeerId, Permission), + + /// A channel with this name already exists in the server. + #[error("duplicate channel name: {0}")] + DuplicateChannelName(String), + + /// The referenced channel was not found. + #[error("channel not found: {0}")] + ChannelNotFound(ChannelId), + + /// The referenced role was not found. + #[error("role not found: {0}")] + RoleNotFound(RoleId), + + /// The peer is not a member of this server. + #[error("not a member: {0}")] + NotAMember(PeerId), + + /// The invite has expired or already been used. + #[error("invite expired or invalid")] + InvalidInvite, +} + +// ───── Permissions ─────────────────────────────────────────────────────────── + +/// Individual permissions that can be granted to roles. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub enum Permission { + /// Send messages in text channels. + SendMessages, + /// Read message history. + ReadMessages, + /// Delete other people's messages. + ManageMessages, + /// Create, rename, or delete channels. + ManageChannels, + /// Create, edit, or delete roles. + ManageRoles, + /// Kick members from the server. + KickMembers, + /// Ban members from the server. + BanMembers, + /// Create invite links. + CreateInvite, + /// Connect to voice channels. + VoiceConnect, + /// Speak in voice channels. + VoiceSpeak, + /// Share screen in voice channels. + ScreenShare, + /// Upload files. + AttachFiles, + /// Full administrative access — implies all other permissions. + Administrator, +} + +// ───── Role ────────────────────────────────────────────────────────────────── + +/// A named bundle of [`Permission`]s that can be assigned to members. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct Role { + /// Unique ID. + pub id: RoleId, + /// Human-readable name (e.g. "Moderator"). + pub name: String, + /// The set of permissions this role grants. + pub permissions: HashSet, + /// Display color as a hex string (e.g. "#FF5733"). + pub color: Option, +} + +impl Role { + /// Create a new role with no permissions. + pub fn new(name: impl Into) -> Self { + Self { + id: RoleId::new(), + name: name.into(), + permissions: HashSet::new(), + color: None, + } + } + + /// Check whether this role grants a specific permission. + pub fn has_permission(&self, perm: Permission) -> bool { + self.permissions.contains(&Permission::Administrator) || self.permissions.contains(&perm) + } +} + +// ───── Channel ─────────────────────────────────────────────────────────────── + +/// Whether a channel carries text or voice. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +pub enum ChannelKind { + /// A text chat channel. + Text, + /// A voice (and optionally video/screenshare) channel. + Voice, +} + +/// A named conversation space inside a [`Server`]. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct Channel { + /// Unique ID. + pub id: ChannelId, + /// Display name (e.g. "general"). + pub name: String, + /// Optional description / topic. + pub topic: Option, + /// Text or voice. + pub kind: ChannelKind, + /// When this channel was created. + pub created_at: DateTime, +} + +impl Channel { + /// Create a new channel. + pub fn new(name: impl Into, kind: ChannelKind) -> Self { + Self { + id: ChannelId::new(), + name: name.into(), + topic: None, + kind, + created_at: Utc::now(), + } + } +} + +// ───── Member ──────────────────────────────────────────────────────────────── + +/// A peer's membership record within a server. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct Member { + /// The peer. + pub peer_id: PeerId, + /// Roles assigned to this member. + pub roles: HashSet, + /// When they joined. + pub joined_at: DateTime, +} + +impl Member { + /// Create a new member with no roles. + pub fn new(peer_id: PeerId) -> Self { + Self { + peer_id, + roles: HashSet::new(), + joined_at: Utc::now(), + } + } +} + +// ───── Invite ──────────────────────────────────────────────────────────────── + +/// A signed token granting the bearer permission to join a server. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct Invite { + /// Unique ID. + pub id: InviteId, + /// The server this invite is for. + pub server_id: ServerId, + /// Who created the invite. + pub created_by: PeerId, + /// When the invite was created. + pub created_at: DateTime, + /// When the invite expires (if ever). + pub expires_at: Option>, + /// Maximum number of uses (None = unlimited). + pub max_uses: Option, + /// How many times this invite has been used. + pub uses: u32, +} + +impl Invite { + /// Check whether this invite is still valid. + pub fn is_valid(&self) -> bool { + if let Some(expires) = self.expires_at { + if Utc::now() > expires { + return false; + } + } + if let Some(max) = self.max_uses { + if self.uses >= max { + return false; + } + } + true + } +} + +// ───── Server ──────────────────────────────────────────────────────────────── + +/// A named community containing channels and members. +/// +/// The server owner has implicit [`Permission::Administrator`] access to +/// everything. Other members' access is determined by their roles. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Server { + /// Unique ID. + pub id: ServerId, + /// Display name. + pub name: String, + /// Optional description. + pub description: Option, + /// The peer who created and owns this server. + pub owner: PeerId, + /// When the server was created. + pub created_at: DateTime, + + channels: HashMap, + roles: HashMap, + members: HashMap, + invites: HashMap, +} + +impl Server { + /// Create a new server. The owner is automatically added as a member. + pub fn new(name: impl Into, owner: PeerId) -> Self { + let mut members = HashMap::new(); + members.insert(owner.clone(), Member::new(owner.clone())); + + Self { + id: ServerId::new(), + name: name.into(), + description: None, + owner, + created_at: Utc::now(), + channels: HashMap::new(), + roles: HashMap::new(), + members, + invites: HashMap::new(), + } + } + + // ── Queries ────────────────────────────────────────────────────────── + + /// All channels in this server. + pub fn channels(&self) -> Vec<&Channel> { + self.channels.values().collect() + } + + /// All roles defined in this server. + pub fn roles(&self) -> Vec<&Role> { + self.roles.values().collect() + } + + /// All current members. + pub fn members(&self) -> Vec<&Member> { + self.members.values().collect() + } + + /// Get a channel by ID. + pub fn channel(&self, id: &ChannelId) -> Option<&Channel> { + self.channels.get(id) + } + + /// Check whether a peer is a member of this server. + pub fn is_member(&self, peer: &PeerId) -> bool { + self.members.contains_key(peer) + } + + /// Check whether a peer has a specific permission. + /// + /// The server owner always has all permissions. + pub fn has_permission(&self, peer: &PeerId, perm: Permission) -> bool { + if *peer == self.owner { + return true; + } + + let member = match self.members.get(peer) { + Some(m) => m, + None => return false, + }; + + member.roles.iter().any(|role_id| { + self.roles + .get(role_id) + .map(|r| r.has_permission(perm)) + .unwrap_or(false) + }) + } + + // ── Mutations ──────────────────────────────────────────────────────── + + /// Create a new channel in this server. + pub fn create_channel( + &mut self, + name: impl Into, + kind: ChannelKind, + ) -> Result { + let name = name.into(); + + // Prevent duplicate channel names. + if self.channels.values().any(|ch| ch.name == name) { + return Err(ChannelError::DuplicateChannelName(name)); + } + + let channel = Channel::new(name, kind); + let id = channel.id.clone(); + self.channels.insert(id.clone(), channel); + Ok(id) + } + + /// Delete a channel by ID. + pub fn delete_channel(&mut self, id: &ChannelId) -> Result<(), ChannelError> { + self.channels + .remove(id) + .map(|_| ()) + .ok_or_else(|| ChannelError::ChannelNotFound(id.clone())) + } + + /// Create a new role. + pub fn create_role(&mut self, role: Role) -> RoleId { + let id = role.id.clone(); + self.roles.insert(id.clone(), role); + id + } + + /// Assign a role to a member. + pub fn assign_role(&mut self, peer: &PeerId, role_id: &RoleId) -> Result<(), ChannelError> { + if !self.roles.contains_key(role_id) { + return Err(ChannelError::RoleNotFound(role_id.clone())); + } + + let member = self + .members + .get_mut(peer) + .ok_or_else(|| ChannelError::NotAMember(peer.clone()))?; + + member.roles.insert(role_id.clone()); + Ok(()) + } + + /// Add a new member to the server. + pub fn add_member(&mut self, peer: PeerId) { + self.members + .entry(peer.clone()) + .or_insert_with(|| Member::new(peer)); + } + + /// Remove a member from the server. Cannot remove the owner. + pub fn remove_member(&mut self, peer: &PeerId) -> Result<(), ChannelError> { + if *peer == self.owner { + return Err(ChannelError::PermissionDenied( + peer.clone(), + Permission::Administrator, + )); + } + + self.members + .remove(peer) + .map(|_| ()) + .ok_or_else(|| ChannelError::NotAMember(peer.clone())) + } + + /// Create a new invite for this server. + pub fn create_invite( + &mut self, + created_by: PeerId, + expires_at: Option>, + max_uses: Option, + ) -> Result { + if !self.is_member(&created_by) { + return Err(ChannelError::NotAMember(created_by)); + } + + let invite = Invite { + id: InviteId::new(), + server_id: self.id.clone(), + created_by, + created_at: Utc::now(), + expires_at, + max_uses, + uses: 0, + }; + + let id = invite.id.clone(); + self.invites.insert(id.clone(), invite); + Ok(id) + } + + /// Use an invite to add a new member. + pub fn use_invite( + &mut self, + invite_id: &InviteId, + peer: PeerId, + ) -> Result<(), ChannelError> { + let invite = self + .invites + .get_mut(invite_id) + .ok_or(ChannelError::InvalidInvite)?; + + if !invite.is_valid() { + return Err(ChannelError::InvalidInvite); + } + + invite.uses += 1; + self.add_member(peer); + Ok(()) + } +} + +// ───── Tests ───────────────────────────────────────────────────────────────── + +#[cfg(test)] +mod tests { + use super::*; + use willow_identity::Identity; + + fn owner_and_server() -> (PeerId, Server) { + let owner = Identity::generate().peer_id(); + let server = Server::new("Test Server", owner.clone()); + (owner, server) + } + + #[test] + fn new_server_has_owner_as_member() { + let (owner, server) = owner_and_server(); + assert!(server.is_member(&owner)); + assert_eq!(server.members().len(), 1); + } + + #[test] + fn owner_has_all_permissions() { + let (owner, server) = owner_and_server(); + assert!(server.has_permission(&owner, Permission::Administrator)); + assert!(server.has_permission(&owner, Permission::ManageChannels)); + assert!(server.has_permission(&owner, Permission::KickMembers)); + } + + #[test] + fn non_member_has_no_permissions() { + let (_, server) = owner_and_server(); + let stranger = Identity::generate().peer_id(); + assert!(!server.has_permission(&stranger, Permission::ReadMessages)); + } + + #[test] + fn create_channels() { + let (_, mut server) = owner_and_server(); + + let text_id = server.create_channel("general", ChannelKind::Text).unwrap(); + let voice_id = server + .create_channel("voice-chat", ChannelKind::Voice) + .unwrap(); + + assert_eq!(server.channels().len(), 2); + assert_eq!(server.channel(&text_id).unwrap().kind, ChannelKind::Text); + assert_eq!(server.channel(&voice_id).unwrap().kind, ChannelKind::Voice); + } + + #[test] + fn duplicate_channel_name_rejected() { + let (_, mut server) = owner_and_server(); + server.create_channel("general", ChannelKind::Text).unwrap(); + + let result = server.create_channel("general", ChannelKind::Text); + assert!(matches!(result, Err(ChannelError::DuplicateChannelName(_)))); + } + + #[test] + fn delete_channel() { + let (_, mut server) = owner_and_server(); + let id = server.create_channel("temp", ChannelKind::Text).unwrap(); + + server.delete_channel(&id).unwrap(); + assert!(server.channel(&id).is_none()); + } + + #[test] + fn delete_nonexistent_channel_fails() { + let (_, mut server) = owner_and_server(); + let fake_id = ChannelId::new(); + assert!(matches!( + server.delete_channel(&fake_id), + Err(ChannelError::ChannelNotFound(_)) + )); + } + + #[test] + fn roles_and_permissions() { + let (_, mut server) = owner_and_server(); + let alice = Identity::generate().peer_id(); + server.add_member(alice.clone()); + + // Alice starts with no permissions. + assert!(!server.has_permission(&alice, Permission::ManageMessages)); + + // Create a moderator role. + let mut mod_role = Role::new("Moderator"); + mod_role.permissions.insert(Permission::ManageMessages); + mod_role.permissions.insert(Permission::KickMembers); + let role_id = server.create_role(mod_role); + + // Assign it to Alice. + server.assign_role(&alice, &role_id).unwrap(); + + assert!(server.has_permission(&alice, Permission::ManageMessages)); + assert!(server.has_permission(&alice, Permission::KickMembers)); + assert!(!server.has_permission(&alice, Permission::ManageRoles)); + } + + #[test] + fn administrator_role_grants_everything() { + let (_, mut server) = owner_and_server(); + let bob = Identity::generate().peer_id(); + server.add_member(bob.clone()); + + let mut admin_role = Role::new("Admin"); + admin_role.permissions.insert(Permission::Administrator); + let role_id = server.create_role(admin_role); + server.assign_role(&bob, &role_id).unwrap(); + + // Administrator implies every other permission. + assert!(server.has_permission(&bob, Permission::ManageChannels)); + assert!(server.has_permission(&bob, Permission::BanMembers)); + assert!(server.has_permission(&bob, Permission::ScreenShare)); + } + + #[test] + fn assign_role_to_non_member_fails() { + let (_, mut server) = owner_and_server(); + let role = Role::new("Test"); + let role_id = server.create_role(role); + + let stranger = Identity::generate().peer_id(); + assert!(matches!( + server.assign_role(&stranger, &role_id), + Err(ChannelError::NotAMember(_)) + )); + } + + #[test] + fn remove_member() { + let (_, mut server) = owner_and_server(); + let alice = Identity::generate().peer_id(); + server.add_member(alice.clone()); + + server.remove_member(&alice).unwrap(); + assert!(!server.is_member(&alice)); + } + + #[test] + fn cannot_remove_owner() { + let (owner, mut server) = owner_and_server(); + assert!(server.remove_member(&owner).is_err()); + } + + #[test] + fn invite_flow() { + let (owner, mut server) = owner_and_server(); + + // Owner creates an invite. + let invite_id = server.create_invite(owner, None, Some(1)).unwrap(); + + // A new peer uses it. + let newcomer = Identity::generate().peer_id(); + server.use_invite(&invite_id, newcomer.clone()).unwrap(); + assert!(server.is_member(&newcomer)); + + // Second use fails (max_uses = 1). + let another = Identity::generate().peer_id(); + assert!(matches!( + server.use_invite(&invite_id, another), + Err(ChannelError::InvalidInvite) + )); + } + + #[test] + fn expired_invite_rejected() { + let (owner, mut server) = owner_and_server(); + + // Create an invite that already expired. + let past = Utc::now() - chrono::Duration::hours(1); + let invite_id = server.create_invite(owner, Some(past), None).unwrap(); + + let peer = Identity::generate().peer_id(); + assert!(matches!( + server.use_invite(&invite_id, peer), + Err(ChannelError::InvalidInvite) + )); + } + + #[test] + fn server_serde_round_trip() { + let (_, mut server) = owner_and_server(); + server.create_channel("general", ChannelKind::Text).unwrap(); + + let bytes = willow_transport::pack(&server).unwrap(); + let decoded: Server = willow_transport::unpack(&bytes).unwrap(); + + assert_eq!(decoded.name, server.name); + assert_eq!(decoded.channels().len(), 1); + } +} diff --git a/crates/identity/Cargo.toml b/crates/identity/Cargo.toml index 90472184..a9cf29a7 100644 --- a/crates/identity/Cargo.toml +++ b/crates/identity/Cargo.toml @@ -1,13 +1,17 @@ [package] -name = "identity" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +name = "willow-identity" +edition.workspace = true +version.workspace = true +license.workspace = true +description = "Cryptographic identity, message signing, and user profiles for Willow" [dependencies] -transport = { path = "../transport" } +willow-transport = { path = "../transport" } + +libp2p = { workspace = true } +serde = { workspace = true } +thiserror = { workspace = true } +chrono = { workspace = true } -libp2p = { version = "0.44.0", default-features = false, features = ["wasm-bindgen", "serde", "libp2p-identify"]} -serde = { version = "1.0.124", features = ["rc"] } -thiserror = "1.0.30" +[dev-dependencies] +tokio = { workspace = true } diff --git a/crates/identity/src/lib.rs b/crates/identity/src/lib.rs index 6b839076..4b38ff95 100644 --- a/crates/identity/src/lib.rs +++ b/crates/identity/src/lib.rs @@ -1,103 +1,338 @@ -use std::rc::Rc; +//! # Willow Identity +//! +//! Cryptographic identity management for the Willow P2P network. +//! +//! Every participant in a Willow network has an [`Identity`] — an Ed25519 +//! keypair that uniquely identifies them and lets them sign messages so that +//! other peers can verify authenticity. +//! +//! ## Key concepts +//! +//! - **[`Identity`]** — your secret keypair. Never leaves the local machine. +//! - **[`PeerId`]** — a public identifier derived from the keypair. Safe to +//! share with anyone. +//! - **[`UserProfile`]** — display name, avatar, status, etc. Attached to a +//! `PeerId`. +//! - **[`SignedPayload`]** / [`pack`] / [`unpack`] — sign arbitrary data so +//! that recipients can verify the sender. +//! +//! ## Examples +//! +//! ``` +//! use willow_identity::{Identity, pack, unpack}; +//! +//! let alice = Identity::generate(); +//! let data = String::from("hello from alice"); +//! let signed = pack(&data, &alice).unwrap(); +//! +//! let (msg, peer_id) = unpack::(&signed).unwrap(); +//! assert_eq!(msg, "hello from alice"); +//! assert_eq!(peer_id, alice.peer_id()); +//! ``` +use std::sync::Arc; + +use chrono::{DateTime, Utc}; use libp2p::identity::{Keypair, PublicKey}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error("Serde error")] - Serde, - #[error("Invalid signature")] +// ───── Errors ──────────────────────────────────────────────────────────────── + +/// Errors produced by identity operations. +#[derive(Debug, thiserror::Error)] +pub enum IdentityError { + /// Serialization or deserialization failed. + #[error("serialization failed: {0}")] + Serde(String), + + /// A cryptographic signature did not verify. + #[error("invalid signature")] InvalidSignature, - #[error("Unable to sign payload")] - SignError, - #[error("Unable to decode public key")] - PublicKey, + + /// The private key could not produce a signature. + #[error("signing failed: {0}")] + SignError(String), + + /// A public key could not be decoded from its wire format. + #[error("failed to decode public key: {0}")] + PublicKeyDecode(String), } +// ───── PeerId ──────────────────────────────────────────────────────────────── + +/// A globally unique, cryptographically derived peer identifier. +/// +/// Wraps [`libp2p::PeerId`] in an `Arc` so it can be cheaply cloned and shared +/// across async tasks. Implements `Serialize` / `Deserialize` for wire +/// transport. #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, Hash)] -pub struct PeerId(Rc); +pub struct PeerId(Arc); -#[derive(Clone)] -pub struct Identity(Rc); -impl Identity { - pub fn as_peer(&self) -> PeerId { - PeerId(self.0.public().to_peer_id().into()) +impl PeerId { + /// Return a reference to the inner [`libp2p::PeerId`]. + pub fn inner(&self) -> &libp2p::PeerId { + &self.0 } } +impl std::fmt::Display for PeerId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} + +impl From for PeerId { + fn from(id: libp2p::PeerId) -> Self { + Self(Arc::new(id)) + } +} + +// ───── Identity ────────────────────────────────────────────────────────────── + +/// A local cryptographic identity backed by an Ed25519 keypair. +/// +/// This is the "secret" side of your presence on the network — it lets you +/// sign messages to prove they came from you. +/// +/// `Identity` is cheap to clone (the keypair lives behind an [`Arc`]) and is +/// `Send + Sync` so it can be shared across tokio tasks. +#[derive(Clone)] +pub struct Identity(Arc); + impl Identity { - pub fn new() -> Self { - Self(Keypair::generate_ed25519().into()) + /// Generate a fresh random Ed25519 identity. + pub fn generate() -> Self { + Self(Arc::new(Keypair::generate_ed25519())) + } + + /// Derive the public [`PeerId`] for this identity. + pub fn peer_id(&self) -> PeerId { + PeerId::from(self.0.public().to_peer_id()) + } + + /// Access the underlying [`Keypair`] (e.g. for configuring libp2p). + pub fn keypair(&self) -> &Keypair { + &self.0 + } + + /// Access the public key. + pub fn public_key(&self) -> PublicKey { + self.0.public() } } impl Default for Identity { fn default() -> Self { - Self::new() + Self::generate() } } -#[derive(Serialize, Deserialize)] -struct OpaquePublicKey(Vec); -#[derive(Serialize, Deserialize)] -pub struct Signature(Vec); +impl std::fmt::Debug for Identity { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("Identity") + .field(&self.peer_id()) + .finish() + } +} + +// ───── User profile ────────────────────────────────────────────────────────── + +/// A human-readable profile attached to a [`PeerId`]. +/// +/// Profiles are gossiped across the network so that peers can show display +/// names and avatars instead of raw peer IDs. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct UserProfile { + /// The peer this profile belongs to. + pub peer_id: PeerId, + /// Display name shown in the UI. + pub display_name: String, + /// Optional avatar (URL or content-addressed hash). + pub avatar: Option, + /// Free-text status line (e.g. "Away", "In a meeting"). + pub status: Option, + /// Short bio. + pub bio: Option, + /// When this profile was last updated. + pub updated_at: DateTime, +} + +impl UserProfile { + /// Create a minimal profile with just a display name. + pub fn new(peer_id: PeerId, display_name: impl Into) -> Self { + Self { + peer_id, + display_name: display_name.into(), + avatar: None, + status: None, + bio: None, + updated_at: Utc::now(), + } + } +} + +// ───── Signed message envelope ─────────────────────────────────────────────── +/// Internal wire format for a signed payload. #[derive(Serialize, Deserialize)] -struct Message { - public_key: OpaquePublicKey, - signature: Signature, +struct SignedMessage { + /// The signer's public key in protobuf encoding. + public_key: Vec, + /// Ed25519 signature over `payload`. + signature: Vec, + /// The serialized inner data. payload: Vec, } -impl Message { - fn verify(&self) -> Result { - let public_key = - PublicKey::from_protobuf_encoding(&self.public_key.0).map_err(|_| Error::PublicKey)?; +impl SignedMessage { + /// Verify the signature and return the signer's [`libp2p::PeerId`]. + fn verify(&self) -> Result { + let public_key = PublicKey::try_decode_protobuf(&self.public_key) + .map_err(|e| IdentityError::PublicKeyDecode(e.to_string()))?; - let verified = public_key.verify(&self.payload, &self.signature.0); - if verified { + if public_key.verify(&self.payload, &self.signature) { Ok(public_key.to_peer_id()) } else { - Err(Error::InvalidSignature) + Err(IdentityError::InvalidSignature) } } } -pub fn pack(payload: &T, identity: Identity) -> Result, Error> { - let payload = transport::pack(payload).map_err(|_| Error::Serde)?; - let signature = Signature(identity.0.sign(&payload).map_err(|_| Error::Serde)?); - let public_key = OpaquePublicKey(identity.0.public().to_protobuf_encoding()); +// ───── Public API ──────────────────────────────────────────────────────────── + +/// Sign and serialize `payload` using the given [`Identity`]. +/// +/// The returned bytes contain the serialized data, the Ed25519 signature, and +/// the signer's public key — everything a recipient needs to verify +/// authenticity via [`unpack`]. +/// +/// # Errors +/// +/// Returns [`IdentityError::Serde`] if serialization fails, or +/// [`IdentityError::SignError`] if the keypair cannot produce a signature. +pub fn pack(payload: &T, identity: &Identity) -> Result, IdentityError> { + let payload_bytes = + willow_transport::pack(payload).map_err(|e| IdentityError::Serde(e.to_string()))?; - transport::pack(&Message { - public_key, + let signature = identity + .0 + .sign(&payload_bytes) + .map_err(|e| IdentityError::SignError(e.to_string()))?; + + let message = SignedMessage { + public_key: identity.public_key().encode_protobuf(), signature, - payload, - }) - .map_err(|_| Error::Serde) + payload: payload_bytes, + }; + + willow_transport::pack(&message).map_err(|e| IdentityError::Serde(e.to_string())) } -pub fn unpack(payload: &[u8]) -> Result<(T, PeerId), Error> { - let message: Message = transport::unpack(payload).map_err(|_| Error::Serde)?; - let peer_id = message.verify()?; - let payload = transport::unpack(&message.payload).map_err(|_| Error::Serde)?; +/// Verify the signature on `data` and deserialize the inner payload. +/// +/// Returns both the deserialized value and the [`PeerId`] of the signer, so +/// the caller can check *who* sent the message. +/// +/// # Errors +/// +/// Returns an error if the bytes are malformed, the signature is invalid, or +/// the inner payload can't be deserialized into `T`. +pub fn unpack(data: &[u8]) -> Result<(T, PeerId), IdentityError> { + let message: SignedMessage = + willow_transport::unpack(data).map_err(|e| IdentityError::Serde(e.to_string()))?; - Ok((payload, PeerId(peer_id.into()))) + let libp2p_peer = message.verify()?; + let payload: T = willow_transport::unpack(&message.payload) + .map_err(|e| IdentityError::Serde(e.to_string()))?; + + Ok((payload, PeerId::from(libp2p_peer))) } +// ───── Tests ───────────────────────────────────────────────────────────────── + #[cfg(test)] mod tests { use super::*; #[test] - fn pack_and_unpack_works() { - let identity = Identity::new(); - let payload = 1; + fn generate_identity_is_unique() { + let a = Identity::generate(); + let b = Identity::generate(); + assert_ne!(a.peer_id(), b.peer_id()); + } - let data = pack(&payload, identity.clone()).unwrap(); - let (result, peer) = unpack::(&data).unwrap(); + #[test] + fn peer_id_round_trip_serde() { + let id = Identity::generate().peer_id(); + let bytes = willow_transport::pack(&id).unwrap(); + let decoded: PeerId = willow_transport::unpack(&bytes).unwrap(); + assert_eq!(decoded, id); + } + + #[test] + fn pack_and_unpack_verifies_signature() { + let alice = Identity::generate(); + let payload = "hello from alice"; + + let data = pack(&payload, &alice).unwrap(); + let (msg, peer) = unpack::(&data).unwrap(); + + assert_eq!(msg, payload); + assert_eq!(peer, alice.peer_id()); + } + + #[test] + fn tampered_payload_fails_verification() { + let alice = Identity::generate(); + let data = pack(&"original", &alice).unwrap(); - assert!(peer.0.is_public_key(&identity.0.public()).unwrap()); - assert_eq!(result, payload) + // Flip a byte near the end (inside the payload region). + let mut tampered = data.clone(); + let len = tampered.len(); + tampered[len - 2] ^= 0xFF; + + // Should fail to deserialize or verify. + let result = unpack::(&tampered); + assert!(result.is_err()); + } + + #[test] + fn user_profile_new() { + let peer = Identity::generate().peer_id(); + let profile = UserProfile::new(peer.clone(), "Alice"); + + assert_eq!(profile.peer_id, peer); + assert_eq!(profile.display_name, "Alice"); + assert!(profile.avatar.is_none()); + assert!(profile.status.is_none()); + assert!(profile.bio.is_none()); + } + + #[test] + fn user_profile_serde_round_trip() { + let peer = Identity::generate().peer_id(); + let mut profile = UserProfile::new(peer, "Bob"); + profile.status = Some("Online".into()); + profile.bio = Some("Just a test user".into()); + + let bytes = willow_transport::pack(&profile).unwrap(); + let decoded: UserProfile = willow_transport::unpack(&bytes).unwrap(); + + assert_eq!(decoded, profile); + } + + #[test] + fn identity_is_send_and_sync() { + fn assert_send_sync() {} + assert_send_sync::(); + assert_send_sync::(); + } + + #[test] + fn peer_id_display() { + let peer = Identity::generate().peer_id(); + let display = format!("{peer}"); + // libp2p PeerIds are base58 encoded, typically starting with "12D3" + assert!(!display.is_empty()); } } diff --git a/crates/messaging/Cargo.toml b/crates/messaging/Cargo.toml new file mode 100644 index 00000000..e13d0bf9 --- /dev/null +++ b/crates/messaging/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "willow-messaging" +edition.workspace = true +version.workspace = true +license.workspace = true +description = "Chat messages, reactions, threads, and Hybrid Logical Clock ordering for Willow" + +[dependencies] +willow-identity = { path = "../identity" } +willow-transport = { path = "../transport" } + +serde = { workspace = true } +chrono = { workspace = true } +uuid = { workspace = true } +thiserror = { workspace = true } + +[dev-dependencies] +tokio = { workspace = true } diff --git a/crates/messaging/src/hlc.rs b/crates/messaging/src/hlc.rs new file mode 100644 index 00000000..5e121007 --- /dev/null +++ b/crates/messaging/src/hlc.rs @@ -0,0 +1,280 @@ +//! # Hybrid Logical Clock (HLC) +//! +//! A clock that combines wall-clock time with a logical counter to produce +//! timestamps that are: +//! +//! 1. **Monotonically increasing** on each node. +//! 2. **Consistent across peers** even when system clocks drift slightly. +//! 3. **Totally ordered** — any two timestamps can be compared. +//! +//! ## How it works +//! +//! Each [`HlcTimestamp`] has two components: +//! +//! - `millis` — milliseconds since the Unix epoch (wall-clock component). +//! - `counter` — a logical counter that breaks ties when events happen within +//! the same millisecond. +//! +//! When generating a new timestamp ([`HLC::now`]): +//! +//! - Take `max(wall_clock, last_millis)`. +//! - If the millisecond didn't advance, increment the counter; otherwise reset +//! it to zero. +//! +//! When receiving a remote timestamp ([`HLC::receive`]): +//! +//! - Take `max(wall_clock, last_millis, remote_millis)`. +//! - Advance the counter past both the local and remote counters if in the same +//! millisecond. +//! +//! ## References +//! +//! Based on the algorithm from *"Logical Physical Clocks and Consistent +//! Snapshots in Globally Distributed Databases"* (Kulkarni et al., 2014). + +use serde::{Deserialize, Serialize}; + +/// A single HLC timestamp that can be compared, serialized, and sent over the +/// network. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +pub struct HlcTimestamp { + /// Wall-clock component (milliseconds since Unix epoch). + pub millis: u64, + /// Logical counter that breaks ties within the same millisecond. + pub counter: u32, +} + +impl HlcTimestamp { + /// The zero timestamp — used as an initial state. + pub const ZERO: Self = Self { + millis: 0, + counter: 0, + }; +} + +impl PartialOrd for HlcTimestamp { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for HlcTimestamp { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.millis + .cmp(&other.millis) + .then(self.counter.cmp(&other.counter)) + } +} + +impl std::fmt::Display for HlcTimestamp { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}:{}", self.millis, self.counter) + } +} + +// ───── HLC state machine ──────────────────────────────────────────────────── + +/// Returns the current wall-clock time in milliseconds since Unix epoch. +fn wall_clock_ms() -> u64 { + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .expect("system clock before Unix epoch") + .as_millis() as u64 +} + +/// A Hybrid Logical Clock instance. +/// +/// Each node in the network maintains its own `HLC`. It must be used to +/// timestamp **every** outbound event and updated on **every** inbound event. +/// +/// # Examples +/// +/// ``` +/// use willow_messaging::hlc::HLC; +/// +/// let mut clock = HLC::new(); +/// +/// // Generate a local timestamp. +/// let t1 = clock.now(); +/// let t2 = clock.now(); +/// assert!(t2 > t1); +/// ``` +pub struct HLC { + latest: HlcTimestamp, +} + +impl HLC { + /// Create a new clock starting from zero. + pub fn new() -> Self { + Self { + latest: HlcTimestamp::ZERO, + } + } + + /// The most recent timestamp this clock has produced or observed. + pub fn latest(&self) -> HlcTimestamp { + self.latest + } + + /// Generate a new timestamp for a local event. + /// + /// Guaranteed to be strictly greater than all previously generated or + /// received timestamps on this clock. + pub fn now(&mut self) -> HlcTimestamp { + let wall = wall_clock_ms(); + let millis = wall.max(self.latest.millis); + + let counter = if millis == self.latest.millis { + self.latest.counter + 1 + } else { + 0 + }; + + self.latest = HlcTimestamp { millis, counter }; + self.latest + } + + /// Update the clock after receiving a remote timestamp. + /// + /// Returns a new local timestamp that is strictly greater than both the + /// local clock and the remote timestamp. + pub fn receive(&mut self, remote: HlcTimestamp) -> HlcTimestamp { + let wall = wall_clock_ms(); + let millis = wall.max(self.latest.millis).max(remote.millis); + + let counter = if millis == self.latest.millis && millis == remote.millis { + self.latest.counter.max(remote.counter) + 1 + } else if millis == self.latest.millis { + self.latest.counter + 1 + } else if millis == remote.millis { + remote.counter + 1 + } else { + 0 + }; + + self.latest = HlcTimestamp { millis, counter }; + self.latest + } +} + +impl Default for HLC { + fn default() -> Self { + Self::new() + } +} + +// ───── Tests ───────────────────────────────────────────────────────────────── + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn now_is_monotonic() { + let mut clock = HLC::new(); + let t1 = clock.now(); + let t2 = clock.now(); + let t3 = clock.now(); + assert!(t1 < t2); + assert!(t2 < t3); + } + + #[test] + fn receive_advances_past_remote() { + let mut clock_a = HLC::new(); + let mut clock_b = HLC::new(); + + let a1 = clock_a.now(); + let b1 = clock_b.receive(a1); + + // B's timestamp must be strictly after A's. + assert!(b1 > a1); + } + + #[test] + fn receive_advances_past_local() { + let mut clock = HLC::new(); + let local = clock.now(); + + // Simulate a remote timestamp far in the future. + let remote = HlcTimestamp { + millis: local.millis + 100_000, + counter: 5, + }; + + let after = clock.receive(remote); + assert!(after > remote); + assert!(after > local); + } + + #[test] + fn counter_resets_on_new_millisecond() { + let mut clock = HLC::new(); + let t1 = clock.now(); + + // Force the clock forward by 1ms so the counter resets. + clock.latest = HlcTimestamp { + millis: t1.millis + 1, + counter: 0, + }; + let t2 = clock.now(); + + // Counter should be 0 or 1 (depending on whether wall clock caught up), + // but definitely not accumulated from the previous millisecond. + assert!(t2.counter <= 1); + } + + #[test] + fn timestamp_ordering_millis_first() { + let a = HlcTimestamp { + millis: 100, + counter: 999, + }; + let b = HlcTimestamp { + millis: 101, + counter: 0, + }; + assert!(a < b, "higher millis wins regardless of counter"); + } + + #[test] + fn timestamp_ordering_counter_breaks_tie() { + let a = HlcTimestamp { + millis: 100, + counter: 0, + }; + let b = HlcTimestamp { + millis: 100, + counter: 1, + }; + assert!(a < b); + } + + #[test] + fn timestamp_serde_round_trip() { + let ts = HlcTimestamp { + millis: 1_700_000_000_000, + counter: 42, + }; + let bytes = willow_transport::pack(&ts).unwrap(); + let decoded: HlcTimestamp = willow_transport::unpack(&bytes).unwrap(); + assert_eq!(decoded, ts); + } + + #[test] + fn two_clocks_converge() { + let mut clock_a = HLC::new(); + let mut clock_b = HLC::new(); + + // Simulate interleaved events. + let a1 = clock_a.now(); + let b1 = clock_b.receive(a1); + let a2 = clock_a.receive(b1); + let b2 = clock_b.receive(a2); + + // Each successive event should be strictly ordered. + assert!(a1 < b1); + assert!(b1 < a2); + assert!(a2 < b2); + } +} diff --git a/crates/messaging/src/lib.rs b/crates/messaging/src/lib.rs new file mode 100644 index 00000000..bae94e4c --- /dev/null +++ b/crates/messaging/src/lib.rs @@ -0,0 +1,329 @@ +//! # Willow Messaging +//! +//! Chat messages, reactions, threads, and distributed ordering for the Willow +//! P2P network. +//! +//! ## Hybrid Logical Clocks +//! +//! Because Willow has no central server, messages from different peers can +//! arrive out of order. We use a **Hybrid Logical Clock** ([`HLC`]) that +//! combines wall-clock time with a logical counter to establish a consistent +//! ordering across all peers — even when their system clocks disagree slightly. +//! +//! ## Message types +//! +//! A [`Message`] can carry different kinds of [`Content`]: +//! +//! - **Text** — plain chat messages with optional formatting +//! - **File** — a reference to a shared file (hash + metadata) +//! - **Reaction** — an emoji reaction to another message +//! - **Reply** — a threaded reply to another message +//! - **Edit** — a replacement for a previously sent message +//! - **Delete** — a tombstone marking a message as removed +//! - **System** — join / leave / channel events +//! +//! ## Message store +//! +//! The [`MessageStore`] trait abstracts over storage backends. +//! [`InMemoryStore`] provides a simple in-process implementation suitable for +//! testing and lightweight nodes. + +pub mod hlc; +pub mod store; + +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; +use uuid::Uuid; + +use willow_identity::PeerId; + +use crate::hlc::HlcTimestamp; + +// ───── IDs ─────────────────────────────────────────────────────────────────── + +/// Unique identifier for a message. +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub struct MessageId(pub Uuid); + +impl MessageId { + /// Generate a new random message ID. + pub fn new() -> Self { + Self(Uuid::new_v4()) + } +} + +impl Default for MessageId { + fn default() -> Self { + Self::new() + } +} + +impl std::fmt::Display for MessageId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} + +/// Unique identifier for a channel that messages belong to. +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub struct ChannelId(pub Uuid); + +impl ChannelId { + pub fn new() -> Self { + Self(Uuid::new_v4()) + } +} + +impl Default for ChannelId { + fn default() -> Self { + Self::new() + } +} + +impl std::fmt::Display for ChannelId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} + +// ───── Content types ───────────────────────────────────────────────────────── + +/// The payload inside a [`Message`]. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum Content { + /// A plain text chat message. + Text { + /// The message body (UTF-8, may contain markdown). + body: String, + }, + + /// A reference to a shared file. + File { + /// Content-addressed hash of the file data. + hash: String, + /// Original filename. + filename: String, + /// MIME type (e.g. `image/png`). + mime_type: String, + /// File size in bytes. + size_bytes: u64, + }, + + /// An emoji reaction to another message. + Reaction { + /// The message being reacted to. + target: MessageId, + /// The emoji (Unicode or custom shortcode). + emoji: String, + }, + + /// A threaded reply to another message. + Reply { + /// The message being replied to. + parent: MessageId, + /// The reply body. + body: String, + }, + + /// An edit replacing a previously sent message. + Edit { + /// The message being edited. + target: MessageId, + /// The new body text. + new_body: String, + }, + + /// A tombstone indicating a message was deleted. + Delete { + /// The message being deleted. + target: MessageId, + }, + + /// A system event (join, leave, channel rename, etc.). + System { + /// Human-readable description of the event. + description: String, + }, +} + +// ───── Message ─────────────────────────────────────────────────────────────── + +/// A single message in a Willow channel. +/// +/// Messages are immutable once created — edits and deletes are represented as +/// new messages that reference the original via [`Content::Edit`] and +/// [`Content::Delete`]. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct Message { + /// Unique identifier for this message. + pub id: MessageId, + /// The channel this message belongs to. + pub channel_id: ChannelId, + /// Who sent this message. + pub author: PeerId, + /// What the message contains. + pub content: Content, + /// Wall-clock time when the message was created. + pub created_at: DateTime, + /// Hybrid Logical Clock timestamp for consistent distributed ordering. + pub hlc: HlcTimestamp, +} + +impl Message { + /// Create a new text message. + pub fn text( + channel_id: ChannelId, + author: PeerId, + body: impl Into, + hlc: &mut hlc::HLC, + ) -> Self { + Self { + id: MessageId::new(), + channel_id, + author, + content: Content::Text { body: body.into() }, + created_at: Utc::now(), + hlc: hlc.now(), + } + } + + /// Create a reaction to another message. + pub fn reaction( + channel_id: ChannelId, + author: PeerId, + target: MessageId, + emoji: impl Into, + hlc: &mut hlc::HLC, + ) -> Self { + Self { + id: MessageId::new(), + channel_id, + author, + content: Content::Reaction { + target, + emoji: emoji.into(), + }, + created_at: Utc::now(), + hlc: hlc.now(), + } + } + + /// Create a reply to another message. + pub fn reply( + channel_id: ChannelId, + author: PeerId, + parent: MessageId, + body: impl Into, + hlc: &mut hlc::HLC, + ) -> Self { + Self { + id: MessageId::new(), + channel_id, + author, + content: Content::Reply { + parent, + body: body.into(), + }, + created_at: Utc::now(), + hlc: hlc.now(), + } + } + + /// Create a file-sharing message. + pub fn file( + channel_id: ChannelId, + author: PeerId, + hash: impl Into, + filename: impl Into, + mime_type: impl Into, + size_bytes: u64, + hlc: &mut hlc::HLC, + ) -> Self { + Self { + id: MessageId::new(), + channel_id, + author, + content: Content::File { + hash: hash.into(), + filename: filename.into(), + mime_type: mime_type.into(), + size_bytes, + }, + created_at: Utc::now(), + hlc: hlc.now(), + } + } +} + +// ───── Tests ───────────────────────────────────────────────────────────────── + +#[cfg(test)] +mod tests { + use super::*; + use willow_identity::Identity; + + #[test] + fn message_id_is_unique() { + let a = MessageId::new(); + let b = MessageId::new(); + assert_ne!(a, b); + } + + #[test] + fn create_text_message() { + let mut hlc = hlc::HLC::new(); + let peer = Identity::generate().peer_id(); + let channel = ChannelId::new(); + + let msg = Message::text(channel.clone(), peer.clone(), "hello!", &mut hlc); + + assert_eq!(msg.channel_id, channel); + assert_eq!(msg.author, peer); + assert!(matches!(msg.content, Content::Text { ref body } if body == "hello!")); + } + + #[test] + fn create_reaction() { + let mut hlc = hlc::HLC::new(); + let peer = Identity::generate().peer_id(); + let channel = ChannelId::new(); + let target = MessageId::new(); + + let msg = Message::reaction(channel, peer, target.clone(), "👍", &mut hlc); + + assert!(matches!( + msg.content, + Content::Reaction { target: ref t, ref emoji } if *t == target && emoji == "👍" + )); + } + + #[test] + fn create_reply() { + let mut hlc = hlc::HLC::new(); + let peer = Identity::generate().peer_id(); + let channel = ChannelId::new(); + let parent = MessageId::new(); + + let msg = Message::reply(channel, peer, parent.clone(), "I agree", &mut hlc); + + assert!(matches!( + msg.content, + Content::Reply { parent: ref p, ref body } if *p == parent && body == "I agree" + )); + } + + #[test] + fn message_serde_round_trip() { + let mut hlc = hlc::HLC::new(); + let peer = Identity::generate().peer_id(); + let channel = ChannelId::new(); + + let msg = Message::text(channel, peer, "serialize me", &mut hlc); + let bytes = willow_transport::pack(&msg).unwrap(); + let decoded: Message = willow_transport::unpack(&bytes).unwrap(); + + assert_eq!(decoded.id, msg.id); + assert_eq!(decoded.content, msg.content); + assert_eq!(decoded.author, msg.author); + } +} diff --git a/crates/messaging/src/store.rs b/crates/messaging/src/store.rs new file mode 100644 index 00000000..4f899919 --- /dev/null +++ b/crates/messaging/src/store.rs @@ -0,0 +1,238 @@ +//! # Message Storage +//! +//! Pluggable backends for persisting and querying messages. +//! +//! The [`MessageStore`] trait defines the interface that every storage backend +//! must implement. [`InMemoryStore`] is a simple reference implementation that +//! keeps everything in RAM — perfect for tests and lightweight nodes. + +use std::collections::HashMap; + +use crate::{ChannelId, Message, MessageId}; + +/// Errors that can occur during storage operations. +#[derive(Debug, thiserror::Error)] +pub enum StoreError { + /// Attempted to insert a message whose ID already exists. + #[error("duplicate message id: {0}")] + DuplicateId(MessageId), + + /// The requested message was not found. + #[error("message not found: {0}")] + NotFound(MessageId), +} + +/// Trait for message storage backends. +/// +/// Implementations must support inserting, retrieving, and listing messages by +/// channel. All operations are synchronous; async wrappers can be layered on +/// top via `tokio::task::spawn_blocking` if needed. +pub trait MessageStore: Send + Sync { + /// Insert a message. Returns an error if the message ID already exists. + fn insert(&mut self, message: Message) -> Result<(), StoreError>; + + /// Retrieve a single message by ID. + fn get(&self, id: &MessageId) -> Result<&Message, StoreError>; + + /// List all messages in a channel, ordered by HLC timestamp. + fn list_channel(&self, channel_id: &ChannelId) -> Vec<&Message>; + + /// Total number of stored messages. + fn len(&self) -> usize; + + /// Whether the store is empty. + fn is_empty(&self) -> bool { + self.len() == 0 + } +} + +/// A simple in-memory message store. +/// +/// Messages are stored in a `HashMap` keyed by [`MessageId`] and indexed by +/// [`ChannelId`] for fast channel listing. +/// +/// **Not persistent** — all data is lost when the process exits. Use this for +/// testing or as a starting point before implementing a disk-backed store. +/// +/// # Examples +/// +/// ``` +/// use willow_messaging::{Message, ChannelId, hlc::HLC, store::{InMemoryStore, MessageStore}}; +/// use willow_identity::Identity; +/// +/// let mut store = InMemoryStore::new(); +/// let mut hlc = HLC::new(); +/// let peer = Identity::generate().peer_id(); +/// let channel = ChannelId::new(); +/// +/// let msg = Message::text(channel.clone(), peer, "hello", &mut hlc); +/// store.insert(msg).unwrap(); +/// +/// assert_eq!(store.list_channel(&channel).len(), 1); +/// ``` +#[derive(Debug, Default)] +pub struct InMemoryStore { + /// All messages keyed by their unique ID. + messages: HashMap, + /// Index: channel ID → ordered list of message IDs. + channel_index: HashMap>, +} + +impl InMemoryStore { + /// Create an empty store. + pub fn new() -> Self { + Self::default() + } +} + +impl MessageStore for InMemoryStore { + fn insert(&mut self, message: Message) -> Result<(), StoreError> { + if self.messages.contains_key(&message.id) { + return Err(StoreError::DuplicateId(message.id)); + } + + let id = message.id.clone(); + let channel_id = message.channel_id.clone(); + + self.messages.insert(id.clone(), message); + + let ids = self.channel_index.entry(channel_id).or_default(); + ids.push(id); + + // Keep the channel index sorted by HLC timestamp. + ids.sort_by(|a, b| { + let ma = &self.messages[a]; + let mb = &self.messages[b]; + ma.hlc.cmp(&mb.hlc) + }); + + Ok(()) + } + + fn get(&self, id: &MessageId) -> Result<&Message, StoreError> { + self.messages + .get(id) + .ok_or_else(|| StoreError::NotFound(id.clone())) + } + + fn list_channel(&self, channel_id: &ChannelId) -> Vec<&Message> { + self.channel_index + .get(channel_id) + .map(|ids| { + ids.iter() + .filter_map(|id| self.messages.get(id)) + .collect() + }) + .unwrap_or_default() + } + + fn len(&self) -> usize { + self.messages.len() + } +} + +// ───── Tests ───────────────────────────────────────────────────────────────── + +#[cfg(test)] +mod tests { + use super::*; + use crate::hlc::HLC; + use willow_identity::Identity; + + fn make_text_msg(channel: &ChannelId, hlc: &mut HLC) -> Message { + let peer = Identity::generate().peer_id(); + Message::text(channel.clone(), peer, "test", hlc) + } + + #[test] + fn insert_and_get() { + let mut store = InMemoryStore::new(); + let mut hlc = HLC::new(); + let channel = ChannelId::new(); + let msg = make_text_msg(&channel, &mut hlc); + let id = msg.id.clone(); + + store.insert(msg).unwrap(); + + let retrieved = store.get(&id).unwrap(); + assert_eq!(retrieved.id, id); + } + + #[test] + fn duplicate_insert_rejected() { + let mut store = InMemoryStore::new(); + let mut hlc = HLC::new(); + let channel = ChannelId::new(); + let msg = make_text_msg(&channel, &mut hlc); + + store.insert(msg.clone()).unwrap(); + let result = store.insert(msg); + + assert!(result.is_err()); + assert!(matches!(result.unwrap_err(), StoreError::DuplicateId(_))); + } + + #[test] + fn get_missing_returns_not_found() { + let store = InMemoryStore::new(); + let result = store.get(&MessageId::new()); + assert!(matches!(result.unwrap_err(), StoreError::NotFound(_))); + } + + #[test] + fn list_channel_returns_ordered() { + let mut store = InMemoryStore::new(); + let mut hlc = HLC::new(); + let channel = ChannelId::new(); + + let m1 = make_text_msg(&channel, &mut hlc); + let m2 = make_text_msg(&channel, &mut hlc); + let m3 = make_text_msg(&channel, &mut hlc); + + // Insert out of order. + store.insert(m3.clone()).unwrap(); + store.insert(m1.clone()).unwrap(); + store.insert(m2.clone()).unwrap(); + + let listed = store.list_channel(&channel); + assert_eq!(listed.len(), 3); + assert!(listed[0].hlc <= listed[1].hlc); + assert!(listed[1].hlc <= listed[2].hlc); + } + + #[test] + fn list_empty_channel() { + let store = InMemoryStore::new(); + let channel = ChannelId::new(); + assert!(store.list_channel(&channel).is_empty()); + } + + #[test] + fn messages_are_channel_isolated() { + let mut store = InMemoryStore::new(); + let mut hlc = HLC::new(); + let ch_a = ChannelId::new(); + let ch_b = ChannelId::new(); + + store.insert(make_text_msg(&ch_a, &mut hlc)).unwrap(); + store.insert(make_text_msg(&ch_a, &mut hlc)).unwrap(); + store.insert(make_text_msg(&ch_b, &mut hlc)).unwrap(); + + assert_eq!(store.list_channel(&ch_a).len(), 2); + assert_eq!(store.list_channel(&ch_b).len(), 1); + } + + #[test] + fn len_and_is_empty() { + let mut store = InMemoryStore::new(); + assert!(store.is_empty()); + assert_eq!(store.len(), 0); + + let mut hlc = HLC::new(); + let channel = ChannelId::new(); + store.insert(make_text_msg(&channel, &mut hlc)).unwrap(); + + assert!(!store.is_empty()); + assert_eq!(store.len(), 1); + } +} diff --git a/crates/network/Cargo.toml b/crates/network/Cargo.toml new file mode 100644 index 00000000..ca877d8b --- /dev/null +++ b/crates/network/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "willow-network" +edition.workspace = true +version.workspace = true +license.workspace = true +description = "P2P networking layer for Willow using libp2p with tokio" + +[dependencies] +willow-identity = { path = "../identity" } +willow-transport = { path = "../transport" } + +libp2p = { workspace = true } +tokio = { workspace = true } +serde = { workspace = true } +anyhow = { workspace = true } +thiserror = { workspace = true } +tracing = { workspace = true } + +[dev-dependencies] +tracing-subscriber = { workspace = true } diff --git a/crates/network/src/behaviour.rs b/crates/network/src/behaviour.rs new file mode 100644 index 00000000..961e14c6 --- /dev/null +++ b/crates/network/src/behaviour.rs @@ -0,0 +1,31 @@ +//! Composite libp2p network behaviour for Willow. +//! +//! [`WillowBehaviour`] combines GossipSub, Kademlia, mDNS, Identify, and +//! Relay Client into a single [`NetworkBehaviour`] that drives the swarm. + +use libp2p::{gossipsub, identify, kad, mdns, relay, swarm::NetworkBehaviour}; + +/// The composite behaviour that powers a Willow peer. +/// +/// Each sub-behaviour handles a different concern: +/// +/// | Field | Protocol | Purpose | +/// |-------------|------------|----------------------------------------| +/// | `gossipsub` | GossipSub | Pub/sub message flooding per topic | +/// | `kademlia` | Kademlia | DHT for peer/content discovery | +/// | `mdns` | mDNS | LAN peer discovery | +/// | `identify` | Identify | Peer metadata exchange on connect | +/// | `relay` | Relay | NAT traversal via relay nodes | +#[derive(NetworkBehaviour)] +pub struct WillowBehaviour { + /// Pub/sub messaging — one topic per channel. + pub gossipsub: gossipsub::Behaviour, + /// Distributed hash table for discovery. + pub kademlia: kad::Behaviour, + /// Local network peer discovery. + pub mdns: mdns::tokio::Behaviour, + /// Peer identification and metadata exchange. + pub identify: identify::Behaviour, + /// Relay client for NAT traversal. + pub relay: relay::client::Behaviour, +} diff --git a/crates/network/src/config.rs b/crates/network/src/config.rs new file mode 100644 index 00000000..b7088a99 --- /dev/null +++ b/crates/network/src/config.rs @@ -0,0 +1,38 @@ +//! Network configuration. + +use std::time::Duration; + +use libp2p::Multiaddr; + +/// Configuration for a [`NetworkNode`](crate::NetworkNode). +/// +/// Provides sane defaults for a friend-group deployment. Override fields as +/// needed for larger or more specialised networks. +#[derive(Debug, Clone)] +pub struct NetworkConfig { + /// Address to listen on. Defaults to all interfaces, OS-assigned port. + pub listen_addr: Multiaddr, + + /// Bootstrap peers to connect to on startup (Kademlia). + /// + /// For a friend group this might be one VPS node that is always online. An + /// empty list means "discover peers via mDNS only". + pub bootstrap_peers: Vec<(libp2p::PeerId, Multiaddr)>, + + /// How long idle connections stay open before being closed. + pub idle_timeout: Duration, + + /// GossipSub heartbeat interval. + pub gossipsub_heartbeat: Duration, +} + +impl Default for NetworkConfig { + fn default() -> Self { + Self { + listen_addr: "/ip4/0.0.0.0/tcp/0".parse().expect("valid multiaddr"), + bootstrap_peers: Vec::new(), + idle_timeout: Duration::from_secs(120), + gossipsub_heartbeat: Duration::from_secs(1), + } + } +} diff --git a/crates/network/src/lib.rs b/crates/network/src/lib.rs new file mode 100644 index 00000000..629272e5 --- /dev/null +++ b/crates/network/src/lib.rs @@ -0,0 +1,63 @@ +//! # Willow Network +//! +//! The P2P networking layer for Willow, built on [libp2p] with a [tokio] +//! runtime. +//! +//! ## Architecture +//! +//! The network is composed of several libp2p protocols working together: +//! +//! - **[GossipSub]** — Pub/sub messaging. Each channel maps to a gossipsub +//! topic, so messages are flooded only to interested peers. +//! - **[Kademlia]** — Distributed hash table for peer discovery and content +//! routing beyond the local network. +//! - **[mDNS]** — Automatic discovery of peers on the same LAN. +//! - **[Identify]** — Exchange of peer metadata on connection. +//! - **[Relay]** — NAT traversal via relay nodes when direct connections fail. +//! +//! ## Usage +//! +//! ```no_run +//! use willow_network::{NetworkConfig, NetworkNode, NetworkEvent}; +//! use willow_identity::Identity; +//! +//! # async fn example() -> anyhow::Result<()> { +//! let identity = Identity::generate(); +//! let config = NetworkConfig::default(); +//! let (mut node, mut events) = NetworkNode::start(identity, config).await?; +//! +//! // Subscribe to a channel topic. +//! node.subscribe("my-server/general")?; +//! +//! // Publish a message. +//! node.publish("my-server/general", b"hello everyone".to_vec())?; +//! +//! // Process incoming events. +//! while let Some(event) = events.recv().await { +//! match event { +//! NetworkEvent::Message { topic, data, source } => { +//! println!("got message on {topic} from {source:?}"); +//! } +//! NetworkEvent::PeerConnected(peer) => { +//! println!("peer connected: {peer}"); +//! } +//! _ => {} +//! } +//! } +//! # Ok(()) +//! # } +//! ``` +//! +//! [GossipSub]: libp2p::gossipsub +//! [Kademlia]: libp2p::kad +//! [mDNS]: libp2p::mdns +//! [Identify]: libp2p::identify +//! [Relay]: libp2p::relay + +pub mod behaviour; +pub mod config; +pub mod node; + +pub use behaviour::WillowBehaviour; +pub use config::NetworkConfig; +pub use node::{NetworkEvent, NetworkNode}; diff --git a/crates/network/src/node.rs b/crates/network/src/node.rs new file mode 100644 index 00000000..8a41ce37 --- /dev/null +++ b/crates/network/src/node.rs @@ -0,0 +1,355 @@ +//! The high-level network node that manages the libp2p swarm. +//! +//! [`NetworkNode`] owns the swarm and runs it on a background tokio task. +//! Callers interact with it through a command-style API (subscribe, publish, +//! dial) and receive events via a [`tokio::sync::mpsc`] channel. + +use anyhow::{Context, Result}; +use libp2p::{ + gossipsub, identify, kad, mdns, noise, + swarm::SwarmEvent, + tcp, yamux, Multiaddr, PeerId, Swarm, SwarmBuilder, +}; +use tokio::sync::mpsc; +use tracing::{debug, info, warn}; + +use crate::{NetworkConfig, WillowBehaviour}; +use willow_identity::Identity; + +// ───── Events ──────────────────────────────────────────────────────────────── + +/// Events emitted by the network layer to the application. +#[derive(Debug, Clone)] +pub enum NetworkEvent { + /// A message was received on a subscribed topic. + Message { + /// The topic (channel) the message was published to. + topic: String, + /// Raw message bytes. + data: Vec, + /// The peer that published the message (if known). + source: Option, + }, + + /// A new peer connected to us. + PeerConnected(PeerId), + + /// A peer disconnected. + PeerDisconnected(PeerId), + + /// A new peer was discovered on the local network via mDNS. + PeerDiscovered { + peer_id: PeerId, + addrs: Vec, + }, + + /// We started listening on an address. + Listening(Multiaddr), +} + +// ───── Commands ────────────────────────────────────────────────────────────── + +/// Internal commands sent from the `NetworkNode` handle to the swarm task. +#[derive(Debug)] +enum Command { + Subscribe(String), + Unsubscribe(String), + Publish { topic: String, data: Vec }, + Dial(Multiaddr), +} + +// ───── NetworkNode ─────────────────────────────────────────────────────────── + +/// Handle to a running Willow network node. +/// +/// This is the main entry point for the networking layer. Call +/// [`NetworkNode::start`] to launch the libp2p swarm on a background task, +/// then use the returned handle to subscribe to topics, publish messages, and +/// connect to peers. +/// +/// Events from the network are delivered through the +/// [`mpsc::Receiver`] returned alongside the handle. +pub struct NetworkNode { + command_tx: mpsc::UnboundedSender, + local_peer_id: PeerId, +} + +impl NetworkNode { + /// Start the network node and return a handle + event stream. + /// + /// This spawns a background tokio task that drives the libp2p swarm. + /// + /// # Arguments + /// + /// - `identity` — the local peer's cryptographic identity. + /// - `config` — network configuration (listen address, bootstrap peers, etc.). + /// + /// # Returns + /// + /// A tuple of `(handle, event_receiver)`. Use the handle to send commands + /// and the receiver to consume network events. + pub async fn start( + identity: Identity, + config: NetworkConfig, + ) -> Result<(Self, mpsc::UnboundedReceiver)> { + let keypair = identity.keypair().clone(); + let local_peer_id = PeerId::from(keypair.public()); + + info!(%local_peer_id, "starting willow network node"); + + let mut swarm = build_swarm(keypair.clone(), &config)?; + + swarm + .listen_on(config.listen_addr.clone()) + .context("failed to listen")?; + + // Bootstrap Kademlia if we have known peers. + for (peer, addr) in &config.bootstrap_peers { + swarm.behaviour_mut().kademlia.add_address(peer, addr.clone()); + } + if !config.bootstrap_peers.is_empty() { + swarm + .behaviour_mut() + .kademlia + .bootstrap() + .ok(); + } + + let (command_tx, command_rx) = mpsc::unbounded_channel(); + let (event_tx, event_rx) = mpsc::unbounded_channel(); + + tokio::spawn(run_swarm(swarm, command_rx, event_tx)); + + let node = Self { + command_tx, + local_peer_id, + }; + + Ok((node, event_rx)) + } + + /// The local peer ID of this node. + pub fn peer_id(&self) -> PeerId { + self.local_peer_id + } + + /// Subscribe to a gossipsub topic (typically a channel identifier). + pub fn subscribe(&self, topic: &str) -> Result<()> { + self.command_tx + .send(Command::Subscribe(topic.to_string())) + .map_err(|_| anyhow::anyhow!("swarm task has stopped"))?; + Ok(()) + } + + /// Unsubscribe from a gossipsub topic. + pub fn unsubscribe(&self, topic: &str) -> Result<()> { + self.command_tx + .send(Command::Unsubscribe(topic.to_string())) + .map_err(|_| anyhow::anyhow!("swarm task has stopped"))?; + Ok(()) + } + + /// Publish a message to a gossipsub topic. + pub fn publish(&self, topic: &str, data: Vec) -> Result<()> { + self.command_tx + .send(Command::Publish { + topic: topic.to_string(), + data, + }) + .map_err(|_| anyhow::anyhow!("swarm task has stopped"))?; + Ok(()) + } + + /// Dial a remote peer by multiaddress. + pub fn dial(&self, addr: Multiaddr) -> Result<()> { + self.command_tx + .send(Command::Dial(addr)) + .map_err(|_| anyhow::anyhow!("swarm task has stopped"))?; + Ok(()) + } +} + +// ───── Swarm construction ──────────────────────────────────────────────────── + +/// Build the libp2p swarm with all configured protocols. +fn build_swarm( + keypair: libp2p::identity::Keypair, + config: &NetworkConfig, +) -> Result> { + let peer_id = PeerId::from(keypair.public()); + + let swarm = SwarmBuilder::with_existing_identity(keypair) + .with_tokio() + .with_tcp( + tcp::Config::default(), + noise::Config::new, + yamux::Config::default, + )? + .with_relay_client(noise::Config::new, yamux::Config::default)? + .with_behaviour(|key, relay_behaviour| { + // GossipSub — use content-based message IDs to deduplicate. + let gossipsub_config = gossipsub::ConfigBuilder::default() + .heartbeat_interval(config.gossipsub_heartbeat) + .validation_mode(gossipsub::ValidationMode::Strict) + .message_id_fn(|msg: &gossipsub::Message| { + use std::collections::hash_map::DefaultHasher; + use std::hash::{Hash, Hasher}; + let mut hasher = DefaultHasher::new(); + msg.data.hash(&mut hasher); + msg.topic.hash(&mut hasher); + gossipsub::MessageId::from(hasher.finish().to_string()) + }) + .build() + .expect("valid gossipsub config"); + + let gossipsub = gossipsub::Behaviour::new( + gossipsub::MessageAuthenticity::Signed(key.clone()), + gossipsub_config, + ) + .expect("valid gossipsub behaviour"); + + // Kademlia + let kademlia = kad::Behaviour::new(peer_id, kad::store::MemoryStore::new(peer_id)); + + // mDNS + let mdns = mdns::tokio::Behaviour::new( + mdns::Config::default(), + peer_id, + ) + .expect("valid mdns behaviour"); + + // Identify + let identify = identify::Behaviour::new(identify::Config::new( + "/willow/1.0.0".to_string(), + key.public(), + )); + + Ok(WillowBehaviour { + gossipsub, + kademlia, + mdns, + identify, + relay: relay_behaviour, + }) + })? + .with_swarm_config(|c| c.with_idle_connection_timeout(config.idle_timeout)) + .build(); + + Ok(swarm) +} + +// ───── Swarm event loop ────────────────────────────────────────────────────── + +/// The main event loop that drives the swarm on a background task. +async fn run_swarm( + mut swarm: Swarm, + mut commands: mpsc::UnboundedReceiver, + events: mpsc::UnboundedSender, +) { + loop { + tokio::select! { + // Process commands from the handle. + cmd = commands.recv() => { + match cmd { + Some(Command::Subscribe(topic)) => { + let topic = gossipsub::IdentTopic::new(&topic); + if let Err(e) = swarm.behaviour_mut().gossipsub.subscribe(&topic) { + warn!(%e, "failed to subscribe"); + } + } + Some(Command::Unsubscribe(topic)) => { + let topic = gossipsub::IdentTopic::new(&topic); + if let Err(e) = swarm.behaviour_mut().gossipsub.unsubscribe(&topic) { + warn!(%e, "failed to unsubscribe"); + } + } + Some(Command::Publish { topic, data }) => { + let topic = gossipsub::IdentTopic::new(&topic); + if let Err(e) = swarm.behaviour_mut().gossipsub.publish(topic, data) { + warn!(%e, "failed to publish"); + } + } + Some(Command::Dial(addr)) => { + if let Err(e) = swarm.dial(addr) { + warn!(%e, "failed to dial"); + } + } + None => { + info!("command channel closed, shutting down swarm"); + return; + } + } + } + + // Process swarm events. + event = swarm.select_next_some() => { + match event { + SwarmEvent::Behaviour(behaviour::WillowBehaviourEvent::Gossipsub( + gossipsub::Event::Message { message, .. }, + )) => { + let topic = message.topic.to_string(); + let _ = events.send(NetworkEvent::Message { + topic, + data: message.data, + source: message.source, + }); + } + + SwarmEvent::Behaviour(behaviour::WillowBehaviourEvent::Mdns( + mdns::Event::Discovered(peers), + )) => { + for (peer_id, addr) in peers { + debug!(%peer_id, %addr, "mDNS: discovered peer"); + swarm.behaviour_mut().gossipsub.add_explicit_peer(&peer_id); + swarm.behaviour_mut().kademlia.add_address(&peer_id, addr.clone()); + let _ = events.send(NetworkEvent::PeerDiscovered { + peer_id, + addrs: vec![addr], + }); + } + } + + SwarmEvent::Behaviour(behaviour::WillowBehaviourEvent::Mdns( + mdns::Event::Expired(peers), + )) => { + for (peer_id, _addr) in peers { + debug!(%peer_id, "mDNS: peer expired"); + swarm.behaviour_mut().gossipsub.remove_explicit_peer(&peer_id); + } + } + + SwarmEvent::Behaviour(behaviour::WillowBehaviourEvent::Identify( + identify::Event::Received { peer_id, info }, + )) => { + debug!(%peer_id, protocol = %info.protocol_version, "identify: received"); + for addr in info.listen_addrs { + swarm.behaviour_mut().kademlia.add_address(&peer_id, addr); + } + } + + SwarmEvent::ConnectionEstablished { peer_id, .. } => { + info!(%peer_id, "connection established"); + let _ = events.send(NetworkEvent::PeerConnected(peer_id)); + } + + SwarmEvent::ConnectionClosed { peer_id, .. } => { + debug!(%peer_id, "connection closed"); + let _ = events.send(NetworkEvent::PeerDisconnected(peer_id)); + } + + SwarmEvent::NewListenAddr { address, .. } => { + info!(%address, "listening on"); + let _ = events.send(NetworkEvent::Listening(address)); + } + + _ => {} + } + } + } + } +} + +// We import behaviour module to reference the generated event enum. +use crate::behaviour; + +use libp2p::futures::StreamExt; diff --git a/crates/timeline/Cargo.toml b/crates/timeline/Cargo.toml deleted file mode 100644 index 3a150607..00000000 --- a/crates/timeline/Cargo.toml +++ /dev/null @@ -1,15 +0,0 @@ -[package] -name = "timeline" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -identity = { path = "../identity" } - -chrono = { version = "0.4", features = ["wasmbind", "serde"] } -yew = { git = "https://github.com/yewstack/yew.git", features = ["csr"] } -yewdux = { git = "https://github.com/intendednull/yewdux.git" } -yewdux-input = { git = "https://github.com/intendednull/yewdux.git" } -uuid = { version = "1.2", features = ["v4"] } diff --git a/crates/timeline/src/lib.rs b/crates/timeline/src/lib.rs deleted file mode 100644 index 0462ae02..00000000 --- a/crates/timeline/src/lib.rs +++ /dev/null @@ -1,138 +0,0 @@ -use std::{collections::HashMap, rc::Rc}; - -use anyhow::ensure; -use chrono::{DateTime, Utc}; -use uuid::Uuid; -use yewdux::prelude::*; - -use identity::PeerId; - -#[derive(Store, Default, PartialEq, Clone, Debug)] -pub struct Timelines { - inner: HashMap, -} - -impl Timelines { - fn create_post(&mut self, id: &PeerId, mut post: Post) -> anyhow::Result<()> { - // Ensure the author is who they say they are. - ensure!(post.author == id.clone()); - - let timeline = self.get_mut(id); - // Ensure it's impossible to overwrite existing posts. - ensure!(!timeline.history.contains_key(&post.id)); - // Add new post - post.timestamp = Utc::now(); - timeline.history.insert(post.id.clone(), post); - - Ok(()) - } - - fn get_mut(&mut self, id: &PeerId) -> &mut Timeline { - self.inner.entry(id.clone()).or_insert_with(|| Timeline { - author: id.clone(), - history: Default::default(), - }) - } -} - -#[derive(PartialEq, Clone, Debug)] -pub struct Timeline { - author: PeerId, - history: HashMap, -} - -#[derive(PartialEq, Eq, Clone, Debug, Hash)] -pub struct PostId(Rc); - -#[derive(PartialEq, Clone, Debug)] -pub struct Post { - id: PostId, - author: PeerId, - content: String, - timestamp: DateTime, -} - -impl Post { - pub fn new(author: PeerId, content: String) -> Self { - Self { - author, - content, - timestamp: Utc::now(), - id: PostId(Uuid::new_v4().into()), - } - } -} - -#[derive(PartialEq, Clone, Debug)] -pub enum Action { - CreatePost(PeerId, Post), -} - -impl Reducer for Action { - fn apply(self, mut timelines: Rc) -> Rc { - let state = Rc::make_mut(&mut timelines); - - match self { - Action::CreatePost(id, post) => { - state.create_post(&id, post).ok(); - } - } - - timelines - } -} - -#[cfg(test)] -mod tests { - use identity::Identity; - - use super::*; - - #[test] - fn post_assures_author_id() { - let peer1 = Identity::new().as_peer(); - let peer2 = Identity::new().as_peer(); - let action = Action::CreatePost(peer2.clone(), Post::new(peer1, "".into())); - - let timelines = action.apply(Rc::new(Default::default())); - - let tl = timelines.inner.get(&peer2); - - assert!(tl.is_none()); - } - - #[test] - fn post_does_not_overwrite() { - let id = Identity::new().as_peer(); - - let mut post = Post::new(id.clone(), "".into()); - let t1 = Action::CreatePost(id.clone(), post.clone()).apply(Rc::new(Default::default())); - post.content = "some new data".into(); - let t2 = Action::CreatePost(id, post).apply(t1.clone()); - - assert_eq!(t1, t2); - } - - #[test] - fn post_timestamp_is_updated() { - let id = Identity::new().as_peer(); - - let mut post = Post::new(id.clone(), "".into()); - let t1 = Utc::now() - chrono::Duration::days(1); - post.timestamp = t1; - let timeline = Action::CreatePost(id.clone(), post).apply(Rc::new(Default::default())); - - let t2 = timeline - .inner - .get(&id) - .unwrap() - .history - .values() - .next() - .unwrap() - .timestamp; - - assert_ne!(t2, t1); - // assert_eq!(t2, Utc::now()); - } -} diff --git a/crates/transport/Cargo.toml b/crates/transport/Cargo.toml index 4189f929..1dbd5f16 100644 --- a/crates/transport/Cargo.toml +++ b/crates/transport/Cargo.toml @@ -1,11 +1,15 @@ [package] -name = "transport" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +name = "willow-transport" +edition.workspace = true +version.workspace = true +license.workspace = true +description = "Binary serialization and protocol framing for Willow P2P messages" [dependencies] -serde = "1.0.124" -bincode = "1.3.2" -anyhow = "1.0.66" +serde = { workspace = true } +bincode = { workspace = true } +anyhow = { workspace = true } +thiserror = { workspace = true } + +[dev-dependencies] +tokio = { workspace = true } diff --git a/crates/transport/src/lib.rs b/crates/transport/src/lib.rs index dfc1b87a..c43afdb2 100644 --- a/crates/transport/src/lib.rs +++ b/crates/transport/src/lib.rs @@ -1,9 +1,290 @@ -use serde::{de::DeserializeOwned, Serialize}; +//! # Willow Transport +//! +//! Binary serialization and protocol framing for Willow P2P messages. +//! +//! This crate provides the lowest-level building block for Willow's networking +//! stack: converting Rust types to bytes and back. Every message that crosses +//! the network goes through this layer. +//! +//! ## Protocol Envelope +//! +//! All messages are wrapped in an [`Envelope`] that carries a protocol version +//! and a type tag so that peers can negotiate compatibility and dispatch +//! messages to the right handler. +//! +//! ## Examples +//! +//! ``` +//! use willow_transport::{pack, unpack}; +//! +//! let greeting = String::from("hello, willow"); +//! let bytes = pack(&greeting).unwrap(); +//! let decoded: String = unpack(&bytes).unwrap(); +//! assert_eq!(decoded, "hello, willow"); +//! ``` -pub fn pack(data: &T) -> anyhow::Result> { - Ok(bincode::serialize(data)?) +use serde::{de::DeserializeOwned, Deserialize, Serialize}; + +/// Current protocol version. Bumped whenever the wire format changes in an +/// incompatible way. +pub const PROTOCOL_VERSION: u16 = 1; + +// ───── Errors ──────────────────────────────────────────────────────────────── + +/// Errors that can occur during serialization or deserialization. +#[derive(Debug, thiserror::Error)] +pub enum TransportError { + /// Failed to serialize a value to bytes. + #[error("serialization failed: {0}")] + Serialize(String), + + /// Failed to deserialize bytes back into a value. + #[error("deserialization failed: {0}")] + Deserialize(String), + + /// The remote peer is speaking a protocol version we don't understand. + #[error("unsupported protocol version {got} (expected {expected})")] + UnsupportedVersion { expected: u16, got: u16 }, +} + +// ───── Message Types ───────────────────────────────────────────────────────── + +/// Identifies the kind of payload inside an [`Envelope`]. +/// +/// This lets the receiving peer dispatch the raw bytes to the correct +/// deserializer without having to guess. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[repr(u8)] +pub enum MessageType { + /// A chat message (text, reactions, edits, etc.). + Chat = 0, + /// A channel or server management operation. + Channel = 1, + /// Peer identity or profile information. + Identity = 2, + /// File transfer metadata or chunk. + File = 3, + /// WebRTC signaling payload (offer, answer, ICE candidates). + Signal = 4, + /// Presence or status update. + Presence = 5, + /// Application-level ping / keep-alive. + Ping = 6, +} + +// ───── Envelope ────────────────────────────────────────────────────────────── + +/// A versioned wrapper around an arbitrary payload. +/// +/// Every byte sequence that enters or leaves the network is framed inside an +/// `Envelope` so that peers can: +/// +/// 1. Reject messages from incompatible protocol versions early. +/// 2. Route the inner payload to the correct handler based on [`MessageType`]. +/// +/// ``` +/// use willow_transport::{Envelope, MessageType, pack, unpack}; +/// +/// let envelope = Envelope::new(MessageType::Chat, b"hello".to_vec()); +/// let bytes = pack(&envelope).unwrap(); +/// let decoded: Envelope = unpack(&bytes).unwrap(); +/// assert_eq!(decoded.message_type, MessageType::Chat); +/// ``` +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Envelope { + /// Protocol version that produced this envelope. + pub version: u16, + /// What kind of payload is inside. + pub message_type: MessageType, + /// The serialized inner payload. + pub payload: Vec, +} + +impl Envelope { + /// Create a new envelope stamped with the current [`PROTOCOL_VERSION`]. + pub fn new(message_type: MessageType, payload: Vec) -> Self { + Self { + version: PROTOCOL_VERSION, + message_type, + payload, + } + } + + /// Validate that this envelope's version is compatible with ours. + pub fn validate_version(&self) -> Result<(), TransportError> { + if self.version != PROTOCOL_VERSION { + return Err(TransportError::UnsupportedVersion { + expected: PROTOCOL_VERSION, + got: self.version, + }); + } + Ok(()) + } +} + +// ───── Core serialization ──────────────────────────────────────────────────── + +/// Serialize any [`Serialize`]-able value to a byte vector using bincode. +/// +/// # Errors +/// +/// Returns [`TransportError::Serialize`] if bincode encoding fails. +pub fn pack(data: &T) -> Result, TransportError> { + bincode::serialize(data).map_err(|e| TransportError::Serialize(e.to_string())) +} + +/// Deserialize a byte slice back into a concrete type. +/// +/// # Errors +/// +/// Returns [`TransportError::Deserialize`] if bincode decoding fails or the +/// bytes don't match the expected type. +pub fn unpack(data: &[u8]) -> Result { + bincode::deserialize(data).map_err(|e| TransportError::Deserialize(e.to_string())) } -pub fn unpack(data: &[u8]) -> anyhow::Result { - Ok(bincode::deserialize(data)?) +/// Wrap an inner payload inside a versioned [`Envelope`], then serialize the +/// whole thing. +/// +/// This is the primary function for outbound messages — it handles both the +/// inner serialization and the framing in a single call. +/// +/// # Errors +/// +/// Returns [`TransportError::Serialize`] if either the payload or the envelope +/// fails to serialize. +pub fn pack_envelope( + message_type: MessageType, + data: &T, +) -> Result, TransportError> { + let payload = pack(data)?; + let envelope = Envelope::new(message_type, payload); + pack(&envelope) +} + +/// Deserialize an [`Envelope`] from raw bytes, validate its version, and +/// deserialize the inner payload. +/// +/// This is the primary function for inbound messages. +/// +/// # Errors +/// +/// Returns an error if the bytes are malformed, the protocol version is +/// unsupported, or the inner payload can't be deserialized into `T`. +pub fn unpack_envelope(data: &[u8]) -> Result<(T, MessageType), TransportError> { + let envelope: Envelope = unpack(data)?; + envelope.validate_version()?; + let payload: T = unpack(&envelope.payload)?; + Ok((payload, envelope.message_type)) +} + +// ───── Tests ───────────────────────────────────────────────────────────────── + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn pack_and_unpack_primitive() { + let value = 42u64; + let bytes = pack(&value).unwrap(); + let decoded: u64 = unpack(&bytes).unwrap(); + assert_eq!(decoded, value); + } + + #[test] + fn pack_and_unpack_string() { + let value = String::from("hello, willow"); + let bytes = pack(&value).unwrap(); + let decoded: String = unpack(&bytes).unwrap(); + assert_eq!(decoded, value); + } + + #[test] + fn pack_and_unpack_struct() { + #[derive(Debug, PartialEq, Serialize, Deserialize)] + struct TestMsg { + id: u32, + body: String, + } + + let msg = TestMsg { + id: 1, + body: "test".into(), + }; + let bytes = pack(&msg).unwrap(); + let decoded: TestMsg = unpack(&bytes).unwrap(); + assert_eq!(decoded, msg); + } + + #[test] + fn envelope_round_trip() { + let payload = vec![1u8, 2, 3, 4]; + let env = Envelope::new(MessageType::Chat, payload.clone()); + + let bytes = pack(&env).unwrap(); + let decoded: Envelope = unpack(&bytes).unwrap(); + + assert_eq!(decoded.version, PROTOCOL_VERSION); + assert_eq!(decoded.message_type, MessageType::Chat); + assert_eq!(decoded.payload, payload); + } + + #[test] + fn envelope_version_validation_passes() { + let env = Envelope::new(MessageType::Ping, vec![]); + assert!(env.validate_version().is_ok()); + } + + #[test] + fn envelope_version_validation_fails() { + let env = Envelope { + version: 999, + message_type: MessageType::Ping, + payload: vec![], + }; + let err = env.validate_version().unwrap_err(); + assert!(matches!( + err, + TransportError::UnsupportedVersion { + expected: PROTOCOL_VERSION, + got: 999 + } + )); + } + + #[test] + fn pack_envelope_round_trip() { + #[derive(Debug, PartialEq, Serialize, Deserialize)] + struct Inner { + x: i32, + } + + let inner = Inner { x: -7 }; + let bytes = pack_envelope(MessageType::Channel, &inner).unwrap(); + let (decoded, msg_type) = unpack_envelope::(&bytes).unwrap(); + + assert_eq!(decoded, inner); + assert_eq!(msg_type, MessageType::Channel); + } + + #[test] + fn deserialize_garbage_fails() { + let garbage = vec![0xFF, 0xFE, 0xFD]; + let result = unpack::(&garbage); + assert!(result.is_err()); + } + + #[test] + fn message_type_values_are_stable() { + // These values are part of the wire protocol — changing them would + // break compatibility with older peers. + assert_eq!(MessageType::Chat as u8, 0); + assert_eq!(MessageType::Channel as u8, 1); + assert_eq!(MessageType::Identity as u8, 2); + assert_eq!(MessageType::File as u8, 3); + assert_eq!(MessageType::Signal as u8, 4); + assert_eq!(MessageType::Presence as u8, 5); + assert_eq!(MessageType::Ping as u8, 6); + } } diff --git a/crates/web/Cargo.toml b/crates/web/Cargo.toml deleted file mode 100644 index 7538c376..00000000 --- a/crates/web/Cargo.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -name = "web" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -yew = { git = "https://github.com/yewstack/yew.git", features = ["csr"] } -yewdux = { git = "https://github.com/intendednull/yewdux.git" } -yewdux-input = { git = "https://github.com/intendednull/yewdux.git" } diff --git a/crates/web/index.html b/crates/web/index.html deleted file mode 100644 index 7d6c9a63..00000000 --- a/crates/web/index.html +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/crates/web/src/main.rs b/crates/web/src/main.rs deleted file mode 100644 index 2bffcc13..00000000 --- a/crates/web/src/main.rs +++ /dev/null @@ -1,93 +0,0 @@ -use std::{ops::Deref, rc::Rc}; - -pub use yew::prelude::*; -pub use yewdux::prelude::*; -pub use yewdux_input::input_value; - -#[derive(Default, PartialEq, Clone, Debug)] -pub struct Item { - message: String, -} - -#[derive(Store, Default, PartialEq, Clone, Debug)] -pub struct Timeline { - history: Vec, -} - -#[derive(PartialEq, Clone, Debug)] -enum Action { - AddItem(Item), -} - -impl Reducer for Action { - fn apply(self, mut timeline: Rc) -> Rc { - let state = Rc::make_mut(&mut timeline); - - match self { - Action::AddItem(item) => state.history.push(item), - } - - timeline - } -} - -#[function_component] -fn ViewTimeline() -> Html { - let timeline = use_store_value::(); - let items = timeline - .history - .iter() - .map(|item| { - html! { -

{&item.message}

- } - }) - .collect::(); - - html! { -
- { items } -
- } -} - -#[function_component] -fn InputItem() -> Html { - let value = use_state(String::default); - let oninput = { - let value = value.clone(); - Callback::from(move |e| { - value.set(input_value(e).unwrap()); - }) - }; - let onclick = { - let value = value.clone(); - Dispatch::::new().apply_callback(move |_| { - let message = value.deref().clone(); - value.set(String::default()); - - Action::AddItem(Item { message }) - }) - }; - - html! { -
-