feat(hid): synchronous Transport, MockTransport, and HID backend - #1
Merged
Merged
Conversation
First M1 slice: the transport foundation, built test-first. - `Transport` is synchronous and policy-free. `ReadOutcome` makes an elapsed timeout a distinct outcome so a poll loop cannot confuse "nothing happened" with "the device went away". - `DeviceMatch`/`select_index` match on VID, PID, and usage page, then the backend opens the enumerated path of the interface that matched (PLAN-REVIEW #8). VID/PID alone would open the wrong collection of a composite device. - `MockTransport` replays scripted reports, timeouts, and disconnects and records every write, so layers above this crate need no hardware. An exhausted script reports a disconnect, which keeps a reconnect loop from spinning against an empty mock. - `HidTransport` wraps `hidapi` over the stock OS HID stack (ADR-0001) and flattens backend errors so nothing above this crate names `hidapi` types. - `bosunctl device list` renders enumerated interfaces with the path used to open them, filtered by criteria given on the command line. No product identifiers are compiled in. - Hardware tests are `#[ignore]`d and gated on `BOSUN_HW=1`. One of them is risk R1: open an interface and prove input reports reach a second reader while LGS holds its own handle. Scope held to M1: no Tokio, no engine, no adapters, no profiles, no descriptor loading yet. Descriptor loading and report decoding are the next slice. Verification: 27 unit tests pass and clippy is clean at `-D warnings`, but they were run by compiling the sources directly with rustc, because this sandbox's egress policy blocks crate downloads from static.crates.io. The hidapi, thiserror, and clap layers therefore compile for the first time in CI, not locally. `rustfmt --check` is clean on every file. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0157Fe2eqqST8yYonUyYbBqk
RaapTechllc
marked this pull request as ready for review
August 31, 2026 11:44
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
First M1 slice: the transport foundation, built test-first. Scope is deliberately narrow — descriptor loading and report decoding are the next slice.
What this adds
bosun-hidTransport— synchronous and policy-free, perAGENTS.md. No Tokio.ReadOutcome— an elapsed timeout is a distinct outcome rather than a zero-length report, so a poll loop cannot silently confuse "nothing happened" with "the device went away".DeviceMatch/select_index/select— match on VID, PID and usage page, then open the enumerated path of the interface that matched (PLAN-REVIEW #8). Matching on VID/PID alone opens whichever collection the OS listed first, which for a composite device is usually the wrong one.MockTransport— replays scripted reports, timeouts, and disconnects, and records every write. An exhausted script reports a disconnect, which models an unplug and keeps a reconnect loop from spinning against an empty mock.HidTransport—hidapiover the stock OS HID stack (ADR-0001). Backend errors are flattened intoHidError::Backendso nothing above this crate nameshidapitypes.bosunctldevice listrenders enumerated interfaces along with the path used to open them, filtered by--vid/--pid/--usage-pagegiven on the command line. No product identifiers are compiled in, so the G13 stays data.Hardware tests (
crates/bosun-hid/tests/hardware.rs)#[ignore]d and additionally gated onBOSUN_HW=1, perCONTRIBUTING.md. One of them is risk R1 directly: open an interface and prove input reports reach a second reader while Logitech Gaming Software holds its own handle.Testing
27 unit tests, covering the failure modes that matter here:
selectpicks the vendor collection out of a composite device and is deterministic when several interfaces match;Duration— includingDuration::MAX— ever converts to a negative timeout, whichhidapiwould read as "block forever";Box<dyn Transport>;0x10000,65536,046D) are rejected rather than silently truncated into the wrong device.The suite was mutation-checked: removing the usage-page criterion and swapping the saturating timeout conversion for a wrapping cast each fail the tests written to catch them.
Verification caveat — please read
The sandbox this was written in has an egress policy that blocks crate downloads from
static.crates.io, socargo build/clippy/testcould not run at all.What was verified locally, by compiling the real sources directly with
rustc --testandclippy-driver:device,transport,mock, andlisting;-D warnings;rustfmt --checkclean on every file.What was not verified locally: anything touching
hidapi,thiserror, orclap.hid.rs,error.rs,main.rs, and the crate-root doctest compile for the first time in CI. Treat the three-OS CI run as the real gate on this PR; I'll drive any failures to green.Generated by Claude Code