# serial-capture
Cross-platform USB virtual COM port capture for Linux and Windows. Records
traffic to a timestamped text log and (optionally) a Wireshark-readable pcapng.
Supported chips: **CDC-ACM**, **FTDI** (FT232/FT2232/FT4232/FT-X),
**WCH CH340/341/343/9102**, **Prolific PL2303**.
```
serial-capture # auto-detect, → stdout
serial-capture -o capture.txt # auto-detect, → file
serial-capture --port /dev/ttyACM0 -o capture.txt # explicit port
serial-capture --port COM4 -o capture.txt --pcap capture.pcapng
```
`--port` is optional: when exactly one USB serial device is connected, the
tool picks it. Otherwise it lists the candidates and exits.
## Install
```
cargo install serial-capture
```
`cargo install` is the only supported distribution today. The pinned USBPcap
installer (~190 KB) ships in `vendor/` and is embedded into the Windows binary
at build time — no network access required. Override with
`USBPCAP_INSTALLER=/path/to/file cargo build` if you need a different version.
Platform support:
| Linux x86_64 | supported |
| Linux aarch64 | supported (passive only; needs usbmon) |
| Windows x86_64 | supported |
| Windows ARM64 | not supported (no signed USBPcap driver) |
| macOS | not supported |
The unsupported platforms fail at compile time with a clear `compile_error!`
and at runtime with an explanatory message, so misuse is hard.
## How it works
```
serial-capture --port COM4 -o capture.txt
```
Captures at the USB layer using **USBPcap** on Windows (auto-installed on
first run via UAC) and **usbmon** on Linux. The user's application keeps
talking to the real port unchanged; we sniff URBs and decode chip-specific
framing (FTDI's 2-byte status header, etc.) to recover serial bytes.
## Output
Default text log format (`--format both`, the default):
```
```
Arrows: `→` = host application → device, `←` = device → host application.
Other formats: `--format hex`, `--format ascii`. Live tail to stdout unless
`--quiet`. `--printable-only` skips events whose payload contains zero text
bytes (printable ASCII or `\t` `\n` `\r`) — useful for filtering binary
keep-alive chatter from a text protocol.
`--pcap FILE` additionally writes a Wireshark-compatible pcapng of the
device's full URB stream (link types `DLT_USB_LINUX_MMAPPED` on Linux,
`DLT_USBPCAP` on Windows). Wireshark's USB dissector reads it natively; you
get control transfers, descriptor exchanges, and bulk traffic.
## First-run requirements
### Linux passive
`usbmon` is in-tree. On first run, if it isn't loaded or `/dev/usbmonN`
isn't readable, the tool offers to fix it for you — one prompt before
each `sudo` invocation, or pass `--yes` / `-y` to skip the prompts:
```
→ /dev/usbmon* is missing. The usbmon kernel module is not loaded.
Run 'sudo modprobe usbmon'? [Y/n]
→ /dev/usbmon* is not readable by the current user.
→ Installing a permissive udev rule (mode 0644 / world-readable) at
/etc/udev/rules.d/60-serial-capture.rules. Persists across reboots
and replugs.
→ NOTE: any local user on this machine will then be able to sniff
USB traffic — keystrokes, USB drives, smartcards, anything on the
bus. Fine for a single-user dev box; on a shared system, decline
and run serial-capture with sudo instead.
Install udev rule and chmod /dev/usbmon* (one sudo invocation)? [Y/n]
```
**Security note.** The auto-setup makes `/dev/usbmon*` world-readable
because that's the only way to grant persistent access without a
logout/login dance (process supplementary groups are frozen at login,
so a fresh `serialcap` group can't apply to your current shell). For a
single-user dev box, the practical risk is approximately zero. On a
shared system — decline the prompt and run `sudo serial-capture …`
instead, or write your own group-scoped rule before re-running.
To also avoid loading the module by hand after a reboot:
```
### Windows passive
USBPcap is auto-installed on first run via UAC. After the install, **unplug
and replug the target device** (USBPcap's filter driver attaches at PnP
enumeration; existing connections aren't filtered until the device
re-enumerates). Then re-run **from an elevated shell** — USBPcap's
installer sets an Administrators-only ACL on `\\.\USBPcapN`, so the
capture process itself must be elevated. The tool detects non-elevated
runs and prints instructions.
**Multi-controller systems.** USBPcap installs one `\\.\USBPcapN` interface
per USB host controller. The tool defaults to `\\.\USBPcap1`. If your
device is on a different controller, capture will silently produce no
traffic — pass `--usbpcap '\\.\USBPcap2'` (or 3, 4, …) to pick the right
one. Wireshark's interface list (or `dumpcap -D`) shows which `USBPcapN`
sees the device.
## CLI reference
```
serial-capture [--port <PORT>] [options]
Port selection
--port <PORT> COM4 (Windows) or /dev/ttyUSB0 / /dev/ttyACM0 (Linux).
Optional: if there's exactly one USB serial device on
the system, it's picked automatically.
Output
-o, --output <FILE> Text log path. Omit to write events to stdout.
--pcap <FILE> Also write Wireshark-compatible pcapng
-q, --quiet Suppress live tail to stdout (with -o), or suppress
all text output (without -o; useful with --pcap)
First-run / privileges
-y, --yes Linux: auto-confirm sudo prompts (load usbmon, write
udev rule). No effect when setup is already complete.
--usbpcap <NAME> Windows: pick a specific filter interface, e.g.
'\\.\USBPcap2'. Defaults to '\\.\USBPcap1'.
Decoder tuning
--ftdi-mps <BYTES> Force FTDI bulk-IN wMaxPacketSize (64 or 512). Use only
when the auto-detection picks the wrong size for an
unusual variant or clone.
```
## Architecture
```
Linux: /dev/usbmonN ──libpcap──▶ decoder ──▶ text + pcapng
User app ──▶ Windows: \\.\USBPcap1 ──Win32───▶ decoder ──▶ text + pcapng
```
Per-chip decoders live in `src/decode/`. The CDC-ACM, CH340, and PL2303
decoders are passthroughs (raw bytes on bulk IN/OUT). The FTDI decoder strips
the 2-byte modem-status header that FTDI prepends to every `wMaxPacketSize`
chunk on its bulk-IN endpoint.
## Limitations / future work
- Hub-IOCTL endpoint discovery on Windows (current FTDI MPS is a PID-based
heuristic; `--ftdi-mps` overrides)
- Auto-detect `\\.\USBPcapN` by walking the device tree to the root hub
(today: defaults to `USBPcap1`, override with `--usbpcap`)
- Tested chip families: CDC-ACM, FTDI logic verified by unit tests; live
end-to-end testing requires the real hardware
## License
GPL-3.0-or-later. See [LICENSE](LICENSE) for the full text.
## Acknowledgements
- [USBPcap](https://desowin.org/usbpcap/) — Windows USB packet capture driver.
The unmodified upstream installer (`vendor/USBPcapSetup-1.5.4.0.exe`) is
bundled and run as a separate process; the installer's components carry
their own licenses (USBPcapDriver: GPL-2.0; USBPcapCMD: BSD-2-Clause).
- Linux `usbmon` and the libpcap project