LiteJam Bluetooth LE Protocol Capture and Analysis
Getting started: tooling, capture methods, and interpretation of iPhone-to-guitar traffic
Project: LiteJam guitar client software and firmware
Document date: September 1, 2026
Analysis host: Windows 11, C:\Users\david\litejam
Link under study: Bluetooth LE, iOS companion app to LiteJam guitar
Executive summary
The LiteJam guitar is a Bluetooth LE peripheral controlled by an iOS application. The objective is to characterize its GATT interface through ordinary observation of communication with the user's own guitar, supplementing the interface already visible in LiteJam's public web clients.
Provenance and scope: This guide concerns standard Bluetooth diagnostics on hardware and accounts owned by the user, together with materials LiteJam made publicly available. It does not require bypassing encryption, authentication, access controls, firmware protection, device security, or any private system.
Two standard diagnostic methods are available, and they see different things. iOS HCI logging is the primary source, because the operating system records the user's own phone-to-guitar traffic at the host-controller interface. The nRF52840 BLE monitor is the corroborating source, providing a view of advertising payloads, connection parameters, link-layer control, and the pairing exchange for the user's own devices.
The controlling qualification is that neither method is passive and free. The RF sniffer can only follow a connection whose setup it witnessed; if it is not already locked onto the guitar when the iPhone connects, the resulting capture is an undecodable encrypted stream. The iOS method has no such race, but it is batch-only: each iteration costs a sysdiagnose and roughly ten minutes of turnaround, and the log contains every radio event on the phone, not just the guitar's.
The practical consequence is that capture order matters. Enumerate the GATT tree from the phone first, with no capture running at all. That map — handles, UUIDs, and properties — is what makes every subsequent packet trace readable, and it costs ten minutes with a free app.
1. The link under investigation
Bluetooth LE is a layered stack, and the two capture tools tap it at different points. Understanding which layer each tool sees is the difference between a productive capture and an unreadable one.
The iPhone is the central: it scans, initiates the connection, and drives the exchange. The LiteJam guitar is the peripheral: it advertises, accepts the connection, and exposes its functionality as a GATT server. All application-level traffic is therefore GATT operations carried over the Attribute Protocol (ATT), which rides on L2CAP, which rides on the link layer.
| Layer | What it carries | nRF52840 RF sniffer | iOS HCI log (.pklg) |
|---|---|---|---|
| Physical / link layer | Advertising packets, connection events, channel hopping, retransmissions | Yes | No |
| Link-layer control | Connection parameter updates, PHY changes, encryption start | Yes | Partially, as HCI events |
| SMP (pairing) | Key exchange, bonding | Yes, if the pairing is captured | Yes |
| L2CAP | Channel framing, fragmentation | Yes, when the monitored session is readable | Yes |
| ATT / GATT | Reads, writes, notifications — the actual protocol | Only when the monitored session is readable | Yes, as logged by the user's own phone |
The asymmetry in the last two rows is the whole argument for using both tools. The BLE monitor sees more of the radio stack, while the operating-system HCI log records the user's own phone-to-guitar traffic in the form available to the phone itself.
2. Tooling installed on this machine
The following is installed and verified working as of the document date.
| Component | Version | Location | Purpose |
|---|---|---|---|
| Wireshark | 4.6.8 | C:\Program Files\Wireshark |
Analysis of both capture types |
| nRF Sniffer for BLE extcap | 4.1.1 | %APPDATA%\Wireshark\extcap\ |
Live RF capture into Wireshark |
| Sniffer Wireshark profile | — | %APPDATA%\Wireshark\profiles\ |
Sniffer-specific columns and coloring |
| nRF Connect for Desktop | 5.4.0 | %LOCALAPPDATA%\Programs\nrfconnect |
Host for the Programmer app |
| nrfutil | 8.2.1 | %LOCALAPPDATA%\Microsoft\WinGet\Packages\ |
Device discovery, DFU packaging |
| Dongle firmware | sniffer 4.1.1 .hex |
tools/nrf_sniffer_ble_4.1.1/hex/ |
Sniffer image for the nRF52840 |
| Python | 3.13.2, pyserial 3.5, psutil 7.2.2 | user install | Runs the extcap plugin |
Two verification results are worth recording, because they were checked rather than assumed. Wireshark's libwiretap.dll contains the Apple PacketLogger reader and libwireshark.dll contains the matching dissector, so iOS .pklg files open natively on this Windows machine — a Mac is required only for live streaming, never for analysis. Separately, an actual extcap capture was run and produced a valid pcapng with no Npcap installed, confirming that the wpcap.dll warning Wireshark prints at startup is benign for this workflow.
Setup and flashing steps live in tools/SNIFFER_SETUP.md. The Programmer app inside nRF Connect for Desktop still needs its one-click install before the dongle can be flashed.
3. Order of operations
Each step below produces the interpretive key for the step after it. Working out of order wastes capture iterations.
- Enumerate the GATT tree from the iPhone. No dongle, no capture, ten minutes. Produces the handle-and-UUID map.
- Record the user's own session with iOS HCI logging. Produces the command and response bytes as logged by the phone, read against the map from step 1.
- Capture RF with the nRF52840. Produces advertising data, connection setup, and link-layer behavior that step 2 cannot show.
- Static analysis of the official IPA. Optional, but frequently the fastest route to opcode constants and UUID strings. Not yet set up on this machine.
4. Step 1 — Enumerate the GATT tree
Install nRF Connect for Mobile (free, App Store) on the iPhone. Connect to the LiteJam and let it walk the attribute database. Record what it reports.
What this yields:
- Every service UUID, including vendor-specific 128-bit UUIDs.
- Every characteristic UUID and its handle — the small integer that identifies it on the wire.
- Each characteristic's properties: read, write, write-without-response, notify, indicate.
- Descriptors, particularly the Client Characteristic Configuration Descriptor (CCCD, UUID
0x2902), whose value determines whether notifications are enabled.
Record it in this form, because captures reference handles, not UUIDs:
| Handle | UUID | Properties | Working hypothesis |
|---|---|---|---|
0x000e |
6E400002-... |
write-without-response | Command channel, phone to guitar |
0x0010 |
6E400003-... |
notify | Status channel, guitar to phone |
The properties alone constrain the protocol before a single packet is captured. A write-without-response characteristic paired with a notify characteristic is a bidirectional pipe, not a set of individually meaningful attributes — the real protocol will be framed inside those payloads. A characteristic that is read-only and small is more likely to be a version, serial number, or battery value.
What this step cannot tell you: what the official app actually writes. Enumeration reveals the shape of the interface, not its use. That requires capture.
5. Step 2 — Operating-system capture via iOS HCI logging
This is the primary diagnostic source. HCI logging is an Apple-provided facility that records the user's own Bluetooth session at the boundary between the phone's host stack and controller.
5.1 Procedure
- Install Apple's Bluetooth logging profile on the iPhone from the Apple profiles and logs page. A free Apple ID is sufficient. Install it under Settings, General, VPN & Device Management.
- Run the LiteJam session to be captured. Keep it short and deliberate.
- Trigger a sysdiagnose: hold Volume Up, Volume Down, and the Side button together for about 1.5 seconds, until the phone vibrates. Wait roughly ten minutes for generation to complete.
- Retrieve it under Settings, Privacy & Security, Analytics & Improvements, Analytics Data. Find
sysdiagnose_<date>.tar.gzand share it to this PC. - Extract the archive. The
bluetooth/directory contains.pklgfiles. Open them directly in Wireshark.
5.2 Capture hygiene
The log covers every Bluetooth event on the phone across the whole logging window, so discipline at capture time saves hours at analysis time.
- Exercise one behavior per session. Connect, perform a single action, disconnect.
- Note the wall-clock time of each action to the second. This is the only reliable way to correlate a UI event with a packet later.
- Repeat each action at least twice. Differential comparison of two instances of the same action is the single most effective technique for separating fixed opcodes from variable fields.
5.3 Limits
The profile expires after a few days and does not survive a reboot, so it must be reinstalled before each capture session; a profile that has silently expired produces an empty bluetooth/ directory, which is the usual explanation for a capture that appears to have failed for no reason. The method is also strictly batch — there is no live view, and a sysdiagnose triggered too long after the event of interest may find the relevant window already rotated out of the log.
6. Step 3 — RF capture with the nRF52840 dongle
Flash the dongle per tools/SNIFFER_SETUP.md, then select the nRF Sniffer for Bluetooth LE COMx interface in Wireshark and switch to the Profile_nRF_Sniffer_Bluetooth_LE profile. Enable the interface toolbar under View, Interface Toolbars.
6.1 The capture-window constraint
The sniffer follows a single device at a time. Select the LiteJam in the toolbar's device list while it is advertising and before the iPhone connects. The sniffer must observe the connection request to learn the access address, CRC init, and hop sequence; without those it cannot follow the connection at all.
The practical sequence is: start the capture, put the guitar into advertising, select it in the toolbar, and only then open the iOS app. If the app connects first, disconnect and start over because the monitor must observe connection setup.
6.2 What this uniquely provides
- Advertising payloads. Device name, service UUIDs, manufacturer-specific data, and TX power, all before any connection exists.
- Connection parameters. Interval, latency, and supervision timeout, which govern how responsive a client can be.
- Link-layer control. Parameter updates, PHY switches, and data-length extension, which explain throughput behavior.
- The pairing exchange, if captured from the start, including the security level actually negotiated.
- Retransmissions and RF conditions, invisible to any host-side log.
6.3 The encryption constraint
If the link is encrypted and the pairing was not captured, ATT content is unreadable. Keys can be supplied through the toolbar when known, but in practice this is the reason the iOS HCI log is the primary source for payloads and the RF capture is the source for everything structural around them.
7. Interpreting the capture
7.1 Display filters
Wireshark's BLE dissectors are the working surface. Useful starting filters:
btatt all ATT traffic
btatt.opcode == 0x52 write commands (write without response)
btatt.opcode == 0x1b handle value notifications
btatt.handle == 0x000e one characteristic only
btatt.value presence of a payload
btle.advertising_address filter RF captures to one device
btcommon.eir_ad.entry.device_name
Add btatt.handle and btatt.value as custom columns. Once those two are visible, a capture reads as a conversation rather than a packet list.
7.2 ATT opcodes worth recognizing
| Opcode | Operation | Significance |
|---|---|---|
0x02 / 0x03 |
Exchange MTU Request / Response | Sets the maximum payload; determines fragmentation behavior |
0x0a / 0x0b |
Read Request / Response | Attribute polling |
0x12 / 0x13 |
Write Request / Response | Acknowledged command |
0x52 |
Write Command | Unacknowledged command — the common case for control pipes |
0x1b |
Handle Value Notification | Peripheral-initiated data, the usual status and streaming path |
0x1d / 0x1e |
Indication / Confirmation | Acknowledged peripheral-initiated data |
0x16 / 0x18 |
Prepare / Execute Write | Long-attribute writes, split across packets |
A write of 0x0100 to a CCCD handle is the client subscribing to notifications; 0x0200 subscribes to indications. That write is almost always the last step of connection setup, and it marks the boundary between session establishment and real traffic.
7.3 Recognize the transport before decoding the payload
Two vendor UUIDs are worth checking for immediately, because either one changes the entire interpretation:
- Nordic UART Service,
6E400001-B5A3-F393-E0A9-E50E24DCCA9E. This is a generic serial pipe. If present, the GATT layer carries no meaning by itself—the interface is a byte stream inside the payloads, and the work becomes framing interpretation rather than attribute mapping. - BLE MIDI, service
03B80E5A-EDE8-4B33-A751-6CE34EC4C700, characteristic7772E5DB-3868-4112-A1A9-F2669D106BF3. Plausible for a guitar product. If present, the payload is standard MIDI behind a 13-bit timestamp header and can be interpreted directly from the published BLE MIDI specification; any vendor-specific SysEx would simply remain unspecified unless LiteJam documents it.
7.4 MTU and fragmentation
The default ATT MTU is 23 bytes, leaving 20 bytes of payload. Anything longer is either fragmented by the application across successive writes or sent through the prepare/execute write sequence. Watch the MTU exchange at the start of the connection: if the negotiated MTU is large, single-packet payloads may be far longer than 20 bytes, and a decoder written against a 20-byte assumption will be wrong.
8. From packets to protocol
Capture produces bytes; interpretation produces a specification. The transition is a disciplined differential process.
Vary one thing at a time. Capture the same UI action twice with no change, then once with a single parameter changed. Bytes identical across all three are fixed structure — opcode, length, framing. Bytes that differ between the first two are counters, timestamps, or nonces. Bytes that differ only in the third are the parameter.
Classify each varying field. A field that increments by one per message is a sequence number. A field that changes with every byte of the payload but shows no other pattern is a checksum or CRC. A field that tracks elapsed time is a timestamp. Sequence numbers and checksums must be reproduced correctly by any client, so identifying them early prevents a long stall later.
Separate request-response pairs from streams. A write followed within milliseconds by a notification on a different handle is a command and its reply. A notification arriving at a fixed interval with no preceding write is telemetry. These call for different client architectures, so the distinction should be settled before any client code is written.
Record conclusions as a table, updated as understanding firms up:
| Direction | Handle | Bytes | Meaning | Confidence |
|---|---|---|---|---|
| Phone to guitar | 0x000e |
A5 01 03 .. |
Preset select, index in byte 3 | Observed 4 times |
Distinguish what was observed from what was inferred. An opcode seen once under one condition is a hypothesis; the same opcode seen across three sessions with a correctly predicted response is a finding.
8.1 Encoding the result as a dissector
Once opcodes are stable, write a Lua dissector and drop it in %APPDATA%\Wireshark\plugins\. Registering it against the vendor characteristic makes every subsequent capture self-documenting, and it converts protocol knowledge into a reusable artifact rather than notes. This is worth doing as soon as three or four commands are confirmed, not at the end.
9. Failure modes and how to recognize them
| Symptom | Cause | Remedy |
|---|---|---|
| RF capture shows only encrypted LL data, no ATT | Sniffer did not witness connection setup | Restart capture; select the device before the app connects |
bluetooth/ directory in sysdiagnose is empty |
Logging profile expired or removed by reboot | Reinstall the profile, recapture |
Relevant window missing from .pklg |
Sysdiagnose triggered too late; log rotated | Trigger immediately after the action |
| No sniffer interface in Wireshark | Dongle not flashed, or not enumerated as a COM port | Reflash via the Programmer app; check Device Manager |
Wireshark warns it cannot load wpcap.dll |
Npcap not installed | Benign for this workflow; ignore |
| Payloads truncate at 20 bytes | Default MTU assumption | Check the MTU exchange; handle fragmentation |
10. What this enables
Once the published and observed interface is characterized, two build paths open. A CoreBluetooth iOS client can be compiled through GitHub Actions macOS runners and installed with Sideloadly, giving an independent app that speaks the same publicly observable interface. Separately, the same nRF52840 dongle can run nRF Connect SDK firmware as an active BLE central, letting this PC drive the user's guitar directly rather than only observe it. That second path requires the nRF Connect SDK toolchain, a substantially larger install deliberately deferred until the interface is known.
Note that a self-built client is a poor discovery tool: it can only log its own traffic, never the official app's. Discovery rests entirely on the capture methods above and on static analysis of the official application.
Conclusion
The decisive asymmetry in this project is that Apple's HCI logging records the user's application-level session while the BLE monitor shows the link-layer structure those messages travel through, and neither substitutes for the other. Treat the HCI log as the interface source and the RF capture as the explanation of how the connection behaves.
The most common way this work stalls is not tooling failure but undisciplined capture. Both methods make each iteration expensive — the RF path through its advertising-window race, the iOS path through its ten-minute batch turnaround — so the value of enumerating the GATT tree first, exercising one behavior per session, and repeating each action for differential comparison is disproportionate to the effort involved.
The remaining constraint is that no capture method reveals intent. Packets show what the official app sends, not why; a byte that never varies across every captured session may be a constant, a protocol version, or a field whose triggering condition was simply never exercised. LiteJam's publicly served web clients provide the appropriate readable cross-check and ultimately supplied the information used by the working controller.