Harden the USB adapter lifecycle - #116
Conversation
Four separate ways the adapter path can take the app down or wedge it. All of them are easy to hit on a powered hub that re-enumerates the dongle, which is how a lot of ground stations are wired. 1. Deliberate null deref. WfbngLink::stop() ran a CRASH() macro (`int *i = 0; *i = 42;`) when the fd was no longer in rtl_devices. That is a recoverable state - the adapter was already gone - and it killed the process. Removed, now a warning and return. 2. NPE on openDevice(). UsbManager.openDevice() returns null when the permission was revoked or the device disappeared between the permission check and the open; getFileDescriptor() was called on it unconditionally. start() now returns false instead, WfbLinkManager reports it and leaves the adapter out of activeWifiAdapters so the next refresh retries it. Before, a failed adapter was recorded as active and never retried. 3. Leaked usbfs descriptors. UsbDeviceConnection was never closed and linkConns was never cleared, so every attach/detach cycle leaked one fd plus the map entry. 4. USB permission dialog on Android 14. requestPermission() got a PendingIntent built from an implicit Intent. Android 14 refuses to deliver those to a runtime registered receiver, so the result never arrived and the app sat on "No permission for wifi adapter(s)". setPackage() added. Also: refreshAdapters() dereferenced getAttachedAdapters() without checking for the null it returns when the device filter fails to parse, and the wfb thread name indexed split()[1] without checking the device name matched /dev/bus/usb/.
PR Summary by QodoHarden USB adapter lifecycle and recovery
AI Description
Diagram
High-Level Assessment
Files changed (3)
|
Code Review by Qodo
1. Early detach wedges worker
|
| __android_log_print(ANDROID_LOG_WARN, TAG, "stop: no rtl device for fd=%d, already gone", fd); | ||
| return; |
There was a problem hiding this comment.
1. Early detach wedges worker 🐞 Bug ☼ Reliability
If stop() runs after Java starts the worker but before native run() registers the fd, the new early return sends no stop signal and Java then blocks indefinitely in Thread.join(). The worker can subsequently finish initialization and enter the blocking RX loop, wedging detach handling or activity shutdown.
Agent Prompt
## Issue description
A stop request arriving before native fd registration is discarded, after which Java can wait forever for the worker that proceeds into its blocking RX loop.
## Issue Context
`WfbNgLink.start()` publishes and starts the Java thread asynchronously. Native setup does not insert the fd into `rtl_devices` until later, while Java `stop()` immediately calls native stop and then joins without a timeout.
## Fix Focus Areas
- app/wfbngrtl8812/src/main/cpp/WfbngLink.cpp[86-128]
- app/wfbngrtl8812/src/main/cpp/WfbngLink.cpp[278-283]
- app/wfbngrtl8812/src/main/java/com/openipc/wfbngrtl8812/WfbNgLink.java[111-115]
- app/wfbngrtl8812/src/main/java/com/openipc/wfbngrtl8812/WfbNgLink.java[149-157]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
Note
Compile tested only (arm64-v8a + armeabi-v7a). The happy path is unchanged;
what changes is what happens when the adapter is not there. Testing with a
hub that re-enumerates the dongle, and with the permission dialog dismissed,
would be the useful check.
Four separate ways the adapter path can take the app down or wedge it. All of
them are easy to hit on a powered OTG hub that re-enumerates the dongle, which is
how a lot of ground stations are wired — and a crash here means going blind mid
flight.
1. Deliberate null dereference
WfbngLink.cppdefinesand runs it in
WfbngLink::stop()when the fd is no longer inrtl_devices.That is a recoverable state — the adapter was already gone — and it kills the
process. Removed; now a warning and a return.
2. NPE on
openDevice()openDevice()returnsnullwhen the permission was revoked or the devicedisappeared between
hasPermission()and here.WfbNgLink.start()now returnsboolean, andWfbLinkManager.startAdapter()reports the failure instead ofcrashing.
That also fixes a second-order bug:
refreshAdapters()used to add the device toactiveWifiAdaptersunconditionally, so an adapter that failed to start wasrecorded as running and never retried on a later refresh. It is only tracked now
if it actually came up.
3. Leaked usbfs descriptors
UsbDeviceConnectionwas neverclose()d andlinkConnswas never cleared, soevery attach/detach cycle leaked one file descriptor plus the map entry. Both
stop()andstopAll()now close and remove.4. USB permission dialog on Android 14
The intent is implicit. Since Android 14 a
PendingIntentbuilt from an implicitintent is not delivered to a runtime-registered receiver, so the permission
result never arrives and the app sits on "No permission for wifi adapter(s)"
even after the user granted it.
setPackage(context.getPackageName())added.Also
refreshAdapters()dereferencedgetAttachedAdapters()without checking thenullit returns whenusb_device_filter.xmlfails to parsesplit("/dev/bus/usb/")[1]without checking thedevice name actually matched
Not in this PR
The wfb-ng RX thread is a plain
new Thread(...)at default priority, eventhough it is the thread pumping libusb. Giving it a realtime-ish priority is
probably worth doing, but it is a behaviour change that deserves its own PR.
Part of a small series of independent fixes found while profiling the receive path.
Each one is standalone and mergeable on its own, in any order — no dependencies
between them, and no shared files except
VideoActivity.java/VideoPlayer.*,which touch different methods:
All five compile clean for arm64-v8a + armeabi-v7a.