Fix app crash on malformed / 32-bit Xiegu CAT handle frame#488
Merged
Conversation
The HANDLE branch of X6100Radio.doReceiveLineEvent parsed the Xiegu
client handle with an unguarded Integer.parseInt(head.substring(1), 16).
Every sibling parse in this class (seq_number, resultCode, play_volume)
and the identical handle parse in the Flex sibling (FlexRadio) wrap the
same call in a try/catch; the X6100 variant moved the parse out of the
XieguResponse constructor and lost the guard.
Two inputs make it throw NumberFormatException:
* a garbled or truncated frame whose tail is not hex — even a lone
"H", whose tail is the empty string; and
* a legitimate high-bit 32-bit handle such as "HFFFFFFFF": the Xiegu
protocol handle is a full 32-bit value (see the HANDLE enum doc) and
0xFFFFFFFF overflows a signed int, so Integer.parseInt rejects it.
That parse runs on the TCP CAT read thread (RadioTcpClient.SocketThread),
whose read loop catches only SocketException/IOException. A
NumberFormatException escaping the listener therefore propagates out of
run(), killing the read thread and crashing the whole app via the
default uncaught-exception handler.
Extract the parse into a package-private static parseXieguHandle(head,
currentHandle): it length-guards the input, parses via Long.parseLong so
the full unsigned 32-bit range round-trips into the int handle field,
and contains any remaining malformed input by keeping the current handle
(matching FlexRadio). The handle field is write-only in this class, so
preserving the prior value on a bad frame is behaviourally safe.
Add X6100RadioHandleParseTest covering low handles, full 8-digit and
high-bit handles (the 0xFFFFFFFF / 0x80000000 overflow cases that used
to crash), and the lone-"H"/non-hex/empty/null frames.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## dev #488 +/- ##
=========================================
Coverage 23.34% 23.35%
- Complexity 149 162 +13
=========================================
Files 165 167 +2
Lines 21401 21450 +49
Branches 3149 3154 +5
=========================================
+ Hits 4997 5009 +12
- Misses 16215 16248 +33
- Partials 189 193 +4
Flags with carried forward coverage won't be shown. Click here to find out more. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Pull request overview
This pull request fixes a deterministic crash in the Xiegu (X6100/G90-family) network CAT receive path by guarding the parsing of the radio-assigned client handle so malformed frames and unsigned 32-bit handle values do not throw and escape the TCP read thread.
Changes:
- Replace the unguarded
Integer.parseInt(..., 16)in theHANDLEresponse branch with a guarded helper method. - Add
parseXieguHandle(head, currentHandle)to tolerate malformed/truncated input and accept the full unsigned 32-bit handle range viaLong.parseLong. - Add a pure JVM unit test suite covering valid, malformed, and high-bit handle inputs (including
0xFFFFFFFF/0x80000000).
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| ft8af/app/src/main/java/com/k1af/ft8af/x6100/X6100Radio.java | Introduces guarded Xiegu handle parsing and routes HANDLE responses through it to prevent crashes. |
| ft8af/app/src/test/java/com/k1af/ft8af/x6100/X6100RadioHandleParseTest.java | Adds unit tests to pin behavior for malformed frames and unsigned 32-bit handles. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Copilot review on PR #488: parsing via Long.parseLong alone was too permissive for an unsigned handle — it accepted a leading sign ("H-1" -> -1 instead of keeping the current handle) and accepted more than 8 hex digits and then silently truncated on the narrowing int cast ("H100000000" -> 0). The error log also copied FlexRadio's misleading "parseInt"/"XieguResponse" wording. Validate the tail as 1-8 unsigned hex digits (isUnsignedHex) before parsing; reject anything else and keep the last good handle. After validation Long.parseLong can't throw, and 0x80000000..0xFFFFFFFF still round-trip into the int field with the same 32-bit pattern. Log an accurate message naming the offending tail. Add tests for lowercase hex, a signed tail, an overlong (9-digit) tail, and a whitespace tail. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes a deterministic full-app crash on the Xiegu (X6100/G90-family) network CAT path.
X6100Radio.doReceiveLineEventparsed the radio-assigned client handle with an unguarded parse:Every other parse in this class (
seq_number,resultCode,play_volume) and the identical handle parse in the siblingflex/FlexRadiowrap the same call in atry/catch. The X6100 variant moved this parse out of theXieguResponseconstructor and lost the guard.Root cause
Two inputs make
Integer.parseInt(head.substring(1), 16)throwNumberFormatException:"H", whose tail is the empty string (parseInt("", 16)throws)."HFFFFFFFF". TheHANDLEenum documents the field as a 32-bit hex representation;0xFFFFFFFF(and anything ≥0x80000000) overflows a signedint, soInteger.parseIntrejects roughly half of all valid handles.The parse runs on the TCP CAT read thread (
flex/RadioTcpClient.SocketThread.run), whose read loop catches onlySocketException/IOException:A
NumberFormatExceptionfrom the listener therefore propagates out ofrun(), kills the read thread, and crashes the whole app via the default uncaught-exception handler.Fix
Extract the parse into a package-private static
parseXieguHandle(head, currentHandle)that:null/ too-short → keep current handle),Long.parseLong(..., 16)so the full unsigned 32-bit range round-trips into theinthandle field (same bit pattern), andFlexRadio).Long.parseLongis used rather thanInteger.parseUnsignedIntbecause the latter is API 26+ and this module'sminSdkis 23 (no core-library desugaring).The
handlefield is write-only within this class (never read), so preserving the prior value on a bad frame is behaviourally safe; the only observable effect of the old code was the crash.Testing
X6100RadioHandleParseTest(pure JUnit): low handles, full 8-digit handles, the0xFFFFFFFF/0x80000000high-bit cases that previously overflowed and crashed, plus lone-"H"/ non-hex / empty / null frames. Against the old code the high-bit, lone-"H", and non-hex cases throwNumberFormatException, so they pin the regression../gradlew :app:testDebugUnitTest— full suite green, no regressions../gradlew :app:assembleDebug— builds (native + packaging).Risk
Low. One localized method extraction on the Xiegu CAT receive path; no protocol/DSP/encoder behaviour changes; valid handles ≤
0x7FFFFFFFparse identically to before.🤖 Generated with Claude Code