commit ba48de2d30f6376397d1a6f37c4d666099d16d11 Author: Stan Grams Date: Sun May 17 23:25:14 2026 +0200 Initial commit Signed-off-by: Stan Grams diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..171fa78 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,11 @@ +# SPDX-FileCopyrightText: 2026 Stan Grams +# +# SPDX-License-Identifier: GPL-2.0-or-later + +# Enable CPU optimizations for better performance +# Set target-cpu to native to use all available CPU features on the build machine + +[build] +# Use native CPU features (AVX2, SSE4.2, etc.) for maximum performance +# This enables better vectorization in rustc and dependencies +rustflags = ["-C", "target-cpu=native", "-C", "opt-level=3"] diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..ba1522d --- /dev/null +++ b/.gitattributes @@ -0,0 +1,24 @@ +# Enforce LF for all text files to prevent CRLF or mixed EOLs +* text=auto eol=lf + +# Treat common binary types as binary +*.png binary +*.jpg binary +*.jpeg binary +*.gif binary +*.ico binary +*.pdf binary +*.zip binary +*.gz binary +*.bz2 binary +*.xz binary +*.7z binary +*.tar binary +*.mp4 binary +*.mov binary +*.mp3 binary +*.wav binary +*.ttf binary +*.otf binary +*.woff binary +*.woff2 binary diff --git a/.github/workflows/wiki.yml b/.github/workflows/wiki.yml new file mode 100644 index 0000000..1918622 --- /dev/null +++ b/.github/workflows/wiki.yml @@ -0,0 +1,39 @@ +name: Sync docs to Wiki + +on: + push: + branches: [main] + paths: + - 'docs/**' + workflow_dispatch: + +permissions: + contents: write + +jobs: + wiki: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Checkout wiki + uses: actions/checkout@v4 + with: + repository: ${{ github.repository }}.wiki + path: wiki + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Sync docs to wiki + run: | + rsync -av --delete --exclude='.git' docs/ wiki/ + cd wiki + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add -A + if git diff --cached --quiet; then + echo "No wiki changes to commit." + else + git commit -m "Sync docs from ${GITHUB_SHA::8}" + git push + fi diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7fab369 --- /dev/null +++ b/.gitignore @@ -0,0 +1,32 @@ +# Rust +/target/ +**/*.rs.bk +Cargo.lock + +# IDEs/Editors +/.idea/ +/.vscode/ +*.swp +*.swo + +# macOS +.DS_Store + +# Logs +*.log + +# Coverage/Bench +*.profraw +*.lcov +coverage/ +benchmarks/ + +# Env +.env +.env.* + +# Claude Code +.claude/ + +# User-defined local files +/.user-defined/ diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..b6da9ce --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,131 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Commands + +```bash +# Build +cargo build --release + +# Run tests +cargo test + +# Lint +cargo clippy + +# Run a single test (by name pattern) +cargo test + +# Run tests for a specific crate +cargo test -p trx-core + +# Generate example config +./target/release/trx-server --print-config > trx-server.toml +./target/release/trx-client --print-config > trx-client.toml + +# Run server +./target/release/trx-server --config trx-server.toml +# or via CLI args: +./target/release/trx-server -r ft817 "/dev/ttyUSB0 9600" + +# Run client +./target/release/trx-client --config trx-client.toml +``` + +## Crate Layout + +This is a Cargo workspace. All crates live under `src/`: + +``` +src/ + trx-core/ # Core types, traits, state machine, controller (~3,500 LOC) + trx-protocol/ # Client↔server protocol DTOs, auth, codec, mapping (~1,100 LOC) + trx-app/ # Shared application helpers (config paths, logging init) + trx-reporting/ # PSKReporter UDP uplink + APRS-IS TCP uplink (~1,150 LOC) + trx-server/ # Server binary: rig_task, audio pipeline, listener (~3,700 LOC) + trx-backend/ # Backend abstraction trait + factory + dummy + trx-backend-ft817/ # Yaesu FT-817 binary CAT (BCD encoding) + trx-backend-ft450d/ # Yaesu FT-450D ASCII CAT + trx-backend-soapysdr/ # SoapySDR RX with full DSP pipeline (~5,000+ LOC) + trx-client/ # Client binary: remote connection, frontend spawning (~1,500 LOC) + trx-frontend/ # Frontend trait (FrontendSpawner), runtime context + trx-frontend-http/ # Web UI: REST API, SSE, WebSocket audio, session auth + trx-frontend-http-json/ # JSON-over-TCP control frontend + trx-frontend-rigctl/ # Hamlib-compatible rigctl TCP interface + trx-configurator/ # Interactive setup wizard + decoders/ + trx-aprs/ # APRS packet decoder (AX.25 + APRS-IS) + trx-cw/ # CW (Morse) decoder (Goertzel tone detection) + trx-ftx/ # Pure Rust FTx decoder (FT8/FT4/FT2, LDPC/OSD) (~3,000+ LOC) + trx-wspr/ # WSPR weak-signal decoder + trx-ais/ # AIS maritime transponder decoder + trx-rds/ # RDS decoder for WFM (~2,000 LOC) + trx-vdes/ # VDES maritime data exchange decoder (~1,300 LOC) + trx-decode-log/ # JSON Lines file logging with date rotation +``` + +## Architecture + +The project is split into a **server** (connects to the radio hardware) and a **client** (exposes user-facing frontends). They communicate over a JSON TCP connection (default port 4530). Audio streams over a separate TCP connection (default port 4531) using Opus encoding. + +### Data flow + +```mermaid +graph TD + Radio["Radio Hardware"] <-->|serial / TCP| Server["trx-server (rig_task.rs)"] + Server <-->|"JSON-TCP :4530"| Client["trx-client (remote_client.rs)"] + Server -->|"Opus-TCP :4531"| Client + Client <-->|internal channels| F1["HTTP Frontend :8080"] + Client <-->|internal channels| F2["rigctl Frontend :4532"] + Client <-->|internal channels| F3["JSON-TCP Frontend"] +``` + +### trx-core controller + +The rig controller (`src/trx-core/src/rig/controller/`) is the central state management component: + +- **`machine.rs`** — `RigMachineState` enum with states: `Disconnected`, `Connecting`, `Initializing`, `PoweredOff`, `Ready`, `Transmitting`, `Error` +- **`handlers.rs`** — `RigCommandHandler` trait; commands: `SetFreq`, `SetMode`, `SetPtt`, `PowerOn/Off`, `ToggleVfo`, `Lock/Unlock`, `GetSnapshot`, etc. +- **`events.rs`** — `RigListener` trait and `RigEventEmitter` for broadcasting frequency/mode/PTT/state/meter/lock/power changes +- **`policies.rs`** — `RetryPolicy` (`ExponentialBackoff`, `FixedDelay`, `NoRetry`) and `PollingPolicy` (`AdaptivePolling`, `FixedPolling`, `NoPolling`) + +### Decoders + +Signal decoders run as background tasks in `trx-server`, consuming decoded audio. `trx-ftx` provides the FT8/FT4/FT2 decoder in pure Rust. Decoded frames can be forwarded to PSKReporter and APRS-IS (IGate) uplinks, or logged via `trx-decode-log`. + +## Diagrams + +Always use [Mermaid](https://mermaid.js.org/) for diagrams in Markdown files. Never use ASCII art, box-drawing characters, or plain-text diagrams. GitHub renders Mermaid natively in ```mermaid fenced code blocks. + +## Commit Format + +``` +[](): +``` + +Types: `feat`, `fix`, `docs`, `style`, `refactor`, `test`, `chore`. Use `(trx-rs)` for repo-wide changes. Sign commits with `git commit -s`. Write isolated commits per crate. + +## Codebase Review Observations + +Full architecture documentation: `docs/Architecture.md` +Improvement plan: `docs/Improvement-Areas.md` + +*Last reviewed: 2026-03-29* + +### Strengths + +- **Explicit state machine**: `RigMachineState` FSM (7 states) prevents invalid states with a deterministic transition table and exhaustive matching. Well-tested with lifecycle, error recovery, and invalid transition tests. `ReadyStateData`/`TransmittingStateData` use `pub(crate)` fields with controlled accessors. +- **Trait-based polymorphism**: Clean abstraction boundaries (`RigCat`, `RigSdr`, `AudioSource`, `RigListener`, `RigCommandHandler`, `CommandExecutor`, `TokenValidator`, `FrontendSpawner`) enable loose coupling and testability. `RigCat`/`RigSdr` split cleanly separates CAT ops from SDR-specific methods. +- **Multi-rig architecture**: Per-rig task isolation with `HashMap` routing, per-rig state/spectrum/audio/decoder-history channels, dual-connection model (main + spectrum) in the client, and backward-compatible single-rig mode. +- **Async concurrency model**: Proper use of tokio channels -- `watch` for state snapshots, `broadcast` for PCM/decode fan-out, `mpsc` for commands. No mutex contention on hot paths. Spectrum deduplication collapses concurrent GetSpectrum requests. +- **Comprehensive SDR support**: Full DSP pipeline with multi-mode demodulation (SSB, AM, SAM, FM, WFM, AIS, VDES), virtual channel management, squelch, noise blanker, spectrum FFT, RDS decoding. AVX2-optimized FM discriminator with scalar fallbacks. +- **Pure Rust decoders**: FT8/FT4/FT2, APRS, CW, WSPR, AIS, VDES, RDS -- all implemented without C FFI dependencies. Consistent decoder pattern: stateful struct → `process_block()` → `decode_if_ready()`. +- **Good test coverage** in protocol layer: codec, mapping, auth all have thorough unit tests with round-trip verification. 45+ mapping tests cover all command variants. +- **Feature-gated backends**: ft817, ft450d, soapysdr compiled conditionally to minimize binary size. Factory pattern with name normalization for registration. +- **Defensive error handling**: Lock poisoning recovery, stream error deduplication with 60s summaries, input truncation in logs (128 chars), per-IP rate limiting on auth endpoints. +- **Well-documented DSP guidelines**: `docs/Optimization-Guidelines.md` captures lessons on NCO design, polyphase resampling, AVX2 batching, and stereo FM decoding. + +### Areas for Improvement + +All P0–P3 items resolved or dropped. See `docs/Improvement-Areas.md` for details. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..99d5b2f --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,41 @@ +## Workflow + +The trx-rs project is organized into three main components: +1. **trx-core**: core library providing the basic functionalities, +2. **trx-server**: server component that hooks up the transceiver, +3. **trx-client**: client component that connects with the trx-server and exposes selected frontends. + +When contributing to the project, please follow these guidelines: +- Fork the repository and create a new branch for your changes. +- Make sure your code follows the project's coding style and conventions. +- Write clear and concise commit messages. +- Submit a pull request with a detailed description of your changes. +- Ensure that your changes are tested and pass all existing tests. + +## Commit Guidelines +- Use imperative mood in commit messages. +- Keep commit messages short and descriptive. +- Use a maximum of 80 characters per line. +- Use a blank line between the commit message and the body. +- Sign your commits with `git commit -s`. +- Explicitly mark LLM usage in commit messages with 'Co-authored-by:'. + +Use the format below for commit titles: +[](): +e.g. +- [feat](trx-core): add new feature xyz +- [fix](trx-frontend): fix http frontend xyz issue +- [docs](trx-rs): update README + +Use `(trx-rs)` for repo-wide changes that are not specific to any crate. + +Allowed types: +- feat: new feature +- fix: bug fix +- docs: documentation changes +- style: code style changes +- refactor: code refactoring +- test: test changes +- chore: build or maintenance changes + +Write isolated commits for each crate. diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..62da909 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,4193 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "actix-codec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a" +dependencies = [ + "bitflags 2.11.1", + "bytes", + "futures-core", + "futures-sink", + "memchr", + "pin-project-lite", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "actix-http" +version = "3.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93acb4a42f64936f9b8cae4a433b237599dd6eb6ed06124eb67132ef8cc90662" +dependencies = [ + "actix-codec", + "actix-rt", + "actix-service", + "actix-utils", + "base64", + "bitflags 2.11.1", + "brotli 8.0.2", + "bytes", + "bytestring", + "derive_more", + "encoding_rs", + "flate2", + "foldhash", + "futures-core", + "h2", + "http 0.2.12", + "httparse", + "httpdate", + "itoa", + "language-tags", + "local-channel", + "mime", + "percent-encoding", + "pin-project-lite", + "rand 0.10.1", + "sha1", + "smallvec", + "tokio", + "tokio-util", + "tracing", + "zstd", +] + +[[package]] +name = "actix-macros" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "actix-router" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14f8c75c51892f18d9c46150c5ac7beb81c95f78c8b83a634d49f4ca32551fe7" +dependencies = [ + "bytestring", + "cfg-if", + "http 0.2.12", + "regex", + "regex-lite", + "serde", + "tracing", +] + +[[package]] +name = "actix-rt" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92589714878ca59a7626ea19734f0e07a6a875197eec751bb5d3f99e64998c63" +dependencies = [ + "futures-core", + "tokio", +] + +[[package]] +name = "actix-server" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a65064ea4a457eaf07f2fba30b4c695bf43b721790e9530d26cb6f9019ff7502" +dependencies = [ + "actix-rt", + "actix-service", + "actix-utils", + "futures-core", + "futures-util", + "mio", + "socket2 0.5.10", + "tokio", + "tracing", +] + +[[package]] +name = "actix-service" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e46f36bf0e5af44bdc4bdb36fbbd421aa98c79a9bce724e1edeb3894e10dc7f" +dependencies = [ + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "actix-utils" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a1dcdff1466e3c2488e1cb5c36a71822750ad43839937f85d2f4d9f8b705d8" +dependencies = [ + "local-waker", + "pin-project-lite", +] + +[[package]] +name = "actix-web" +version = "4.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff87453bc3b56e9b2b23c1cc0b1be8797184accf51d2abe0f8a33ec275d316bf" +dependencies = [ + "actix-codec", + "actix-http", + "actix-macros", + "actix-router", + "actix-rt", + "actix-server", + "actix-service", + "actix-utils", + "actix-web-codegen", + "bytes", + "bytestring", + "cfg-if", + "cookie", + "derive_more", + "encoding_rs", + "foldhash", + "futures-core", + "futures-util", + "impl-more", + "itoa", + "language-tags", + "log", + "mime", + "once_cell", + "pin-project-lite", + "regex", + "regex-lite", + "serde", + "serde_json", + "serde_urlencoded", + "smallvec", + "socket2 0.6.3", + "time", + "tracing", + "url", +] + +[[package]] +name = "actix-web-codegen" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f591380e2e68490b5dfaf1dd1aa0ebe78d84ba7067078512b4ea6e4492d622b8" +dependencies = [ + "actix-router", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "actix-ws" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12d4f2fbee3ef7a22fa6cb0e416b962237a167ed0419f22d4e451da2d7f082f8" +dependencies = [ + "actix-codec", + "actix-http", + "actix-web", + "bytestring", + "futures-core", + "tokio", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +dependencies = [ + "alloc-no-stdlib", +] + +[[package]] +name = "alsa" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed7572b7ba83a31e20d1b48970ee402d2e3e0537dcfe0a3ff4d6eb7508617d43" +dependencies = [ + "alsa-sys", + "bitflags 2.11.1", + "cfg-if", + "libc", +] + +[[package]] +name = "alsa-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db8fee663d06c4e303404ef5f40488a53e062f89ba8bfed81f42325aafad1527" +dependencies = [ + "libc", + "pkg-config", +] + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "audiopus_sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62314a1546a2064e033665d658e88c620a62904be945f8147e6b16c3db9f8651" +dependencies = [ + "cmake", + "log", + "pkg-config", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bindgen" +version = "0.66.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2b84e06fc203107bfbad243f4aba2af864eb7db3b1cf46ea0a023b0b433d2a7" +dependencies = [ + "bitflags 2.11.1", + "cexpr", + "clang-sys", + "lazy_static", + "lazycell", + "peeking_take_while", + "proc-macro2", + "quote", + "regex", + "rustc-hash 1.1.0", + "shlex", + "syn", +] + +[[package]] +name = "bindgen" +version = "0.72.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" +dependencies = [ + "bitflags 2.11.1", + "cexpr", + "clang-sys", + "itertools", + "proc-macro2", + "quote", + "regex", + "rustc-hash 2.1.2", + "shlex", + "syn", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" + +[[package]] +name = "block-buffer" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdd35008169921d80bc60d3d0ab416eecb028c4cd653352907921d95084790be" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "brotli" +version = "7.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc97b8f16f944bba54f0433f07e30be199b6dc2bd25937444bbad560bcea29bd" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor 4.0.3", +] + +[[package]] +name = "brotli" +version = "8.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor 5.0.0", +] + +[[package]] +name = "brotli-decompressor" +version = "4.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a334ef7c9e23abf0ce748e8cd309037da93e606ad52eb372e4ce327a0dcfbdfd" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + +[[package]] +name = "brotli-decompressor" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + +[[package]] +name = "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 = "bytestring" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "113b4343b5f6617e7ad401ced8de3cc8b012e73a594347c307b90db3e9271289" +dependencies = [ + "bytes", +] + +[[package]] +name = "cc" +version = "1.2.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43c5703da9466b66a946814e1adf53ea2c90f10063b86290cc9eb67ce3478a20" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + +[[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", + "rand_core 0.10.1", +] + +[[package]] +name = "chrono" +version = "0.4.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" +dependencies = [ + "iana-time-zone", + "num-traits", + "serde", + "windows-link", +] + +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "clap" +version = "4.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" + +[[package]] +name = "cmake" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" +dependencies = [ + "cc", +] + +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "console" +version = "0.15.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8" +dependencies = [ + "encode_unicode", + "libc", + "once_cell", + "unicode-width", + "windows-sys 0.59.0", +] + +[[package]] +name = "const-oid" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" + +[[package]] +name = "convert_case" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "cookie" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" +dependencies = [ + "percent-encoding", + "time", + "version_check", +] + +[[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 = "coreaudio-rs" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "321077172d79c662f64f5071a03120748d5bb652f5231570141be24cfcd2bace" +dependencies = [ + "bitflags 1.3.2", + "core-foundation-sys", + "coreaudio-sys", +] + +[[package]] +name = "coreaudio-sys" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ceec7a6067e62d6f931a2baf6f3a751f4a892595bcec1461a3c94ef9949864b6" +dependencies = [ + "bindgen 0.72.1", +] + +[[package]] +name = "cpal" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "873dab07c8f743075e57f524c583985fbaf745602acbe916a01539364369a779" +dependencies = [ + "alsa", + "core-foundation-sys", + "coreaudio-rs", + "dasp_sample", + "jni", + "js-sys", + "libc", + "mach2", + "ndk", + "ndk-context", + "oboe", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows", +] + +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crypto-common" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77727bb15fa921304124b128af125e7e3b968275d1b108b379190264f4423710" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "dasp_sample" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c87e182de0887fd5361989c677c4e8f5000cd9491d6d563161a8f3a5519fc7f" + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "derive_more" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn", + "unicode-xid", +] + +[[package]] +name = "dialoguer" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "658bce805d770f407bc62102fca7c2c64ceef2fbcb2b8bd19d2765ce093980de" +dependencies = [ + "console", + "shell-words", + "tempfile", + "thiserror 1.0.69", + "zeroize", +] + +[[package]] +name = "digest" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4850db49bf08e663084f7fb5c87d202ef91a3907271aff24a94eb97ff039153c" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", +] + +[[package]] +name = "dirs" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.61.2", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "encode_unicode" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" + +[[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 = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "fastrand" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" + +[[package]] +name = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[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 = "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 = "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 = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "h2" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http 0.2.12", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[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.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hound" +version = "3.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62adaabb884c94955b19907d60019f4e145d091c75345379e70d1ee696f7854f" + +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http" +version = "1.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 1.4.0", +] + +[[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 1.4.0", + "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 = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hybrid-array" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3944cf8cf766b40e2a1a333ee5e9b563f854d5fa49d6a8ca2764e97c6eddb214" +dependencies = [ + "typenum", +] + +[[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", + "http 1.4.0", + "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 1.4.0", + "hyper", + "hyper-util", + "rustls", + "tokio", + "tokio-rustls", + "tower-service", + "webpki-roots", +] + +[[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 1.4.0", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2 0.6.3", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core 0.62.2", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "image" +version = "0.24.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5690139d2f55868e080017335e4b94cb7414274c74f1669c84fb5feba2c9f69d" +dependencies = [ + "bytemuck", + "byteorder", + "color_quant", + "num-traits", + "png", +] + +[[package]] +name = "impl-more" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a5a9a0ff0086c7a148acb942baaabeadf9504d10400b5a05645853729b9cd2" + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.0", + "serde", + "serde_core", +] + +[[package]] +name = "io-kit-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "617ee6cf8e3f66f3b4ea67a4058564628cde41901316e19f559e14c7c72c5e7b" +dependencies = [ + "core-foundation-sys", + "mach2", +] + +[[package]] +name = "ipnet" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" + +[[package]] +name = "iri-string" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25e659a4bb38e810ebc252e53b5814ff908a8c58c2a9ce2fae1bbec24cbf4e20" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +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.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys 0.3.1", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" +dependencies = [ + "jni-sys 0.4.1", +] + +[[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.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2964e92d1d9dc3364cae4d718d93f227e3abb088e747d92e0395bfdedf1c12ca" +dependencies = [ + "cfg-if", + "futures-util", + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "language-tags" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + +[[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.185" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f" + +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link", +] + +[[package]] +name = "libredox" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c" +dependencies = [ + "libc", +] + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litemap" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" + +[[package]] +name = "local-channel" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6cbc85e69b8df4b8bb8b89ec634e7189099cea8927a276b7384ce5488e53ec8" +dependencies = [ + "futures-core", + "futures-sink", + "local-waker", +] + +[[package]] +name = "local-waker" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d873d7c67ce09b42110d801813efbc9364414e356be9935700d368351657487" + +[[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 = "mach2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d640282b302c0bb0a2a8e0233ead9035e3bed871f0b7e81fe4a1ec829765db44" +dependencies = [ + "libc", +] + +[[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", + "simd-adler32", +] + +[[package]] +name = "mio" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "mio-serial" +version = "5.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "029e1f407e261176a983a6599c084efd322d9301028055c87174beac71397ba3" +dependencies = [ + "log", + "mio", + "nix 0.29.0", + "serialport", + "winapi", +] + +[[package]] +name = "ndk" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2076a31b7010b17a38c01907c45b945e8f11495ee4dd588309718901b1f7a5b7" +dependencies = [ + "bitflags 2.11.1", + "jni-sys 0.3.1", + "log", + "ndk-sys", + "num_enum", + "thiserror 1.0.69", +] + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "ndk-sys" +version = "0.5.0+25.2.9519653" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c196769dd60fd4f363e11d948139556a344e79d451aeb2fa2fd040738ef7691" +dependencies = [ + "jni-sys 0.3.1", +] + +[[package]] +name = "nix" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" +dependencies = [ + "bitflags 1.3.2", + "cfg-if", + "libc", +] + +[[package]] +name = "nix" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +dependencies = [ + "bitflags 2.11.1", + "cfg-if", + "cfg_aliases", + "libc", +] + +[[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-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "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_enum" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" +dependencies = [ + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "oboe" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8b61bebd49e5d43f5f8cc7ee2891c16e0f41ec7954d36bcb6c14c5e0de867fb" +dependencies = [ + "jni", + "ndk", + "ndk-context", + "num-derive", + "num-traits", + "oboe-sys", +] + +[[package]] +name = "oboe-sys" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8bb09a4a2b1d668170cfe0a7d5bc103f8999fb316c98099b6a9939c9f2e79d" +dependencies = [ + "cc", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "opus" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d3809943dff6fbad5f0484449ea26bdb9cb7d8efdf26ed50d3c7f227f69eb5c" +dependencies = [ + "audiopus_sys", +] + +[[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 = "peeking_take_while" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pickledb" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c53a5ade47760e8cc4986bdc5e72daeffaaaee64cbc374f9cfe0a00c1cd87b1f" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pkg-config" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" + +[[package]] +name = "png" +version = "0.17.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "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 = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "primal-check" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc0d895b311e3af9902528fbb8f928688abbd95872819320517cc24ca6b2bd08" +dependencies = [ + "num-integer", +] + +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit 0.25.11+spec-1.1.0", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "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 2.1.2", + "rustls", + "socket2 0.6.3", + "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 = [ + "bytes", + "getrandom 0.3.4", + "lru-slab", + "rand 0.9.4", + "ring", + "rustc-hash 2.1.2", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.18", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2 0.6.3", + "tracing", + "windows-sys 0.60.2", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "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 = "realfft" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f821338fddb99d089116342c46e9f1fbf3828dba077674613e734e01d6ea8677" +dependencies = [ + "rustfft", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.11.1", +] + +[[package]] +name = "redox_users" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" +dependencies = [ + "getrandom 0.2.17", + "libredox", + "thiserror 2.0.18", +] + +[[package]] +name = "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-lite" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab834c73d247e67f4fae452806d17d3c7501756d98c8808d7c9c7aa7d18f973" + +[[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.12.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +dependencies = [ + "base64", + "bytes", + "futures-core", + "http 1.4.0", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "js-sys", + "log", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots", +] + +[[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-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[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 = "rustfft" +version = "6.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21db5f9893e91f41798c88680037dba611ca6674703c1a18601b01a72c8adb89" +dependencies = [ + "num-complex", + "num-integer", + "num-traits", + "primal-check", + "strength_reduce", + "transpose", +] + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags 2.11.1", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.23.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69f9466fb2c14ea04357e91413efb882e2a6d4a406e625449bc0a5d360d53a21" +dependencies = [ + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pki-types" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8279bb85272c9f10811ae6a6c547ff594d6a7f3c6c6b02ee9726d1d0dcfcdd06" +dependencies = [ + "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 = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[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 = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serialport" +version = "4.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4d91116f97173694f1642263b2ff837f80d933aa837e2314969f6728f661df3" +dependencies = [ + "bitflags 2.11.1", + "cfg-if", + "core-foundation", + "core-foundation-sys", + "io-kit-sys", + "mach2", + "nix 0.26.4", + "scopeguard", + "unescaper", + "windows-sys 0.52.0", +] + +[[package]] +name = "sgp4" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9467b9a7be8485ed8be0f336d399c8f32c0fcd60686e7dd2ed3dab75c9a73eb3" +dependencies = [ + "chrono", + "serde", + "serde_json", +] + +[[package]] +name = "sha1" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shell-words" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc6fe69c597f9c37bfeeeeeb33da3530379845f10be461a66d16d03eca2ded77" + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "simd-adler32" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" + +[[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 = "soapysdr" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7497ace07aab956a89bc84c74478879ae099be8e061b59d8f80bfeacec3d9bda" +dependencies = [ + "log", + "num-complex", + "soapysdr-sys", +] + +[[package]] +name = "soapysdr-sys" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb5e50f86d0bf0c3312b77fce8737f760ce30adfa22baae97ffdd66a939356b" +dependencies = [ + "bindgen 0.66.1", + "cc", + "pkg-config", +] + +[[package]] +name = "socket2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "socket2" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "strength_reduce" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe895eb47f22e2ddd4dabc02bce419d2e643c8e3b585c78158b349195bc24d82" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.4.2", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + +[[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 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]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "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 = "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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67dee974fe86fd92cc45b7a95fdd2f99a36a6d7b0d431a231178d3d670bbcc6" +dependencies = [ + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2 0.6.3", + "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-serial" +version = "5.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa1d5427f11ba7c5e6384521cfd76f2d64572ff29f3f4f7aa0f496282923fdc8" +dependencies = [ + "cfg-if", + "futures", + "log", + "mio-serial", + "serialport", + "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", + "tokio-util", +] + +[[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 = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime 0.6.11", + "toml_edit 0.22.27", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +dependencies = [ + "serde", +] + +[[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_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime 0.6.11", + "toml_write", + "winnow 0.7.15", +] + +[[package]] +name = "toml_edit" +version = "0.25.11+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b" +dependencies = [ + "indexmap", + "toml_datetime 1.1.1+spec-1.1.0", + "toml_parser", + "winnow 1.0.1", +] + +[[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 1.0.1", +] + +[[package]] +name = "toml_write" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" + +[[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.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +dependencies = [ + "bitflags 2.11.1", + "bytes", + "futures-util", + "http 1.4.0", + "http-body", + "iri-string", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +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 = [ + "nu-ansi-term", + "sharded-slab", + "smallvec", + "thread_local", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "transpose" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad61aed86bc3faea4300c7aee358b4c6d0c8d6ccc36524c96e4c92ccf26e77e" +dependencies = [ + "num-integer", + "strength_reduce", +] + +[[package]] +name = "trx-ais" +version = "0.1.0" +dependencies = [ + "trx-core", +] + +[[package]] +name = "trx-app" +version = "0.1.0" +dependencies = [ + "dirs", + "serde", + "thiserror 2.0.18", + "toml", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "trx-aprs" +version = "0.1.0" +dependencies = [ + "trx-core", +] + +[[package]] +name = "trx-backend" +version = "0.1.0" +dependencies = [ + "serde", + "serde_json", + "tokio", + "tokio-serial", + "tracing", + "trx-backend-ft450d", + "trx-backend-ft817", + "trx-backend-soapysdr", + "trx-core", + "uuid", +] + +[[package]] +name = "trx-backend-ft450d" +version = "0.1.0" +dependencies = [ + "serde", + "tokio", + "tokio-serial", + "tracing", + "trx-core", +] + +[[package]] +name = "trx-backend-ft817" +version = "0.1.0" +dependencies = [ + "serde", + "tokio", + "tokio-serial", + "tracing", + "trx-core", +] + +[[package]] +name = "trx-backend-soapysdr" +version = "0.1.0" +dependencies = [ + "num-complex", + "rustfft", + "serde", + "soapysdr", + "tokio", + "tracing", + "trx-core", + "trx-rds", + "uuid", +] + +[[package]] +name = "trx-client" +version = "0.1.0" +dependencies = [ + "bytes", + "clap", + "cpal", + "dirs", + "flate2", + "opus", + "serde", + "serde_json", + "tokio", + "toml", + "tracing", + "trx-app", + "trx-core", + "trx-frontend", + "trx-frontend-http", + "trx-frontend-http-json", + "trx-frontend-rigctl", + "trx-protocol", + "uuid", +] + +[[package]] +name = "trx-configurator" +version = "0.1.0" +dependencies = [ + "clap", + "dialoguer", + "tempfile", + "tokio-serial", + "toml_edit 0.22.27", +] + +[[package]] +name = "trx-core" +version = "0.1.0" +dependencies = [ + "flate2", + "reqwest", + "serde", + "serde_json", + "sgp4", + "tokio", + "tracing", + "uuid", +] + +[[package]] +name = "trx-cw" +version = "0.1.0" +dependencies = [ + "trx-core", +] + +[[package]] +name = "trx-decode-log" +version = "0.1.0" +dependencies = [ + "chrono", + "dirs", + "serde", + "serde_json", + "tempfile", + "tracing", + "trx-core", +] + +[[package]] +name = "trx-frontend" +version = "0.1.0" +dependencies = [ + "bytes", + "serde_json", + "tokio", + "trx-core", + "trx-protocol", + "uuid", +] + +[[package]] +name = "trx-frontend-http" +version = "0.1.0" +dependencies = [ + "actix-web", + "actix-ws", + "base64", + "brotli 7.0.0", + "bytes", + "dirs", + "flate2", + "futures-util", + "hex", + "pickledb", + "rand 0.8.6", + "serde", + "serde_json", + "tokio", + "tokio-stream", + "tracing", + "trx-core", + "trx-frontend", + "trx-protocol", + "uuid", +] + +[[package]] +name = "trx-frontend-http-json" +version = "0.1.0" +dependencies = [ + "serde_json", + "tokio", + "tracing", + "trx-core", + "trx-frontend", + "trx-protocol", +] + +[[package]] +name = "trx-frontend-rigctl" +version = "0.1.0" +dependencies = [ + "tokio", + "tracing", + "trx-core", + "trx-frontend", + "trx-protocol", +] + +[[package]] +name = "trx-ftx" +version = "0.1.0" +dependencies = [ + "hound", + "num-complex", + "realfft", + "rustfft", +] + +[[package]] +name = "trx-protocol" +version = "0.1.0" +dependencies = [ + "serde", + "serde_json", + "trx-core", +] + +[[package]] +name = "trx-rds" +version = "0.1.0" +dependencies = [ + "rustfft", + "trx-core", +] + +[[package]] +name = "trx-reporting" +version = "0.1.0" +dependencies = [ + "serde", + "tokio", + "tracing", + "trx-core", +] + +[[package]] +name = "trx-server" +version = "0.1.0" +dependencies = [ + "base64", + "bytes", + "chrono", + "clap", + "cpal", + "dirs", + "flate2", + "num-complex", + "opus", + "pickledb", + "serde", + "serde_json", + "tokio", + "tokio-serial", + "toml", + "tracing", + "trx-ais", + "trx-app", + "trx-aprs", + "trx-backend", + "trx-core", + "trx-cw", + "trx-decode-log", + "trx-ftx", + "trx-protocol", + "trx-reporting", + "trx-vdes", + "trx-wefax", + "trx-wspr", + "trx-wxsat", + "uuid", +] + +[[package]] +name = "trx-vdes" +version = "0.1.0" +dependencies = [ + "num-complex", + "trx-core", +] + +[[package]] +name = "trx-wefax" +version = "0.1.0" +dependencies = [ + "base64", + "png", + "tracing", + "trx-core", +] + +[[package]] +name = "trx-wspr" +version = "0.1.0" + +[[package]] +name = "trx-wxsat" +version = "0.1.0" +dependencies = [ + "image", + "num-complex", + "rustfft", + "trx-core", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "typenum" +version = "1.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" + +[[package]] +name = "unescaper" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4064ed685c487dbc25bd3f0e9548f2e34bab9d18cefc700f9ec2dba74ba1138e" +dependencies = [ + "thiserror 2.0.18", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-segmentation" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" + +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + +[[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 = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[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", + "serde_core", + "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.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf938a0bacb0469e83c1e148908bd7d5a6010354cf4fb73279b7447422e3a89" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f371d383f2fb139252e0bfac3b81b265689bf45b6874af544ffa4c975ac1ebf8" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eeff24f84126c0ec2db7a449f0c2ec963c6a49efe0698c4242929da037ca28ed" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d08065faf983b2b80a79fd87d8254c409281cf7de75fc4b773019824196c904" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd04d9e306f1907bd13c6361b5c6bfc7b3b3c095ed3f8a9246390f8dbdee129" +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 2.11.1", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + +[[package]] +name = "web-sys" +version = "0.3.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f2dfbb17949fa2088e5d39408c48368947b86f7834484e87b73de55bc14d97d" +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-roots" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52f5ee44c96cf55f1b349600768e3ece3a8f26010c05265ab73f945bb1a2eb9d" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.54.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9252e5725dbed82865af151df558e754e4a3c2c30818359eb17465f1346a1b49" +dependencies = [ + "windows-core 0.54.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-core" +version = "0.54.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12661b9c89351d684a50a8a643ce5f608e20243b9fb84687800163429f161d65" +dependencies = [ + "windows-result 0.1.2", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result 0.4.1", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-result" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" +dependencies = [ + "windows-targets 0.52.6", +] + +[[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.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-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.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.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.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[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.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[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.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[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.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[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.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[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.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[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.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[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 = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" +dependencies = [ + "memchr", +] + +[[package]] +name = "winnow" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09dac053f1cd375980747450bfc7250c264eaae0583872e845c0c7cd578872b5" +dependencies = [ + "memchr", +] + +[[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 2.11.1", + "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.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df" +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" + +[[package]] +name = "zstd" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.16+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/Cargo.lock.license b/Cargo.lock.license new file mode 100644 index 0000000..bd55892 --- /dev/null +++ b/Cargo.lock.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2026 Stan Grams + +SPDX-License-Identifier: GPL-2.0-or-later diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..d39ad9d --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,48 @@ +# SPDX-FileCopyrightText: 2026 Stan Grams +# +# SPDX-License-Identifier: GPL-2.0-or-later + +[workspace] +members = [ + "src/decoders/trx-ais", + "src/decoders/trx-wxsat", + "src/decoders/trx-aprs", + "src/decoders/trx-cw", + "src/decoders/trx-decode-log", + "src/decoders/trx-ftx", + "src/decoders/trx-rds", + "src/decoders/trx-vdes", + "src/decoders/trx-wefax", + "src/decoders/trx-wspr", + "src/trx-core", + "src/trx-protocol", + "src/trx-app", + "src/trx-reporting", + "src/trx-server", + "src/trx-server/trx-backend", + "src/trx-server/trx-backend/trx-backend-ft817", + "src/trx-server/trx-backend/trx-backend-ft450d", + "src/trx-server/trx-backend/trx-backend-soapysdr", + "src/trx-client", + "src/trx-client/trx-frontend", + "src/trx-client/trx-frontend/trx-frontend-http", + "src/trx-client/trx-frontend/trx-frontend-http-json", + "src/trx-client/trx-frontend/trx-frontend-rigctl", + "src/trx-configurator", +] +resolver = "2" + +[workspace.package] +version = "0.1.0" + +[workspace.dependencies] +flate2 = "1" +tokio = "1" +uuid = { version = "1", features = ["v4", "serde"] } +tokio-serial = "5" +serde = "1" +serde_json = "1" +toml = "0.8" +tracing = "0.1" +tracing-subscriber = "0.3" +clap = "4" diff --git a/LICENSES/BSD-2-Clause.txt b/LICENSES/BSD-2-Clause.txt new file mode 100644 index 0000000..cbe01ea --- /dev/null +++ b/LICENSES/BSD-2-Clause.txt @@ -0,0 +1,9 @@ +Copyright (c) 2025 Stan Grams + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/LICENSES/GPL-2.0-or-later.txt b/LICENSES/GPL-2.0-or-later.txt new file mode 100644 index 0000000..9efa6fb --- /dev/null +++ b/LICENSES/GPL-2.0-or-later.txt @@ -0,0 +1,338 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, see . + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Moe Ghoul, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..142433e --- /dev/null +++ b/README.md @@ -0,0 +1,147 @@ +
+ trx-rs logo + +# trx-rs + +A modular amateur radio control stack written in Rust. + +[![License](https://img.shields.io/badge/license-GPL--2.0--or--later-blue.svg)](LICENSES) + +
+ +`trx-rs` splits radio hardware access from user-facing interfaces so you can run +rig control, SDR DSP, decoding, audio streaming, and web access as separate, +composable pieces. + +| | | +|---|---| +| **Backends** | Yaesu FT-817, Yaesu FT-450D, SoapySDR | +| **Frontends** | Web UI, rigctl-compatible TCP, JSON-over-TCP | +| **Decoders** | AIS, APRS, CW, FT8, RDS, VDES, WSPR | +| **Audio** | Opus streaming between server, client, and browser | + +## Quick Start + +### 1. Install dependencies + +
+Debian / Ubuntu + +```bash +sudo apt install build-essential pkg-config cmake libopus-dev libasound2-dev +# Optional — SDR support +sudo apt install libsoapysdr-dev +``` +
+ +
+Fedora + +```bash +sudo dnf install gcc pkg-config cmake opus-devel alsa-lib-devel +# Optional — SDR support +sudo dnf install SoapySDR-devel +``` +
+ +
+Arch Linux + +```bash +sudo pacman -S base-devel pkgconf cmake opus alsa-lib +# Optional — SDR support +sudo pacman -S soapysdr +``` +
+ +
+macOS (Homebrew) + +```bash +brew install cmake opus +# Optional — SDR support +brew install soapysdr +``` +
+ +See [Build Requirements](https://github.com/sgrams/trx-rs/wiki/User-Manual#build-requirements) +in the wiki for details on each library. + +> **Note:** `cmake` is required even when a system Opus library is installed. +> The `audiopus_sys` crate probes for Opus via `pkg-config`; if it is not found +> (or `pkg-config` is unavailable), it falls back to compiling a vendored copy +> of Opus with CMake. A missing `cmake` therefore fails the build with +> `is cmake not installed?` rather than a missing-Opus error. + +### 2. Build + +```bash +cargo build --release +``` + +Build without SDR support: `cargo build --release --no-default-features` + +### 3. Configure + +Run the interactive setup wizard to generate config files for your station: + +```bash +./target/release/trx-configurator +``` + +The wizard walks you through rig selection, serial port detection, audio +settings, and frontend options, then writes `trx-server.toml` and +`trx-client.toml`. + +Alternatively, generate example configs and edit them by hand: + +```bash +./target/release/trx-server --print-config > trx-server.toml +./target/release/trx-client --print-config > trx-client.toml +``` + +### 4. Run + +```bash +./target/release/trx-server --config trx-server.toml +./target/release/trx-client --config trx-client.toml +``` + +Open the configured HTTP frontend address in a browser (default `http://localhost:8080`). + +## How It Works + +```mermaid +graph TD + SDR1["SDR #1"] & SDR2["SDR #2"] <-->|USB| S1["trx-server A"] + SDR3["SDR #3"] & FT817["FT-817"] <-->|USB / serial| S2["trx-server B"] + + S1 <-->|"JSON-TCP :4530"| C1["trx-client"] + S1 -->|"Opus-TCP per rig"| C1 + S2 <-->|"JSON-TCP :4530"| C1 + S2 -->|"Opus-TCP per rig"| C1 + + C1 <-->|internal channels| F1["Web UI :8080"] + C1 <-->|internal channels| F2["rigctl :4532"] +``` + +Each `trx-server` owns one or more rigs and runs DSP, decoding, and audio capture locally. +A `trx-client` connects to any number of servers over TCP and exposes them through +a unified set of frontends. + +## Documentation + +| Resource | Description | +|----------|-------------| +| [User Manual](https://github.com/sgrams/trx-rs/wiki/User-Manual) | Configuration, features, and usage | +| [Architecture](https://github.com/sgrams/trx-rs/wiki/Architecture) | System design, crate layout, data flow, and internals | +| [Optimization Guidelines](https://github.com/sgrams/trx-rs/wiki/Optimization-Guidelines) | Performance guidelines for the real-time DSP pipeline | +| [Planned Features](https://github.com/sgrams/trx-rs/wiki/Planned-Features) | Roadmap and design notes | +| [Contributing](CONTRIBUTING.md) | Commit conventions, workflow, and code style | + +## License + +GPL-2.0-or-later. See [`LICENSES`](LICENSES) for the full license text and +bundled third-party license files. Bundled third-party components (Leaflet and +the Leaflet AIS tracksymbol plugin under `assets/web/vendor/`) retain their +original BSD-2-Clause license. diff --git a/assets/trx-logo.png b/assets/trx-logo.png new file mode 100644 index 0000000..0109d7f Binary files /dev/null and b/assets/trx-logo.png differ diff --git a/docs/Architecture.md b/docs/Architecture.md new file mode 100644 index 0000000..82d7f0b --- /dev/null +++ b/docs/Architecture.md @@ -0,0 +1,1087 @@ +# trx-rs Architecture + +## Table of Contents + +1. [Project Purpose](#project-purpose) +2. [Technology Stack](#technology-stack) +3. [High-Level Architecture](#high-level-architecture) +4. [Crate Layout](#crate-layout) +5. [Core Library (trx-core)](#core-library-trx-core) +6. [Protocol Layer (trx-protocol)](#protocol-layer-trx-protocol) +7. [Server (trx-server)](#server-trx-server) +8. [Backend Abstraction (trx-backend)](#backend-abstraction-trx-backend) +9. [Client (trx-client)](#client-trx-client) +10. [Frontend System (trx-frontend)](#frontend-system-trx-frontend) +11. [Signal Decoders](#signal-decoders) +12. [DSP & Spectrum Pipeline](#dsp--spectrum-pipeline) +13. [Plugin System](#plugin-system) +14. [Configuration](#configuration) +15. [Concurrency Model](#concurrency-model) +16. [Authentication & Security](#authentication--security) +17. [Data Flow Diagrams](#data-flow-diagrams) + +--- + +## Project Purpose + +**trx-rs** is a modular amateur radio transceiver control daemon written in Rust. It separates radio hardware access (server) from user-facing control interfaces (client), enabling: + +- **Remote control** of transceivers over TCP networks +- **Multi-rig operation** with per-rig isolation and routing +- **SDR integration** with real-time DSP (demodulation, spectrum, decode) +- **Pluggable backends** for different radio hardware +- **Multiple frontends** — web UI, Hamlib-compatible rigctl, JSON-over-TCP +- **Signal decoding** — APRS, CW, FT8, WSPR, RDS — with live streaming and logging +- **Uplinks** — PSKReporter, APRS-IS IGate + +Target users are amateur radio operators who want networked, automated, or multi-radio control from a single host or across a LAN. + +--- + +## Technology Stack + +| Layer | Technology | +|-------|-----------| +| Language | Rust (2021 edition) | +| Async runtime | Tokio | +| Web framework | Actix-web (HTTP frontend) | +| Serialization | Serde / JSON | +| Config format | TOML | +| Audio codec | Opus | +| SDR interface | soapysdr crate (wraps SoapySDR C library) | +| CAT serial | tokio-serial | +| CLI | clap | +| Logging | tracing / tracing-subscriber | +| FTx decode | trx-ftx (pure Rust) | + +--- + +## High-Level Architecture + +```mermaid +graph TD + subgraph server["trx-server"] + HW["Radio Hardware"] <-->|"CAT protocol
serial / TCP"| Backend["Rig Backend
(ft817 / ft450d / sdr)"] + Backend --> RigTask["rig_task.rs
(state machine)"] + RigTask --> Listener["listener.rs
(JSON TCP :4530)"] + RigTask --> Audio["audio.rs
(Opus :4531)"] + Audio --> Decoders["Decoders
(APRS, CW, FT8, WSPR, RDS)"] + Decoders --> Uplinks["PSKReporter / APRS-IS"] + end + + subgraph client["trx-client"] + Remote["remote_client.rs
(polls state, routes commands)"] + Remote <-->|"mpsc / watch channels"| HTTP["trx-frontend-http
(Web UI :8080)"] + Remote <-->|"mpsc / watch channels"| Rigctl["trx-frontend-rigctl
(rigctl :4532)"] + Remote <-->|"mpsc / watch channels"| JSON["trx-frontend-http-json
(JSON/TCP)"] + end + + Listener <-->|"JSON TCP :4530"| Remote + Audio -->|"Opus TCP :4531"| Remote + + HTTP & Rigctl & JSON <--> Users["End Users
(Browser / Hamlib / Custom tools)"] +``` + +The server and client are separate binaries. They communicate over **JSON-over-TCP** (control) and **Opus-encoded TCP** (audio). Both binaries can load shared-library plugins at startup. + +--- + +## Crate Layout + +``` +trx-rs/ # Workspace root +├── Cargo.toml # Workspace manifest (shared dependencies) +│ +└── src/ + ├── trx-core/ # Core types, traits, state machine + ├── trx-protocol/ # Client↔server message types, auth, codec + ├── trx-app/ # Shared app helpers (config loading, plugins, logging) + │ + ├── trx-server/ # Server binary + │ ├── src/ + │ │ ├── main.rs + │ │ ├── config.rs + │ │ ├── rig_task.rs # Per-rig polling loop + │ │ ├── listener.rs # JSON TCP server (:4530) + │ │ ├── audio.rs # Opus audio server (:4531) + │ │ ├── pskreporter.rs # PSKReporter uplink + │ │ └── aprsfi.rs # APRS-IS IGate uplink + │ │ + │ └── trx-backend/ # Backend abstraction + factory + │ ├── src/lib.rs # RegistrationContext, RigAccess enum + │ ├── trx-backend-ft817/ # Yaesu FT-817 CAT + │ ├── trx-backend-ft450d/ # Yaesu FT-450D CAT + │ └── trx-backend-soapysdr/ # SoapySDR SDR (RX-only) + │ ├── src/ + │ │ ├── lib.rs # SoapySdrRig impl + │ │ ├── real_iq_source.rs + │ │ ├── dsp/ # DSP pipeline, FIR, oscillator, AGC + │ │ ├── demod/ # AM, FM, WFM, SSB, CW demodulators + │ │ └── spectrum.rs # FFT spectrum generation + │ + ├── trx-client/ # Client binary + │ ├── src/ + │ │ ├── main.rs + │ │ ├── config.rs + │ │ ├── remote_client.rs # TCP connection to server + │ │ └── audio_client.rs # Audio stream handler + │ │ + │ └── trx-frontend/ # Frontend abstraction + registration + │ ├── src/lib.rs # FrontendSpawner trait, FrontendRuntimeContext + │ ├── trx-frontend-http/ # Actix-web: REST + SSE + WebSocket + │ ├── trx-frontend-http-json/ # JSON-over-TCP thin control frontend + │ └── trx-frontend-rigctl/ # Hamlib-compatible rigctl TCP (:4532) + │ + └── decoders/ + ├── trx-aprs/ # APRS packet decoder + ├── trx-cw/ # CW / Morse decoder + ├── trx-ftx/ # Pure Rust FTx decoder (FT8/FT4/FT2) + ├── trx-wspr/ # WSPR beacon decoder + ├── trx-rds/ # FM RDS decoder + └── trx-decode-log/ # JSON Lines log rotation for decoded frames +``` + +--- + +## Core Library (trx-core) + +**Path:** `src/trx-core/src/` + +The foundation of the system. All other crates depend on trx-core for shared types and traits. + +### Key Re-exports (`lib.rs`) + +```rust +pub use rig::command::RigCommand; +pub use rig::request::RigRequest; +pub use rig::response::{RigError, RigResult}; +pub use rig::state::{RigMode, RigSnapshot, RigState, RigFilterState, SpectrumData}; +pub use rig::AudioSource; +pub use decode::DecodedMessage; +pub use audio::AudioStreamInfo; +``` + +### Rig State (`rig/state.rs`) + +The `RigState` struct is the canonical snapshot of a rig at any point in time: + +```rust +pub struct RigState { + pub rig_info: Option, + pub status: RigStatus, + pub initialized: bool, + pub control: RigControl, + pub server_callsign: Option, + pub spectrum: Option, // FFT frame from SDR + pub filter: Option, // Runtime DSP parameters + // ... decoder enable flags, CW params, etc. +} + +pub struct RigStatus { + pub freq: Freq, + pub mode: RigMode, + pub tx_en: bool, + pub vfo: Option, + pub tx: Option, // power, SWR, ALC + pub rx: Option, // signal strength + pub lock: Option, +} + +pub enum RigMode { + LSB, USB, CW, CWR, AM, WFM, FM, DIG, PKT, Other(String) +} +``` + +### Rig Commands (`rig/command.rs`) + +All control actions are represented as enum variants: + +```rust +pub enum RigCommand { + // Basic control + GetSnapshot, SetFreq(Freq), SetMode(RigMode), SetPtt(bool), + PowerOn, PowerOff, ToggleVfo, Lock, Unlock, + // TX + GetTxLimit, SetTxLimit(u8), + // Decoders + SetAprsDecodeEnabled(bool), SetCwDecodeEnabled(bool), + SetFt8DecodeEnabled(bool), SetWsprDecodeEnabled(bool), + ResetAprsDecoder, ResetCwDecoder, ResetFt8Decoder, ResetWsprDecoder, + // CW keyer + SetCwAuto(bool), SetCwWpm(u32), SetCwToneHz(u32), + // SDR DSP + SetBandwidth(u32), SetFirTaps(u32), SetSdrGain(f64), + SetCenterFreq(Freq), GetSpectrum, + // WFM + SetWfmDeemphasis(u32), SetWfmStereo(bool), SetWfmDenoise(bool), +} +``` + +### State Machine (`rig/controller/machine.rs`) + +Manages the lifecycle of a rig connection: + +``` +Disconnected → Connecting → Initializing → PoweredOff + ↘ + Ready ⇄ Transmitting + ↓ + Error + ↓ (recoverable) + Connecting +``` + +```rust +pub enum RigMachineState { + Disconnected, + Connecting, + Initializing, + PoweredOff, + Ready, + Transmitting, + Error(RigStateError), +} +``` + +Transitions are triggered by `RigEvent` (Connected, PoweredOn, PttOn, Error, etc.) and processed by `process_event(&mut self, event: RigEvent)`. + +### Command Handlers (`rig/controller/handlers.rs`) + +Each command implements `RigCommandHandler`: + +```rust +pub trait RigCommandHandler: Debug + Send + Sync { + fn name(&self) -> &'static str; + fn can_execute(&self, ctx: &dyn CommandContext) -> ValidationResult; + fn execute<'a>( + &'a self, executor: &'a mut dyn CommandExecutor + ) -> Pin> + Send + 'a>>; +} + +pub enum ValidationResult { + Ok, + InvalidState(String), // Wrong machine state + InvalidParams(String), // Bad parameters + Locked, // Rig is locked +} +``` + +### Event System (`rig/controller/events.rs`) + +Observers subscribe via the `RigListener` trait. `RigEventEmitter` maintains a list of `Arc` and calls them on state changes. + +```rust +pub trait RigListener: Send + Sync { + fn on_frequency_change(&self, old: Option, new: Freq) {} + fn on_mode_change(&self, old: Option<&RigMode>, new: &RigMode) {} + fn on_ptt_change(&self, transmitting: bool) {} + fn on_state_change(&self, old: &RigMachineState, new: &RigMachineState) {} + fn on_meter_update(&self, rx: Option<&RigRxStatus>, tx: Option<&RigTxStatus>) {} + fn on_lock_change(&self, locked: bool) {} + fn on_power_change(&self, powered: bool) {} +} +``` + +### Operational Policies (`rig/controller/policies.rs`) + +Govern reconnection and polling behaviour: + +```rust +pub trait RetryPolicy: Send { + fn next_delay(&mut self) -> Duration; +} + +pub struct ExponentialBackoff { + max_attempts: u32, + base_delay: Duration, + max_delay: Duration, + // Delays include ±25% randomized jitter to prevent thundering herd +} + +pub trait PollingPolicy: Send { + fn next_interval(&mut self) -> Duration; +} + +pub struct AdaptivePolling { + idle_interval: Duration, + tx_interval: Duration, // faster polling during TX +} +``` + +### Audio Wire Format (`audio.rs`) + +``` +[ 1 byte type ][ 4 bytes BE length ][ N bytes payload ] + +Types: + 0x00 AudioStreamInfo (sample rate, channels, frame duration) + 0x01 RX audio frame (Opus-encoded PCM) + 0x02 TX audio frame (Opus-encoded PCM) + 0x03 APRS decode + 0x04 CW decode + 0x05 FT8 decode + 0x06 WSPR decode +``` + +### Error Types (`rig/response.rs`) + +```rust +pub struct RigError { + pub message: String, + pub kind: RigErrorKind, +} + +pub enum RigErrorKind { + Transient, // Retry-able (timeout, busy) + Permanent, // Don't retry (unsupported operation) +} + +pub type RigResult = Result; +pub type DynResult = Result>; +``` + +--- + +## Protocol Layer (trx-protocol) + +**Path:** `src/trx-protocol/src/` + +Bridges the internal `RigCommand`/`RigState` world to JSON messages exchanged over TCP. + +### Message Types (`types.rs`) + +```rust +// Client → Server +pub struct ClientEnvelope { + pub token: Option, // Auth token + pub rig_id: Option, // Multi-rig routing (None = default rig) + pub cmd: ClientCommand, +} + +pub enum ClientCommand { + GetState, GetRigs, + SetFreq { freq_hz: u64 }, SetCenterFreq { freq_hz: u64 }, + SetMode { mode: String }, SetPtt { ptt: bool }, + PowerOn, PowerOff, ToggleVfo, Lock, Unlock, + GetTxLimit, SetTxLimit { limit: u8 }, + SetBandwidth { bandwidth_hz: u32 }, SetFirTaps { taps: u32 }, + SetSdrGain { gain_db: f64 }, + SetWfmDeemphasis { deemphasis_us: u32 }, + SetWfmStereo { enabled: bool }, SetWfmDenoise { enabled: bool }, + SetAprsDecodeEnabled { enabled: bool }, /* ... other decoders ... */ + GetSpectrum, + // ... +} + +// Server → Client +pub struct ClientResponse { + pub success: bool, + pub rig_id: Option, + pub state: Option, // Updated rig state + pub rigs: Option>, // Response to GetRigs + pub error: Option, +} + +pub struct RigEntry { + pub rig_id: String, + pub display_name: Option, + pub state: RigSnapshot, + pub audio_port: Option, +} +``` + +### Type Mapping (`mapping.rs`) + +`client_command_to_rig(ClientCommand) → RigCommand` and the reverse conversion ensure the protocol types stay decoupled from the core domain model. + +### Authentication (`auth.rs`) + +```rust +pub trait TokenValidator: Send + Sync { + fn validate(&self, token: &str) -> bool; +} + +pub struct SimpleTokenValidator { tokens: HashSet } +pub struct NoAuthValidator; // Always returns true (debug/local use) +``` + +--- + +## Server (trx-server) + +**Path:** `src/trx-server/src/` + +### Startup Sequence + +1. Parse CLI / TOML config (`config.rs`) +2. Register backends via `RegistrationContext` (built-ins + plugins) +3. For each configured rig: + - Build or pre-configure the rig backend + - Spawn `run_rig_task()` as a Tokio task +4. Spawn `run_listener()` (JSON TCP on `:4530`) +5. Spawn audio streaming server (`:4531`) +6. Wait for shutdown signal + +### Multi-Rig Routing + +Rigs are stored in `Arc>`. Each `RigHandle` contains: +- `mpsc::Sender` — send commands to the rig task +- `watch::Receiver` — read latest state + +`listener.rs` routes incoming `ClientEnvelope.rig_id` to the correct handle. If `rig_id` is absent, the server's default rig is used. + +Auto-generated IDs follow the pattern `{model}_{index}` (e.g., `ft817_0`, `soapysdr_1`) when not explicitly set in config. + +### Rig Task (`rig_task.rs`) + +Each rig runs an independent async loop: + +``` +connect → initialize → poll loop + ↓ on error + retry with ExponentialBackoff + ↓ on persistent error + Error state → wait for recovery +``` + +The task: +- Drives the `RigStateMachine` through state transitions +- Polls rig status at `AdaptivePolling` intervals (faster during TX) +- Handles incoming `RigCommand`s from `mpsc::Receiver` +- Broadcasts `RigState` snapshots via `watch::Sender` + +### JSON TCP Listener (`listener.rs`) + +Accepts connections on port 4530. Per connection: +1. Read newline-delimited JSON (`ClientEnvelope`) +2. Validate token +3. Route to rig by `rig_id` +4. Convert `ClientCommand → RigCommand` and send to rig task +5. Await result and return `ClientResponse` + +### Audio Server (`audio.rs`) + +Separate TCP listener on port 4531. Per connection: +1. Send `AudioStreamInfo` header +2. Send buffered decoder history (APRS, CW, FT8, WSPR, RDS frames) +3. Stream Opus-encoded RX audio frames as they arrive +4. Interleave decoder messages (`0x03`–`0x06` frame types) + +`DecoderHistories` maintains ring buffers of recent decoded events so late-connecting clients get context. + +### Uplinks + +| Module | Purpose | +|--------|---------| +| `pskreporter.rs` | Posts FT8/WSPR spots to pskreporter.net | +| `aprsfi.rs` | Forwards APRS packets to APRS-IS network (IGate) | + +Both are optional, configured per-rig. + +--- + +## Backend Abstraction (trx-backend) + +**Path:** `src/trx-server/trx-backend/` + +### Factory Pattern (`src/lib.rs`) + +```rust +pub enum RigAccess { + Serial { path: String, baud: u32 }, + Tcp { addr: String }, + Sdr { args: String }, +} + +type BackendFactory = fn(RigAccess) -> DynResult>; + +pub struct RegistrationContext { + factories: HashMap, +} + +impl RegistrationContext { + pub fn register_backend(&mut self, name: &str, factory: BackendFactory); + pub fn build_rig(&self, name: &str, access: RigAccess) -> DynResult>; +} +``` + +Built-in registrations (via `register_builtin_backends_on`): +- `"ft817"` → `Ft817::new` +- `"ft450d"` → `Ft450d::new` +- `"soapysdr"` → `SoapySdrRig::new_from_config(SoapySdrConfig { ... })` (if `soapysdr` feature enabled) + +### RigCat Trait (from trx-core) + +All backends implement `RigCat`: + +```rust +pub trait RigCat: Rig { + async fn get_status(&mut self) -> RigResult; + async fn set_freq(&mut self, freq: Freq) -> RigResult<()>; + async fn set_mode(&mut self, mode: RigMode) -> RigResult<()>; + async fn set_ptt(&mut self, on: bool) -> RigResult<()>; + async fn power_on(&mut self) -> RigResult<()>; + async fn power_off(&mut self) -> RigResult<()>; + async fn toggle_vfo(&mut self) -> RigResult<()>; + // ... more operations +} +``` + +### FT-817 Backend (`trx-backend-ft817/`) + +- CAT protocol over serial (9600 baud default) +- BCD-encoded frequency/mode commands +- VFO A/B tracking +- Meter reads: S-meter, TX power, SWR, ALC +- Bands: 160m through 70cm + GHz receive + +### FT-450D Backend (`trx-backend-ft450d/`) + +- Similar structure to FT-817 +- Uses FT-450D-specific CAT command set + +### SoapySDR Backend (`trx-backend-soapysdr/`) + +RX-only SDR backend with real-time DSP: + +```rust +pub struct SoapySdrRig { + freq: Freq, + mode: RigMode, + pipeline: dsp::SdrPipeline, // Multi-channel DSP + bandwidth_hz: u32, + fir_taps: u32, + spectrum_buf: Arc>>>, + center_offset_hz: i64, + wfm_deemphasis_us: u32, + wfm_stereo: bool, + wfm_denoise: bool, + gain_db: f64, +} +``` + +--- + +## Client (trx-client) + +**Path:** `src/trx-client/src/` + +### Startup Sequence + +1. Parse CLI / TOML config +2. Register frontends via `FrontendRegistrationContext` (built-ins + plugins) +3. Spawn `run_remote_client()` — connects to server, drives `watch::Sender` +4. Spawn enabled frontends (HTTP, rigctl, http-json) +5. Wait for shutdown + +### Remote Client (`remote_client.rs`) + +Maintains the server TCP connection: + +```rust +pub struct RemoteClientConfig { + pub addr: String, + pub token: Option, + pub selected_rig_id: Arc>>, + pub known_rigs: Arc>>, + pub poll_interval: Duration, + pub spectrum: Arc>, +} +``` + +Workflow: +1. Connect to `addr` (host:4530) +2. Poll `GetState` at configured interval (default 750 ms) +3. Poll `GetSpectrum` at ~40 ms (25 fps) if backend supports it +4. Forward commands from frontends (`mpsc::Receiver`) to server +5. Broadcast received `RigState` to all frontends via `watch::Sender` + +Multi-rig: `selected_rig_id` can be changed at runtime to switch which rig the client targets. `known_rigs` is populated by periodic `GetRigs` calls. + +### Audio Client (`audio_client.rs`) + +Connects to the audio port (`:4531`) and relays: +- Opus-encoded audio frames → local PCM broadcast channel +- Decoder messages → frontend display + +--- + +## Frontend System (trx-frontend) + +**Path:** `src/trx-client/trx-frontend/` + +### Abstraction (`src/lib.rs`) + +```rust +pub trait FrontendSpawner { + fn spawn_frontend( + state_rx: watch::Receiver, + rig_tx: mpsc::Sender, + callsign: Option, + listen_addr: SocketAddr, + context: Arc, + ) -> JoinHandle<()>; +} + +pub struct FrontendRuntimeContext { + pub rigctl_clients: AtomicUsize, + pub rigctl_addr: Option, + pub http_clients: AtomicUsize, + pub known_rigs: Arc>>, + pub selected_rig_id: Arc>>, + pub spectrum: Arc>, +} +``` + +### HTTP Frontend (`trx-frontend-http/`) + +Built on **Actix-web**, serves a browser-based control panel. + +**REST Endpoints:** + +| Method | Path | Description | +|--------|------|-------------| +| GET | `/status` | Current rig state + frontend metadata | +| POST | `/cmd/{command}` | Execute a rig command | +| GET | `/events` | SSE stream of state changes | +| GET | `/audio` | WebSocket audio stream | +| GET | `/favicon.png` | Static asset | + +**Web UI features:** frequency display/entry, mode selector, PTT indicator, S-meter/TX-power/SWR meters, decoder toggles, decode history, spectrum waterfall (SDR), rig picker (multi-rig). + +### Rigctl Frontend (`trx-frontend-rigctl/`) + +Hamlib-compatible plaintext TCP interface on port 4532. Allows WSJT-X, JS8Call, and other Hamlib-aware applications to control the rig without modification. + +### HTTP-JSON Frontend (`trx-frontend-http-json/`) + +JSON-over-TCP frontend on an ephemeral (or configured) port. Thin wrapper that passes `ClientCommand`/`ClientResponse` pairs — useful for scripting or automation tools. + +--- + +## Signal Decoders + +**Path:** `src/decoders/` + +All decoders run as background Tokio tasks inside `trx-server`. They subscribe to the PCM audio broadcast channel from the active rig and publish decoded messages. + +| Crate | Decoder | Notes | +|-------|---------|-------| +| `trx-aprs` | APRS (AX.25) | Forwards to APRS-IS if enabled | +| `trx-cw` | CW / Morse | Auto WPM detection | +| `trx-ftx` | FTx | Pure Rust FT8/FT4/FT2 decoder; posts to PSKReporter | +| `trx-wspr` | WSPR beacons | Posts to PSKReporter | +| `trx-rds` | FM RDS | Station name, radiotext, time | +| `trx-decode-log` | Logging infrastructure | JSON Lines, date-rotated files | + +Control commands (e.g., `SetAprsDecodeEnabled(bool)`, `ResetCwDecoder`) are routed through `rig_task.rs` to the active decoder tasks. + +Decoded events are multiplexed onto the audio stream wire protocol (`0x03`–`0x06` frame types) and also buffered in `DecoderHistories` for replay to newly connected clients. + +--- + +## DSP & Spectrum Pipeline + +**Path:** `src/trx-server/trx-backend/trx-backend-soapysdr/src/` + +### Architecture + +``` +IQ Samples (from SoapySDR device) + ↓ +SdrPipeline (per-channel) + ├── Channel 0: Mixer → FIR Filter → Demod → AGC → PCM + ├── Channel 1: Mixer → FIR Filter → Demod → AGC → PCM + └── ... + ↓ +Audio broadcast channel (Vec) + ↓ +Decoders / Audio server +``` + +### Demodulators (`demod/`) + +| Module | Mode | +|--------|------| +| `am.rs` | AM (envelope detection) | +| `fm.rs` | Narrowband FM | +| `wfm.rs` | Wideband FM (stereo + deemphasis + denoise) | +| `ssb.rs` | LSB and USB | +| `cw.rs` | CW (Morse, beat-frequency oscillator) | + +WFM demodulator supports: +- Stereo pilot detection and L+R/L−R matrix decoding +- Configurable de-emphasis time constant (50 us EU / 75 us US) +- Optional noise reduction + +### Spectrum (`spectrum.rs`) + +Real-time FFT of the mixer output is stored in `spectrum_buf` and snapshotted on demand: + +```rust +pub struct SpectrumData { + pub magnitudes: Vec, // FFT magnitude bins (linear) + pub low_hz: f64, + pub high_hz: f64, + pub center_hz: f64, +} +``` + +Clients poll via `RigCommand::GetSpectrum` → `ClientCommand::GetSpectrum`. The remote client polls at ~25 fps and caches in `SharedSpectrum`. The HTTP frontend reads this cache to drive the waterfall display. + +--- + +## Plugin System + +**Path:** `src/trx-app/src/plugins.rs` + +Both `trx-server` and `trx-client` support dynamic plugins loaded at startup. + +### Search Paths (in order) + +1. `./plugins/` +2. `~/.config/trx-rs/plugins/` +3. Directories in `TRX_PLUGIN_DIRS` environment variable (`:` on Unix, `;` on Windows) + +### Backend Plugins + +Export symbol: `trx_register_backend(context: *mut RegistrationContext)` + +Plugins call `context.register_backend("my-rig", factory_fn)` to add new rig drivers without rebuilding the server binary. + +### Frontend Plugins + +Export symbol: `trx_register_frontend(context: *mut FrontendRegistrationContext)` + +Plugins call `context.register_frontend("my-ui", spawner_fn)` to add new control interfaces. + +An example plugin is provided at `examples/trx-plugin-example/` (not a workspace member). + +--- + +## Configuration + +**Format:** TOML. Generated with `--print-config` flag. + +**Search order:** +1. `--config ` CLI argument +2. `./trx-server.toml` / `./trx-client.toml` +3. `~/.config/trx-rs/trx-server.toml` +4. `/etc/trx-rs/trx-server.toml` + +### Server Config Structure + +```toml +[general] +callsign = "W5XYZ" +log_level = "info" +latitude = 35.5 +longitude = -97.5 + +[listen] +addr = "127.0.0.1" +port = 4530 +audio_port = 4531 + +[rig] # Legacy single-rig flat config +model = "ft817" +[rig.access] +type = "serial" +path = "/dev/ttyUSB0" +baud = 9600 + +[behavior] +max_retries = 3 +retry_delay_secs = 1 +polling_interval_ms = 250 + +[audio] +sample_rate = 48000 +frame_duration_ms = 20 +dev = "" # CPAL device name (empty = default) + +[sdr] # SoapySDR global params +args = "driver=rtlsdr" +sample_rate = 2000000 +bandwidth_hz = 2000000 +gain_mode = "manual" +gain_db = 25.0 +center_offset_hz = 0 + +[[sdr.channels]] +if_hz = 0 +mode = "USB" +audio_bandwidth_hz = 2800 +fir_taps = 64 + +[pskreporter] +enabled = true +callsign = "W5XYZ" +gridsquare = "EM13AH" + +[aprsfi] +enabled = true +callsign = "W5XYZ-11" + +[decode_logs] +enabled = true +dir = "~/.trx-rs/decode-logs" + +# Multi-rig (takes priority over flat [rig] section) +[[rigs]] +id = "ft817_0" +name = "HF Transceiver" +[rigs.rig] +model = "ft817" +[rigs.rig.access] +type = "serial" +path = "/dev/ttyUSB0" +baud = 9600 + +[[rigs]] +id = "sdr_0" +name = "VHF/UHF SDR" +[rigs.rig] +model = "soapysdr" +[rigs.rig.access] +type = "sdr" +args = "driver=rtlsdr" +``` + +### Client Config Structure + +```toml +[remote] +url = "localhost:4530" +rig_id = "" # Empty = server default rig +poll_interval_ms = 750 + +[remote.auth] +token = "" + +[frontends.http] +enabled = true +listen = "127.0.0.1" +port = 8080 + +[frontends.rigctl] +enabled = true +listen = "127.0.0.1" +port = 4532 + +[frontends.http_json] +enabled = false +port = 0 +``` + +--- + +## Concurrency Model + +The system is built on **Tokio** and uses channels for all cross-task communication: + +| Channel | Type | Purpose | +|---------|------|---------| +| `rig_tx` / `rig_rx` | `mpsc` | Frontend → rig task (commands) | +| `state_tx` / `state_rx` | `watch` | Rig task → frontends (state updates) | +| `audio_tx` / `audio_rx` | `broadcast` | Rig → decoders / audio server (PCM frames) | +| `shutdown_tx` / `shutdown_rx` | `watch` | Main → all tasks (graceful shutdown signal) | + +### Task Tree (server) + +``` +main + ├── rig_task [per rig] — polls hardware, drives state machine + ├── listener — accepts JSON TCP connections + │ └── per-connection task — reads commands, sends responses + ├── audio_server — accepts audio TCP connections + │ └── per-connection task — streams Opus frames + ├── decoder tasks — APRS, CW, FT8, WSPR, RDS + ├── pskreporter — uplink task + └── aprsfi — uplink task +``` + +### Task Tree (client) + +``` +main + ├── remote_client — polls server, maintains state_tx + ├── audio_client — streams audio from server + ├── http_frontend — Actix-web server + ├── rigctl_frontend — Hamlib TCP server + └── http_json_frontend — JSON-over-TCP server +``` + +--- + +## Authentication & Security + +### Token-Based Auth (JSON TCP) + +- Clients include `token` in every `ClientEnvelope` +- Server validates via `TokenValidator` trait +- `SimpleTokenValidator` — `HashSet` loaded from config +- `NoAuthValidator` — always passes (debug / local-only mode) + +### HTTP Frontend Auth + +- Optional token or HTTP Basic Auth middleware +- Configured in `[frontends.http.auth]` +- Rate limiting supported + +### Transport Security + +No built-in TLS. For remote use, tunnel over SSH or place behind a TLS-terminating reverse proxy (nginx, Caddy, etc.). + +--- + +## Data Flow Diagrams + +### Command Flow (set frequency) + +``` +Browser → POST /cmd/set_freq?hz=14225000 + ↓ trx-frontend-http +RigRequest::Command(RigCommand::SetFreq(14225000)) + ↓ mpsc channel (rig_tx) +remote_client.rs + ↓ TCP +listener.rs (server) + ↓ mpsc channel +rig_task.rs → backend.set_freq(14225000) + ↓ CAT serial / SoapySDR API +Radio hardware + ↑ ACK +rig_task.rs updates RigState → watch::Sender + ↑ TCP +remote_client.rs receives ClientResponse + ↑ watch::Sender +trx-frontend-http sends SSE event to browser +``` + +### State Update Flow (polling) + +``` +rig_task.rs polls rig_status() every ~250 ms + → RigState updated → watch::Sender +remote_client.rs receives via watch::Receiver + → broadcasts to frontends via watch::Sender +HTTP frontend reads watch::Receiver + → pushes SSE "state" event to connected browsers +``` + +### Spectrum Update Flow + +``` +SoapySdrRig::run_spectrum_snapshot() + → FFT of IQ buffer → SpectrumData stored in Arc> +remote_client.rs polls GetSpectrum every 40 ms + → stores SpectrumData in SharedSpectrum (Arc>) +HTTP frontend reads SharedSpectrum + → renders waterfall in browser via WebSocket or polling +``` + +### Audio Flow + +``` +SoapySDR IQ → DSP pipeline → PCM (Vec) + → broadcast::Sender> + ↙ (decoders subscribe) ↘ (audio server subscribes) +APRS/CW/FT8/WSPR/RDS Opus encode +decode tasks ↓ TCP + ↓ audio client (trx-client) +DecoderHistories buffer ↓ + ↓ broadcast locally +listener connections ↓ +stream decoder messages HTTP WebSocket / local speakers +``` + +--- + +## Detailed Component Notes + +### Rig Task Internals (`rig_task.rs` — 1,315 lines) + +The rig task is the heart of the server. Key implementation details: + +- **Command batching**: Accumulates pending requests before processing sequentially in FIFO order. +- **Spectrum deduplication**: Concurrent `GetSpectrum` requests are collapsed — one DSP computation broadcasts to all waiting responders. +- **Adaptive polling**: Poll interval adjusts based on TX state (100ms during TX, 500ms idle). +- **Grace period**: 800ms pause on polling after power-on/off operations to let hardware settle. +- **VFO priming**: Optional initialization sequence that toggles VFO A/B to populate the state cache. +- **Per-rig decoder histories**: Each rig maintains independent `Arc` for all 11 decoder types. +- **Configurable timeouts**: `command_exec_timeout` (default 10s) and `poll_refresh_timeout` (default 8s) are configurable via `RigTaskConfig` and the TOML `[timeouts]` section. +- **Crash recovery**: Rig tasks are monitored; on crash, an `Error` state is broadcast to clients via the watch channel so they see the failure instead of silent timeout. + +### Audio Pipeline (`audio.rs` — 3,977 lines) + +The audio module handles decoder history storage and stream management: + +- **`DecoderHistories`**: Per-rig mutable store for 11 decoder history queues (AIS, VDES, APRS, HF_APRS, CW, FT8, FT4, FT2, WSPR, WXSAT, LRPT). +- **Time-based retention**: 24h TTL on all history with periodic pruning. +- **Capacity bounds**: Per-decoder max of 10,000 entries (`MAX_HISTORY_ENTRIES`) prevents unbounded memory growth on busy channels. +- **Atomic total count**: `AtomicUsize` with CAS loop avoids acquiring 11 mutex locks in `snapshot_all()`. +- **Lock poisoning recovery with logging**: Uses `lock_or_recover()` helper that logs a warning when recovering from a poisoned mutex. +- **`StreamErrorLogger`**: Suppresses duplicate stream errors with 60s periodic summaries and error classification (alsa_poll_failure, input/output_stream_error). +- **Device enumeration helpers**: `find_input_device()` and `find_output_device()` extract the repeated device lookup logic from `run_capture()`/`run_playback()`. +- **CRC filtering**: APRS records filtered by `crc_ok` before storage. + +### Remote Client Dual-Connection Model + +`remote_client.rs` maintains two independent TCP connections to the server: + +1. **Main connection** (port 4530): State polling, command forwarding, rig discovery. +2. **Spectrum connection** (dedicated): Polls `GetSpectrum` at 50ms intervals (20 fps) independently to avoid blocking the main connection during command processing. + +Constants: `CONNECT_TIMEOUT: 5s`, `IO_TIMEOUT: 15s`, `SPECTRUM_IO_TIMEOUT: 3s`. Exponential backoff with jitter on reconnect. + +### FrontendRuntimeContext Sub-Structs + +The `FrontendRuntimeContext` struct in `trx-frontend/src/lib.rs` is decomposed into coherent sub-structs: + +| Sub-struct | Purpose | Key fields | +|-----------|---------|------------| +| `AudioContext` | Audio streaming channels | `rx`, `tx`, `info`, `decode_rx`, `clients` | +| `DecodeHistoryContext` | Decode history for all types | `ais`, `vdes`, `aprs`, `hf_aprs`, `cw`, `ft8`, `ft4`, `ft2`, `wspr` | +| `HttpAuthConfig` | HTTP auth settings | `enabled`, `rx_passphrase`, `session_ttl_secs`, `tokens` | +| `HttpUiConfig` | HTTP UI display config | `show_sdr_gain_control`, `initial_map_zoom`, `spectrum_*` | +| `RigRoutingContext` | Remote rig state & routing | `active_rig_id`, `remote_rigs`, `rig_states`, `server_connected` | +| `OwnerInfo` | Station metadata | `callsign`, `website_url`, `ais_vessel_url_base` | +| `VChanContext` | Virtual channel audio | `audio`, `audio_cmd`, `destroyed`, `rig_audio_cmd` | +| `SpectrumContext` | Spectrum data | `sender`, `per_rig` | +| `PerRigAudioContext` | Per-rig audio channels | `rx`, `info` | + +### Decoder Implementation Patterns + +All real-time decoders follow a consistent pattern: + +```rust +// 1. Stateful decoder struct with sample buffer +pub struct XxxDecoder { sample_buf: Vec, ... } + +// 2. Block/sample processing +pub fn process_block(&mut self, samples: &[f32]) { ... } + +// 3. Result extraction +pub fn decode_if_ready(&mut self) -> Vec { ... } +``` + +| Decoder | Algorithm | Sample Rate | Key Constants | +|---------|-----------|-------------|---------------| +| FT8/FT4/FT2 | Waterfall + LDPC/OSD | Varies | MAX_LDPC_ITERATIONS=20, MAX_CANDIDATES=120 | +| CW | Goertzel tone detection | Varies | 10ms windows, tone range 300–1200 Hz | +| APRS | Bell 202 AFSK (1200/2200 Hz) | 9600 | HDLC framing, NRZI, CRC-16-CCITT | +| AIS | GMSK 9600 baud | 9600 | Narrowband FM input | +| WSPR | Fano decoder | 12000 | 162 symbols, 120s slot, 1.46 Hz spacing | +| RDS | RRC matched filter + Costas PLL | Native | 57 kHz subcarrier, 1187.5 bps, OSD FEC | +| VDES | pi/4-QPSK 76.8 ksps | 100k | Burst detection, partial Turbo FEC | + +### Backend Reliability Workarounds (FT-817) + +The FT-817 CAT backend (`trx-backend-ft817/`) includes empirical workarounds for hardware quirks: + +- **Duplicate frame sends**: `set_mode()` and `set_ptt()` send CAT frames twice with 80ms delay (radio sometimes drops first frame). +- **Panel unlock before commands**: Clears stale bytes from the serial buffer. +- **Power-on dummy frame**: CPU wakes before CAT framing locks; dummy frame ensures readiness. +- **VFO state inference**: Infers VFO A/B by matching frequencies against cached values (fragile when frequencies collide). +- **Read timeout**: 800ms per CAT read operation (not configurable). diff --git a/docs/Home.md b/docs/Home.md new file mode 100644 index 0000000..e9b80f1 --- /dev/null +++ b/docs/Home.md @@ -0,0 +1,14 @@ +# trx-rs + +`trx-rs` is a modular amateur radio control stack written in Rust. It splits +hardware access, DSP, transport, and user-facing interfaces into separate +components so a radio or SDR can be controlled locally while audio, decoding, +and remote control are exposed elsewhere on the network. + +## Documentation + +- [User Manual](User-Manual) — configuration, features, and usage +- [Architecture](Architecture) — system design, crate layout, data flow, and internals +- [Optimization Guidelines](Optimization-Guidelines) — performance guidelines for the real-time DSP pipeline +- [Planned Features](Planned-Features) — planned features and design notes +- [Improvement Areas](Improvement-Areas) — codebase audit: quality, architecture, security, performance, and improvement plan diff --git a/docs/Improvement-Areas.md b/docs/Improvement-Areas.md new file mode 100644 index 0000000..65221fa --- /dev/null +++ b/docs/Improvement-Areas.md @@ -0,0 +1,211 @@ +# Improvement Areas + +A comprehensive audit of the trx-rs codebase covering code quality, architecture, +security, testing, and performance. Each item includes the affected location and +a suggested fix. + +*Last updated: 2026-03-29* + +--- + +## Resolved Items + +
+Click to expand resolved items from previous audits + +### Plugin signing and cross-platform validation — DROPPED + +Plugin system has been removed from the codebase. No longer applicable. + +### Session store mutex poisoning (auth.rs) — RESOLVED + +**Location:** `src/trx-client/trx-frontend/trx-frontend-http/src/auth.rs` + +All 6 `.write().unwrap()` / `.lock().unwrap()` calls replaced with +`.unwrap_or_else(|e| { warn!(...); e.into_inner() })` pattern. Lock poisoning now +logs a warning and recovers the inner data instead of crashing. + +### No rate limiting on TCP listener — RESOLVED + +**Location:** `src/trx-server/src/listener.rs` + +Added `ConnectionTracker` with per-IP connection limiting (default: 10 concurrent +connections per IP). Connections exceeding the limit are rejected with a log warning. +Slots are released when clients disconnect. + +### RigState is a 33-field flat struct — RESOLVED + +**Location:** `src/trx-core/src/rig/state.rs` + +Decoder fields grouped into `DecoderConfig` (8 bools) and `DecoderResetSeqs` +(8 u64 counters). Both use `#[serde(flatten)]` for backward-compatible JSON wire +format. Updated across all consumers. + +### No `spawn_blocking` timeout — RESOLVED + +**Location:** `src/trx-server/src/listener.rs` + +Satellite pass computation wrapped in `tokio::time::timeout(30s, ...)` with +graceful fallback to empty results on timeout or panic. + +### Command handler boilerplate — RESOLVED + +**Location:** `src/trx-core/src/rig/controller/handlers.rs` + +Created `rig_command!` declarative macro. 7 unit commands use the macro; 4 commands +with custom fields/validation remain as explicit impls. + +### No command execution timeouts at CommandExecutor level — RESOLVED + +**Location:** `src/trx-server/src/rig_task.rs` + +`tokio::time::timeout(command_exec_timeout, process_command(...))` wraps all +command execution. Default timeout: 10s, configurable via `RigTaskConfig`. + +### No forward compatibility in protocol — RESOLVED + +**Location:** `src/trx-protocol/src/types.rs`, `src/trx-protocol/src/codec.rs` + +Added optional `protocol_version: Option` to `ClientEnvelope` and +`ClientResponse`. `parse_envelope()` distinguishes malformed JSON from +unrecognised `cmd` values. + +### `unsafe` string construction in spectrum encoding — RESOLVED + +**Location:** `src/trx-client/trx-frontend/trx-frontend-http/src/api.rs` + +Replaced `unsafe { String::from_utf8_unchecked(out) }` with safe +`String::from_utf8(out).expect(...)`. + +### `#[allow(dead_code)]` cleanup — RESOLVED + +Reduced from 6 to 4 annotations, all in trx-backend-soapysdr where fields serve +as lifetime anchors (`device`, `iq_tx`) or document reserved capacity +(`fixed_slot_count`, `process_pair`). + +### VDES decoder incomplete FEC — RESOLVED + +Turbo FEC decoder, CRC-16-CCITT validation, and M.2092-1 link-layer frame parsing +implemented. + +### Plugin system lacks versioning — DROPPED + +Plugin system removed from the codebase. + +### Configurator serial detection stubbed — RESOLVED + +Implemented using `tokio_serial::available_ports()` with USB, Bluetooth, PCI, and +Unknown port type descriptions. + +### Inconsistent frequency/rig naming — DOCUMENTED AS INTENTIONAL + +Field names reflect distinct semantic contexts: `freq_hz` (dial), `center_hz` +(SDR capture center), `cw_center_hz` (CW tone); `rig_id` (config key), `id` +(runtime UUID); `model` (hardware string), `rig_model` (config parameter). + +### Decoder task duplication in audio.rs — RESOLVED + +**Location:** `src/trx-server/src/audio.rs` + +APRS and HF APRS decoders merged into a single parameterised +`run_aprs_decoder_inner()` function. FT8 and FT4 decoders merged into +`run_ftx_decoder_inner()`. All decoder tasks now include `tracing::info_span!` +around `block_in_place()` calls for opt-in latency measurement. + +### Missing tests for critical modules — RESOLVED + +**Location:** `src/trx-server/src/listener.rs`, `src/trx-client/trx-frontend/trx-frontend-http/` + +Added multi-rig state isolation and command routing tests in `listener.rs`. +Added background decode `evaluate_bookmark` pure-function tests. + +### Missing integration tests for multi-rig scenarios — RESOLVED + +**Location:** `src/trx-server/src/listener.rs` + +Added integration tests covering simultaneous state management across two rigs +with a dummy backend, verifying state isolation and command routing. + +### Decode log silent failures — RESOLVED + +**Location:** `src/decoders/trx-decode-log/src/lib.rs` + +`flush()` errors are now logged via `warn!`. On file rotation failure, the old +writer is kept rather than silently dropping writes; a degradation warning is +emitted. + +### `api.rs` file size and organization — RESOLVED + +**Location:** `src/trx-client/trx-frontend/trx-frontend-http/src/api/` + +Split 2,831-LOC monolith into 7 logically grouped modules: `mod.rs` (shared +types and route configuration), `decoder.rs`, `rig.rs`, `vchan.rs`, `sse.rs`, +`bookmarks.rs`, `assets.rs`. + +### Background decode state complexity — RESOLVED + +**Location:** `src/trx-client/trx-frontend/trx-frontend-http/src/background_decode.rs` + +Extracted the 8-guard decision cascade into a pure `evaluate_bookmark()` function +returning `ChannelAction` enum (`Active` or `Skip { reason }`). Added unit tests +for all decision paths. + +### Actix-web pinned to exact version — RESOLVED + +**Location:** `src/trx-client/trx-frontend/trx-frontend-http/Cargo.toml` + +Relaxed from `actix-web = "=4.4.1"` to `actix-web = "4.4"` to allow patch-level +security updates. + +### Magic numbers in VDES plausibility scoring — RESOLVED + +**Location:** `src/decoders/trx-vdes/src/lib.rs` + +Inline magic numbers replaced with documented named constants: +`PLAUSIBILITY_UNSYNCED_THRESHOLD` (−35) and +`PLAUSIBILITY_LOW_CONFIDENCE_THRESHOLD` (15). + +### FT-817 VFO inference fragile with same frequency — DOCUMENTED + +**Location:** `src/trx-server/trx-backend/trx-backend-ft817/src/lib.rs` + +When both VFOs share the same frequency, inference defaults to VFO A. Resolved +after VFO toggle primes both sides. Well-documented in code comments; remains +a known limitation. + +### Excessive string cloning in remote client — RESOLVED + +**Location:** `src/trx-client/src/remote_client.rs` + +Hot-path spectrum polling loop now caches the token to avoid per-poll cloning. +State update path restructured to send to the main watch channel last (taking +ownership) and avoid one redundant `RigState::clone()`. + +### Missing doc comments on public decoder structs — RESOLVED + +**Location:** `src/decoders/trx-ais/src/lib.rs`, `src/decoders/trx-vdes/src/lib.rs`, +`src/decoders/trx-rds/src/lib.rs` + +Added comprehensive doc comments to `AisDecoder`, `VdesDecoder`, and `RdsDecoder` +describing valid sample rates, usage examples, and reset semantics. + +### Turbo decoder precondition not asserted — RESOLVED + +**Location:** `src/decoders/trx-vdes/src/turbo.rs` + +Added `debug_assert_eq!` on interleaver and deinterleaver lengths in +`turbo_decode_soft()`. + +### No tracing spans for decoder performance — RESOLVED + +**Location:** `src/trx-server/src/audio.rs` + +Added `tracing::info_span!` around `block_in_place()` calls in all 10 decoder +tasks (APRS, HF APRS, AIS A/B, VDES, CW, FT8, FT4, FT2, WSPR, LRPT) for +opt-in per-decoder latency measurement. + +
+ +--- + +All previous improvement items have been resolved. No outstanding issues. diff --git a/docs/Optimization-Guidelines.md b/docs/Optimization-Guidelines.md new file mode 100644 index 0000000..22a4901 --- /dev/null +++ b/docs/Optimization-Guidelines.md @@ -0,0 +1,175 @@ +# DSP Optimization Guidelines + +This document captures lessons learned and best practices for optimizing +the real-time DSP pipelines in trx-rs, particularly the WFM stereo decoder +and audio encoding paths. + +## General Principles + +1. **Measure first.** Profile with real workloads before optimizing. + Synthetic benchmarks miss cache effects, branch prediction patterns, + and real signal statistics. + +2. **Eliminate transcendentals from inner loops.** A single `sin_cos` or + `atan2` per sample at 200 kHz composite rate costs millions of calls + per second. Replace with: + - **Quadrature NCO** for oscillators: maintain `(cos, sin)` state and + rotate by a precomputed `(cos_inc, sin_inc)` each sample. Cost: + 4 muls + 2 adds. Renormalize every ~1024 samples to prevent drift. + - **Double-angle identities** to derive `sin(2θ), cos(2θ)` from + `sin(θ), cos(θ)`: `sin2 = 2·sin·cos`, `cos2 = 2·cos²−1`. + - **I/Q arm extraction** for PLL phase error: if you have + `i = lp(signal * cos)` and `q = lp(signal * -sin)`, then + `sin(err) = q/mag`, `cos(err) = i/mag` — no `atan2` or `sin_cos` + needed for the rotation. + +3. **Batch operations for SIMD.** Separate data-parallel work (e.g. FM + discriminator: conjugate-multiply + atan2) from sequential-state work + (PLL, biquads). Process the parallel part in batches of 8 using AVX2, + then feed scalar results into the sequential pipeline. + +4. **Power-of-2 sizes for circular buffers.** Use `& (N-1)` bitmask + instead of `% N` modulo. Ensure buffer lengths (e.g. `WFM_RESAMP_TAPS`) + are powers of two. + +5. **Circular buffers over shift registers.** Writing one sample at a + ring-buffer position is O(1); `rotate_left(1)` is O(N). For a 32-tap + FIR called 3× per composite sample, this eliminates ~200 byte-moves + per sample. + +6. **Decimate slow-changing metrics.** Stereo detection (pilot coherence, + lock, drive) changes over tens of milliseconds. Running it every 16th + sample instead of every sample saves ~94% of that work with no audible + effect. Accumulate values over the window and process the average. + +## Filter Design + +- **Match filter cutoffs** across parallel paths (sum and diff) to ensure + identical group delay. Mismatched cutoffs cause frequency-dependent + phase errors that directly degrade stereo separation. + +- **4th-order Butterworth** (two cascaded biquads) is generally sufficient + when the polyphase resampler provides additional stopband rejection. + 6th-order adds 50% more biquad evaluations per sample for diminishing + returns. + +- **Q values for Butterworth cascades:** + - 4th-order: Q₁ = 0.5412, Q₂ = 1.3066 + - 6th-order: Q₁ = 0.5176, Q₂ = 0.7071, Q₃ = 1.9319 + +## Polyphase Resampler + +- **Compute cutoff from actual rate ratio:** `cutoff = output_rate / input_rate`. + A fixed cutoff (e.g. 0.94) can be catastrophically wrong — at 200 kHz + composite to 48 kHz audio, it passes everything up to 94 kHz while the + output Nyquist is only 24 kHz. The 38 kHz stereo subcarrier residuals + alias directly into the treble range. + +- **Blackman-Harris window** gives ~92 dB stopband rejection vs ~43 dB + for Hamming, at the same tap count. Use it for the windowed-sinc + coefficients: + ``` + w(n) = 0.35875 − 0.48829·cos(2πn/N) + 0.14128·cos(4πn/N) − 0.01168·cos(6πn/N) + ``` + +- **32 taps** with Blackman-Harris and a proper cutoff gives >60 dB + stopband rejection — more than enough. 64 taps doubles the MAC count + for marginal improvement. + +- **64 polyphase phases** balances fractional sample resolution against + coefficient bank size (64 × 32 × 4 = 8 KB fits comfortably in L1 + cache). 128 phases offer diminishing returns for double the memory. + +## FM Discriminator + +- **Batch with AVX2:** The conjugate-multiply + atan2 pattern is + data-parallel (each output depends only on two adjacent input samples). + Process 8 samples at a time using 256-bit SIMD. + +- **Use a high-precision atan2 polynomial** for AVX2. A 7th-order minimax + polynomial (max error ~2.4e-7 rad) avoids the treble distortion that + cheap 1st-order approximations (e.g. `0.273*(1−|z|)`) introduce on + strong signals. Coefficients: + ``` + c0 = 0.999_999_5 + c1 = −0.333_326_1 + c2 = 0.199_777_1 + c3 = −0.138_776_8 + ``` + +- **Branchless argument reduction** for atan2: swap `|y|` and `|x|` using + masks rather than branches, apply quadrant correction via arithmetic + shift and copysign. + +## WFM Stereo Specifics + +- **Pilot notch before diff demod:** The 19 kHz pilot leaks into the + 38 kHz multiplication and creates intermod products. Notch it from the + composite signal before `x * cos(2θ)`. This notch is separate from the + mono-path pilot notch (which sits after the sum LPF). + +- **IQ hard limiter before FM discriminator:** For WFM, only the phase + carries information. Normalizing IQ magnitude to 1.0 prevents + overdeviation artifacts and clipping. Guard against zero magnitude. + +- **Binary stereo blend:** A smooth blend function (e.g. smoothstep) + sounds good in theory but reduces real-world separation. Use + `blend = 1.0` when pilot is detected, `0.0` otherwise. + +- **STEREO_MATRIX_GAIN = 0.50:** The correct unity factor for + `L = (S+D)/2`, `R = (S−D)/2`. Lower values waste headroom; higher + values clip. + +## Opus Encoding + +- **Complexity 5** (down from default 9-10) saves significant CPU with + minimal quality impact at bitrates ≥128 kbps. The higher complexity + levels run expensive psychoacoustic search algorithms that produce + negligible improvement at high bitrates. + +- **256 kbps** is transparent for stereo FM broadcast audio. Going higher + wastes bandwidth; going below 128 kbps may introduce artifacts on + complex program material. + +- **`Application::Audio`** (not VoIP) — uses the MDCT-based CELT mode + optimized for music and broadband audio rather than speech. + +## AVX2 Guidelines + +- Gate all AVX2 code behind `#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]` + and runtime `is_x86_feature_detected!("avx2")` checks. + +- Mark unsafe SIMD functions with `#[target_feature(enable = "avx2")]` + so the compiler generates AVX2 code for the function body. + +- Provide scalar fallbacks for non-x86 targets and CPUs without AVX2. + +- Add epsilon guards (e.g. `1e-12`) to denominators in SIMD paths where + both numerator and denominator can be zero simultaneously. + +## What NOT to Optimize + +- **Biquad filters** — already minimal (5 muls + 4 adds per sample). + The sequential state dependency prevents SIMD vectorization within a + single stream. + +- **One-pole lowpass filters** — single multiply-accumulate, cannot be + made faster. + +- **DC blockers** — trivial per-sample cost. + +- **Deemphasis** — single biquad, runs at audio rate (not composite rate). + +## Profiling Tips + +- Use `cargo build --release` — debug builds are 10-50x slower and + misleading for DSP profiling. + +- `perf stat` / `Instruments` on the inner loop to check IPC, cache + misses, and branch mispredictions. + +- Compare CPU% with stereo enabled vs disabled to isolate stereo-specific + costs (diff path biquads, pilot PLL, 38 kHz demod, resampler channels). + +- Watch for unexpected `libm` calls in disassembly — the compiler may + not inline `f32::atan2` or `f32::sin_cos` even in release mode. diff --git a/docs/Planned-Features.md b/docs/Planned-Features.md new file mode 100644 index 0000000..4b628f7 --- /dev/null +++ b/docs/Planned-Features.md @@ -0,0 +1,324 @@ +# Planned Features + +## Recorder + +The recorder captures the demodulated audio stream alongside associated metadata (FFT data, decoded signals, rig state) into a structured session on disk, with full playback and seeking support from within the application. + +### Requirements + +| ID | Description | +|----|-------------| +| REQ-REC-001 | When the user starts recording, the system shall record the currently demodulated audio stream. | +| REQ-REC-002 | When recording audio, the system shall store the recording in OPUS format. | +| REQ-REC-003 | While recording audio, the system shall automatically detect whether the recording should be stored in mono or stereo and select the appropriate format. | +| REQ-REC-004 | While recording is active, the system shall simultaneously record FFT data and all currently visible decoded elements, including APRS and FT8. | +| REQ-REC-005 | While recording metadata, the system shall store FFT data and decoded signal data in a structured data file format. | +| REQ-PLAY-001 | Where recorded sessions exist, the system shall allow playback of recordings from within the same application. | +| REQ-PLAY-002 | During playback, the system shall allow the user to seek to any position in the recording. | +| REQ-SYNC-001 | The system shall maintain time synchronization between the audio recording and the associated data file with at least one-second resolution. | +| REQ-REC-006 | While recording is active, the system shall allow the current cursor position to be stored. | + +--- + +### Architecture + +#### New Crate: `trx-recorder` + +A new crate `src/trx-server/trx-recorder/` handles all record and playback logic. It is a library crate consumed by `trx-server`. + +``` +src/trx-server/ + trx-recorder/ + src/ + lib.rs # Public API: RecorderHandle, start_recorder_task() + session.rs # RecordingSession: file management, open/close/finalise + writer.rs # AudioWriter: PCM → Opus encoder + data_file.rs # DataFileWriter: structured JSON Lines data track + index.rs # SeekIndex: time → byte-offset table for audio seeking + playback.rs # PlaybackEngine: file → PCM broadcast for clients + config.rs # RecorderConfig (serde, derives Default) +``` + +#### Integration Points in `trx-server` + +| Source | What is tapped | How | +|--------|---------------|-----| +| `audio.rs` `pcm_tx` | Raw demodulated PCM frames | New `broadcast::Receiver>` subscriber | +| `audio.rs` spectrum broadcast | FFT/spectrum frames per `RigState.spectrum` | New subscriber on the spectrum watch channel | +| `audio.rs` decoded-message broadcast | FT8, WSPR, CW, APRS, FT4, FT2, APRS-HF frames | New `broadcast::Receiver` subscriber | +| `rig_task.rs` state watch | Frequency/mode/PTT changes | `watch::Receiver` clone | +| New `RecorderCommand` enum | Start, Stop, MarkCursor | Injected into the existing command pipeline | + +No existing code paths are modified beyond: +1. Passing a `RecorderHandle` (cheap `Arc` wrapper) into the audio and rig tasks. +2. Adding `RecorderCommand` variants to the command enum (alongside existing `SetFreq`, `SetMode`, etc.). +3. Adding a `[recorder]` section to `ServerConfig`. + +--- + +### Session Layout on Disk + +Each recording is a **session directory** named by UTC start time and opening rig state: + +``` +/ + 20260317T142301Z_14074000_USB/ + audio.opus + data.jsonl # structured event log (see below) + index.bin # seek index: sorted table of (offset_ms u64, audio_byte u64) +``` + +`output_dir` defaults to `~/.local/share/trx-rs/recordings`. + +#### Audio File (REQ-REC-001, REQ-REC-002, REQ-REC-003) + +- **Format**: Opus, using the `opus` crate (already a workspace dependency via `trx-backend-soapysdr`). Seek index (`index.bin`) provides byte → time mapping. +- **Channel count**: determined at session open from `AudioConfig.channels`. If `channels == 1` → mono; if `channels == 2` → stereo. Written into the file header and recorded in the session's first data event. +- **Sample rate**: preserved from `AudioConfig.sample_rate` (default 48 000 Hz). + +#### Data File (REQ-REC-004, REQ-REC-005) + +`data.jsonl` — one JSON object per line, each with a required `offset_ms` field giving the millisecond offset from session start (satisfies REQ-SYNC-001 at ≥1 s resolution): + +```jsonl +{"offset_ms":0,"type":"session_start","freq_hz":14074000,"mode":"USB","channels":1,"sample_rate":48000,"format":"opus"} +{"offset_ms":1000,"type":"rig_state","freq_hz":14074000,"mode":"USB","ptt":false} +{"offset_ms":2000,"type":"fft","bins_db":[-90.1,-88.4,...]} +{"offset_ms":3412,"type":"ft8","snr_db":-12,"dt_s":0.3,"freq_hz":14074350,"message":"CQ W5XYZ EN34"} +{"offset_ms":4100,"type":"aprs","from":"W5XYZ-9","to":"APRS","path":"WIDE1-1","info":"!3351.00N/09722.00W-"} +{"offset_ms":5000,"type":"cursor","label":"interesting QSO"} +{"offset_ms":61000,"type":"session_end"} +``` + +Supported `type` values: + +| Type | Source | Cadence | +|------|--------|---------| +| `session_start` | recorder | once, at open | +| `session_end` | recorder | once, at close | +| `rig_state` | `watch::Receiver` change | on change | +| `fft` | spectrum data from `RigState.spectrum` | ≤1 Hz (configurable, default 1 s) | +| `ft8` / `ft4` / `ft2` / `wspr` | `DecodedMessage` broadcast | on decode event | +| `aprs` / `aprs_hf` | `DecodedMessage` broadcast | on decode event | +| `cw` | `DecodedMessage` broadcast | on decode event | +| `cursor` | `RecorderCommand::MarkCursor { label }` | on user request | + +#### Seek Index (REQ-PLAY-002) + +`index.bin` is a flat binary table of 16-byte records written every `index_interval_ms` (default 1 000 ms): + +``` +[offset_ms: u64 LE][audio_byte_offset: u64 LE] ... +``` + +At playback seek time, binary search on `offset_ms` locates the nearest audio frame boundary, enabling random-access playback without full file scan. + +--- + +### RecorderConfig + +Added to `ServerConfig` under `[recorder]`: + +```toml +[recorder] +enabled = false +output_dir = "~/.local/share/trx-rs/recordings" +opus_bitrate_bps = 32000 +fft_record_interval_ms = 1000 +index_interval_ms = 1000 +max_session_duration_s = 3600 # auto-split at 1 h; 0 = unlimited +``` + +--- + +### Command API + +New variants added to the existing command enum (handled in `rig_task.rs`): + +```rust +StartRecording, +StopRecording, +MarkCursor { label: String }, +``` + +These are exposed via: +- **HTTP frontend**: `POST /api/recorder/start`, `POST /api/recorder/stop`, `POST /api/recorder/cursor` +- **http-json frontend**: same commands as JSON messages + +--- + +### Playback Engine (REQ-PLAY-001, REQ-PLAY-002) + +`PlaybackEngine` opens a session directory and: + +1. Reads `audio.opus` and decodes PCM frames in real time. +2. Publishes decoded PCM frames onto a `broadcast::Sender>` — the **same channel type** as the live `pcm_tx`, so existing decoder tasks and audio-streaming clients receive playback data transparently. +3. Replays `data.jsonl` events on their original `offset_ms` timestamps, injecting them into the `DecodedMessage` broadcast so the HTTP frontend displays historic decodes during playback. +4. For seek: binary-searches `index.bin` to find the audio byte offset, then replays data events from the same point. + +The playback state machine has two modes, switched by a new `RigState.playback` field: + +```rust +pub enum PlaybackState { + Live, + Playing { session: String, offset_ms: u64 }, + Paused { session: String, offset_ms: u64 }, +} +``` + +While `PlaybackState` is not `Live`, the server suppresses live hardware polling and PCM capture to avoid mixing live and playback audio. + +--- + +### Time Synchronisation (REQ-SYNC-001) + +All timestamps use a single `session_epoch: std::time::Instant` captured at `StartRecording`. Every PCM frame, every data event, and every seek-index entry is stamped as `(Instant::now() - session_epoch).as_millis() as u64`. This gives sub-millisecond internal precision; the requirement of ≥1 s resolution is met by orders of magnitude. + +Wall-clock UTC is embedded only in `session_start` (`wall_clock_utc`) and in the session directory name, providing absolute time anchoring without depending on system clock monotonicity for sync. + +--- + +### Implementation Phases + +#### Phase 1 — Audio recording (REQ-REC-001, REQ-REC-002, REQ-REC-003) + +1. Add `trx-recorder` crate skeleton; `RecorderConfig`; `RecorderHandle`. +2. Implement `AudioWriter` with Opus output. +3. Subscribe `AudioWriter` to `pcm_tx` in `audio.rs`; open session on `StartRecording` command. +4. Auto-detect channel count from `AudioConfig.channels`. + +#### Phase 2 — Metadata recording (REQ-REC-004, REQ-REC-005, REQ-SYNC-001) + +1. Implement `DataFileWriter`; define full event schema. +2. Subscribe to `DecodedMessage` broadcast; fan-in all decoder types. +3. Subscribe to state watch; emit `rig_state` events on freq/mode change. +4. Emit `fft` events at configured interval from spectrum data. +5. Write `SeekIndex` in parallel with audio. + +#### Phase 3 — Cursor (REQ-REC-006) + +1. Add `MarkCursor` command + HTTP endpoint. +2. Write `cursor` event to `data.jsonl` with current `offset_ms`. + +#### Phase 4 — Playback (REQ-PLAY-001, REQ-PLAY-002) + +1. Implement `PlaybackEngine`; Opus decode + PCM broadcast. +2. Add `PlaybackState` to `RigState`; suppress live capture during playback. +3. Implement seek via `index.bin` binary search. +4. Replay `data.jsonl` events; feed into `DecodedMessage` broadcast. +5. Expose start/stop/seek endpoints in `trx-frontend-http`. + +--- + +### Dependencies to Add + +| Crate | Use | Already present? | +|-------|-----|-----------------| +| `opus` | Opus encode/decode | Yes (via trx-backend-soapysdr) | +| `serde_json` | data.jsonl serialisation | Yes | +| `tokio::fs` | async file I/O | Yes | + +--- + +### Open Questions + +1. **Playback isolation**: Should playback be exclusive (block all CAT commands) or concurrent? Initial design blocks CAT polling; revisit if users need to change frequency during playback. +2. **Session listing API**: The HTTP frontend needs an endpoint to enumerate sessions (`GET /api/recorder/sessions`). Schema TBD in Phase 4. +3. **Storage limits**: `max_session_duration_s` auto-splits sessions; a `max_total_size_gb` housekeeping option may be needed but is out of scope for initial phases. + +--- + +## Configurator Helper + +An interactive CLI tool that guides users through creating configuration files +for trx-rs. Instead of editing TOML by hand, the user answers prompts and the +tool generates valid, commented configuration files. + +### Overview + +The configurator is a standalone Rust binary (`trx-configurator`) that reuses +the existing config structs from `trx-app`, `trx-server`, and `trx-client`. It +walks the user through a question-driven flow, validates inputs against the same +rules the binaries use at startup, and writes one or more of: + +- `trx-server.toml` — server configuration +- `trx-client.toml` — client configuration +- `trx-rs.toml` — combined server + client configuration + +The user chooses which file(s) to generate. + +### Requirements + +| ID | Description | +|----|-------------| +| REQ-CFG-001 | The tool shall interactively prompt the user for configuration values. | +| REQ-CFG-002 | The tool shall generate `trx-server.toml`, `trx-client.toml`, or `trx-rs.toml` per user selection. | +| REQ-CFG-003 | The tool shall validate all inputs using the same validation logic as the server and client binaries. | +| REQ-CFG-004 | The tool shall write commented TOML with descriptions of each field. | +| REQ-CFG-005 | The tool shall detect connected serial devices and offer them for rig access configuration. | +| REQ-CFG-006 | The tool shall detect available SoapySDR devices and offer them for SDR backend configuration. | +| REQ-CFG-007 | The tool shall support a non-interactive mode that generates a default config file. | +| REQ-CFG-008 | The tool shall not overwrite existing files without confirmation. | + +### Architecture + +#### New Crate: `trx-configurator` + +A new binary crate at `src/trx-configurator/` that depends on `trx-app` for +config types and validation. + +``` +src/trx-configurator/ + src/ + main.rs # CLI entry point, mode selection + prompts.rs # Interactive prompt helpers (with defaults, validation) + detect.rs # Hardware detection (serial ports, SoapySDR devices) + writer.rs # TOML serialisation with inline comments +``` + +#### Flow + +``` +trx-configurator + ├── What would you like to generate? + │ [ ] trx-server.toml + │ [ ] trx-client.toml + │ [ ] trx-rs.toml (combined) + │ + ├── (if server) + │ ├── General: callsign, location + │ ├── Rig: model selection, access (serial/tcp/sdr) + │ │ └── detect serial ports / SoapySDR devices + │ ├── Listen: address, port + │ ├── Audio: sample rate, channels, codec settings + │ ├── SDR: (if soapysdr selected) gain, channels, decoders + │ ├── Uplinks: PSKReporter, APRS-IS + │ └── Decode logs: enable, directory + │ + ├── (if client) + │ ├── Remote: server URL, auth token + │ ├── Frontends: HTTP, rigctl, http-json (enable/disable, ports) + │ └── Audio: bridge settings + │ + └── Write file(s) with confirmation + +``` + +#### Hardware Detection + +- **Serial ports**: enumerate available serial devices using `serialport` crate + (already a transitive dependency). Present as selectable list with device + path and description. +- **SoapySDR devices**: if built with `soapysdr` feature, call + `SoapySDR::enumerate("")` to list available SDR hardware. Present device + driver, label, and serial number. + +#### Dependencies + +| Crate | Use | Already present? | +|-------|-----|-----------------| +| `dialoguer` | Interactive prompts, selection, confirmation | No | +| `toml_edit` | TOML serialisation preserving comments | No | +| `trx-app` | Config types and validation | Yes | +| `serialport` | Serial port enumeration | Yes (transitive) | +| `soapysdr` | SDR device enumeration (optional) | Yes (feature-gated) | diff --git a/docs/RDS-Tuning-Notes.md b/docs/RDS-Tuning-Notes.md new file mode 100644 index 0000000..5d7661c --- /dev/null +++ b/docs/RDS-Tuning-Notes.md @@ -0,0 +1,95 @@ +# RDS Parameter Tuning Notes + +*Decoder tuning rationale for `trx-rds`. Recorded 2026-03-27; reflects the +shipped parameter set. Kept as a reference for why these constants were chosen — +not an open work item.* + +## Goal +Maximum sensitivity (weak-signal decode) with zero false positive PI decodes. + +## Changes Applied + +### `src/decoders/trx-rds/src/lib.rs` + +#### Constants tuned +- `RRC_ALPHA = 0.50` (was 0.75) — narrower noise bandwidth, ~0.6 dB SNR gain +- `COSTAS_KI = 3.5e-7` — loop damping ζ≈0.68, well-damped (1e-6 caused instability) +- `PI_ACC_THRESHOLD = 3` (was 2) — accumulate 3 Block A observations before committing PI +- `OSD_MAX_FLIP_COST = 0.45` — Tech 9: reject OSD corrections where flipped bits had + high confidence (genuine errors have cost ≲ 0.3; noise matches cost 0.6–1.2) + +#### Soft confidence fix +In `Candidate::process_sample`, the soft confidence passed to `push_bit_soft` is now +`biphase_i.abs()` (was full vector magnitude). This aligns confidence with the bit +decision sign and prevents OSD(2) from false-decoding noise when the Costas loop +has residual phase error. + +#### OSD(2) in locked mode (kept) +`decode_block_soft` performs OSD(2): hard decode → all 26 single-bit flips → all +325 two-bit flip pairs. Only active in locked mode; sequential B→C→D block-type +gating limits false positives. + +#### Search mode: hard decode only +Removed OSD(1) from Block A acquisition (search mode). With OSD(1), ~13% of +random 26-bit words would falsely pass the Block A test per bit, allowing wrong +clock-phase candidates to accumulate false groups as fast as the correct candidate +accumulates real ones. Hard decode reduces the false Block A rate to ~0.5%. + +#### Tech 9: OSD cost ceiling +`decode_block_soft` now enforces `OSD_MAX_FLIP_COST = 0.45` — the sum of soft +confidences for all flipped bits must not exceed this threshold. At 9–10 dB SNR, +genuine bit errors have very low `|biphase_I|` (cost ≲ 0.3), while noise-induced +OSD matches flip high-confidence bits (cost 0.6–1.2). This eliminates most +spurious OSD(2) matches without affecting real weak-signal corrections. + +#### Tech 10: PI consistency gate +`process_group` rejects groups whose Block A PI differs from the candidate's +established PI. This prevents a single false OSD decode from polluting accumulated +text fields (PS, RT, PTYN) with garbage from noise or interference. + +#### Candidate selection: incumbent tracking +Added `best_candidate_idx: Option` to `RdsDecoder`. The incumbent (winning) +candidate can always update `best_state` at equal score (its `ps_seen`/`rt_seen` +arrays accumulate coherently). A challenger must achieve a strictly higher score to +take over. The incumbent's `best_score` is also updated when it returns `None` +(no state change) so challengers cannot leapfrog with a single false group. + +#### Test fixes +- `blocks_to_chips`: added NRZI (NRZ-Mark) pre-encoding. The differential biphase + decoder computes `bit = input_bit XOR prev_input_bit`; without NRZI the recovered + bits were XOR-of-consecutive-bits, not the original data. +- `decode_block_soft_rejects_three_bit_error`: removed (OSD(2) legitimately finds + distance-2 codewords; `pure_noise_produces_zero_pi_decodes` is the real guard). +- New test: `blocks_to_chips_round_trips_all_groups` — verifies round-trip decode + of all 16 blocks across all 4 PS segments without BPSK modulation. + +### `src/trx-server/trx-backend/trx-backend-soapysdr/src/demod/wfm.rs` + +- `PILOT_LOCK_THRESHOLD = 0.20` (was 0.25) — pilot reference enabled at lower coherence +- Added `PILOT_LOCK_ONSET = 0.30` constant (was hardcoded 0.4) +- `pilot_lock` ramp: `((pilot_coherence - PILOT_LOCK_ONSET) / 0.2).clamp(0.0, 1.0)` + — pilot reference engages at coherence ≥ 0.36 instead of ≥ 0.45 + +## Test Status + +``` +cargo test -p trx-rds +``` + +16/16 passing: +- ✅ decode_block_recognizes_valid_offsets +- ✅ decode_block_soft_corrects_single_bit_error +- ✅ decode_block_soft_corrects_two_bit_error_osd2 +- ✅ block_decode_rate_osd1_vs_osd2 +- ✅ decode_block_soft_prefers_least_costly_flip +- ✅ full_group_with_two_bit_errors_in_each_locked_block +- ✅ pi_accumulation_corrects_weak_pi_after_threshold +- ✅ decoder_emits_ps_and_pty_from_group_0a +- ✅ rrc_tap_dc_gain +- ✅ pure_noise_produces_zero_pi_decodes (2 seconds of noise, zero false PI) +- ✅ end_to_end_with_pilot_reference_decodes_pi +- ✅ end_to_end_noisy_signal_snr_10db_decodes_pi +- ✅ end_to_end_noisy_signal_snr_9db_decodes_pi ← new, 9 dB threshold +- ✅ costas_tracks_without_diverging_on_clean_signal +- ✅ blocks_to_chips_round_trips_all_groups +- ✅ end_to_end_clean_signal_decodes_ps diff --git a/docs/Settings-Menu-UX-Analysis.md b/docs/Settings-Menu-UX-Analysis.md new file mode 100644 index 0000000..6f76009 --- /dev/null +++ b/docs/Settings-Menu-UX-Analysis.md @@ -0,0 +1,163 @@ +# Settings Menu — UI/UX Analysis & Improvement Plan + +*Authored: 2026-03-30* + +## 1. Current Structure + +The Settings tab (`#tab-settings`) contains four sub-tabs: + +| Sub-tab | Purpose | Complexity | +|---|---|---| +| **Scheduler** | Grayline / Time Span / Satellite scheduling | High — nested modes, forms, timeline | +| **Background Decode** | Hidden background decoder channels | Medium — toggle + bookmark checklist | +| **Bandplan** | IARU region overlay on spectrum | Low — dropdown + checkbox | +| **History** | Clear server-side decode history | Low — 10 clear buttons | + +--- + +## 2. Identified Issues + +### 2.1 Information Architecture + +| # | Issue | Severity | +|---|---|---| +| IA-1 | **"Settings" is a catch-all bucket.** Scheduler and Background Decode are operational features, not user preferences. Bandplan and History are true settings/maintenance. Mixing them under one tab creates cognitive overhead. | Medium | +| IA-2 | **Scheduler sub-tab is overloaded.** It packs three conceptually distinct features (Grayline, Time Span, Satellite) into one scrollable panel via conditional `display:none` sections. Users must scroll past irrelevant sections. | Medium | +| IA-3 | **History clearing is buried.** Users wanting to clear FT8 decode history must navigate to Settings → History — an unintuitive path. This action is more naturally accessible from the Digital Modes tab itself. | Low | +| IA-4 | **No search or categorization.** With 4 sub-tabs today, it's manageable, but the flat sub-tab bar won't scale if more settings (e.g., audio, display theme, reporting/PSKReporter, notifications) are added. | Low | + +### 2.2 Interaction Design + +| # | Issue | Severity | +|---|---|---| +| IX-1 | **Save button visibility is inconsistent.** Save/Reset buttons use `style="display:none"` and are shown dynamically, but there is no dirty-state indicator. Users can change fields without realizing they haven't saved. | High | +| IX-2 | **No confirmation on destructive actions.** The 10 history-clear buttons and "Reset to Disabled" (scheduler) fire immediately on click. No confirmation dialog protects against accidental data loss. | High | +| IX-3 | **Entry table details collapsed by default.** The Time Span entry table is inside a `
` element — users must expand it to see, edit, or delete entries. This adds an unnecessary click when entries already exist. | Medium | +| IX-4 | **Satellite form uses a modal overlay; Time Span form is inline.** Inconsistent form presentation within the same sub-tab. Both should use the same pattern. | Medium | +| IX-5 | **Toast notification positioning.** The `.sch-toast` uses `position: fixed; bottom: 1.5rem` which can overlap with the main tab bar or mobile navigation. It also disappears without user control. | Low | +| IX-6 | **Bookmark filter in Background Decode has no "select all / deselect all" shortcut.** With many bookmarks, toggling them one by one is tedious. | Medium | + +### 2.3 Visual & Layout + +| # | Issue | Severity | +|---|---|---| +| VL-1 | **Scheduler has no visual state summary.** The "No activity yet." card doesn't show whether the scheduler is enabled or what mode it's in at a glance. Users must inspect the mode dropdown. | Medium | +| VL-2 | **History clear buttons are uniform.** All 10 buttons look identical (`sch-write sch-reset-btn`). No indication of which decoders have data to clear. Buttons for empty histories are noise. | Low | +| VL-3 | **Mobile responsiveness is partial.** The `@media (max-width: 600px)` rules handle `.sch-row` and `.bgd-*` layout, but the Time Span table (`.sch-ts-table` with 8 columns) overflows on narrow screens. | Medium | +| VL-4 | **Sub-tab bar can overflow.** It uses `overflow-x: auto` but gives no visual scroll indicator. On small screens, the "History" tab can be hidden off-screen with no affordance. | Low | + +### 2.4 Accessibility + +| # | Issue | Severity | +|---|---|---| +| A-1 | **Missing `aria-label` on several controls.** The scheduler mode select has one, but the grayline lat/lon inputs, interleave fields, and satellite fields lack accessible names beyond their visible label text (which is acceptable for `