Skip to content

fix(kiwisdr): harden receiver setup and waterfall status#3678

Merged
ten9876 merged 2 commits into
aethersdr:mainfrom
rfoust:codex/kiwisdr-server-policy-followup
Jun 20, 2026
Merged

fix(kiwisdr): harden receiver setup and waterfall status#3678
ten9876 merged 2 commits into
aethersdr:mainfrom
rfoust:codex/kiwisdr-server-policy-followup

Conversation

@rfoust

@rfoust rfoust commented Jun 20, 2026

Copy link
Copy Markdown
Collaborator

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:

  • Add /status preflight handling for ext_api=0, users, users_max, and preempt.
  • Show capacity messaging before opening SND/W/F sockets when no receiver slot is available.
  • Preserve preemptable-server behavior by not rejecting full servers that report available preempt slots.
  • Track Kiwi waterfall channel availability separately from audio connection state.
  • Show a specific panadapter overlay when audio is connected but no Kiwi waterfall channel is available.
  • Update the Kiwi applet status detail for connected receivers with unavailable waterfall data.
  • Reduce routine Kiwi trace log spam while retaining close-time protocol trace context.
  • Keep Kiwi WF Rate consistently limited to the supported 0..4 range.
  • Harden SND frame handling around observed header layout, compression flags, and dynamic audio rate negotiation.

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

  • Local build passes (cmake --build build --parallel)
  • Behavior verified on public KiwiSDR endpoints during maintainer testing
  • Existing tests pass (CI)
  • Reproduction steps documented if user-reported bug
  • Kiwi protocol test passes (ctest --test-dir build -R "[Kk]iwi" --output-on-failure)
  • git diff --check passes
  • Accessibility check run (python3 tools/check_a11y.py; existing warning-only findings remain)

Checklist

  • Commits are signed (docs/COMMIT-SIGNING.md)
  • No new flat-key AppSettings calls — use nested-JSON-under-one-key (Principle V)
  • Code is clean-room — not decompiled, disassembled, or reverse-engineered from a proprietary binary (Principle IV)
  • All meter UI uses MeterSmoother (AGENTS.md convention)
  • Documentation updated if user-visible behavior changed
  • Security-sensitive changes reference a GHSA if applicable

@rfoust rfoust marked this pull request as ready for review June 20, 2026 06:14
@rfoust rfoust requested review from a team as code owners June 20, 2026 06:14
Copilot AI review requested due to automatic review settings June 20, 2026 06:14

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 /status preflight 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.

Comment thread src/core/KiwiSdrClient.cpp
Comment thread src/core/KiwiSdrClient.cpp
Comment thread src/gui/MainWindow_KiwiSdr.cpp

@aethersdr-agent aethersdr-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
@rfoust rfoust force-pushed the codex/kiwisdr-server-policy-followup branch from 1c3bd8a to 6e41f38 Compare June 20, 2026 12:43
…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 ten9876 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ 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=0 honored at connect — every connection path funnels through the /status preflight; ext_api=0State::Error before 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:

  1. 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.
  2. too_busy=0 behavior 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.

@ten9876 ten9876 merged commit 845ab55 into aethersdr:main Jun 20, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants