From f3c361efd164d8bb5f36a99169c13b3936080ed1 Mon Sep 17 00:00:00 2001 From: rUv Date: Sat, 22 Aug 2026 14:59:22 -0400 Subject: [PATCH] fix: align multistatic CSI time and clear Rust advisories (#1669) Use mesh-aligned capture timestamps, remediate Rust advisories, harden the audit gate, and correct deployment claims. Includes the live MQTT subscriber lifetime fix verified against Mosquitto. --- .github/workflows/security-scan.yml | 27 + README.md | 16 +- aether-arena/calibration/README.md | 16 +- .../wifi-pose-efficiency-frontier.md | 13 +- docs/user-guide.md | 53 +- v2/.cargo/audit.toml | 30 +- v2/Cargo.lock | 987 ++++-------------- v2/Cargo.toml | 2 +- v2/crates/homecore-recorder/Cargo.toml | 14 +- v2/crates/homecore-recorder/src/db.rs | 13 + v2/crates/ruview-swarm | 2 +- v2/crates/wifi-densepose-bfld/Cargo.toml | 2 +- .../wifi-densepose-bfld/src/ha_discovery.rs | 2 +- .../src/rumqttc_publisher.rs | 14 +- .../tests/mosquitto_integration.rs | 25 +- .../wifi-densepose-bfld/tests/rumqttc_lwt.rs | 4 +- .../tests/rumqttc_publisher_smoke.rs | 2 +- .../wifi-densepose-sensing-server/Cargo.toml | 2 +- .../wifi-densepose-sensing-server/src/main.rs | 92 +- .../src/mqtt/publisher.rs | 40 +- .../src/multistatic_bridge.rs | 118 ++- .../tests/mqtt_integration.rs | 13 +- 22 files changed, 579 insertions(+), 908 deletions(-) diff --git a/.github/workflows/security-scan.yml b/.github/workflows/security-scan.yml index deedcc53..68375748 100644 --- a/.github/workflows/security-scan.yml +++ b/.github/workflows/security-scan.yml @@ -14,6 +14,33 @@ env: PYTHON_VERSION: '3.11' jobs: + # Rust dependency advisories are deterministic for the checked-in lockfile, + # so this job gates the PR and retains the exact machine-readable report. + rust-audit: + name: Rust Dependency Audit + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: Checkout code + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + + - name: Install cargo-audit + run: cargo install cargo-audit --locked --version 0.22.2 + + - name: Audit the checked-in Rust lockfile + run: | + set -o pipefail + cargo audit --file v2/Cargo.lock --json | tee v2/cargo-audit.json + + - name: Upload Rust advisory report + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + if: always() + with: + name: cargo-audit-report + path: v2/cargo-audit.json + if-no-files-found: error + # Static Application Security Testing (SAST) sast: name: Static Application Security Testing diff --git a/README.md b/README.md index 01771892..5a16412d 100644 --- a/README.md +++ b/README.md @@ -245,8 +245,8 @@ See the measured benchmarks, witness records, and one-command reproducibility ch |------|-------|---------| | **MM-Fi pose model (SOTA)** | [`ruvnet/wifi-densepose-mmfi-pose`](https://huggingface.co/ruvnet/wifi-densepose-mmfi-pose) | 82.69% torso-PCK@20 (single) · 83.59% (ensemble+TTA) · 75K-param micro variant 74.30% | | **AetherArena benchmark Space** | [`ruvnet/aether-arena`](https://huggingface.co/spaces/ruvnet/aether-arena) | self-correcting, auditable MM-Fi leaderboard | -| **Full MM-Fi study (honest picture)** | [`docs/benchmarks/mmfi-wifi-sensing-study.md`](docs/benchmarks/mmfi-wifi-sensing-study.md) | pose + action; zero-shot cross-subject ~64%, +~30 s in-room calibration → 72.2% | -| **Efficiency frontier** | [`docs/benchmarks/wifi-pose-efficiency-frontier.md`](docs/benchmarks/wifi-pose-efficiency-frontier.md) | SOTA-beating WiFi pose in a 20 KB int4 edge model | +| **Full MM-Fi study (honest picture)** | [`docs/benchmarks/mmfi-wifi-sensing-study.md`](docs/benchmarks/mmfi-wifi-sensing-study.md) | pose + action; zero-shot cross-subject ~64%, labeled in-room calibration → 72.2% | +| **Efficiency frontier** | [`docs/benchmarks/wifi-pose-efficiency-frontier.md`](docs/benchmarks/wifi-pose-efficiency-frontier.md) | SOTA-beating MM-Fi pose in a ~37 KB int4 model; live ESP32 compatibility not established | | **Pretrained encoder** | [`ruvnet/wifi-densepose-pretrained`](https://huggingface.co/ruvnet/wifi-densepose-pretrained) | 82.3% held-out temporal-triplet, 8 KB int4 | | **Reproducible proof (Trust Kill Switch)** | [`archive/v1/data/proof/verify.py`](archive/v1/data/proof/verify.py) + [`expected_features.sha256`](archive/v1/data/proof/expected_features.sha256) | one-command deterministic pipeline replay (SHA-256 of output vs published hash) | | **Benchmark-proof ADR** | [ADR-168](docs/adr/ADR-168-benchmark-proof.md) | how the numbers are produced and verified | @@ -488,12 +488,20 @@ Neural Network: processed signals → 17 body keypoints + vital signs + room mod Output: real-time pose, breathing, heart rate, room fingerprint, drift alerts ``` -No training cameras required — the [Self-Learning system (ADR-024)](docs/adr/ADR-024-contrastive-csi-embedding-model.md) bootstraps from raw WiFi data alone. [MERIDIAN (ADR-027)](docs/adr/ADR-027-cross-environment-domain-generalization.md) ensures the model works in any room, not just the one it trained in. +The [Self-Learning system (ADR-024)](docs/adr/ADR-024-contrastive-csi-embedding-model.md) provides +camera-free representation-learning components. Cross-room pose remains a separate, data-gated +problem: [MERIDIAN (ADR-027)](docs/adr/ADR-027-cross-environment-domain-generalization.md) is +**Proposed**, while the measured calibration reference requires labeled CSI/keypoint pairs and +model-specific adapters. See the [model compatibility boundary](docs/user-guide.md#model-and-capture-compatibility). --- ## 🏢 Use Cases & Applications +> **Safety boundary:** these are research and prototype applications, not medical devices, +> emergency systems, or safety-certified controls. Vital-sign and pose outputs require independent +> validation on the exact hardware, room, subjects, and failure conditions before operational use. + WiFi sensing works anywhere WiFi exists. No new hardware in most cases — just software on existing access points or a $8 ESP32 add-on. Because there are no cameras, deployments avoid privacy regulations (GDPR video, HIPAA imaging) by design. **Scaling:** Each AP distinguishes ~3-5 people (56 subcarriers). Multi-AP multiplies linearly — a 4-AP retail mesh covers ~15-20 occupants. No hard software limit; the practical ceiling is signal physics. @@ -528,7 +536,7 @@ WiFi sensing works anywhere WiFi exists. No new hardware in most cases — just | Use Case | What It Does | Hardware | Key Metric | Edge Module | |----------|-------------|----------|------------|-------------| | **Smart home automation** | Room-level presence triggers (lights, HVAC, music) that work through walls — no dead zones, no motion-sensor timeouts | 2-3 ESP32-S3 nodes ($24) | Through-wall range ~5m | [HVAC Presence](docs/edge-modules/building.md), [Lighting Zones](docs/edge-modules/building.md) | -| **Fitness & sports** | Rep counting, posture correction, breathing cadence during exercise — no wearable, no camera in locker rooms | 3+ ESP32-S3 mesh | Pose: 17 keypoints | [Breathing Sync](docs/edge-modules/exotic.md), [Gait Analysis](docs/edge-modules/medical.md) | +| **Fitness & sports research** | Explore motion and breathing cadence without a wearable or camera; reliable posture correction requires a validated compatible pose model | 3+ ESP32-S3 mesh + edge host | Prototype; no live S3 pose accuracy claim | [Breathing Sync](docs/edge-modules/exotic.md), [Gait Analysis](docs/edge-modules/medical.md) | | **Childcare & schools** | Naptime breathing monitoring, playground headcount, restricted-area alerts — privacy-safe for minors | 2-4 ESP32-S3 per zone | Breathing: ±1 BPM | [Sleep Apnea](docs/edge-modules/medical.md), [Perimeter Breach](docs/edge-modules/security.md) | | **Event venues & concerts** | Crowd density mapping, crush-risk detection via breathing compression, emergency evacuation flow tracking | Multi-AP mesh (4-8 APs) | Density per m² | [Customer Flow](docs/edge-modules/retail.md), [Panic Motion](docs/edge-modules/security.md) | | **Stadiums & arenas** | Section-level occupancy for dynamic pricing, concession staffing, emergency egress flow modeling | Enterprise AP grid | 15-20 per AP mesh | [Dwell Heatmap](docs/edge-modules/retail.md), [Queue Length](docs/edge-modules/retail.md) | diff --git a/aether-arena/calibration/README.md b/aether-arena/calibration/README.md index 0b407525..a15cf3bc 100644 --- a/aether-arena/calibration/README.md +++ b/aether-arena/calibration/README.md @@ -1,8 +1,14 @@ # RuView Calibration Service (reference implementation) -Turn a **shared WiFi-CSI pose base model** into a room-specific one with a **30-second labeled -calibration** and a **~11 KB per-room LoRA adapter**. This is the deployable resolution of the -cross-subject / cross-environment generalization problem (full study: [ADR-150 §3.3–3.6](../../docs/adr/ADR-150-rf-foundation-encoder.md)). +Fit a room-specific **~11 KB LoRA adapter** for a shared WiFi-CSI pose base from a short **labeled +capture**. This is a measured MM-Fi reference path for cross-subject / cross-environment adaptation +(full study: [ADR-150 §3.3–3.6](../../docs/adr/ADR-150-rf-foundation-encoder.md)); it is not proof of +plug-and-play adaptation from a live ESP32 stream. + +> **Not the proposed MERIDIAN fast path.** Both producers below require paired CSI and keypoint +> labels, and their tensor shapes and adapter files are model-specific. ADR-027's automatic, +> unlabeled 10-second MERIDIAN calibration remains **Proposed** and is not implemented as an +> end-to-end deployment command. ## Why @@ -66,8 +72,8 @@ Adapters are **model-specific**. There are two calibration producers here: | `cog_calibrate.py` | cog **conv+MLP** (`pose_v1.safetensors`, 56×20) | `[N,56,20]` | `.safetensors` (`fc1.a`/`fc1.b`/`fc2.a`/`fc2.b`) | Rust `cog-pose-estimation run --adapter` | ```bash -# Produce a cog-format per-room adapter for the deployed Rust pose engine: -python cog_calibrate.py --base pose_v1.safetensors --data calib.npz --out room.safetensors +# Produce a cog-format per-room adapter from X:[N,56,20], Y:[N,17,2]: +python cog_calibrate.py --base pose_v1.safetensors --data cog-calib.npz --out room.safetensors # then in the cog runtime: cog-pose-estimation run --config --adapter room.safetensors ``` diff --git a/docs/benchmarks/wifi-pose-efficiency-frontier.md b/docs/benchmarks/wifi-pose-efficiency-frontier.md index f6df647a..41089990 100644 --- a/docs/benchmarks/wifi-pose-efficiency-frontier.md +++ b/docs/benchmarks/wifi-pose-efficiency-frontier.md @@ -5,10 +5,15 @@ PCK@20 (MultiFormer Table VII metric: `‖pred−gt‖ ≤ 0.2·‖R-shoulder The flagship [`ruvnet/wifi-densepose-mmfi-pose`](https://huggingface.co/ruvnet/wifi-densepose-mmfi-pose) reaches **83.59%** torso-PCK@20 (vs MultiFormer 72.25%, CSI2Pose 68.41%). But the headline number -isn't the whole story for **edge deployment** — on a Raspberry Pi / ESP32-class target, *params and +isn't the whole story for **edge deployment** — on a Raspberry Pi-class edge host, *params and latency* matter as much as accuracy. So we swept model size to map the **accuracy-per-parameter frontier**: how small can a WiFi-CSI pose model be and still beat the prior published SOTA? +> **Hardware compatibility boundary.** These models consume MM-Fi tensors shaped +> `[3,114,10]`. Parameter size alone does not make that input, model architecture, or runtime +> compatible with an ESP32-S3/C6 capture node. The measurements below are dataset and x86/GPU +> measurements; no ESP32 inference latency or live ESP32-to-MM-Fi adapter is claimed. + ## The frontier | Model | Params | Latency (batch=1) | torso-PCK@20 | vs SOTA (72.25%) | @@ -38,8 +43,10 @@ Size alone isn't the claim — what matters is **accuracy at the deployed precis **The honest edge result:** `micro` is **lossless at int8 (73.5 KB, 74.70%)**, and at **int4 (36.7 KB) naïve post-training quantization falls below SOTA (70.21%) — but quantization-aware training fully -recovers it to 74.46%**, still beating MultiFormer. So a **SOTA-beating WiFi-pose model genuinely runs -in ~37 KB int4** (with QAT) or **~73 KB int8** (no retraining) — deployable on the sensing node itself. +recovers it to 74.46%**, still beating MultiFormer. So a **SOTA-beating WiFi-pose model fits in +~37 KB int4** (with QAT) or **~73 KB int8** (no retraining). That is a model-footprint result, not +evidence that it runs on an ESP32 sensing node; a compatible capture adapter and embedded runtime +still need to be implemented and measured. `nano` (40K params) sits at the SOTA line in fp32 and is best treated as int8. (We also tested flagship→tiny **knowledge distillation**: it did *not* help — the tiny students reach diff --git a/docs/user-guide.md b/docs/user-guide.md index 9e5b5a9a..da59d4a9 100644 --- a/docs/user-guide.md +++ b/docs/user-guide.md @@ -75,7 +75,7 @@ WiFi DensePose turns commodity WiFi signals into real-time human pose estimation | Option | Cost | Capabilities | |--------|------|-------------| -| ESP32-S3 mesh (3-6 boards) | ~$54 | Full CSI: pose, breathing, heartbeat, presence | +| ESP32-S3 mesh (3-6 boards) | ~$54 | CSI capture for presence, motion, and vital-sign heuristics; live 17-keypoint pose remains below-target and is not a validated capability | | Intel 5300 / Atheros AR9580 | $50-100 | Full CSI with 3x3 MIMO (Linux only) | | Any WiFi laptop | $0 | RSSI-only: coarse presence and motion detection | @@ -1214,6 +1214,22 @@ levels. Read the label, not the headline ([ADR-187](adr/ADR-187-archive-v1-depre **Does it actually run, and can a single ESP32 do pose? ([#509](https://github.com/ruvnet/RuView/issues/509), [#1125](https://github.com/ruvnet/RuView/issues/1125))** Yes, it runs, and the results are reproducible: the deterministic signal-pipeline proof (`python archive/v1/data/proof/verify.py`, must print `VERDICT: PASS`), the committed pose training dump (`v2/crates/cog-pose-estimation/cog/artifacts/train_results.json`), and the auditable MM-Fi arena all back specific numbers. But a single-antenna, 56-subcarrier CSI stream at a 20-frame window does *not* carry the fine-grained spatial information the multi-antenna NIC research relies on — so the shippable pose accuracy the project stands behind today is the **MM-Fi benchmark number**, not a live single-ESP32 number. The path to a first reproducible on-device baseline (PCK@20 ≥ 35%) is tracked in [ADR-079](adr/ADR-079-camera-ground-truth-training.md) / [#645](https://github.com/ruvnet/RuView/issues/645). +### Model and capture compatibility + +These artifacts share a pose objective, but not an input contract or adapter format. A checkpoint +is usable only when capture preprocessing, tensor shape, architecture, and runtime all match. + +| Artifact/path | Required input | Measured status | Live ESP32 compatibility | +|---------------|----------------|-----------------|--------------------------| +| MM-Fi flagship and `micro` transformer | `[N,3,114,10]` amplitude | **MEASURED** on MM-Fi `random_split`; calibration reference is Python `.npz` LoRA | No direct S3/C6 path is validated; resampling a 56-tone SISO stream does not recreate three-antenna MM-Fi input | +| Cog `pose_v1.safetensors` | `[N,56,20]` amplitude | **MEASURED** PCK@20 = 3.0%, below the ≥35% target; cog-format LoRA is `.safetensors` | Shape matches the canonical window, but the documented live runtime remains below-target/stub and is not a reliable pose claim | +| Viewer `heuristic_pose_from_amplitude` | live canonical amplitude | Skeleton-layout placeholder, not a trained pose model | Renders a demonstrator skeleton only; it is not pose accuracy evidence | +| MERIDIAN automatic unlabeled calibration | proposed ~200-frame target-room capture | **PROPOSED** in ADR-027; no validated end-to-end command | Not available. The current calibration tools require paired CSI/keypoint labels and model-specific inputs | + +Calibration files are not interchangeable: `calibrate.py` targets the MM-Fi transformer, while +`cog_calibrate.py` targets the cog conv+MLP. See the +[calibration reference](../aether-arena/calibration/README.md) for their exact schemas. + ### Download ```bash @@ -1458,7 +1474,7 @@ The pipeline runs 10 phases: 3. Subcarrier resampling (114->56 or 30->56 via Catmull-Rom interpolation) 4. Graph transformer construction (17 COCO keypoints, 16 bone edges) 5. Cross-attention training (CSI features -> body pose) -6. **Domain-adversarial training** (MERIDIAN: gradient reversal + virtual domain augmentation) +6. Experimental domain-adversarial components (MERIDIAN research modules; not a validated automatic deployment path) 7. Composite loss optimization (MSE + CE + UV + temporal + bone + symmetry) 8. SONA adaptation (micro-LoRA + EWC++) 9. Sparse inference optimization (hot/cold neuron partitioning) @@ -1474,14 +1490,18 @@ Progressive loading enables instant startup (Layer A loads in <5ms with basic in ### Cross-Environment Adaptation (MERIDIAN) -Models trained in one room typically lose 40-70% accuracy in a new room due to different WiFi multipath patterns. The MERIDIAN system (ADR-027) solves this with a 10-second automatic calibration: +Models trained in one room can lose substantial accuracy in a new room because the multipath +distribution changes. ADR-027 proposes an automatic adaptation design, and the Rust tree contains +individual research components, but RuView does **not** currently provide a validated command that +turns ~200 unlabeled frames into a working room adapter. -1. **Deploy** the trained model in a new room -2. **Collect** ~200 unlabeled CSI frames (10 seconds at 20 Hz) -3. The system automatically generates environment-specific LoRA weights via contrastive test-time training -4. No labels, no retraining, no user intervention +Current, testable calibration is the separate **labeled** reference in +`aether-arena/calibration/`: collect paired CSI/keypoint samples, then fit a model-specific LoRA +adapter. The MM-Fi transformer expects `[N,3,114,10]`; the cog expects `[N,56,20]`. Neither adapter +loads into the other model, and neither result establishes live ESP32 pose accuracy without a +leakage-free held-out capture and mean-pose baseline. -MERIDIAN components (all pure Rust, +12K parameters): +ADR-027 research components: | Component | What it does | |-----------|-------------| @@ -1489,7 +1509,7 @@ MERIDIAN components (all pure Rust, +12K parameters): | Domain Factorizer | Separates pose-relevant from room-specific features | | Geometry Encoder | Encodes AP positions (FiLM conditioning with DeepSets) | | Virtual Augmentor | Generates synthetic environments for robust training | -| Rapid Adaptation | 10-second unsupervised calibration via contrastive TTT | +| Rapid Adaptation | Proposed unlabeled contrastive TTT; not wired as a validated deployment workflow | See [ADR-027](adr/ADR-027-cross-environment-domain-generalization.md) for the full design. @@ -1647,7 +1667,7 @@ Not every ADR-295–296 remediation item is preview-only. Three are live now: | Target | Use case | Source target flag | Notes | |---|---|---|---| -| **ESP32-S3** (default) | Production CSI mesh, 17-keypoint pose | `idf.py set-target esp32s3` | Dual-core 240 MHz, PSRAM, native USB-OTG, DVP camera path | +| **ESP32-S3** (default) | Production CSI capture mesh; presence/motion/vital heuristics | `idf.py set-target esp32s3` | Dual-core 240 MHz, PSRAM, native USB-OTG, DVP camera path; live 17-keypoint pose is not validated | | **ESP32-C6** ([ADR-110](adr/ADR-110-esp32-c6-firmware-extension.md)) | Wi-Fi 6 / 802.15.4 research, battery seed nodes | `idf.py set-target esp32c6` | Single-core 160 MHz, no PSRAM, 802.11ax HE PHY, 802.15.4 (Thread/Zigbee), LP-core hibernation ~5 µA | The same `firmware/esp32-csi-node` source tree builds for both. ESP-IDF picks up `sdkconfig.defaults.esp32c6` automatically when the target is set to `esp32c6`; otherwise it uses `sdkconfig.defaults` (S3). All C6-only modules are `#ifdef`-gated, so the S3 build is byte-identical to today. @@ -2071,7 +2091,11 @@ Pre-trained models are available on HuggingFace: - **SOTA MM-Fi pose model** (82.69% torso-PCK@20) — https://huggingface.co/ruvnet/wifi-densepose-mmfi-pose - **AetherArena leaderboard Space** — https://huggingface.co/spaces/ruvnet/aether-arena -Download and start sensing immediately — no datasets, no GPU, no training needed. Results are reproducible via `python archive/v1/data/proof/verify.py` (deterministic SHA-256 proof) — see [ADR-168](adr/ADR-168-benchmark-proof.md). +The encoder artifact can be downloaded for its documented inference path without retraining. The +MM-Fi pose checkpoint is benchmark evidence, not a drop-in live ESP32 model; it requires its exact +input contract and runtime described in [Model and capture compatibility](#model-and-capture-compatibility). +The deterministic signal-pipeline proof is reproducible via `python archive/v1/data/proof/verify.py` +(see [ADR-168](adr/ADR-168-benchmark-proof.md)), but that proof does not validate pose accuracy. ### Quick Start with Pre-Trained Models @@ -2636,7 +2660,12 @@ No. Run `docker run -p 3000:3000 ruvnet/wifi-densepose:latest` and open `http:// No. Consumer WiFi exposes only RSSI (one number per access point), not CSI (56+ complex subcarrier values per frame). RSSI supports coarse presence and motion detection. Full pose estimation requires CSI-capable hardware like an ESP32-S3 ($8) or a research NIC. **Q: How accurate is the pose estimation?** -Accuracy depends on hardware and environment. With a 3-node ESP32 mesh in a single room, the system tracks 17 COCO keypoints. The core algorithm follows the CMU "DensePose From WiFi" paper ([arXiv:2301.00250](https://arxiv.org/abs/2301.00250)). The MERIDIAN domain generalization system (ADR-027) reduces cross-environment accuracy loss from 40-70% to under 15% via 10-second automatic calibration. +The strongest published RuView result is the MM-Fi transformer benchmark (82.69% torso-PCK@20 on +the matched `random_split` protocol), not a live ESP32 result. The committed cog model measured +3.0% PCK@20 on its holdout, below the ≥35% target, and the viewer's live skeleton is a heuristic +placeholder. No measured claim currently shows a 3-node ESP32 mesh reliably tracking 17 keypoints. +ADR-027's 10-second unlabeled MERIDIAN adaptation is Proposed; the available measured calibration +reference uses labeled, model-specific samples. **Q: Does it work through walls?** Yes. WiFi signals penetrate non-metallic materials (drywall, wood, concrete up to ~30cm). Metal walls/doors significantly attenuate the signal. With a single AP the effective through-wall range is approximately 5 meters. With a 3-6 node multistatic mesh (ADR-029), attention-weighted cross-viewpoint fusion extends the effective range to ~8 meters through standard residential walls. diff --git a/v2/.cargo/audit.toml b/v2/.cargo/audit.toml index 60dfc335..3a93e85d 100644 --- a/v2/.cargo/audit.toml +++ b/v2/.cargo/audit.toml @@ -1,13 +1,9 @@ # cargo-audit configuration — v2 workspace # Managed by security audit (fix/security-audit-rustsec-clippy branch). # -# This file suppresses advisories in two categories: -# A) CVE-bearing advisories in TRANSITIVE deps we cannot upgrade directly -# because the parent published crate (ruvector-core 2.2.0) has not yet -# published a version with the fix. These are tracked as issues. -# B) UNMAINTAINED-only advisories (no CVE) flowing through dependencies -# that are purely transitive / build-time and have no user-facing attack -# surface in this workspace. +# This file suppresses UNMAINTAINED-only advisories (no CVE) flowing through +# dependencies that are purely transitive / build-time and have no +# user-facing attack surface in this workspace. # Each entry documents the root cause and the mitigation path. [advisories] @@ -24,26 +20,6 @@ # Mitigation: Accept transitively until Tauri v2 drops GTK3 or a workspace # override path becomes available. ignore = [ - # ----------------------------------------------------------------------- - # CATEGORY A — transitive CVEs from ruvector-core 2.2.0 → reqwest 0.11 - # ruvector-core 2.2.0 (latest on crates.io) depends on reqwest 0.11.27, - # which pulls in rustls 0.21 / rustls-webpki 0.101.7. We cannot upgrade - # this without a new ruvector-core release. Tracked in issue #812. - # The workspace's own TLS stack uses rustls-webpki 0.103.13 (patched); - # the vulnerable 0.101.7 instance is not reachable from our TLS code. - "RUSTSEC-2026-0098", # rustls-webpki 0.101.7: URI name constraint bypass - "RUSTSEC-2026-0099", # rustls-webpki 0.101.7: wildcard name constraint bypass - "RUSTSEC-2026-0104", # rustls-webpki 0.101.7: reachable panic in CRL parsing - # quinn-proto 0.11.13 is also pulled through midstreamer-quic 0.3 (now - # upgraded). The remaining 0.11.13 instance comes from the same - # ruvector-core transitive chain. Tracked in issue #812. - "RUSTSEC-2026-0037", # quinn-proto 0.11.13: DoS in Quinn endpoints - # CRL Distribution Point matching bug — same ruvector-core / reqwest 0.11 - # transitive chain; rustls-webpki 0.101.7 also affected. - "RUSTSEC-2026-0049", # rustls-webpki <0.103.10: CRL authority matching - - # ----------------------------------------------------------------------- - # CATEGORY B — unmaintained / no CVE "RUSTSEC-2024-0411", # gdkwayland-sys: unmaintained "RUSTSEC-2024-0412", # gdk: unmaintained "RUSTSEC-2024-0413", # atk: unmaintained diff --git a/v2/Cargo.lock b/v2/Cargo.lock index c8fda42c..07bac71d 100644 --- a/v2/Cargo.lock +++ b/v2/Cargo.lock @@ -321,6 +321,26 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "async-tungstenite" +version = "0.34.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8447f02eaa65412035e2d3eeaa3fc82bbb8d7137c84c5976b4af685136012ee9" +dependencies = [ + "atomic-waker", + "futures-core", + "futures-io", + "futures-task", + "futures-util", + "log", + "pin-project-lite", + "rustls-native-certs", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tungstenite 0.29.0", +] + [[package]] name = "atk" version = "0.18.2" @@ -392,10 +412,10 @@ dependencies = [ "base64 0.22.1", "bytes", "futures-util", - "http 1.4.0", - "http-body 1.0.1", + "http", + "http-body", "http-body-util", - "hyper 1.8.1", + "hyper", "hyper-util", "itoa", "matchit", @@ -409,7 +429,7 @@ dependencies = [ "serde_path_to_error", "serde_urlencoded", "sha1", - "sync_wrapper 1.0.2", + "sync_wrapper", "tokio", "tokio-tungstenite", "tower 0.5.3", @@ -427,13 +447,13 @@ dependencies = [ "async-trait", "bytes", "futures-util", - "http 1.4.0", - "http-body 1.0.1", + "http", + "http-body", "http-body-util", "mime", "pin-project-lite", "rustversion", - "sync_wrapper 1.0.2", + "sync_wrapper", "tower-layer", "tower-service", "tracing", @@ -1916,12 +1936,6 @@ dependencies = [ "crc-catalog", ] -[[package]] -name = "crc-any" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a62ec9ff5f7965e4d7280bd5482acd20aadb50d632cf6c1d74493856b011fa73" - [[package]] name = "crc-catalog" version = "2.5.0" @@ -2964,12 +2978,6 @@ dependencies = [ "litrs", ] -[[package]] -name = "dotenvy" -version = "0.15.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" - [[package]] name = "downcast-rs" version = "2.0.2" @@ -3091,9 +3099,6 @@ name = "either" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" -dependencies = [ - "serde", -] [[package]] name = "elliptic-curve" @@ -3157,7 +3162,7 @@ dependencies = [ "rustc_version", "toml 0.9.12+spec-1.1.0", "vswhom", - "winreg 0.55.0", + "winreg", ] [[package]] @@ -3331,17 +3336,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "etcetera" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" -dependencies = [ - "cfg-if", - "home", - "windows-sys 0.48.0", -] - [[package]] name = "event-listener" version = "5.4.2" @@ -3522,6 +3516,17 @@ dependencies = [ "spin 0.9.8", ] +[[package]] +name = "flume" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e139bc46ca777eb5efaf62df0ab8cc5fd400866427e56c68b22e414e53bd3be" +dependencies = [ + "futures-core", + "futures-sink", + "spin 0.9.8", +] + [[package]] name = "fnv" version = "1.0.7" @@ -4689,35 +4694,16 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.27" +version = "0.4.16" 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 2.13.0", - "slab", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "h2" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "171fefbc92fe4a4de27e0698d6a5b392d6a0e333506bc49133760b3bcf948733" +checksum = "a9f37a958b41b3b19ee2707c06439c0e9e547e847223eb791ecb0cb821c65e27" dependencies = [ "atomic-waker", "bytes", "fnv", "futures-core", "futures-sink", - "http 1.4.0", + "http", "indexmap 2.13.0", "slab", "tokio", @@ -4956,15 +4942,6 @@ dependencies = [ "serde", ] -[[package]] -name = "home" -version = "0.5.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" -dependencies = [ - "windows-sys 0.61.2", -] - [[package]] name = "homecore" version = "0.1.0-alpha.0" @@ -4994,7 +4971,7 @@ dependencies = [ "homecore-automation", "homecore-recorder", "http-body-util", - "hyper 1.8.1", + "hyper", "serde", "serde_json", "thiserror 1.0.69", @@ -5110,7 +5087,8 @@ dependencies = [ "serde", "serde_json", "sha2 0.10.9", - "sqlx", + "sqlx-core", + "sqlx-sqlite", "thiserror 1.0.69", "tokio", "tracing", @@ -5157,17 +5135,6 @@ dependencies = [ "match_token", ] -[[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" @@ -5178,17 +5145,6 @@ dependencies = [ "itoa", ] -[[package]] -name = "http-body" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" -dependencies = [ - "bytes", - "http 0.2.12", - "pin-project-lite", -] - [[package]] name = "http-body" version = "1.0.1" @@ -5196,7 +5152,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "http 1.4.0", + "http", ] [[package]] @@ -5207,8 +5163,8 @@ checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", "futures-core", - "http 1.4.0", - "http-body 1.0.1", + "http", + "http-body", "pin-project-lite", ] @@ -5239,30 +5195,6 @@ dependencies = [ "typenum", ] -[[package]] -name = "hyper" -version = "0.14.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" -dependencies = [ - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "h2 0.3.27", - "http 0.2.12", - "http-body 0.4.6", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "socket2 0.5.10", - "tokio", - "tower-service", - "tracing", - "want", -] - [[package]] name = "hyper" version = "1.8.1" @@ -5273,9 +5205,9 @@ dependencies = [ "bytes", "futures-channel", "futures-core", - "h2 0.4.14", - "http 1.4.0", - "http-body 1.0.1", + "h2", + "http", + "http-body", "httparse", "httpdate", "itoa", @@ -5286,32 +5218,18 @@ dependencies = [ "want", ] -[[package]] -name = "hyper-rustls" -version = "0.24.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" -dependencies = [ - "futures-util", - "http 0.2.12", - "hyper 0.14.32", - "rustls 0.21.12", - "tokio", - "tokio-rustls 0.24.1", -] - [[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 1.8.1", + "http", + "hyper", "hyper-util", - "rustls 0.23.37", + "rustls", "tokio", - "tokio-rustls 0.26.4", + "tokio-rustls", "tower-service", "webpki-roots 1.0.7", ] @@ -5322,7 +5240,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0" dependencies = [ - "hyper 1.8.1", + "hyper", "hyper-util", "pin-project-lite", "tokio", @@ -5337,7 +5255,7 @@ checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ "bytes", "http-body-util", - "hyper 1.8.1", + "hyper", "hyper-util", "native-tls", "tokio", @@ -5355,15 +5273,15 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "http 1.4.0", - "http-body 1.0.1", - "hyper 1.8.1", + "http", + "http-body", + "hyper", "ipnet", "libc", "percent-encoding", "pin-project-lite", - "socket2 0.6.2", - "system-configuration 0.7.0", + "socket2 0.5.10", + "system-configuration", "tokio", "tower-service", "tracing", @@ -5598,15 +5516,6 @@ dependencies = [ "mach2", ] -[[package]] -name = "ioctl-rs" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7970510895cee30b3e9128319f2cefd4bde883a39f38baa279567ba3a7eb97d" -dependencies = [ - "libc", -] - [[package]] name = "ipnet" version = "2.12.0" @@ -5917,9 +5826,6 @@ name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" -dependencies = [ - "spin 0.9.8", -] [[package]] name = "leb128fmt" @@ -6189,58 +6095,6 @@ dependencies = [ "rawpointer", ] -[[package]] -name = "mavlink" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94356eb6ed56a834d6dca79a8c33c650d3d03d3ea79ae762ec1c9182b6fdc1e2" -dependencies = [ - "bitflags 1.3.2", - "mavlink-bindgen", - "mavlink-core", - "num-derive 0.3.3", - "num-traits", - "serde", - "serde_arrays", -] - -[[package]] -name = "mavlink-bindgen" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6c28f3eafc35544c7b4aee7cf9ec35b96c79a05de4bad3fe145bdac23570b04" -dependencies = [ - "crc-any", - "lazy_static", - "proc-macro2", - "quick-xml 0.36.2", - "quote", - "thiserror 1.0.69", -] - -[[package]] -name = "mavlink-core" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e64d975ca3cf0ad8a7c278553f91d77de15fcde9b79bf6bc542e209dd0c7dee" -dependencies = [ - "byteorder", - "crc-any", - "serde", - "serde_arrays", - "serial", -] - -[[package]] -name = "md-5" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" -dependencies = [ - "cfg-if", - "digest 0.10.7", -] - [[package]] name = "md5" version = "0.8.1" @@ -6253,7 +6107,7 @@ version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fe7c11a1eb3cfbfcf702d1601c1f5f4c102cdc8665b8a557783ef634741676e" dependencies = [ - "flume", + "flume 0.11.1", "if-addrs", "log", "polling", @@ -6460,6 +6314,16 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a0b3262dc837d2513fe2ef31ff8461352ef932dcca31ba0c0abe33547cf6b9b" +[[package]] +name = "mqttbytes-core-next" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "366b6ba2b4209ca4bc5ac731ccddf570d09831981eed07e5fbd63564cf0cf1aa" +dependencies = [ + "bytes", + "thiserror 2.0.18", +] + [[package]] name = "muda" version = "0.17.1" @@ -6637,10 +6501,10 @@ dependencies = [ "libc", "log", "openssl", - "openssl-probe 0.2.1", + "openssl-probe", "openssl-sys", "schannel", - "security-framework 3.7.0", + "security-framework", "security-framework-sys", "tempfile", ] @@ -6882,22 +6746,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "num-bigint-dig" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" -dependencies = [ - "lazy_static", - "libm", - "num-integer", - "num-iter", - "num-traits", - "rand 0.8.5", - "smallvec", - "zeroize", -] - [[package]] name = "num-complex" version = "0.4.6" @@ -6916,17 +6764,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" -[[package]] -name = "num-derive" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "num-derive" version = "0.4.2" @@ -7295,12 +7132,6 @@ dependencies = [ "syn 2.0.117", ] -[[package]] -name = "openssl-probe" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" - [[package]] name = "openssl-probe" version = "0.2.1" @@ -7346,7 +7177,7 @@ version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9966929966d17620d7c316c643ba62631826e10021409357772d5eea84f62c35" dependencies = [ - "http 1.4.0", + "http", "opentelemetry", "opentelemetry-proto", "opentelemetry_sdk", @@ -7540,7 +7371,7 @@ dependencies = [ "hashbrown 0.17.1", "indexmap 2.13.0", "log", - "num-derive 0.4.2", + "num-derive", "num-traits", "ordered-float 5.3.0", "rstar 0.13.0", @@ -7849,17 +7680,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" -[[package]] -name = "pkcs1" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" -dependencies = [ - "der", - "pkcs8", - "spki", -] - [[package]] name = "pkcs8" version = "0.10.2" @@ -7884,13 +7704,13 @@ checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" [[package]] name = "plist" -version = "1.8.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "740ebea15c5d1428f910cd1a5f52cebf8d25006245ed8ade92702f4943d91e07" +checksum = "7da1d65da6dd5d1e44199ac0f58712d241c0f439f80adea8924d832384087f85" dependencies = [ "base64 0.22.1", "indexmap 2.13.0", - "quick-xml 0.38.4", + "quick-xml", "serde", "time", ] @@ -8231,7 +8051,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b570b25f7617e43d59005d0990ccb79e950a423952cea19671b7a876da390adf" dependencies = [ "anyhow", - "itertools 0.10.5", + "itertools 0.14.0", "proc-macro2", "quote", "syn 2.0.117", @@ -8359,18 +8179,9 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quick-xml" -version = "0.36.2" +version = "0.41.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7649a7b4df05aed9ea7ec6f628c67c9953a43869b8bc50929569b2999d443fe" -dependencies = [ - "memchr", -] - -[[package]] -name = "quick-xml" -version = "0.38.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b66c2058c55a409d601666cffe35f04333cf1013010882cec174a7467cd4e21c" +checksum = "e660451e55124f798a69a5af3f49ccfbefbd41910eefd25caf2393e1f3473ec1" dependencies = [ "memchr", ] @@ -8387,8 +8198,8 @@ dependencies = [ "quinn-proto", "quinn-udp", "rustc-hash 2.1.1", - "rustls 0.23.37", - "socket2 0.6.2", + "rustls", + "socket2 0.5.10", "thiserror 2.0.18", "tokio", "tracing", @@ -8408,7 +8219,7 @@ dependencies = [ "rand 0.9.2", "ring", "rustc-hash 2.1.1", - "rustls 0.23.37", + "rustls", "rustls-pki-types", "rustls-platform-verifier", "slab", @@ -8427,7 +8238,7 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.6.2", + "socket2 0.5.10", "tracing", "windows-sys 0.52.0", ] @@ -8862,47 +8673,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" -[[package]] -name = "reqwest" -version = "0.11.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" -dependencies = [ - "base64 0.21.7", - "bytes", - "encoding_rs", - "futures-core", - "futures-util", - "h2 0.3.27", - "http 0.2.12", - "http-body 0.4.6", - "hyper 0.14.32", - "hyper-rustls 0.24.2", - "ipnet", - "js-sys", - "log", - "mime", - "once_cell", - "percent-encoding", - "pin-project-lite", - "rustls 0.21.12", - "rustls-pemfile 1.0.4", - "serde", - "serde_json", - "serde_urlencoded", - "sync_wrapper 0.1.2", - "system-configuration 0.5.1", - "tokio", - "tokio-rustls 0.24.1", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "webpki-roots 0.25.4", - "winreg 0.50.0", -] - [[package]] name = "reqwest" version = "0.12.28" @@ -8912,14 +8682,15 @@ dependencies = [ "base64 0.22.1", "bytes", "encoding_rs", + "futures-channel", "futures-core", "futures-util", - "h2 0.4.14", - "http 1.4.0", - "http-body 1.0.1", + "h2", + "http", + "http-body", "http-body-util", - "hyper 1.8.1", - "hyper-rustls 0.27.9", + "hyper", + "hyper-rustls", "hyper-tls", "hyper-util", "js-sys", @@ -8930,15 +8701,15 @@ dependencies = [ "percent-encoding", "pin-project-lite", "quinn", - "rustls 0.23.37", + "rustls", "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", - "sync_wrapper 1.0.2", + "sync_wrapper", "tokio", "tokio-native-tls", - "tokio-rustls 0.26.4", + "tokio-rustls", "tower 0.5.3", "tower-http", "tower-service", @@ -8959,10 +8730,10 @@ dependencies = [ "bytes", "futures-core", "futures-util", - "http 1.4.0", - "http-body 1.0.1", + "http", + "http-body", "http-body-util", - "hyper 1.8.1", + "hyper", "hyper-util", "js-sys", "log", @@ -8970,7 +8741,7 @@ dependencies = [ "pin-project-lite", "serde", "serde_json", - "sync_wrapper 1.0.2", + "sync_wrapper", "tokio", "tokio-util", "tower 0.5.3", @@ -9096,26 +8867,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4e27ee8bb91ca0adcf0ecb116293afa12d393f9c2b9b9cd54d33e8078fe19839" -[[package]] -name = "rsa" -version = "0.9.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" -dependencies = [ - "const-oid 0.9.6", - "digest 0.10.7", - "num-bigint-dig", - "num-integer", - "num-traits", - "pkcs1", - "pkcs8", - "rand_core 0.6.4", - "signature", - "spki", - "subtle", - "zeroize", -] - [[package]] name = "rstar" version = "0.8.4" @@ -9254,21 +9005,41 @@ dependencies = [ ] [[package]] -name = "rumqttc" -version = "0.24.0" +name = "rumqttc-core-next" +version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1568e15fab2d546f940ed3a21f48bbbd1c494c90c99c4481339364a497f94a9" +checksum = "249896ab27ed630590971738264baa8f722f18965d2e387c706c40a3c2a572cc" dependencies = [ + "async-tungstenite", + "rustls-native-certs", + "rustls-pki-types", + "rustls-webpki", + "thiserror 2.0.18", + "tokio", + "tokio-rustls", +] + +[[package]] +name = "rumqttc-v4-next" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3767dde22a804ea806107d0f4331e33cb794d1b8468b11c86fb0c1366f07054" +dependencies = [ + "async-tungstenite", "bytes", - "flume", + "fixedbitset 0.5.7", + "flume 0.12.0", "futures-util", "log", - "rustls-native-certs 0.7.3", - "rustls-pemfile 2.2.0", - "rustls-webpki 0.102.8", - "thiserror 1.0.69", + "mqttbytes-core-next", + "rumqttc-core-next", + "rustls-native-certs", + "rustls-pki-types", + "rustls-webpki", + "thiserror 2.0.18", "tokio", - "tokio-rustls 0.25.0", + "tokio-rustls", + "tokio-util", ] [[package]] @@ -9325,32 +9096,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "rustls" -version = "0.21.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" -dependencies = [ - "log", - "ring", - "rustls-webpki 0.101.7", - "sct", -] - -[[package]] -name = "rustls" -version = "0.22.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" -dependencies = [ - "log", - "ring", - "rustls-pki-types", - "rustls-webpki 0.102.8", - "subtle", - "zeroize", -] - [[package]] name = "rustls" version = "0.23.37" @@ -9361,52 +9106,21 @@ dependencies = [ "once_cell", "ring", "rustls-pki-types", - "rustls-webpki 0.103.13", + "rustls-webpki", "subtle", "zeroize", ] -[[package]] -name = "rustls-native-certs" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5bfb394eeed242e909609f56089eecfe5fda225042e8b171791b9c95f5931e5" -dependencies = [ - "openssl-probe 0.1.6", - "rustls-pemfile 2.2.0", - "rustls-pki-types", - "schannel", - "security-framework 2.11.1", -] - [[package]] name = "rustls-native-certs" version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" dependencies = [ - "openssl-probe 0.2.1", + "openssl-probe", "rustls-pki-types", "schannel", - "security-framework 3.7.0", -] - -[[package]] -name = "rustls-pemfile" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" -dependencies = [ - "base64 0.21.7", -] - -[[package]] -name = "rustls-pemfile" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" -dependencies = [ - "rustls-pki-types", + "security-framework", ] [[package]] @@ -9430,11 +9144,11 @@ dependencies = [ "jni", "log", "once_cell", - "rustls 0.23.37", - "rustls-native-certs 0.8.3", + "rustls", + "rustls-native-certs", "rustls-platform-verifier-android", - "rustls-webpki 0.103.13", - "security-framework 3.7.0", + "rustls-webpki", + "security-framework", "security-framework-sys", "webpki-root-certs", "windows-sys 0.52.0", @@ -9446,27 +9160,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" -[[package]] -name = "rustls-webpki" -version = "0.101.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" -dependencies = [ - "ring", - "untrusted", -] - -[[package]] -name = "rustls-webpki" -version = "0.102.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" -dependencies = [ - "ring", - "rustls-pki-types", - "untrusted", -] - [[package]] name = "rustls-webpki" version = "0.103.13" @@ -9533,9 +9226,9 @@ dependencies = [ [[package]] name = "ruvector-core" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fe578d02c93613b26d40105c9663df5e86414195e91db0ea7470d3b9c64843" +checksum = "ecaff2299e821f1f9aacd85f6fd16a16de6e7245b67843415d4f49f2f5f70084" dependencies = [ "anyhow", "bincode 2.0.1", @@ -9551,7 +9244,7 @@ dependencies = [ "rand_distr 0.4.3", "rayon", "redb", - "reqwest 0.11.27", + "reqwest 0.12.28", "rkyv", "serde", "serde_json", @@ -9869,7 +9562,6 @@ dependencies = [ "candle-nn 0.9.2", "criterion", "hmac", - "mavlink", "nalgebra 0.33.2", "ort", "rand 0.8.5", @@ -10069,16 +9761,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" -[[package]] -name = "sct" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" -dependencies = [ - "ring", - "untrusted", -] - [[package]] name = "sec1" version = "0.7.3" @@ -10093,19 +9775,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "security-framework" -version = "2.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" -dependencies = [ - "bitflags 2.11.0", - "core-foundation 0.9.4", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - [[package]] name = "security-framework" version = "3.7.0" @@ -10196,15 +9865,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "serde_arrays" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38636132857f68ec3d5f3eb121166d2af33cb55174c4d5ff645db6165cbef0fd" -dependencies = [ - "serde", -] - [[package]] name = "serde_bytes" version = "0.11.19" @@ -10365,48 +10025,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serial" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1237a96570fc377c13baa1b88c7589ab66edced652e43ffb17088f003db3e86" -dependencies = [ - "serial-core", - "serial-unix", - "serial-windows", -] - -[[package]] -name = "serial-core" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f46209b345401737ae2125fe5b19a77acce90cd53e1658cda928e4fe9a64581" -dependencies = [ - "libc", -] - -[[package]] -name = "serial-unix" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f03fbca4c9d866e24a459cbca71283f545a37f8e3e002ad8c70593871453cab7" -dependencies = [ - "ioctl-rs", - "libc", - "serial-core", - "termios", -] - -[[package]] -name = "serial-windows" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15c6d3b776267a75d31bbdfd5d36c0ca051251caafc285827052bc53bcdc8162" -dependencies = [ - "libc", - "serial-core", -] - [[package]] name = "serialize-to-javascript" version = "0.1.2" @@ -10782,19 +10400,6 @@ dependencies = [ "der", ] -[[package]] -name = "sqlx" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fefb893899429669dcdd979aff487bd78f4064e5e7907e4269081e0ef7d97dc" -dependencies = [ - "sqlx-core", - "sqlx-macros", - "sqlx-mysql", - "sqlx-postgres", - "sqlx-sqlite", -] - [[package]] name = "sqlx-core" version = "0.8.6" @@ -10804,7 +10409,6 @@ dependencies = [ "base64 0.22.1", "bytes", "chrono", - "crc", "crossbeam-queue", "either", "event-listener", @@ -10817,12 +10421,8 @@ dependencies = [ "indexmap 2.13.0", "log", "memchr", - "native-tls", "once_cell", "percent-encoding", - "serde", - "serde_json", - "sha2 0.10.9", "smallvec", "thiserror 2.0.18", "tokio", @@ -10832,127 +10432,6 @@ dependencies = [ "uuid", ] -[[package]] -name = "sqlx-macros" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2d452988ccaacfbf5e0bdbc348fb91d7c8af5bee192173ac3636b5fb6e6715d" -dependencies = [ - "proc-macro2", - "quote", - "sqlx-core", - "sqlx-macros-core", - "syn 2.0.117", -] - -[[package]] -name = "sqlx-macros-core" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19a9c1841124ac5a61741f96e1d9e2ec77424bf323962dd894bdb93f37d5219b" -dependencies = [ - "dotenvy", - "either", - "heck 0.5.0", - "hex", - "once_cell", - "proc-macro2", - "quote", - "serde", - "serde_json", - "sha2 0.10.9", - "sqlx-core", - "sqlx-mysql", - "sqlx-postgres", - "sqlx-sqlite", - "syn 2.0.117", - "tokio", - "url", -] - -[[package]] -name = "sqlx-mysql" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" -dependencies = [ - "atoi", - "base64 0.22.1", - "bitflags 2.11.0", - "byteorder", - "bytes", - "chrono", - "crc", - "digest 0.10.7", - "dotenvy", - "either", - "futures-channel", - "futures-core", - "futures-io", - "futures-util", - "generic-array 0.14.7", - "hex", - "hkdf", - "hmac", - "itoa", - "log", - "md-5", - "memchr", - "once_cell", - "percent-encoding", - "rand 0.8.5", - "rsa", - "serde", - "sha1", - "sha2 0.10.9", - "smallvec", - "sqlx-core", - "stringprep", - "thiserror 2.0.18", - "tracing", - "uuid", - "whoami", -] - -[[package]] -name = "sqlx-postgres" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" -dependencies = [ - "atoi", - "base64 0.22.1", - "bitflags 2.11.0", - "byteorder", - "chrono", - "crc", - "dotenvy", - "etcetera", - "futures-channel", - "futures-core", - "futures-util", - "hex", - "hkdf", - "hmac", - "home", - "itoa", - "log", - "md-5", - "memchr", - "once_cell", - "rand 0.8.5", - "serde", - "serde_json", - "sha2 0.10.9", - "smallvec", - "sqlx-core", - "stringprep", - "thiserror 2.0.18", - "tracing", - "uuid", - "whoami", -] - [[package]] name = "sqlx-sqlite" version = "0.8.6" @@ -10961,7 +10440,7 @@ checksum = "c2d12fe70b2c1b4401038055f90f151b78208de1f9f89a7dbfd41587a10c3eea" dependencies = [ "atoi", "chrono", - "flume", + "flume 0.11.1", "futures-channel", "futures-core", "futures-executor", @@ -10970,7 +10449,6 @@ dependencies = [ "libsqlite3-sys", "log", "percent-encoding", - "serde", "serde_urlencoded", "sqlx-core", "thiserror 2.0.18", @@ -11040,17 +10518,6 @@ dependencies = [ "quote", ] -[[package]] -name = "stringprep" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" -dependencies = [ - "unicode-bidi", - "unicode-normalization", - "unicode-properties", -] - [[package]] name = "strsim" version = "0.11.1" @@ -11123,12 +10590,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "sync_wrapper" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" - [[package]] name = "sync_wrapper" version = "1.0.2" @@ -11191,17 +10652,6 @@ dependencies = [ "windows 0.57.0", ] -[[package]] -name = "system-configuration" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" -dependencies = [ - "bitflags 1.3.2", - "core-foundation 0.9.4", - "system-configuration-sys 0.5.0", -] - [[package]] name = "system-configuration" version = "0.7.0" @@ -11210,17 +10660,7 @@ checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" dependencies = [ "bitflags 2.11.0", "core-foundation 0.9.4", - "system-configuration-sys 0.6.0", -] - -[[package]] -name = "system-configuration-sys" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" -dependencies = [ - "core-foundation-sys", - "libc", + "system-configuration-sys", ] [[package]] @@ -11360,7 +10800,7 @@ dependencies = [ "glob", "gtk", "heck 0.5.0", - "http 1.4.0", + "http", "jni", "libc", "log", @@ -11547,7 +10987,7 @@ dependencies = [ "cookie", "dpi", "gtk", - "http 1.4.0", + "http", "jni", "objc2", "objc2-ui-kit", @@ -11570,7 +11010,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e11ea2e6f801d275fdd890d6c9603736012742a1c33b96d0db788c9cdebf7f9e" dependencies = [ "gtk", - "http 1.4.0", + "http", "jni", "log", "objc2", @@ -11602,7 +11042,7 @@ dependencies = [ "dunce", "glob", "html5ever", - "http 1.4.0", + "http", "infer", "json-patch", "kuchikiki", @@ -11688,15 +11128,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "termios" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5d9cf598a6d7ce700a4e6a9199da127e6819a61e64b68609683cc9a01b5683a" -dependencies = [ - "libc", -] - [[package]] name = "termtree" version = "0.5.1" @@ -11886,34 +11317,13 @@ dependencies = [ "tokio", ] -[[package]] -name = "tokio-rustls" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" -dependencies = [ - "rustls 0.21.12", - "tokio", -] - -[[package]] -name = "tokio-rustls" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" -dependencies = [ - "rustls 0.22.4", - "rustls-pki-types", - "tokio", -] - [[package]] name = "tokio-rustls" version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" dependencies = [ - "rustls 0.23.37", + "rustls", "tokio", ] @@ -11962,7 +11372,7 @@ dependencies = [ "futures-util", "log", "tokio", - "tungstenite", + "tungstenite 0.24.0", ] [[package]] @@ -12125,18 +11535,18 @@ dependencies = [ "async-trait", "base64 0.22.1", "bytes", - "http 1.4.0", - "http-body 1.0.1", + "http", + "http-body", "http-body-util", - "hyper 1.8.1", + "hyper", "hyper-timeout", "hyper-util", "percent-encoding", "pin-project", - "rustls-native-certs 0.8.3", - "sync_wrapper 1.0.2", + "rustls-native-certs", + "sync_wrapper", "tokio", - "tokio-rustls 0.26.4", + "tokio-rustls", "tokio-stream", "tower 0.5.3", "tower-layer", @@ -12210,7 +11620,7 @@ dependencies = [ "indexmap 2.13.0", "pin-project-lite", "slab", - "sync_wrapper 1.0.2", + "sync_wrapper", "tokio", "tokio-util", "tower-layer", @@ -12229,8 +11639,8 @@ dependencies = [ "bytes", "futures-core", "futures-util", - "http 1.4.0", - "http-body 1.0.1", + "http", + "http-body", "http-body-util", "http-range-header", "httpdate", @@ -12394,7 +11804,7 @@ dependencies = [ "byteorder", "bytes", "data-encoding", - "http 1.4.0", + "http", "httparse", "log", "rand 0.8.5", @@ -12403,6 +11813,24 @@ dependencies = [ "utf-8", ] +[[package]] +name = "tungstenite" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c01152af293afb9c7c2a57e4b559c5620b421f6d133261c60dd2d0cdb38e6b8" +dependencies = [ + "bytes", + "data-encoding", + "http", + "httparse", + "log", + "rand 0.9.2", + "rustls", + "rustls-pki-types", + "sha1", + "thiserror 2.0.18", +] + [[package]] name = "tynm" version = "0.2.0" @@ -12541,33 +11969,12 @@ version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" -[[package]] -name = "unicode-bidi" -version = "0.3.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" - [[package]] name = "unicode-ident" version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" -[[package]] -name = "unicode-normalization" -version = "0.1.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" -dependencies = [ - "tinyvec", -] - -[[package]] -name = "unicode-properties" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" - [[package]] name = "unicode-segmentation" version = "1.12.0" @@ -12623,7 +12030,7 @@ dependencies = [ "base64 0.22.1", "log", "once_cell", - "rustls 0.23.37", + "rustls", "rustls-pki-types", "serde", "serde_json", @@ -12656,7 +12063,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d81f9efa9df032be5934a46a068815a10a042b494b6a58cb0a1a97bb5467ed6f" dependencies = [ "base64 0.22.1", - "http 1.4.0", + "http", "httparse", "log", ] @@ -12851,12 +12258,6 @@ dependencies = [ "wit-bindgen", ] -[[package]] -name = "wasite" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" - [[package]] name = "wasm-bindgen" version = "0.2.114" @@ -13443,12 +12844,6 @@ dependencies = [ "rustls-pki-types", ] -[[package]] -name = "webpki-roots" -version = "0.25.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" - [[package]] name = "webpki-roots" version = "0.26.11" @@ -13671,16 +13066,6 @@ dependencies = [ "web-sys", ] -[[package]] -name = "whoami" -version = "1.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d4a4db5077702ca3015d3d02d74974948aba2ad9e12ab7df718ee64ccd7e97d" -dependencies = [ - "libredox", - "wasite", -] - [[package]] name = "wide" version = "0.7.33" @@ -13721,7 +13106,7 @@ dependencies = [ "blake3", "crc", "proptest", - "rumqttc", + "rumqttc-v4-next", "serde", "serde_json", "static_assertions", @@ -13803,7 +13188,7 @@ name = "wifi-densepose-desktop" version = "0.3.1" dependencies = [ "chrono", - "flume", + "flume 0.11.1", "futures", "hex", "hmac", @@ -14053,7 +13438,7 @@ dependencies = [ "p256", "proptest", "rand 0.8.5", - "rumqttc", + "rumqttc-v4-next", "ruvector-mincut", "ruview-auth", "serde", @@ -14872,16 +14257,6 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2ee1708bef14716a11bae175f579062d4554d95be2c6829f518df847b7b3fdd0" -[[package]] -name = "winreg" -version = "0.50.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" -dependencies = [ - "cfg-if", - "windows-sys 0.48.0", -] - [[package]] name = "winreg" version = "0.55.0" @@ -15020,7 +14395,7 @@ dependencies = [ "gdkx11", "gtk", "html5ever", - "http 1.4.0", + "http", "javascriptcore-rs", "jni", "kuchikiki", diff --git a/v2/Cargo.toml b/v2/Cargo.toml index 72f65489..629bf0ee 100644 --- a/v2/Cargo.toml +++ b/v2/Cargo.toml @@ -255,7 +255,7 @@ midstreamer-attractor = "0.2" # ruvector integration (published on crates.io) # Vendored at origin/main (a083bd77f) in vendor/ruvector; using crates.io versions # until published. Bumps per ADR-152 §2.6 (2026-06-10 vendor sync survey). -ruvector-core = "2.2.0" +ruvector-core = "2.3.0" ruvector-mincut = "2.0.6" ruvector-attn-mincut = "2.0.4" ruvector-temporal-tensor = "2.0.6" diff --git a/v2/crates/homecore-recorder/Cargo.toml b/v2/crates/homecore-recorder/Cargo.toml index bcb40fc2..7551fb46 100644 --- a/v2/crates/homecore-recorder/Cargo.toml +++ b/v2/crates/homecore-recorder/Cargo.toml @@ -31,10 +31,16 @@ homecore = { path = "../homecore", version = "0.1.0-alpha.0" } # Async runtime tokio = { version = "1", features = ["sync", "rt", "rt-multi-thread", "time", "macros"] } -# SQLite via sqlx — only the lite feature set; no postgres, no tls -sqlx = { version = "0.8.1", default-features = false, features = [ - "runtime-tokio-native-tls", - "sqlite", +# SQLite-only SQLx crates, pinned in lockstep because their direct APIs are +# semver-exempt. Depending on the umbrella `sqlx` package also resolves its +# unused MySQL backend (and vulnerable `rsa`) into Cargo.lock. +sqlx-core = { version = "=0.8.6", default-features = false, features = [ + "_rt-tokio", + "chrono", + "uuid", +] } +sqlx-sqlite = { version = "=0.8.6", default-features = false, features = [ + "bundled", "chrono", "uuid", ] } diff --git a/v2/crates/homecore-recorder/src/db.rs b/v2/crates/homecore-recorder/src/db.rs index 3ca686fc..70b6c34e 100644 --- a/v2/crates/homecore-recorder/src/db.rs +++ b/v2/crates/homecore-recorder/src/db.rs @@ -26,6 +26,19 @@ use homecore::StateMachine; use crate::dedup::fnv64a_hash; use crate::schema::ALL_DDL; +// Preserve the narrow `sqlx::*` call surface used in this module while +// depending only on SQLx core + SQLite. The umbrella crate resolves unused +// database backends into Cargo.lock, including MySQL's vulnerable RSA stack. +mod sqlx { + pub use sqlx_core::error::Error; + pub use sqlx_core::query::query; + pub use sqlx_core::query_as::query_as; + + pub mod sqlite { + pub use sqlx_sqlite::{SqliteConnectOptions, SqlitePool, SqlitePoolOptions}; + } +} + type SearchStateRecord = ( i64, String, diff --git a/v2/crates/ruview-swarm b/v2/crates/ruview-swarm index 267aba5b..5cc4b862 160000 --- a/v2/crates/ruview-swarm +++ b/v2/crates/ruview-swarm @@ -1 +1 @@ -Subproject commit 267aba5be2288aa6cbe574492062b04fa8c8a6ce +Subproject commit 5cc4b8625f17668e5eeede5f3d428baf143a2f7e diff --git a/v2/crates/wifi-densepose-bfld/Cargo.toml b/v2/crates/wifi-densepose-bfld/Cargo.toml index d374e258..514b33d9 100644 --- a/v2/crates/wifi-densepose-bfld/Cargo.toml +++ b/v2/crates/wifi-densepose-bfld/Cargo.toml @@ -39,7 +39,7 @@ serde = { workspace = true, features = ["derive"], optional = true } serde_json = { workspace = true, optional = true } # MQTT publisher backend (optional). Matches the `rumqttc` choice already in # `wifi-densepose-sensing-server` so both crates share TLS / version posture. -rumqttc = { version = "0.24", default-features = false, features = ["use-rustls"], optional = true } +rumqttc = { package = "rumqttc-v4-next", version = "0.34", default-features = false, features = ["use-rustls-ring"], optional = true } wifi-veil = { workspace = true, optional = true } [dev-dependencies] diff --git a/v2/crates/wifi-densepose-bfld/src/ha_discovery.rs b/v2/crates/wifi-densepose-bfld/src/ha_discovery.rs index 6dcdf10e..b3d248ad 100644 --- a/v2/crates/wifi-densepose-bfld/src/ha_discovery.rs +++ b/v2/crates/wifi-densepose-bfld/src/ha_discovery.rs @@ -31,7 +31,7 @@ use crate::PrivacyClass; /// }; /// use rumqttc::MqttOptions; /// -/// let opts = MqttOptions::new("seed-01", "broker.local", 1883); +/// let opts = MqttOptions::new("seed-01", ("broker.local", 1883)); /// let (retained_pub, _conn) = RumqttPublisher::connect(opts.clone(), 64); /// let mut retained_pub = retained_pub.with_retain(true); /// publish_discovery(&mut retained_pub, "seed-01", PrivacyClass::Anonymous)?; diff --git a/v2/crates/wifi-densepose-bfld/src/rumqttc_publisher.rs b/v2/crates/wifi-densepose-bfld/src/rumqttc_publisher.rs index 603cff11..5383eb85 100644 --- a/v2/crates/wifi-densepose-bfld/src/rumqttc_publisher.rs +++ b/v2/crates/wifi-densepose-bfld/src/rumqttc_publisher.rs @@ -13,7 +13,7 @@ //! use wifi_densepose_bfld::{publish_event, RumqttPublisher}; //! use rumqttc::MqttOptions; //! -//! let opts = MqttOptions::new("seed-01", "broker.local", 1883); +//! let opts = MqttOptions::new("seed-01", ("broker.local", 1883)); //! let (mut publisher, mut connection) = RumqttPublisher::connect(opts, 100); //! thread::spawn(move || for _ in connection.iter() { /* drain */ }); //! // ... build BfldEvent ... @@ -22,7 +22,7 @@ #![cfg(feature = "mqtt")] -use rumqttc::{Client, Connection, LastWill, MqttOptions, QoS}; +use rumqttc::{Client, Connection, LastWill, MqttOptions, PublishOptions, QoS}; use crate::availability::{availability_topic, PAYLOAD_NOT_AVAILABLE}; use crate::mqtt_topics::{Publish, TopicMessage}; @@ -60,7 +60,7 @@ impl RumqttPublisher { /// shown in the module-level doc example). #[must_use] pub fn connect(opts: MqttOptions, capacity: usize) -> (Self, Connection) { - let (client, connection) = Client::new(opts, capacity); + let (client, connection) = Client::builder(opts).capacity(capacity).build(); (Self::new(client, QoS::AtLeastOnce), connection) } @@ -87,7 +87,7 @@ impl RumqttPublisher { /// opt in to the LWT without using `connect_with_lwt`. #[must_use] pub fn with_lwt(mut opts: MqttOptions, node_id: &str) -> MqttOptions { - // rumqttc 0.24 LastWill::new takes (topic, message, qos, retain). + // LastWill::new takes (topic, message, qos, retain). // retain = true so HA sees "offline" on next start even if the session // dropped while HA was down. let will = LastWill::new( @@ -105,6 +105,10 @@ impl Publish for RumqttPublisher { fn publish(&mut self, msg: &TopicMessage) -> Result<(), Self::Error> { self.client - .publish(&msg.topic, self.qos, self.retain, msg.payload.as_bytes()) + .publish( + &msg.topic, + msg.payload.as_bytes(), + PublishOptions::new(self.qos).retain(self.retain), + ) } } diff --git a/v2/crates/wifi-densepose-bfld/tests/mosquitto_integration.rs b/v2/crates/wifi-densepose-bfld/tests/mosquitto_integration.rs index 0c16e4d5..7056a2d4 100644 --- a/v2/crates/wifi-densepose-bfld/tests/mosquitto_integration.rs +++ b/v2/crates/wifi-densepose-bfld/tests/mosquitto_integration.rs @@ -23,9 +23,7 @@ use std::thread; use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH}; use rumqttc::{Client, Event, Incoming, MqttOptions, Packet, QoS}; -use wifi_densepose_bfld::{ - publish_event, BfldEvent, PrivacyClass, RumqttPublisher, -}; +use wifi_densepose_bfld::{publish_event, BfldEvent, PrivacyClass, RumqttPublisher}; const SUBSCRIBE_TIMEOUT: Duration = Duration::from_secs(5); const RECEIVE_TIMEOUT: Duration = Duration::from_secs(10); @@ -69,9 +67,9 @@ fn spawn_subscriber( port: u16, topic_filter: &str, ) -> (Receiver<(String, String)>, Receiver<()>) { - let mut opts = MqttOptions::new(unique_client_id("bfld-sub"), host, port); - opts.set_keep_alive(Duration::from_secs(5)); - let (client, mut connection) = Client::new(opts, 64); + let mut opts = MqttOptions::new(unique_client_id("bfld-sub"), (host, port)); + opts.set_keep_alive(5); + let (client, mut connection) = Client::builder(opts).capacity(64).build(); client .subscribe(topic_filter, QoS::AtLeastOnce) .expect("subscribe enqueue"); @@ -79,13 +77,18 @@ fn spawn_subscriber( let (incoming_tx, incoming_rx) = channel(); let (suback_tx, suback_rx) = channel(); thread::spawn(move || { + // rumqttc-v4-next stops the connection once every request sender is + // dropped. Keep the subscriber client alive for as long as its pump + // thread runs; otherwise the broker sees a clean disconnect directly + // after SUBACK and no subsequent publications can be delivered. + let _client_guard = client; for notification in connection.iter() { match notification { Ok(Event::Incoming(Packet::SubAck(_))) => { let _ = suback_tx.send(()); } Ok(Event::Incoming(Incoming::Publish(p))) => { - let topic = p.topic.clone(); + let topic = String::from_utf8_lossy(&p.topic).to_string(); let payload = String::from_utf8_lossy(&p.payload).to_string(); if incoming_tx.send((topic, payload)).is_err() { break; @@ -141,8 +144,8 @@ fn live_broker_anonymous_event_roundtrips_all_six_topics() { // Publisher with its own connection. Spawn a thread iterating the // Connection so publishes actually reach the broker. - let mut opts = MqttOptions::new(unique_client_id("bfld-pub"), &host, port); - opts.set_keep_alive(Duration::from_secs(5)); + let mut opts = MqttOptions::new(unique_client_id("bfld-pub"), (host.as_str(), port)); + opts.set_keep_alive(5); let (mut publisher, mut pub_connection) = RumqttPublisher::connect(opts, 64); thread::spawn(move || { for _ in pub_connection.iter() { /* drain protocol events */ } @@ -197,8 +200,8 @@ fn live_broker_restricted_event_omits_identity_risk() { .recv_timeout(SUBSCRIBE_TIMEOUT) .expect("SubAck within 5s"); - let mut opts = MqttOptions::new(unique_client_id("bfld-pub-r"), &host, port); - opts.set_keep_alive(Duration::from_secs(5)); + let mut opts = MqttOptions::new(unique_client_id("bfld-pub-r"), (host.as_str(), port)); + opts.set_keep_alive(5); let (mut publisher, mut pub_connection) = RumqttPublisher::connect(opts, 64); thread::spawn(move || for _ in pub_connection.iter() {}); thread::sleep(Duration::from_millis(200)); diff --git a/v2/crates/wifi-densepose-bfld/tests/rumqttc_lwt.rs b/v2/crates/wifi-densepose-bfld/tests/rumqttc_lwt.rs index 1ab73c78..d079b3e1 100644 --- a/v2/crates/wifi-densepose-bfld/tests/rumqttc_lwt.rs +++ b/v2/crates/wifi-densepose-bfld/tests/rumqttc_lwt.rs @@ -9,7 +9,7 @@ use wifi_densepose_bfld::{ }; fn unreachable_opts(client_id: &str) -> MqttOptions { - MqttOptions::new(client_id, "127.0.0.1", 1) + MqttOptions::new(client_id, ("127.0.0.1", 1)) } #[test] @@ -89,7 +89,7 @@ fn caller_built_options_can_opt_in_via_with_lwt_then_pass_to_connect() { // Operators with custom MqttOptions (e.g., TLS, credentials) build their // own opts, then call with_lwt before passing to RumqttPublisher::connect. let mut opts = unreachable_opts("bfld-lwt-6"); - opts.set_keep_alive(std::time::Duration::from_secs(30)); + opts.set_keep_alive(30); let opts = with_lwt(opts, "seed-01"); let (_publisher, _connection) = RumqttPublisher::connect(opts, 16); } diff --git a/v2/crates/wifi-densepose-bfld/tests/rumqttc_publisher_smoke.rs b/v2/crates/wifi-densepose-bfld/tests/rumqttc_publisher_smoke.rs index 1f5a3832..d4e2bc85 100644 --- a/v2/crates/wifi-densepose-bfld/tests/rumqttc_publisher_smoke.rs +++ b/v2/crates/wifi-densepose-bfld/tests/rumqttc_publisher_smoke.rs @@ -12,7 +12,7 @@ use wifi_densepose_bfld::{publish_event, BfldEvent, PrivacyClass, Publish, Rumqt fn unreachable_opts() -> MqttOptions { // Port 1 is reserved (RFC 1700) and the loopback address will refuse // immediately — perfect for a construction smoke test that must not block. - MqttOptions::new("bfld-smoke-iter23", "127.0.0.1", 1) + MqttOptions::new("bfld-smoke-iter23", ("127.0.0.1", 1)) } fn sample_event() -> BfldEvent { diff --git a/v2/crates/wifi-densepose-sensing-server/Cargo.toml b/v2/crates/wifi-densepose-sensing-server/Cargo.toml index bd99fd86..5099c4be 100644 --- a/v2/crates/wifi-densepose-sensing-server/Cargo.toml +++ b/v2/crates/wifi-densepose-sensing-server/Cargo.toml @@ -110,7 +110,7 @@ rand = "0.8" # client (ADR-115 §10 references). `rustls` is preferred over openssl on # Windows to keep parity with the rest of the workspace (`ureq` above also # uses rustls). -rumqttc = { version = "0.24", default-features = false, features = ["use-rustls"], optional = true } +rumqttc = { package = "rumqttc-v4-next", version = "0.34", default-features = false, features = ["use-rustls-ring"], optional = true } # `otel` feature — OTLP log export (`telemetry` module). Same gating # principle as `mqtt`: the heavy exporter stack (opentelemetry SDK + diff --git a/v2/crates/wifi-densepose-sensing-server/src/main.rs b/v2/crates/wifi-densepose-sensing-server/src/main.rs index 88d46090..0b35a5c6 100644 --- a/v2/crates/wifi-densepose-sensing-server/src/main.rs +++ b/v2/crates/wifi-densepose-sensing-server/src/main.rs @@ -286,6 +286,9 @@ struct Esp32Frame { /// ADR-110 byte 18: PPDU type the CSI was sampled from. Pre-ADR-110 /// firmware sends 0 ⇒ `PpduType::HtLegacy`. ppdu_type: wifi_densepose_hardware::PpduType, + /// ADR-110 byte 19 metadata, including whether this frame was captured + /// while the node had a valid IEEE 802.15.4 mesh-time solution. + adr018_flags: wifi_densepose_hardware::Adr018Flags, amplitudes: Vec, phases: Vec, } @@ -675,6 +678,12 @@ struct NodeState { latest_sync: Option, /// Last time a sync packet from this node was received (for staleness). latest_sync_at: Option, + /// Sequence number of the newest CSI frame admitted to `frame_history`. + /// Kept alongside the history so multistatic fusion can timestamp the + /// exact sample it consumes, rather than the host's UDP arrival time. + latest_csi_sequence: Option, + /// Whether byte 19 bit 4 marked that newest admitted CSI frame as synced. + latest_csi_sync_valid: bool, /// ADR-110 iter 18: EMA-tracked CSI frame rate for this node. /// Replaces the hardcoded 20 Hz fallback in /// `mesh_aligned_us_for_csi_frame` once `csi_fps_samples ≥ 5`. @@ -832,6 +841,9 @@ impl NodeState { /// staleness gate). pub(crate) fn mesh_aligned_us(&self, local_at_frame_us: u64) -> Option { let sync = self.latest_sync.as_ref()?; + if !sync.flags.is_valid { + return None; + } let seen_at = self.latest_sync_at?; // Drop stale syncs — firmware emits at ~0.5 Hz default, anything // older than 9 s likely means the mesh transport dropped. @@ -850,10 +862,20 @@ impl NodeState { /// no fresh sync has been observed for this node. pub(crate) fn mesh_aligned_us_for_csi_frame(&self, frame_sequence: u32) -> Option { let sync = self.latest_sync.as_ref()?; + if !sync.flags.is_valid { + return None; + } let seen_at = self.latest_sync_at?; if seen_at.elapsed() > std::time::Duration::from_secs(9) { return None; } + // A recently-received sync datagram can overtake an older CSI + // datagram in UDP delivery order. Only extrapolate forward (including + // a genuine u32 wrap); otherwise fall back to host arrival time. + let delta_frames = frame_sequence.wrapping_sub(sync.sequence); + if delta_frames > i32::MAX as u32 { + return None; + } // Iter 18: use the measured per-node fps once we have ≥5 inter-frame // samples; until then fall back to the 20 Hz firmware ceiling. The // §A0.12 capture showed real bench fps ≈ 10, so the measured value @@ -862,6 +884,16 @@ impl NodeState { Some(sync.mesh_aligned_us_for_sequence(frame_sequence, fps)) } + /// Mesh timestamp for the newest CSI frame admitted to `frame_history`. + /// Both the frame-level sync-valid bit and a fresh, valid sync packet are + /// required; callers retain their existing host-arrival fallback. + pub(crate) fn mesh_aligned_us_for_latest_csi_frame(&self) -> Option { + if !self.latest_csi_sync_valid { + return None; + } + self.mesh_aligned_us_for_csi_frame(self.latest_csi_sequence?) + } + /// ADR-110 iter 18 — update the per-node observed-fps EMA from a fresh /// CSI frame arrival. Call once per accepted CSI frame from /// `udp_receiver_task`. Uses `last_frame_time` as the previous-frame @@ -927,6 +959,21 @@ impl NodeState { first_sensing_frame } + /// Record an accepted CSI sample and preserve the wire metadata needed by + /// the multistatic bridge to recover capture time. Grid-rejected frames + /// intentionally use `observe_csi_frame_arrival` directly because they do + /// not replace the sample at the back of `frame_history`. + pub(crate) fn observe_accepted_csi_frame( + &mut self, + sequence: u32, + sync_valid: bool, + now: std::time::Instant, + ) -> bool { + self.latest_csi_sequence = Some(sequence); + self.latest_csi_sync_valid = sync_valid; + self.observe_csi_frame_arrival(now) + } + pub(crate) fn new() -> Self { Self { frame_history: VecDeque::new(), @@ -951,6 +998,8 @@ impl NodeState { edge_vitals: None, latest_sync: None, latest_sync_at: None, + latest_csi_sequence: None, + latest_csi_sync_valid: false, csi_fps_ema: 20.0, csi_fps_samples: 0, latest_features: None, @@ -1945,7 +1994,8 @@ fn parse_esp32_frame(buf: &[u8]) -> Option { // [12..15] sequence (u32 LE) // [16] rssi (i8) // [17] noise_floor (i8) - // [18..19] reserved + // [18] PPDU type + // [19] ADR-018 flags (bit 4 = IEEE 802.15.4 sync valid) // [20..] I/Q data // Issue #1005: until 2026-06 this code read n_subcarriers from byte 6 // alone (an ESP32-C6 HE-SU frame's 256 = 0x0100 LE decoded as 0 — the @@ -1966,6 +2016,7 @@ fn parse_esp32_frame(buf: &[u8]) -> Option { }; let noise_floor = buf[17] as i8; let ppdu_type = wifi_densepose_hardware::PpduType::from_byte(buf[18]); + let adr018_flags = wifi_densepose_hardware::Adr018Flags::from_byte(buf[19]); let iq_start = 20; let n_pairs = n_antennas as usize * n_subcarriers as usize; @@ -1995,6 +2046,7 @@ fn parse_esp32_frame(buf: &[u8]) -> Option { rssi, noise_floor, ppdu_type, + adr018_flags, amplitudes, phases, }) @@ -2024,7 +2076,7 @@ mod issue_1009_n_subcarriers_u16_tests { buf[16] = (-40i8) as u8; // rssi buf[17] = (-90i8) as u8; // noise_floor buf[18] = 0; // ppdu_type - buf[19] = 0; + buf[19] = 0x10; // ADR-018: IEEE 802.15.4 sync valid for k in 0..n_subcarriers as usize { buf[20 + k * 2] = (5 + (k % 40) as i8) as u8; // i buf[20 + k * 2 + 1] = (k % 30) as u8; // q @@ -2047,6 +2099,7 @@ mod issue_1009_n_subcarriers_u16_tests { assert_eq!(frame.node_id, 7); assert_eq!(frame.rssi, -40); assert_eq!(frame.sequence, 42); + assert!(frame.adr018_flags.ieee802154_sync_valid); } #[test] @@ -2941,6 +2994,7 @@ async fn windows_wifi_task(state: SharedState, tick_ms: u64) { rssi: first_rssi.clamp(-128.0, 127.0) as i8, noise_floor: -90, ppdu_type: wifi_densepose_hardware::PpduType::HtLegacy, + adr018_flags: wifi_densepose_hardware::Adr018Flags::default(), amplitudes: multi_ap_frame.amplitudes.clone(), phases: multi_ap_frame.phases.clone(), }; @@ -3129,6 +3183,7 @@ async fn windows_wifi_fallback_tick(state: &SharedState, seq: u32) { rssi: rssi_dbm as i8, noise_floor: -90, ppdu_type: wifi_densepose_hardware::PpduType::HtLegacy, + adr018_flags: wifi_densepose_hardware::Adr018Flags::default(), amplitudes: vec![signal_pct], phases: vec![0.0], }; @@ -3504,6 +3559,7 @@ fn generate_simulated_frame(tick: u64) -> Esp32Frame { rssi: (-40.0 + 5.0 * (t * 0.2).sin()) as i8, noise_floor: -90, ppdu_type: wifi_densepose_hardware::PpduType::HtLegacy, + adr018_flags: wifi_densepose_hardware::Adr018Flags::default(), amplitudes, phases, } @@ -6702,8 +6758,11 @@ async fn udp_receiver_task( // ADR-110 iter 19 — feed the per-node fps EMA from real // CSI arrivals. The helper sets `last_frame_time` as a // side effect, so the previous bare assignment is gone. - let first_sensing_frame = - ns.observe_csi_frame_arrival(std::time::Instant::now()); + let first_sensing_frame = ns.observe_accepted_csi_frame( + frame.sequence, + frame.adr018_flags.ieee802154_sync_valid, + std::time::Instant::now(), + ); if first_sensing_frame && telemetry::curated_events_enabled() { info!(name: semconv::EVENT_RUVIEW_NODE_ONLINE, { "ruview.node.id" = node_id }, "node {node_id} online (CSI)"); } @@ -9327,6 +9386,31 @@ mod sync_snapshot_helper_tests { "10 s old sync must trigger the 9 s staleness gate"); } + #[test] + fn latest_csi_mesh_time_requires_both_validity_signals() { + let now = std::time::Instant::now(); + let mut ns = NodeState::new(); + ns.apply_sync_packet(populated_sync(9), now); + + ns.observe_accepted_csi_frame(21, false, now); + assert!( + ns.mesh_aligned_us_for_latest_csi_frame().is_none(), + "an unsynchronized CSI capture must use the host-time fallback" + ); + + ns.observe_accepted_csi_frame(21, true, now + std::time::Duration::from_millis(50)); + assert_eq!( + ns.mesh_aligned_us_for_latest_csi_frame(), + Some(27_684_885) + ); + + ns.latest_sync.as_mut().unwrap().flags.is_valid = false; + assert!( + ns.mesh_aligned_us_for_latest_csi_frame().is_none(), + "an invalid sync packet must not timestamp even a flagged CSI frame" + ); + } + #[test] fn snapshot_reflects_leader_state() { // Same data shape that /api/v1/mesh emits for a leader node. diff --git a/v2/crates/wifi-densepose-sensing-server/src/mqtt/publisher.rs b/v2/crates/wifi-densepose-sensing-server/src/mqtt/publisher.rs index fd670e05..96b46944 100644 --- a/v2/crates/wifi-densepose-sensing-server/src/mqtt/publisher.rs +++ b/v2/crates/wifi-densepose-sensing-server/src/mqtt/publisher.rs @@ -30,7 +30,10 @@ use std::sync::Arc; use std::time::{Duration, Instant}; -use rumqttc::{AsyncClient, ClientError, EventLoop, MqttOptions, QoS, Transport, TlsConfiguration}; +use rumqttc::{ + AsyncClient, ClientError, EventLoop, MqttOptions, PublishOptions, QoS, Transport, + TlsConfiguration, +}; use tokio::sync::broadcast; use tokio::task::JoinHandle; use tracing::{error, info, warn}; @@ -70,14 +73,14 @@ const NODE_SNAPSHOT_STALE_AFTER: Duration = Duration::from_secs(10); /// Build a `rumqttc::MqttOptions` from validated [`MqttConfig`]. fn build_mqtt_options(cfg: &MqttConfig) -> MqttOptions { - let mut opts = MqttOptions::new(&cfg.client_id, &cfg.host, cfg.port); - opts.set_keep_alive(Duration::from_secs(30)); + let mut opts = MqttOptions::new(&cfg.client_id, (cfg.host.as_str(), cfg.port)); + opts.set_keep_alive(30); opts.set_clean_session(true); if let (Some(u), Some(p)) = (cfg.username.as_deref(), cfg.password.as_deref()) { - opts.set_credentials(u, p); + opts.set_credentials(u.to_owned(), p.as_bytes().to_vec()); } else if let Some(u) = cfg.username.as_deref() { - opts.set_credentials(u, ""); + opts.set_credentials(u.to_owned(), Vec::::new()); } opts.set_transport(build_transport(&cfg.tls)); @@ -223,7 +226,8 @@ async fn run( mut state_rx: broadcast::Receiver, ) { let opts = build_mqtt_options(&cfg); - let (client, mut eventloop): (AsyncClient, EventLoop) = AsyncClient::new(opts, 256); + let (client, mut eventloop): (AsyncClient, EventLoop) = + AsyncClient::builder(opts).capacity(256).build(); let entities = DiscoveryBuilder::enabled_entities( cfg.privacy_mode, @@ -369,7 +373,13 @@ async fn publish_all_discovery( let cfg = b.build(e); let topic = b.config_topic(e); let payload = serde_json::to_string(&cfg).expect("discovery payload always serialises"); - client.publish(&topic, QoS::AtLeastOnce, true, payload).await?; + client + .publish( + &topic, + payload, + PublishOptions::new(QoS::AtLeastOnce).retained(), + ) + .await?; } Ok(()) } @@ -380,7 +390,13 @@ async fn publish_availability( state: &str, ) -> Result<(), ClientError> { for topic in &avail.online_topics { - client.publish(topic, QoS::AtLeastOnce, true, state).await?; + client + .publish( + topic, + state, + PublishOptions::new(QoS::AtLeastOnce).retained(), + ) + .await?; } Ok(()) } @@ -441,7 +457,13 @@ async fn publish_state(client: &AsyncClient, m: &StateMessage) -> Result<(), Cli 1 => QoS::AtLeastOnce, _ => QoS::ExactlyOnce, }; - client.publish(&m.topic, qos, m.retain, m.payload.clone()).await + client + .publish( + &m.topic, + m.payload.clone(), + PublishOptions::new(qos).retain(m.retain), + ) + .await } #[cfg(test)] diff --git a/v2/crates/wifi-densepose-sensing-server/src/multistatic_bridge.rs b/v2/crates/wifi-densepose-sensing-server/src/multistatic_bridge.rs index e3ad83ea..a84f8989 100644 --- a/v2/crates/wifi-densepose-sensing-server/src/multistatic_bridge.rs +++ b/v2/crates/wifi-densepose-sensing-server/src/multistatic_bridge.rs @@ -24,7 +24,13 @@ const DEFAULT_FREQ_MHZ: u32 = 2437; // Channel 6 /// Monotonic reference point for timestamp generation. All node timestamps /// are relative to this instant, avoiding wall-clock/monotonic mixing issues. -static EPOCH: LazyLock = LazyLock::new(Instant::now); +/// Backdate the lazy initialization beyond the active-node window so frames +/// recorded just before the first bridge call retain their arrival-time skew. +static EPOCH: LazyLock = LazyLock::new(|| { + Instant::now() + .checked_sub(STALE_THRESHOLD + STALE_THRESHOLD) + .unwrap_or_else(Instant::now) +}); /// Shared length-only canonicalizer (issue #1170). The default 56-tone grid /// matches what `MultistaticFuser` (ADR-154) expects. Stateless and immutable, @@ -54,10 +60,18 @@ pub fn node_frame_from_state(node_id: u8, ns: &NodeState) -> Option f64 { mod tests { use super::*; use std::collections::VecDeque; + use wifi_densepose_hardware::{SyncPacket, SyncPacketFlags}; /// Helper: build a minimal NodeState for testing. Uses `NodeState::new()` /// then mutates the `pub(crate)` fields the bridge needs. @@ -225,6 +240,99 @@ mod tests { assert_eq!(ch.hardware_type, HardwareType::Esp32S3); } + fn mark_mesh_timed_frame( + ns: &mut NodeState, + node_id: u8, + sync_sequence: u32, + frame_sequence: u32, + mesh_epoch_us: u64, + host_arrival: Instant, + ) { + ns.apply_sync_packet( + SyncPacket { + node_id, + proto_ver: 1, + flags: SyncPacketFlags { + is_leader: node_id == 1, + is_valid: true, + smoothed_used: node_id != 1, + }, + local_us: 10_000_000, + epoch_us: mesh_epoch_us, + sequence: sync_sequence, + }, + Instant::now(), + ); + ns.observe_accepted_csi_frame(frame_sequence, true, host_arrival); + } + + #[test] + fn mesh_timestamp_replaces_skewed_host_arrival_time() { + let mut history = VecDeque::new(); + history.push_back(vec![10.0, 20.0, 30.0]); + let host_arrival = Instant::now(); + let mut ns = make_node_state(history, None, 0); + mark_mesh_timed_frame(&mut ns, 1, 100, 101, 1_000_000, host_arrival); + + let frame = node_frame_from_state(1, &ns).expect("mesh-timed frame"); + assert_eq!(frame.timestamp_us, 1_050_000); + } + + #[test] + fn mesh_time_allows_fusion_despite_udp_arrival_skew() { + let base = Instant::now() - Duration::from_millis(500); + let mut states = HashMap::new(); + + let mut first_history = VecDeque::new(); + first_history.push_back(vec![1.0; 64]); + let mut first = make_node_state(first_history, None, 0); + mark_mesh_timed_frame(&mut first, 1, 100, 101, 1_000_000, base); + states.insert(1, first); + + let mut second_history = VecDeque::new(); + second_history.push_back(vec![1.1; 64]); + let mut second = make_node_state(second_history, None, 0); + mark_mesh_timed_frame( + &mut second, + 2, + 200, + 201, + 1_005_000, + base + Duration::from_millis(200), + ); + states.insert(2, second); + + let frames = node_frames_from_states(&states); + let spread = frames.iter().map(|f| f.timestamp_us).max().unwrap() + - frames.iter().map(|f| f.timestamp_us).min().unwrap(); + assert_eq!(spread, 5_000, "mesh capture spread, not 200 ms UDP skew"); + assert!( + MultistaticFuser::new().fuse(&frames).is_ok(), + "mesh-aligned frames inside the 60 ms guard must fuse" + ); + } + + #[test] + fn unsynchronized_frames_keep_host_arrival_guard() { + let base = Instant::now() - Duration::from_millis(500); + let mut states = HashMap::new(); + + for (node_id, arrival) in [ + (1, base), + (2, base + Duration::from_millis(200)), + ] { + let mut history = VecDeque::new(); + history.push_back(vec![1.0; 64]); + states.insert(node_id, make_node_state(history, Some(arrival), 0)); + } + + let frames = node_frames_from_states(&states); + assert!( + MultistaticFuser::new().fuse(&frames).is_err(), + "without valid mesh time, 200 ms arrival skew must still trip the 60 ms guard" + ); + } + #[test] fn heterogeneous_node_counts_canonicalize_and_fuse() { // Issue #1170 regression: a mixed mesh with HT20 (64-bin) and HT40 diff --git a/v2/crates/wifi-densepose-sensing-server/tests/mqtt_integration.rs b/v2/crates/wifi-densepose-sensing-server/tests/mqtt_integration.rs index f71bb3d6..c8dd1c8c 100644 --- a/v2/crates/wifi-densepose-sensing-server/tests/mqtt_integration.rs +++ b/v2/crates/wifi-densepose-sensing-server/tests/mqtt_integration.rs @@ -104,12 +104,11 @@ async fn subscribe_client(port: u16, topics: &[&str]) -> (AsyncClient, EventLoop .unwrap_or(0); let mut opts = MqttOptions::new( format!("ruview-test-sub-{}-{}", std::process::id(), suffix), - "127.0.0.1", - port, + ("127.0.0.1", port), ); - opts.set_keep_alive(Duration::from_secs(10)); + opts.set_keep_alive(10); opts.set_clean_session(true); - let (client, mut eventloop) = AsyncClient::new(opts, 256); + let (client, mut eventloop) = AsyncClient::builder(opts).capacity(256).build(); for t in topics { client.subscribe(*t, QoS::AtLeastOnce).await.unwrap(); } @@ -147,7 +146,11 @@ async fn collect_published( let remain = until - tokio::time::Instant::now(); match timeout(remain, eventloop.poll()).await { Ok(Ok(Event::Incoming(Packet::Publish(p)))) => { - out.push((p.topic, p.payload.to_vec(), p.retain)); + out.push(( + String::from_utf8_lossy(&p.topic).to_string(), + p.payload.to_vec(), + p.retain, + )); } Ok(Ok(_)) => {} // ignore other events Ok(Err(e)) => {