mirror of
https://github.com/d4rken-org/capod.git
synced 2026-09-16 03:06:11 -04:00
feat(aap): Decode unknown settings, extend DeviceInfo, fix UTF-8 parsing
- Add UnknownSetting catch-all for 9 unconfirmed H2+ setting IDs (0x29, 0x2C, 0x2F, 0x30, 0x33, 0x37, 0x38, 0x3B, 0x3E) - Downgrade known non-settings commands (0x0000, 0x0002, 0x000C, 0x0017, 0x002B, 0x004E, 0x0055, 0x0057) from INFO to VERBOSE - Add earbud serials and build number to DeviceInfo (parsed from 0x001D segments 8-10, shown in DeviceInfoCard, persisted in cache) - Fix UTF-8 device name parsing (curly quotes etc. no longer break segment indices) - Add [was: ...] to setting change logs for easier protocol diff analysis - Label DeviceInfoDump segments 5-12 (firmwareVersionDup, protocolVersion, updaterAppId, earbudSerials, buildNumber, encryptedBlob, timestamp)
This commit is contained in:
+28
@@ -572,4 +572,32 @@ class DefaultAapDeviceProfileTest : BaseAapSessionTest() {
|
||||
@Test fun `unknown setting ID returns null`() { profile.decodeSetting(settingsMessage(0x7F, 0x01)).shouldBeNull() }
|
||||
@Test fun `non-settings command returns null`() { profile.decodeSetting(aapMessage("04 00 04 00 1D 00 01 02 03 04")).shouldBeNull() }
|
||||
@Test fun `payload too short returns null`() { profile.decodeSetting(aapMessage("04 00 04 00 09 00 0D")).shouldBeNull() }
|
||||
|
||||
// ── DeviceInfo UTF-8 ────────────────────────────────────
|
||||
|
||||
@Test
|
||||
fun `decodeDeviceInfo handles curly quote in device name`() {
|
||||
// "Matthias\u2019s AirPods Pro" — curly right single quote is UTF-8 E2 80 99.
|
||||
// The old ASCII-only parser would split at these bytes, breaking all segment indices.
|
||||
// Header bytes must all be < 0x20 to avoid being mistaken for string content.
|
||||
val msg = aapMessage(
|
||||
"04 00 04 00 1D 00 02 0F 00 04 00",
|
||||
// [0] name: "Matthias\u2019s AirPods Pro" (UTF-8)
|
||||
"4D 61 74 74 68 69 61 73 E2 80 99 73 20 41 69 72 50 6F 64 73 20 50 72 6F 00",
|
||||
// [1] model
|
||||
"41 33 30 34 38 00",
|
||||
// [2] manufacturer
|
||||
"41 70 70 6C 65 20 49 6E 63 2E 00",
|
||||
// [3] serial
|
||||
"57 35 4A 37 4B 56 30 4E 30 34 00",
|
||||
// [4] firmware
|
||||
"37 41 33 30 35 00",
|
||||
)
|
||||
val info = profile.decodeDeviceInfo(msg)!!
|
||||
info.name shouldBe "Matthias\u2019s AirPods Pro"
|
||||
info.modelNumber shouldBe "A3048"
|
||||
info.manufacturer shouldBe "Apple Inc."
|
||||
info.serialNumber shouldBe "W5J7KV0N04"
|
||||
info.firmwareVersion shouldBe "7A305"
|
||||
}
|
||||
}
|
||||
|
||||
+14
-4
@@ -56,6 +56,9 @@ class AirPodsPro2UsbcAapSessionTest : BaseAapSessionTest() {
|
||||
info.name shouldBe "AirPods Pro"
|
||||
info.modelNumber shouldBe "A3048"
|
||||
info.manufacturer shouldBe "Apple Inc."
|
||||
info.leftEarbudSerial shouldBe "H3KL7HR926JY"
|
||||
info.rightEarbudSerial shouldBe "H3KL2AYL26K0"
|
||||
info.buildNumber shouldBe "8454480"
|
||||
}
|
||||
|
||||
// ── Battery ──────────────────────────────────────────────
|
||||
@@ -242,11 +245,18 @@ class AirPodsPro2UsbcAapSessionTest : BaseAapSessionTest() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `unknown settings IDs return null`() {
|
||||
val unknownIds = listOf(0x29, 0x2C, 0x2F, 0x33)
|
||||
for (id in unknownIds) {
|
||||
profile.decodeSetting(settingsMessage(id, 0x02)).shouldBeNull()
|
||||
fun `unconfirmed settings IDs decode as UnknownSetting`() {
|
||||
val unconfirmedIds = listOf(0x29, 0x2C, 0x2F, 0x33)
|
||||
for (id in unconfirmedIds) {
|
||||
val setting = decodeSetting<AapSetting.UnknownSetting>(settingsMessage(id, 0x02))
|
||||
setting.settingId shouldBe id
|
||||
setting.rawValue shouldBe 0x02
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `truly unknown setting ID returns null`() {
|
||||
profile.decodeSetting(settingsMessage(0x7F, 0x01)).shouldBeNull()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+14
-4
@@ -56,6 +56,9 @@ class AirPodsPro3AapSessionTest : BaseAapSessionTest() {
|
||||
info.name shouldBe "AirPods Pro 3"
|
||||
info.modelNumber shouldBe "A3064"
|
||||
info.manufacturer shouldBe "Apple Inc."
|
||||
info.leftEarbudSerial shouldBe "GMPHNZ16P5Z0000UHZ"
|
||||
info.rightEarbudSerial shouldBe "GMVHNX15UED0000UHY"
|
||||
info.buildNumber shouldBe "8454624"
|
||||
}
|
||||
|
||||
// ── Battery ──────────────────────────────────────────────
|
||||
@@ -236,11 +239,18 @@ class AirPodsPro3AapSessionTest : BaseAapSessionTest() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `unknown settings IDs return null`() {
|
||||
val unknownIds = listOf(0x29, 0x2C, 0x2F, 0x33, 0x30, 0x37, 0x38, 0x3B)
|
||||
for (id in unknownIds) {
|
||||
profile.decodeSetting(settingsMessage(id, 0x01)).shouldBeNull()
|
||||
fun `unconfirmed settings IDs decode as UnknownSetting`() {
|
||||
val unconfirmedIds = listOf(0x29, 0x2C, 0x2F, 0x33, 0x30, 0x37, 0x38, 0x3B)
|
||||
for (id in unconfirmedIds) {
|
||||
val setting = decodeSetting<AapSetting.UnknownSetting>(settingsMessage(id, 0x01))
|
||||
setting.settingId shouldBe id
|
||||
setting.rawValue shouldBe 0x01
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `truly unknown setting ID returns null`() {
|
||||
profile.decodeSetting(settingsMessage(0x7F, 0x01)).shouldBeNull()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+3
@@ -56,6 +56,9 @@ class AirPodsProAapSessionTest : BaseAapSessionTest() {
|
||||
info.name shouldBe "AirPods Pro"
|
||||
info.modelNumber shouldBe "A2084"
|
||||
info.manufacturer shouldBe "Apple Inc."
|
||||
info.leftEarbudSerial shouldBe "GXDDRFNW0C6K"
|
||||
info.rightEarbudSerial shouldBe "H6RHL0HF0C6J"
|
||||
info.buildNumber shouldBe "3344646"
|
||||
}
|
||||
|
||||
// ── Battery ──────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user