fix(kiwisdr): harden receiver setup and waterfall status#3678
Conversation
There was a problem hiding this comment.
Pull request overview
This PR hardens the native KiwiSDR receive integration by adding /status preflight checks, improving protocol/frame handling and diagnostics, and surfacing “waterfall unavailable” conditions in the UI while keeping the Kiwi waterfall rate constrained to the server-supported range.
Changes:
- Add KiwiSDR
/statuspreflight to detect ext_api policy and capacity limits before opening stream sockets. - Track and surface Kiwi waterfall-channel availability separately from audio connection state (UI overlay + applet status detail).
- Harden SND/W/F protocol handling and reduce routine trace spam while retaining close-time protocol context; clamp Kiwi WF rate to
0..4.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/kiwi_sdr_protocol_test.cpp | Updates protocol tests for revised SND header parsing behavior. |
| src/gui/SpectrumWidget.h | Extends KiwiSDR overlay API to accept an optional title; stores title state. |
| src/gui/SpectrumWidget.cpp | Renders the new KiwiSDR overlay title and tracks overlay title changes. |
| src/gui/SpectrumOverlayMenu.cpp | Clamps Kiwi WF Rate UI range and sync logic to the supported 0..4 range. |
| src/gui/MainWindow_KiwiSdr.cpp | Adds UI overlay/app-status behavior for “waterfall unavailable” while connected. |
| src/core/KiwiSdrProtocol.cpp | Adjusts SND header parsing to handle observed server header layout. |
| src/core/KiwiSdrManager.h / .cpp | Adds manager accessors/signals for waterfall availability + rate clamping updates. |
| src/core/KiwiSdrClient.h / .cpp | Adds status preflight, protocol tracing, waterfall availability tracking, and hardened frame handling. |
There was a problem hiding this comment.
Thanks for the thorough follow-up hardening, @rfoust — this is a solid, well-scoped pass. The /status preflight, the separated waterfall-availability tracking, and the consistent 0..4 WF-rate clamp all read cleanly, and the new serverSoundFrame test case nicely covers the 10-byte header branch. All 6 CI checks are green. A few notes, one worth fixing:
1. send_failed trace is dead code (confirming Copilot) — KiwiSdrClient.cpp:2467 / 2484
Both sendSoundCommand and sendWaterfallCommand call traceOutboundCommand(..., true) with a hard-coded true before sendTextMessage() is even invoked. Since that sent flag is the only thing that sets m_protocolSendFailed and emits send_failed=true in traceProtocolEvent (see traceOutboundCommand, line 2202), the failure path can never trigger — close-time traces will always report a clean send even when QWebSocket::sendTextMessage() returns -1. This defeats the stated goal of retaining useful close-time protocol context. Suggest reordering so the real result drives the flag, e.g.:
const qint64 written = m_soundSocket->sendTextMessage(command);
traceOutboundCommand(StreamKind::Sound, command, written >= 0);
qCDebug(lcKiwiSdr).noquote() << "KiwiSDR SND ->" << ... << redactedKiwiCommand(command);(Same shape for the W/F path.) Low severity since it's diagnostics-only, but easy to get right.
2. Naming nit (Copilot #3) — MainWindow_KiwiSdr.cpp:305
The local kiwiWaterfallAvailable (server-reported channel availability) sits right next to spectrum->setKiwiSdrWaterfallAvailable(true) (whether the pan is in Kiwi mode at all) — two genuinely different concepts one keyword apart. Valid readability concern; a rename like kiwiWaterfallChannelAvailable would remove the foot-gun. Optional.
3. Minor — status preflight scheme. kiwiStatusUrl() always uses http on m_port even for secure (wss/443) endpoints, so on a TLS-only Kiwi the preflight will likely fail and wait out the 5s transfer timeout before connecting. The failure path degrades gracefully (logs status preflight unavailable and proceeds to openWebSockets()), so this isn't blocking — just flagging the added latency on secure endpoints in case it's worth deriving the scheme/port from m_secureWebSocket.
Nothing here blocks the intent of the PR. Conventions look good (nested-JSON settings preserved, RAII via this-parented QNetworkAccessManager/reply cleanup in cleanupSockets, clean-room protocol observations per Principle IV). Item 1 is the only one I'd ask you to address before merge.
🤖 aethersdr-agent · cost: $3.1371 · model: claude-opus-4-8
Handle KiwiSDR status preflight for external API and capacity limits, preserve preemptable server behavior, surface unavailable waterfall channels distinctly from audio connection state, reduce routine protocol trace logging, and keep Kiwi waterfall rate values within the supported range. Tests: - git diff --check - ctest --test-dir build -R "[Kk]iwi" --output-on-failure - python3 tools/check_a11y.py - cmake --build build --parallel
1c3bd8a to
6e41f38
Compare
…ethersdr#3678 review) The /status preflight failed OPEN: if the http /status fetch errored for any reason — network blip, timeout, non-Kiwi host, or a proxied/TLS-only Kiwi (the fetch was http-only) — it fell through and opened the WebSockets anyway, bypassing the ext_api/capacity check. A web-only (ext_api=0) operator whose /status was momentarily unreachable or HTTPS-only could still get a native connection, defeating the whole point of the preflight. Now: - Retry the /status fetch over https (port 443) when the http attempt fails — covers proxied / TLS-only receivers (kiwiStatusUrl gains a `secure` arg; m_statusPreflightSecure tracks the attempt, reset per connect). - If both http and https fail, FAIL CLOSED: we can't read ext_api, so we can't confirm the operator allows external API clients — refuse with a clear message instead of connecting. (A receiver whose /status is unreachable is almost always down for the WebSocket path too, so this rarely blocks a working one.) Honoring ext_api now holds even when /status can't be reached the usual way. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ten9876
left a comment
There was a problem hiding this comment.
✅ Approved
Reviewed in depth across the server-policy preflight, SND/protocol hardening, thread-safety, clean-room provenance, and the waterfall-availability UX. This is solid, well-scoped work — thanks @rfoust.
Verified good
ext_api=0honored at connect — every connection path funnels through the/statuspreflight;ext_api=0→State::Errorbefore any socket opens, with a clear operator-facing message.- Capacity/preempt correct — refuses only when
users >= users_max && preempt <= 0; a full-but-preemptable server is correctly allowed; no off-by-one. - SND decode is memory-safe — size-guarded, correct sign-extension, and compressed/unsupported frames are refused before decode (not mis-read as PCM). Dynamic rate negotiation bounds-checks [8k,48k] and rebuilds the resampler cleanly.
- No thread races (the client runs on the GUI thread), waterfall-availability resets correctly across connect/disconnect/reconnect/remove and never clobbers the connection-error overlay, WF-rate 0..4 is consistent everywhere, Principle V respected, no leaks.
Fix folded in (e423d657) — the one should-fix from review: the /status preflight failed open (if the http fetch errored — incl. proxied/TLS-only Kiwis — it connected anyway, bypassing the policy check). It now retries over https and fails closed when /status can't be read, so ext_api=0 is honored even when the status page is unreachable, not just on the happy path.
Two non-blocking follow-ups for you:
- Description wording — it says "AGPL"; KiwiSDR is GPL/MIT. The clean-room itself is clean (the flag constants don't match KiwiSDR's source values → genuine independent observation); just worth correcting the characterization in the body.
too_busy=0behavior change — it now raises an error/disconnect where it previously continued. You verified live so it's likely correct; a quick confirm it can't false-reject otherwise-good servers would be good.
This pairs cleanly with #3679 (which honors ext_api at browse time) — disjoint files, no conflicts. Nice work.
Summary
Adds follow-up hardening for the native KiwiSDR receive integration. This improves KiwiSDR protocol setup, reduces noisy trace logging, handles operator/server limits earlier, surfaces waterfall-unavailable conditions clearly, and keeps Kiwi waterfall controls consistent with the server-supported range.
Notable changes:
/statuspreflight handling forext_api=0,users,users_max, andpreempt.preemptslots.0..4range.Constitution principle honored
Principle IV — protocol behavior is implemented from allowed protocol references, clean observations, and GPL-compatible KiwiSDR server protocol references, without using AGPL client/source-derived code.
Principle VII — remote endpoint input and server-reported status are validated before connection setup, and unsupported/limited server states are surfaced explicitly to the operator.
Test plan
cmake --build build --parallel)ctest --test-dir build -R "[Kk]iwi" --output-on-failure)git diff --checkpassespython3 tools/check_a11y.py; existing warning-only findings remain)Checklist
docs/COMMIT-SIGNING.md)AppSettingscalls — use nested-JSON-under-one-key (Principle V)MeterSmoother(AGENTS.md convention)