diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/ble/protocol/ProximityPairing.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/ble/protocol/ProximityPairing.kt index a8a9b591..8f9ecf82 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/ble/protocol/ProximityPairing.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/ble/protocol/ProximityPairing.kt @@ -2,7 +2,9 @@ package eu.darken.capod.pods.core.apple.ble.protocol import android.bluetooth.le.ScanFilter import dagger.Reusable +import eu.darken.capod.common.debug.logging.Logging.Priority.DEBUG import eu.darken.capod.common.debug.logging.log +import eu.darken.capod.common.debug.logging.logTag import javax.inject.Inject @@ -16,6 +18,17 @@ object ProximityPairing { return null } + // Pods also emit type 0x07 frames whose payload does not use the plaintext status + // format, e.g. prefix 0x07 from the identity address on connect (#603). Their bytes + // are garbage at the standard offsets and would create a ghost device. + if (message.data.firstOrNull() != PAIRING_MESSAGE_PREFIX) { + log(TAG, DEBUG) { + val hex = message.data.joinToString(" ") { "%02X".format(it.toInt()) } + "Dropping proximity pairing message with unsupported prefix: [$hex]" + } + return null + } + return ProximityMessage( type = message.type, length = message.length, @@ -49,4 +62,9 @@ object ProximityPairing { // This is the default message length among official Apple devices, clones may have different length internal const val PAIRING_MESSAGE_LENGTH = 25 + + // First payload byte of every known plaintext status broadcast, including pairing mode + internal val PAIRING_MESSAGE_PREFIX = 0x01.toUByte() + + private val TAG = logTag("Pod", "Apple", "ProximityPairing") } diff --git a/app/src/test/java/eu/darken/capod/pods/core/apple/ble/devices/airpods/AirPodsGen4Test.kt b/app/src/test/java/eu/darken/capod/pods/core/apple/ble/devices/airpods/AirPodsGen4Test.kt index acd25181..9968b7ea 100644 --- a/app/src/test/java/eu/darken/capod/pods/core/apple/ble/devices/airpods/AirPodsGen4Test.kt +++ b/app/src/test/java/eu/darken/capod/pods/core/apple/ble/devices/airpods/AirPodsGen4Test.kt @@ -1,6 +1,7 @@ package eu.darken.capod.pods.core.apple.ble.devices.airpods import eu.darken.capod.pods.core.apple.PodModel +import eu.darken.capod.pods.core.apple.ble.BlePodSnapshot import eu.darken.capod.pods.core.apple.ble.devices.BaseBlePodsTest import eu.darken.capod.pods.core.apple.ble.devices.DualApplePods import eu.darken.capod.pods.core.apple.ble.devices.HasAppleColor @@ -43,4 +44,14 @@ class AirPodsGen4Test : BaseBlePodsTest() { model shouldBe PodModel.AIRPODS_GEN4 } } -} \ No newline at end of file + + @Test + fun `frame with unsupported payload prefix is rejected - #603`() = runTest { + // Captured from AirPods Gen4 on connect: sent from the identity address, payload prefix + // 0x07 instead of 0x01, model bytes match but battery/color bytes are not plaintext. + // Decoding it as a status frame created a duplicate device card. + create("07 19 07 19 20 35 DF DE 46 76 CE 66 5C F4 6E BA B6 AC 1B 69 4C A7 D8 09 BF 9A 04") { + this shouldBe null + } + } +}