Purpose: step-by-step procedure to capture, extract, and analyze Bluetooth HCI traffic
between a phone and the Pixel Buds Pro 2, in order to fill in the confidence-rated
placeholders in PROTOCOL.md (magic bytes, checksum, opcode table, battery
approach). This document is the how — the relevant CAP-NNN-FINDINGS.md and
PROTOCOL.md are the what we found.
Two devices are used, for two different purposes:
| Device | Role | Why |
|---|---|---|
| Pixel 7a, Android 17 (with Google Play Services), official Pixel Buds app | Primary capture | Only source of actual libmaestro command frames (ANC toggle, EQ write) triggered on demand |
| Pixel 9a, GrapheneOS, no Pixel Buds app | Secondary / validation capture | Confirms pairing/bonding behavior, GATT service discovery, and any spontaneous/OS-level traffic on the actual target OS, without app-driven commands |
Do the Pixel 7a capture first — it's the one that produces frames you can actually match
against pbpctrl's documented commands. The Pixel 9a capture is a validation/baseline
step, not a substitute.
- Wireshark installed (has a built-in Bluetooth HCI dissector — no plugins needed).
- Android platform-tools (
adb) installed and on yourPATH. - A few hundred MB of free disk space —
adb bugreportoutput can be large. - A USB cable for each phone (data-capable, not charge-only).
- Developer options enabled.
- USB debugging enabled.
- Bluetooth HCI snoop logging enabled (see §2).
- The Pixel Buds Pro 2 already known to pair reliably with that phone (test this once before your real capture session, so pairing issues don't eat into your capture time or contaminate your log with retries).
A notes app, a voice memo, or simply writing down wall-clock times as you go. This is what lets you match a Wireshark frame to "the moment I tapped ANC → Off" later. Don't skip this — without it, correlating frames to actions is much harder after the fact.
Applies to both phones; steps are identical regardless of GrapheneOS vs. stock Android, since HCI snoop logging is an AOSP-level feature.
- Enable Developer options (skip if already enabled): Settings → About phone → Software information → tap Build number 7 times.
- Go to Settings → System → Developer options.
- Enable "Enable Bluetooth HCI snoop log".
- Enable "USB debugging".
- Restart Bluetooth — reboot the phone (recommended default). A toggle off/on (Settings → Bluetooth) is the officially documented way to make the logger pick up the setting, and normally works. A reboot is recommended here anyway, for a cost-based reason rather than a confirmed reliability problem: on the rare occasion the toggle doesn't take, the failure is silent — you only discover it after doing a full round of actions and pulling an empty bugreport (see the FAQ in §7). A phone reboot costs about a minute; redoing a capture session doesn't. If you've already verified on your specific phone that a plain toggle reliably starts logging, that's a fine, faster alternative — just confirm it once (§7 FAQ has the recovery steps if a session comes back empty either way).
- Connect the phone to your computer via USB and accept the "Allow USB debugging?" dialog on the phone (approve your computer's RSA key fingerprint).
- Verify the connection:
adb devicesshould list the phone asdevice(notunauthorizedoroffline).
Note on log rotation: the on-device snoop log has a size cap and will eventually overwrite old data during long sessions. Keep individual capture sessions short and focused (a handful of isolated actions, see §4) rather than one long open-ended session, so nothing rotates out before you extract it.
Do not attempt adb root or a direct adb pull of
/data/misc/bluetooth/logs/btsnoop_hci.log — on a normal production/non-rooted build,
including stock Pixel firmware and GrapheneOS, this will fail with a permission error,
since adbd cannot run as root on such a build. This is expected and not specific to
GrapheneOS; it has nothing to do with whether the device is capable of being rooted,
only with the fact that neither phone is rooted here.
The supported, working method on both phones:
-
Run on your computer:
adb bugreport buds_captureThis produces
buds_capture.zip(or a similarly named file/folder depending on platform-tools version) in your current directory. No root required. A GrapheneOS community forum report documentsadb bugreportproducing a report containing the BTSnoop log on a stock, non-rooted GrapheneOS build on Android 16 — this is a community observation, not an official GrapheneOS platform guarantee, but it's consistent withadb bugreportbeing the standard, documented AOSP mechanism (below) rather than anything GrapheneOS-specific. -
Unzip the result. Depending on your unzip tool, this may spill its contents directly into your current directory (an
FS/folder, various.txt/.zipfiles, etc.) rather than into a named subfolder — that's normal, not a sign anything went wrong. -
Check the raw log path first — simplest, no tooling needed if it's there: look for either of these inside the unzipped
FS/tree:FS/data/log/bt/btsnoop_hci.logFS/data/misc/bluetooth/logs/btsnoop_hci.log
If one of these exists, you're done — it's already a raw BTSnoop file, open it directly in Wireshark (§5). No need to run
btsnooz.pyat all. The exact path varies by Android version, which is why step 4 below exists as a version-resilient alternative — but check here first, since it's usually simpler when present. -
If neither raw path exists — extract via
btsnooz.py, per current AOSP documentation:# Get btsnooz.py from the AOSP source tree if you don't already have it: # https://cs.android.com/android/platform/superproject/+/android-latest-release:packages/modules/Bluetooth/system/tools/scripts/btsnooz.py # Find the actual bugreport text file first — do NOT assume it's named after # whatever you passed to `adb bugreport`. That name only applies to the .zip; # the .txt inside always keeps Android's own generated name: ls bugreport-*.txt # e.g. bugreport-lynx-CP2A.260705.006-2026-08-09-08-52-28.txt btsnooz.py bugreport-<device>-<build>-<timestamp>.txt > buds_capture_btsnoop.logThis doesn't depend on guessing an internal zip path that has already moved between Android releases (see step 3 above) —
btsnooz.pyextracts the BTSnoop data directly from the bugreport's text dump, which is a more stable interface across versions, at the cost of an extra tool to set up. If neither the raw path (step 3) nor abugreport-*.txtfile is found at all, search the extracted contents by filename:find . -iname "*btsnoop*" -
Copy/rename the resulting log somewhere memorable, e.g.
captures/2026-08-02_pixel7a_anc-toggle.log, so repeated sessions don't overwrite each other. -
Once you've confirmed extraction worked, you can disable the HCI snoop toggle again (§2 step 3) to avoid unnecessary background logging and disk usage between sessions.
PROPOSAL — pending maintainer approval: step 3 (raw file) and step 4 (
btsnooz.pyfallback) are not interchangeable in practice — always check step 3 first and prefer it whenever the raw file is present, and always record which path a given capture actually used in that capture's ownCAP-NNN-FINDINGS.md(perPROJECT_RULES.mdrule 11's reproducibility requirement) — a capture's usability for anything beyond short control-frame sequencing may depend on this choice, not just on what happened during the session itself. SeeDESKRESEARCH_FINDINGS.md's 2026-08-28 entry for the full 5-session comparison and evidence (still 🟡 HYPOTHESIS, not a controlled test).
Groups A–Q below are the main run-through capture scenarios — efficient bundles of related
actions to run in one session — not the project's official test record; Z, R, S, T, U, V, W, X,
and Y are additional special-purpose groups added later (pipeline validation, occasional/targeted
follow-ups), each documented in its own subsection below. Each numbered item is
annotated with its permanent Test-ID (e.g. ANC-001) from
TESTPLAN_BLUETOOTH_HCI_SNOOP.md, which is where the actual catalog of investigated
behaviors, their existence-confidence, and their evidence status live. A Group can bundle
Test-IDs from more than one feature area for capture efficiency (e.g. Group C bundles
CONV-001 and MULTI-001, which are unrelated features) — the Group is about how to
run the session, the Test-ID is what's actually being investigated. See
TESTPLAN_BLUETOOTH_HCI_SNOOP.md §0.1 for the full reasoning.
The single most important rule: one user-level action per capture window, with a pause before and after. A capture full of overlapping actions is very hard to attribute correctly in Wireshark; a capture of ten cleanly isolated actions is straightforward. Note that "one action" means one thing you deliberately triggered — not one frame. Some actions (pairing/bonding above all, see Group A below) legitimately produce a burst of related frames as their own multi-step protocol exchange runs to completion; that whole burst still counts as the result of a single isolated action, not as several actions to capture separately.
Recommended rhythm per action: wait ~5s → note the exact time → perform the action → wait ~5–10s → move to the next action.
This timing is a practical heuristic, not a guarantee that every response arrives within it. Since this guide deliberately avoids live capture (§7 FAQ — the live-capture port usually isn't available on modern Android, so you only see the traffic after extraction), there's no way to watch responses arrive in real time and wait until they settle; a fixed interval is the only thing that's actually practical to follow. The known failure mode: an occasional slow/delayed response can land after you've already moved on, and get misattributed to whatever the next action was. Mitigation is in the analysis phase, not the capture phase — see §5 step 3.
Source: the action list below is the validated inventory from TESTPLAN_BLUETOOTH_HCI_SNOOP.md
sections 1 (User Actions from the Pixel Buds App) and 3 (Automatic Actions Initiated by
the App) — checked against your own app screenshots and official Google support
documentation. It supersedes the older, shorter action list this section used to contain.
This is a comprehensive list (~40 actions). You do not need to do it all in one
sitting — per the FAQ in §7 ("Do I need to do a full capture session every time?"), you
can pull a fresh bugreport for any subset at any time. Splitting this into 3–4 shorter
sessions by group (below) is often more manageable than one long one, and keeps each
adb bugreport capture focused (see the log rotation note in §6).
Run one continuous Bluetooth HCI snoop logging session per group — the
on-device logger you enabled in §2 keeps recording throughout, there's nothing
you need to re-invoke to "keep it going" — and collect a single adb bugreport
(§3) once, at the end of the group. Log each action with its own timestamp,
following the usual rhythm: wait ~5s → note the exact time → perform the
action → wait ~5–10s → move to the next action.
PROJECT_RULES.md §3): the original advice below was to prioritize Group K to confirm/refute
the RFCOMM framing hypothesis. That hypothesis question is now resolved for ANC specifically
(PROTOCOL.md §4.1 — the official Fast Pair Message Stream, Group 0x08, byte- and
timing-verified against CAP-001), which is what Group K's own frame would have helped confirm.
The new top priority is Group T (EQ command isolation, below) — EQ's command channel is the
one major control feature this project still needs to attribute, now that the earlier assumption
"EQ probably shares ANC's channel" no longer holds (PROTOCOL.md §2.3's 2026-08-14 addendum). The
same caution applies to Group T's result as applied here to Group K: a superficial byte-pattern
match on a single capture is a HYPOTHESIS, not a FACT — see Group T's own cross-command check.
Original tip (2026-08-08 or earlier, kept for the record): this document's own verification
methodology flags the "Play sound on Left earbud" action (group K below) as a specifically
valuable, low-risk target — its frame can be directly compared against the Fast Pair Message
Stream spec's own worked example (0x04 0x01 ... for a ring action) to confirm or refute the
framing hypothesis in PROTOCOL.md §2.1. If you only have time for a short session, prioritize
group K.
Caution when you get there: a superficial resemblance to the worked example (e.g. a
plausible-looking group/code byte pair) is not by itself confirmation — don't promote the
framing hypothesis to FACT off one frame that merely looks compatible. See the
cross-command check in Group K below.
Before spending your pairing baseline (Group A) or any real reverse-engineering capture, confirm the whole tooling chain actually works end to end, using the cheapest, most trivial, already-connected action you have — this catches a broken pipeline (logging didn't start, bugreport came back empty, extraction script misconfigured, wrong Wireshark filters) on a throwaway capture instead of on your pairing baseline or Find My Buds, both of which are more valuable and, in the pairing case, mildly disruptive to redo.
- With the Buds already connected (any existing pairing is fine — this step doesn't
need a fresh bond), work through §2 (enable HCI snoop, restart Bluetooth) and confirm
adb deviceslists the phone. - Perform one trivial, already-familiar action — e.g. a single ANC mode toggle. Note the timestamp.
- Pull a bugreport (§3), extract via
btsnooz.py, and open the result in Wireshark. - Confirm you can actually see, before doing any protocol interpretation: general HCI traffic, an RFCOMM stream, frames addressed to/from the Buds' Bluetooth address, and that your noted timestamp lines up with visible activity in the log.
- If any of the above is missing, fix the tooling (§2, §3, §6 Notes & Gotchas) and
repeat this step — don't move on to Group A until this passes. Log this as your first
entry in the Capture Index (§9); mark it
discardedafterward if you'd rather not keep a throwaway capture around, but it still counts as a real, worthwhile session.
- Pairing / bonding baseline [
PAIR-001] — capture this as its own isolated session, ideally before the command groups below (B–P). While connecting, this session also incidentally covers [BATT-004] (battery via RFCOMM on connect) — no separate action needed for that, just note it happened here if relevant. If the Buds are already paired, "forget" the device on the phone side first (Bluetooth settings → the paired device → Forget), then re-pair through Bluetooth settings — this captures a real bonding handshake instead of skipping it because "it's already paired." This is a lightweight, safely repeatable action: it does not touch the Buds' own memory or the Find My Device link, unlike the full factory reset in Group P #16 below — do not confuse the two. Expect a burst, not a single frame: pairing is one user action (tapping the device in the picker) that triggers an automatic multi-step exchange (inquiry, authentication, link-key exchange, SDP, profile connect) — this entire burst is the result of that one action, not several actions to isolate individually; there is nothing to click separately for each sub-step. Wait for the connection to settle before moving on to Group B.
- ANC → Off [
ANC-001]. Wait. Note time. - ANC → Noise Cancellation (active) [
ANC-002]. Wait. Note time. - ANC → Adaptive [
ANC-003] (if your firmware exposes it — confirmed present inrelease_5.203perPROTOCOL.md§4.1). Wait. Note time. - ANC → Transparency [
ANC-004]. Wait. Note time.
Repeat recommendation (2026-08-14), lower priority than Group T: despite ANC's opcode reaching 🟢 FACT, one sub-question stays open —
CAP-001's first two taps (Transparency, Off) have no corresponding0x12"Set ANC state" frame anywhere in that log (PROTOCOL.md§4.1,CAP-001-FINDINGS.md§5), possibly because the ANC row was still visibly greyed out (UI-state realization, not a genuine command) rather than a missed capture. A clean repeat of this Group with the same isolation requirements as before — but starting only once the ANC row is already fully active/enabled on screen, and doing a genuine single tap per window rather than the six-in-a-rowCAP-001did — would confirm whether every real tap produces a0x12frame, and re-confirm the bit-mapping (PROTOCOL.md§4.1's table) on a clean, single-tap capture before it's relied on for implementation.
- Toggle 'Conversation Detection' on/off [
CONV-001]. Wait. Note time. - Toggle 'Multipoint' on/off [
MULTI-001]. Wait. Note time.
Note, added 2026-08-30 (audit finding): neither Group D nor Group E has its own row in §9's Capture Index —
EQP-001/003–008andEQS-001–003/005's actual wire capture happened under Group T (CAP-005/CAP-015), which superseded these as isolated, one-action-at-a-time sessions rather than the bundled run-through described below. Treat "D"/"E" inTESTPLAN_BLUETOOTH_HCI_SNOOP.md's Capture-scenario column as historical/superseded by "T," not as a still-open, separately-runnable scenario.
- Select EQ preset: Standard [
EQP-001]. Wait. Note time. - Select EQ preset: Bass Boost [
EQP-002]. Wait. Note time. - Select EQ preset: Bass Reduction [
EQP-003]. Wait. Note time. - Select EQ preset: Balanced [
EQP-004]. Wait. Note time. - Select EQ preset: Vocal Boost [
EQP-005]. Wait. Note time. - Select EQ preset: Clarity [
EQP-006]. Wait. Note time. - Select EQ preset: Last saved [
EQP-007]. Wait. Note time. - Save current EQ as a new preset ('Save') [
EQP-008] — a distinct write action from preset selection. Wait. Note time.
Change one band at a time by a clearly visible amount — not all bands in one gesture.
16. Adjust EQ slider: High treble [EQS-001]. Wait. Note time.
17. Adjust EQ slider: Treble [EQS-002]. Wait. Note time.
18. Adjust EQ slider: Mid [EQS-003]. Wait. Note time.
19. Adjust EQ slider: Bass [EQS-004]. Wait. Note time.
20. Adjust EQ slider: Low bass [EQS-005]. Wait. Note time.
- Toggle 'Touch controls' fully on/off [
TOUCH-001]. Wait. Note time. - Toggle 'Head gestures' fully on/off [
HEAD-001]. Wait. Note time.
- Set 'Press and hold' Left → Toggle ANC [
HOLD-001]. Wait. Note time. - Set 'Press and hold' Left → Digital assistant [
HOLD-002]. Wait. Note time. - Set 'Press and hold' Right → Toggle ANC [
HOLD-003]. Wait. Note time. - Set 'Press and hold' Right → Digital assistant [
HOLD-004]. Wait. Note time. - Check/uncheck one ANC mode in the press-and-hold rotation list [
HOLD-005] (e.g. remove 'Off' from the cycle). Wait. Note time.
- Toggle 'Mono audio' on/off [
AUDIO-001]. Wait. Note time. - Toggle 'Volume EQ' on/off [
AUDIO-002]. Wait. Note time. - Shift the 'Volume balance' slider [
AUDIO-003]. Wait. Note time. (PerTESTPLAN_BLUETOOTH_HCI_SNOOP.md§1 this is stored locally on the earbuds themselves — a good candidate for a confirmable persistent write.)
- Tap the 'Firmware up to date' check (manual) [
FW-001]. Wait. Note time. - Open 'More settings' to view firmware version per component [
FW-002] — may trigger a status query. Wait. Note time. - View serial numbers per component (same screen) [
FW-003]. Wait. Note time. - View connection status ("Earbud status: Connected") [
FW-004]. Wait. Note time.
- Toggle 'In-ear detection' on/off [
INEAR-001]. Wait. Note time. - Toggle case sound 'Earbuds replaced' on/off [
CASE-001]. Wait. Note time. - Toggle case sound 'Other notifications' on/off [
CASE-002]. Wait. Note time.
- Play sound on Left earbud [
FIND-001]. Wait. Note time. - Play sound on Right earbud [
FIND-002]. Wait. Note time. - Play sound on Case [
FIND-003]. Wait. Note time. - Play sound on both earbuds simultaneously [
FIND-004]. Wait. Note time.
Before treating the framing hypothesis as confirmed from #38 alone: cross-check
against 2–3 semantically different commands — e.g. an ANC mode change (Group B) and one
EQ preset or slider write (Group D/E). One matching frame is a promising HYPOTHESIS, not
a FACT (PROJECT_RULES.md §1: FACT requires repeated confirmation, not a single
observation). For each of these frames, check specifically whether the same structural
elements line up across all of them: magic/group byte(s), length-field semantics,
channel/message-ID position, where the protobuf payload boundary falls, whether a
checksum is present, and whether a Message Group/Code reading (§2 Hypothesis A) explains
the leading bytes as consistently as a magic-byte reading (§2 Hypothesis B) would. If all
sampled commands share the same structure, that's what actually raises confidence toward
FACT — not the Left-earbud frame resembling the spec's worked example on its own.
These aren't taps — they're deliberate waiting periods to catch background/automatic app
traffic per TESTPLAN_BLUETOOTH_HCI_SNOOP.md §3. Log explicit boundaries for each window, not just a
single timestamp — otherwise settling traffic from whatever you did right before the
window starts is hard to distinguish from genuinely spontaneous traffic during it. For
each item below, note: observation start (when you stopped touching anything),
any event of interest during the window, observation end, the Bluetooth
connection state (connected/reconnecting/idle), and the app's foreground/background
state.
42. Idle wait with the app open [BATT-001], ~60s right after connecting, without touching
anything — intended to catch the "battery status notification on every reconnect"
behavior. Note the start time, and leave a clean ~10s gap after the preceding
connect action before this window starts, so reconnect-settling traffic doesn't
bleed into what you're trying to observe as spontaneous.
43. Force-close and reopen the app [OBS-001] — intended to catch any status query the app sends
on launch. Note the exact time of reopening as the window start, and note when you
consider the window over (e.g. ~30–60s after reopening, or once traffic visibly
settles).
After finishing a group, pull the bugreport once (§3) — you don't need a separate bugreport per action, just clean timestamps to slice the single log into segments afterward.
Correction, 2026-08-14 (does not invalidate running this Group, but changes what to expect): removing the bond does not reliably clear Android's cached GATT service/characteristic database.
CAP-003-FINDINGS.md§1 found the cache survived bond removal (zero liveRead By Group Typediscovery traffic), andCAP-004-FINDINGS.md§6 independently reconfirmed the same negative result — two for two attempts using this method. Group W below is the only method that has an untried, stronger chance of working (pm clear com.android.bluetooth, or discovery from a phone that has never connected to this device before) — see alsoTESTPLAN_BLUETOOTH_HCI_SNOOP.md'sGATT-001row. Group R is kept in this guide because it is still useful for its bonus data (a fresh classic pairing exchange, step 4 below) even though it is no longer expected to force live GATT discovery on its own; if your goal is specifically the GATT handle→UUID mapping, run Group W instead, not this Group.
Android caches the GATT service/characteristic database per bonded device and does not
rediscover it on a normal reconnect — every other group in this guide runs against an
already-discovered device, so none of them exercise this. GATT-001
in TESTPLAN_BLUETOOTH_HCI_SNOOP.md needs Group W, not this Group, for its Pixel-7a-specific
resolution (see the correction above).
Why not just reconnect normally: there is no non-hacky way to force a GATT refresh from
outside an app — the only programmatic option (BluetoothGatt.refresh()) is a hidden @hide
API requiring reflection, which AGENTS.md §3 bans for this project's own code. Removing the
bond was the first thing tried to force it, but per the correction above, it is not reliable
for that purpose — it remains useful only as described above.
- Remove the bond via system Bluetooth settings — Settings → Connected devices → Pixel
Buds Pro 2 → Forget. Use the system settings, not the Pixel Buds app's own "Forget"
button —
CAP-001'sCAP-001-FINDINGS.md§6 found the app-level Forget did not fully clear a BLE-level association, so it isn't reliable for this purpose. Confirm the device no longer appears in the paired-devices list. - Work through §2 (enable HCI snoop, restart Bluetooth/reboot) as usual.
- Reconnect using a generic BLE tool (e.g. nRF Connect), not the official Pixel Buds app — install it on the Pixel 7a if not already present. This is a deliberate exception to this guide's usual preference for the official app: here the goal is a human-readable view of the discovered GATT structure on screen, not attributing a specific proprietary command, so a generic scanner is more useful, not less rigorous. The HCI snoop log captures everything at the system level regardless of which app initiates the connection. Do not expect this to actually trigger live discovery (see the correction above) — treat any live discovery traffic that does appear as a bonus, not the expected outcome.
- Isolate the whole connect-and-discover sequence as one action window: note the exact
time you tap Connect in the BLE tool, and the time the service/characteristic list
finishes populating on screen (nRF Connect's UI may still show a service list here even
without live discovery traffic — it can be serving its own cached view; do not assume an
on-screen list means fresh wire traffic occurred, check the log). Expect a full classic SSP
pairing exchange to also appear in this capture (removing the bond clears both classic and
BLE state for a dual-mode device) — this is this Group's actual, reliable bonus data
point, per the correction above: a
PAIR-001capture (bond removal via system settings only — no factory reset happens in this Group, so this can never be genuinePAIR-002data), not aGATT-001one. - If the tool supports it, manually read or subscribe to specific characteristics of
interest once they're identified on screen (e.g. anything near handle
0x0f2aor the0x0c0Xcluster flagged inCAP-002'sCAP-002-FINDINGS.md§4/§7) — each such action is its own isolated event, noted with its own timestamp, the same way a UI tap is treated elsewhere in this guide. - Extract and analyze as usual (§3, §5). In Wireshark, filter specifically for the ATT
opcodes that perform GATT discovery:
btatt.opcode == 0x10/0x11(Read By Group Type Request/Response — primary service discovery) andbtatt.opcode == 0x08/0x09(Read By Type Request/Response — characteristic discovery). Their responses contain the handle-to-UUID mapping this Group exists to capture.
Group S — Google Play Services disabled, no Pixel Buds app (occasional, not part of the normal run-through)
Purpose: isolate whether the Fast Pair Message Stream traffic identified in CAP-002
(CAP-002-FINDINGS.md §3 — the [Group][Code][Length][Value]-framed channel carrying, among other
things, the "Revision 6" string) is Buds-initiated (would still appear identically) or
driven by Google Play Services' phone-side Fast Pair/Nearby logic (would disappear or
change). This is directly relevant to this project's Zero-GMS goal (AGENTS.md §1,
PROJECT.md): if the Buds only send this data because GMS asks for it in a specific way,
this project's own app will need to replicate that GMS-side behavior, not just listen
passively.
Setup (validated manually before capturing, per the maintainer's own testing): with the
Pixel Buds app uninstalled and Google Play Services disabled (Settings → Apps → see all
apps → Google Play Services → Disable; adb shell pm disable-user --user 0 com.google.android.gms
is the scriptable equivalent), pairing the Buds via system Bluetooth settings still succeeds,
but the Fast Pair "Connect" half-sheet (the purple dialog with the Buds product image,
normally shown when GMS is enabled) does not appear — confirming the half-sheet itself is
GMS-driven UI, not app- or Buds-driven. Whether the underlying RFCOMM Message Stream traffic
is also GMS-driven, or independent of it, is what this capture is for — it is not
assumed by the setup validation above.
- Confirm the Pixel Buds app is uninstalled and Google Play Services is disabled, per the setup note above.
- Work through §2 (enable HCI snoop, restart Bluetooth/reboot) as usual.
- Pair via system Bluetooth settings [
GFPS-001] — there is no app and no Fast Pair half-sheet to use here, so this is the only pairing path available. Note whether the device was already unpaired (e.g. left over from a prior Group R session) or whether this capture also includes a fresh bonding handshake — record this explicitly in this session'sCAP-004-EVENT-NOTES.md, since it changes what else can be read from the same capture (see Group R step 4 for the equivalent bonus-PAIR-001note — Group S is also bond-removal-only, never a factory reset, so this isPAIR-001data too, notPAIR-002). - Isolate the whole pair-and-settle sequence as one action window: note the exact connect-tap time and when the connection visibly settles.
- Extract and analyze as usual (§3, §5). Specifically check whether a channel/DLCI carrying
the same
[Group][Code][Length][Value]framing asCAP-002§3 appears at all, and if so, whether the same fields (e.g. Code0x09's value) match. Do not assume an outcome before analyzing — either result (present or absent) is a real, useful finding for the open question above, not a "pass" or "fail" of this Group.
Group T — EQ command isolation (occasional, not part of the normal run-through; current top priority, added 2026-08-14)
Why this replaces the earlier ANC-first priority tip: ANC's command channel is now confirmed
(PROTOCOL.md §4.1 — the official Fast Pair Message Stream, Group 0x08, DLCI 0x04), which also
retires the earlier assumption that EQ "probably shares ANC's channel." EQ's command channel is
therefore still completely open, and is now the single highest-priority capture target for this
project's original implementation goal (see the corrected note at the end of §4.1's intro, below
Group S).
- Change EQ preset: Bass Boost [
EQP-002], as a single isolated action — same rhythm as Group D (≥10s silence before, ≥10s after) — but run alone this time, not bundled with the other seven preset taps back-to-back the way Group D does it, so the capture has only one candidate command frame to attribute. - After the window settles, run a second, independent, isolated window with a structurally
different EQ action — Adjust EQ slider: Bass [
EQS-004] — before drawing any conclusion, matching Group K's own "don't promote off one matching frame" discipline.
Analysis instructions for this Group specifically:
- First check whether any official Fast Pair extension page not yet checked (beyond Hearable
Controls, Device Action, SASS, Device Information — see
PROTOCOL.md§2.3/§4.1) documents an EQ-shaped message group — the same kind of check that resolved ANC. - If no official page covers it, inspect every open channel's traffic in a tight window
around the tap: DLCI 0x04 (Message Stream, watch for any Group not already accounted for),
DLCI 0x02 (
libmaestro's confirmed Pigweed-HDLC framing,PROTOCOL.md§2.2a — decode the Address/Control fields of every "Sent" sub-frame near the tap and check whether one appears specifically at that moment, distinct from its otherwise-undecoded baseline traffic), and DLCI 0x08 (the private[Group][Code][Length][Value]envelope,CAP-004-FINDINGS.md§5a). - Apply Group K's cross-command check: a single matching frame from step 1's two actions is a HYPOTHESIS, not a FACT — the two actions exist specifically so there's a second, structurally different sample to check against.
Purpose: CAP-004-FINDINGS.md §5a's Task 5 found Code 0x12 alternates its value on an
irregular interval — 🟡 HYPOTHESIS of a free-running liveness/sequence-parity bit, not yet tested
against a real physical event. This Group also closes INEAR-004's existing gap (no capture
scenario exists yet for "bud removed from ear, not placed back in case" — flagged in
TESTPLAN_BLUETOOTH_HCI_SNOOP.md §9).
- Remove one worn earbud and hold it in hand (not placing it back in the case) [
INEAR-004], with the connection otherwise idle. Note the exact time. Keep logging Code0x12occurrences on DLCI 0x08 for at least 60s before and 60s after this moment. - Close the case lid while the connection is still active (buds elsewhere, e.g. still worn or
in hand — not the normal "buds back in case"
CASE-006sequence) [OBS-003]. Note the exact time. - A deliberate, multi-minute idle wait (≥3 minutes, connection active, nothing touched)
[
OBS-003]. Note the start and end time.
Analysis: for each bracketed moment, check whether the Code 0x12 field1 2↔3 alternation
breaks, skips, or pauses at that moment (supports event-driven) or continues unperturbed on its
own irregular cadence (supports a free-running counter) — per CAP-004-FINDINGS.md §5a's own
framing of this open question.
Purpose: CAP-002-FINDINGS.md §5 found zero AT+ traffic anywhere outside CAP-001's own
pairing-time handshake across a full 8+ hour log, and CAP-001-FINDINGS.md §6 Task 6 ruled out
any SCO/eSCO HCI event in all four captures to date — both findings converge on the same missing
scenario: none of the four captures so far ever contains an actual phone call, the one trigger
that would exercise HFP's Service Level Connection setup and channel 5/DLCI 0x0a's audio path.
- Place or receive an actual phone call while connected to the Buds [
CALL-001]. Note the exact start and end time of the call. - Optionally, during the call, trigger a deliberate audio-routing action (e.g. switch the audio output device) as a bonus data point — note its time separately.
Analysis: check whether a full HFP AT-command SLC handshake reappears (matching CAP-001's
shape) and whether channel 5/DLCI 0x0a carries any payload this time, or whether an HCI-level
Setup Synchronous Connection (0x0428) / Enhanced Setup Synchronous Connection (0x043D) /
Synchronous Connection Complete (0x2C) event appears at all (none has, in any capture to date
— CAP-001-FINDINGS.md §6 Task 6).
✅ RESOLVED 2026-09-01 (CAP-034, maintainer sign-off obtained per AGENTS.md §6). The
0x0c0X/0x0f2X handle↔UUID mapping question this Group exists for is now closed — CAP-034
combined Option (a) (pm clear com.android.bluetooth) with Option (b)'s underlying goal (a phone
never before connected to this Buds unit, the Pixel 9a) for the first time, and the resulting
unlimited-snaplen discovery burst resolved the full 15-primary-service GATT profile. See the
CAP-034 row in the Capture Index above and CAP-034-FINDINGS.md/PROTOCOL.md §6 for the full
result. This Group's own text below is left as historical context for how the resolution was
reached — a future session normally has no remaining reason to repeat this Group for GATT-001
specifically, unless a new GATT question arises (e.g. the still-open name of FE2C1238… or
"Unknown Service"'s purpose, CAP-034-FINDINGS.md §8).
Purpose: CAP-002, CAP-003, and CAP-004 all failed to trigger a live Read By Group Type
GATT discovery against the Buds — Android's cached GATT database survived bond removal in every
attempt so far (CAP-003-FINDINGS.md §1, re-confirmed in CAP-004-FINDINGS.md §6). Group R's
"remove the bond" method is not a reliable trigger, contrary to what this document and
TESTPLAN_BLUETOOTH_HCI_SNOOP.md's GATT-001 row previously stated (corrected here and there —
see the 2026-08-14 note on GATT-001 below and in TESTPLAN_BLUETOOTH_HCI_SNOOP.md §5).
- Option (a) — clear the Bluetooth system app's cache directly:
adb shell pm clear com.android.bluetooth[GATT-001]. Risk, note before running: this clears all of the phone's Bluetooth pairings and state, not just the Buds' — expect to have to re-pair every other Bluetooth device on that phone afterward. Confirm this is acceptable before running it. Also clear nRF Connect's own app cache/data (adb shell pm clear no.nordicsemi.android.mcp— check the actual package name installed; Settings → Apps → nRF Connect → Storage → Clear storage/cache works too) if using it to reconnect —CAP-004-FINDINGS.md§6 found nRF Connect's on-screen service list may be served from the app's own cached data rather than fresh wire discovery, which would make the UI look like discovery succeeded even when the Android Bluetooth stack itself never emitted a liveRead By Group Typeexchange. Clearing onlycom.android.bluetoothwithout also clearing nRF Connect's own cache risks a false-positive "it worked" read from the UI that the HCI snoop log won't actually back up — always confirm against the log (§5), never the tool's UI alone. - Option (b) — discover from a phone that has never connected to this device before: run the
discovery from the Pixel 9a (GrapheneOS), which has not previously run any app or tool against
this specific Buds unit [
GATT-001]. If using nRF Connect (or any BLE scanner) on this phone too, use a fresh install or clear its cache first, same reasoning as option (a) — "never connected before" should mean the tool's cache is clean, not just the device's pairing state. - Isolate the connect-and-discover sequence as its own window, same as Group R step 4.
Purpose: CAP-001-FINDINGS.md §3 found AT+CIND?'s battchg=3 (≈60%) and AT+BIEV=2,100
(100%) disagreeing at the same moment — unresolved whether either indicator actually tracks a
real battery-level change over time.
- Start logging well before an expected, natural battery-level decline (e.g. at the start of a normal day of use), keeping the connection active/idle rather than disconnecting between checks.
- Periodically (e.g. every 15–30 minutes) note the wall-clock time — no action needed, both indicators are expected to update on their own per their respective triggers.
- End the session after a natural, visible battery-percentage drop has occurred on screen. Can be combined with Group V's session if a phone call happens to occur naturally within the same window — no need to force this, note it if it happens.
Analysis: extract every AT+CIND? (battchg) and AT+BIEV=2,... value across the session
with its timestamp, and compare both trends against the on-screen battery percentage over the
same window.
Note on Group S's repeatability (added 2026-08-14):
CAP-004-FINDINGS.md§8 item 4 flags that Group S's Cross-Transport-Key-Derivation bonding result (§2 there) might be an artifact of nRF Connect's early BLE connection rather than of GMS being disabled —CAP-004used nRF Connect first, deviating from this Group's own system-settings-only procedure (see the "⚠️ Procedure deviation" note inCAP-004-EVENT-NOTES.md). A repeat of Group S exactly as described above (system Bluetooth settings only, no BLE tool at any point) would isolate this confound. The coreGFPS-001result (§4a/§4b there) is not expected to change, only the §2 CTKD-vs-classic-SSP bonding-mechanism finding.Resolved 2026-08-26 (
CAP-012): the repeat was captured, exactly as described above (no BLE tool at any point, independently verified both on screen and on the wire —CAP-012-FINDINGS.md§2). Confirmed: classic SSP, not CTKD — this isolates the confound cleanly, as predicted. As also predicted, theGFPS-001channel-topology result (DLCI 0x04 never opens) reproducesCAP-004's; however,CAP-012's own log turned out to be severely ACL-truncated (btsnooz-fallback extraction), so the payload-content half ofGFPS-001— whichCAP-004's untruncated log could answer — is inconclusive here, not a second confirming data point (CAP-012-FINDINGS.md§1/§4). A further repeat with a working, non-truncated snoop log would still be worthwhile for that reason alone.
Note on Group A's repeatability, optional (added 2026-08-14).
CAP-001-FINDINGS.md§6 found a BLE link and a still-valid link key both existing before the on-screen "Forget" tap and before the case was reopened — unresolved whether "Forget" fully clears prior association state. A repeat of Group A that starts HCI snoop logging before any association with the device exists at all (e.g. immediately after a phone restart, before ever opening the case or any Buds app) would isolate this.Update (2026-08-26):
CAP-013attempted this repeat — did not achieve it, primary question still open. Logging did not actually start before the clearing action (which itself was a phone-wide "Reset Bluetooth & Wi-Fi," not a single-device "Forget") — the log's first frame lands 2m21s after that action, and after the case-open/pair-button/device-list-tap sequence too (CAP-013-FINDINGS.md§0). The primary question above remains 🔴 OPEN.CAP-013did resolve the secondaryPAIR-004question (fresh SSP, no key reuse, for whatever bonding state was active when its own logging window began) — seeTESTPLAN_BLUETOOTH_HCI_SNOOP.md'sPAIR-004row. PROPOSAL — pending maintainer approval: a further repeat is still needed, this time with logging verifiably started before the clearing action itself (e.g. enable HCI snoop logging immediately after a phone restart, before touching Bluetooth settings at all) — proposed asCAP-031(next free ID perid_registry.csv, not yet assigned/registered).Update (2026-08-27),
CAP-031, PROPOSAL — pending maintainer approval: second consecutive attempt, still did not achieve it — primary question still open. This session used a genuine narrow, per-device "Forget" (screenshot-confirmed, unlikeCAP-013's broader reset) and added a live snoop-log file-size-polling check during recording specifically to catchCAP-013's failure mode — but the log's first frame (06:06:37.16) still lands 66s after the on-screen Forget tap (06:05:31), and after the case-open/pair-button/first-scan-attempt sequence too (CAP-031-FINDINGS.md§0). The primary question remains 🔴 OPEN, untested a third time.CAP-031did reconfirm the secondaryPAIR-004question (fresh SSP, no key reuse — a sixth confirming instance) and, as bonus negative results, found that neither ofCAP-013's two other anomalies (DLCI 0x02's ~61s-delayed open, the unattributed second BLE link) reproduce this session (CAP-031-FINDINGS.md§5/§6) — both look like single-session artifacts, not recurring behavior. A fourth attempt is still needed for the primary question, this time verifying the snoop log's own content freshness (last-frame timestamp against a live wall clock) immediately before the Forget tap, not just the log file's size (CAP-031-FINDINGS.md§8's proposed root cause and method).Update (2026-08-27),
CAP-032, PROPOSAL — pending maintainer approval: fourth attempt succeeded. This session was extracted via the raw BTSnoop file path (§3 step 3) instead of thebtsnooz.pyfallback used for the three prior attempts — the resulting log is genuinely untruncated (frame.cap_len == frame.lenfor all 2,455 frames) and its first frame (18:29:45.72) lands ~58s before the on-screen Forget tap (18:30:42), and ~30s before the video itself starts. The primary question is now answered for this session: 🟢 no BLE link or valid classic link key existed for the Buds anywhere in the covered pre-Forget window (CAP-032-FINDINGS.md§0.3) — zero classic connection events, exactly one LE connection (to an unrelated random-address device, not the Buds), and aDelete Stored Link Keyissued at the Forget tap's own moment reportingNum_Keys_Deleted = 0. This does not reproduceCAP-001's original finding — it shows the opposite, a clean counter-example rather than a confirmation or a refutation of that session's own result;CAP-001's session-specific puzzle (why that session had residual state) remains independently open.PAIR-004's secondary question is reconfirmed a seventh time (fresh SSP, no key reuse). This also supports (one data point, not yet independently isolated) the hypothesis that thebtsnooz-fallback extraction path itself — not the individual session — was responsible forCAP-012/CAP-013/CAP-031's truncation; see the proposed §3 addendum below.
Group Y — BLE-only connection isolation for the 0x0044 notification burst (occasional, added 2026-08-20)
Purpose: CAP-016-FINDINGS.md §11 found a 73-frame Handle Value Notification burst on BLE
ATT handle 0x0044 (connection handle 0x0002, 23 of the 73 frames containing a recurring
0xfea9 byte-pair marker), confined to a ~29s window right after the BLE link forms and before
the classic link exists. Not yet isolated from a physical trigger: every capture that shows this
burst to date also has a bud removal/insertion happening nearby in the same session, so it's
unconfirmed whether the burst is caused by the BLE link forming at all, or by the bud/case action
that happened to coincide with it in those captures.
- Enable Bluetooth and let the phone's BLE link to the already-paired Buds form on its own
[
GATT-002] — do not touch the buds or the case at any point before, during, or for at least 60s after the link forms. The buds/case stay exactly wherever they already are (worn, in hand, or already sitting open/closed) — this is the opposite of Group M's procedure, which deliberately triggers bud/case events; here the goal is a clean BLE-connect with zero physical events nearby to correlate against. Note the exact time Bluetooth was (re-)enabled or the BLE link began forming. - Keep the observation window open and logging for at least 60s past that point, per the usual observation-window discipline (§4.1 Group L's boundary-logging convention: note observation start, any event of interest, and observation end explicitly).
Analysis: filter the resulting log for btatt.opcode==0x1b and btatt.handle==0x0044. If the
burst still appears despite no bud/case action anywhere in or near the window, that's a clean
positive result narrowing 0x0044's trigger to "BLE link establishment alone" (PROTOCOL.md §6).
If it does not appear, that's an equally useful negative result, pointing back toward a bud/case
physical action as the real trigger after all — either outcome closes this open question, per the
three-way outcome guidance already used for Group Q's items 19–20.
Group AA — SDP UUID branch isolation for gbm.a()'s "default internal rfcomm socket" path (occasional, added 2026-08-30)
Purpose: REVERSE_ENGINEERING.md's gbm/fzd entries and DECISIONS.md ADR-018 found the
companion app's own decompiled code (gbm.java:35-43) picks between two internal RFCOMM sockets
depending on which of two SDP UUIDs is present in the discovered set: "pigweed" (25e97ff7-...,
confirmed = DLCI 0x02 in every capture so far) or "default" (3a046f6d-..., never observed on the
wire anywhere — a raw-byte scan of all 26 capture files this project has, in every format
(*btsnoop_hci.log, *btsnooz_hci.log, both nRF Connect logs), found zero occurrences in either
byte order; see REVERSE_ENGINEERING.md's gbm entry Open questions). This Group covers the two
hypotheses that are safely testable with a single Pixel Buds Pro 2 unit on its current firmware
(release_5.203):
SDP-001:gbm.a()'s discovered-UUID set may come from an SDP browse whose content depends on who triggers it — the OS's own default pairing flow, vs. the companion app's ownfetchUuidsWithSdp()call (fxm.java:110, which only fires when the HID UUID0x1124is absent). Every existing capture has the app already open, so this hasn't been isolated.SDP-002(opportunistic): a firmware update might be what changes which UUID gets advertised at all — the "pigweed"/"default" pair reads plausibly as a pre-/post-migration artifact. Only testable the next time an actual OTA update becomes available, same caveat asFWUPD-001/FWUPD-002.
Explicitly out of scope for this Group — not safely or practically testable, recorded here so
they aren't silently retried: deliberately downgrading firmware (unsupported by Google, real
bricking risk — see WORKSTATION_PREPARATIONS.md's Disaster Recovery section; never attempt this),
and testing against a different physical unit or hardware generation (the maintainer owns one
Pixel Buds Pro 2 — PROJECT_RULES.md §8's own-hardware scope). If SDP-001/SDP-002 both come
back negative, the leading remaining explanation is that the "default" branch is unreachable on any
currently-shipping release_5.203+ unit — a static-analysis question (checking for a
firmware-version gate elsewhere in the APK, APK_REVERSE_ENGINEERING_PROCEDURE.md §4), not a
capture question, and out of this Group's scope.
- [
SDP-001] Force-stop the Pixel Buds companion app first (Settings → Apps → Pixel Buds → Force stop), so it cannot react to the pairing at all. Start Bluetooth HCI snoop logging (§2). "Forget" the Buds via system Bluetooth settings only — the same safe, repeatable action already used forPAIR-001(Group A #1), notCASE-007's factory reset. Re-pair via system Bluetooth settings' "Pair new device" flow only. Note the exact time pairing completes. - Keep observing for at least 60s after bonding completes, without opening the companion app or touching the buds/case — this is the window where the pre-app-fetch UUID set (if one exists and differs from the baseline) would show up.
- Still
SDP-001, second half of the same session: now open the companion app normally and let it connect as usual — this reproduces every prior capture's baseline in the same log, for a direct in-session before/after comparison rather than relying on a separate session. - Pull the bugreport (§3) once, at the end.
- [
SDP-002], opportunistic, separate session whenever a firmware update becomes available: repeat the SDP-observation half of steps 1–2 (a fresh SDP browse doesn't require a fresh bond — simply reconnecting is enough if already bonded) immediately before installing the update, and again immediately after it completes and the Buds reconnect.
Analysis: pre-filter by address first, per §13's CLI-hygiene rule, then to btsdp:
tshark -r CAP-NNN-btsnoop_hci.log -Y "bluetooth.addr == 04:00:6e:cf:6e:07 and btsdp" -T fields -e frame.number -e frame.time_relative -e btsdp.data_element.value.uuid_128 -e btsdp.protocol.channel
(exact command already validated against CAP-001/CAP-002/CAP-032 in REVERSE_ENGINEERING.md's
gbm entry). Three-way outcome, matching Group Y's own guidance above:
- Positive: the pre-app-open window (step 2) shows the "default" UUID (
3a046f6d-..., either byte order) instead of or alongside "pigweed" — closes the open question; write it up per the usual FACT/HYPOTHESIS discipline (PROJECT_RULES.md§1) before touchingPROTOCOL.md. - Negative: still only "pigweed" (or no
btsdptraffic at all, e.g. if the OS doesn't run a full SDP browse without the app prompting it) — consistent with every capture to date; narrows the explanation towardSDP-002or a static-analysis-only dead-code question rather than a UI-timing artifact. SDP-002positive: a before/after firmware comparison shows the advertised UUID changing — directly explains the two-UUID code as a migration artifact.
Group AB — DLCI 0x08/0x0a/0x06/0x12 GMS-independence check on GrapheneOS (occasional, added 2026-09-02)
🟡 PARTIALLY ADVANCED 2026-09-02 (CAP-035, maintainer sign-off obtained per AGENTS.md §6).
DLCI 0x08's content reproduces byte-identical with Google Play Services present but
dumpsys-verified disabled (com.google.android.gms enabled=3) — a rigorously confirmed
strengthening of CAP-004-FINDINGS.md §4a's existing finding, not the fully conclusive
genuinely-GMS-absent result this Group was designed to reach (the Pixel 9a used still has GMS
installed, merely disabled, not absent as this Group's own Purpose below assumed it typically would
be). DLCI 0x0a opens but stays silent both times; DLCI 0x06/0x12 never open at all — first-time
clean negatives for both. See the CAP-035 row in the Capture Index and CAP-035-FINDINGS.md for
the full result. Still open: a repeat with GMS genuinely uninstalled, if full closure of the
OS-stack-vs-GMS question is wanted.
Purpose: CAP-033-FINDINGS.md §3 found the Buds' own SDP database names DLCI 0x08 as
"GSND CONTROL", DLCI 0x0a as "GSND AUDIO", DLCI 0x06 as "DEBUG APP", and DLCI 0x12 as
"BTIS" — but a full, mechanical companion-APK search (per ADR-017's boundary) found zero
references to any of these names/UUIDs anywhere in the app's own decompiled code, meaning the app
never registers or looks up these services itself. Two explanations remain open: (1) Android's own
Bluetooth stack connects to these services at the OS/vendor level, independent of any app, or (2)
Google Play Services' Nearby module does. CAP-004-FINDINGS.md §4a already found DLCI 0x08's
content (the google-pixel-buds-pro-v1 capability blob, Europe/Amsterdam, firmware string)
reappears unchanged with GMS disabled and the Pixel Buds app uninstalled — a single data point,
and a weaker test than this Group runs, since "disabled" is not the same as "absent." This Group
repeats that isolation on the Pixel 9a (GrapheneOS), where Google Play Services is not merely
disabled but typically not installed at all — the strongest available version of this test without
decompiling any OS-level binary (a materially different, not-yet-authorized scope — see this
session's own conversation record for the reasoning). Same underlying logic as Group S, applied to a
cleaner GMS-absent environment and to four channels instead of one.
Runs exclusively on the Pixel 9a (GrapheneOS) — no app-driven commands are possible on this device regardless (§4.2), which is a feature here, not a limitation: it keeps the phone-side software surface minimal (system Bluetooth stack only, no Pixel Buds app, no third-party GATT tool).
Test-ID: GSND-001 (new — added to TESTPLAN_BLUETOOTH_HCI_SNOOP.md and id_registry.csv
alongside this Group).
Setup — verify, don't assume, per PROJECT_RULES.md §1 (record the actual findings in
CAP-035-EVENT-NOTES.md, not just the intended state):
- Confirm no Google Play Services package is present at all (GrapheneOS's optional sandboxed Play
compatibility layer is a separate, explicitly-installed app — check it isn't present either):
If any GMS-related package is present, additionally confirm/record its disabled state (
adb shell pm list packages | grep -i "google\|gms\|play"adb shell dumpsys package <package> | grep -i enabled) — "absent" and "present but disabled" are different conditions and change how strong this session's result is. - Confirm the Pixel Buds Companion App is not installed:
adb shell pm list packages | grep -i pixelbuds(or check the app drawer). - Confirm nRF Connect (or any other BLE/GATT tool) is not used this session — system Bluetooth settings only, the same constraint as Group S.
- If the Buds are currently bonded to this phone (e.g. left over from
CAP-034), "Forget" them via Settings → Connected devices → the Buds → Forget — a narrow, per-device action, not the broader "Reset Wi-Fi, mobile & Bluetooth" system option (that wipes every other paired device and all Wi-Fi networks on the phone for no benefit this session actually needs — this Group isn't chasing a GATT-cache question, so Group W's heavier cache-busting isn't relevant here). A plain per-device Forget is enough to get a clean fresh-pairing handshake as a bonusPAIR-001data point; it is not strictly required for the DLCI-content question itself (CAP-010-FINDINGS.md§5 already showed DLCI 0x08's handshake reproduces on a reconnect to an already-bonded device too), but keeps this session's own artifacts simple to read. - Enable Bluetooth HCI snoop logging (§2) and reboot, same as every other session.
Procedure:
- Start video recording (wall-clock overlay) and confirm HCI snoop logging is active.
- Open the case, press the pairing button. [
PAIR-001] - On the phone: Settings → Connected devices → Pair new device → select the Buds from the list.
Confirm the system pairing dialog. Note the exact tap time. [
PAIR-001,GSND-001] - Once connected, do not open any app or tool — leave the connection idle for at least
90–120 seconds. This is deliberately longer than Group S's window: DLCI 0x06/0x0a's only prior
payload-bearing occurrence (
CAP-021-FINDINGS.md§4a) did not appear immediately on connect, so a short window risks a false negative for those two channels specifically. [GSND-001,BATT-003] - Disconnect and reconnect once, as an isolated pair of actions — the one condition under which
DLCI 0x0a has ever carried content before. [
PAIR-003,GSND-001] - Idle again for ~30–60s after the reconnect settles.
- Stop recording and logging. Extract via the raw btsnoop path first (§3), and verify snaplen
integrity immediately (
capinfos/frame.cap_len == frame.len) before any analysis, per every prior session's practice.
Analysis: filter on the Buds' address first (§13 CLI-hygiene), then check, per channel:
tshark -r CAP-035-btsnoop_hci.log -Y "bluetooth.addr == 04:00:6e:cf:6e:07 and btrfcomm.dlci in {6,8,10,18}" \
-T fields -e frame.number -e frame.time_relative -e btrfcomm.dlci -e _ws.col.Info
(DLCI values: 0x06=6, 0x08=8, 0x0a=10, 0x12=18 — decimal, since tshark fields compare numerically.)
For DLCI 0x08 specifically, if it opens, reproduce CAP-001/CAP-004's exact content check:
tshark -r CAP-035-btsnoop_hci.log -Y 'frame contains "google-pixel-buds-pro-v1"' -T fields -e frame.number -e frame.time
Three-way outcome, stated in advance so the result isn't read selectively (same discipline as Group S/Y/AA):
- DLCI 0x08's content appears, byte-identical to prior captures, with no GMS present at all —
strengthens
CAP-004's single data point into a 2nd, stronger confirmation that this channel is OS/vendor-Bluetooth-stack-level, not GMS/Nearby-dependent. Write up as aCAP-035-FINDINGS.mdproposal per the usual FACT/HYPOTHESIS discipline — this session alone does not authorize self-promoting anything to 🟢 FACT inPROTOCOL.md(AGENTS.md§6). - DLCI 0x08's content is absent or differs — contradicts
CAP-004, and becomes a new, actively interesting open question in its own right (not a "failed" session — an absence here is exactly as informative as a presence, same principle as Group S). - DLCI 0x0a/0x06/0x12 show any payload at all (previously unobserved outside
CAP-021's single DLCI 0x0a burst) — first data point on whether these depend on GMS presence; record whatever is found, don't force an interpretation beyond what the bytes show.
Group AC — Settings-state read-back on (re)connect and on settings-screen open (occasional, added 2026-09-04)
Purpose: ARCHITECTURE.md §3.1 (State Reconciliation) requires this project's own app to query
the hardware's actual current state on every (re)connection — ANC mode, battery, EQ,
touch-control config — before trusting or displaying any locally cached value. Whether the
official app does the same thing, and over which channel, has never been isolated. What the
evidence looks like today:
- DLCI 0x04 (Fast Pair Message Stream): a read direction is documented for ANC — Group
0x08Code0x11"Get ANC state", Seeker → Provider (PROTOCOL.md§4.1's own table, straight from the official Hearable Controls extension). But this project has never observed a0x11frame in any capture: only0x12(Set) and0x13(Notify) appear on the wire (CAP-001-FINDINGS.md§5). Documented-but-never-observed is not the same as absent — nobody has looked for it in a window where it would be expected to fire. - DLCI 0x02 (
libmaestro's Pigweed HDLC channel): no read direction is documented at all. Every entry inPROTOCOL.md§4.2 (EQ) and §4.5 (Conversation Detection, Multipoint, touch & press-and-hold, head gestures, in-ear detection, mono audio, Volume EQ, volume balance, case sounds) is evidenced exclusively by a write: a UI tap producing aSentframe, whose only observed response is anRcvd-direction echo of the same field/prefix shape — "no distinct ACK opcode observed," in §4.5.1's own wording, repeated verbatim across the §4.5 subsections. Not one of those entries rests on a frame that reads as "give me the current value."
So the question this Group exists to answer is genuinely open, and deliberately narrow: does the
official app ever issue a state query — as opposed to a state write — and if so, on what
trigger? The two candidate triggers this Group brackets are the two that matter for
ARCHITECTURE.md §3.1's design: connection establishment, and opening a settings screen.
Runs on the Pixel 7a with the official Pixel Buds companion app and Google Play Services enabled — the normal baseline, deliberately. This is the opposite setup from Groups S/AB, which strip GMS and the app away to isolate what the Buds do on their own. Here the official app's own behavior is the object of study, so it must be present, connected, and behaving normally.
Test-ID: OBS-004 (new — added to TESTPLAN_BLUETOOTH_HCI_SNOOP.md and id_registry.csv
alongside this Group). Deliberately distinct from OBS-001 (app launch after a force-close,
Group L) and FW-002 (the firmware/serial info screen, Group I) — see that row's Note for why
neither covers this.
A clean negative is a real result here, not a failed session. If the log shows no query traffic
anywhere in the three windows below, that is citable evidence that the official app does not
read settings state back before a user change — which is itself directly load-bearing for
ARCHITECTURE.md §3.1 (this project would then be doing something the official app doesn't, and
needs to justify how). This project already has precedent for treating a clean negative as real
evidence rather than an empty result: DESKRESEARCH_FINDINGS.md's 2026-08-28 cross-capture pass
records "the best available negative-result test for this question … and it comes back clean" as a
finding in its own right, with the same FACT/HYPOTHESIS labelling discipline as any positive one.
Same discipline applies here — record the absence explicitly, with the filters that were run and
the windows they covered, per PROJECT_RULES.md §1 rule 4a.
Procedure — three isolated observation windows. No setting is touched at any point in this
session. Not a slider, not a toggle, not an ANC mode, not a preset — the entire value of this
capture depends on every frame in it being app- or connection-initiated rather than user-initiated.
If a setting is touched by accident, note it explicitly in CAP-036-EVENT-NOTES.md and treat the
affected window as contaminated rather than quietly keeping it. Leave a clean few-second buffer
before each window, per §4's usual rhythm, so the previous window's settling traffic doesn't bleed
into the next one.
- Reconnect — not a fresh pair [
OBS-004, incidentalPAIR-003]. The Buds must already be bonded to this phone; do not "Forget"/re-pair (a bonding handshake would flood the window with pairing traffic and defeat the isolation). Trigger a reconnect the way a user normally would — e.g. take the buds out of the case, or toggle the connection from system Bluetooth settings. Note the exact time the connection is established on screen, then idle ~10–15s without navigating anywhere at all, app included. - Open the EQ screen [
OBS-004]. Note the exact time the EQ screen finishes rendering, then idle ~15–20s without touching any slider or preset. Do not scroll into a drag by accident — the sliders are the whole risk on this screen. - Navigate away (back out to the device details screen), leave a clean few-second buffer, then
open the touch-controls screen [
OBS-004]. Note the exact open time, then idle ~15–20s without touching any toggle.
Optional bonus, time permitting — same idle-only pattern, still purely observational, still nothing touched: repeat window 3's shape for the in-ear detection and multipoint settings screens. These are cheap extra data points on whether any per-screen query exists, and they cost nothing but session time; skip them freely if the session is running long, and note in the event notes whether they were run at all.
- Stop recording and logging. Extract via the raw btsnoop path first (§3 step 3), and verify
snaplen integrity immediately (
capinfos/frame.cap_len == frame.len) before any analysis — a truncated log would silently hide exactly the short frames this Group is looking for.
Analysis: pre-filter by the Buds' address first, per §13's CLI-hygiene rule, before layering on any protocol filter — a shared, non-restarted snoop log can contain unrelated devices' traffic. Then, per window:
tshark -r CAP-036-btsnoop_hci.log -Y "bluetooth.addr == 04:00:6e:cf:6e:07 and btrfcomm.dlci in {2,4,8}" \
-T fields -e frame.number -e frame.time -e frame.p2p_dir -e btrfcomm.dlci -e btrfcomm.len -e data.data
(DLCI values decimal: 0x02=2, 0x04=4, 0x08=8.) Specifically:
- DLCI 0x04: does a
08 11 …frame ("Get ANC state") appear anywhere — in any of the three windows, or at connection setup? This is the one read-direction opcode this project knows to exist by name, and has never seen. - DLCI 0x02: is there any
Sent-direction HDLC frame inside a window where nothing was touched? Split each RFCOMM payload on the0x7eflag byte before decoding (multiple complete HDLC sub-frames routinely pack into one RFCOMM I-frame —DESKRESEARCH_FINDINGS.md's 2026-08-28 pass documents this exact trap). Compare any such frame's shape against §4.5's knownfield5{field4{…}}write envelope: a write-shaped frame with no user action behind it means something different from an unrecognized, differently-shaped frame — say which was found. - DLCI 0x08: the private
[Group][Code][Length][Value]envelope already pushes unsolicited content (battery, capabilities, firmware string) on connect — expect traffic here in window 1 and do not mistake a known push for a query. Attribute it againstPROTOCOL.md§4.3 Option E before reading anything new into it.
Three-way outcome, stated in advance so the result isn't read selectively (same discipline as Groups S/Y/AA/AB):
- A query frame appears in one or more windows — identify which channel and which trigger,
record it in this session's own
CAP-NNN-FINDINGS.mdwith frame numbers and raw hex perPROJECT_RULES.md§1 rule 4a, and treat the opcode itself as 🟡 HYPOTHESIS until it replicates. Do not self-promote it to 🟢 FACT inPROTOCOL.md, and do not write aDECISIONS.mdADR for it — both require explicit maintainer sign-off (AGENTS.md§6). - No query traffic in any window, with clean, uncontaminated windows — a positive finding in its
own right (see the negative-result note above): the official app does not read settings state back
before a user change, at least not on these two triggers, on this firmware. Record it as such,
with the exact filters run and windows covered, and copy the resulting open item into
PROTOCOL.md§6 per §8's mandatory rule. - Inconclusive — a setting was touched by accident, a window was contaminated by an unrelated action, or the log came back truncated. This supports neither conclusion above; note it as 🔴 unconfirmed and re-run rather than reading a weak result either way.
Related open question this Group also informs (do not conflate it with the above):
PROTOCOL.md §6 Behavior carries an item from CAP-024-FINDINGS.md §4 — does opening the "Case
sounds" screen itself trigger a state-sync write on DLCI 0x02, or does a write only register on
an explicit tap? Windows 2 and 3 here are the same experimental shape (a settings screen opened with
nothing touched) applied to different screens, so whatever they show is directly relevant to that
question too — but a write on screen-open and a read on screen-open are different findings, and
this Group's own question is the read. Keep them labelled separately in the findings.
Group AD — "Get ANC state" reconnect-reliability + dock-state transition, purpose-built repeat (occasional, added 2026-09-05)
Purpose: DECISIONS.md ADR-022 promoted DLCI 0x04's "Get ANC state" (0x11) trigger-
reliability to 🟢 FACT and ADR-024 promoted the "Notify ANC state" Settable-toggles byte as a
dock-state indicator (0x00 = both earbuds docked, 0xe8 otherwise) — but both promotions rest
on retrospective analysis of captures never designed for either question (settings-toggle tests,
a pairing repeat, an ANC repeat), and no single session has yet captured the dock-state
transition itself within one continuous recording. This Group is the first purpose-built
session for both: 5 isolated reconnects in one sitting, deliberately alternating dock state.
Runs on the Pixel 7a with the official Pixel Buds companion app and Google Play Services enabled — the same baseline as Group AC, so nothing about the phone/app/GMS condition is a new variable here; only the repeat structure and deliberate dock-state alternation are new.
Test-ID: OBS-004 (existing, CAP-036's own Test-ID — this is a deeper replication of the
same underlying question, not a new one) plus incidental PAIR-003.
Procedure — 5 reconnects, alternating dock state, buffered ~3–5s apart: buds start docked;
repeat 1 reconnect from docked; repeat 2 reconnect from undocked (both buds removed from the case,
not worn, held/on the table); repeat 3 reconnect from docked again; repeat 4 reconnect from
undocked; repeat 5 reconnect from docked. Toggle Bluetooth off between every repeat for a clean
disconnect. See CAP-037-EVENT-NOTES.md for the full step-by-step procedure and preparation
checklist.
Analysis: for each of the 5 repeats, confirm 08 11 00 00/08 13 fires and record its
Settable-toggles byte against that repeat's known dock state. A miss on any repeat is a
counter-example to ADR-022; a Settable-toggles value that doesn't match dock state on any repeat
is a counter-example to ADR-024 — either must be reported plainly, not reconciled away. See
CAP-037-EVENT-NOTES.md's Decode/Analysis checklist for the full three-way outcome.
Group AE — Realistic physical reconnect trigger vs. a system-Bluetooth-toggle reconnect (occasional, added 2026-09-05)
Purpose: every reconnect this project has captured to date on the Pixel 7a with the official
app (CAP-036, CAP-037) was triggered by toggling Bluetooth in system settings while the Buds
sat in the open case, never removed, never worn — not the normal user flow of taking the Buds out
of the case and inserting them into the ears. That normal flow could exercise wire traffic a pure
OS-level toggle never touches: an in-ear-detection event, A2DP audio-profile setup, or a different
sequencing of the DLCI 0x02/0x04/0x08 connection burst. This Group tests that directly.
Runs on the Pixel 7a with the official Pixel Buds companion app and Google Play Services enabled, same baseline as Group AC/AD.
Test-ID: OBS-005 (new), incidental PAIR-003 and the INEAR family.
Procedure — two windows: (1) with Bluetooth already on, physically remove both earbuds from
the case and insert them into the ears, the normal user flow, and idle ~15–20s; (2) optional/time
permitting — with the Buds back in the case and Bluetooth on, close the lid, then reopen it
without touching the Bluetooth toggle, to see whether the lid alone triggers a differently-
shaped reconnect than an OS-level toggle. See CAP-038-EVENT-NOTES.md for the full procedure.
Analysis: confirm the DLCI 0x04 Get/Notify pair still fires (expected, per ADR-022's
channel-(re)establishment-scoped trigger) and its Settable-toggles byte reads "undocked"
(0xe8, per ADR-024, now genuinely worn rather than merely "not in the case"). The core question
is whether any traffic appears here that has no counterpart in CAP-036/CAP-037 — an
in-ear-detection push, A2DP setup, or a structurally different connection burst. See
CAP-038-EVENT-NOTES.md's Decode/Analysis checklist.
Group AF — Settable-toggles byte: Set-tap vs. reconnect-Get, fixed dock state (occasional, added 2026-09-05)
Purpose: CAP-036-FINDINGS.md §3 originally framed this open question as "does a Set-
triggered Notify differ from a Get-triggered Notify?" DECISIONS.md ADR-024 reframed it as
tracking dock state instead — but that reframing has never been tested by comparing a Set and a
Get within one session, at a fixed, known dock state. This Group closes that gap directly:
one ANC tap (a Set) and one forced reconnect (a Get), both with the Buds worn (undocked)
throughout, so dock state cannot explain any difference found.
Runs on the Pixel 7a with the official Pixel Buds companion app and Google Play Services enabled, same baseline. Buds stay in the ears (undocked) for the entire session, including during the forced reconnect.
Test-ID: OBS-006 (new), incidental ANC-family and PAIR-003.
Procedure: one ANC mode tap (any mode different from the current one), then a clean buffer,
then force a reconnect via the Bluetooth toggle without removing the Buds from the ears. See
CAP-039-EVENT-NOTES.md for the full procedure.
Analysis: decode both the Set-triggered and the Get-triggered Notify frames' Settable-toggles
byte and compare directly. Identical values (both 0xe8, matching the constant undocked state)
support ADR-024's dock-state reading as trigger-independent; a difference despite identical dock
state is a genuine counter-example to ADR-024 and must be flagged prominently, not reconciled
quietly. See CAP-039-EVENT-NOTES.md's Decode/Analysis checklist.
Group AG — DLCI 0x08's unmapped Get-shaped codes vs. a known-changing value (occasional, added 2026-09-05)
Purpose: CAP-036-FINDINGS.md §5 found DLCI 0x08's connect-time burst contains several
zero-length [Group][Code][00 00]-shaped Sent frames (05 0c, 04 02, 04 04, 04 11,
04 13, 04 15, 0e 04) structurally identical to DLCI 0x04's confirmed "Get" pattern — but none
of these Group/Code pairs is mapped to any known setting, and their Rcvd responses have never
been examined. Per AGENTS.md §13.6's zero-creativity rule, these can only be decoded via
correlation against a known, independently-verifiable value, not by guessing.
Runs on the Pixel 7a with the official Pixel Buds companion app and Google Play Services enabled, same baseline, with the app open on Device details so on-screen battery/case values are visible for cross-check.
Test-ID: PRIV-001 (new), incidental PAIR-003 and the BATT family.
Procedure: bracket a known-changing value across 4 reconnects — either an opportunistic
battery-discharge gap (if a convenient one exists), or the recommended default: vary which
component (Left/Right/both/neither) is docked at each of 4 reconnects, reading the resulting
on-screen Left/Right/Case values each time. See CAP-040-EVENT-NOTES.md for the full procedure
and both bracketing options.
Analysis: for each unmapped code, find its Sent Get frame and the immediately-following
Rcvd response(s) on the same Group, decode any numeric fields, and check whether any field
tracks the bracketed value across all 4 repeats. A field that stays constant is not a match; only
report a semantic reading for a field that visibly tracks the known value repeat after repeat. See
CAP-040-EVENT-NOTES.md's Decode/Analysis checklist for the full three-way outcome.
Group AH — DLCI 0x02's connect-time RPC burst vs. non-default settings state (occasional, added 2026-09-05)
Purpose: CAP-036-FINDINGS.md §4 found a dense, undecoded RPC-shaped burst on DLCI 0x02
immediately after it opens on reconnect, captured under a session where every setting was at its
default (EQ centered, touch controls on). This leaves open whether the burst's content reflects
current settings state at all — directly relevant to ARCHITECTURE.md §3.1 (State Reconciliation):
if libmaestro's own channel carries a settings-state read-back, that would be a second,
independent mechanism alongside DLCI 0x04's confirmed ANC read (ADR-021/ADR-022). This Group tests
it by deliberately setting non-default EQ and touch-controls values before reconnecting, then
comparing the resulting burst byte-for-byte against CAP-036's own default-settings burst.
Runs on the Pixel 7a with the official Pixel Buds companion app and Google Play Services
enabled, same baseline as CAP-036 — a clean single-variable comparison (settings state only).
Test-ID: OBS-007 (new), incidental PAIR-003.
Procedure: set EQ bands well off-center and touch controls off before recording starts (the
fixed starting condition for this session), confirm on screen, then reconnect once and idle ~15s.
See CAP-041-EVENT-NOTES.md for the full procedure and preparation checklist.
Analysis: extract this session's own connect-time DLCI 0x02 burst using the same method as
CAP-036-FINDINGS.md §4, and diff it byte-for-byte against CAP-036's own burst. Any payload that
differs and also plausibly matches a known settings-value shape (e.g. a 5-band float quintet,
PROTOCOL.md §4.2) is a strong candidate for a libmaestro-side state read-back; a byte-for-byte
match despite genuinely different settings is a clean negative. See CAP-041-EVENT-NOTES.md's
Decode/Analysis checklist.
Group AI — Long pure-idle bracket for the periodic DLCI 0x02/0x04/0x08/HFP push cadence (occasional, added 2026-09-05)
Purpose: CAP-036-FINDINGS.md §12.5 found a periodic push recurring on DLCI 0x02, 0x04, 0x08,
and HFP's AT+BIEV all within 7–18ms of each other, starting ~3.5 minutes after connection —
but CAP-036's own session only ran ~7 minutes, too short to characterize the cadence's longer-run
behavior the way CAP-009-FINDINGS.md §2's 101-minute HFP-only session already does for AT+BIEV
alone (PROTOCOL.md §4.3 Option C: "settling burst, then irregular, median ~20s, up to ~14.6
minutes"). This Group is a dedicated, 15+ minute, genuinely idle bracket to extend that
characterization across all four channels at once — this is exactly the capture scenario
TESTPLAN_BLUETOOTH_HCI_SNOOP.md's OBS-002 row has been waiting for.
Runs on the Pixel 7a with the official Pixel Buds companion app (backgrounded, not kept open) and Google Play Services enabled, same baseline. Dock state must be picked once and held constant for the whole session (worn or docked, either is fine, but ADR-024 shows dock state affects at least one of these frames' content, so a mid-session dock-state change would confound the cadence measurement).
Test-ID: OBS-002 (existing — this is that row's first dedicated capture scenario).
Procedure: connect normally, confirm the fixed dock state once, then background the app and
leave everything untouched (ideally screen off) for at least 15 minutes. See
CAP-042-EVENT-NOTES.md for the full procedure, including a note on using periodic wall-clock
check-in shots rather than continuous video for a session this long.
Analysis: extract every DLCI 0x02/0x04/0x08 push and every AT+BIEV=2 occurrence across the
whole session, check whether the cross-channel near-lockstep timing holds up over dozens of
occurrences (not just CAP-036's handful), and compare the gap distribution against CAP-009's
existing HFP-only model. Also cross-check against CAP-027's cross-sync-caveat
(DESKRESEARCH_FINDINGS.md 2026-09-04 round 2) — that session found the sync breaking down 3
times during active A2DP streaming; this idle session is the control for whether it holds up
perfectly with no streaming at all. See CAP-042-EVENT-NOTES.md's Decode/Analysis checklist.
Purpose: PROTOCOL.md §4.5.3's ANC-mode rotation checklist (qhr field 12, confirmed
field-number identity as qht) has 16 wire-observed boolean flags (HOLD-005, CAP-021) but no
Left/Right-distinguishing field for this specific write — unlike HOLD-001–HOLD-004, it's
unknown which frames belong to which earbud's own rotation list.
- Open Device details → Controls and gestures → the ANC-mode rotation checklist for the Left
earbud specifically (the UI is confirmed to expose this per-earbud, per
PROTOCOL.md§4.5.3's own UI description). - Toggle each of the 4 checklist items (Noise cancellation / Off / Adaptive / Transparency) for the Left earbud one at a time, with a clear pause (≥10s) and a distinct video-visible action between each toggle, so each write can be isolated to one specific checklist item.
- Repeat step 2 for the Right earbud's own rotation checklist, again one item at a time.
Analysis: do the Left-earbud toggles and Right-earbud toggles produce distinguishable wire
patterns (e.g. a different inner field position, a different correlation-ID pattern, or simply a
confirmed video-to-frame 1:1 timing correlation good enough to assign each frame to a side by
elimination)? This directly closes PROTOCOL.md §6's open item on this question.
Purpose: PROTOCOL.md §4.5.7 confirms qhr field 17 = Volume balance (full identity, 🟢
FACT), but its numeric scale/range beyond the 7 samples in one continuous drag (CAP-022) and
which direction (Left/Right) corresponds to negative vs. positive zigzag-decoded values remain 🔴
open — a single continuous drag at 1fps video-sampling resolution wasn't enough to resolve this.
- Open Device details → Sound → the Balance slider.
- Drag the slider to its full Left extreme, release, and hold for ≥3s before any further action (an isolated, discrete sample, not a continuous drag) — video-confirm the slider's own on-screen position/label at this extreme.
- Return the slider to center, pause ≥5s, then drag to its full Right extreme, release, hold ≥3s, video-confirm.
- Repeat steps 2–3 at least once more for a second independent sample of each extreme.
- Optionally, sample 1–2 clearly-labeled intermediate positions (e.g. "25% Left", "25% Right" if the UI shows a numeric/percentage label) the same isolated way.
Analysis: zigzag-decode ((n>>1) ^ -(n&1)) each isolated sample's field 17 value and match
it against the video-confirmed slider position/label at that exact moment — this directly closes
PROTOCOL.md §6's open item on this field's scale/direction.
Purpose: CAP-021's 1123-frame DLCI 0x0a burst (PROTOCOL.md §6) has recurred in exactly 1 of
16+ sessions checked — passively waiting for it is not expected to work. TODO.md's own
"Recommended priority order" §5 asks for a purpose-built hypothesis test bracketing candidate
triggers one at a time, per PROJECT_RULES.md §4's fixed template (hypothesis, setup, expected
outcome, actual outcome, conclusion).
Run as up to 3 separate bracketed sub-sessions, each testing exactly one candidate trigger, logged either as 3 rows under this one Group or as 3 short sequential capture windows in one continuous log with clear boundary timestamps — the maintainer's choice at execution time, record which was used:
- Trigger candidate 1 — app backgrounded/foregrounded: with the Buds connected and idle, background the official app for ≥2 minutes, then foreground it again; log throughout.
- Trigger candidate 2 — a scheduled sync window: leave the Buds connected and the phone
otherwise idle (screen off, app backgrounded) for an extended window (≥15 minutes, matching
CAP-042's own idle-bracket precedent) to see if the burst appears without any explicit action. - Trigger candidate 3 — a charge-state change: dock one or both Buds into the case (charging begins) or remove them (charging stops) while logging, isolating this specific transition.
Analysis, per PROJECT_RULES.md §4's template: for each bracketed trigger, record explicitly
whether the 1123-frame (or any size) DLCI 0x0a burst appeared in or shortly after that specific
window — a negative result for all three is itself a valuable, reportable outcome, not a failed
session.
Purpose: REVERSE_ENGINEERING.md's qhr entry fully traces field 13 (ANC state, DLCI 0x02) to
exactly two code-side callers — an in-app QuickActionsFragment toggle-group tap, and a physical
press-and-hold gesture (gvi/gvj) — but neither has ever been wire-confirmed: no capture has yet
correlated a qhr-field-13 write on DLCI 0x02 with an itself otherwise-unexplained DLCI-0x04
Notify (the specific pattern CAP-038-FINDINGS.md §5 observed twice with no preceding Get/Set on
DLCI 0x04).
- With full DLCI 0x02 traffic retained (raw-path extraction, not
btsnooz.py), perform an isolated ANC-mode change via the in-appQuickActionsFragmenttoggle group (a single tap, pause ≥10s before the next action, matchingCAP-006's own isolated-tap discipline). - Separately, perform an isolated ANC-mode change via a physical press-and-hold gesture on one earbud (again, single gesture, pause ≥10s).
Analysis: for each of the two actions, check whether a field5{field4{field13=N}}} write
appears on DLCI 0x02 at that moment — a positive match (especially time-correlated with the
tap/gesture, mirroring CAP-006's DLCI-0x04 confirmation methodology) would be strong evidence
DLCI 0x02 also carries ANC state in parallel to DLCI 0x04's already-confirmed path.
Purpose: CAP-041-FINDINGS.md §4 and CAP-036-FINDINGS.md §12.6 both found a recurring
2-field sub-message inside DLCI 0x02's connect-time/periodic burst holding a constant value that
happens to match the on-screen Case battery percentage throughout the session — but because the
value never changed in either session, this is consistent with, but does not confirm, the field
tracking Case battery (it could equally be any other session-constant value that happens to
match).
- Start a session with the Case at a known, video-confirmed battery percentage (check via the official app's Device details screen before starting).
- Over an extended session (≥30 minutes, allowing genuine charge/discharge to occur — e.g. leave the Case charging via USB for part of the window, or simply let it discharge naturally if a bud is docked), periodically re-check and video-confirm the on-screen Case percentage.
- Ensure the DLCI 0x02 periodic push (already confirmed to recur every few minutes when the app is
foregrounded, per
CAP-036-FINDINGS.md§12.5) is captured throughout.
Analysis: does the recurring 2-field sub-message's value actually change in step with the video-confirmed Case percentage changes? A positive correlation across a genuine change would promote this from "consistent with" to real evidence; a mismatch would be an equally useful, reportable negative result.
Group AO — EQ outer field 16-vs-18: drag-and-release without ever tapping Save (occasional, added 2026-09-13, ai-sessions/0017)
Purpose: PROTOCOL.md §4.2's "Outer field 16 vs. 18" item is a genuine, unreconciled tension:
CAP-015's wire timing reads as "field 18 fires on slider-release" (no video-visible Save tap
before any of 15 field-18 frames), but a full call-graph trace of fyd.d/fyd.e
(REVERSE_ENGINEERING.md's qjw entry) found field 18 (fyd.d) reachable through exactly two
code paths — a dedicated key_eq_save_button/title_eq_save_button click handler (self-describing
log "On click save EQ button"), and, newly found by this session's own re-verification, a
navigate-away-from-the-EQ-screen path (hod.java:36, self-describing log "Navigate away, save EQ", gated on an unsaved-changes-shaped flag) — but no slider-release code path to field 18
anywhere in this app version's decompiled source. This capture is designed to isolate all three
candidate triggers from each other for the first time.
- Open the EQ screen (Device details → Sound → Equalizer → custom sliders).
- Drag slider 1 to a new position, release, and wait ≥10s with the
Savebutton never tapped and without navigating away from the EQ screen (stay on this exact screen the whole time) — video must clearly show both: theSavebutton not being touched, and the screen not changing. - Repeat step 2 for slider 2 (a second, independent release-only sample, still without leaving the screen or tapping Save).
- As a clearly separated second half of the same session: drag slider 3, release, wait ≥10s (no
Save tap, no navigation, replicating steps 2–3's isolation once more), then deliberately tap the
Savebutton and video-confirm the tap. - As a third, separated part: drag slider 4, release, wait ≥3s, then navigate away from the EQ
screen (e.g. press back to Device details) without ever tapping
Save— video must show the screen change clearly.
Analysis: for each of the three isolated conditions (release-only, release+Save, release+navigate-away),
does a field5{field4{field18=...}} write appear on DLCI 0x02, and if so, at which specific action?
This directly distinguishes the three candidate triggers REVERSE_ENGINEERING.md's qjw entry now
names, closing PROTOCOL.md §4.2's own open item with a within-session positive/negative contrast
instead of a single ambiguous reading.
Group AP — Battery Notification: bracket a single-bud insertion/removal, connection-free (occasional, added 2026-09-13, ai-sessions/0017)
Purpose: CAP-043 (Group Q repeat) established, under rigorously clean connection-free isolation,
that the Buds' idle/case-closed 0xFE2C BLE advertisement does not structurally match PROTOCOL.md
§4.3 Option A's documented Battery Notification layout — but only tested the idle/case-closed
condition. The official Fast Pair spec itself describes the Battery Notification extension as
"optional when a single bud is inserted/removed" — a materially different trigger condition,
not yet bracketed by any capture to date.
- Force-stop the official Pixel Buds app (as
CAP-043did) and confirm, before starting the log, that no classic RFCOMM connection to the Buds is active (system Bluetooth settings showing "not connected," or the Buds already disconnected). - Start HCI snoop logging and a screen/phone-camera recording of the system Bluetooth settings
panel (matching
CAP-043's own methodology). - With the case closed and the phone otherwise idle, remove exactly one earbud from the case (video-confirm the exact removal moment), then wait ≥15s without touching anything else.
- Re-insert that same earbud into the case (video-confirm), wait ≥15s again.
- Repeat steps 3–4 once more for the other earbud, as an independent second sample.
- Throughout, avoid opening the official app or making any classic RFCOMM connection — per
AGENTS.md§7's bounded scanning exception (filtered to the bonded device, foreground-triggered, time-boxed).
Analysis: does a 0xFE2C service-data advertisement carrying the documented Battery Notification
layout ([Flags=0x00][Account Key Data][0x33/0x34 marker][L][R][Case]) appear within a few seconds of
either bracketed insertion/removal event, even though it does not appear during idle/case-closed
conditions? A clean negative here (matching CAP-011/CAP-036/CAP-043's existing non-matches)
would materially strengthen the case for reframing PROTOCOL.md §4.3 Option A's status, per
CAP-043-FINDINGS.md §7's own recommendation — see ai-sessions/0017_MAINTENANCE_RESULT_2026_09_13.md
Phase 2 for that scoping question.
Group AQ — Head gestures (Nod/Shake) with an active call/notification (occasional, added 2026-09-13, ai-sessions/0017)
Purpose: CAP-028 (Group O) found zero wire-visible traffic on the Buds' own connection during
a claimed Nod/Shake gesture window — but TESTPLAN_BLUETOOTH_HCI_SNOOP.md's own description ties
Nod/Shake's actual function to an active call ("answers a call") or notification ("dismisses a text
reply"), and no call/notification was active during CAP-028's own window, so the clean-negative
result cannot distinguish "functionally inert, as expected" from "gesture not actually triggered."
This Group fixes that specific gap.
- Confirm "Use head gestures" is toggled ON (Device details → Controls and gestures) before starting
— per
CAP-020's own precondition, re-confirm on camera this time (do not rely on a carried-over assumption). - With the Buds connected and worn, trigger an actual incoming phone call (e.g. call the test
phone from a second phone). While the call is ringing, perform a Nod gesture — camera angled to
capture both the phone screen (showing the call being answered) and the user's head/ears
(the gesture itself), unlike
CAP-028's phone-only framing. - During the call, end it normally; then trigger a second incoming call and perform a Shake gesture to reject it — same dual camera framing.
- Separately, trigger a text-message notification (with 'Spoken notifications'/dictation-reply context if feasible) and perform a Nod (reply via dictation) or Shake (dismiss) gesture, camera angled the same dual way.
Analysis: does a Nod/Shake gesture performed while a call/notification is genuinely active produce
any wire-visible signal (SCO/eSCO setup change, AVRCP command, DLCI 0x02/0x04/0x08 write) correlated
with the gesture, now that both the gesture itself and its effect are camera-confirmed? Also check the
REVERSE_ENGINEERING.md HeadGesture/qin register (updated by ai-sessions/0017 Phase 3 — the
SubscribeToResults response enum is confirmed 3-valued, structurally consistent with exactly two
real gesture types plus one unset/unknown sentinel, though which raw value is Nod vs. Shake is not
recoverable from static analysis) against any DLCI 0x02 traffic this session captures.
Group AR — HOLD-005 ANC-rotation-checklist Left/Right split, genuine re-run (occasional, added 2026-09-13, ai-sessions/0017)
Purpose: CAP-045 (Group AJ) did not run Group AJ's own procedure — the maintainer performed
physical press-and-hold ANC cycling instead, and the rotation-checklist screen was never opened, so
HOLD-005's Left/Right question remains exactly as open as before CAP-045. This Group is a fresh
attempt at the same question, not a CAP-045 v2 reusing Group AJ's own letter — Group AJ's
procedure is unchanged and still valid; this Group exists specifically to add an explicit anti-repeat
safeguard, named as such, referencing CAP-045 by name as the reason for the extra emphasis.
- Anti-repeat safeguard (mandatory, named per
CAP-045's own gap): before toggling anything, video-confirm the actual on-screen destination is "Device details → Controls and gestures → [the ANC-mode rotation checklist screen]" — a checkbox list titled with the four modes (Noise cancellation / Off / Adaptive / Transparency) must be clearly visible on-camera for at least one full toggle before this session counts as having run the procedure at all. If the checklist screen is not visible on camera at this point, stop and restart — do not proceed and hope the wire data disambiguates it after the fact, as happened inCAP-045. - With the checklist screen confirmed open for the Left earbud specifically (per
PROTOCOL.md§4.5.3's own UI description confirming this is per-earbud), toggle each of the 4 checklist items one at a time, with a clear pause (≥10s) and a distinct video-visible action between each toggle. - Navigate to the Right earbud's own rotation checklist (video-confirm the screen again, per step 1's safeguard) and repeat step 2.
Analysis: identical to Group AJ's own — do the Left-earbud and Right-earbud toggles produce distinguishable wire patterns (inner field position, correlation-ID pattern, or a confirmed video-to-frame 1:1 timing correlation good enough to assign each frame to a side by elimination)?
Group AS — Live GetSoftwareInfo/GetHardwareInfo correlation against the connect-time burst (occasional, added 2026-09-13, ai-sessions/0017)
Purpose: PROTOCOL.md §6's serial-number candidate (CAP-036 frame 1423, three length-14
strings inside DLCI 0x02's connect-time burst) was re-traced this session
(ai-sessions/0017_MAINTENANCE_RESULT_2026_09_13.md Phase 5): the decoded sub-message's own top-level
fields (1/2/3, each a direct STRING) structurally match qjm/qjr (GetHardwareInfo's two oneof
alternatives, qiv) exactly, and do not match qie (GetSoftwareInfo's own oneof alternative,
whose 3 fields are typed MESSAGE, requiring an extra nesting level not present in the observed
bytes) — a correction to, not a confirmation of, the existing HYPOTHESIS. Static analysis alone cannot
go further; this needs a live correlation.
- With full DLCI 0x02 traffic retained (raw-path extraction, not
btsnooz.py), open Device details → More settings → "View firmware version per component (L/R/Case)" and "View serial numbers per component" (FW-002/FW-003) — video-confirm and transcribe the exact displayed serial numbers per component (Left/Right/Case) on camera. - Separately, disconnect and reconnect the Buds (a fresh classic connection) to capture a fresh connect-time DLCI 0x02 burst.
Analysis: does the connect-time burst's own three-length-14-string sub-message match the
video-transcribed serial numbers from step 1, confirming the "EC"/"DR"/"DL"-substring
Case/Right/Left semantic reading (still 🟡 HYPOTHESIS per AGENTS.md §13.6 even if the RPC identity
resolves)? And does decoding the burst's surrounding RPC envelope (service/method identifiers, per
REVERSE_ENGINEERING.md's nqx/npy/nqo pw_rpc entries) show a GetHardwareInfo call/response
rather than GetSoftwareInfo, resolving this session's own structural finding one way or the other?
No app-driven commands are possible here, so this session focuses on connection-level and passive behavior:
- Pairing [
PAIR-001], via system Bluetooth settings (Settings → Connected devices → Pair new device). Note the start time precisely — this is the most information-dense part of this session (bonding handshake, initial service discovery), and like Group A #1 it's one user action producing a multi-frame burst, not something to isolate frame by frame. - Idle observation [
BATT-003] — once connected, wait ~30–60 seconds without touching anything. This can reveal spontaneous status frames the Buds send unprompted. Note the observation start (once you stop touching anything, leaving a clean gap after the pairing burst settles), the observation end, and confirm the connection state stayedconnectedthroughout — so this window isn't later confused with settling traffic from item 1. - Open the Bluetooth device detail screen for the Buds in system settings [
GATT-001] (this can trigger GATT service/characteristic discovery on some Android versions). - Disconnect and reconnect once, as its own isolated pair of actions [
PAIR-003], to observe both a clean teardown and a reconnection to an already-bonded device (useful for validatingARCHITECTURE.md§6/§7 resilience assumptions).
Pull the bugreport (§3) the same way.
Source: TESTPLAN_BLUETOOTH_HCI_SNOOP.md sections 2 (User Actions via the Case & Buds) and 4
(Automatic Actions Initiated by the Hardware). These actions are grouped separately from
4.1/4.2 because they aren't tied to a specific phone — a tap on the bud, or the case
button, behaves the same regardless of which device is connected. Run this group on
whichever phone you already have connected and logging at the time; if you want to
compare hardware behavior across both OSes for a specific action, repeat that one action
on the other phone as its own short session rather than redoing the whole group.
Same rhythm as before: wait ~5s → note the exact time → perform the action → wait ~5–10s → move to the next action.
- Open the charging case lid [
CASE-003]. Wait. Note time. - Remove Left earbud from the case [
CASE-004]. Wait. Note time. - Remove Right earbud from the case [
CASE-005]. Wait. Note time. - Insert Left earbud into the ear [
INEAR-002]. Wait. Note time. - Insert Right earbud into the ear [
INEAR-003]. Wait. Note time. - Place buds back in the case and close the lid [
CASE-006]. Wait. Note time. (Expected to terminate the active Bluetooth Classic connection — good for validatingARCHITECTURE.md§6/§7 disconnect handling.)
- Tap once on a bud [
TOUCH-002]. Wait. Note time. - Double-tap on a bud [
TOUCH-003]. Wait. Note time. - Triple-tap on a bud [
TOUCH-004]. Wait. Note time. - Swipe forward on a bud (volume up) [
TOUCH-005]. Wait. Note time. - Swipe backward on a bud (volume down) [
TOUCH-006]. Wait. Note time. - Press and hold on a bud [
TOUCH-007]. Wait. Note time. (Behavior depends on the per-earbud configuration set in §4.1 Group G — note which mode was active when you test this.)
Requires 'Head gestures' enabled (§4.1 Group F).
13. Nod [HEAD-002] (simulating answering a call, or a text reply if 'Spoken notifications' is
on). Wait. Note time.
14. Shake [HEAD-003] (simulating rejecting a call/dismissing a text reply). Wait. Note time.
- Start speaking with Conversation Detection on [
CONV-002] (§4.1 Group C), to trigger the detection event. Wait. Note time. - Hold the case button for 30 seconds [
CASE-007] (case open, buds inside, plugged into power) —⚠️ this is a confirmed full factory reset, not just pairing mode (perTESTPLAN_BLUETOOTH_HCI_SNOOP.md§2). Do this deliberately, last, and only once you're ready to re-pair from scratch — it will also reset the Find My Device link on the Pro 2. If you do trigger it, capture the subsequent re-pair as its own isolated session right afterward [PAIR-002] (same rhythm as Group A #1) — this gives a second, from-true-factory-state bonding capture to compare against Group A's lightweight forget-and-re-pair baseline. It is optional and not a prerequisite for anything else in this guide — Group A's lightweight baseline is sufficient on its own for every other group. - (Open question, see
PROTOCOL.md§6) Try a shorter/different press [CASE-008] on the case button to see if it triggers pairing mode without a full reset. No officially confirmed duration exists for this — treat your own finding here as[VERIFIED-LOCAL]material for the relevantCAP-NNN-FINDINGS.mdonce confirmed.
These are waiting periods to catch spontaneous hardware-initiated traffic per
TESTPLAN_BLUETOOTH_HCI_SNOOP.md §4 — nothing to tap, just capture while the condition holds. As with
Group L, log explicit observation start and observation end boundaries, not just
a single timestamp, so this traffic isn't confused with settling traffic from whatever
preceded the window.
For items 19–20 below, "nothing found" is not a single outcome — record which of these three actually happened, since they mean different things for protocol reconstruction:
- Local behavior confirmed + Bluetooth traffic observed → record the frame(s) as
[VERIFIED-LOCAL]per the usual process (§5 step 4). - Local behavior confirmed, but no Bluetooth traffic in the log despite a clean
observation window → this is itself a positive finding, not an absence of one — it's
evidence for a purely on-device implementation. Record it in the relevant
CAP-NNN-FINDINGS.mdas such (e.g. 🟢 FACT: "no wire-visible signal observed on trigger, N attempts"), don't just leave the row blank. - Inconclusive — you're not sure the local trigger actually fired (e.g. unclear whether the clap was loud enough, or the environment change was large enough), or the observation window was contaminated by other traffic. This does not support either conclusion above; note it as 🔴 unconfirmed and, if practical, retry with a clearer trigger before drawing any conclusion.
- Passive BLE scan while the case is closed and idle [
BATT-002/BATT-003] — intended to catch the Fast Pair Battery Notification advertisement (PROTOCOL.md§4.3 Option A) without any active RFCOMM connection. This doesn't require the buds to be connected to the capturing phone at all — any nearby scan should do, per the spec. This is a one-off, manual reverse-engineering capture, not a template for the app. The production app's own BLE scanning stays governed separately, and more narrowly, by the bounded exception inAGENTS.md§7 /DECISIONS.mdADR-006 — this experiment does not authorize a broader scanning implementation than that. - Trigger a loud, sudden sound near the buds while worn [
LOUD-001] (e.g. clap sharply nearby) to attempt to observe Loud Noise Protection engaging (PROTOCOL.md§4.5/§6) — confirm you actually noticed the local effect (e.g. audible volume dip) before concluding anything about the Bluetooth traffic (or lack of it); see the three-way outcome guidance above. - Move between distinctly different acoustic environments while worn [
ADAPT-002] (e.g. quiet room → street) to attempt to observe Adaptive Audio adjusting (PROTOCOL.md§4.5/§6) — same guidance as #19: confirm the local effect first, then classify the Bluetooth-traffic outcome using the three categories above.
- Open the extracted
CAP-*-btsnoop_hci.logfile directly in Wireshark (File → Open, or drag-and-drop — no special import steps needed, Wireshark recognizes the BTSnoop file format natively). This gets you HCI/L2CAP/RFCOMM/ATT-level framing for free — it does not mean Wireshark understands thelibmaestropayload itself. There is no dissector for a proprietary, undocumented protocol, so the actual command bytes inside an RFCOMM frame will show up as opaque raw data; decoding what they mean is manual work you do against the hypotheses inPROTOCOL.md§2 (see step 4 below) — that manual decoding is the actual point of this whole procedure. - Useful filters to narrow the view:
bthci_acl— general ACL-level Bluetooth traffic.btrfcomm— RFCOMM traffic specifically (this is wherelibmaestroframes live).btatt— GATT/ATT traffic (relevant for the BLE battery-service investigation).btle— BLE Link Layer traffic (advertising/scan reports) — this is where the Fast Pair Battery Notification capture (Group Q #18) shows up, notbtrfcomm.bluetooth.addr == <buds MAC>— restrict to the Buds' Bluetooth address once you've identified it (visible in the pairing frames or in Wireshark's Bluetooth device list under View → Bluetooth Devices).
- Use the timestamp column together with your own action log (§1.3, §4) to identify
which frame(s) correspond to which action. Wireshark's relative or UTC time display
(View → Time Display Format) should be set to whatever you used when noting action
times, to avoid an offset mismatch. Since the capture-side wait time (§4) is a
heuristic, not a guarantee: if a frame appears just after your noted "wait ~5–10s"
window for one action but before the next action's timestamp, treat it as a probable
late response to the earlier action rather than automatically attributing it to
whatever came next — check payload plausibility against both candidates before
deciding, and note the ambiguity in this session's
CAP-NNN-FINDINGS.mdif it can't be resolved from the log alone. - For each identified command frame:
- Note the raw bytes (right-click → Copy → ...as Hex Stream is fastest).
- If it's an RFCOMM frame (
btrfcomm— an app-triggered command, or the Find My Buds/Ring action): compare the structure against the envelope hypothesis inPROTOCOL.md§2 (magic byte, length field, channel/msg ID, payload, checksum). - If it's a BLE advertisement (
btle— the Battery Notification, Group Q #18): compare it against the Fast Pair Battery Notification structure inPROTOCOL.md§4.3 Option A (flags, account key data, battery-level-length/type byte, then the 3 battery bytes) instead. This is a different, unrelated structure — it is not RFCOMM traffic and was never expected to match the §2 envelope hypothesis; don't force-fit it there or record a false "doesn't match" finding. - Record the confirmed values in this session's
CAP-NNN-FINDINGS.mdfirst, then promote them intoPROTOCOL.md§4.1's opcode table (RFCOMM frames) or §4.3 (battery), mark the entry[VERIFIED-LOCAL]with today's date, and raise its confidence to 🟢. Include the Test-ID fromTESTPLAN_BLUETOOTH_HCI_SNOOP.md(e.g.ANC-001) and this session'sCAP-NNNID in that note — this is what closes the evidence chain inTESTPLAN_BLUETOOTH_HCI_SNOOP.md§0.1. Also add the Test-ID to this session's row in the Capture Index (§9) Test(s) column if it isn't there yet.
- If a frame doesn't match the expected envelope shape at all, don't force-fit it —
note it as an open question (
PROTOCOL.md§6) rather than recording a guess as fact.
- Log rotation: the in-device snoop buffer is finite; very long or idle-heavy sessions can push earlier frames out before you extract them. Keep sessions focused (§2, §4).
- A Bluetooth restart is required for the HCI snoop logging setting to take effect — per Android's own source documentation, not just this guide's experience (this project has no capture statistics of its own yet to say how common empty-log failures are). Reboot by default (§2 step 5); a plain toggle only if you've confirmed it works reliably on your specific phone.
adb rootwill not work on either phone for this purpose — both stock Pixel firmware and GrapheneOS are, as set up for this project, normal production/non-rooted builds, on whichadbdcannot run as root. This is about the current, as-configured state of these two phones, not a claim that Pixel hardware can't be rooted by other means — don't waste time trying to root a device just for this;adb bugreportis the supported path and is sufficient.- Path differences across Android versions: the internal bugreport path for the
snoop log has moved between
FS/data/log/bt/andFS/data/misc/bluetooth/logs/across Android releases — if your expected path is empty, search the archive by filename instead of assuming the path is wrong. - GrapheneOS specifics: no additional developer-mode dance is required beyond
standard USB debugging —
adb bugreportfunctions the same as on stock Android for this purpose on a non-rooted production build. - Firmware version drift: if automatic Buds firmware updates are still enabled (see
the earlier discussion on checking/disabling them in the official app), a capture done
today may not match one done next month. Always note the confirmed firmware version
(
PROTOCOL.md§0/§0.1) alongside any capture you take. - Two Buds, one identity: the case, left bud, and right bud may all appear as
distinct addresses/roles in some traffic (notably GATT). Don't assume all frames come
from a single logical peer — check
bluetooth.addrper frame when in doubt. btsnoozACL truncation isn't limited to BLE/GATT — added 2026-08-26,CAP-012.CAP-017-FINDINGS.md§2 already documented severe (~15-byte) ACL truncation on a BLE/GATT session's log;CAP-012-FINDINGS.md§1 shows the identical phenomenon on a classic BR/EDR session (RFCOMM/SDP/HFP data, not GATT) — confirmed the same way,frame.cap_lencapped at a small value regardless offrame.lenfor everyHCI_ACLpacket. This is the signature of §3 step 4'sbtsnooz.py-from-bugreport fallback path being used instead of step 3's raw, untruncatedbtsnoop_hci.log— checkcapinfos(Packet size limit: inferred: ...) right after extraction, before spending analysis time on a session, and prefer the step 3 raw path whenever it's present in the bugreport archive.
Q: Do I need to do a full capture session every time I want to check one new command? No — you can pull a fresh bugreport (§3) any time after enabling logging (§2), covering just that one new isolated action. You don't need to redo pairing or previously-confirmed commands each time.
Q: The zip from adb bugreport doesn't contain a Bluetooth folder at all — what now?
This has been reported when the HCI snoop toggle wasn't actually active during the
session (see the "A Bluetooth restart is required" note in §6) — re-check §2 step 5,
reboot the phone this time even if you used a plain toggle before, reproduce the action,
and pull a fresh bugreport. If it still doesn't appear, search the whole archive by
filename (find . -iname "*btsnoop*") rather than assuming a fixed path.
Q: Can I use Wireshark's live capture (the "Android Bluetooth Btsnoop" USB interface)
instead of pulling a bugreport each time?
In principle yes on some Android versions, but on modern Android the live-capture port
is frequently not exposed by default, requiring workarounds (e.g. manually forwarding a
local port via nc over adb shell) that add complexity without much benefit for this
project's needs. The adb bugreport method in §3 is simpler and reliable across both
phones — stick with it unless you have a specific reason to need live capture.
Q: Is it safe/expected that adb root fails on GrapheneOS?
Yes — this is expected, deliberate behavior on any non-rooted production Android build,
not a GrapheneOS-specific restriction, and not something to work around by rooting the
device. Rooting significantly weakens the security model this whole project is built
around (see AGENTS.md §2), so adb bugreport remains the correct approach even though
it's slightly more roundabout than a direct file pull.
Q: My Pixel 7a capture shows encrypted-looking or unreadable payload bytes even inside the RFCOMM stream — is that expected? Most likely, not actual encryption — a few distinct things get conflated under "looks encrypted," worth separating:
- HCI-boundary visibility: an HCI snoop log is captured at the boundary between the host (Android's Bluetooth stack) and the controller (the Bluetooth chip). On typical implementations, Bluetooth link-layer encryption/decryption happens in the controller, below this boundary — so ACL data crossing HCI toward the host is usually already plaintext at the L2CAP/RFCOMM level, not still link-encrypted.
- What this means in practice: raw, unfamiliar-looking bytes inside an RFCOMM frame are, for this project, far more likely to just be serialized protobuf data you don't recognize yet (protobuf's binary encoding often looks fairly random to the eye without the schema) than genuine Bluetooth link-layer encryption. Don't assume "unreadable" means "encrypted, therefore unusable" — it's probably exactly the payload you're trying to reverse engineer.
- When it might genuinely be encryption: if bytes are unreadable consistently, across many otherwise-isolated captures, and never resolve into anything matching the envelope hypothesis no matter how you slice the offsets, that's a better (though still not certain) signal of something below Wireshark's visibility — e.g. a higher-layer/application-level encryption scheme, rather than standard Bluetooth link encryption, which per the point above shouldn't normally still be present at this layer.
- If this happens consistently, note it as an open question in
PROTOCOL.md§6 — including which of the above you've ruled out — rather than assuming the envelope hypothesis in §2 is simply wrong.
Q: Should I capture on the GrapheneOS phone first, since that's my actual target platform? No — do the Pixel 7a (official app) capture first. It's the only source of frames you can positively attribute to a specific command, since you're the one triggering them through the known, official UI. The GrapheneOS capture is for validating connection/pairing/passive behavior on the target OS, not for discovering new commands.
Q: How do I know when I've captured "enough" and can stop?
When every row in PROTOCOL.md §4.1's opcode table and every open question in §6
that's answerable via traffic analysis (as opposed to, say, firmware version lookup) has
moved from 🔴/🟡 to 🟢 with a [VERIFIED-LOCAL] tag — or when you've made a deliberate,
documented decision to leave a specific low-priority item (e.g. Find My Buds) unverified
for now.
Every capture session should end with at least one of these, recorded first in that
session's CAP-NNN-FINDINGS.md and then promoted directly into PROTOCOL.md
(PROTOCOL_NOTES.md has been retired — there is no intermediate buffer):
-
PROTOCOL.md§2 — envelope structure fields confirmed/corrected. -
PROTOCOL.md§4.1 — opcode table rows filled in, confidence raised to 🟢. -
PROTOCOL.md§4.3 — battery approach confirmed or ruled out (now split into Option A: BLE advertisement, and Option B: RFCOMM Message Stream — see the current version of that section). -
PROTOCOL.md§0.1 — firmware version and any version-specific differences logged. -
PROTOCOL.md§6 — open questions resolved where this capture resolves them.
Mandatory, not optional: every new 🔴 OPEN QUESTION recorded in a
CAP-NNN-FINDINGS.md (or DESKRESEARCH_FINDINGS.md) MUST also be copied into
PROTOCOL.md §6, in the matching Framing/Commands & schemas/Behavior
subsection, even if it doesn't get resolved this session. An open question
that stays only in the findings doc is effectively invisible to future
sessions, since §6 is the consolidated list agents are expected to check
(AGENTS.md §0.1) — it must not depend on someone remembering to go re-read
every individual findings file. (Example of this rule being applied: the
UI-baseline-vs-wire-baseline firmware distinction, first noted in §0.1's
2026-08-14 addendum but not copied into §6 until 2026-08-15 — don't repeat
that gap for new open questions going forward.)
Treat an capture session that doesn't result in at least one of the above as incomplete — either the action wasn't actually isolated/identifiable, or something in the setup (§2, §6) needs revisiting before the next attempt.
Every capture session gets a row here, added at or immediately after extraction
(§3) — this is the authoritative index that CAP-NNN-FINDINGS.md and
PROTOCOL.md evidence entries reference back to, per PROJECT_RULES.md
rule 3 (traceability) and rule 14 (capture metadata). A capture that never
gets a row here is, for evidence purposes, effectively lost — don't skip this
step, even for a quick one-action session.
ID format: CAP-NNN, zero-padded, strictly incrementing, never reused —
if a capture turns out to be unusable, mark it discarded in Status rather
than deleting the row or reassigning its number to a later capture. Register
every new CAP-NNN in id_registry.csv (repo root) when adding its row here
— scripts/lint_docs.py checks every reference against that registry, which
is how the 2026-08-18 CAP-005/CAP-007/CAP-010 ID-reuse incident (see
CHANGELOG.md) would have been caught mechanically instead of found by hand.
| ID | Date | Phone | Android | Buds FW | App version | Group(s) | Test(s) | Purpose | Bugreport file | Extracted log | Status |
|---|---|---|---|---|---|---|---|---|---|---|---|
CAP-001 |
2026-08-09 | Pixel 7a | 17 | release_5.203 | 1.0.955078536 | Z, A, B, M | PAIR-001, CASE-003, CASE-004, CASE-005, ANC-001, ANC-002, ANC-003, ANC-004, CASE-006 |
Pipeline validation; scope grew beyond Z into full pairing baseline + all 4 ANC modes + case/bud handling | captures/CAP-001-2026-08-09_08-51-00_08-52-20-Group_Z/CAP-001-btsnoop_hci.log |
same file (already btsnooz-extracted) |
analyzed — see CAP-001-FINDINGS.md in that folder; ANC-opcode attribution inconclusive due to lack of action isolation |
CAP-002 |
2026-08-09 | Pixel 7a | 17 | release_5.203 | 1.0.955078536 | A | PAIR-001, CASE-001 |
Fresh pairing/bonding baseline (deleted stored link key first) through the Pixel Buds app's first-run setup flow (Fast Pair save-to-account, CDM permission, Device details load) | captures/CAP-002-2026-08-09_17-04-53_17-06-46-Group_A/CAP-002-btsnoop_hci.log (sliced from a shared, non-restarted ~8h20m snoop log — see that folder's CAP-002-EVENT-NOTES.md process note) |
same file | analyzed — see CAP-002-FINDINGS.md in that folder; Fast Pair Message Stream Device Information group tentatively identified (channel 2/DLCI 0x04); no RFCOMM traffic found during app setup/Device-details load; HFP channel opened but no AT-command traffic observed (contrast with CAP-001) |
CAP-003 |
2026-08-10 | Pixel 7a | 17 | release_5.203 | nRF Connect (generic BLE tool), then official app v1.0.955078536 (took over partway) | R | PAIR-001 |
Forced GATT rediscovery attempt (pairing removed via system settings, connected via nRF Connect instead of the official app) to resolve CAP-002's open 0x0f2a/0x0c0X handle UUIDs; classic pairing captured as a bonus data point |
captures/CAP-003-2026-08-10_20-59-16_21-00-37-Group_R/CAP-003-btsnoop_hci.log (short, freshly-restarted log, no slicing needed) |
same file | analyzed — see CAP-003-FINDINGS.md in that folder; primary goal not achieved — Android's GATT database cache survived the pairing removal, so zero Read By Group Type/characteristic-discovery traffic occurred; 0x0f2a/0x0c0X UUIDs still unresolved; new handle 0x0f28 found (polled every ~60s, value 0x31); reinforces that RFCOMM channel numbers are session-local while GATT handles are stable across sessions |
CAP-004 |
2026-08-11 | Pixel 7a | 17 | release_5.203 | nRF Connect (BLE phase), then system Bluetooth settings (no Pixel Buds app at any point — uninstalled) | S | PAIR-001, CASE-003 |
GFPS-001 — Google Play Services disabled + Pixel Buds app uninstalled, to isolate whether CAP-002's Fast Pair Message Stream traffic is Buds-initiated or GMS-driven; bonus classic pairing captured (procedure deviated from Group S's system-settings-only description — nRF Connect was used first) |
captures/CAP-004-2026-08-11_06-22-36_06-25-12-Group_S/CAP-004-btsnoop_hci.log (contains unrelated background Fitbit Charge 6 traffic, excluded — see CAP-004-FINDINGS.md §1) |
same file | analyzed — see CAP-004-FINDINGS.md in that folder; mixed GFPS-001 result: CAP-002 §3's channel-2/DLCI-0x04 TLV content (Model ID, "Revision 6", etc.) is absent (channel 2 never opens) — GMS-and/or-app-dependent, unresolved which (GMS was disabled and the official app was uninstalled together in this session, a confound not yet isolated — see CAP-004-FINDINGS.md §4a); but CAP-001's channel-4/DLCI-0x08 content (google-pixel-buds-pro-v1, Europe/Amsterdam) reappears unchanged — not GMS-dependent (this one isn't confounded, since it's a presence-despite-both-removed result, not an absence); classic bonding used Cross-Transport Key Derivation (LE Secure Connections → classic key), not classic SSP as in CAP-002/CAP-003; new, unidentified Message Stream Groups 0x04/0x05/0x09 found; nRF Connect's cached GATT service list gives named candidate services (Google Fast Pair Service 0xFE2C, Accessory Non-Owner Service, Device Information) for the still-open 0x0f2a/0x0c0X handle questions |
CAP-005 |
2026-08-15 | Pixel 7a | 17 | release_5.203 | 1.0.955078536 | T | EQP-002, EQS-004 |
Highest priority. EQ command isolation — single isolated EQ preset change ("Heavy bass"), then a second isolated EQ Bass-slider drag + Save, to find EQ's command channel now that it's known not to share ANC's (PROTOCOL.md §4.1/§2.3) |
captures/CAP-005-2026-08-15_15-02-31_15-03-45-Group_T/CAP-005-btsnoop_hci.log |
same file | analyzed — see CAP-005-FINDINGS.md in that folder; goal achieved (🟡 HYPOTHESIS level): both actions isolate cleanly to DLCI 0x02 (libmaestro's Pigweed pw_hdlc channel, PROTOCOL.md §2.2a) only — DLCI 0x04/0x08 silent in both windows; a nested, protobuf-tag-consistent envelope decodes down to a 5×float32 band-gain quintet, with only the Bass field changing (+3.0 on preset, then −4.1 on the drag); the slider drag additionally revealed a second, later "Save"-tap wire burst (outer field 18) distinct from the live-drag burst (outer field 16), not anticipated by this capture's own event notes — first EQ-attributable content decoded on any channel, still 🟡 not 🟢 (single capture, field-to-band mapping inferred from one changed field) |
CAP-006 |
2026-08-15 | Pixel 7a | 17 | release_5.203 | 1.0.955078536 | B (repeat) | ANC-001, ANC-002, ANC-003, ANC-004 |
Clean, single-tap-per-window repeat of Group B to resolve CAP-001's first-two-taps-have-no-0x12-frame open sub-question and re-confirm the ANC bit-mapping before implementation |
captures/CAP-006-2026-08-15_17-23-49_17-25-06-Group_B/CAP-006-btsnoop_hci.log |
same file | analyzed — see CAP-006-FINDINGS.md in that folder; goal achieved, ADR-009 blocker resolved: all 4 isolated single taps produced a matching 0x12 "Set ANC state" frame (exactly 4 0x12 frames in the whole 233s log, one per tap, zero extras/misses), each within ~1.3s of its video-observed tap; bit-mapping re-confirmed byte-for-byte against PROTOCOL.md §4.1 (0x08=ANC, 0x20=Off, 0x40=Adaptive, 0x80=Transparency); CAP-001's 2/6 miss rate does not reproduce under isolated single-tap conditions — FrameEncoder implementation for this command proposed as unblocked, pending maintainer sign-off/DECISIONS.md update |
CAP-007 |
2026-08-16 | Pixel 7a | 17 (⚪ assumed, not visible on screen this capture) | release_5.203 | n/a (Android system Bluetooth "Device details" page, not the companion app) | U | INEAR-004(partial), CASE-004, OBS-003 |
DLCI 0x08 Group 0x04 Code 0x12 liveness/event bracket — bud-removed-from-case, case-closed-while-active, and multi-minute idle brackets, to test whether the alternating value is event-driven or a free-running counter (CAP-004-FINDINGS.md §5a Task 5) |
captures/CAP-007-2026-08-16_09-14-10_09-17-57-Group_U/CAP-007-btsnoop_hci.log |
same file | analyzed — see CAP-007-FINDINGS.md in that folder; Group U's own question answered (🟢 FACT): Code 0x12 is Buds-initiated (Rcvd-only), fires on DLCI 0x08 channel-(re)open and also autonomously after a ~184s idle gap with no channel churn — neither purely reactive nor purely free-running; value cycles 0x02/0x03/0x04 (corrects CAP-004's "2↔3" characterization); original in-ear-mapping goal not achieved — no earbud was ever inserted into an ear in this session (only removed from the open case), and that one physical event produced no value change on any DLCI (clean negative result); case-lid-close bracket also produced zero wire traffic (clean negative result) |
CAP-008 |
2026-08-26 | Pixel 7a | 17 (⚪ assumed) | release_5.203 | 1.0.955078536 (⚪ assumed, not shown on screen this session) | V | CALL-001 |
First capture of an actual phone call — resolves whether HFP AT-command SLC setup reoccurs (CAP-002-FINDINGS.md §5) and whether channel 5/DLCI 0x0a carries any payload or SCO/eSCO HCI event (CAP-001-FINDINGS.md §6 Task 6) |
captures/CAP-008-2026-08-26_09-38-44_09-41-36-Group_V/CAP-008-btsnoop_hci.log |
same file | analyzed — see CAP-008-FINDINGS.md in that folder; both open questions resolved: the full HFP SLC handshake reoccurs on a fresh classic-link connection (frames 776–1132, on RFCOMM channel 6/DLCI 0x0c this session — a third distinct channel-number assignment for the same profile, reinforcing CAP-001's "channel numbers are session-local" note); two clean Enhanced Setup Synchronous Connection/Synchronous Connection Complete (mSBC, eSCO) pairs appear, one per call — the first SCO/eSCO HCI events in this project, closing CAP-001-FINDINGS.md §6 Task 6. CALL-001 itself is now sub-second wire/video-correlated for both call start and end, across two independent calls. DLCI 0x0a stays silent through both calls (14th consecutive negative result) but is now specifically ruled out as the call's audio path. Bonus findings: AVDTP Suspend/Start (A2DP) brackets each call almost exactly; AT+BIEV HF Indicator #2 tracks the Left earbud this session (vs. Right in CAP-009) — refines the open "fixed-Right vs. HFP-primary" question; one isolated unsolicited +CIEV:6,... (battchg) push, a new 🔴 open question |
CAP-009 |
2026-08-23 | Pixel 7a (⚪ assumed) | 17 (⚪ assumed) | release_5.203 (⚪ assumed) | n/a (system Bluetooth only, no companion app screen this session) | X | BATT-006 |
Battery-level discrepancy bracket — cross-check AT+CIND/battchg against AT+BIEV over a natural battery decline (CAP-001-FINDINGS.md §3); no phone call occurred, so not combined with CAP-008 |
captures/CAP-009-2026-08-23_18-33-52_20-12-55-Group_X/CAP-009-btsnoop_hci.log |
same file | analyzed — see CAP-009-FINDINGS.md in that folder; BATT-006 resolved: AT+CIND?'s battchg is a single non-repeating snapshot at SLC setup (never a live reading); AT+BIEV=2 pushes at an irregular (not fixed ~6–7s) cadence and tracks the Right earbud's percentage specifically, not Left/Case/an aggregate, across 86 minutes; HFP/RFCOMM never reopens after the 20:02:27 case+USB reconnect (A2DP-only from then on). Bonus: 75-occurrence, 101-minute confirmation of the already-FACT PROTOCOL.md §4.3 Option E (DLCI 0x08 Group 0x0e Code 0x01, DECISIONS.md ADR-014) — first session to capture a live charge cycle (Left 93%→100%) and the case's empty→populated transition. Re-analyzed 2026-08-2x (independent video timeline, full wire re-run): all 7 maintainer-noted transitions confirmed (2 refined by minutes, periodic-manual-check lag not an error); corrected an assumed ~50s video-coverage gap to the actual ~2s; found a 🟡 HYPOTHESIS candidate for PROTOCOL.md §4.3 Option B's open battery code (DLCI 0x04 Group 0x03 Code 0x03) and a 🟡 HYPOTHESIS BLE Fast Pair scan explanation for the post-reconnect on-screen updates. All 5 proposals from this pass maintainer-approved (DECISIONS.md ADR-015) |
CAP-010 |
2026-08-16 | Pixel 7a | 17 | release_5.203 | official Pixel Buds Companion App (version not visible on screen) | W (attempted) | PAIR-001 |
Intended as Group W's stronger GATT cache-busting attempt; actual on-screen procedure was a standard system-Settings forget-and-re-pair on the same Pixel 7a used throughout this project — neither pm clear com.android.bluetooth nor the Pixel 9a was used, so Group W's own method was not actually exercised (CAP-010-FINDINGS.md §1) |
captures/CAP-010-2026-08-16_11-42-31_11-45-01-Group_W/CAP-010-btsnoop_hci.log |
same file | analyzed — see CAP-010-FINDINGS.md in that folder; GATT-001 still unresolved, 4th consecutive negative result (CAP-002, CAP-003, CAP-004, CAP-010): zero Read By Group Type/Find Information traffic despite a genuinely fresh classic bond this time — explained by the procedure gap above, not new evidence against Group W's untried methods; independently reproduces the 0x0f2a/0x0c0X handle cluster's stable numbering and FORM (now a 3rd–4th confirming session), the DLCI 0x08 private handshake incl. release_5.203 (5th confirming session), and the classic fresh-pairing state machine (4th confirming session); new byte-level detail for 0x0c0c (40B notify) and 0x0c13/0x0c14 (9/10/32B, doesn't fit the 0x0c04/0x0c05 AES-block pattern — 🟡 possibly a structurally distinct characteristic) |
CAP-011 |
2026-08-21 | Pixel 7a | 17 (⚪ assumed) | release_5.203 (⚪ assumed) | official Pixel Buds Companion App (open for most of session) | Q (#18) | BATT-002, BATT-003 |
Passive BLE scan intended for case-closed/idle, to capture the Fast Pair Battery Notification advertisement independently of RFCOMM (PROTOCOL.md §4.3 Option A) |
captures/CAP-011-2026-08-21_09-45-17_09-55-16-Group_Q/CAP-011-btsnoop_hci.log |
same file | analyzed — see CAP-011-FINDINGS.md in that folder; procedure deviation: an active classic RFCOMM+GATT connection was present throughout (app left open on "Device details"), not the intended connection-free scan; 0xFE2C Fast Pair Service BLE advertisements confirmed present (🟢 FACT, 634 frames, 5 rotating addresses) but the sampled payloads do not structurally match PROTOCOL.md §4.3 Option A's documented Battery Notification byte layout — recorded as inconclusive, not force-fit; a clean connection-free repeat is still needed. Re-analyzed 2026-08-23 (maintainer spotted a 1% battery drop in the recording): pinpointed the exact change to 09:52:25.8 and found a DLCI 0x08 message (Group 0x0e Code 0x01, cross-confirmed by Group 0x04 Code 0x03) tracking the Left/Right values across 4 occurrences in the log — proposed as PROTOCOL.md §4.3 Option E, 🟡 HYPOTHESIS pending sign-off; see CAP-011-FINDINGS.md §7 |
CAP-012 |
2026-08-26 | Pixel 7a | 17 (⚪ assumed, not visible on screen this capture) | release_5.203 (⚪ assumed — not re-confirmed on the wire this session, log severely ACL-truncated) | uninstalled (GMS disabled) | S (repeat) | GFPS-001, PAIR-001, CASE-003, bonus PAIR-003 (first Pixel 7a occurrence) |
Repeat of Group S following its original system-settings-only procedure exactly (no nRF Connect at any point, independently confirmed both on screen and on the wire — zero BLE connection to the Buds anywhere in the log), to isolate whether CAP-004's Cross-Transport-Key-Derivation bonding result was an artifact of nRF Connect's early BLE connection (CAP-004-FINDINGS.md §8 item 4) |
captures/CAP-012-2026-08-26_15-30-19_15-32-28-Group_S/CAP-012-btsnooz_hci.log (severely ACL-truncated, ~15-byte captured length per data packet — btsnooz-fallback extraction rather than a raw untruncated log; see CAP-012-FINDINGS.md §1) |
same file | analyzed — see CAP-012-FINDINGS.md in that folder; hypothesis confirmed (🟢 FACT): this session uses classic Secure Simple Pairing, not CTKD — CAP-004's CTKD result is isolated to nRF Connect's early BLE connection, not to the GMS-disabled/no-app condition itself (§2/§10 there); GFPS-001's channel-topology result reproduces CAP-004's (DLCI 0x04 never opens) but the payload-content sub-question is inconclusive, not resolved, due to this log's truncation (§1/§4); bonus finding: a manual disconnect/reconnect (PAIR-003) retriggers the full HFP AT-command handshake (§6), narrowing a PROTOCOL.md §6 open question |
CAP-013 |
2026-08-26 | Pixel 7a | 17 (⚪ assumed, not screen-confirmed) | release_5.203 | official Pixel Buds Companion App (version not visible on screen) | A (repeat) | PAIR-001, PAIR-004, incidental BATT-004, APP-001, APP-002 |
Intended: HCI snoop logging started before any prior association with the device exists — resolves whether a clearing action fully clears prior bonding/BLE-association state (CAP-001-FINDINGS.md §6). PROPOSAL — pending maintainer approval: status/row text below proposed, not yet maintainer-approved. |
captures/CAP-013-2026-08-26_17-09-01_17-14-04-Group_A/CAP-013-btsnooz_hci.log (severely ACL-truncated, ~15-byte captured length per data packet — btsnooz-fallback extraction, same issue as CAP-012; see CAP-013-FINDINGS.md §1) |
same file | analyzed — partial: pre-clearing-action window not captured — see CAP-013-FINDINGS.md/CAP-013-EVENT-NOTES.md in that folder. Verified (not assumed) that the intended method wasn't actually followed: the on-screen clearing action was "Reset Bluetooth & Wi-Fi" (not a single-device "Forget"), and the log's first frame (17:11:45.8) starts 2m21s after that action — also after Bluetooth re-enable, case-open, pair-button-press, and device-list-tap, none of which are logged. Primary question (CAP-001-FINDINGS.md §6) remains 🔴 OPEN, not answered. Secondary question (PAIR-004) is answered: 🟢 CONFIRMED fresh classic SSP handshake (full IO-Capability/User-Confirmation/new-Link-Key-Notification sequence, frames 117–270) for the bonding state active when this capture's window began — no key-reuse path observed. Bonus: DLCI 0x02 (libmaestro candidate) opens ~61s after the other 4 channels this session, coinciding with an app-permission "Allow" tap — 🟡 single-sample, not promoted; a second BLE link to an unrelated random address appears mid-session, unattributed (🔴 OPEN). A genuine repeat (logging before the clearing action itself) is still needed — see proposed follow-up capture ID below. |
CAP-014 |
2026-08-27 | Pixel 7a | 17 (⚪ assumed, not re-confirmed on screen this session) | release_5.203 (confirmed on-screen 20:59:07) | nRF Connect for Mobile (Nordic Semiconductor); official Pixel Buds app also surfaced twice, 20:54:39 and 20:58:52–53, unintentionally | W (repeat — PROPOSAL, pending maintainer approval: neither of Group W's own candidate methods was actually used this session either, see deviation note below) | GATT-001, incidental PAIR-001 |
Intended: repeat the CAP-017 nRF-Connect procedure with (1) a fixed/longer HCI snoop snaplen and (2) an on-screen drill-down into "Accessory Non-Owner Service"/"Unknown Service" — (2)'s Accessory-Non-Owner half was correctly not executed, that instruction conflicted with DECISIONS.md ADR-008 (CAP-014-FINDINGS.md §2) |
captures/CAP-014-2026-08-27_20-53-37_20-59-17-Group_W/CAP-014-btsnoop_hci.log |
same file | analyzed — see CAP-014-FINDINGS.md in that folder; PROPOSAL, pending maintainer approval: (1) succeeded — snaplen confirmed fixed, 0 truncated frames out of 4,663 (§0); but GATT-001's handle↔UUID mapping is still 🔴 OPEN — root cause now identified precisely (§4): this session reused the same, already-bonded Pixel 7a with a cached GATT client, so Android served the 0x0c0X/0x0f2X cluster from its cached database instead of re-declaring it on the wire (only the GATT service itself, handles 0x0001–0x0009, was genuinely re-discovered live). Neither Group W Option (a) pm clear com.android.bluetooth nor Option (b) (Pixel 9a) was actually tried — both remain the clearly-identified next step, now combined with a confirmed-working snaplen for the first time. Reproduces 0x0f2a="Revision 6" (3rd session, byte-identical to CAP-002) and 0x0f32=0x64/0x0f33 CCCD (2nd session, byte-identical to CAP-017, narrowing that open question); bonding used Cross-Transport Key Derivation (2nd confirmed instance after CAP-004, consistent with CAP-012's "BLE-tool-first" explanation) |
CAP-015 |
2026-08-18 | Pixel 7a | 17 | release_5.203 | 1.0.955078536 | T | EQP-002, EQS-004 |
Completes/supersedes CAP-005's Group T goal: five distinct EQ presets tapped in sequence, then all five EQ sliders each dragged to both extremes and back near-zero (3 passes), to resolve CAP-005's open field-to-band mapping question |
captures/CAP-015-2026-08-18_06-11-06_06-17-40-Group_T/CAP-015-btsnoop_hci.log |
same file | analyzed — see CAP-015-FINDINGS.md in that folder; field-to-band mapping promoted to 🟢 FACT (field 1↔Low bass, 2↔Bass, 3↔Mid, 4↔Treble, 5↔Upper treble, wire order reversed from on-screen order) — matches CAP-005's single-band inference exactly; also established the ±6.0 band-gain clamp (🟢 FACT, units unconfirmed) and a confirmed preset-quintet reference table |
CAP-016 |
2026-08-18 | Pixel 7a | 17 (build CP2A.260705.006, confirmed on-screen) |
release_5.203 | n/a (Android system Bluetooth "Device details" page, not the companion app) | U | CASE-004, CASE-005, CASE-006, OBS-003 |
Re-run of Group U (distinct session from CAP-007): Bluetooth-on, both buds removed from case one at a time, empty-case lid close/reopen, both buds re-docked, disconnect, lid close — general case/bud-removal correlation rather than Group U's own narrower liveness-bracket procedure (see CAP-016-FINDINGS.md scope note) |
captures/CAP-016-2026-08-18_06-31-31_06-33-58-Group_U/CAP-016-btsnoop_hci.log |
same file | analyzed — see CAP-016-FINDINGS.md in that folder; clean single-attempt, Buds-initiated reconnect on first bud removal (🟢 FACT, contrast CAP-001's 3-attempt phone-initiated connect); ACL disconnects the instant both buds are re-docked, Buds-initiated (reason 0x13), 🟢 FACT; case-lid open/close while buds are elsewhere produces zero wire signal, 🟢 FACT — 2nd independent capture confirming this (CAP-007 §3.4); a full RFCOMM channel bounce (all 4 DLCIs) recurs with no camera-visible trigger this time, ruling out "bud removal is the sole cause" for that class of event (CAP-007 §3.3's hypothesis); ANC-state Notify's "settable-toggles" byte (0x00 vs 0xe8) newly observed to change value, tracking the app's own "no ANC mode selectable" UI state — proposed refinement to PROTOCOL.md's ANC Notify field table, not yet promoted; INEAR-002/INEAR-003/INEAR-004 still not exercised — no earbud is shown inserted into or removed from an ear on camera in this session either |
CAP-017 |
2026-08-16 | Pixel 7a | 17 | release_5.203 (⚪ assumed — not re-confirmed on-the-wire this session, DLCI 0x08 never opened) | nRF Connect for Mobile (Nordic Semiconductor), not the official app | W | GATT-001 |
Second same-day Group W attempt (distinct session from CAP-010's 11:42 attempt), this time driving a brand-new third-party GATT client (nRF Connect) instead of the official app, to force a real cache-miss discovery walk |
captures/CAP-017-2026-08-16_18-30-12_18-37-12-Group_W/CAP-017-btsnoop_hci.log |
same file | analyzed — see CAP-017-FINDINGS.md in that folder; GATT-001's discovery goal achieved for the first time in this project (🟢 FACT) via a third path not previously identified (fresh GATT client UID has no cache to hit) — 137 live Read By Type/Read By Group Type/Find Information frames on the wire (vs. zero in every prior attempt); full 15-service GATT profile recovered from video (incl. Google Fast Pair Service 0xFE2C and two previously-undocumented 128-bit UUIDs); but the wire log itself is severely ACL-truncated (~15B snaplen), so UUID bytes aren't recoverable from the log, and no characteristic-level drill-down happened on screen — confirmed by a dedicated full-video verification pass (§4b) that the handle↔UUID mapping cannot be closed from this capture's artifacts; recapture needed with (1) fixed snaplen and (2) on-screen characteristic drill-down into "Unknown Service" (109b862f-…) — "Accessory Non-Owner Service" is out of scope for any drill-down/read, per DECISIONS.md ADR-008; do not include it in a future session's plan (correction added post-CAP-014, which caught this exact instruction still standing here and had to decline it — see CAP-014-FINDINGS.md §2). CAP-014's repeat (below) fixed the snaplen but did not achieve a genuine cache-miss discovery, so the mapping is still open — see that row |
CAP-018 |
2026-09-12 | Pixel 7a | 17 | release_5.203 (⚪ assumed, carried over) | 1.0.955078536 | Y | GATT-002 |
BLE-only connection isolation for the 0x0044 notification burst (CAP-016-FINDINGS.md §11) — enable Bluetooth and let the BLE link to the already-paired Buds form without touching the buds/case at all, to isolate whether the burst is triggered by BLE link establishment alone, independent of any bud/case action |
captures/CAP-018-2026-09-12_06-07-20_06-09-52-Group_Y/CAP-018-btsnoop_hci.log |
same file | analyzed — see CAP-018-FINDINGS.md in that folder; isolation confirmed (buds/case untouched for the full 152s video), but the burst itself is not attributable to the Buds this session: the only LE connection in the whole log (chandle 0x0004, address distinct from the Buds' classic address) carries a GATT profile matching an unrelated nearby device (a standard Heart Rate service present, no Fast Pair Service/no Buds-known handle cluster) — the burst rides handle 0x0044, inside that connection's own 0x0040–0x0045 "Unknown Service" range, structurally the same unattributed-background-device pattern already seen in CAP-032-FINDINGS.md §5. The Buds' own classic connection carries zero ATT traffic throughout. Bonus: this session's classic reconnect took an unusually long ~64s from Bluetooth-toggle to Create Connection, unexplained |
CAP-019 |
2026-08-21 | Pixel 7a | 17 (⚪ assumed) | release_5.203 (⚪ assumed) | 1.0.955078536 (assumed same as prior sessions) | C | CONV-001, MULTI-001 |
Conversation Detection and Multipoint on/off toggles | captures/CAP-019-2026-08-21_07-35-50_07-39-30-Group_C/CAP-019-btsnoop_hci.log |
same file | analyzed — see CAP-019-FINDINGS.md in that folder; both toggles isolate to a single DLCI 0x02 Sent frame each (🟡 HYPOTHESIS, field5{field4{field22=1}}/field5{field4{field11=1}}); Multipoint additionally triggers a DLCI 0x04 Group 0x07 (SASS) burst including an ASCII "in-use" string — first content-level correlation of SASS traffic to a specific action |
CAP-020 |
2026-08-21 | Pixel 7a | 17 (⚪ assumed) | release_5.203 (⚪ assumed) | 1.0.955078536 (assumed) | F | TOUCH-001, HEAD-001 |
Touch controls and Head gestures top-level on/off toggles; leaves Head gestures ON for CAP-028 (Group O) |
captures/CAP-020-2026-08-21_07-46-14_07-47-49-Group_F/CAP-020-btsnoop_hci.log |
same file | analyzed — see CAP-020-FINDINGS.md in that folder; both toggles isolate to a single DLCI 0x02 Sent frame each (🟡 HYPOTHESIS, field5{field4{field4=1}}/field5{field4{field29=2}}) — first identification of the general-purpose field5{field4{...}} settings-write envelope, reused by every other capture in this batch |
CAP-021 |
2026-08-21 | Pixel 7a | 17 (⚪ assumed) | release_5.203 (⚪ assumed) | 1.0.955078536 (assumed) | G | HOLD-001–HOLD-005 |
Per-earbud press-and-hold configuration and ANC-mode rotation list | captures/CAP-021-2026-08-21_07-59-36_08-07-04-Group_G/CAP-021-btsnoop_hci.log |
same file | analyzed — see CAP-021-FINDINGS.md in that folder; all 4 Left/Right × ANC/Assistant combinations (HOLD-001–HOLD-004) isolate cleanly (🟡 HYPOTHESIS, field5{field4{field7{field1|2{field4=5|6}}}}, field1=Left/field2=Right, 5=ANC/6=Digital assistant); HOLD-005's 16-frame checklist burst confirms field order matches on-screen order but cannot be split between Left's/Right's lists from wire content alone |
CAP-022 |
2026-08-21 | Pixel 7a | 17 (⚪ assumed) | release_5.203 (⚪ assumed) | 1.0.955078536 (assumed) | H | AUDIO-001–AUDIO-003 |
Mono audio, Volume EQ, Volume balance | captures/CAP-022-2026-08-21_08-15-24_08-17-27-Group_H/CAP-022-btsnoop_hci.log |
same file | analyzed — see CAP-022-FINDINGS.md in that folder; all 3 isolate to DLCI 0x02 writes (🟡 HYPOTHESIS: field19=Mono audio, field15=Volume EQ, field17=Volume balance, 7 samples from one continuous drag); Volume balance's persistent-write claim not tested (no disconnect/reconnect this session) |
CAP-023 |
2026-08-21 | Pixel 7a | 17 (⚪ assumed) | release_5.203 | 1.0.955078536 (assumed) | I | FW-001, FW-002 (FW-003/FW-004 not exercised — gap) |
Firmware/device-info screen; resolves PROTOCOL.md §0.1's open wire-baseline-vs-UI-baseline firmware-version question |
captures/CAP-023-2026-08-21_08-23-40_08-25-21-Group_I/CAP-023-btsnoop_hci.log |
same file | analyzed — see CAP-023-FINDINGS.md in that folder; primary goal achieved: on-screen firmware version (release_5.203, all 3 components) matches DLCI 0x08's private-envelope string byte-for-byte, same session — resolves which on-the-wire string the app calls "the firmware version"; FW-001's manual check produces zero wire traffic (🟢 FACT, cached not live-queried); FW-003/FW-004 not visited this session |
CAP-024 |
2026-08-21 | Pixel 7a | 17 (⚪ assumed) | release_5.203 (confirmed same day, CAP-023) |
1.0.955078536 (assumed) | J | INEAR-001, CASE-001, CASE-002 |
In-ear detection toggle and case-sound settings | captures/CAP-024-2026-08-21_08-31-27_08-33-24-Group_J/CAP-024-btsnoop_hci.log |
same file | analyzed — see CAP-024-FINDINGS.md in that folder; all 3 isolate to DLCI 0x02 writes (🟡 HYPOTHESIS: field2=In-ear detection, field28="Bud return"/CASE-001, field27="Other alerts"/CASE-002); no case-specific vs. bud-specific channel distinction found — same shared envelope |
CAP-025 |
2026-08-21 | Pixel 7a | 17 (⚪ assumed) | release_5.203 (⚪ assumed) | 1.0.955078536 (assumed) | K | FIND-001, FIND-002 (FIND-003/FIND-004 attempted, resolve to a different mechanism) |
Find My Buds/Ring, the last remaining fully unattributed app command; PROTOCOL.md §4.4's hypothesis tested |
captures/CAP-025-2026-08-21_08-40-52_08-45-26-Group_K/CAP-025-btsnoop_hci.log |
same file | analyzed — see CAP-025-FINDINGS.md in that folder; FIND-001/FIND-002 strongly confirmed, video-correlated: DLCI 0x04 Group 0x04 Code 0x01, Value=0x01(start Right)/0x02(start Left)/0x00(stop) — matches the spec's worked ACK example exactly; major structural finding: FIND-003(Case)/FIND-004(both) route through a separate Find Hub/Find-My-Device-Network UI flow that produces no local Ring command on the wire — a different, likely GMS/account-mediated mechanism, not a capture gap |
CAP-026 |
2026-09-12 | Pixel 7a | 17 | release_5.203 (⚪ assumed) | 1.0.955078536 | L | BATT-001, OBS-001 |
Passive observation windows (idle-wait-for-reconnect-notification, force-close/reopen) | captures/CAP-026-2026-09-12_06-49-53_06-52-49-Group_L/CAP-026-btsnoop_hci.log |
same file | analyzed — see CAP-026-FINDINGS.md; BATT-001 wire-correlated for the first time: connect-time battery data (DLCI 0x04 Option B candidate + DLCI 0x08 Option E) lands ~12–13s before the on-screen reconnect notification appears — Left/Right match on-screen exactly, Case reads 95 vs. on-screen 93% (a further instance of ADR-014's short/no-flag-form-may-be-stale pattern). OBS-001 clean negative: app force-close+reopen (classic connection never drops) produces zero DLCI 0x02/0x04/0x08 traffic |
CAP-027 |
2026-08-30 | Pixel 7a (⚪ assumed, not screen-confirmed) | 17 (⚪ assumed) | release_5.203 (🟢 confirmed on-wire) | official Pixel Buds Companion App (not screen-confirmed; Spotify used as the music source) | N | TOUCH-002–TOUCH-007 |
Physical touch gestures on the bud hardware; TOUCH-007's behavior depends on the per-earbud mode set in CAP-021 (Group G) |
captures/CAP-027-2026-08-30_15-45-14_15-49-07-Group_N/CAP-027-btsnoop_hci.log |
same file | analyzed — see CAP-027-FINDINGS.md in that folder; structural finding: TOUCH-002–TOUCH-006 ride AVRCP (Pass Through/VolumeChanged), not any RFCOMM DLCI at all — all 13 instances 1:1-correlated to a wire event; TOUCH-007 (press-and-hold) is the exception, confirmed on DLCI 0x04 (official Fast Pair Message Stream, already-FACT "Notify ANC state" 0x13 shape from PROTOCOL.md §4.1), not DLCI 0x02 (libmaestro). Both TOUCH-007 timestamps (previously TBD) recovered this way. Left-bud TOUCH-007 shows two rotation-step Notify frames 5.68s apart, unresolved whether that's one long hold or two presses (🔴 open) |
CAP-028 |
2026-09-12 | Pixel 7a | 17 | release_5.203 (⚪ assumed) | 1.0.955078536 | O | HEAD-002, HEAD-003 |
Physical head gestures; Head gestures already enabled from CAP-020 (Group F) |
captures/CAP-028-2026-09-12_07-16-15_07-17-18-Group_O/CAP-028-btsnoop_hci.log |
same file | analyzed — see CAP-028-FINDINGS.md; camera points at the phone screen, not the user's head, so the gestures themselves are not video-visible. Clean negative on the Buds' own connection: zero DLCI 0x02/0x04/0x08 traffic during the entire claimed gesture window, no AVRCP traffic, no SCO/eSCO call anywhere in the log — consistent with (not distinguishable from) Nod/Shake being functionally inert with no active call/notification. Bonus, third occurrence of a pattern: a second, unrelated LE connection (standard Heart Rate service, no Fast Pair Service) coincidentally overlaps the gesture window with substantial ATT traffic on handle 0x0044/0x0042 — same signature as CAP-018, not Buds-attributable |
CAP-029 |
2026-09-12 | Pixel 7a | 17 | release_5.203 (⚪ assumed) | 1.0.955078536 | P | CONV-002, CASE-007, PAIR-002 (CASE-008 not exercised) |
Conversation Detection voice trigger, factory reset (CASE-007, run this session) and its re-pair (PAIR-002) |
captures/CAP-029-2026-09-12_07-53-53_07-58-05-Group_P/CAP-029-btsnoop_hci.log |
same file | analyzed — see CAP-029-FINDINGS.md; CONV-002 clean negative: media visibly pauses on screen but zero DLCI 0x02/0x04/0x08 traffic accompanies it, no ANC-mode change anywhere pre-reset. CASE-007/PAIR-002 confirmed: reproduces the established factory-reset pattern and a genuine fresh-SSP re-pair. CASE-008 not attempted. Major correction to the draft: the draft's claimed final "forget the device again" did not happen — video and wire both confirm the device stays connected through the end of the session |
CAP-030 |
planned | either phone | TBD | TBD | TBD | Q (items #19–20) | LOUD-001, ADAPT-002 |
Group Q's two remaining items (item #18 already planned separately as CAP-011) — attempt to observe Loud Noise Protection and Adaptive Audio engaging; requires firmware ≥4.467 |
— | — | planned |
CAP-031 |
2026-08-27 | Pixel 7a | 17 (⚪ assumed, not screen-confirmed) | release_5.203 | official Pixel Buds Companion App (version not visible on screen) | A (repeat, 3rd attempt) | PAIR-001, PAIR-004, incidental BATT-004 |
Third attempt at CAP-001-FINDINGS.md §6's original goal — HCI snoop logging started before any prior association with the device exists, this time with a live in-recording file-size-polling check added specifically to avoid CAP-013's failure mode. PROPOSAL — pending maintainer approval: status/row text below proposed, not yet maintainer-approved. |
captures/CAP-031-2026-08-27_06-04-48_06-08-10-Group_A/CAP-031-btsnooz_hci.log (btsnooz-format, inferred 15–126-byte captured length per packet, same truncation issue as CAP-012/CAP-013; see CAP-031-FINDINGS.md §1) |
same file | analyzed — partial: pre-clearing-action window not captured, a second consecutive failure of this method — see CAP-031-FINDINGS.md/CAP-031-EVENT-NOTES.md in that folder. This session used a genuine narrow, per-device "Forget" (screenshot-confirmed, unlike CAP-013's broader reset) and a live snoop-log file-size-polling check during recording, but the log's first frame (06:06:37.16) still starts 66s after the on-screen Forget tap (06:05:31) — also after case-open, pair-button-press, and the entire first "Pair new device" scan attempt, none of which are logged. Primary question (CAP-001-FINDINGS.md §6) remains 🔴 OPEN, not answered, untested a third time. Secondary question (PAIR-004) is reconfirmed: 🟢 CONFIRMED fresh classic SSP handshake (frames 598–689, a sixth confirming instance) — no key-reuse path observed. Bonus, both negative results: CAP-013's DLCI 0x02 ~61s-delay does not reproduce (opens 1.64s after DLCI 0x00, within the initial burst) and CAP-013's unattributed second BLE link does not reproduce (exactly one LE link this session, to the Buds' own public address) — both now look like single-session artifacts. A fourth attempt is still needed, this time verifying snoop-log content freshness (not just file size) before the Forget tap. |
CAP-032 |
2026-08-27 | Pixel 7a | 17 (⚪ assumed, not screen-confirmed) | release_5.203 | official Pixel Buds Companion App (version not visible on screen) | A (repeat, 4th attempt) | PAIR-001, PAIR-004, incidental BATT-004 |
Fourth attempt at CAP-001-FINDINGS.md §6's original goal — this time extracted via the raw BTSnoop file path (CAPTURE_BLUETOOTH_HCI_SNOOP.md §3 step 3) rather than the btsnooz.py fallback used for CAP-012/CAP-013/CAP-031. PROPOSAL — pending maintainer approval: status/row text below proposed, not yet maintainer-approved. |
captures/CAP-032-2026-08-27_18-30-15_18-32-33-Group_A/CAP-032-btsnoop_hci.log (genuine raw, untruncated BTSnoop — frame.cap_len == frame.len for all 2,455 frames, no capinfos-inferred size cap; see CAP-032-FINDINGS.md §0.1) |
same file | analyzed — success: pre-clearing-action window captured for the first time in four attempts — see CAP-032-FINDINGS.md/CAP-032-EVENT-NOTES.md in that folder. The log's first frame (18:29:45.72) starts ~58s before the on-screen Forget tap (18:30:42) and ~30s before the video itself begins. Primary question (CAP-001-FINDINGS.md §6) is now answered for this session: 🟢 no prior BLE link or valid classic link key existed for the Buds anywhere in the covered pre-Forget window (CAP-032-FINDINGS.md §0.3) — this does not reproduce CAP-001's original finding (a clean counter-example, not a contradiction; CAP-001's own session-specific puzzle remains independently open). Secondary question (PAIR-004) reconfirmed: 🟢 CONFIRMED fresh classic SSP handshake (frames 1090–1153, a seventh confirming instance). Bonus: the untruncated log fully decodes DLCI 0x08's battery push ([100,1,1]/[100,1,2]/[57,1,3] = Left/Right/Case, matching the on-screen reading exactly) and firmware/capability-identifier fields; a previously-undocumented vendor-specific HCI command (0xFD57/0x0157, frame 91) embeds the Buds' address 105ms into the log, structurally consistent with bulk bonded-device provisioning at BT-enable time, not a connection — recorded 🔴 OPEN QUESTION, not bearing on the primary question. The btsnooz-vs-raw extraction-path hypothesis (CAP-013-FINDINGS.md §1, CAP-031-FINDINGS.md §1) is supported by this one data point (CAP-032-FINDINGS.md §0.1/§7 Test C), not yet independently isolated. |
CAP-033 |
2026-08-30 | Pixel 7a | TBD | release_5.203 (🟢 confirmed on-wire) | n/a — app force-stopped throughout the entire recorded session | AA | SDP-001, SDP-002(not attempted — no update pending) |
SDP UUID branch isolation for gbm.a()'s "default internal rfcomm socket" path — system-settings-only pairing (app force-stopped) to check whether the pre-app-fetch SDP UUID set ever differs from every existing capture's "pigweed"-only result (REVERSE_ENGINEERING.md's gbm/fzd entries, DECISIONS.md ADR-018) |
captures/CAP-033-2026-08-30_15-17-03_15-19-52-Group_AA/CAP-033-btsnoop_hci.log |
same file | analyzed, isolation not fully clean — see CAP-033-FINDINGS.md §1 ("Forget" preceded Force-stop by ~10s, reverse of procedure, though scoped away from the actual SDP-browse window; Step 3's app-open baseline was never executed at all, on- or off-camera). SDP-001 result capped at 🟡 HYPOTHESIS: "default" UUID still zero occurrences (raw byte scan, both byte orders), "pigweed" UUID confirmed present and named "MAESTRO APP" on-the-wire (SDP service-name string, frame 1279) — first wire-level (not just APK-code) corroboration of DECISIONS.md ADR-018's channel identity. Bonus structural finding: the same SDP response names DLCI 0x08 as "GSND CONTROL" and DLCI 0x0a as "GSND AUDIO" — new, previously-undocumented leads for PROTOCOL.md §2.3's open DLCI-0x08 identity question and CAP-021-FINDINGS.md §4a's unattributed DLCI-0x0a burst, proposed pending maintainer review, not promoted |
CAP-034 |
2026-09-01 | Pixel 9a (GrapheneOS) — never before connected to this Buds unit | TBD | ⚪ assumed release_5.203 (not re-confirmed — no official app used, DLCI 0x08 never opens) |
nRF Connect for Mobile (Nordic Semiconductor); official Pixel Buds Companion App not installed | W (4th attempt) | GATT-001, incidental PAIR-001/PAIR-003/BATT-003 |
4th Group W attempt at the 0x0c0X/0x0f2X GATT handle↔UUID mapping — combines CAP-014's confirmed-unlimited snaplen fix with Group W's own untried cache-busting method (pm clear com.android.bluetooth on a phone never before connected to this Buds unit), the first session to have both at once |
captures/CAP-034-2026-09-01_06-46-31_06-52-45-Group_W/CAP-034-btsnoop_hci.log |
same file | analyzed, ✅ RESOLVED — maintainer sign-off obtained per AGENTS.md §6 — see CAP-034-FINDINGS.md in that folder. Confirmed 0/3,717 truncated frames (max 684B). The sole LE connection (chandle 0x0040) yields one genuine, full 0x0001–0xffff discovery walk (06:47:42.147–45.490) before bonding — the "reconnect" at 06:51:22 is a Database Hash cache-hit only, no second discovery pass. Resolves the full 15-primary-service GATT profile: 0x0c00–0x0c14 = Google Fast Pair Service (0xFE2C) with all 5 spec-defined characteristics (Model ID, Key-based Pairing, Passkey, Account Key, Additional Data) plus Message Stream PSM and one still-unnamed FE2C1238… characteristic; 0x0f20–0x0f2a = Device Information (0x0f28=Serial Number String, 0x0f2a=Firmware Revision String); 0x0f30–0x0f33 = Battery Service (0x0f32=Battery Level). Independently corroborated by nRF Connect's own on-screen UUID rendering and live-verified against the official Fast Pair spec. Corrects CAP-017-FINDINGS.md §6's hypothesis that "Unknown Service" (109b862f-…) might be the 0x0c0X cluster's container — it does not; it occupies a separate range (0x0f37–0x0f3e), own purpose still unidentified. ADR-008 compliance confirmed (Accessory Non-Owner Service appears only in unavoidable discovery inventory, never read/written). See PROTOCOL.md §6 and §4.3 Option D for the promoted findings |
CAP-035 |
2026-09-02 | Pixel 9a (GrapheneOS) | TBD | ⚪ assumed release_5.203 (not re-confirmed — no official app used, DLCI 0x08's firmware string was never independently re-checked) |
none — no Pixel Buds app, no nRF Connect, system Bluetooth settings only | AB | GSND-001, incidental PAIR-001/PAIR-003/BATT-003 |
GMS-independence check for DLCI 0x08 ("GSND CONTROL")/0x0a ("GSND AUDIO")/0x06 ("DEBUG APP")/0x12 ("BTIS"), per CAP-033-FINDINGS.md §3's negative APK-search result (see CAPTURE_BLUETOOTH_HCI_SNOOP.md Group AB) |
captures/CAP-035-2026-09-02_06-50-53_06-57-24-Group_AB/CAP-035-btsnoop_hci.log |
same file | analyzed, maintainer sign-off obtained per AGENTS.md §6 — see CAP-035-FINDINGS.md in that folder. Confirmed 0/1,945 truncated frames. Two videos turned out to be sequential with only a ~7s recording-stop/restart gap (not the ~5min originally logged) — resolved directly from frame comparison, correcting the event timeline including a ~91–97s error in the original disconnect/reconnect time estimates. GMS precondition: com.google.android.gms present but dumpsys-verified disabled (enabled=3 = COMPONENT_ENABLED_STATE_DISABLED_USER) — not genuinely absent, so this is a rigorously verified second data point for CAP-004-FINDINGS.md §4a's existing "GMS present but disabled" finding, not a stronger novel "GMS-absent" confirmation. Result: DLCI 0x08 content reproduces byte-identical twice (connect + reconnect); DLCI 0x0a opens in lockstep both times but carries zero payload; DLCI 0x06/0x12 never open at all — clean negatives for both, first time either has been specifically checked. ADR-008 compliant. Still open: a repeat with GMS genuinely uninstalled, for full closure of the OS-stack-vs-GMS question |
CAP-036 |
2026-09-04 | Pixel 7a | 14 | ⚪ assumed release_5.203 (carried over, not re-checked on-screen this session) |
1.0.955078535 | AC | OBS-004, incidental PAIR-003 |
Settings-state read-back isolation — official app + GMS enabled (normal baseline, the opposite of Group S/AB), idle-only observation windows (reconnect, EQ screen, Controls-and-gestures/Touch-controls screens, Multipoint screen) with no setting touched at any point, to isolate whether the official app ever issues a state query (as opposed to the write-direction-only evidence in PROTOCOL.md §4.2/§4.5) before any user-initiated change — the requirement ARCHITECTURE.md §3.1 imposes on this project's own app. A clean negative is a real result here, not a failed session |
captures/CAP-036-2026-09-04_06-35-58_06-41-18-Group_AC/CAP-036-btsnoop_hci.log (raw path, confirmed untruncated: 0/2,492 mismatched frames, no snaplen cap) |
same file | analyzed — see CAP-036-FINDINGS.md in that folder. Split outcome, by trigger: on reconnection, DLCI 0x04's spec-documented but never-before-observed "Get ANC state" (0x11) query fires once, 34ms after the channel opens, answered ~10.7ms later by "Notify ANC state" (0x13, current=Off, matching the on-screen state) — 🟡 HYPOTHESIS pending replication, proposed (not self-promoted) for PROTOCOL.md §4.1. On settings-screen-open (EQ, Controls-and-gestures, Touch controls, More settings, Multipoint — five clean, video-verified idle windows), zero query or write traffic occurs on DLCI 0x02/0x04/0x08 — a citable negative result. A frame-by-frame video re-pass corrected several of this session's own placeholder timestamps by 5–30 seconds against what the video actually shows (see CAP-036-FINDINGS.md §2). Three new 🔴 open questions raised (DLCI 0x08 Get-shaped connect-time frames with unmapped Group/Code pairs; an undecoded DLCI 0x02 connection-settling RPC burst; a Settable toggles byte discrepancy between this session's Notify frame and previously-documented Set frames) |
CAP-037 |
2026-09-06 | Pixel 7a | 14 (⚪ assumed, carried from CAP-036) |
⚪ assumed release_5.203 |
1.0.955078536 (⚪ assumed) | AD | OBS-004, incidental PAIR-003 |
Purpose-built repeat of the "Get ANC state" reconnect-reliability + dock-state-transition question — planned as 5 isolated reconnects, alternating docked/undocked | captures/CAP-037-2026-09-06_06-11-46_06-29-38-Group_AD/CAP-037-btsnoop_hci.log |
same file, raw path, 0/29,956 truncated | analyzed — see CAP-037-FINDINGS.md. Procedure ran far longer than planned: 34 reconnects over ~20 minutes (not 5), 26 with real DLCI 0x04 payload — all 26 produce a 08 11/08 13 Get/Notify pair, zero misses, the largest single-session replication of DECISIONS.md ADR-022 on file. All 26 Settable-toggles samples (16 docked=0x00, 10 undocked=0xe8) match video-confirmed dock state, reconfirming ADR-024; a perfect Settable↔Current-state co-occurrence pattern proposed as a new 🟡 HYPOTHESIS. One 🔴 open anomaly: a mid-connection Settable flip with no preceding Get on one chandle |
CAP-038 |
2026-09-06 | Pixel 7a | 14 (⚪ assumed) | ⚪ assumed release_5.203 |
1.0.955078536 (⚪ assumed) | AE | OBS-005, incidental PAIR-003, INEAR-family |
Realistic physical reconnect trigger (Buds removed from case and worn) vs. CAP-036/CAP-037's OS-toggle-only trigger |
captures/CAP-038-2026-09-06_06-49-50_06-53-57-Group_AE/CAP-038-btsnoop_hci.log |
same file, raw path, 0/4,426 truncated | analyzed — see CAP-038-FINDINGS.md. Video (247.07s) ends ~2m10s before the log; the documented "Window 2" (case-lid-only trigger) has zero video or wire evidence — not verifiably executed, a capture gap not a result. New finding: on the first (most "realistic") reconnect, the Fast Pair Message Stream/libmaestro channels opened under session-local DLCI numbers 0x05/0x03 instead of the usual 0x04/0x02 (consistent with CAP-001-FINDINGS.md §2's established session-local-numbering finding, not a new anomaly) — but that same reconnect's "Notify ANC state" frame read Settable-toggles=0x00 (docked) immediately after physical removal from the case, an unreconciled 🔴 tension with ADR-024. Two ANC Notify frames occur with no Get and no Set anywhere in the log, plausibly a hardware press-and-hold gesture (matches CAP-027-FINDINGS.md §4's mechanism). No A2DP/AVDTP signaling found. Settable-toggles again tracks dock state correctly (reconfirms ADR-024) |
CAP-039 |
2026-09-06 | Pixel 7a | 14 (⚪ assumed) | ⚪ assumed release_5.203 |
1.0.955078536 (⚪ assumed) | AF | OBS-006, incidental ANC-family, PAIR-003 |
Same-session comparison of the "Notify ANC state" Settable-toggles byte after a Set vs. a Get, dock state held constant |
captures/CAP-039-2026-09-06_07-07-17_07-11-43-Group_AF/CAP-039-btsnoop_hci.log |
same file, raw path, 0/6,184 truncated | analyzed — see CAP-039-FINDINGS.md. The documented "one Set, one Get" plan did not occur: video/wire re-pass found 6 reconnects and 4 ANC taps (alternating Noise cancellation↔Adaptive, never "Off") instead. All 10 same-session Set/Get Settable-toggles samples read 0xe8 (undocked, constant) — a strong, zero-counter-example same-session confirmation of DECISIONS.md ADR-024's trigger-independence. Bonus: DLCI 0x04 Group 0x03 Code 0x03 reproduces the existing battery-code candidate with a live 98→97% Left transition |
CAP-040 |
2026-09-06 | Pixel 7a | 14 (⚪ assumed) | ⚪ assumed release_5.203 |
1.0.955078536 (⚪ assumed) | AG | PRIV-001, incidental PAIR-003, BATT-family |
DLCI 0x08's unmapped zero-length Get-shaped codes decoded via correlation against a known-changing value — executed as ~15 repeats of a docked/undocked cycle using the app's own Connect/Disconnect buttons | captures/CAP-040-2026-09-06_07-28-00_07-57-05-Group_AG/CAP-040-btsnoop_hci.log (its .log.last is not part of this session — see below) |
main file only, raw path, 0/8,539 truncated | analyzed — see CAP-040-FINDINGS.md. Integrity correction: CAP-040-btsnoop_hci.log.last is leftover on-device buffer content from CAP-039's own session plus the idle gap before CAP-040, not an intra-session rotation — only the main log is this session's own evidence (mirrors the same pattern independently found for CAP-042/CAP-041, below). Major finding: the app's own in-app "Connect"/"Disconnect" buttons produce zero wire-visible signal (no ACL teardown, no DLCI bounce) across ~15 taps — invalidating the session's own dock/undock bracketing for PRIV-001's intended correlation test (DLCI 0x08 opens exactly once, so all 7 flagged codes have N=1). Bonus, in the video's own previously-unreviewed tail: a genuine real reconnect cluster and a 🟡 HYPOTHESIS that the 0xff battery sentinel appears momentarily at the instant of physical docking |
CAP-041 |
2026-09-06 | Pixel 7a | 14 (⚪ assumed) | 🟢 confirmed release_5.203 (on-screen, 17:11:32) |
1.0.955078536 (⚪ assumed) | AH | OBS-007, incidental PAIR-003 |
DLCI 0x02's connect-time RPC burst captured with deliberately non-default EQ/touch-controls settings, diffed against CAP-036's own default-settings burst |
captures/CAP-041-2026-09-06_17-10-39_17-17-48-Group_AH/CAP-041-btsnoop_hci.log |
same file, raw path, 0/4,003 truncated | analyzed — see CAP-041-FINDINGS.md. Settings were changed continuously through the session instead of once beforehand, producing 3 wire-confirmed reconnects (not the planned 4) each against its own distinct, non-default settings state. The connect-time burst's length/shape signature is essentially invariant across all 3 states and CAP-036's default baseline — a scoped negative for a libmaestro-side settings read-back at the length level (full byte-for-byte content diff not completed, flagged as a follow-up). Bonus: a recurring 2-field sub-message inside the burst holds a constant value matching on-screen Case% throughout — consistent with, not confirming, CAP-036-FINDINGS.md §12.6's open item |
CAP-042 |
2026-09-06 | Pixel 7a | ⚪ assumed (carried from CAP-036, not visible — screen off) |
⚪ assumed release_5.203 |
n/a (backgrounded) | AI | OBS-002 |
Long, genuinely idle bracket to characterize the periodic DLCI 0x02/0x04/0x08/HFP cross-channel push cadence, app backgrounded | captures/CAP-042-2026-09-06_17-30-02_18-07-42-Group_AI/CAP-042-btsnoop_hci.log (its .log.last is not part of this session — see below) |
main file only, raw path, 0/8,560 truncated | analyzed — see CAP-042-FINDINGS.md. Integrity correction: CAP-042-btsnoop_hci.log.last reproduces CAP-041's own three Connection Complete events verbatim and simply continues through the idle gap before CAP-042 — it is not this session's own data, contrary to this task's own initial "confirmed rotated" framing. True session duration is ~37m39s (main log only), not ~55m45s. Central finding: the periodic push recurs only twice in the whole session (~16m and ~35m in, far sparser than CAP-036's short-session sample), and HFP's AT+BIEV drops out of the previously-documented 4-channel near-lockstep sync entirely — DLCI 0x02/0x04/0x08 keep their own mutual sync. Proposed 🔴 open question: an app-foreground-driven trigger, not a Buds-autonomous mechanism. Also revises CAP-027-FINDINGS.md's "streaming-specific" sync-breakdown framing — idle sessions desync too |
CAP-043 |
2026-09-13 | Pixel 7a | 17 | release_5.203 (⚪ assumed, maintainer-supplied context) | 1.0.955078536 (⚪ assumed, maintainer-supplied context) | Q (repeat) | BATT-002, BATT-003 |
Repeat of CAP-011's Battery Notification BLE scan, this time with the official app force-stopped and no active classic RFCOMM+GATT connection throughout the entire capture — closes CAP-011-FINDINGS.md §4's open item either way |
captures/CAP-043-2026-09-13_09-50-31_09-53-41-Group_Q/CAP-043-btsnoop_hci.log |
same file | analyzed — see CAP-043-FINDINGS.md in that folder; isolation genuinely clean this time (zero classic/RFCOMM/SDP traffic to the Buds' address across the entire 306.9s log, confirmed by both dissector filter and raw-byte scan); own-Buds 0xFE2C advertisement (RSSI -20..-23 dBm, 60 occurrences, byte-identical payload throughout) still does not structurally match PROTOCOL.md §4.3 Option A's documented Battery Notification layout (first byte 0x10, not 0x00; no 0x33/0x34 marker at any offset) — a second confirmed non-match, this time ruling out "active connection suppresses the advertisement" as an explanation; BATT-003 not exercised (no value change occurred this session by design) |
CAP-044 |
2026-09-13 | Pixel 7a | 17 | release_5.203 (🟢 confirmed on-wire) | 1.0.955078536 (⚪ assumed, maintainer-supplied context) | AA (repeat, 2nd attempt) | SDP-001, SDP-002 (opportunistic, not attempted) |
Repeat of CAP-033's SDP UUID branch isolation, this time with Force-stop strictly preceding Forget and step 3's app-open baseline browse actually executed |
captures/CAP-044-2026-09-13_13-05-51_13-11-07-Group_AA/CAP-044-btsnoop_hci.log |
same file | analyzed — see CAP-044-FINDINGS.md in that folder; isolation partially achieved, for a different reason than CAP-033: Force-stop does precede Forget this time, but no Force-stop is confirmed covering the 2m8s window between the in-app Forget and the actual "Pair" tap (13:08:07, corrected from the draft's assumed 13:07:00) that triggers the one-and-only SDP browse in this log; step 3 (app-open) is confirmed executed on-camera but produces no second SDP transaction since the classic connection/RFCOMM channels never close between the initial pairing and the later app reopen. "Default" UUID still absent (5th consecutive negative); "MAESTRO APP"/"GSND CONTROL"/"GSND AUDIO"/"GFPS RFCOMM" channel-name↔UUID↔RFCOMM-channel mappings from CAP-033 all reproduce byte-for-byte via this session's targeted per-UUID SDP queries (a different query style than CAP-033's single generic wildcard browse) |
CAP-045 |
2026-09-12 | Pixel 7a | 17 | release_5.203 (⚪ assumed) | 1.0.955078536 | AJ (new) | HOLD-005 (not actually exercised), TOUCH-007 (incidental) |
Intended: HOLD-005 Left/Right ANC-rotation-checklist split |
captures/CAP-045-2026-09-12_08-22-51_08-24-32-Group_AJ/CAP-045-btsnoop_hci.log |
same file | analyzed — see CAP-045-FINDINGS.md; the planned procedure was not run: the checklist screen was never opened — instead the maintainer physically long-pressed each earbud to cycle ANC mode (TOUCH-007's already-documented Notify-without-Set mechanism, CAP-027-FINDINGS.md §4). Zero rotation-checklist (qhr field 12) writes anywhere in the log. HOLD-005's own Left/Right question remains open; a genuine re-run is still needed. Bonus: 10-sample replication confirms the Notify frame structurally has no Left/Right field — side is only inferable from timing/video |
CAP-046 |
2026-09-12 | Pixel 7a | 17 | release_5.203 (⚪ assumed) | 1.0.955078536 | AK (new) | AUDIO-003 |
Volume balance (field 17) scale/direction — isolated extreme-position samples with tight video correlation |
captures/CAP-046-2026-09-12_17-02-59_17-05-49-Group_AK/CAP-046-btsnoop_hci.log |
same file | analyzed — see CAP-046-FINDINGS.md; polarity resolved, opposite of the working assumption: field17=+100=Left, field17=-100=Right (3/3 video-confirmed), range exactly ±100. Only 2 of the draft's claimed 4 samples actually occurred — no intermediate-position data exists. Bonus: field19 (Mono audio) fires in exact lockstep (8/8) with every Balance-extreme/center transition |
CAP-047 |
2026-09-14 | Pixel 7a | 17 | release_5.203 |
1.0.955078536 | AL (new) | none existing — a candidate Test-ID (area CASE) is proposed in CAP-047-FINDINGS.md §9 item 4 for "swapped-slot (mismatched L/R) docking", awaiting maintainer sign-off |
Purpose-built hypothesis test for the CAP-021 DLCI 0x0a burst trigger — only Trigger candidate 3 (charge-state change) run this session, across two recordings due to a repeated swapped-slot (not corrected) docking attempt; Trigger candidates 1/2 not attempted |
captures/CAP-047-2026-09-14_05-51-38_06-35-30-Group_AL/CAP-047-btsnoop_hci.log (Recording 1) + CAP-047-btsnoop_hci-2.log (Recording 2); its .log.last is not part of this session — see below |
same files, raw path, 0/3,250 and 0/4,716 truncated | analyzed — see CAP-047-FINDINGS.md. Integrity correction: CAP-047-btsnoop_hci.log.last is leftover pre-session (logging-setup) buffer content, not part of this session — only the two named logs above are this session's own evidence (mirrors the CAP-040/CAP-042 pattern, not CAP-049's). Trigger 3: clean, complete negative — zero DLCI 0x0a-role payload across six bracketed charge-state transitions in two full, untruncated logs (raises the burst's "1 of N" denominator to at least 20 sessions checked). Major bonus finding: a dense video re-check (1fps + 4fps, cross-validated against the phone's own per-earbud charging-icon indicator) confirms no corrected (matching-slot) docking ever occurred in either recording, contrary to a maintainer recollection. Further bonus findings: DECISIONS.md ADR-024's dock-state byte reads "both docked" during a swapped-slot seating (confirms it doesn't check slot correctness); Recording 1's swapped dock never disconnects the ACL link while both of Recording 2's swapped docks do, despite an identical dock-sensor reading — an unreconciled tension with ADR-016, flagged for maintainer review; two new ADR-024 counter-example readings found, alongside CAP-048-FINDINGS.md's existing ones |
CAP-048 |
2026-09-12 | Pixel 7a | 17 | release_5.203 (⚪ assumed) | 1.0.955078536 | AD (repeat) | OBS-004 |
Repeat of CAP-037's dock-state anomaly with an open ACL, with continuous physical dock-state video |
captures/CAP-048-2026-09-12_17-41-41_17-52-44-Group_AD/CAP-048-btsnoop_hci.log (.log.last present, determined leftover pre-session content, not used) |
same file | analyzed — see CAP-048-FINDINGS.md. CAP-037's own flagged anomaly directly resolved: a mid-connection Settable-toggles flip with no preceding Get is confirmed, via video at the exact wire timestamp, to be a genuine real-time docking action — consistent with ADR-016 (disconnect follows once the second bud docks too). 13/13 zero-miss ADR-022 replication. A genuine, video-confirmed counter-example to a simple ADR-024 reading: two fresh reconnects report "docked" while the case is visibly empty on video — reported plainly, not reconciled away. The draft's own claimed 25-reconnect rapid-alternation table does not survive verification — actual cadence was much slower/less regular. Bonus: an unexplained 7-event connection-retry burst, plausibly linked to a closed case |
CAP-049 |
2026-09-12 | Pixel 7a | 17 | release_5.203 (⚪ assumed) | 1.0.955078536 | AF (repeat) | OBS-006 |
Repeat of CAP-039's unexplained disconnect/reconnect cycling, with continuous phone-screen recording |
captures/CAP-049-2026-09-12_18-13-54_18-21-49-Group_AF/CAP-049-btsnoop_hci-combined.log (genuine same-session .log.last rotation, safely combined per this session's own Step G method — see CAP-049-FINDINGS.md §0; video renamed from -recordings.mp4) |
combined file (main+.log.last) |
analyzed — see CAP-049-FINDINGS.md. Clean negative: the cycling did not reproduce — after one deliberate reconnect, the classic connection stays open and stable for the rest of the ~9-minute session (RFCOMM traffic still flowing near the log's own end). OBS-006's Set-vs-Get Settable-toggles comparison confirmed consistent (both 0xe8), a further same-session ADR-024 confirmation |
CAP-050 |
2026-09-14 | Pixel 7a | 17 (⚪ assumed, carried over) | ⚪ assumed release_5.203 (carried over) |
1.0.955078536 (⚪ assumed) | AG (repeat) | PRIV-001 |
Repeat of CAP-040's DLCI 0x08 unmapped Get-shaped codes correlation, this time using a trigger confirmed to actually reopen DLCI 0x08 (OS Bluetooth toggle or physical dock/undock), not the app's own Connect/Disconnect buttons |
captures/CAP-050-2026-09-14_21-01-01_21-13-30-Group_AG/CAP-050-btsnoop_hci.log |
same file, raw path, 0/20,060 truncated | analyzed — see CAP-050-FINDINGS.md. Session-local DLCI reassignment confirmed a second time (one reconnect placed HFP, not the private envelope, on DLCI 0x08) — the private envelope was re-identified per reconnect by content signature, giving 15 confirmed + 1 inconclusive channel opens (not the video's own "well over 20" in-app-label estimate). All 7 PRIV-001-flagged codes fire on every private-envelope (re)open (14 full samples, vs. CAP-040's N=1); 5 of 7 resolve to "not a match"/"no candidate found" (constant neighbors), 2 (04 04/04 15) remain inconclusive — their neighbors (04.05/04.16) fluctuate near dock-state changes but don't reproduce for the same physical configuration across reconnects. Both of CAP-050-EVENT-NOTES.md's own flagged ambiguous dock-state windows resolved via wire+video correlation; one genuine Settable-toggles stale-reading counter-example found (consistent with CAP-048-FINDINGS.md §5's already-documented pattern, not a new contradiction of DECISIONS.md ADR-024). No clear video evidence of the maintainer-recalled mis-docking event at the review densities applied |
CAP-051 |
2026-09-14 | Pixel 7a | 17 (⚪ assumed, carried over) | ⚪ assumed release_5.203 (carried over) |
1.0.955078536 (⚪ assumed) | AM (new) | none existing — a candidate Test-ID (ANC-005) is proposed in CAP-051-FINDINGS.md §5 item 4, awaiting maintainer sign-off; incidental ANC-family, TOUCH-007 |
qhr field 13 ANC-parallel-path wire confirmation — isolated in-app tap and isolated physical press-and-hold gesture, checking whether either correlates with a DLCI 0x02 field13 write alongside DLCI 0x04's confirmed path |
captures/CAP-051-2026-09-14_21-42-55_21-44-09-Group_AM/CAP-051-btsnoop_hci.log |
same file, raw path (confirmed, not just claimed), 0/2,457 truncated | analyzed — see CAP-051-FINDINGS.md. All four ANC-mode actions confirmed against DLCI 0x04's already-FACT path: the in-app tap produces a genuine Set(0x12)+ACK+Notify sequence; all three physical press-and-hold gestures produce only a spontaneous Notify(0x13) with no preceding Set/Get anywhere in the log — directly confirming CAP-038-FINDINGS.md §5's previously-unconfirmed "physical gesture" hypothesis for its own Get-less/Set-less Notify frames. Central Group AM question: clean, confirmed negative — no field5{field4{field13=N}} write (or any DLCI 0x02 Sent-direction payload at all) appears on DLCI 0x02 for any of the four actions, contrasting with REVERSE_ENGINEERING.md's own qhr-entry static-analysis finding (a real, compiled write path exists for both triggers) — not contradicted, but not observed to fire in this session |
CAP-052 |
planned | Pixel 7a | TBD | TBD | TBD | AN (new) | none existing — flagged as a TESTPLAN_BLUETOOTH_HCI_SNOOP.md follow-up, not assigned here (see CAP-052-EVENT-NOTES.md) |
CAP-041 Case%-change bracket — ≥30-minute session with a genuine Case battery charge/discharge, to test whether DLCI 0x02's recurring 2-field sub-message actually tracks Case% or merely coincided with a constant value |
— | — | planned |
CAP-053 |
planned | Pixel 7a | TBD | TBD | TBD | AO (new) | EQS-004, EQP-008 |
EQ outer field 16-vs-18 — drag-and-release without ever tapping Save, isolated from the newly-found navigate-away trigger too, added ai-sessions/0017 |
— | — | planned |
CAP-054 |
planned | Pixel 7a | TBD | TBD | TBD | AP (new) | BATT-002, BATT-003 |
Connection-free Battery Notification scan bracketing a single-bud insertion/removal event (per the Fast Pair spec's own "optional" trigger condition), added ai-sessions/0017 |
— | — | planned |
CAP-055 |
planned | Pixel 7a | TBD | TBD | TBD | AQ (new) | HEAD-002, HEAD-003 |
Nod/Shake head gestures performed while an actual incoming call/notification is active, camera angled to also capture the gesture itself, added ai-sessions/0017 |
— | — | planned |
CAP-056 |
planned | Pixel 7a | TBD | TBD | TBD | AR (new) | HOLD-005 |
Genuine re-run of the ANC-rotation-checklist Left/Right split (CAP-045 never opened the checklist screen), with a mandatory on-camera anti-repeat safeguard, added ai-sessions/0017 |
— | — | planned |
CAP-057 |
planned | Pixel 7a | TBD | TBD | TBD | AS (new) | FW-002, FW-003 (incidental) |
Live GetSoftwareInfo/GetHardwareInfo correlation against the DLCI 0x02 connect-time burst's 3-string sub-message, following this session's structural finding that it matches qjm/qjr (GetHardwareInfo) better than qie (GetSoftwareInfo), added ai-sessions/0017 |
— | — | planned |
Column notes:
- Phone —
Pixel 7a(primary, official app) orPixel 9a(secondary, GrapheneOS), per the two-device setup described at the top of this document. - Group(s) — the letter(s) from §4 (Z, A–Q, R, S — Z, R, and S are special-purpose groups that intentionally sort outside the A–Q run-through: Z is pipeline-validation, always done first; R is the occasional forced-GATT-discovery procedure; S is the occasional GMS-disabled/no-app procedure — both R and S are done only when needed) covered in this session; one bugreport pull can cover several groups if captured as one continuous logging session (§4.1).
- Test(s) — the
TESTPLAN_BLUETOOTH_HCI_SNOOP.mdTest-ID(s) actually exercised in this session (e.g.ANC-001, ANC-002) — this is what aCAP-NNN-FINDINGS.mdfinding should ultimately trace back through: finding → this row's capture ID + frame number → the Test-ID here → the catalog entry inTESTPLAN_BLUETOOTH_HCI_SNOOP.md. Usually a subset of everything the Group(s) column's scenario covers, since not every attempt succeeds cleanly. - Status — one of:
planned(added 2026-08-14 — a specified but not yet run session, per its own capture-scenario paragraph in §4; every other column staysTBD/—until the session actually happens),captured(extracted, not yet reviewed),analyzed(reviewed in Wireshark per §5, findings recorded per §8),promoted(a finding from this capture has been written intoPROTOCOL.mdwith a[VERIFIED-LOCAL]tag),discarded(unusable — note why, e.g. in an extra remarks column if needed). - Reference a capture from
PROTOCOL.mdby its ID (e.g. "confirmed inCAP-001, frame 214") rather than by date or description, so the reference survives even if this row's description is later edited.
https://github.com/tedsluis/opencontrolpixelbudspro2/blob/main/CAPTURE_BLUETOOTH_HCI_SNOOP.md - https://tedsluis.github.io/opencontrolpixelbudspro2/CAPTURE_BLUETOOTH_HCI_SNOOP