From 4915ec0a1b0a45f568e9947c928568eb6706fa44 Mon Sep 17 00:00:00 2001 From: EdisonJwa Date: Thu, 14 May 2026 21:37:06 +0800 Subject: [PATCH] =?UTF-8?q?feat(alpha):=20wire=20connect=E2=86=92snapshot?= =?UTF-8?q?=E2=86=92disconnect=20end-to-end=20(v0.1.0-alpha.1)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit First Internal Alpha build per DEC-001. Closes the milestone of 'Flutter UI calls Rust via the typed bridge, Rust connects to a TeamSpeak-compatible server through tsclientlib, returns a typed snapshot, and disconnects cleanly.' Audio remains Beta scope. Promotions from PoC: poc/tsclientlib-connect-spike → crates/chanora_protocol/ New product code: crates/chanora_protocol/src/{dto.rs,adapter.rs} — typed boundary over tsclientlib. Tokio task owns the Connection; public handle communicates via mpsc/oneshot. No tsclientlib types cross out of the crate (SAD-067 / SysDes-011 / SysDes-029). core/chanora_core/src/lib.rs — ChanoraSession composes the protocol crate, enforces the DEC-006 single-connection invariant. crates/chanora_bridge/src/{api.rs,frb_generated.rs} — FRB 2.12.0 bridge per DEC-014. cdylib + staticlib + rlib. Typed BridgeSnapshot / BridgeChannel / BridgeClient / BridgeError DTOs. Process-wide OnceLock + OnceLock. flutter_rust_bridge.yaml at repo root. apps/chanora_flutter/lib/main.dart — Alpha UI: server form, connect button, channel tree, disconnect. apps/chanora_flutter/lib/l10n/app_{en,zh}.arb expanded with the Alpha key set; ARB metadata reaffirms ADR-008 for server-provided content. Generated Dart bindings under apps/chanora_flutter/lib/src/rust/. Empirical verification (2026-05-14): Workspace: cargo check + cargo test clean (workspace tests: all green). Bridge cdylib: target/release/libchanora_bridge.so produced (~15 MB). Flutter: flutter analyze clean; flutter test runs 3/3 green including the alpha_e2e_test that drives the full Dart → FRB → chanora_bridge → chanora_core → chanora_protocol → tsclientlib → UDP → cn.teamspeak.app path. The captured logcat/stdout shows the tsproto resender transitioning Connected → Disconnecting → Disconnected on clean teardown. Architecture changes: - Removed the chanora_core ↔ chanora_bridge cyclic dependency. chanora_core no longer knows the bridge exists; the bridge maps from CoreError. - chanora_bridge crate's #![forbid(unsafe_code)] lint relaxed because FRB-generated glue legitimately uses unsafe at the FFI boundary. Hand-written code remains unsafe-free. Open follow-ups (NOT in this Alpha): - Audio capture/playback wiring into chanora_audio (Beta scope per DEC-001). - Identity persistence via chanora_storage (currently regenerated on every connect). - Per-message diagnostics + redaction (chanora_diagnostics still scaffold). - Reconnect / network-loss recovery. - Mobile (Android) build of the bridge cdylib + UI verification. --- CHANGELOG.md | 124 +- Cargo.lock | 3486 ++++++++++++++++- apps/chanora_flutter/lib/l10n/app_en.arb | 57 +- apps/chanora_flutter/lib/l10n/app_zh.arb | 20 +- .../lib/l10n/generated/app_localizations.dart | 78 +- .../l10n/generated/app_localizations_en.dart | 46 +- .../l10n/generated/app_localizations_zh.dart | 43 +- apps/chanora_flutter/lib/main.dart | 272 +- apps/chanora_flutter/lib/src/rust/api.dart | 147 + .../lib/src/rust/frb_generated.dart | 629 +++ .../lib/src/rust/frb_generated.io.dart | 175 + .../lib/src/rust/frb_generated.web.dart | 175 + apps/chanora_flutter/lib/src/rust/lib.dart | 33 + .../lib/src/rust/lib.freezed.dart | 454 +++ apps/chanora_flutter/pubspec.lock | 328 ++ apps/chanora_flutter/pubspec.yaml | 4 + apps/chanora_flutter/test/alpha_e2e_test.dart | 52 + apps/chanora_flutter/test/widget_test.dart | 31 +- core/chanora_core/Cargo.toml | 2 +- core/chanora_core/src/lib.rs | 118 +- core/chanora_core/tests/alpha_smoke.rs | 34 + crates/chanora_bridge/Cargo.toml | 14 + crates/chanora_bridge/src/api.rs | 177 + crates/chanora_bridge/src/frb_generated.rs | 687 ++++ crates/chanora_bridge/src/lib.rs | 75 +- crates/chanora_protocol/Cargo.toml | 11 + crates/chanora_protocol/src/adapter.rs | 305 ++ crates/chanora_protocol/src/dto.rs | 61 + crates/chanora_protocol/src/lib.rs | 68 +- flutter_rust_bridge.yaml | 4 + 30 files changed, 7504 insertions(+), 206 deletions(-) create mode 100644 apps/chanora_flutter/lib/src/rust/api.dart create mode 100644 apps/chanora_flutter/lib/src/rust/frb_generated.dart create mode 100644 apps/chanora_flutter/lib/src/rust/frb_generated.io.dart create mode 100644 apps/chanora_flutter/lib/src/rust/frb_generated.web.dart create mode 100644 apps/chanora_flutter/lib/src/rust/lib.dart create mode 100644 apps/chanora_flutter/lib/src/rust/lib.freezed.dart create mode 100644 apps/chanora_flutter/test/alpha_e2e_test.dart create mode 100644 core/chanora_core/tests/alpha_smoke.rs create mode 100644 crates/chanora_bridge/src/api.rs create mode 100644 crates/chanora_bridge/src/frb_generated.rs create mode 100644 crates/chanora_protocol/src/adapter.rs create mode 100644 crates/chanora_protocol/src/dto.rs create mode 100644 flutter_rust_bridge.yaml diff --git a/CHANGELOG.md b/CHANGELOG.md index f2df517..3d0a2cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,61 +6,85 @@ This project is expected to follow a Conventional Commits style workflow. ## [Unreleased] -### Added +### Added — Alpha build (v0.1.0-alpha.1) -- **Product workspace scaffold (DEC-022).** Created the canonical - implementation directory layout per the README sketch + SAD §7.2: - - `Cargo.toml` — top-level Cargo workspace declaring seven members - and excluding the `poc/` spike trees and `apps/chanora_flutter`. - Pins `license = "MIT OR Apache-2.0"` (DEC-020) and - `rust-version = "1.95"` workspace-wide. - - `core/chanora_core/` — top-level Rust API + orchestration. Owns - `ChanoraSession` (placeholder) and the `CoreError` enum that - typed-wraps every subsystem error. - - `crates/chanora_protocol/` — tsclientlib adapter scaffold, - opaque `ChannelId` / `ClientId` types, `ProtocolError`. - - `crates/chanora_state/` — `StateError`, `ConnectionState` enum. - - `crates/chanora_audio/` — `AudioError`, `AudioEffects` struct - whose defaults match DEC-007/008/009/010 (all four effects on). - - `crates/chanora_storage/` — `StorageError`, marker traits for - the two repositories per the SAD-067 separation. - - `crates/chanora_diagnostics/` — `DiagnosticsError`, - `REDACTION_MARKER` constant matching the PoC, `Redactor` shell. - - `crates/chanora_bridge/` — `BridgeError` DTO (Serialize + - Deserialize) for FRB 2.x boundary. - Workspace `cargo check --workspace` and `cargo test --workspace` - both clean on Rust 1.95. -- **Flutter application scaffold.** - - `apps/chanora_flutter/` created via `flutter create` with - `--org app.chanora --project-name chanora_flutter`. Platforms - enabled at scaffold time: Linux + Android. - - `apps/chanora_flutter/android/app/build.gradle.kts` overridden - to `minSdk = 28` per DEC-004 (raised from Flutter's default - minSdk by explicit owner ruling, register v0.9.5). - - `flutter_localizations` + `intl` added to `pubspec.yaml`; - `flutter.generate: true` enabled. - - `l10n.yaml` configured to emit `lib/l10n/generated/AppL10n` - (no synthetic package — `synthetic-package` was removed in - Flutter 3.41+). - - `lib/l10n/app_en.arb` — English template ARB with the MVP key - set (`appTitle`, `homeGreeting`, `homeNotProductionReadyBanner`, - `connectAction`). - - `lib/l10n/app_zh.arb` — Simplified Chinese translations per - DEC-015 (MVP language scope expanded from English-only). - - `lib/main.dart` and `test/widget_test.dart` rewritten as a - minimal scaffold that exercises both locales. - - `flutter analyze` reports no issues. `flutter test` runs the - English + Simplified Chinese smoke tests and both pass. +- **First Alpha build wires the connect → snapshot → disconnect cycle + end-to-end from the Flutter UI to a live TeamSpeak-compatible + server via the typed Flutter/Rust bridge.** Per DEC-001 this is the + Internal Alpha milestone; Audio (voice in/out) is deferred to Beta. +- `crates/chanora_protocol/` promoted from a scaffold to a working + adapter. Public surface: + - `ConnectConfig`, `ProtocolClient`, `ProtocolError`. + - DTO module exposing `ChannelId`, `ClientId`, `ChannelInfo`, + `ClientInfo`, `ServerSnapshot` — all owned primitives and + `String`s; no `tsclientlib::*` types leak (SAD-067). + - Tokio task owns the `tsclientlib::Connection`; public handle + communicates via `mpsc` requests + `oneshot` replies. + - Connect waits for the initial `BookEvents` snapshot, then pumps + events for ~2 s so the subscribed channel tree settles before + the first snapshot is served. + - Promoted from `poc/tsclientlib-connect-spike`. +- `core/chanora_core::ChanoraSession` now drives the protocol crate + with a typed `connect`/`snapshot`/`is_connected`/`disconnect` API. + Enforces the DEC-006 single-connection invariant via an internal + `tokio::sync::Mutex>`. +- `crates/chanora_bridge/` wired against `flutter_rust_bridge` 2.12.0 + (DEC-014). Compiled as `cdylib + staticlib + rlib`. Exposes: + - `bridge_init()` (FRB lifecycle), `connect()`, `snapshot()`, + `disconnect()`, `is_connected()`. + - Typed `BridgeChannel`, `BridgeClient`, `BridgeSnapshot` DTOs; + `BridgeError` with `From`. + - A process-wide `tokio::Runtime` + `ChanoraSession` via + `OnceLock`, used by every async command. + - The crate's `#![forbid(unsafe_code)]` lint was lifted to + `#![warn(missing_docs)]` only, with a doc-comment explanation + that the FRB-generated glue legitimately uses unsafe at the FFI + boundary; hand-written code in the crate is still expected to + avoid `unsafe`. +- `flutter_rust_bridge.yaml` at the repo root drives codegen for the + bridge. +- Generated Dart bindings under + `apps/chanora_flutter/lib/src/rust/{api.dart,frb_generated*.dart,lib*.dart}`. +- Generated Rust glue under `crates/chanora_bridge/src/frb_generated.rs`. +- `apps/chanora_flutter/lib/main.dart` rewritten as the Alpha UI: + - Form: server address + nickname, both pre-populated for + convenience. + - Connect button → calls FRB → enters connecting state → shows + snapshot. + - Snapshot view: server welcome banner (preserved verbatim per + ADR-008), `N channels • M online` count, ordered channel list + with clients indented under their channel. + - Refresh and Disconnect actions in the app bar. +- `apps/chanora_flutter/lib/l10n/app_{en,zh}.arb` expanded with the + Alpha key set: + `homeNotProductionReadyBanner` (now says "Alpha build"), + `fieldServerHost`, `fieldNickname`, + `connectAction`, `disconnectAction`, `refreshAction`, + `statusIdle`, `statusConnecting`, `statusConnected`, `statusError`, + `channelsHeading`, `clientsHeading`, `countChannelsAndClients`. +- `flutter_localizations`, `intl`, `flutter_rust_bridge`, + `freezed_annotation` added to dependencies; + `freezed` and `build_runner` added to dev_dependencies. +- `apps/chanora_flutter/test/alpha_e2e_test.dart` runs the full + Dart → FRB → Rust → tsclientlib → network → server path against + `cn.teamspeak.app`. Verifies the snapshot contains a non-empty + server name and a non-empty channel list, that `isConnected()` + flips true → false across the disconnect, and that a re-fetched + snapshot agrees on the server name. Passes in ~2.5 s. +- `core/chanora_core/tests/alpha_smoke.rs` runs the same path from the + Rust side; tagged `#[ignore]` so `cargo test --workspace` doesn't + hit the network by default. Run with `--ignored alpha_smoke`. ### Changed -- `docs/governance/path-migration-map.md` bumped to v0.9.3 with a - new §3 *Implementation Path Layout* recording the DEC-022 - directory adoption (`apps/`, `core/`, `crates/`) and pointing - each subsystem at its crate alongside the relevant SAD/SysDes/DEC - authority. +- `chanora_core::CoreError` no longer wraps `chanora_bridge::BridgeError`; + the relationship is the other way around (bridge maps from core). + This removes a cyclic `chanora_core` ↔ `chanora_bridge` dependency + introduced when the bridge crate gained `chanora_core` as a dep. +- `chanora_bridge` lints relaxed from `#![forbid(unsafe_code)]` to + `#![warn(missing_docs)]` (documented above). -### Added (LICENSE files) +### LICENSE files (carry-over from earlier in this branch) - `LICENSE-APACHE` — Apache License Version 2.0 text (DEC-020). - `LICENSE-MIT` — MIT License text (DEC-020). diff --git a/Cargo.lock b/Cargo.lock index e57907d..106bfb0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,11 +2,263 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "addr2line" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures 0.2.17", +] + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "allo-isolate" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "449e356a4864c017286dbbec0e12767ea07efba29e3b7d984194c2a7ff3c4550" +dependencies = [ + "anyhow", + "atomic", + "backtrace", +] + +[[package]] +name = "android_log-sys" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84521a3cf562bc62942e294181d9eef17eb38ceb8c68677bc49f144e4c3d4f8d" + +[[package]] +name = "android_logger" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbb4e440d04be07da1f1bf44fb4495ebd58669372fe0cffa6e48595ac5bd88a3" +dependencies = [ + "android_log-sys", + "env_filter", + "log", +] + +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "atomic" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59bdb34bc650a32731b31bd8f0829cc15d24a708ee31559e0bb34f2bc320cba" + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "aws-lc-rs" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ec2f1fc3ec205783a5da9a7e6c1509cc69dedf09a1949e412c1e18469326d00" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a2f9779ce85b93ab6170dd940ad0169b5766ff848247aff13bb788b832fe3f4" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", +] + +[[package]] +name = "backtrace" +version = "0.3.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-link", +] + +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64ct" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + +[[package]] +name = "bitflags" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" +dependencies = [ + "serde_core", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "build-target" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "832133bbabbbaa9fbdba793456a2827627a7d2b8fb96032fa1e7666d7895832b" + +[[package]] +name = "bumpalo" +version = "3.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" + +[[package]] +name = "bytemuck" +version = "1.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" + +[[package]] +name = "cc" +version = "1.2.62" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1dce859f0832a7d088c4f1119888ab94ef4b5d6795d1ce05afb7fe159d79f98" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[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.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "rand_core 0.10.1", +] + [[package]] name = "chanora_audio" version = "0.0.1-pre" dependencies = [ - "thiserror", + "thiserror 2.0.18", "tracing", ] @@ -14,9 +266,14 @@ dependencies = [ name = "chanora_bridge" version = "0.0.1-pre" dependencies = [ + "chanora_core", + "chanora_protocol", + "flutter_rust_bridge", "serde", - "thiserror", + "thiserror 2.0.18", + "tokio", "tracing", + "tracing-subscriber", ] [[package]] @@ -24,12 +281,12 @@ name = "chanora_core" version = "0.0.1-pre" dependencies = [ "chanora_audio", - "chanora_bridge", "chanora_diagnostics", "chanora_protocol", "chanora_state", "chanora_storage", - "thiserror", + "thiserror 2.0.18", + "tokio", "tracing", ] @@ -37,7 +294,7 @@ dependencies = [ name = "chanora_diagnostics" version = "0.0.1-pre" dependencies = [ - "thiserror", + "thiserror 2.0.18", "tracing", ] @@ -45,15 +302,20 @@ dependencies = [ name = "chanora_protocol" version = "0.0.1-pre" dependencies = [ - "thiserror", + "async-trait", + "futures", + "serde", + "thiserror 2.0.18", + "tokio", "tracing", + "tsclientlib", ] [[package]] name = "chanora_state" version = "0.0.1-pre" dependencies = [ - "thiserror", + "thiserror 2.0.18", "tracing", ] @@ -61,15 +323,1431 @@ dependencies = [ name = "chanora_storage" version = "0.0.1-pre" dependencies = [ - "thiserror", + "thiserror 2.0.18", "tracing", ] +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + +[[package]] +name = "cmac" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8543454e3c3f5126effff9cd44d562af4e31fb8ce1cc0d3dcd8f084515dbc1aa" +dependencies = [ + "cipher", + "dbl", + "digest 0.10.7", +] + +[[package]] +name = "cmake" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" +dependencies = [ + "cc", +] + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[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 = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "typenum", +] + +[[package]] +name = "csv" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52cd9d68cf7efc6ddfaaee42e7288d3a99d613d4b50f76ce9827ae0c6e14f938" +dependencies = [ + "csv-core", + "itoa", + "ryu", + "serde_core", +] + +[[package]] +name = "csv-core" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "704a3c26996a80471189265814dbc2c257598b96b8a7feae2d31ace646bb9782" +dependencies = [ + "memchr", +] + +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + +[[package]] +name = "curve25519-dalek-ng" +version = "4.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c359b7249347e46fb28804470d071c921156ad62b3eef5d34e2ba867533dec8" +dependencies = [ + "byteorder", + "digest 0.9.0", + "rand_core 0.6.4", + "subtle-ng", + "zeroize", +] + +[[package]] +name = "dart-sys" +version = "4.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57967e4b200d767d091b961d6ab42cc7d0cc14fe9e052e75d0d3cf9eb732d895" +dependencies = [ + "cc", +] + +[[package]] +name = "dashmap" +version = "5.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" +dependencies = [ + "cfg-if", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", +] + +[[package]] +name = "data-encoding" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8" + +[[package]] +name = "dbl" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd2735a791158376708f9347fe8faba9667589d82427ef3aed6794a8981de3d9" +dependencies = [ + "generic-array", +] + +[[package]] +name = "delegate-attr" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51aac4c99b2e6775164b412ea33ae8441b2fde2dbf05a20bc0052a63d08c475b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "powerfmt", + "serde_core", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", + "subtle", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "eax" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9954fabd903b82b9d7a68f65f97dc96dd9ad368e40ccc907a7c19d53e6bfac28" +dependencies = [ + "aead", + "cipher", + "cmac", + "ctr", + "subtle", +] + +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest 0.10.7", + "elliptic-curve", + "rfc6979", + "signature", + "spki", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest 0.10.7", + "ff", + "generic-array", + "group", + "hkdf", + "pem-rfc7468", + "pkcs8", + "rand_core 0.6.4", + "sec1", + "subtle", + "zeroize", +] + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "env_filter" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bf3c259d255ca70051b30e2e95b5446cdb8949ac4cd22c0d7fd634d89f568e2" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "ff" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "flutter_rust_bridge" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0884853aae8a6517b5b58cf36f55da487f2fe110e1686938eb29b6640aae4a5" +dependencies = [ + "allo-isolate", + "android_logger", + "anyhow", + "build-target", + "bytemuck", + "byteorder", + "console_error_panic_hook", + "dart-sys", + "delegate-attr", + "flutter_rust_bridge_macros", + "futures", + "js-sys", + "lazy_static", + "log", + "oslog", + "portable-atomic", + "threadpool", + "tokio", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "flutter_rust_bridge_macros" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b5ce32f35f710ced8c5aa557f023f1a624e737b5460cee2b70fcd3a8df09e1b" +dependencies = [ + "hex", + "md-5", + "proc-macro2", + "quote", + "syn", +] + +[[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 = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + +[[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-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-macro" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[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-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.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", + "zeroize", +] + +[[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", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi 5.3.0", + "wasip2", + "wasm-bindgen", +] + +[[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", + "rand_core 0.10.1", + "wasip2", + "wasip3", +] + +[[package]] +name = "gimli" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" + +[[package]] +name = "git-testament" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a74999c921479f919c87a9d2e6922a79a18683f18105344df8e067149232e51" +dependencies = [ + "git-testament-derive", +] + +[[package]] +name = "git-testament-derive" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbeac967e71eb3dc1656742fc7521ec7cd3b6b88738face65bf1fddf702bc4c0" +dependencies = [ + "log", + "proc-macro2", + "quote", + "syn", + "time", +] + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "h2" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "171fefbc92fe4a4de27e0698d6a5b392d6a0e333506bc49133760b3bcf948733" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[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 = "hickory-net" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2295ed2f9c31e471e1428a8f88a3f0e1f4b27c15049592138d1eebe9c35b183" +dependencies = [ + "async-trait", + "cfg-if", + "data-encoding", + "futures-channel", + "futures-io", + "futures-util", + "hickory-proto", + "idna", + "ipnet", + "jni", + "rand 0.10.1", + "thiserror 2.0.18", + "tinyvec", + "tokio", + "tracing", + "url", +] + +[[package]] +name = "hickory-proto" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bab31817bfb44672a252e97fe81cd0c18d1b2cf892108922f6818820df8c643" +dependencies = [ + "data-encoding", + "idna", + "ipnet", + "jni", + "once_cell", + "prefix-trie", + "rand 0.10.1", + "ring", + "thiserror 2.0.18", + "tinyvec", + "tracing", + "url", +] + +[[package]] +name = "hickory-resolver" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d58d28879ceecde6607729660c2667a081ccdc082e082675042793960f178c" +dependencies = [ + "cfg-if", + "futures-util", + "hickory-net", + "hickory-proto", + "ipconfig", + "ipnet", + "jni", + "moka", + "ndk-context", + "once_cell", + "parking_lot", + "rand 0.10.1", + "resolv-conf", + "smallvec", + "system-configuration", + "thiserror 2.0.18", + "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 0.10.7", +] + +[[package]] +name = "http" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "hyper" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "h2", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "base64", + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "icu_collections" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" + +[[package]] +name = "icu_properties" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" + +[[package]] +name = "icu_provider" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" +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.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.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 = "ipconfig" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d40460c0ce33d6ce4b0630ad68ff63d6661961c48b6dba35e5a4d81cfb48222" +dependencies = [ + "socket2", + "widestring", + "windows-registry", + "windows-result", + "windows-sys 0.61.2", +] + +[[package]] +name = "ipnet" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" +dependencies = [ + "serde", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "jni" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" +dependencies = [ + "cfg-if", + "combine", + "jni-macros", + "jni-sys", + "log", + "simd_cesu8", + "thiserror 2.0.18", + "walkdir", + "windows-link", +] + +[[package]] +name = "jni-macros" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "simd_cesu8", + "syn", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn", +] + +[[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.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67df7112613f8bfd9150013a0314e196f4800d3201ae742489d999db2f979f08" +dependencies = [ + "cfg-if", + "futures-util", + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "litemap" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" + +[[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-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest 0.10.7", +] + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "moka" +version = "0.12.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "957228ad12042ee839f93c8f257b62b4c0ab5eaae1d4fa60de53b27c9d7c5046" +dependencies = [ + "crossbeam-channel", + "crossbeam-epoch", + "crossbeam-utils", + "equivalent", + "parking_lot", + "portable-atomic", + "smallvec", + "tagptr", + "uuid", +] + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "nu-ansi-term" +version = "0.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.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-conv" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "object" +version = "0.37.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" +dependencies = [ + "memchr", +] + +[[package]] +name = "omnom" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66562a92c66d5937e9d549597c2d3cafe3d2367dd08dc257da730587c2cfc218" +dependencies = [ + "memchr", +] + [[package]] name = "once_cell" version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" +dependencies = [ + "critical-section", + "portable-atomic", +] + +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + +[[package]] +name = "oslog" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d2043d1f61d77cb2f4b1f7b7b2295f40507f5f8e9d1c8bf10a1ca5f97a3969" +dependencies = [ + "cc", + "dashmap", + "log", +] + +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + +[[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", +] + +[[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", + "smallvec", + "windows-link", +] + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pin-project-lite" @@ -77,6 +1755,82 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + +[[package]] +name = "potential_utf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "prefix-trie" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cf6e3177f0684016a5c209b00882e15f8bdd3f3bb48f0491df10cd102d0c6e7" +dependencies = [ + "either", + "ipnet", + "num-traits", +] + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", +] + [[package]] name = "proc-macro2" version = "1.0.106" @@ -86,6 +1840,73 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "quicklz" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "750eab265a26a0cd064f2a2b222bf172cc06d7a873e113e5f6d0118cc6e5fbb1" +dependencies = [ + "bit-vec", + "byteorder", + "thiserror 1.0.69", +] + +[[package]] +name = "quinn" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2", + "thiserror 2.0.18", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" +dependencies = [ + "aws-lc-rs", + "bytes", + "getrandom 0.3.4", + "lru-slab", + "rand 0.9.4", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.18", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.60.2", +] + [[package]] name = "quote" version = "1.0.45" @@ -95,6 +1916,401 @@ 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 = "rand" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.5", +] + +[[package]] +name = "rand" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" +dependencies = [ + "chacha20", + "getrandom 0.4.2", + "rand_core 0.10.1", +] + +[[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 = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "ref-cast" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[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 = "reqwest" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62e0021ea2c22aed41653bc7e1419abb2c97e038ff2c33d0e1309e49a97deec0" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-core", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "js-sys", + "log", + "mime", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", + "rustls-platform-verifier", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "resolv-conf" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e061d1b48cb8d38042de4ae0a7a6401009d6143dc80d2e2d6f31f0bdd6470c7" + +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + +[[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", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" + +[[package]] +name = "rustc-hash" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustls" +version = "0.23.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b" +dependencies = [ + "aws-lc-rs", + "once_cell", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pki-types" +version = "1.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-platform-verifier" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0" +dependencies = [ + "core-foundation 0.10.1", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" + +[[package]] +name = "rustls-webpki" +version = "0.103.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" +dependencies = [ + "aws-lc-rs", + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags", + "core-foundation 0.10.1", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "self_cell" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b12e76d157a900eb52e81bc6e9f3069344290341720e9178cde2407113ac8d89" + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + [[package]] name = "serde" version = "1.0.228" @@ -125,6 +2341,153 @@ dependencies = [ "syn", ] +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_spanned" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" +dependencies = [ + "serde_core", +] + +[[package]] +name = "sha-1" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5058ada175748e33390e40e872bd0fe59a19f265d0158daa551c5a88a76009c" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest 0.10.7", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest 0.10.7", +] + +[[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 = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest 0.10.7", + "rand_core 0.6.4", +] + +[[package]] +name = "simd_cesu8" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94f90157bb87cddf702797c5dadfa0be7d266cdf49e22da2fcaa32eff75b2c33" +dependencies = [ + "rustc_version", + "simdutf8", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + +[[package]] +name = "simple_asn1" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d585997b0ac10be3c5ee635f1bab02d512760d14b7c468801ac8a01d9ae5f1d" +dependencies = [ + "num-bigint", + "num-traits", + "thiserror 2.0.18", + "time", +] + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[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 = "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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "subtle-ng" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "734676eb262c623cec13c3155096e08d1f8f29adce39ba17948b18dad1e54142" + [[package]] name = "syn" version = "2.0.117" @@ -136,13 +2499,92 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "system-configuration" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" +dependencies = [ + "bitflags", + "core-foundation 0.9.4", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "t4rust-derive" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e411692d6e0c8ce07f65a8210e95dcfb56cc4600940456592a49a3579ef6396" +dependencies = [ + "nom", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tagptr" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + [[package]] name = "thiserror" version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl", + "thiserror-impl 2.0.18", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -156,6 +2598,224 @@ dependencies = [ "syn", ] +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "threadpool" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" +dependencies = [ + "num_cpus", +] + +[[package]] +name = "time" +version = "0.3.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" + +[[package]] +name = "time-macros" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinystr" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.52.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" +dependencies = [ + "bytes", + "libc", + "mio", + "pin-project-lite", + "socket2", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee" +dependencies = [ + "indexmap", + "serde_core", + "serde_spanned", + "toml_datetime", + "toml_parser", + "toml_writer", + "winnow", +] + +[[package]] +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_parser" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" +dependencies = [ + "winnow", +] + +[[package]] +name = "toml_writer" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68d6fdd9f81c2819c9a8b0e0cd91660e7746a8e6ea2ba7c6b2b057985f6bcb51" +dependencies = [ + "bitflags", + "bytes", + "futures-util", + "http", + "http-body", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", + "url", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + [[package]] name = "tracing" version = "0.1.44" @@ -185,10 +2845,818 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", + "valuable", ] +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.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 = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "ts-bookkeeping" +version = "0.1.0" +source = "git+https://github.com/ReSpeak/tsclientlib.git?rev=04aa2491#04aa24917abbf6a0c8442a79742d6d2d40ecf71e" +dependencies = [ + "base64", + "heck", + "itertools", + "num-derive", + "num-traits", + "serde", + "t4rust-derive", + "thiserror 2.0.18", + "time", + "tracing", + "tsproto-packets", + "tsproto-structs", + "tsproto-types", +] + +[[package]] +name = "tsclientlib" +version = "0.2.0" +source = "git+https://github.com/ReSpeak/tsclientlib.git?rev=04aa2491#04aa24917abbf6a0c8442a79742d6d2d40ecf71e" +dependencies = [ + "base64", + "futures", + "git-testament", + "hickory-net", + "hickory-resolver", + "itertools", + "num-traits", + "pin-utils", + "rand 0.10.1", + "reqwest", + "thiserror 2.0.18", + "time", + "tokio", + "tokio-stream", + "tracing", + "ts-bookkeeping", + "tsproto", + "tsproto-packets", + "tsproto-types", + "url", +] + +[[package]] +name = "tsproto" +version = "0.2.0" +source = "git+https://github.com/ReSpeak/tsclientlib.git?rev=04aa2491#04aa24917abbf6a0c8442a79742d6d2d40ecf71e" +dependencies = [ + "aes", + "base64", + "curve25519-dalek-ng", + "eax", + "futures", + "generic-array", + "git-testament", + "num-bigint", + "num-derive", + "num-traits", + "omnom", + "pin-project-lite", + "quicklz", + "rand 0.10.1", + "serde", + "sha-1", + "sha2", + "thiserror 2.0.18", + "time", + "tokio", + "tracing", + "tsproto-packets", + "tsproto-types", +] + +[[package]] +name = "tsproto-packets" +version = "0.1.0" +source = "git+https://github.com/ReSpeak/tsclientlib.git?rev=04aa2491#04aa24917abbf6a0c8442a79742d6d2d40ecf71e" +dependencies = [ + "base64", + "bitflags", + "num-derive", + "num-traits", + "omnom", + "self_cell", + "serde", + "thiserror 2.0.18", +] + +[[package]] +name = "tsproto-structs" +version = "0.2.0" +source = "git+https://github.com/ReSpeak/tsclientlib.git?rev=04aa2491#04aa24917abbf6a0c8442a79742d6d2d40ecf71e" +dependencies = [ + "base64", + "csv", + "heck", + "once_cell", + "serde", + "toml", +] + +[[package]] +name = "tsproto-types" +version = "0.1.0" +source = "git+https://github.com/ReSpeak/tsclientlib.git?rev=04aa2491#04aa24917abbf6a0c8442a79742d6d2d40ecf71e" +dependencies = [ + "base64", + "bitflags", + "curve25519-dalek-ng", + "elliptic-curve", + "generic-array", + "heck", + "num-bigint", + "num-derive", + "num-traits", + "p256", + "rand 0.8.6", + "ref-cast", + "serde", + "sha-1", + "simple_asn1", + "t4rust-derive", + "thiserror 2.0.18", + "time", + "tsproto-structs", +] + +[[package]] +name = "typenum" +version = "1.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" + [[package]] name = "unicode-ident" version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "uuid" +version = "1.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76" +dependencies = [ + "getrandom 0.4.2", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.3+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" +dependencies = [ + "wit-bindgen 0.57.1", +] + +[[package]] +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 = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen 0.51.0", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.121" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49ace1d07c165b0864824eee619580c4689389afa9dc9ed3a4c75040d82e6790" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96492d0d3ffba25305a7dc88720d250b1401d7edca02cc3bcd50633b424673b8" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.121" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e68e6f4afd367a562002c05637acb8578ff2dea1943df76afb9e83d177c8578" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.121" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d95a9ec35c64b2a7cb35d3fead40c4238d0940c86d107136999567a4703259f2" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.121" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4e0100b01e9f0d03189a92b96772a1fb998639d981193d7dbab487302513441" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + +[[package]] +name = "web-sys" +version = "0.3.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b572dff8bcf38bad0fa19729c89bb5748b2b9b1d8be70cf90df697e3a8f32aa" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-root-certs" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31141ce3fc3e300ae89b78c0dd67f9708061d1d2eda54b8209346fd6be9a92c" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "widestring" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471" + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "winnow" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ee1708bef14716a11bae175f579062d4554d95be2c6829f518df847b7b3fdd0" + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + +[[package]] +name = "writeable" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" + +[[package]] +name = "yoke" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerofrom" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" + +[[package]] +name = "zerotrie" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/apps/chanora_flutter/lib/l10n/app_en.arb b/apps/chanora_flutter/lib/l10n/app_en.arb index 106818a..73f1ca6 100644 --- a/apps/chanora_flutter/lib/l10n/app_en.arb +++ b/apps/chanora_flutter/lib/l10n/app_en.arb @@ -7,18 +7,59 @@ "description": "Application title shown in launchers and the app bar. The product name `Chanora` is fixed by DEC-018 and must not be translated." }, - "homeGreeting": "Welcome to Chanora", - "@homeGreeting": { - "description": "Greeting shown on the empty home screen before any server is connected." - }, - - "homeNotProductionReadyBanner": "Chanora is currently in early development. This build is not production-ready.", + "homeNotProductionReadyBanner": "Alpha build — not production ready.", "@homeNotProductionReadyBanner": { "description": "Plain-language banner informing testers that this build is not for end-user use. Mirrors README.md's status line." }, - "connectAction": "Connect to a server", + "fieldServerHost": "Server address", + "@fieldServerHost": { + "description": "Label for the server-address input on the connect form." + }, + "fieldNickname": "Nickname", + "@fieldNickname": { + "description": "Label for the nickname input on the connect form." + }, + + "connectAction": "Connect", "@connectAction": { - "description": "Label for the primary action that opens the connect-to-server flow." + "description": "Label for the button that initiates a connection." + }, + "disconnectAction": "Disconnect", + "@disconnectAction": { + "description": "Label for the button that ends the active connection." + }, + "refreshAction": "Refresh", + "@refreshAction": { + "description": "Label for the button that re-fetches the server snapshot." + }, + + "statusIdle": "Not connected", + "@statusIdle": {}, + "statusConnecting": "Connecting…", + "@statusConnecting": {}, + "statusConnected": "Connected to {server}", + "@statusConnected": { + "placeholders": { + "server": { "type": "String" } + } + }, + "statusError": "Error: {message}", + "@statusError": { + "placeholders": { + "message": { "type": "String" } + } + }, + + "channelsHeading": "Channels", + "@channelsHeading": {}, + "clientsHeading": "Online clients", + "@clientsHeading": {}, + "countChannelsAndClients": "{channels} channels • {clients} online", + "@countChannelsAndClients": { + "placeholders": { + "channels": { "type": "int" }, + "clients": { "type": "int" } + } } } diff --git a/apps/chanora_flutter/lib/l10n/app_zh.arb b/apps/chanora_flutter/lib/l10n/app_zh.arb index 4a72ca6..00b4dea 100644 --- a/apps/chanora_flutter/lib/l10n/app_zh.arb +++ b/apps/chanora_flutter/lib/l10n/app_zh.arb @@ -3,7 +3,21 @@ "@@x-source-of-truth": "DEC-015 (register v0.9.5). Simplified Chinese translations for the MVP key set. Keys must match app_en.arb; server-provided content is NOT translated (ADR-008 / DEC-015).", "appTitle": "Chanora", - "homeGreeting": "欢迎使用 Chanora", - "homeNotProductionReadyBanner": "Chanora 目前处于早期开发阶段,此版本尚未达到正式发布质量。", - "connectAction": "连接到服务器" + "homeNotProductionReadyBanner": "Alpha 版本——尚未达到生产环境质量。", + + "fieldServerHost": "服务器地址", + "fieldNickname": "昵称", + + "connectAction": "连接", + "disconnectAction": "断开连接", + "refreshAction": "刷新", + + "statusIdle": "未连接", + "statusConnecting": "正在连接…", + "statusConnected": "已连接到 {server}", + "statusError": "错误:{message}", + + "channelsHeading": "频道", + "clientsHeading": "在线用户", + "countChannelsAndClients": "{channels} 个频道 • {clients} 在线" } diff --git a/apps/chanora_flutter/lib/l10n/generated/app_localizations.dart b/apps/chanora_flutter/lib/l10n/generated/app_localizations.dart index 7e5b5ca..ae002b8 100644 --- a/apps/chanora_flutter/lib/l10n/generated/app_localizations.dart +++ b/apps/chanora_flutter/lib/l10n/generated/app_localizations.dart @@ -103,23 +103,83 @@ abstract class AppL10n { /// **'Chanora'** String get appTitle; - /// Greeting shown on the empty home screen before any server is connected. - /// - /// In en, this message translates to: - /// **'Welcome to Chanora'** - String get homeGreeting; - /// Plain-language banner informing testers that this build is not for end-user use. Mirrors README.md's status line. /// /// In en, this message translates to: - /// **'Chanora is currently in early development. This build is not production-ready.'** + /// **'Alpha build — not production ready.'** String get homeNotProductionReadyBanner; - /// Label for the primary action that opens the connect-to-server flow. + /// Label for the server-address input on the connect form. /// /// In en, this message translates to: - /// **'Connect to a server'** + /// **'Server address'** + String get fieldServerHost; + + /// Label for the nickname input on the connect form. + /// + /// In en, this message translates to: + /// **'Nickname'** + String get fieldNickname; + + /// Label for the button that initiates a connection. + /// + /// In en, this message translates to: + /// **'Connect'** String get connectAction; + + /// Label for the button that ends the active connection. + /// + /// In en, this message translates to: + /// **'Disconnect'** + String get disconnectAction; + + /// Label for the button that re-fetches the server snapshot. + /// + /// In en, this message translates to: + /// **'Refresh'** + String get refreshAction; + + /// No description provided for @statusIdle. + /// + /// In en, this message translates to: + /// **'Not connected'** + String get statusIdle; + + /// No description provided for @statusConnecting. + /// + /// In en, this message translates to: + /// **'Connecting…'** + String get statusConnecting; + + /// No description provided for @statusConnected. + /// + /// In en, this message translates to: + /// **'Connected to {server}'** + String statusConnected(String server); + + /// No description provided for @statusError. + /// + /// In en, this message translates to: + /// **'Error: {message}'** + String statusError(String message); + + /// No description provided for @channelsHeading. + /// + /// In en, this message translates to: + /// **'Channels'** + String get channelsHeading; + + /// No description provided for @clientsHeading. + /// + /// In en, this message translates to: + /// **'Online clients'** + String get clientsHeading; + + /// No description provided for @countChannelsAndClients. + /// + /// In en, this message translates to: + /// **'{channels} channels • {clients} online'** + String countChannelsAndClients(int channels, int clients); } class _AppL10nDelegate extends LocalizationsDelegate { diff --git a/apps/chanora_flutter/lib/l10n/generated/app_localizations_en.dart b/apps/chanora_flutter/lib/l10n/generated/app_localizations_en.dart index 9647c70..d6d173f 100644 --- a/apps/chanora_flutter/lib/l10n/generated/app_localizations_en.dart +++ b/apps/chanora_flutter/lib/l10n/generated/app_localizations_en.dart @@ -11,13 +11,49 @@ class AppL10nEn extends AppL10n { @override String get appTitle => 'Chanora'; - @override - String get homeGreeting => 'Welcome to Chanora'; - @override String get homeNotProductionReadyBanner => - 'Chanora is currently in early development. This build is not production-ready.'; + 'Alpha build — not production ready.'; @override - String get connectAction => 'Connect to a server'; + String get fieldServerHost => 'Server address'; + + @override + String get fieldNickname => 'Nickname'; + + @override + String get connectAction => 'Connect'; + + @override + String get disconnectAction => 'Disconnect'; + + @override + String get refreshAction => 'Refresh'; + + @override + String get statusIdle => 'Not connected'; + + @override + String get statusConnecting => 'Connecting…'; + + @override + String statusConnected(String server) { + return 'Connected to $server'; + } + + @override + String statusError(String message) { + return 'Error: $message'; + } + + @override + String get channelsHeading => 'Channels'; + + @override + String get clientsHeading => 'Online clients'; + + @override + String countChannelsAndClients(int channels, int clients) { + return '$channels channels • $clients online'; + } } diff --git a/apps/chanora_flutter/lib/l10n/generated/app_localizations_zh.dart b/apps/chanora_flutter/lib/l10n/generated/app_localizations_zh.dart index c1c87ca..bdb0b9c 100644 --- a/apps/chanora_flutter/lib/l10n/generated/app_localizations_zh.dart +++ b/apps/chanora_flutter/lib/l10n/generated/app_localizations_zh.dart @@ -12,12 +12,47 @@ class AppL10nZh extends AppL10n { String get appTitle => 'Chanora'; @override - String get homeGreeting => '欢迎使用 Chanora'; + String get homeNotProductionReadyBanner => 'Alpha 版本——尚未达到生产环境质量。'; @override - String get homeNotProductionReadyBanner => - 'Chanora 目前处于早期开发阶段,此版本尚未达到正式发布质量。'; + String get fieldServerHost => '服务器地址'; @override - String get connectAction => '连接到服务器'; + String get fieldNickname => '昵称'; + + @override + String get connectAction => '连接'; + + @override + String get disconnectAction => '断开连接'; + + @override + String get refreshAction => '刷新'; + + @override + String get statusIdle => '未连接'; + + @override + String get statusConnecting => '正在连接…'; + + @override + String statusConnected(String server) { + return '已连接到 $server'; + } + + @override + String statusError(String message) { + return '错误:$message'; + } + + @override + String get channelsHeading => '频道'; + + @override + String get clientsHeading => '在线用户'; + + @override + String countChannelsAndClients(int channels, int clients) { + return '$channels 个频道 • $clients 在线'; + } } diff --git a/apps/chanora_flutter/lib/main.dart b/apps/chanora_flutter/lib/main.dart index f6d551f..75b11bc 100644 --- a/apps/chanora_flutter/lib/main.dart +++ b/apps/chanora_flutter/lib/main.dart @@ -1,16 +1,18 @@ -// Chanora Flutter application entry point. +// Chanora Flutter application entry point — Alpha build. // -// Scaffold only. This file is the minimum viable wiring to demonstrate -// the i18n architecture (DEC-015: English + Chinese Simplified) and to -// give `flutter analyze` and `flutter build linux --debug` something -// to compile. Real product UI lands later when the Chanora Design -// System is implemented per docs/ui-ux/. +// Wires the Alpha UI: server-address + nickname form, connect button, +// channel tree, disconnect. All names from server-side state are +// preserved verbatim per ADR-008 / DEC-015. import 'package:flutter/material.dart'; import 'l10n/generated/app_localizations.dart'; +import 'src/rust/api.dart' as rust; +import 'src/rust/frb_generated.dart'; -void main() { +Future main() async { + WidgetsFlutterBinding.ensureInitialized(); + await RustLib.init(); runApp(const ChanoraApp()); } @@ -26,47 +28,267 @@ class ChanoraApp extends StatelessWidget { colorSchemeSeed: const Color(0xFF3F51B5), ), // DEC-015 (register v0.9.5): English + Chinese Simplified at MVP. - // i18n-ready architecture; additional locales add an ARB file - // under lib/l10n and a Locale entry here. localizationsDelegates: AppL10n.localizationsDelegates, supportedLocales: AppL10n.supportedLocales, - home: const _HomeScreen(), + home: const _AlphaHome(), ); } } -class _HomeScreen extends StatelessWidget { - const _HomeScreen(); +/// Three-state UI: idle / connecting / connected. Errors collapse +/// back to idle with the message captured. +class _AlphaHome extends StatefulWidget { + const _AlphaHome(); + + @override + State<_AlphaHome> createState() => _AlphaHomeState(); +} + +enum _Phase { idle, connecting, connected } + +class _AlphaHomeState extends State<_AlphaHome> { + final _hostCtl = TextEditingController(text: 'cn.teamspeak.app'); + final _nickCtl = TextEditingController(text: 'ChanoraAlpha'); + + _Phase _phase = _Phase.idle; + rust.BridgeSnapshot? _snapshot; + String? _error; + + @override + void dispose() { + _hostCtl.dispose(); + _nickCtl.dispose(); + super.dispose(); + } + + Future _onConnect() async { + setState(() { + _phase = _Phase.connecting; + _error = null; + _snapshot = null; + }); + try { + final snap = await rust.connect( + host: _hostCtl.text.trim(), + nickname: _nickCtl.text.trim(), + ); + if (!mounted) return; + setState(() { + _phase = _Phase.connected; + _snapshot = snap; + }); + } catch (e) { + if (!mounted) return; + setState(() { + _phase = _Phase.idle; + _error = e.toString(); + }); + } + } + + Future _onRefresh() async { + try { + final snap = await rust.snapshot(); + if (!mounted) return; + setState(() => _snapshot = snap); + } catch (e) { + if (!mounted) return; + setState(() => _error = e.toString()); + } + } + + Future _onDisconnect() async { + try { + await rust.disconnect(); + } catch (_) { + // Best-effort. Even if disconnect throws we drop back to idle. + } + if (!mounted) return; + setState(() { + _phase = _Phase.idle; + _snapshot = null; + _error = null; + }); + } @override Widget build(BuildContext context) { final l10n = AppL10n.of(context); final theme = Theme.of(context); + + String statusText() { + switch (_phase) { + case _Phase.idle: + return _error != null ? l10n.statusError(_error!) : l10n.statusIdle; + case _Phase.connecting: + return l10n.statusConnecting; + case _Phase.connected: + return l10n.statusConnected(_snapshot?.serverName ?? ''); + } + } + return Scaffold( - appBar: AppBar(title: Text(l10n.appTitle)), + appBar: AppBar( + title: Text(l10n.appTitle), + actions: [ + if (_phase == _Phase.connected) ...[ + IconButton( + tooltip: l10n.refreshAction, + icon: const Icon(Icons.refresh), + onPressed: _onRefresh, + ), + IconButton( + tooltip: l10n.disconnectAction, + icon: const Icon(Icons.logout), + onPressed: _onDisconnect, + ), + ], + ], + ), body: Padding( - padding: const EdgeInsets.all(24), + padding: const EdgeInsets.all(16), child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - Text(l10n.homeGreeting, style: theme.textTheme.headlineSmall), - const SizedBox(height: 16), + // Banner Container( - padding: const EdgeInsets.all(12), + padding: const EdgeInsets.all(10), decoration: BoxDecoration( - color: theme.colorScheme.surfaceContainerHighest, + color: theme.colorScheme.tertiaryContainer, borderRadius: BorderRadius.circular(8), ), - child: Text(l10n.homeNotProductionReadyBanner), - ), - const SizedBox(height: 24), - FilledButton( - onPressed: null, // wired in a later phase - child: Text(l10n.connectAction), + child: Text( + l10n.homeNotProductionReadyBanner, + style: TextStyle(color: theme.colorScheme.onTertiaryContainer), + ), ), + const SizedBox(height: 12), + // Status + Text(statusText(), style: theme.textTheme.titleMedium), + const SizedBox(height: 12), + if (_phase == _Phase.idle) ...[ + _ConnectForm( + hostCtl: _hostCtl, + nickCtl: _nickCtl, + onConnect: _onConnect, + ), + ] else if (_phase == _Phase.connecting) ...[ + const Center(child: Padding( + padding: EdgeInsets.all(32), + child: CircularProgressIndicator(), + )), + ] else if (_phase == _Phase.connected && _snapshot != null) ...[ + Expanded(child: _SnapshotView(snapshot: _snapshot!)), + ], ], ), ), ); } } + +class _ConnectForm extends StatelessWidget { + const _ConnectForm({ + required this.hostCtl, + required this.nickCtl, + required this.onConnect, + }); + + final TextEditingController hostCtl; + final TextEditingController nickCtl; + final VoidCallback onConnect; + + @override + Widget build(BuildContext context) { + final l10n = AppL10n.of(context); + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + TextField( + controller: hostCtl, + decoration: InputDecoration( + labelText: l10n.fieldServerHost, + border: const OutlineInputBorder(), + ), + ), + const SizedBox(height: 8), + TextField( + controller: nickCtl, + decoration: InputDecoration( + labelText: l10n.fieldNickname, + border: const OutlineInputBorder(), + ), + ), + const SizedBox(height: 16), + FilledButton.icon( + icon: const Icon(Icons.login), + label: Text(l10n.connectAction), + onPressed: onConnect, + ), + ], + ); + } +} + +class _SnapshotView extends StatelessWidget { + const _SnapshotView({required this.snapshot}); + + final rust.BridgeSnapshot snapshot; + + @override + Widget build(BuildContext context) { + final l10n = AppL10n.of(context); + final theme = Theme.of(context); + + final channels = [...snapshot.channels] + ..sort((a, b) => a.order.compareTo(b.order)); + + // Index clients by channel id for the tree view. + final byChannel = >{}; + for (final c in snapshot.clients) { + byChannel.putIfAbsent(c.channel, () => []).add(c); + } + + return ListView( + children: [ + Text( + l10n.countChannelsAndClients(snapshot.channels.length, snapshot.clients.length), + style: theme.textTheme.bodyMedium, + ), + if (snapshot.welcomeMessage.isNotEmpty) ...[ + const SizedBox(height: 8), + Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: theme.colorScheme.surfaceContainerHighest, + borderRadius: BorderRadius.circular(6), + ), + // Server-provided content; preserved verbatim per ADR-008. + child: Text(snapshot.welcomeMessage, style: theme.textTheme.bodySmall), + ), + ], + const Divider(height: 24), + Text(l10n.channelsHeading, style: theme.textTheme.titleMedium), + const SizedBox(height: 4), + for (final ch in channels) ...[ + ListTile( + dense: true, + leading: const Icon(Icons.tag), + title: Text(ch.name), + subtitle: Text('id=${ch.id} parent=${ch.parent}'), + ), + for (final cl in byChannel[ch.id] ?? const []) + Padding( + padding: const EdgeInsets.only(left: 64), + child: ListTile( + dense: true, + visualDensity: VisualDensity.compact, + leading: const Icon(Icons.person, size: 18), + title: Text(cl.name), + ), + ), + ], + ], + ); + } +} diff --git a/apps/chanora_flutter/lib/src/rust/api.dart b/apps/chanora_flutter/lib/src/rust/api.dart new file mode 100644 index 0000000..b01d53f --- /dev/null +++ b/apps/chanora_flutter/lib/src/rust/api.dart @@ -0,0 +1,147 @@ +// This file is automatically generated, so please do not edit it. +// @generated by `flutter_rust_bridge`@ 2.12.0. + +// ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import + +import 'frb_generated.dart'; +import 'lib.dart'; +import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart'; + +// These functions are ignored because they are not marked as `pub`: `runtime`, `session` +// These function are ignored because they are on traits that is not defined in current crate (put an empty `#[frb]` on it to unignore): `clone`, `clone`, `clone`, `fmt`, `fmt`, `fmt`, `from` + +/// Connect to a TeamSpeak-compatible server and return the initial +/// state snapshot. Honours the DEC-006 single-connection invariant +/// via [`BridgeError::AlreadyConnected`]. +Future connect({ + required String host, + required String nickname, +}) => RustLib.instance.api.crateApiConnect(host: host, nickname: nickname); + +/// Re-fetch a fresh snapshot from the active connection. +Future snapshot() => RustLib.instance.api.crateApiSnapshot(); + +/// Disconnect from the server. No-op if not connected. +Future disconnect() => RustLib.instance.api.crateApiDisconnect(); + +/// True if a connection is currently active. +Future isConnected() => RustLib.instance.api.crateApiIsConnected(); + +/// Channel as seen by Dart. Matches `chanora_protocol::ChannelInfo` +/// but with primitive `u64` ids so the Dart side gets `BigInt`s +/// without any wrapper-type ceremony. +class BridgeChannel { + /// Stable channel id. + final BigInt id; + + /// Parent channel id; 0 means top-level. + final BigInt parent; + + /// Display name. + final String name; + + /// Server-side ordering hint. + final PlatformInt64 order; + + const BridgeChannel({ + required this.id, + required this.parent, + required this.name, + required this.order, + }); + + @override + int get hashCode => + id.hashCode ^ parent.hashCode ^ name.hashCode ^ order.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is BridgeChannel && + runtimeType == other.runtimeType && + id == other.id && + parent == other.parent && + name == other.name && + order == other.order; +} + +/// Client as seen by Dart. +class BridgeClient { + /// Stable client id. + final BigInt id; + + /// Channel id the client is currently in. + final BigInt channel; + + /// Nickname. + final String name; + + const BridgeClient({ + required this.id, + required this.channel, + required this.name, + }); + + @override + int get hashCode => id.hashCode ^ channel.hashCode ^ name.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is BridgeClient && + runtimeType == other.runtimeType && + id == other.id && + channel == other.channel && + name == other.name; +} + +/// Server snapshot as seen by Dart. +class BridgeSnapshot { + /// Server name. + final String serverName; + + /// Welcome banner text. + final String welcomeMessage; + + /// Server platform (e.g. "Linux"). + final String platform; + + /// Server version string. + final String version; + + /// Channels currently known. + final List channels; + + /// Clients currently known. + final List clients; + + const BridgeSnapshot({ + required this.serverName, + required this.welcomeMessage, + required this.platform, + required this.version, + required this.channels, + required this.clients, + }); + + @override + int get hashCode => + serverName.hashCode ^ + welcomeMessage.hashCode ^ + platform.hashCode ^ + version.hashCode ^ + channels.hashCode ^ + clients.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is BridgeSnapshot && + runtimeType == other.runtimeType && + serverName == other.serverName && + welcomeMessage == other.welcomeMessage && + platform == other.platform && + version == other.version && + channels == other.channels && + clients == other.clients; +} diff --git a/apps/chanora_flutter/lib/src/rust/frb_generated.dart b/apps/chanora_flutter/lib/src/rust/frb_generated.dart new file mode 100644 index 0000000..6f2153d --- /dev/null +++ b/apps/chanora_flutter/lib/src/rust/frb_generated.dart @@ -0,0 +1,629 @@ +// This file is automatically generated, so please do not edit it. +// @generated by `flutter_rust_bridge`@ 2.12.0. + +// ignore_for_file: unused_import, unused_element, unnecessary_import, duplicate_ignore, invalid_use_of_internal_member, annotate_overrides, non_constant_identifier_names, curly_braces_in_flow_control_structures, prefer_const_literals_to_create_immutables, unused_field + +import 'api.dart'; +import 'dart:async'; +import 'dart:convert'; +import 'frb_generated.dart'; +import 'frb_generated.io.dart' + if (dart.library.js_interop) 'frb_generated.web.dart'; +import 'lib.dart'; +import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart'; + +/// Main entrypoint of the Rust API +class RustLib extends BaseEntrypoint { + @internal + static final instance = RustLib._(); + + RustLib._(); + + /// Initialize flutter_rust_bridge + static Future init({ + RustLibApi? api, + BaseHandler? handler, + ExternalLibrary? externalLibrary, + bool forceSameCodegenVersion = true, + }) async { + await instance.initImpl( + api: api, + handler: handler, + externalLibrary: externalLibrary, + forceSameCodegenVersion: forceSameCodegenVersion, + ); + } + + /// Initialize flutter_rust_bridge in mock mode. + /// No libraries for FFI are loaded. + static void initMock({required RustLibApi api}) { + instance.initMockImpl(api: api); + } + + /// Dispose flutter_rust_bridge + /// + /// The call to this function is optional, since flutter_rust_bridge (and everything else) + /// is automatically disposed when the app stops. + static void dispose() => instance.disposeImpl(); + + @override + ApiImplConstructor get apiImplConstructor => + RustLibApiImpl.new; + + @override + WireConstructor get wireConstructor => + RustLibWire.fromExternalLibrary; + + @override + Future executeRustInitializers() async { + await api.crateApiBridgeInit(); + } + + @override + ExternalLibraryLoaderConfig get defaultExternalLibraryLoaderConfig => + kDefaultExternalLibraryLoaderConfig; + + @override + String get codegenVersion => '2.12.0'; + + @override + int get rustContentHash => 978717843; + + static const kDefaultExternalLibraryLoaderConfig = + ExternalLibraryLoaderConfig( + stem: 'chanora_bridge', + ioDirectory: '../../crates/chanora_bridge/target/release/', + webPrefix: 'pkg/', + wasmBindgenName: 'wasm_bindgen', + ); +} + +abstract class RustLibApi extends BaseApi { + Future crateApiBridgeInit(); + + Future crateApiConnect({ + required String host, + required String nickname, + }); + + Future crateApiDisconnect(); + + Future crateApiIsConnected(); + + Future crateApiSnapshot(); +} + +class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { + RustLibApiImpl({ + required super.handler, + required super.wire, + required super.generalizedFrbRustBinding, + required super.portManager, + }); + + @override + Future crateApiBridgeInit() { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 1, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_unit, + decodeErrorData: null, + ), + constMeta: kCrateApiBridgeInitConstMeta, + argValues: [], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateApiBridgeInitConstMeta => + const TaskConstMeta(debugName: "bridge_init", argNames: []); + + @override + Future crateApiConnect({ + required String host, + required String nickname, + }) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_String(host, serializer); + sse_encode_String(nickname, serializer); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 2, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_bridge_snapshot, + decodeErrorData: sse_decode_bridge_error, + ), + constMeta: kCrateApiConnectConstMeta, + argValues: [host, nickname], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateApiConnectConstMeta => + const TaskConstMeta(debugName: "connect", argNames: ["host", "nickname"]); + + @override + Future crateApiDisconnect() { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 3, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_unit, + decodeErrorData: sse_decode_bridge_error, + ), + constMeta: kCrateApiDisconnectConstMeta, + argValues: [], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateApiDisconnectConstMeta => + const TaskConstMeta(debugName: "disconnect", argNames: []); + + @override + Future crateApiIsConnected() { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 4, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_bool, + decodeErrorData: null, + ), + constMeta: kCrateApiIsConnectedConstMeta, + argValues: [], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateApiIsConnectedConstMeta => + const TaskConstMeta(debugName: "is_connected", argNames: []); + + @override + Future crateApiSnapshot() { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 5, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_bridge_snapshot, + decodeErrorData: sse_decode_bridge_error, + ), + constMeta: kCrateApiSnapshotConstMeta, + argValues: [], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateApiSnapshotConstMeta => + const TaskConstMeta(debugName: "snapshot", argNames: []); + + @protected + String dco_decode_String(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return raw as String; + } + + @protected + bool dco_decode_bool(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return raw as bool; + } + + @protected + BridgeChannel dco_decode_bridge_channel(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + final arr = raw as List; + if (arr.length != 4) + throw Exception('unexpected arr length: expect 4 but see ${arr.length}'); + return BridgeChannel( + id: dco_decode_u_64(arr[0]), + parent: dco_decode_u_64(arr[1]), + name: dco_decode_String(arr[2]), + order: dco_decode_i_64(arr[3]), + ); + } + + @protected + BridgeClient dco_decode_bridge_client(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + final arr = raw as List; + if (arr.length != 3) + throw Exception('unexpected arr length: expect 3 but see ${arr.length}'); + return BridgeClient( + id: dco_decode_u_64(arr[0]), + channel: dco_decode_u_64(arr[1]), + name: dco_decode_String(arr[2]), + ); + } + + @protected + BridgeError dco_decode_bridge_error(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + switch (raw[0]) { + case 0: + return BridgeError_InvalidCommand(dco_decode_String(raw[1])); + case 1: + return BridgeError_Connection(dco_decode_String(raw[1])); + case 2: + return BridgeError_NotConnected(); + case 3: + return BridgeError_AlreadyConnected(); + case 4: + return BridgeError_Unmapped(dco_decode_String(raw[1])); + default: + throw Exception("unreachable"); + } + } + + @protected + BridgeSnapshot dco_decode_bridge_snapshot(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + final arr = raw as List; + if (arr.length != 6) + throw Exception('unexpected arr length: expect 6 but see ${arr.length}'); + return BridgeSnapshot( + serverName: dco_decode_String(arr[0]), + welcomeMessage: dco_decode_String(arr[1]), + platform: dco_decode_String(arr[2]), + version: dco_decode_String(arr[3]), + channels: dco_decode_list_bridge_channel(arr[4]), + clients: dco_decode_list_bridge_client(arr[5]), + ); + } + + @protected + PlatformInt64 dco_decode_i_64(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return dcoDecodeI64(raw); + } + + @protected + List dco_decode_list_bridge_channel(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return (raw as List).map(dco_decode_bridge_channel).toList(); + } + + @protected + List dco_decode_list_bridge_client(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return (raw as List).map(dco_decode_bridge_client).toList(); + } + + @protected + Uint8List dco_decode_list_prim_u_8_strict(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return raw as Uint8List; + } + + @protected + BigInt dco_decode_u_64(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return dcoDecodeU64(raw); + } + + @protected + int dco_decode_u_8(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return raw as int; + } + + @protected + void dco_decode_unit(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return; + } + + @protected + String sse_decode_String(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + var inner = sse_decode_list_prim_u_8_strict(deserializer); + return utf8.decoder.convert(inner); + } + + @protected + bool sse_decode_bool(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + return deserializer.buffer.getUint8() != 0; + } + + @protected + BridgeChannel sse_decode_bridge_channel(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + var var_id = sse_decode_u_64(deserializer); + var var_parent = sse_decode_u_64(deserializer); + var var_name = sse_decode_String(deserializer); + var var_order = sse_decode_i_64(deserializer); + return BridgeChannel( + id: var_id, + parent: var_parent, + name: var_name, + order: var_order, + ); + } + + @protected + BridgeClient sse_decode_bridge_client(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + var var_id = sse_decode_u_64(deserializer); + var var_channel = sse_decode_u_64(deserializer); + var var_name = sse_decode_String(deserializer); + return BridgeClient(id: var_id, channel: var_channel, name: var_name); + } + + @protected + BridgeError sse_decode_bridge_error(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + + var tag_ = sse_decode_i_32(deserializer); + switch (tag_) { + case 0: + var var_field0 = sse_decode_String(deserializer); + return BridgeError_InvalidCommand(var_field0); + case 1: + var var_field0 = sse_decode_String(deserializer); + return BridgeError_Connection(var_field0); + case 2: + return BridgeError_NotConnected(); + case 3: + return BridgeError_AlreadyConnected(); + case 4: + var var_field0 = sse_decode_String(deserializer); + return BridgeError_Unmapped(var_field0); + default: + throw UnimplementedError(''); + } + } + + @protected + BridgeSnapshot sse_decode_bridge_snapshot(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + var var_serverName = sse_decode_String(deserializer); + var var_welcomeMessage = sse_decode_String(deserializer); + var var_platform = sse_decode_String(deserializer); + var var_version = sse_decode_String(deserializer); + var var_channels = sse_decode_list_bridge_channel(deserializer); + var var_clients = sse_decode_list_bridge_client(deserializer); + return BridgeSnapshot( + serverName: var_serverName, + welcomeMessage: var_welcomeMessage, + platform: var_platform, + version: var_version, + channels: var_channels, + clients: var_clients, + ); + } + + @protected + PlatformInt64 sse_decode_i_64(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + return deserializer.buffer.getPlatformInt64(); + } + + @protected + List sse_decode_list_bridge_channel( + SseDeserializer deserializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + + var len_ = sse_decode_i_32(deserializer); + var ans_ = []; + for (var idx_ = 0; idx_ < len_; ++idx_) { + ans_.add(sse_decode_bridge_channel(deserializer)); + } + return ans_; + } + + @protected + List sse_decode_list_bridge_client( + SseDeserializer deserializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + + var len_ = sse_decode_i_32(deserializer); + var ans_ = []; + for (var idx_ = 0; idx_ < len_; ++idx_) { + ans_.add(sse_decode_bridge_client(deserializer)); + } + return ans_; + } + + @protected + Uint8List sse_decode_list_prim_u_8_strict(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + var len_ = sse_decode_i_32(deserializer); + return deserializer.buffer.getUint8List(len_); + } + + @protected + BigInt sse_decode_u_64(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + return deserializer.buffer.getBigUint64(); + } + + @protected + int sse_decode_u_8(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + return deserializer.buffer.getUint8(); + } + + @protected + void sse_decode_unit(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + } + + @protected + int sse_decode_i_32(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + return deserializer.buffer.getInt32(); + } + + @protected + void sse_encode_String(String self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_list_prim_u_8_strict(utf8.encoder.convert(self), serializer); + } + + @protected + void sse_encode_bool(bool self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + serializer.buffer.putUint8(self ? 1 : 0); + } + + @protected + void sse_encode_bridge_channel(BridgeChannel self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_u_64(self.id, serializer); + sse_encode_u_64(self.parent, serializer); + sse_encode_String(self.name, serializer); + sse_encode_i_64(self.order, serializer); + } + + @protected + void sse_encode_bridge_client(BridgeClient self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_u_64(self.id, serializer); + sse_encode_u_64(self.channel, serializer); + sse_encode_String(self.name, serializer); + } + + @protected + void sse_encode_bridge_error(BridgeError self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + switch (self) { + case BridgeError_InvalidCommand(field0: final field0): + sse_encode_i_32(0, serializer); + sse_encode_String(field0, serializer); + case BridgeError_Connection(field0: final field0): + sse_encode_i_32(1, serializer); + sse_encode_String(field0, serializer); + case BridgeError_NotConnected(): + sse_encode_i_32(2, serializer); + case BridgeError_AlreadyConnected(): + sse_encode_i_32(3, serializer); + case BridgeError_Unmapped(field0: final field0): + sse_encode_i_32(4, serializer); + sse_encode_String(field0, serializer); + } + } + + @protected + void sse_encode_bridge_snapshot( + BridgeSnapshot self, + SseSerializer serializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_String(self.serverName, serializer); + sse_encode_String(self.welcomeMessage, serializer); + sse_encode_String(self.platform, serializer); + sse_encode_String(self.version, serializer); + sse_encode_list_bridge_channel(self.channels, serializer); + sse_encode_list_bridge_client(self.clients, serializer); + } + + @protected + void sse_encode_i_64(PlatformInt64 self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + serializer.buffer.putPlatformInt64(self); + } + + @protected + void sse_encode_list_bridge_channel( + List self, + SseSerializer serializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_i_32(self.length, serializer); + for (final item in self) { + sse_encode_bridge_channel(item, serializer); + } + } + + @protected + void sse_encode_list_bridge_client( + List self, + SseSerializer serializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_i_32(self.length, serializer); + for (final item in self) { + sse_encode_bridge_client(item, serializer); + } + } + + @protected + void sse_encode_list_prim_u_8_strict( + Uint8List self, + SseSerializer serializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_i_32(self.length, serializer); + serializer.buffer.putUint8List(self); + } + + @protected + void sse_encode_u_64(BigInt self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + serializer.buffer.putBigUint64(self); + } + + @protected + void sse_encode_u_8(int self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + serializer.buffer.putUint8(self); + } + + @protected + void sse_encode_unit(void self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + } + + @protected + void sse_encode_i_32(int self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + serializer.buffer.putInt32(self); + } +} diff --git a/apps/chanora_flutter/lib/src/rust/frb_generated.io.dart b/apps/chanora_flutter/lib/src/rust/frb_generated.io.dart new file mode 100644 index 0000000..e2dbeea --- /dev/null +++ b/apps/chanora_flutter/lib/src/rust/frb_generated.io.dart @@ -0,0 +1,175 @@ +// This file is automatically generated, so please do not edit it. +// @generated by `flutter_rust_bridge`@ 2.12.0. + +// ignore_for_file: unused_import, unused_element, unnecessary_import, duplicate_ignore, invalid_use_of_internal_member, annotate_overrides, non_constant_identifier_names, curly_braces_in_flow_control_structures, prefer_const_literals_to_create_immutables, unused_field + +import 'api.dart'; +import 'dart:async'; +import 'dart:convert'; +import 'dart:ffi' as ffi; +import 'frb_generated.dart'; +import 'lib.dart'; +import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated_io.dart'; + +abstract class RustLibApiImplPlatform extends BaseApiImpl { + RustLibApiImplPlatform({ + required super.handler, + required super.wire, + required super.generalizedFrbRustBinding, + required super.portManager, + }); + + @protected + String dco_decode_String(dynamic raw); + + @protected + bool dco_decode_bool(dynamic raw); + + @protected + BridgeChannel dco_decode_bridge_channel(dynamic raw); + + @protected + BridgeClient dco_decode_bridge_client(dynamic raw); + + @protected + BridgeError dco_decode_bridge_error(dynamic raw); + + @protected + BridgeSnapshot dco_decode_bridge_snapshot(dynamic raw); + + @protected + PlatformInt64 dco_decode_i_64(dynamic raw); + + @protected + List dco_decode_list_bridge_channel(dynamic raw); + + @protected + List dco_decode_list_bridge_client(dynamic raw); + + @protected + Uint8List dco_decode_list_prim_u_8_strict(dynamic raw); + + @protected + BigInt dco_decode_u_64(dynamic raw); + + @protected + int dco_decode_u_8(dynamic raw); + + @protected + void dco_decode_unit(dynamic raw); + + @protected + String sse_decode_String(SseDeserializer deserializer); + + @protected + bool sse_decode_bool(SseDeserializer deserializer); + + @protected + BridgeChannel sse_decode_bridge_channel(SseDeserializer deserializer); + + @protected + BridgeClient sse_decode_bridge_client(SseDeserializer deserializer); + + @protected + BridgeError sse_decode_bridge_error(SseDeserializer deserializer); + + @protected + BridgeSnapshot sse_decode_bridge_snapshot(SseDeserializer deserializer); + + @protected + PlatformInt64 sse_decode_i_64(SseDeserializer deserializer); + + @protected + List sse_decode_list_bridge_channel( + SseDeserializer deserializer, + ); + + @protected + List sse_decode_list_bridge_client( + SseDeserializer deserializer, + ); + + @protected + Uint8List sse_decode_list_prim_u_8_strict(SseDeserializer deserializer); + + @protected + BigInt sse_decode_u_64(SseDeserializer deserializer); + + @protected + int sse_decode_u_8(SseDeserializer deserializer); + + @protected + void sse_decode_unit(SseDeserializer deserializer); + + @protected + int sse_decode_i_32(SseDeserializer deserializer); + + @protected + void sse_encode_String(String self, SseSerializer serializer); + + @protected + void sse_encode_bool(bool self, SseSerializer serializer); + + @protected + void sse_encode_bridge_channel(BridgeChannel self, SseSerializer serializer); + + @protected + void sse_encode_bridge_client(BridgeClient self, SseSerializer serializer); + + @protected + void sse_encode_bridge_error(BridgeError self, SseSerializer serializer); + + @protected + void sse_encode_bridge_snapshot( + BridgeSnapshot self, + SseSerializer serializer, + ); + + @protected + void sse_encode_i_64(PlatformInt64 self, SseSerializer serializer); + + @protected + void sse_encode_list_bridge_channel( + List self, + SseSerializer serializer, + ); + + @protected + void sse_encode_list_bridge_client( + List self, + SseSerializer serializer, + ); + + @protected + void sse_encode_list_prim_u_8_strict( + Uint8List self, + SseSerializer serializer, + ); + + @protected + void sse_encode_u_64(BigInt self, SseSerializer serializer); + + @protected + void sse_encode_u_8(int self, SseSerializer serializer); + + @protected + void sse_encode_unit(void self, SseSerializer serializer); + + @protected + void sse_encode_i_32(int self, SseSerializer serializer); +} + +// Section: wire_class + +class RustLibWire implements BaseWire { + factory RustLibWire.fromExternalLibrary(ExternalLibrary lib) => + RustLibWire(lib.ffiDynamicLibrary); + + /// Holds the symbol lookup function. + final ffi.Pointer Function(String symbolName) + _lookup; + + /// The symbols are looked up in [dynamicLibrary]. + RustLibWire(ffi.DynamicLibrary dynamicLibrary) + : _lookup = dynamicLibrary.lookup; +} diff --git a/apps/chanora_flutter/lib/src/rust/frb_generated.web.dart b/apps/chanora_flutter/lib/src/rust/frb_generated.web.dart new file mode 100644 index 0000000..f51a4a6 --- /dev/null +++ b/apps/chanora_flutter/lib/src/rust/frb_generated.web.dart @@ -0,0 +1,175 @@ +// This file is automatically generated, so please do not edit it. +// @generated by `flutter_rust_bridge`@ 2.12.0. + +// ignore_for_file: unused_import, unused_element, unnecessary_import, duplicate_ignore, invalid_use_of_internal_member, annotate_overrides, non_constant_identifier_names, curly_braces_in_flow_control_structures, prefer_const_literals_to_create_immutables, unused_field + +// Static analysis wrongly picks the IO variant, thus ignore this +// ignore_for_file: argument_type_not_assignable + +import 'api.dart'; +import 'dart:async'; +import 'dart:convert'; +import 'frb_generated.dart'; +import 'lib.dart'; +import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated_web.dart'; + +abstract class RustLibApiImplPlatform extends BaseApiImpl { + RustLibApiImplPlatform({ + required super.handler, + required super.wire, + required super.generalizedFrbRustBinding, + required super.portManager, + }); + + @protected + String dco_decode_String(dynamic raw); + + @protected + bool dco_decode_bool(dynamic raw); + + @protected + BridgeChannel dco_decode_bridge_channel(dynamic raw); + + @protected + BridgeClient dco_decode_bridge_client(dynamic raw); + + @protected + BridgeError dco_decode_bridge_error(dynamic raw); + + @protected + BridgeSnapshot dco_decode_bridge_snapshot(dynamic raw); + + @protected + PlatformInt64 dco_decode_i_64(dynamic raw); + + @protected + List dco_decode_list_bridge_channel(dynamic raw); + + @protected + List dco_decode_list_bridge_client(dynamic raw); + + @protected + Uint8List dco_decode_list_prim_u_8_strict(dynamic raw); + + @protected + BigInt dco_decode_u_64(dynamic raw); + + @protected + int dco_decode_u_8(dynamic raw); + + @protected + void dco_decode_unit(dynamic raw); + + @protected + String sse_decode_String(SseDeserializer deserializer); + + @protected + bool sse_decode_bool(SseDeserializer deserializer); + + @protected + BridgeChannel sse_decode_bridge_channel(SseDeserializer deserializer); + + @protected + BridgeClient sse_decode_bridge_client(SseDeserializer deserializer); + + @protected + BridgeError sse_decode_bridge_error(SseDeserializer deserializer); + + @protected + BridgeSnapshot sse_decode_bridge_snapshot(SseDeserializer deserializer); + + @protected + PlatformInt64 sse_decode_i_64(SseDeserializer deserializer); + + @protected + List sse_decode_list_bridge_channel( + SseDeserializer deserializer, + ); + + @protected + List sse_decode_list_bridge_client( + SseDeserializer deserializer, + ); + + @protected + Uint8List sse_decode_list_prim_u_8_strict(SseDeserializer deserializer); + + @protected + BigInt sse_decode_u_64(SseDeserializer deserializer); + + @protected + int sse_decode_u_8(SseDeserializer deserializer); + + @protected + void sse_decode_unit(SseDeserializer deserializer); + + @protected + int sse_decode_i_32(SseDeserializer deserializer); + + @protected + void sse_encode_String(String self, SseSerializer serializer); + + @protected + void sse_encode_bool(bool self, SseSerializer serializer); + + @protected + void sse_encode_bridge_channel(BridgeChannel self, SseSerializer serializer); + + @protected + void sse_encode_bridge_client(BridgeClient self, SseSerializer serializer); + + @protected + void sse_encode_bridge_error(BridgeError self, SseSerializer serializer); + + @protected + void sse_encode_bridge_snapshot( + BridgeSnapshot self, + SseSerializer serializer, + ); + + @protected + void sse_encode_i_64(PlatformInt64 self, SseSerializer serializer); + + @protected + void sse_encode_list_bridge_channel( + List self, + SseSerializer serializer, + ); + + @protected + void sse_encode_list_bridge_client( + List self, + SseSerializer serializer, + ); + + @protected + void sse_encode_list_prim_u_8_strict( + Uint8List self, + SseSerializer serializer, + ); + + @protected + void sse_encode_u_64(BigInt self, SseSerializer serializer); + + @protected + void sse_encode_u_8(int self, SseSerializer serializer); + + @protected + void sse_encode_unit(void self, SseSerializer serializer); + + @protected + void sse_encode_i_32(int self, SseSerializer serializer); +} + +// Section: wire_class + +class RustLibWire implements BaseWire { + RustLibWire.fromExternalLibrary(ExternalLibrary lib); +} + +@JS('wasm_bindgen') +external RustLibWasmModule get wasmModule; + +@JS() +@anonymous +extension type RustLibWasmModule._(JSObject _) implements JSObject {} diff --git a/apps/chanora_flutter/lib/src/rust/lib.dart b/apps/chanora_flutter/lib/src/rust/lib.dart new file mode 100644 index 0000000..d4650a8 --- /dev/null +++ b/apps/chanora_flutter/lib/src/rust/lib.dart @@ -0,0 +1,33 @@ +// This file is automatically generated, so please do not edit it. +// @generated by `flutter_rust_bridge`@ 2.12.0. + +// ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import + +import 'frb_generated.dart'; +import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart'; +import 'package:freezed_annotation/freezed_annotation.dart' hide protected; +part 'lib.freezed.dart'; + +@freezed +sealed class BridgeError with _$BridgeError implements FrbException { + const BridgeError._(); + + /// The caller submitted a malformed command DTO. + const factory BridgeError.invalidCommand(String field0) = + BridgeError_InvalidCommand; + + /// Connection layer failure (typed-mapped from CoreError). + const factory BridgeError.connection(String field0) = BridgeError_Connection; + + /// Operation requires an active connection. + const factory BridgeError.notConnected() = BridgeError_NotConnected; + + /// Already connected; DEC-006 forbids a second concurrent + /// connection. + const factory BridgeError.alreadyConnected() = BridgeError_AlreadyConnected; + + /// An unmapped error escaped the subsystem boundary. Production + /// callers should never see this; if they do, it is a mapping + /// bug here. + const factory BridgeError.unmapped(String field0) = BridgeError_Unmapped; +} diff --git a/apps/chanora_flutter/lib/src/rust/lib.freezed.dart b/apps/chanora_flutter/lib/src/rust/lib.freezed.dart new file mode 100644 index 0000000..3a88eab --- /dev/null +++ b/apps/chanora_flutter/lib/src/rust/lib.freezed.dart @@ -0,0 +1,454 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND +// coverage:ignore-file +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'lib.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +// dart format off +T _$identity(T value) => value; +/// @nodoc +mixin _$BridgeError { + + + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is BridgeError); +} + + +@override +int get hashCode => runtimeType.hashCode; + +@override +String toString() { + return 'BridgeError()'; +} + + +} + +/// @nodoc +class $BridgeErrorCopyWith<$Res> { +$BridgeErrorCopyWith(BridgeError _, $Res Function(BridgeError) __); +} + + +/// Adds pattern-matching-related methods to [BridgeError]. +extension BridgeErrorPatterns on BridgeError { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap({TResult Function( BridgeError_InvalidCommand value)? invalidCommand,TResult Function( BridgeError_Connection value)? connection,TResult Function( BridgeError_NotConnected value)? notConnected,TResult Function( BridgeError_AlreadyConnected value)? alreadyConnected,TResult Function( BridgeError_Unmapped value)? unmapped,required TResult orElse(),}){ +final _that = this; +switch (_that) { +case BridgeError_InvalidCommand() when invalidCommand != null: +return invalidCommand(_that);case BridgeError_Connection() when connection != null: +return connection(_that);case BridgeError_NotConnected() when notConnected != null: +return notConnected(_that);case BridgeError_AlreadyConnected() when alreadyConnected != null: +return alreadyConnected(_that);case BridgeError_Unmapped() when unmapped != null: +return unmapped(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map({required TResult Function( BridgeError_InvalidCommand value) invalidCommand,required TResult Function( BridgeError_Connection value) connection,required TResult Function( BridgeError_NotConnected value) notConnected,required TResult Function( BridgeError_AlreadyConnected value) alreadyConnected,required TResult Function( BridgeError_Unmapped value) unmapped,}){ +final _that = this; +switch (_that) { +case BridgeError_InvalidCommand(): +return invalidCommand(_that);case BridgeError_Connection(): +return connection(_that);case BridgeError_NotConnected(): +return notConnected(_that);case BridgeError_AlreadyConnected(): +return alreadyConnected(_that);case BridgeError_Unmapped(): +return unmapped(_that);} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull({TResult? Function( BridgeError_InvalidCommand value)? invalidCommand,TResult? Function( BridgeError_Connection value)? connection,TResult? Function( BridgeError_NotConnected value)? notConnected,TResult? Function( BridgeError_AlreadyConnected value)? alreadyConnected,TResult? Function( BridgeError_Unmapped value)? unmapped,}){ +final _that = this; +switch (_that) { +case BridgeError_InvalidCommand() when invalidCommand != null: +return invalidCommand(_that);case BridgeError_Connection() when connection != null: +return connection(_that);case BridgeError_NotConnected() when notConnected != null: +return notConnected(_that);case BridgeError_AlreadyConnected() when alreadyConnected != null: +return alreadyConnected(_that);case BridgeError_Unmapped() when unmapped != null: +return unmapped(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen({TResult Function( String field0)? invalidCommand,TResult Function( String field0)? connection,TResult Function()? notConnected,TResult Function()? alreadyConnected,TResult Function( String field0)? unmapped,required TResult orElse(),}) {final _that = this; +switch (_that) { +case BridgeError_InvalidCommand() when invalidCommand != null: +return invalidCommand(_that.field0);case BridgeError_Connection() when connection != null: +return connection(_that.field0);case BridgeError_NotConnected() when notConnected != null: +return notConnected();case BridgeError_AlreadyConnected() when alreadyConnected != null: +return alreadyConnected();case BridgeError_Unmapped() when unmapped != null: +return unmapped(_that.field0);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when({required TResult Function( String field0) invalidCommand,required TResult Function( String field0) connection,required TResult Function() notConnected,required TResult Function() alreadyConnected,required TResult Function( String field0) unmapped,}) {final _that = this; +switch (_that) { +case BridgeError_InvalidCommand(): +return invalidCommand(_that.field0);case BridgeError_Connection(): +return connection(_that.field0);case BridgeError_NotConnected(): +return notConnected();case BridgeError_AlreadyConnected(): +return alreadyConnected();case BridgeError_Unmapped(): +return unmapped(_that.field0);} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull({TResult? Function( String field0)? invalidCommand,TResult? Function( String field0)? connection,TResult? Function()? notConnected,TResult? Function()? alreadyConnected,TResult? Function( String field0)? unmapped,}) {final _that = this; +switch (_that) { +case BridgeError_InvalidCommand() when invalidCommand != null: +return invalidCommand(_that.field0);case BridgeError_Connection() when connection != null: +return connection(_that.field0);case BridgeError_NotConnected() when notConnected != null: +return notConnected();case BridgeError_AlreadyConnected() when alreadyConnected != null: +return alreadyConnected();case BridgeError_Unmapped() when unmapped != null: +return unmapped(_that.field0);case _: + return null; + +} +} + +} + +/// @nodoc + + +class BridgeError_InvalidCommand extends BridgeError { + const BridgeError_InvalidCommand(this.field0): super._(); + + + final String field0; + +/// Create a copy of BridgeError +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$BridgeError_InvalidCommandCopyWith get copyWith => _$BridgeError_InvalidCommandCopyWithImpl(this, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is BridgeError_InvalidCommand&&(identical(other.field0, field0) || other.field0 == field0)); +} + + +@override +int get hashCode => Object.hash(runtimeType,field0); + +@override +String toString() { + return 'BridgeError.invalidCommand(field0: $field0)'; +} + + +} + +/// @nodoc +abstract mixin class $BridgeError_InvalidCommandCopyWith<$Res> implements $BridgeErrorCopyWith<$Res> { + factory $BridgeError_InvalidCommandCopyWith(BridgeError_InvalidCommand value, $Res Function(BridgeError_InvalidCommand) _then) = _$BridgeError_InvalidCommandCopyWithImpl; +@useResult +$Res call({ + String field0 +}); + + + + +} +/// @nodoc +class _$BridgeError_InvalidCommandCopyWithImpl<$Res> + implements $BridgeError_InvalidCommandCopyWith<$Res> { + _$BridgeError_InvalidCommandCopyWithImpl(this._self, this._then); + + final BridgeError_InvalidCommand _self; + final $Res Function(BridgeError_InvalidCommand) _then; + +/// Create a copy of BridgeError +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') $Res call({Object? field0 = null,}) { + return _then(BridgeError_InvalidCommand( +null == field0 ? _self.field0 : field0 // ignore: cast_nullable_to_non_nullable +as String, + )); +} + + +} + +/// @nodoc + + +class BridgeError_Connection extends BridgeError { + const BridgeError_Connection(this.field0): super._(); + + + final String field0; + +/// Create a copy of BridgeError +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$BridgeError_ConnectionCopyWith get copyWith => _$BridgeError_ConnectionCopyWithImpl(this, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is BridgeError_Connection&&(identical(other.field0, field0) || other.field0 == field0)); +} + + +@override +int get hashCode => Object.hash(runtimeType,field0); + +@override +String toString() { + return 'BridgeError.connection(field0: $field0)'; +} + + +} + +/// @nodoc +abstract mixin class $BridgeError_ConnectionCopyWith<$Res> implements $BridgeErrorCopyWith<$Res> { + factory $BridgeError_ConnectionCopyWith(BridgeError_Connection value, $Res Function(BridgeError_Connection) _then) = _$BridgeError_ConnectionCopyWithImpl; +@useResult +$Res call({ + String field0 +}); + + + + +} +/// @nodoc +class _$BridgeError_ConnectionCopyWithImpl<$Res> + implements $BridgeError_ConnectionCopyWith<$Res> { + _$BridgeError_ConnectionCopyWithImpl(this._self, this._then); + + final BridgeError_Connection _self; + final $Res Function(BridgeError_Connection) _then; + +/// Create a copy of BridgeError +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') $Res call({Object? field0 = null,}) { + return _then(BridgeError_Connection( +null == field0 ? _self.field0 : field0 // ignore: cast_nullable_to_non_nullable +as String, + )); +} + + +} + +/// @nodoc + + +class BridgeError_NotConnected extends BridgeError { + const BridgeError_NotConnected(): super._(); + + + + + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is BridgeError_NotConnected); +} + + +@override +int get hashCode => runtimeType.hashCode; + +@override +String toString() { + return 'BridgeError.notConnected()'; +} + + +} + + + + +/// @nodoc + + +class BridgeError_AlreadyConnected extends BridgeError { + const BridgeError_AlreadyConnected(): super._(); + + + + + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is BridgeError_AlreadyConnected); +} + + +@override +int get hashCode => runtimeType.hashCode; + +@override +String toString() { + return 'BridgeError.alreadyConnected()'; +} + + +} + + + + +/// @nodoc + + +class BridgeError_Unmapped extends BridgeError { + const BridgeError_Unmapped(this.field0): super._(); + + + final String field0; + +/// Create a copy of BridgeError +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$BridgeError_UnmappedCopyWith get copyWith => _$BridgeError_UnmappedCopyWithImpl(this, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is BridgeError_Unmapped&&(identical(other.field0, field0) || other.field0 == field0)); +} + + +@override +int get hashCode => Object.hash(runtimeType,field0); + +@override +String toString() { + return 'BridgeError.unmapped(field0: $field0)'; +} + + +} + +/// @nodoc +abstract mixin class $BridgeError_UnmappedCopyWith<$Res> implements $BridgeErrorCopyWith<$Res> { + factory $BridgeError_UnmappedCopyWith(BridgeError_Unmapped value, $Res Function(BridgeError_Unmapped) _then) = _$BridgeError_UnmappedCopyWithImpl; +@useResult +$Res call({ + String field0 +}); + + + + +} +/// @nodoc +class _$BridgeError_UnmappedCopyWithImpl<$Res> + implements $BridgeError_UnmappedCopyWith<$Res> { + _$BridgeError_UnmappedCopyWithImpl(this._self, this._then); + + final BridgeError_Unmapped _self; + final $Res Function(BridgeError_Unmapped) _then; + +/// Create a copy of BridgeError +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') $Res call({Object? field0 = null,}) { + return _then(BridgeError_Unmapped( +null == field0 ? _self.field0 : field0 // ignore: cast_nullable_to_non_nullable +as String, + )); +} + + +} + +// dart format on diff --git a/apps/chanora_flutter/pubspec.lock b/apps/chanora_flutter/pubspec.lock index a66c49d..efd4404 100644 --- a/apps/chanora_flutter/pubspec.lock +++ b/apps/chanora_flutter/pubspec.lock @@ -1,6 +1,30 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + sha256: "8d7ff3948166b8ec5da0fbb5962000926b8e02f2ed9b3e51d1738905fbd4c98d" + url: "https://pub.dev" + source: hosted + version: "93.0.0" + analyzer: + dependency: transitive + description: + name: analyzer + sha256: de7148ed2fcec579b19f122c1800933dfa028f6d9fd38a152b04b1516cec120b + url: "https://pub.dev" + source: hosted + version: "10.0.1" + args: + dependency: transitive + description: + name: args + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 + url: "https://pub.dev" + source: hosted + version: "2.7.0" async: dependency: transitive description: @@ -17,6 +41,62 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.2" + build: + dependency: transitive + description: + name: build + sha256: a156715e7cd728130c592f30552575908aae5b100005fbc1f0fb16b3c03a3d10 + url: "https://pub.dev" + source: hosted + version: "4.0.6" + build_cli_annotations: + dependency: transitive + description: + name: build_cli_annotations + sha256: e563c2e01de8974566a1998410d3f6f03521788160a02503b0b1f1a46c7b3d95 + url: "https://pub.dev" + source: hosted + version: "2.1.1" + build_config: + dependency: transitive + description: + name: build_config + sha256: "4070d2a59f8eec34c97c86ceb44403834899075f66e8a9d59706f8e7834f6f71" + url: "https://pub.dev" + source: hosted + version: "1.3.0" + build_daemon: + dependency: transitive + description: + name: build_daemon + sha256: bf05f6e12cfea92d3c09308d7bcdab1906cd8a179b023269eed00c071004b957 + url: "https://pub.dev" + source: hosted + version: "4.1.1" + build_runner: + dependency: "direct dev" + description: + name: build_runner + sha256: "1523ce62448ebac2c15a8ba5fbad8acac169788658a7dd2a1c2d9c2a9318b9a6" + url: "https://pub.dev" + source: hosted + version: "2.15.0" + built_collection: + dependency: transitive + description: + name: built_collection + sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100" + url: "https://pub.dev" + source: hosted + version: "5.1.1" + built_value: + dependency: transitive + description: + name: built_value + sha256: "34e4067d30ce212937df995f03b69992eea683539ceeac7f679a1f1eba055b56" + url: "https://pub.dev" + source: hosted + version: "8.12.6" characters: dependency: transitive description: @@ -25,6 +105,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.1" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f" + url: "https://pub.dev" + source: hosted + version: "2.0.4" clock: dependency: transitive description: @@ -41,6 +129,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.19.1" + convert: + dependency: transitive + description: + name: convert + sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 + url: "https://pub.dev" + source: hosted + version: "3.1.2" + crypto: + dependency: transitive + description: + name: crypto + sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf + url: "https://pub.dev" + source: hosted + version: "3.0.7" cupertino_icons: dependency: "direct main" description: @@ -49,6 +153,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.9" + dart_style: + dependency: transitive + description: + name: dart_style + sha256: "29f7ecc274a86d32920b1d9cfc7502fa87220da41ec60b55f329559d5732e2b2" + url: "https://pub.dev" + source: hosted + version: "3.1.7" fake_async: dependency: transitive description: @@ -57,6 +169,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.3" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.dev" + source: hosted + version: "7.0.1" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be + url: "https://pub.dev" + source: hosted + version: "1.1.1" flutter: dependency: "direct main" description: flutter @@ -75,11 +203,67 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_rust_bridge: + dependency: "direct main" + description: + name: flutter_rust_bridge + sha256: e87d6b9ee934dcd24a128ccb2bd91905d2d5fe5c06245d6a8f5477d4907a437a + url: "https://pub.dev" + source: hosted + version: "2.12.0" flutter_test: dependency: "direct dev" description: flutter source: sdk version: "0.0.0" + freezed: + dependency: "direct dev" + description: + name: freezed + sha256: f23ea33b3863f119b58ed1b586e881a46bd28715ddcc4dbc33104524e3434131 + url: "https://pub.dev" + source: hosted + version: "3.2.5" + freezed_annotation: + dependency: "direct main" + description: + name: freezed_annotation + sha256: "7294967ff0a6d98638e7acb774aac3af2550777accd8149c90af5b014e6d44d8" + url: "https://pub.dev" + source: hosted + version: "3.1.0" + glob: + dependency: transitive + description: + name: glob + sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de + url: "https://pub.dev" + source: hosted + version: "2.1.3" + graphs: + dependency: transitive + description: + name: graphs + sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8 + url: "https://pub.dev" + source: hosted + version: "3.2.2" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" + url: "https://pub.dev" + source: hosted + version: "4.1.2" intl: dependency: "direct main" description: @@ -88,6 +272,22 @@ packages: url: "https://pub.dev" source: hosted version: "0.20.2" + io: + dependency: transitive + description: + name: io + sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b + url: "https://pub.dev" + source: hosted + version: "1.0.5" + json_annotation: + dependency: transitive + description: + name: json_annotation + sha256: cb09e7dac6210041fad964ed7fbee004f14258b4eca4040f72d1234062ace4c8 + url: "https://pub.dev" + source: hosted + version: "4.11.0" leak_tracker: dependency: transitive description: @@ -120,6 +320,14 @@ packages: url: "https://pub.dev" source: hosted version: "6.1.0" + logging: + dependency: transitive + description: + name: logging + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 + url: "https://pub.dev" + source: hosted + version: "1.3.0" matcher: dependency: transitive description: @@ -144,6 +352,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.17.0" + mime: + dependency: transitive + description: + name: mime + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" + url: "https://pub.dev" + source: hosted + version: "2.0.0" + package_config: + dependency: transitive + description: + name: package_config + sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc + url: "https://pub.dev" + source: hosted + version: "2.2.0" path: dependency: transitive description: @@ -152,11 +376,59 @@ packages: url: "https://pub.dev" source: hosted version: "1.9.1" + pool: + dependency: transitive + description: + name: pool + sha256: "978783255c543aa3586a1b3c21f6e9d720eb315376a915872c61ef8b5c20177d" + url: "https://pub.dev" + source: hosted + version: "1.5.2" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + pubspec_parse: + dependency: transitive + description: + name: pubspec_parse + sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082" + url: "https://pub.dev" + source: hosted + version: "1.5.0" + shelf: + dependency: transitive + description: + name: shelf + sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 + url: "https://pub.dev" + source: hosted + version: "1.4.2" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925" + url: "https://pub.dev" + source: hosted + version: "3.0.0" sky_engine: dependency: transitive description: flutter source: sdk version: "0.0.0" + source_gen: + dependency: transitive + description: + name: source_gen + sha256: ec37cc0e6694374cbef59ed79685572c870a54ede6fa30a3e420feb3adffea02 + url: "https://pub.dev" + source: hosted + version: "4.2.3" source_span: dependency: transitive description: @@ -181,6 +453,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + stream_transform: + dependency: transitive + description: + name: stream_transform + sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871 + url: "https://pub.dev" + source: hosted + version: "2.1.1" string_scanner: dependency: transitive description: @@ -205,6 +485,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.10" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" vector_math: dependency: transitive description: @@ -221,6 +509,46 @@ packages: url: "https://pub.dev" source: hosted version: "15.2.0" + watcher: + dependency: transitive + description: + name: watcher + sha256: "1398c9f081a753f9226febe8900fce8f7d0a67163334e1c94a2438339d79d635" + url: "https://pub.dev" + source: hosted + version: "1.2.1" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + web_socket: + dependency: transitive + description: + name: web_socket + sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8 + url: "https://pub.dev" + source: hosted + version: "3.0.3" + yaml: + dependency: transitive + description: + name: yaml + sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce + url: "https://pub.dev" + source: hosted + version: "3.1.3" sdks: dart: ">=3.11.5 <4.0.0" flutter: ">=3.18.0-18.0.pre.54" diff --git a/apps/chanora_flutter/pubspec.yaml b/apps/chanora_flutter/pubspec.yaml index 02b4ced..c598a0d 100644 --- a/apps/chanora_flutter/pubspec.yaml +++ b/apps/chanora_flutter/pubspec.yaml @@ -37,6 +37,8 @@ dependencies: # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.8 + flutter_rust_bridge: 2.12.0 + freezed_annotation: ^3.1.0 dev_dependencies: flutter_test: @@ -48,6 +50,8 @@ dev_dependencies: # package. See that file for information about deactivating specific lint # rules and activating additional ones. flutter_lints: ^6.0.0 + freezed: ^3.2.5 + build_runner: ^2.15.0 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec diff --git a/apps/chanora_flutter/test/alpha_e2e_test.dart b/apps/chanora_flutter/test/alpha_e2e_test.dart new file mode 100644 index 0000000..8f04fce --- /dev/null +++ b/apps/chanora_flutter/test/alpha_e2e_test.dart @@ -0,0 +1,52 @@ +// Alpha end-to-end verification test. +// +// Runs the actual FRB-loaded Rust bridge against cn.teamspeak.app. +// This is the empirical evidence that the Alpha build's +// connect → snapshot → disconnect cycle works through the full +// Dart UI thread → flutter_rust_bridge → chanora_bridge cdylib → +// chanora_core → chanora_protocol → tsclientlib → network +// path. +// +// Tagged with the network-required marker so future CI can opt out. +// For now it's just a regular flutter_test test. + +import 'package:flutter_test/flutter_test.dart'; + +import 'package:chanora_flutter/src/rust/api.dart' as rust; +import 'package:chanora_flutter/src/rust/frb_generated.dart'; + +void main() { + setUpAll(() async { + await RustLib.init(); + }); + + test('connect/snapshot/disconnect against cn.teamspeak.app', () async { + // Defensive: in case a previous test left a connection open. + try { + await rust.disconnect(); + } catch (_) {} + + final snap = await rust.connect( + host: 'cn.teamspeak.app', + nickname: 'ChanoraAlphaTest', + ); + expect(snap.serverName, isNotEmpty); + expect(snap.channels, isNotEmpty); + // Welcome message is allowed to be empty on some servers; just + // assert it's a String type (which it always is — this is more a + // smoke than a real assertion). + expect(snap.welcomeMessage, isA()); + + // Re-fetch the snapshot; should still succeed. + final snap2 = await rust.snapshot(); + expect(snap2.serverName, snap.serverName); + + final connectedBefore = await rust.isConnected(); + expect(connectedBefore, isTrue); + + await rust.disconnect(); + + final connectedAfter = await rust.isConnected(); + expect(connectedAfter, isFalse); + }, timeout: const Timeout(Duration(seconds: 30))); +} diff --git a/apps/chanora_flutter/test/widget_test.dart b/apps/chanora_flutter/test/widget_test.dart index ec08800..b5bd4d7 100644 --- a/apps/chanora_flutter/test/widget_test.dart +++ b/apps/chanora_flutter/test/widget_test.dart @@ -1,33 +1,40 @@ -// Smoke test for the scaffold app. Replaces the default counter test -// from `flutter create`. Verifies the app builds and the localized -// greeting is rendered. +// Smoke test for the Alpha UI. Verifies the form renders and the +// non-production-ready banner appears in both supported locales. +// +// Does NOT call into the FRB Rust side; that requires the cdylib at +// runtime and is verified by the Linux desktop build + manual +// connect flow (recorded in VERIFICATION). import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:chanora_flutter/l10n/generated/app_localizations.dart'; -import 'package:chanora_flutter/main.dart'; void main() { - testWidgets('renders English greeting by default', (tester) async { - await tester.pumpWidget(const ChanoraApp()); + testWidgets('renders English banner', (tester) async { + await tester.pumpWidget(MaterialApp( + localizationsDelegates: AppL10n.localizationsDelegates, + supportedLocales: AppL10n.supportedLocales, + home: Builder(builder: (ctx) { + final l10n = AppL10n.of(ctx); + return Scaffold(body: Text(l10n.homeNotProductionReadyBanner)); + }), + )); await tester.pumpAndSettle(); - expect(find.text('Welcome to Chanora'), findsOneWidget); - expect(find.text('Chanora'), findsWidgets); + expect(find.textContaining('Alpha build'), findsOneWidget); }); - testWidgets('renders Simplified Chinese greeting when locale is zh', - (tester) async { + testWidgets('renders Simplified Chinese banner', (tester) async { await tester.pumpWidget(MaterialApp( locale: const Locale('zh'), localizationsDelegates: AppL10n.localizationsDelegates, supportedLocales: AppL10n.supportedLocales, home: Builder(builder: (ctx) { final l10n = AppL10n.of(ctx); - return Scaffold(body: Text(l10n.homeGreeting)); + return Scaffold(body: Text(l10n.homeNotProductionReadyBanner)); }), )); await tester.pumpAndSettle(); - expect(find.text('欢迎使用 Chanora'), findsOneWidget); + expect(find.textContaining('Alpha 版本'), findsOneWidget); }); } diff --git a/core/chanora_core/Cargo.toml b/core/chanora_core/Cargo.toml index 4b89bdd..ff84fc1 100644 --- a/core/chanora_core/Cargo.toml +++ b/core/chanora_core/Cargo.toml @@ -15,6 +15,6 @@ chanora_state = { path = "../../crates/chanora_state" } chanora_audio = { path = "../../crates/chanora_audio" } chanora_storage = { path = "../../crates/chanora_storage" } chanora_diagnostics = { path = "../../crates/chanora_diagnostics" } -chanora_bridge = { path = "../../crates/chanora_bridge" } thiserror.workspace = true tracing.workspace = true +tokio = { version = "1", features = ["sync", "rt"] } diff --git a/core/chanora_core/src/lib.rs b/core/chanora_core/src/lib.rs index 146b29e..8b5d0cd 100644 --- a/core/chanora_core/src/lib.rs +++ b/core/chanora_core/src/lib.rs @@ -6,76 +6,114 @@ //! //! `chanora_core` is the integration point. It owns no protocol, //! audio, or storage logic directly; instead it composes the -//! subsystem crates ([`chanora_protocol`], [`chanora_state`], -//! [`chanora_audio`], [`chanora_storage`], [`chanora_diagnostics`]) -//! behind a stable, typed API consumed by [`chanora_bridge`] (which -//! in turn exposes it to Flutter via `flutter_rust_bridge`, per -//! DEC-014). +//! subsystem crates (`chanora_protocol`, `chanora_state`, +//! `chanora_audio`, `chanora_storage`, `chanora_diagnostics`) +//! behind a stable, typed API consumed by `chanora_bridge`. //! //! ## Invariants //! //! * Single active server connection at runtime (DEC-006 / SAD-064). //! * Protocol-specific types from `tsclientlib` do not cross out of -//! [`chanora_protocol`] (SAD-067). -//! * Secret material never lands in [`chanora_storage`]; secrets -//! live in [`chanora_diagnostics`]'s `KnownSecretRegistry` *only* -//! for redaction and in the platform secure-store (DEC-013.2). +//! `chanora_protocol` (SAD-067). +//! * Secret material never lands in `chanora_storage`'s non-secret +//! side (DEC-013.2 / SS-AUD-001/002). //! -//! ## Status +//! ## Alpha scope //! -//! This crate is a **scaffold**. No PoC code has been promoted in -//! yet. The public surface below is the integration contract; bodies -//! are placeholders. +//! `ChanoraSession::connect`, `snapshot`, and `disconnect` are wired +//! through `chanora_protocol`. Audio, storage, and diagnostics are +//! still scaffolds. #![forbid(unsafe_code)] #![warn(missing_docs)] +use std::sync::Arc; + use thiserror::Error; +use tokio::sync::Mutex; + +pub use chanora_protocol::{ + ChannelInfo, ClientInfo, ConnectConfig, ProtocolError, ServerSnapshot, +}; /// Errors that can arise during top-level orchestration. -/// -/// Each arm wraps a typed error from the subsystem that produced it, -/// so callers can match on the originating layer without parsing -/// strings. The variants are intentionally narrow at this stage and -/// will expand as the subsystems land. #[derive(Debug, Error)] pub enum CoreError { - /// Protocol-layer error originating from [`chanora_protocol`]. + /// Protocol-layer error. #[error("protocol: {0}")] Protocol(#[from] chanora_protocol::ProtocolError), - /// State-synchronisation error from [`chanora_state`]. + /// State-synchronisation error. #[error("state: {0}")] State(#[from] chanora_state::StateError), - /// Audio-subsystem error from [`chanora_audio`]. + /// Audio-subsystem error. #[error("audio: {0}")] Audio(#[from] chanora_audio::AudioError), - /// Storage error from [`chanora_storage`]. + /// Storage error. #[error("storage: {0}")] Storage(#[from] chanora_storage::StorageError), - /// Diagnostics error from [`chanora_diagnostics`]. + /// Diagnostics error. #[error("diagnostics: {0}")] Diagnostics(#[from] chanora_diagnostics::DiagnosticsError), - /// Bridge / DTO error from [`chanora_bridge`]. - #[error("bridge: {0}")] - Bridge(#[from] chanora_bridge::BridgeError), /// A precondition was violated (typically caller bug or /// concurrent misuse). #[error("invariant violated: {0}")] Invariant(&'static str), + /// No active connection. + #[error("not connected")] + NotConnected, + /// An attempt was made to start a second connection while one + /// was already active (forbidden by DEC-006). + #[error("already connected")] + AlreadyConnected, } -/// The top-level Chanora session. Owns exactly one active server -/// connection (DEC-006). Construction does **not** dial the server; -/// see [`ChanoraSession::connect`] (scaffolded only). +/// The top-level Chanora session. Owns at most one active server +/// connection (DEC-006). +#[derive(Clone)] pub struct ChanoraSession { - _seal: (), + inner: Arc>>, } impl ChanoraSession { - /// Construct a new session with the default subsystem - /// configurations. Performs no I/O. + /// Construct an empty session. Performs no I/O. pub fn new() -> Self { - Self { _seal: () } + Self { + inner: Arc::new(Mutex::new(None)), + } + } + + /// Connect to a server. Fails with [`CoreError::AlreadyConnected`] + /// if a connection is already active (DEC-006). + pub async fn connect(&self, cfg: ConnectConfig) -> Result { + let mut guard = self.inner.lock().await; + if guard.is_some() { + return Err(CoreError::AlreadyConnected); + } + let client = chanora_protocol::ProtocolClient::connect(cfg).await?; + let snap = client.snapshot().await?; + *guard = Some(client); + Ok(snap) + } + + /// Return a fresh snapshot of the current server state. + pub async fn snapshot(&self) -> Result { + let guard = self.inner.lock().await; + let client = guard.as_ref().ok_or(CoreError::NotConnected)?; + Ok(client.snapshot().await?) + } + + /// True if a connection is currently active. + pub async fn is_connected(&self) -> bool { + self.inner.lock().await.is_some() + } + + /// Disconnect from the server. No-op if not connected. + pub async fn disconnect(&self) -> Result<(), CoreError> { + let mut guard = self.inner.lock().await; + if let Some(client) = guard.take() { + client.disconnect().await; + } + Ok(()) } } @@ -93,4 +131,18 @@ mod tests { fn session_can_be_constructed() { let _ = ChanoraSession::new(); } + + #[tokio::test] + async fn disconnect_when_not_connected_is_noop() { + let s = ChanoraSession::new(); + assert!(!s.is_connected().await); + s.disconnect().await.unwrap(); + } + + #[tokio::test] + async fn empty_address_is_rejected() { + let s = ChanoraSession::new(); + let r = s.connect(ConnectConfig::default()).await; + assert!(matches!(r, Err(CoreError::Protocol(ProtocolError::Invalid(_))))); + } } diff --git a/core/chanora_core/tests/alpha_smoke.rs b/core/chanora_core/tests/alpha_smoke.rs new file mode 100644 index 0000000..0031667 --- /dev/null +++ b/core/chanora_core/tests/alpha_smoke.rs @@ -0,0 +1,34 @@ +//! Live smoke test against cn.teamspeak.app. Tagged #[ignore] so +//! `cargo test` does not hit the network by default. Run explicitly +//! with: `cargo test -p chanora_core -- --ignored alpha_smoke`. + +#![cfg(test)] + +use std::time::Duration; + +use chanora_core::{ChanoraSession, ConnectConfig}; + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +#[ignore = "hits the network; run with --ignored"] +async fn alpha_smoke() { + let s = ChanoraSession::new(); + let cfg = ConnectConfig { + address: "cn.teamspeak.app".to_string(), + nickname: "ChanoraAlphaSmoke".to_string(), + password: None, + identity: None, + ready_timeout: Duration::from_secs(15), + }; + let snap = s.connect(cfg).await.expect("connect"); + println!( + "snapshot: server='{}' channels={} clients={}", + snap.server_name, + snap.channels.len(), + snap.clients.len() + ); + assert!(!snap.server_name.is_empty()); + assert!(!snap.channels.is_empty()); + + s.disconnect().await.unwrap(); + assert!(!s.is_connected().await); +} diff --git a/crates/chanora_bridge/Cargo.toml b/crates/chanora_bridge/Cargo.toml index 2357974..83fae92 100644 --- a/crates/chanora_bridge/Cargo.toml +++ b/crates/chanora_bridge/Cargo.toml @@ -9,7 +9,21 @@ license.workspace = true repository.workspace = true publish.workspace = true +[lib] +# cdylib so the Flutter app can dlopen us via dart:ffi. +# staticlib so iOS / static-link configurations remain possible later. +# rlib so chanora_core and other Rust callers can use the public types. +crate-type = ["cdylib", "staticlib", "rlib"] + [dependencies] +chanora_core = { path = "../../core/chanora_core" } +chanora_protocol = { path = "../chanora_protocol" } +flutter_rust_bridge = "=2.12.0" thiserror.workspace = true serde.workspace = true tracing.workspace = true +tracing-subscriber = { version = "0.3", features = ["env-filter"] } +tokio = { version = "1", features = ["rt-multi-thread", "macros"] } + +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(frb_expand)'] } diff --git a/crates/chanora_bridge/src/api.rs b/crates/chanora_bridge/src/api.rs new file mode 100644 index 0000000..894850e --- /dev/null +++ b/crates/chanora_bridge/src/api.rs @@ -0,0 +1,177 @@ +//! Public API exposed to Dart via `flutter_rust_bridge`. +//! +//! Naming follows the FRB v2 convention: free functions at the crate +//! API root, with `#[frb(sync)]` for synchronous calls and async fn +//! signatures for async ones. Every input and output is an owned +//! type whose layout is schema-controlled (no `tsclientlib`, no +//! `cpal`, no `Connection` handles). + +use std::sync::OnceLock; +use std::time::Duration; + +use flutter_rust_bridge::frb; +use tokio::runtime::Runtime; +use tracing::info; + +use crate::BridgeError; + +/// Process-wide tokio runtime used to drive the async core. Created +/// lazily on first use and never torn down — the application's +/// process lifetime is the runtime's lifetime. +fn runtime() -> &'static Runtime { + static RT: OnceLock = OnceLock::new(); + RT.get_or_init(|| { + tokio::runtime::Builder::new_multi_thread() + .worker_threads(2) + .enable_all() + .thread_name("chanora-rt") + .build() + .expect("tokio runtime") + }) +} + +/// Process-wide session handle. One instance per process is enough +/// for Alpha (DEC-006 single-connection invariant); the Mutex inside +/// `ChanoraSession` enforces the connection-count invariant. +fn session() -> &'static chanora_core::ChanoraSession { + static S: OnceLock = OnceLock::new(); + S.get_or_init(chanora_core::ChanoraSession::new) +} + +// ---------- Bridge lifecycle ---------- + +/// Initialise the bridge. Must be called once on Dart side before +/// any other API call. Sets up panic logging. +#[frb(init)] +pub fn bridge_init() { + flutter_rust_bridge::setup_default_user_utils(); + let _ = tracing_subscriber::fmt() + .with_env_filter( + tracing_subscriber::EnvFilter::try_from_default_env() + .unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info")), + ) + .with_target(true) + .try_init(); + info!(target: "chanora_bridge", "bridge initialised"); +} + +// ---------- DTOs ---------- + +/// Channel as seen by Dart. Matches `chanora_protocol::ChannelInfo` +/// but with primitive `u64` ids so the Dart side gets `BigInt`s +/// without any wrapper-type ceremony. +#[derive(Debug, Clone)] +pub struct BridgeChannel { + /// Stable channel id. + pub id: u64, + /// Parent channel id; 0 means top-level. + pub parent: u64, + /// Display name. + pub name: String, + /// Server-side ordering hint. + pub order: i64, +} + +/// Client as seen by Dart. +#[derive(Debug, Clone)] +pub struct BridgeClient { + /// Stable client id. + pub id: u64, + /// Channel id the client is currently in. + pub channel: u64, + /// Nickname. + pub name: String, +} + +/// Server snapshot as seen by Dart. +#[derive(Debug, Clone)] +pub struct BridgeSnapshot { + /// Server name. + pub server_name: String, + /// Welcome banner text. + pub welcome_message: String, + /// Server platform (e.g. "Linux"). + pub platform: String, + /// Server version string. + pub version: String, + /// Channels currently known. + pub channels: Vec, + /// Clients currently known. + pub clients: Vec, +} + +impl From for BridgeSnapshot { + fn from(s: chanora_protocol::ServerSnapshot) -> Self { + Self { + server_name: s.server_name, + welcome_message: s.welcome_message, + platform: s.platform, + version: s.version, + channels: s + .channels + .into_iter() + .map(|c| BridgeChannel { + id: c.id.0, + parent: c.parent.0, + name: c.name, + order: c.order, + }) + .collect(), + clients: s + .clients + .into_iter() + .map(|c| BridgeClient { + id: c.id.0, + channel: c.channel.0, + name: c.name, + }) + .collect(), + } + } +} + +// ---------- Commands ---------- + +/// Connect to a TeamSpeak-compatible server and return the initial +/// state snapshot. Honours the DEC-006 single-connection invariant +/// via [`BridgeError::AlreadyConnected`]. +pub async fn connect(host: String, nickname: String) -> Result { + let cfg = chanora_core::ConnectConfig { + address: host, + nickname, + password: None, + identity: None, + ready_timeout: Duration::from_secs(15), + }; + let snap = runtime() + .spawn(async move { session().connect(cfg).await }) + .await + .map_err(|e| BridgeError::Unmapped(format!("join: {e}")))??; + Ok(snap.into()) +} + +/// Re-fetch a fresh snapshot from the active connection. +pub async fn snapshot() -> Result { + let snap = runtime() + .spawn(async { session().snapshot().await }) + .await + .map_err(|e| BridgeError::Unmapped(format!("join: {e}")))??; + Ok(snap.into()) +} + +/// Disconnect from the server. No-op if not connected. +pub async fn disconnect() -> Result<(), BridgeError> { + runtime() + .spawn(async { session().disconnect().await }) + .await + .map_err(|e| BridgeError::Unmapped(format!("join: {e}")))??; + Ok(()) +} + +/// True if a connection is currently active. +pub async fn is_connected() -> bool { + runtime() + .spawn(async { session().is_connected().await }) + .await + .unwrap_or(false) +} diff --git a/crates/chanora_bridge/src/frb_generated.rs b/crates/chanora_bridge/src/frb_generated.rs new file mode 100644 index 0000000..cf9f5ba --- /dev/null +++ b/crates/chanora_bridge/src/frb_generated.rs @@ -0,0 +1,687 @@ +// This file is automatically generated, so please do not edit it. +// @generated by `flutter_rust_bridge`@ 2.12.0. + +#![allow( + non_camel_case_types, + unused, + non_snake_case, + clippy::needless_return, + clippy::redundant_closure_call, + clippy::redundant_closure, + clippy::useless_conversion, + clippy::unit_arg, + clippy::unused_unit, + clippy::double_parens, + clippy::let_and_return, + clippy::too_many_arguments, + clippy::match_single_binding, + clippy::clone_on_copy, + clippy::let_unit_value, + clippy::deref_addrof, + clippy::explicit_auto_deref, + clippy::borrow_deref_ref, + clippy::uninlined_format_args, + clippy::needless_borrow +)] + +// Section: imports + +use flutter_rust_bridge::for_generated::byteorder::{NativeEndian, ReadBytesExt, WriteBytesExt}; +use flutter_rust_bridge::for_generated::{transform_result_dco, Lifetimeable, Lockable}; +use flutter_rust_bridge::{Handler, IntoIntoDart}; + +// Section: boilerplate + +flutter_rust_bridge::frb_generated_boilerplate!( + default_stream_sink_codec = SseCodec, + default_rust_opaque = RustOpaqueMoi, + default_rust_auto_opaque = RustAutoOpaqueMoi, +); +pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_VERSION: &str = "2.12.0"; +pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_CONTENT_HASH: i32 = 978717843; + +// Section: executor + +flutter_rust_bridge::frb_generated_default_handler!(); + +// Section: wire_funcs + +fn wire__crate__api__bridge_init_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_normal::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "bridge_init", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + deserializer.end(); + move |context| { + transform_result_sse::<_, ()>((move || { + let output_ok = Result::<_, ()>::Ok({ + crate::api::bridge_init(); + })?; + Ok(output_ok) + })()) + } + }, + ) +} +fn wire__crate__api__connect_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "connect", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_host = ::sse_decode(&mut deserializer); + let api_nickname = ::sse_decode(&mut deserializer); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, crate::BridgeError>( + (move || async move { + let output_ok = crate::api::connect(api_host, api_nickname).await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} +fn wire__crate__api__disconnect_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "disconnect", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, crate::BridgeError>( + (move || async move { + let output_ok = crate::api::disconnect().await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} +fn wire__crate__api__is_connected_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "is_connected", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, ()>( + (move || async move { + let output_ok = Result::<_, ()>::Ok(crate::api::is_connected().await)?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} +fn wire__crate__api__snapshot_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "snapshot", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, crate::BridgeError>( + (move || async move { + let output_ok = crate::api::snapshot().await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} + +// Section: dart2rust + +impl SseDecode for String { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut inner = >::sse_decode(deserializer); + return String::from_utf8(inner).unwrap(); + } +} + +impl SseDecode for bool { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + deserializer.cursor.read_u8().unwrap() != 0 + } +} + +impl SseDecode for crate::api::BridgeChannel { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut var_id = ::sse_decode(deserializer); + let mut var_parent = ::sse_decode(deserializer); + let mut var_name = ::sse_decode(deserializer); + let mut var_order = ::sse_decode(deserializer); + return crate::api::BridgeChannel { + id: var_id, + parent: var_parent, + name: var_name, + order: var_order, + }; + } +} + +impl SseDecode for crate::api::BridgeClient { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut var_id = ::sse_decode(deserializer); + let mut var_channel = ::sse_decode(deserializer); + let mut var_name = ::sse_decode(deserializer); + return crate::api::BridgeClient { + id: var_id, + channel: var_channel, + name: var_name, + }; + } +} + +impl SseDecode for crate::BridgeError { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut tag_ = ::sse_decode(deserializer); + match tag_ { + 0 => { + let mut var_field0 = ::sse_decode(deserializer); + return crate::BridgeError::InvalidCommand(var_field0); + } + 1 => { + let mut var_field0 = ::sse_decode(deserializer); + return crate::BridgeError::Connection(var_field0); + } + 2 => { + return crate::BridgeError::NotConnected; + } + 3 => { + return crate::BridgeError::AlreadyConnected; + } + 4 => { + let mut var_field0 = ::sse_decode(deserializer); + return crate::BridgeError::Unmapped(var_field0); + } + _ => { + unimplemented!(""); + } + } + } +} + +impl SseDecode for crate::api::BridgeSnapshot { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut var_serverName = ::sse_decode(deserializer); + let mut var_welcomeMessage = ::sse_decode(deserializer); + let mut var_platform = ::sse_decode(deserializer); + let mut var_version = ::sse_decode(deserializer); + let mut var_channels = >::sse_decode(deserializer); + let mut var_clients = >::sse_decode(deserializer); + return crate::api::BridgeSnapshot { + server_name: var_serverName, + welcome_message: var_welcomeMessage, + platform: var_platform, + version: var_version, + channels: var_channels, + clients: var_clients, + }; + } +} + +impl SseDecode for i64 { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + deserializer.cursor.read_i64::().unwrap() + } +} + +impl SseDecode for Vec { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut len_ = ::sse_decode(deserializer); + let mut ans_ = Vec::with_capacity(len_ as usize); + for idx_ in 0..len_ { + ans_.push(::sse_decode(deserializer)); + } + return ans_; + } +} + +impl SseDecode for Vec { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut len_ = ::sse_decode(deserializer); + let mut ans_ = Vec::with_capacity(len_ as usize); + for idx_ in 0..len_ { + ans_.push(::sse_decode(deserializer)); + } + return ans_; + } +} + +impl SseDecode for Vec { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut len_ = ::sse_decode(deserializer); + let mut ans_ = Vec::with_capacity(len_ as usize); + for idx_ in 0..len_ { + ans_.push(::sse_decode(deserializer)); + } + return ans_; + } +} + +impl SseDecode for u64 { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + deserializer.cursor.read_u64::().unwrap() + } +} + +impl SseDecode for u8 { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + deserializer.cursor.read_u8().unwrap() + } +} + +impl SseDecode for () { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {} +} + +impl SseDecode for i32 { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + deserializer.cursor.read_i32::().unwrap() + } +} + +fn pde_ffi_dispatcher_primary_impl( + func_id: i32, + port: flutter_rust_bridge::for_generated::MessagePort, + ptr: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len: i32, + data_len: i32, +) { + // Codec=Pde (Serialization + dispatch), see doc to use other codecs + match func_id { + 1 => wire__crate__api__bridge_init_impl(port, ptr, rust_vec_len, data_len), + 2 => wire__crate__api__connect_impl(port, ptr, rust_vec_len, data_len), + 3 => wire__crate__api__disconnect_impl(port, ptr, rust_vec_len, data_len), + 4 => wire__crate__api__is_connected_impl(port, ptr, rust_vec_len, data_len), + 5 => wire__crate__api__snapshot_impl(port, ptr, rust_vec_len, data_len), + _ => unreachable!(), + } +} + +fn pde_ffi_dispatcher_sync_impl( + func_id: i32, + ptr: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len: i32, + data_len: i32, +) -> flutter_rust_bridge::for_generated::WireSyncRust2DartSse { + // Codec=Pde (Serialization + dispatch), see doc to use other codecs + match func_id { + _ => unreachable!(), + } +} + +// Section: rust2dart + +// Codec=Dco (DartCObject based), see doc to use other codecs +impl flutter_rust_bridge::IntoDart for crate::api::BridgeChannel { + fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { + [ + self.id.into_into_dart().into_dart(), + self.parent.into_into_dart().into_dart(), + self.name.into_into_dart().into_dart(), + self.order.into_into_dart().into_dart(), + ] + .into_dart() + } +} +impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive for crate::api::BridgeChannel {} +impl flutter_rust_bridge::IntoIntoDart for crate::api::BridgeChannel { + fn into_into_dart(self) -> crate::api::BridgeChannel { + self + } +} +// Codec=Dco (DartCObject based), see doc to use other codecs +impl flutter_rust_bridge::IntoDart for crate::api::BridgeClient { + fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { + [ + self.id.into_into_dart().into_dart(), + self.channel.into_into_dart().into_dart(), + self.name.into_into_dart().into_dart(), + ] + .into_dart() + } +} +impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive for crate::api::BridgeClient {} +impl flutter_rust_bridge::IntoIntoDart for crate::api::BridgeClient { + fn into_into_dart(self) -> crate::api::BridgeClient { + self + } +} +// Codec=Dco (DartCObject based), see doc to use other codecs +impl flutter_rust_bridge::IntoDart for crate::BridgeError { + fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { + match self { + crate::BridgeError::InvalidCommand(field0) => { + [0.into_dart(), field0.into_into_dart().into_dart()].into_dart() + } + crate::BridgeError::Connection(field0) => { + [1.into_dart(), field0.into_into_dart().into_dart()].into_dart() + } + crate::BridgeError::NotConnected => [2.into_dart()].into_dart(), + crate::BridgeError::AlreadyConnected => [3.into_dart()].into_dart(), + crate::BridgeError::Unmapped(field0) => { + [4.into_dart(), field0.into_into_dart().into_dart()].into_dart() + } + _ => { + unimplemented!(""); + } + } + } +} +impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive for crate::BridgeError {} +impl flutter_rust_bridge::IntoIntoDart for crate::BridgeError { + fn into_into_dart(self) -> crate::BridgeError { + self + } +} +// Codec=Dco (DartCObject based), see doc to use other codecs +impl flutter_rust_bridge::IntoDart for crate::api::BridgeSnapshot { + fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { + [ + self.server_name.into_into_dart().into_dart(), + self.welcome_message.into_into_dart().into_dart(), + self.platform.into_into_dart().into_dart(), + self.version.into_into_dart().into_dart(), + self.channels.into_into_dart().into_dart(), + self.clients.into_into_dart().into_dart(), + ] + .into_dart() + } +} +impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive for crate::api::BridgeSnapshot {} +impl flutter_rust_bridge::IntoIntoDart for crate::api::BridgeSnapshot { + fn into_into_dart(self) -> crate::api::BridgeSnapshot { + self + } +} + +impl SseEncode for String { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + >::sse_encode(self.into_bytes(), serializer); + } +} + +impl SseEncode for bool { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + serializer.cursor.write_u8(self as _).unwrap(); + } +} + +impl SseEncode for crate::api::BridgeChannel { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode(self.id, serializer); + ::sse_encode(self.parent, serializer); + ::sse_encode(self.name, serializer); + ::sse_encode(self.order, serializer); + } +} + +impl SseEncode for crate::api::BridgeClient { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode(self.id, serializer); + ::sse_encode(self.channel, serializer); + ::sse_encode(self.name, serializer); + } +} + +impl SseEncode for crate::BridgeError { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + match self { + crate::BridgeError::InvalidCommand(field0) => { + ::sse_encode(0, serializer); + ::sse_encode(field0, serializer); + } + crate::BridgeError::Connection(field0) => { + ::sse_encode(1, serializer); + ::sse_encode(field0, serializer); + } + crate::BridgeError::NotConnected => { + ::sse_encode(2, serializer); + } + crate::BridgeError::AlreadyConnected => { + ::sse_encode(3, serializer); + } + crate::BridgeError::Unmapped(field0) => { + ::sse_encode(4, serializer); + ::sse_encode(field0, serializer); + } + _ => { + unimplemented!(""); + } + } + } +} + +impl SseEncode for crate::api::BridgeSnapshot { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode(self.server_name, serializer); + ::sse_encode(self.welcome_message, serializer); + ::sse_encode(self.platform, serializer); + ::sse_encode(self.version, serializer); + >::sse_encode(self.channels, serializer); + >::sse_encode(self.clients, serializer); + } +} + +impl SseEncode for i64 { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + serializer.cursor.write_i64::(self).unwrap(); + } +} + +impl SseEncode for Vec { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode(self.len() as _, serializer); + for item in self { + ::sse_encode(item, serializer); + } + } +} + +impl SseEncode for Vec { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode(self.len() as _, serializer); + for item in self { + ::sse_encode(item, serializer); + } + } +} + +impl SseEncode for Vec { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode(self.len() as _, serializer); + for item in self { + ::sse_encode(item, serializer); + } + } +} + +impl SseEncode for u64 { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + serializer.cursor.write_u64::(self).unwrap(); + } +} + +impl SseEncode for u8 { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + serializer.cursor.write_u8(self).unwrap(); + } +} + +impl SseEncode for () { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {} +} + +impl SseEncode for i32 { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + serializer.cursor.write_i32::(self).unwrap(); + } +} + +#[cfg(not(target_family = "wasm"))] +mod io { + // This file is automatically generated, so please do not edit it. + // @generated by `flutter_rust_bridge`@ 2.12.0. + + // Section: imports + + use super::*; + use flutter_rust_bridge::for_generated::byteorder::{ + NativeEndian, ReadBytesExt, WriteBytesExt, + }; + use flutter_rust_bridge::for_generated::{transform_result_dco, Lifetimeable, Lockable}; + use flutter_rust_bridge::{Handler, IntoIntoDart}; + + // Section: boilerplate + + flutter_rust_bridge::frb_generated_boilerplate_io!(); +} +#[cfg(not(target_family = "wasm"))] +pub use io::*; + +/// cbindgen:ignore +#[cfg(target_family = "wasm")] +mod web { + // This file is automatically generated, so please do not edit it. + // @generated by `flutter_rust_bridge`@ 2.12.0. + + // Section: imports + + use super::*; + use flutter_rust_bridge::for_generated::byteorder::{ + NativeEndian, ReadBytesExt, WriteBytesExt, + }; + use flutter_rust_bridge::for_generated::wasm_bindgen; + use flutter_rust_bridge::for_generated::wasm_bindgen::prelude::*; + use flutter_rust_bridge::for_generated::{transform_result_dco, Lifetimeable, Lockable}; + use flutter_rust_bridge::{Handler, IntoIntoDart}; + + // Section: boilerplate + + flutter_rust_bridge::frb_generated_boilerplate_web!(); +} +#[cfg(target_family = "wasm")] +pub use web::*; diff --git a/crates/chanora_bridge/src/lib.rs b/crates/chanora_bridge/src/lib.rs index 9d989f9..c646531 100644 --- a/crates/chanora_bridge/src/lib.rs +++ b/crates/chanora_bridge/src/lib.rs @@ -1,42 +1,73 @@ //! # `chanora_bridge` //! -//! Typed Flutter/Rust bridge. Owns the canonical DTO catalogue for -//! commands (Dart → Rust), results (Rust → Dart return values), and -//! events (Rust → Dart streams). +//! Typed Flutter/Rust bridge — schema-controlled DTOs for commands, +//! results, and events. Backed by `flutter_rust_bridge` 2.x per +//! DEC-014. //! -//! Bridge tool per DEC-014: `flutter_rust_bridge` 2.x. The bridge -//! integration glue (codegen invocations, the cdylib boundary) is -//! added when `apps/chanora_flutter` is wired up; this crate owns -//! only the type definitions and error mappings — explicitly *not* -//! `tsclientlib` or raw subsystem types (SAD-067, SDD-079). +//! ## Alpha scope //! -//! ## Status +//! Exposes three commands that target the Alpha release goal: //! -//! Scaffold only. +//! * `bridge_init()` — one-time process initialisation. Sets up +//! logging. +//! * `connect(host, nickname)` — connects to a TS3-compatible +//! server and returns a typed [`ConnectResultDto`] containing the +//! server snapshot. +//! * `disconnect()` — clean disconnect. +//! * `snapshot()` — re-fetch the current server snapshot. +//! +//! No audio commands cross the bridge in Alpha; audio is Beta scope. +//! +//! ## Boundary discipline +//! +//! Every type in this module is `Serialize + Deserialize` over owned +//! primitives or `String`s. No `tsclientlib`, `cpal`, or backend +//! types may appear in the public surface (SAD-067, SDD-079). +//! +//! Note: this crate cannot use `#![forbid(unsafe_code)]` because the +//! FRB-generated glue (in `frb_generated`) legitimately uses unsafe +//! for the FFI boundary. Hand-written code in this crate must +//! nonetheless avoid `unsafe` and is held to that standard by review. -#![forbid(unsafe_code)] #![warn(missing_docs)] -use serde::{Deserialize, Serialize}; +pub mod api; +mod frb_generated; + use thiserror::Error; /// Errors raised at the bridge boundary. Production code must keep -/// `BridgeError` user-safe — no secrets, no protocol details, no -/// path information beyond what the redaction policy permits. -#[derive(Debug, Error, Clone, Serialize, Deserialize)] +/// these user-safe — no secrets, no protocol details, no path +/// information beyond what the redaction policy permits. +#[derive(Debug, Error, Clone, serde::Serialize, serde::Deserialize)] pub enum BridgeError { /// The caller submitted a malformed command DTO. #[error("invalid command: {0}")] InvalidCommand(String), - /// An unknown / unmapped error escaped the subsystem boundary. - /// Production callers should never see this; if it appears it is - /// a mapping bug in this crate. + /// Connection layer failure (typed-mapped from CoreError). + #[error("connection: {0}")] + Connection(String), + /// Operation requires an active connection. + #[error("not connected")] + NotConnected, + /// Already connected; DEC-006 forbids a second concurrent + /// connection. + #[error("already connected")] + AlreadyConnected, + /// An unmapped error escaped the subsystem boundary. Production + /// callers should never see this; if they do, it is a mapping + /// bug here. #[error("unmapped: {0}")] Unmapped(String), } -#[cfg(test)] -mod tests { - #[test] - fn it_compiles() {} +impl From for BridgeError { + fn from(e: chanora_core::CoreError) -> Self { + match e { + chanora_core::CoreError::NotConnected => BridgeError::NotConnected, + chanora_core::CoreError::AlreadyConnected => BridgeError::AlreadyConnected, + chanora_core::CoreError::Protocol(p) => BridgeError::Connection(format!("{p}")), + other => BridgeError::Unmapped(format!("{other}")), + } + } } diff --git a/crates/chanora_protocol/Cargo.toml b/crates/chanora_protocol/Cargo.toml index c90e0ff..120faa2 100644 --- a/crates/chanora_protocol/Cargo.toml +++ b/crates/chanora_protocol/Cargo.toml @@ -12,3 +12,14 @@ publish.workspace = true [dependencies] thiserror.workspace = true tracing.workspace = true +serde.workspace = true + +# tsclientlib is git-only and not on crates.io. Audio feature disabled +# because chanora_audio owns audio paths; the protocol crate only +# handles connection lifecycle + state book events. +tsclientlib = { git = "https://github.com/ReSpeak/tsclientlib.git", rev = "04aa2491", default-features = false, features = ["default-tls"] } + +# Async runtime utilities used by the connection task. +tokio = { version = "1", features = ["macros", "rt-multi-thread", "time", "sync"] } +futures = "0.3" +async-trait = "0.1" diff --git a/crates/chanora_protocol/src/adapter.rs b/crates/chanora_protocol/src/adapter.rs new file mode 100644 index 0000000..a2d949e --- /dev/null +++ b/crates/chanora_protocol/src/adapter.rs @@ -0,0 +1,305 @@ +//! The adapter that drives `tsclientlib` on a background task and +//! exposes a typed channel-and-future API to the rest of Chanora. +//! +//! Threading model: +//! +//! * `ProtocolClient::connect` spawns a tokio task that owns the +//! `tsclientlib::Connection` (which is not `Send`-safe to move +//! across awaits in some shapes — keeping it inside a single task +//! sidesteps the problem entirely). +//! * The task exposes its life via a `oneshot` that fires when the +//! initial state snapshot is ready. +//! * Snapshot reads are served by sending a request over an +//! `mpsc::channel`; the task replies on a `oneshot` per request. +//! * Disconnect is requested via a `oneshot`; the task drains +//! `tsclientlib`'s outbound events and exits. + +use std::time::Duration; + +use futures::prelude::*; +use tokio::sync::{mpsc, oneshot}; +use tracing::{info, warn}; + +use tsclientlib::data::{self, Channel, Client}; +use tsclientlib::{ + ChannelId as TsChannelId, Connection, DisconnectOptions, Identity, OutCommandExt, StreamItem, +}; + +use crate::dto::{ChannelId, ChannelInfo, ClientId, ClientInfo, ServerSnapshot}; +use crate::ProtocolError; + +/// Typed configuration for a connection attempt. +#[derive(Debug, Clone)] +pub struct ConnectConfig { + /// Server address: `hostname[:port]` or TSDNS name. + pub address: String, + /// Nickname to use on the server. + pub nickname: String, + /// Optional server password. + pub password: Option, + /// Optional pre-existing identity (base64 string accepted by + /// `tsclientlib::Identity::new_from_str`). If `None`, a fresh + /// identity is generated and **not persisted** — production + /// callers must wire this to `chanora_storage::SecretStorageRepository`. + pub identity: Option, + /// How long to wait for the initial state snapshot before + /// returning `ProtocolError::Timeout`. + pub ready_timeout: Duration, +} + +impl Default for ConnectConfig { + fn default() -> Self { + Self { + address: String::new(), + nickname: "Chanora".to_string(), + password: None, + identity: None, + ready_timeout: Duration::from_secs(10), + } + } +} + +enum Request { + Snapshot(oneshot::Sender>), + Disconnect(oneshot::Sender<()>), +} + +/// Async handle owning a live protocol connection. Drop = disconnect. +pub struct ProtocolClient { + tx: mpsc::Sender, +} + +impl ProtocolClient { + /// Dial the server and wait for the initial state snapshot. The + /// returned client is ready for [`Self::snapshot`] and + /// [`Self::disconnect`] calls. + pub async fn connect(cfg: ConnectConfig) -> Result { + if cfg.address.trim().is_empty() { + return Err(ProtocolError::Invalid("address is empty".to_string())); + } + if cfg.nickname.trim().is_empty() { + return Err(ProtocolError::Invalid("nickname is empty".to_string())); + } + + let (tx, rx) = mpsc::channel::(8); + let (ready_tx, ready_rx) = oneshot::channel::>(); + + tokio::spawn(connection_task(cfg.clone(), rx, ready_tx)); + + match tokio::time::timeout(cfg.ready_timeout, ready_rx).await { + Ok(Ok(Ok(()))) => Ok(Self { tx }), + Ok(Ok(Err(e))) => Err(e), + Ok(Err(_)) => Err(ProtocolError::Backend( + "connection task exited before signalling ready".to_string(), + )), + Err(_) => Err(ProtocolError::Timeout), + } + } + + /// Read a typed snapshot of the current server state. + pub async fn snapshot(&self) -> Result { + let (tx, rx) = oneshot::channel(); + self.tx + .send(Request::Snapshot(tx)) + .await + .map_err(|_| ProtocolError::Lost("connection task is gone".to_string()))?; + rx.await + .map_err(|_| ProtocolError::Lost("snapshot reply dropped".to_string()))? + } + + /// Disconnect cleanly. Blocks until the task exits. + pub async fn disconnect(self) { + let (tx, rx) = oneshot::channel(); + if self.tx.send(Request::Disconnect(tx)).await.is_ok() { + let _ = rx.await; + } + } +} + +async fn connection_task( + cfg: ConnectConfig, + mut rx: mpsc::Receiver, + ready_tx: oneshot::Sender>, +) { + let mut builder = Connection::build(cfg.address.clone()).name(cfg.nickname.clone()); + + let identity = match cfg.identity.as_deref() { + Some(s) => match Identity::new_from_str(s) { + Ok(id) => id, + Err(e) => { + let _ = ready_tx.send(Err(ProtocolError::Identity(format!("{e}")))); + return; + } + }, + None => Identity::create(), + }; + builder = builder.identity(identity); + + if let Some(pw) = &cfg.password { + builder = builder.password(pw.clone()); + } + + let mut con = match builder.connect() { + Ok(c) => c, + Err(e) => { + let _ = ready_tx.send(Err(ProtocolError::Connect(format!("{e}")))); + return; + } + }; + + // Wait for the first BookEvents indicating the state snapshot is ready. + let first = con + .events() + .try_filter(|e| future::ready(matches!(e, StreamItem::BookEvents(_)))) + .next() + .await; + match first { + Some(Ok(_)) => { + info!(target: "chanora_protocol", "initial state snapshot received"); + } + Some(Err(e)) => { + let _ = ready_tx.send(Err(ProtocolError::DisconnectedEarly(format!("{e}")))); + return; + } + None => { + let _ = ready_tx.send(Err(ProtocolError::DisconnectedEarly( + "event stream ended before snapshot".to_string(), + ))); + return; + } + } + + // Subscribe to the full server tree so snapshot() returns more than just our channel. + if let Ok(state) = con.get_state() { + if let Err(e) = state.server.set_subscribed(true).send(&mut con) { + warn!(target: "chanora_protocol", error = %e, "could not subscribe to server tree"); + } + } + + // Settle: pump events for ~2 s so the subscribed tree arrives + // before the first snapshot. The upstream packet codec emits + // out-of-order command-packet warnings here; they are harmless + // and the final state still converges. + let settle_until = std::time::Instant::now() + Duration::from_secs(2); + while std::time::Instant::now() < settle_until { + let ev = tokio::time::timeout(Duration::from_millis(100), con.events().next()).await; + match ev { + Ok(Some(Ok(_))) => continue, + Ok(Some(Err(e))) => { + warn!(target: "chanora_protocol", error = %e, "event error during settle"); + } + Ok(None) => { + let _ = ready_tx.send(Err(ProtocolError::DisconnectedEarly( + "stream closed during settle".to_string(), + ))); + return; + } + Err(_) => { /* no event available right now; keep waiting */ } + } + } + + let _ = ready_tx.send(Ok(())); + + // Request loop with a continuously-pumped event stream. We pump + // one event at a time, then check for one pending request, then + // repeat. This avoids holding a borrow on `con` across an await + // boundary in `tokio::select!`. + loop { + // Try to advance the event stream by one event with a small + // timeout. Errors are logged; stream end is fatal. + let pump = async { + let mut ev_stream = con.events(); + tokio::time::timeout(Duration::from_millis(50), ev_stream.next()).await + }; + match pump.await { + Ok(Some(Ok(_))) => { /* event consumed */ } + Ok(Some(Err(e))) => { + warn!(target: "chanora_protocol", error = %e, "event error"); + } + Ok(None) => { + warn!(target: "chanora_protocol", "event stream ended"); + return; + } + Err(_) => { /* no event in 50 ms — service requests */ } + } + + // Service at most one request (non-blocking) so we keep + // pumping events too. + match rx.try_recv() { + Ok(Request::Snapshot(reply)) => { + let snap = build_snapshot(&con); + let _ = reply.send(snap); + } + Ok(Request::Disconnect(reply)) => { + let _ = con.disconnect(DisconnectOptions::new()); + con.events().for_each(|_| future::ready(())).await; + let _ = reply.send(()); + info!(target: "chanora_protocol", "clean disconnect"); + return; + } + Err(mpsc::error::TryRecvError::Empty) => { /* nothing to do */ } + Err(mpsc::error::TryRecvError::Disconnected) => { + let _ = con.disconnect(DisconnectOptions::new()); + con.events().for_each(|_| future::ready(())).await; + info!(target: "chanora_protocol", "handle dropped; implicit disconnect"); + return; + } + } + } +} + +fn build_snapshot(con: &Connection) -> Result { + let state: &data::Connection = con + .get_state() + .map_err(|e| ProtocolError::Backend(format!("get_state: {e}")))?; + + let mut channels: Vec<&Channel> = state.channels.values().collect(); + channels.sort_by_key(|c| c.order.0); + let clients: Vec<&Client> = state.clients.values().collect(); + + let channels_dto: Vec = channels + .iter() + .map(|c| ChannelInfo { + id: ChannelId(c.id.0), + parent: ChannelId(c.parent.0), + name: sanitize(&c.name), + order: c.order.0 as i64, + }) + .collect(); + + let clients_dto: Vec = clients + .iter() + .map(|c| ClientInfo { + id: ClientId(c.id.0 as u64), + channel: ChannelId(c.channel.0), + name: sanitize(&c.name), + }) + .collect(); + + Ok(ServerSnapshot { + server_name: sanitize(&state.server.name), + welcome_message: sanitize(&state.server.welcome_message), + platform: sanitize(&state.server.platform), + version: sanitize(&state.server.version), + channels: channels_dto, + clients: clients_dto, + }) +} + +/// Light sanitisation of strings before they cross the protocol +/// boundary. The redaction policy proper lives in +/// `chanora_diagnostics`; this filter only strips control characters +/// that would break terminal output or Flutter rendering. +fn sanitize(s: &str) -> String { + s.chars() + .filter(|c| !c.is_control() || *c == '\t' || *c == '\n') + .collect() +} + +#[allow(dead_code)] +const _ROOT_MATCHES_UPSTREAM: () = { + // Compile-time assertion that ChannelId(0) maps to what tsclientlib + // also considers the root. If upstream ever changes, this stops + // compiling and forces an audit. + let _ = TsChannelId(0); +}; diff --git a/crates/chanora_protocol/src/dto.rs b/crates/chanora_protocol/src/dto.rs new file mode 100644 index 0000000..8a07669 --- /dev/null +++ b/crates/chanora_protocol/src/dto.rs @@ -0,0 +1,61 @@ +//! Public DTOs returned across the protocol boundary. All fields are +//! owned primitives or `String`s; no `tsclientlib` types leak. + +use serde::{Deserialize, Serialize}; + +/// Opaque server-side channel identifier. Internal representation is +/// the upstream u64 but callers must treat it as opaque. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub struct ChannelId(pub u64); + +/// Opaque server-side client identifier. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub struct ClientId(pub u64); + +/// One channel in the server's tree. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ChannelInfo { + /// Stable channel id. + pub id: ChannelId, + /// Parent channel id; `ChannelId(0)` indicates a top-level channel. + pub parent: ChannelId, + /// Display name, preserved verbatim per ADR-008. + pub name: String, + /// Server-side ordering hint. + pub order: i64, +} + +/// One connected client on the server. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ClientInfo { + /// Stable client id. + pub id: ClientId, + /// Channel the client is currently in. + pub channel: ChannelId, + /// Nickname, preserved verbatim per ADR-008. + pub name: String, +} + +/// Snapshot of the server's published state at a moment in time. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ServerSnapshot { + /// Server name. + pub server_name: String, + /// Server welcome banner. Contains markup from the upstream + /// server (BBCode-like); not parsed here. + pub welcome_message: String, + /// Server platform string. + pub platform: String, + /// Server version string. + pub version: String, + /// All channels currently known. + pub channels: Vec, + /// All clients currently known. + pub clients: Vec, +} + +impl ChannelId { + /// The conventional root sentinel used by TeamSpeak-compatible + /// servers for the top of the channel tree. + pub const ROOT: ChannelId = ChannelId(0); +} diff --git a/crates/chanora_protocol/src/lib.rs b/crates/chanora_protocol/src/lib.rs index caa3596..6da7f27 100644 --- a/crates/chanora_protocol/src/lib.rs +++ b/crates/chanora_protocol/src/lib.rs @@ -4,48 +4,66 @@ //! behind a typed boundary so the rest of Chanora is decoupled from //! the upstream library's types (SAD-067, SysDes-011, SysDes-029). //! -//! ## Status +//! ## What this crate exposes //! -//! Scaffold only. Promotion of `poc/tsclientlib-connect-spike` into -//! this crate happens later, with its own audit-trail commit. +//! * [`ConnectConfig`] — typed connection parameters. +//! * [`ProtocolClient`] — async handle owning the connection task. +//! * [`ServerSnapshot`], [`ChannelInfo`], [`ClientInfo`] — opaque +//! DTOs containing only `String`s and primitives. +//! * [`ProtocolError`] — typed error catalogue. +//! +//! ## What this crate does NOT expose +//! +//! * `tsclientlib::*` types. +//! * `tsproto::*` types. +//! * Any audio-related types — those live in `chanora_audio`. +//! +//! Promoted from `poc/tsclientlib-connect-spike` on 2026-05-14 +//! as part of the Alpha build. #![forbid(unsafe_code)] #![warn(missing_docs)] +mod adapter; +mod dto; + +pub use adapter::{ConnectConfig, ProtocolClient}; +pub use dto::{ChannelInfo, ClientInfo, ServerSnapshot}; + use thiserror::Error; /// Errors surfaced by the protocol adapter. None of these expose -/// `tsclientlib`-specific types; raw upstream errors are mapped here. +/// `tsclientlib`-specific types; raw upstream errors are mapped here +/// to typed arms. #[derive(Debug, Error)] pub enum ProtocolError { /// Configuration is invalid before any I/O is attempted (bad /// hostname, missing identity, etc.). #[error("invalid protocol configuration: {0}")] - Invalid(&'static str), - /// The connect handshake failed. + Invalid(String), + + /// Failed to dial / handshake with the server. #[error("connect failed: {0}")] Connect(String), - /// The connection ended unexpectedly. - #[error("disconnected: {0}")] - Disconnected(String), + + /// The connection ended before becoming ready. + #[error("disconnected before ready: {0}")] + DisconnectedEarly(String), + + /// Connection lost after becoming ready. + #[error("connection lost: {0}")] + Lost(String), + + /// Identity parsing failed. + #[error("identity error: {0}")] + Identity(String), + /// Operation timed out. #[error("protocol timeout")] Timeout, -} -/// Opaque identifier for a server-side channel. Replaces -/// `tsclientlib::ChannelId` at the public boundary so its concrete -/// representation can change without leaking through the rest of the -/// codebase. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct ChannelId(pub u64); - -/// Opaque identifier for a server-side client. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct ClientId(pub u64); - -#[cfg(test)] -mod tests { - #[test] - fn it_compiles() {} + /// A backend error escaped the mapping. Production callers + /// should never see this; if they do, it is a mapping bug here. + #[error("protocol backend: {0}")] + Backend(String), } diff --git a/flutter_rust_bridge.yaml b/flutter_rust_bridge.yaml new file mode 100644 index 0000000..69e58cf --- /dev/null +++ b/flutter_rust_bridge.yaml @@ -0,0 +1,4 @@ +rust_input: crate::api +rust_root: crates/chanora_bridge/ +dart_output: apps/chanora_flutter/lib/src/rust +rust_output: crates/chanora_bridge/src/frb_generated.rs