Document host 0x004D handshake and harden 0x001D parsing on Android - #797
Open
jashparekh wants to merge 1 commit into
Open
jashparekh wants to merge 1 commit into
jashparekh wants to merge 1 commit into
Conversation
Add host-capabilities docs for third-party Android L2CAP clients, extend device-info notes, and pace the initial AACP connect burst with short delays.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Third-party Android hosts that open the Apple AAP L2CAP channel (PSM
0x1001) often miss unsolicited device information (0x001D) even when the link is up. Common causes are skipping host capabilities (0x004D) after the initial handshake, sending subscribe packets back-to-back with no pacing, discarding inbound data before the read loop runs, and parsing0x001Das if UTF-8 fields always start at byte offset 6.This PR documents the recommended connect sequence for non-Apple clients, fixes the broken
docs/host-capabilities.mdlink fromopcodes.md, and makes small Android-side changes so LibrePods is more tolerant of real-world0x001Dframing.Not in scope: changing the
0xD7feature-flag payload LibrePods already sends, fixing Fluoride L2CAP/socket creation on devices that never connect, or resolving notification-mask differences (FF FF FE FFvsFF FF FF FF) — see #770.Problem / motivation
opcodes.mdlinks to/docs/host-capabilities.md, which did not exist (404).docs/device-info.mddescribed field order but not Android-specific framing (nested04 00 04 00headers, length prefixes, multiple0x001DSDUs per session).AACPManager.parseInformationPacket()assumed payload strings always begin after a fixed 6-byte header slice; some stacks deliver a longer preamble, yielding empty name/model/serial in the UI while the wire still contains valid strings.AirPodsServicesent handshake →0x004D→0x0Fimmediately on the IO thread, while a delayed coroutine repeated the same sequence 200 ms later. The first burst had no pacing; on timing-sensitive hosts the accessory sometimes emitted a thin startup burst (short0x002Bonly) before richer metadata arrived.Documentation changes
New:
docs/host-capabilities.md0x004D: host → accessory, after handshake.0x0001→ ~100–350 ms →0x004D→ ~100–350 ms →0x000F→ continuous read.FF…per AAP Definitions, LibrePodsD7…in app code, shorterFFvariants in other clients).0x001Dmay arrive before notification register completes;0x004Fis not a device-info read.Updated:
docs/device-info.md0x001Dframes; battery0x0004may be sparse even when0x001Dsucceeds.Updated:
docs/opcodes.md,docs/AAP Definitions.md0x004Das host → accessory; link new doc after handshake section.Android code changes
AACPManager.parseInformationPacket()04 00 04 00 1D 00headers, and score candidate null-terminated UTF-8 string runs (e.g. manufacturerApple Inc., modelA####, serial-shaped tokens).AirPodsInformationfields.docs/device-info.md.AirPodsService(L2CAP connect)sendSetFeatureFlagsPacket()/sendNotificationRequest()sequence into the existingDispatchers.IOcoroutine withdelay(200)between steps (same pacing already used for the retry burst).Manual testing (Google Pixel, Android 17)
Testing was done on a Google Pixel phone running Android 17 with bonded AirPods Pro (USB-C and Pro 3 generations), using the native L2CAP path (no root/Xposed required on this OS build for AAP socket connect).
0x1001succeeds0x004D, accessory sends burst including0x002Band unsolicited0x001Don good runs0x002B(~102 B) and no0x001Don the wire0x002Band0x001D(~200+ B payload); name, model, firmware, and serial fields populate in app storage / UI0x001Dframes with non-zero preamble before string block0x0004sometimes absent on Android even when0x001Dpresent (documented; not fixed here)Platforms: Changes are not gated on SDK 37. They help any device where LibrePods already establishes AAP L2CAP; they do not by themselves fix OEMs that cannot open the socket (see existing L2CAP issue threads).
Related issues (partial overlap)
0x4D; battery notification mask still openTest plan (for reviewers)
host-capabilities.md←opcodes.md,device-info.md, AAP Definitions handshake section.AACPManagertest harness; parser change is covered by manual0x001Dcaptures.Files changed
docs/host-capabilities.md(new)docs/device-info.md,docs/opcodes.md,docs/AAP Definitions.mdandroid/.../AACPManager.kt,android/.../AirPodsService.kt