Skip to content

Fix FlexRadio meter-stream crash on a nameless dBm/swr meter#495

Open
patrickrb wants to merge 1 commit into
devfrom
optio/task-80c5c7d3-14f6-4907-8f48-41f93eabfcf9
Open

Fix FlexRadio meter-stream crash on a nameless dBm/swr meter#495
patrickrb wants to merge 1 commit into
devfrom
optio/task-80c5c7d3-14f6-4907-8f48-41f93eabfcf9

Conversation

@patrickrb

Copy link
Copy Markdown
Owner

Summary

FlexMeterList.setMeters() guards the unknown-meter case (infos.get(val) == null) but then unconditionally dereferences meter.name.contains("PWR") in the dBm/swr branch. FlexMeterInfo.nam is null whenever a meter-definition frame carried a .unit token but no (or an empty) .nam token, so a dBm/swr meter with no name threw an uncaught NullPointerException, crashing the whole app.

This is a direct follow-up to PR #491 (which hardened the sibling FlexMeterInfos parser against a corrupt/partial METER_LIST frame) — same subsystem, same read-thread crash class.

Root cause

Unsafe coupling: the consumer (FlexMeterList) assumes .nam and .unit are always present together, but FlexMeterInfos populates them independently from separate #-delimited tokens. A partial/split METER_LIST frame that supplies only N.unit=dBm for a meter whose .nam was never seen (or a N.nam= with an empty value, which split("=") drops) yields unit=dBm/swr, nam=null. The existing FlexMeterInfosTest.reparseUpdatesExistingDefinitions test already exercises exactly this "unit without name" shape via setMeterInfos("meter 1.unit=SWR").

Why it crashes the whole app

Meter value frames are parsed on the FlexRadio meter-stream read thread, whose loop catches only SocketException/IOException (the same context documented for the PR #491 crash). The escaping NullPointerException is not caught, so the app dies — and because setMeters runs on every meter update, it recurs on every subsequent frame.

Fix

Null-guard meter.name before contains("PWR"). A nameless meter cannot be identified as a PWR meter, so it simply retains its raw dBm value — the correct conservative behavior. One-line, localized, no protocol/behavior change for well-formed frames.

Testing performed

  • New: FlexMeterListTest (pure JVM, no Robolectric). Written first; the two nameless-meter cases reproduced the NullPointerException before the fix and pass after:
    • dbmMeterWithoutName_doesNotCrash / swrMeterWithoutName_doesNotCrash — the crash paths.
    • namedPwrMeter_stillConvertsDbmToPower — FWDPWR dBm→watt conversion still works (30 dBm → 1.0 W).
    • namedNonPwrDbmMeter_keepsRawDbm — named non-PWR dBm meter keeps raw value.
    • unknownMeterId_isSkipped — pins the existing unknown-id guard.
  • ./gradlew :app:testDebugUnitTest — full suite green.
  • ./gradlew :app:assembleDebug — builds/packages clean.

Risk assessment

Very low. The change only adds a != null short-circuit ahead of an existing dereference; for any meter that does have a name (the normal case), behavior is identical. No native/DSP code touched, no protocol change, no performance impact (one reference comparison per dBm/swr meter update).

🤖 Generated with Claude Code

FlexMeterList.setMeters() already guards the unknown-meter case
(infos.get(val) == null) but then unconditionally dereferences
meter.name.contains("PWR") in the dBm/swr branch.

FlexMeterInfo.nam is left null whenever the meter-definition frame
carried a .unit token but no (or an empty) .nam token — e.g. a partial
or split METER_LIST frame that supplies "N.unit=dBm" for a meter whose
.nam was never seen (the existing FlexMeterInfosTest.reparseUpdates...
case exercises exactly this "unit without name" shape). Reaching the
dBm/swr branch for such a meter threw an uncaught NullPointerException.

Meter value frames are parsed on the FlexRadio meter-stream read thread,
whose loop catches only SocketException/IOException (same context as the
FlexMeterInfos crash fixed in PR #491), so the escaping NPE crashed the
whole app on every subsequent meter update.

Root cause: unsafe coupling — the consumer assumes .nam and .unit are
always present together, but FlexMeterInfo populates them independently.

Fix: null-guard meter.name before contains("PWR"). A nameless meter can't
be identified as a PWR meter, so it simply keeps its raw dBm value, which
is the correct conservative behavior.

Adds FlexMeterListTest (pure JVM, no Robolectric) covering the nameless
dBm and swr meters (the crash), plus the named FWDPWR dBm->watt
conversion, a named non-PWR dBm meter, and the unknown-id skip guard to
lock in existing behavior.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@codecov

codecov Bot commented Jul 9, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 23.35%. Comparing base (6135d45) to head (db5033a).

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff            @@
##                dev     #495   +/-   ##
=========================================
  Coverage     23.35%   23.35%           
  Complexity      162      162           
=========================================
  Files           167      167           
  Lines         21450    21450           
  Branches       3154     3154           
=========================================
  Hits           5009     5009           
  Misses        16248    16248           
  Partials        193      193           
Flag Coverage Δ
android 13.26% <ø> (ø)
native 9.93% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

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.

1 participant