Decode, analyze, and diff USB capture files (.pcap/.pcapng) produced by Packetry and Cynthion. Use when the user mentions a .pcap or .pcapng from Packetry or Cynthion, asks to decode/analyze/read/diff/summarize a USB capture, or has questions about specific endpoints, descriptors, or transfer sequences in a capture file.
Scanned 9/10/2026
Install to Claude Code
npx -y skills add curtisgalloway/public-skills --skill cynthion-pcap-decode --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Cynthion Pcap Decode?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/curtisgalloway-cynthion-pcap-decode)More formats (shields.io, HTML) on the badges page.
---
name: cynthion-pcap-decode
description: Decode, analyze, and diff USB capture files (.pcap/.pcapng) produced by Packetry and Cynthion. Use when the user mentions a .pcap or .pcapng from Packetry or Cynthion, asks to decode/analyze/read/diff/summarize a USB capture, or has questions about specific endpoints, descriptors, or transfer sequences in a capture file.
---
<!--
SPDX-FileCopyrightText: 2026 contributors
SPDX-License-Identifier: Apache-2.0
-->
# Cynthion pcap decode
Decode USB captures from Packetry/Cynthion into four progressively richer
views: raw packets → transactions → transfers → decoded descriptors and
class-specific content.
## Trigger phrases
- "decode this pcap"
- "analyze / read / summarize a USB capture"
- "what's in this .pcap from Packetry?"
- "diff two USB captures"
- "show me the device descriptor from this capture"
- "what HID reports does this capture contain?"
- "compare capture A and capture B"
## Prerequisites
`tshark` (from Wireshark) is the preferred decoder — its USB 2.0 dissector
handles real-world captures far more reliably than the bundled Python
fallback. Install it via the `cynthion-setup` skill, or directly:
```bash
brew install wireshark # macOS
sudo apt install tshark # Debian/Ubuntu
# Windows: install Wireshark from https://wireshark.org/download.html
python3 --version # 3.8+ (stdlib only — no extra packages)
```
A native Python fallback is included for environments where tshark cannot be
installed; it covers packets → transactions → transfers and standard
descriptors but is best-effort. Force it with `--native` if needed. For
anything beyond a smoke test, install tshark.
## Quick start
```bash
cd skills/cynthion-pcap-decode/scripts
# Full decode — JSON output (default)
python3 decode.py capture.pcap
# Human-readable transcript
python3 decode.py capture.pcap --format transcript
# Markdown summary report
python3 decode.py capture.pcap --format markdown
# Enumeration phase only (SET_ADDRESS + descriptor reads)
python3 decode.py capture.pcap --phase enumeration --format transcript
# Filter by device address and endpoint
python3 decode.py capture.pcap --filter address=2 --filter endpoint=1
# Filter by transfer type
python3 decode.py capture.pcap --filter transfer-type=control
# Time window (seconds from start of capture)
python3 decode.py capture.pcap --time-range 0.0,2.5
# Force native Python decoder (no tshark)
python3 decode.py capture.pcap --native
# Diff two captures
python3 diff.py before.pcap after.pcap
python3 diff.py before.pcap after.pcap --endpoint 1 --format markdown
```
## Four-layer model
### Layer 1 — Packets
Individual USB 2.0 wire packets, identified by their PID byte:
| Group | PIDs | Meaning |
|-----------|-------------------------------|---------|
| Token | SETUP, IN, OUT, SOF | Address the device/endpoint; start a transaction |
| Data | DATA0, DATA1, DATA2, MDATA | Carry the payload; alternate to detect retransmit |
| Handshake | ACK, NAK, STALL, NYET | Receiver reports success, busy, or error |
| Special | SPLIT, PING, PRE | High-speed split transactions and preamble |
SOF (Start of Frame) packets are frame delimiters sent every 1 ms (FS) or
125 µs (HS). The decoder discards them — they are not part of transactions.
### Layer 2 — Transactions
One token + optional data + optional handshake, grouped by address/endpoint:
```
SETUP addr=0 ep=0 → DATA0 [8 bytes] → ACK (control SETUP stage)
IN addr=0 ep=0 → DATA0 [18 bytes] → ACK (control data stage)
IN addr=1 ep=1 → NAK (device busy, no data yet)
OUT addr=2 ep=2 → DATA1 [64 bytes] → ACK (bulk write)
```
Transactions with NAK or STALL handshakes are preserved in the output but
marked accordingly. Transactions with no handshake (timeout/loss) are also
kept and flagged.
DATA0/DATA1 alternation is used to detect retransmissions: if the host
receives no ACK, it retransmits the same DATA0/DATA1. The decoder does not
deduplicate retransmissions — they appear as separate transactions.
### Layer 3 — Transfers
Transactions reassembled into logical USB transfers:
**Control transfers** (always EP0):
1. SETUP stage — 8-byte SETUP packet establishes the request
2. Data stage (optional) — one or more IN or OUT transactions carrying payload
3. Status stage — zero-length packet (ZLP) in the opposite direction, ACKed
**Bulk transfers** — one or more transactions on a non-zero endpoint, each
up to wMaxPacketSize bytes. A short packet (< wMaxPacketSize) signals end of
transfer. The decoder emits each successful data-bearing transaction as its
own transfer (bulk endpoint descriptors are needed for full reassembly).
**Interrupt transfers** — like bulk but polled at a fixed interval. The
decoder cannot distinguish interrupt from bulk without an endpoint descriptor;
it defaults to "bulk". Add `--filter transfer-type=interrupt` if you know the
endpoint type.
**Isochronous transfers** — periodic, no handshake. Not yet fully supported;
raw packets are preserved.
### Layer 4 — Decoded content
Control transfers on EP0 are decoded automatically:
**Standard GET_DESCRIPTOR responses:**
| Descriptor | What it tells you |
|---|---|
| Device (0x01) | VID, PID, USB version, device class, max packet size |
| Configuration (0x02) | Number of interfaces, power draw |
| Interface (0x04) | Class/subclass/protocol (identifies HID, MSC, CDC, etc.) |
| Endpoint (0x05) | Direction, type (bulk/interrupt/iso), max packet size |
| String (0x03) | Manufacturer, product, serial (UTF-16LE) |
| HID (0x21) | HID descriptor metadata |
**Class-specific decoders** (invoked automatically when the request type is
`class`):
| Class | What is decoded |
|---|---|
| HID keyboard (boot protocol) | Modifier bitmask + keycode array from 8-byte reports |
| HID mouse (boot protocol) | Button byte + signed dx/dy/wheel from 3–4 byte reports |
| MSC (SCSI) | CBW: opcode, LUN, transfer length, SCSI CDB; CSW: status |
| CDC-ACM | SET_LINE_CODING (baud/parity/stop/bits), SET_CONTROL_LINE_STATE (DTR/RTS) |
| MIDI | Cable number + MIDI event bytes per USB-MIDI event packet |
| UVC | Stub — raw bytes, length |
| USB Audio | Stub — raw bytes, length |
| Vendor-specific | First 64 bytes as hex |
## JSON output structure
```json
[
{
"index": 0,
"type": "control",
"addr": 0,
"endp": 0,
"direction": "IN",
"time_s": 0.000003,
"payload_len": 18,
"payload_hex": "1201000200000040...",
"setup": {
"bmRequestType": "0x80",
"direction": "IN",
"type": "standard",
"recipient": "device",
"bRequest": 6,
"wValue": "0x0100",
"wIndex": 0,
"wLength": 64
},
"decoded": {
"descriptor_type": 1,
"descriptor_name": "Device",
"bLength": 18,
"bDescriptorType": 1,
"bcdUSB": "0x0200",
"bDeviceClass": "0x00",
"idVendor": "0x1d50",
"idProduct": "0x615b",
...
}
}
]
```
## Diff output
`diff.py` aligns the transaction sequences from two captures by
`(addr, endpoint, direction)` and sequence position, then reports:
- **only in A** — transaction present in capture A but not B (e.g. extra
enumeration step)
- **only in B** — transaction present in B but not A
- **changed** — same position, different payload bytes; changed offsets are
marked with `*` in the hex dump and listed by offset number
Typical use: compare captures before and after a firmware update to confirm
the device descriptor or HID report format did not change unexpectedly.
## Pitfalls
**Do not use dpkt or scapy directly on these files.**
Packetry writes `LINKTYPE_USB_2_0` (link type 288), which is raw wire-level
USB 2.0 — PID byte followed by packet-specific fields. `dpkt` and many scapy
USB paths expect `LINKTYPE_USB_LINUX` (link type 220, the Linux usbmon format)
and will silently misparse packets or crash. Always use this skill's decoder
or tshark's `usbll` dissector.
**tshark field names differ between `-T json` and `-T ek`.**
`-T json` uses dot-separated names (e.g. `usbll.pid`); `-T ek` uses
underscore-separated names (e.g. `usbll_usbll_pid`). Integer field encodings
are also inconsistent across tshark versions — `usbll.pid` is emitted as a
0x-prefixed hex string (`"0xa5"`) in tshark 4.2 but was a decimal string in
earlier releases, while `usbll.device_addr` and `usbll.endp` stay decimal.
`decode.py`'s `_ek_int` helper handles both. If you write custom
post-processing, parse int fields with `int(s, 0)` / a similar helper rather
than assuming one format, and do not mix `-T json` and `-T ek` output.
**Large captures must stream — do not load into memory.**
`decode.py` feeds tshark output through a generator pipeline; no packet list
is held in memory. If you write custom analysis on top of this skill, keep
the generator chain intact rather than collecting into a list up front.
**Packetry/Cynthion knowledge in LLM training data is often stale.**
If you are uncertain about the current pcap format, tshark field names, or
Cynthion gateware behavior, read the current docs rather than relying on
memory:
- https://cynthion.readthedocs.io
- https://packetry.readthedocs.io
- https://github.com/greatscottgadgets/packetry
**USB 3.x SuperSpeed is not captured.**
Cynthion captures USB 2.0 (HS/FS/LS) only. USB 3.x devices may fall back to
USB 2.0 when connected through Cynthion; the decoder handles the resulting
traffic correctly.
## Sample captures for testing
The Packetry repository includes test fixtures:
```bash
# Mouse (HID, 2182 packets, confirmed link type 288)
curl -L https://raw.githubusercontent.com/greatscottgadgets/packetry/main/tests/mouse/capture.pcap \
-o mouse.pcap
python3 decode.py mouse.pcap --phase enumeration --format transcript
python3 decode.py mouse.pcap --filter transfer-type=bulk --format markdown
```
Expected enumeration output includes a Device Descriptor with class 0x00
(device class defined at interface), a HID Configuration Descriptor, and
a String Descriptor for the product name.
## When a new Cynthion skill calls this one
`diff.py` is designed to be imported as a library:
```python
from diff import diff_captures
diffs = diff_captures("before.pcap", "after.pcap", endp_filter=1)
```
`decode.py` exposes the full pipeline as importable functions:
```python
from decode import stream_packets, stream_transactions, stream_transfers, Filters
# Build a custom filter, stream transfers, do your own analysis
```
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!