Context
Testing BleGattSoundTrigger (from #139) against a real-world "Licensed 3rd Party Find My Device" accessory: a RAVEMEN ABF01 bike finder. The GATT write completes successfully (Write Response, no error), but the accessory never actually plays a sound.
What real capture shows the actual protocol to be
Captured the iPhone's own HCI trace for a successful Play Sound against this same accessory (PacketLogger + Apple's iOS Bluetooth logging profile). Two things stood out:
-
The characteristic AirGuard documents (4F860003) isn't the one this accessory uses. The iPhone subscribes to indications on all four fd44 characteristics (4F860001-4) plus one on a second, vendor service (FD43 / 94110001), then writes two opcodes in sequence to 4F860002:
01 00 02 (a handshake/query write - the client always sends this first)
01 09 02 (the actual start-sound opcode)
There's no separate stop opcode - the client just disconnects after ~30s and that ends the sound. The accessory also proactively sends unsolicited indications on 4F860002 reporting its own state (01 0D 02 = idle, 01 0C 02 60 00 00 00 = sounding).
-
Reproducing this exact byte sequence from Android still doesn't work. Implemented the full sequence above (all 4+1 indications, both writes, in order) - GATT-level success throughout, but the accessory stays silent. Captured Android's own HCI snoop log (Enable Bluetooth HCI snoop log in Developer Options) for the same attempt and compared it against the iPhone's trace side by side:
- The iPhone, immediately after connecting, issues
HCI_LE_Start_Encryption with a stable, pre-shared LTK (the same value across multiple separate sessions) and RAND/EDIV both zero - i.e. no SMP pairing exchange happens at all. The key is already known to both sides out-of-band, presumably derived from the same account-level Find My master key that produces the OF rolling public keys.
- The Android capture shows zero encryption or SMP activity whatsoever for the same connection. The GATT write is accepted at the ATT level regardless (no permission bit enforces encryption on the characteristic), but the accessory's firmware appears to gate the actual sound on the link being encrypted with its expected key - a check that has nothing to do with ATT permissions.
Android's public BluetoothGatt/BluetoothDevice API has no method to request encryption with an app-supplied LTK - createBond() is the only path to an encrypted link, and it negotiates a fresh key via standard SMP pairing rather than using a pre-known one. As far as I can tell this makes it impossible for a non-rooted third-party Android app to replicate what Find My/the native app does here, for this class of accessory.
(For due diligence: also tried directly injecting the captured LTK into a rooted test device's Bluetooth bond store, to confirm this conclusively by side-stepping the API gap entirely. Ran into the OS's own Bluetooth HAL and a userspace BlueZ stack fighting over the same radio, which crashed the test device before producing a clean result - so this is corroborating evidence, not a confirmed proof.)
Why post this
Not claiming this is unfixable - just documenting what was actually observed in case:
- it explains other "connects fine, never sounds" reports against third-party accessories,
- someone has a workaround I haven't thought of (a different Android-side approach, a way to get the account's derived LTK into the stack, etc.),
- it's useful context for anyone picking this up later.
Happy to share more of the raw capture if useful. Credit to SEEMOO Lab's AirGuard for the original fd44/DULT/AirTag protocol reverse-engineering that #139 (and this investigation) built on.
Context
Testing
BleGattSoundTrigger(from #139) against a real-world "Licensed 3rd Party Find My Device" accessory: a RAVEMEN ABF01 bike finder. The GATT write completes successfully (Write Response, no error), but the accessory never actually plays a sound.What real capture shows the actual protocol to be
Captured the iPhone's own HCI trace for a successful Play Sound against this same accessory (PacketLogger + Apple's iOS Bluetooth logging profile). Two things stood out:
The characteristic AirGuard documents (
4F860003) isn't the one this accessory uses. The iPhone subscribes to indications on all fourfd44characteristics (4F860001-4) plus one on a second, vendor service (FD43/94110001), then writes two opcodes in sequence to4F860002:01 00 02(a handshake/query write - the client always sends this first)01 09 02(the actual start-sound opcode)There's no separate stop opcode - the client just disconnects after ~30s and that ends the sound. The accessory also proactively sends unsolicited indications on
4F860002reporting its own state (01 0D 02= idle,01 0C 02 60 00 00 00= sounding).Reproducing this exact byte sequence from Android still doesn't work. Implemented the full sequence above (all 4+1 indications, both writes, in order) - GATT-level success throughout, but the accessory stays silent. Captured Android's own HCI snoop log (
Enable Bluetooth HCI snoop login Developer Options) for the same attempt and compared it against the iPhone's trace side by side:HCI_LE_Start_Encryptionwith a stable, pre-shared LTK (the same value across multiple separate sessions) and RAND/EDIV both zero - i.e. no SMP pairing exchange happens at all. The key is already known to both sides out-of-band, presumably derived from the same account-level Find My master key that produces the OF rolling public keys.Android's public
BluetoothGatt/BluetoothDeviceAPI has no method to request encryption with an app-supplied LTK -createBond()is the only path to an encrypted link, and it negotiates a fresh key via standard SMP pairing rather than using a pre-known one. As far as I can tell this makes it impossible for a non-rooted third-party Android app to replicate what Find My/the native app does here, for this class of accessory.(For due diligence: also tried directly injecting the captured LTK into a rooted test device's Bluetooth bond store, to confirm this conclusively by side-stepping the API gap entirely. Ran into the OS's own Bluetooth HAL and a userspace BlueZ stack fighting over the same radio, which crashed the test device before producing a clean result - so this is corroborating evidence, not a confirmed proof.)
Why post this
Not claiming this is unfixable - just documenting what was actually observed in case:
Happy to share more of the raw capture if useful. Credit to SEEMOO Lab's AirGuard for the original
fd44/DULT/AirTag protocol reverse-engineering that #139 (and this investigation) built on.