From 8a0c1d2f6cca640354a292a97f819efe16ce42de Mon Sep 17 00:00:00 2001 From: Carter Li Date: Tue, 13 Oct 2026 20:48:36 +0800 Subject: [PATCH] Wifi (Android): fixes Android 17 support Fixes the Wifi transmit link speed being read from a field a vendor writes in front of it, rather than from the transmit speed itself --- src/detection/wifi/wifi_android.c | 55 +++++++++++++++++-------------- 1 file changed, 30 insertions(+), 25 deletions(-) diff --git a/src/detection/wifi/wifi_android.c b/src/detection/wifi/wifi_android.c index 120f9b531..01c2dbee2 100644 --- a/src/detection/wifi/wifi_android.c +++ b/src/detection/wifi/wifi_android.c @@ -109,17 +109,18 @@ // more accurate -- pays nothing for it. // // Three things about the call are positional rather than negotiated, and all three were measured -// on an Android 16 device and an Android 11 device: +// across the Android 11, 13, 16 and 17 devices: // // * The transaction code is a build-time constant of the `.aidl` (`getConnectionInfo` is 29 on -// Android 11 and 41 on Android 16) -- see common/android/dex.h. +// Android 11, 36 on Android 13 and 41 on Android 16 and 17) -- see common/android/dex.h. // * The reply layout drifts, and not only by release. Android 12 dropped the duplicate length word -// that Android 11 and 10 wrote in front of the SSID octets, and the Android 16 device fills an -// int between the transmit and the receive speed that the Android 11 device leaves out -- while -// AOSP's own Android 16 writes the head exactly the Android 11 way, so that one int is a vendor -// addition rather than a release difference. Frequency is the only channel centre in the head, so -// it is located by value, with the whole result validated before it is used. A wrong guess shows -// up as a rejected layout, not as a plausible wrong number. +// that Android 11 and 10 wrote in front of the SSID octets, and the Android 17 device writes a +// word of its own between the link speed and the transmit speed that the Android 11 and 13 +// devices leave out -- a vendor addition rather than a release difference, since AOSP's own +// Android 16 writes the head the Android 11 way. Frequency is the only channel centre in the +// head, so it is located by value, with the whole result validated before it is used, and the +// two direction specific speeds are read as its neighbours. A wrong guess shows up as a +// rejected layout, not as a plausible wrong number. // * The tail of the parcel, which holds the Wi-Fi standard, is not laid out the same way by every // vendor: the Android 11 device carries three words there that AOSP's own Android 11 // does not. It is located by shape for that reason. @@ -171,13 +172,14 @@ typedef enum FFWifiAndroidRadioState : int32_t } FFWifiAndroidRadioState; // An AIDL reply opens with the exception code and, for a Parcelable return, a non-null marker; -// WifiInfo itself then starts at 8. The network id, the RSSI and the link speed have held those -// offsets from Android 9 to 16, and the transmit speed joined them in Android 10 without moving -// since, so all four are read at fixed offsets. Everything after them is located by shape. +// WifiInfo itself then starts at 8. The network id, the RSSI and the link speed are the first three +// fields `writeToParcel` writes, so nothing a release or a vendor adds behind them can move them -- +// they have held those offsets from Android 9 to 17 and are read there. The two direction specific +// speeds sit behind them but are located from the frequency instead, because a vendor does write a +// word of its own in front of that pair: see the note at FF_WIFI_ANDROID_FREQ_TX_LINK_SPEED. #define FF_WIFI_ANDROID_OFF_NET_ID 8 #define FF_WIFI_ANDROID_OFF_RSSI 12 #define FF_WIFI_ANDROID_OFF_LINK_SPEED 16 -#define FF_WIFI_ANDROID_OFF_TX_LINK_SPEED 20 // A `WifiInfo` with no connection to describe carries the sentinels for the two fields that are // always at a fixed offset: -1 for a network id it does not have and -127 for a signal it cannot @@ -206,11 +208,15 @@ typedef enum FFWifiAndroidRadioState : int32_t #define FF_WIFI_ANDROID_FREQ_SSID_BASE_NO_IP 0x08 #define FF_WIFI_ANDROID_SSID_LENGTH 0x04 -// The receive speed is the word in front of Frequency; the transmit speed is the word after the link -// speed. The Android 16 device writes one extra word between those two that AOSP does not -- it repeats the -// transmit speed -- so the receive speed can only be reached from Frequency, and Frequency cannot be -// reached by stepping over the two link speeds. Both were checked against `cmd wifi status` at the -// same moment: 131/219 on the Android 16 device, 86/-1 on the Android 11 device. +// The transmit speed and the receive speed are the two words in front of Frequency, in that order: +// `mTxLinkSpeed, mRxLinkSpeed, mFrequency` are written back to back by every head this file has +// been checked against, from Android 11 through 17. The pair is read from Frequency rather than +// stepped to from the link speed, because the Android 17 device writes a word of its own between +// the link speed and the transmit speed: its dex names that word `mCurrentLinkSpeed`, so the word +// after the link speed is not the transmit speed there and the pair sits one word further along. +// Both were checked against `cmd wifi status` at the same moment: 131/219 on the Android 16 device, +// 86/-1 on the Android 11 device. +#define FF_WIFI_ANDROID_FREQ_TX_LINK_SPEED 0x08 #define FF_WIFI_ANDROID_FREQ_RX_LINK_SPEED 0x04 #define FF_WIFI_ANDROID_SSID 0x08 // Android 12 and later #define FF_WIFI_ANDROID_SSID_LEGACY 0x0c // Android 11 and older @@ -643,7 +649,7 @@ static void debugDumpReply(const uint8_t* data, size_t size) { #endif static const char* parseConnectionInfo(const uint8_t* data, size_t size, FFWifiAndroidConnection* connection) { - if (size < FF_WIFI_ANDROID_OFF_TX_LINK_SPEED + 4) { + if (size < FF_WIFI_ANDROID_FREQ_SCAN_FROM + 4) { FF_DEBUG("The reply is %zu bytes, too short to hold a WifiInfo", size); return "The reply is too short to hold a WifiInfo"; } @@ -666,12 +672,11 @@ static const char* parseConnectionInfo(const uint8_t* data, size_t size, FFWifiA FF_DEBUG("The reply is %zu bytes and carries no WifiInfo", size); return nullptr; } - FF_DEBUG("Reply is %zu bytes: netId %d, rssi %d, link speed %d, tx link speed %d", + FF_DEBUG("Reply is %zu bytes: netId %d, rssi %d, link speed %d", size, ffBinderReadI32(data, size, FF_WIFI_ANDROID_OFF_NET_ID), ffBinderReadI32(data, size, FF_WIFI_ANDROID_OFF_RSSI), - ffBinderReadI32(data, size, FF_WIFI_ANDROID_OFF_LINK_SPEED), - ffBinderReadI32(data, size, FF_WIFI_ANDROID_OFF_TX_LINK_SPEED)); + ffBinderReadI32(data, size, FF_WIFI_ANDROID_OFF_LINK_SPEED)); // The two orders WifiSsid has written its length in: once before the octets on Android 11 and // older, not at all on Android 12 and later, which leaves the octets one word earlier. The one @@ -755,7 +760,7 @@ static const char* parseConnectionInfo(const uint8_t* data, size_t size, FFWifiA connection->frequency = frequency; connection->rssi = ffBinderReadI32(data, size, FF_WIFI_ANDROID_OFF_RSSI); connection->linkSpeed = ffBinderReadI32(data, size, FF_WIFI_ANDROID_OFF_LINK_SPEED); - connection->txLinkSpeed = ffBinderReadI32(data, size, FF_WIFI_ANDROID_OFF_TX_LINK_SPEED); + connection->txLinkSpeed = ffBinderReadI32(data, size, base - FF_WIFI_ANDROID_FREQ_TX_LINK_SPEED); connection->rxLinkSpeed = ffBinderReadI32(data, size, base - FF_WIFI_ANDROID_FREQ_RX_LINK_SPEED); findStrings(data, size, stringsFrom, connection); @@ -777,9 +782,9 @@ static const char* parseConnectionInfo(const uint8_t* data, size_t size, FFWifiA if (!findSecurityType(data, size, connection)) { FF_DEBUG("No security type could be located in this reply, so the field stays empty"); } - FF_DEBUG("Standard %d (%s), rx link speed %d, connected %s", - connection->standard, wifiStandardName(connection->standard) ?: "unknown", connection->rxLinkSpeed, - connection->connected ? "yes" : "no"); + FF_DEBUG("Standard %d (%s), tx link speed %d, rx link speed %d, connected %s", + connection->standard, wifiStandardName(connection->standard) ?: "unknown", connection->txLinkSpeed, + connection->rxLinkSpeed, connection->connected ? "yes" : "no"); return nullptr; }