FirmwareKit.Comm
1.1.3
See the version list below for details.
dotnet add package FirmwareKit.Comm --version 1.1.3
NuGet\Install-Package FirmwareKit.Comm -Version 1.1.3
<PackageReference Include="FirmwareKit.Comm" Version="1.1.3" />
<PackageVersion Include="FirmwareKit.Comm" Version="1.1.3" />
<PackageReference Include="FirmwareKit.Comm" />
paket add FirmwareKit.Comm --version 1.1.3
#r "nuget: FirmwareKit.Comm, 1.1.3"
#:package FirmwareKit.Comm@1.1.3
#addin nuget:?package=FirmwareKit.Comm&version=1.1.3
#tool nuget:?package=FirmwareKit.Comm&version=1.1.3
FirmwareKit.Comm
English | 简体中文
A cross-platform USB communication library for FirmwareKit. It provides a unified USB abstraction over native platform backends (Windows / Linux / macOS / HarmonyOS) and LibUsbDotNet, with device discovery, filtering, session management, and structured transfer diagnostics. Transport primitives only — protocol layers (Sahara, Firehose, Fastboot, and so on) are out of scope and are implemented by callers on top of the unified session interfaces.
Features
- Unified session API: synchronous (
IUsbDeviceSession) and asynchronous (IAsyncUsbDeviceSession) read/write/control-transfer over one abstraction, with per-direction serialization for full-duplex protocol threads. - Four native backends + libusb: Windows WinUSB (and legacy driver), Linux usbfs, macOS IOUSBHost.framework, HarmonyOS USBManager DDK, and LibUsbDotNet.
- Device discovery & filtering: by
VendorId,ProductId,SerialNumber,DevicePath, interface class/subclass/protocol, interface number(s), and explicit endpoint addresses. - Packet semantics:
ReadPacket/ReadPacketAsyncreturn aUsbReadResultthat distinguishes a short packet (USB message boundary) from a timeout — what fastboot/EDL/bootrom framing needs.ReadExact/ReadExactAsyncread a fixed length within a total deadline. - Zero-length packet (ZLP) control:
WriteZlp/WriteZlpAsyncterminate transfers whose payload is an exact multiple of the endpoint max packet size. - Progress reporting:
ReadPacketAsync/WriteAsyncoverloads acceptIProgress<long>and report cumulative bytes after each chunk (flashing large images). - Safety guardrails: a read-length cap (
UsbTransferPolicies.MaxReadLength) prevents OOM from untrusted protocol lengths; session disposal is idempotent;ReadInto(Span<byte>)is available on net8.0+. - Observable enumeration diagnostics:
UsbCommunicationLayer.GetEnumerationDiagnostics()and per-finder counters distinguish "mechanism did not run" from "ran but found no devices" on device-less CI. - Mode-switch workflows:
WaitForUsbDeviceAppearAsync/WaitForUsbDeviceDisappearAsync/WaitForUsbDeviceModeSwitchAsync, plus re-opening byDeviceKeyviaOpenDeviceSessionByKey. - Hot-plug monitoring: event-driven where available (
WM_DEVICECHANGEon Windows native, libusb hotplug), polling fallback otherwise;Added/Removed/Changedchange events. - Backend capability model:
UsbApiCapabilities(and per-backendUsbBackendCapability) exposes async support, hot-plug support, external-runtime requirements, andResetReenumeratesDevice. - Diagnostics:
UsbTracestructured transfer events (TransferObserved) and opt-in frame capture;LogFormatteddefers string interpolation until logging is enabled. - Extensibility: register custom providers via
RegisterUsbApi/UsbApiRegistry. - Built-in CLI:
FirmwareKit.Comm.CLIwithapis,devices,all-devices,monitor,selftest,io-test, andinterrupt-testcommands.
Design Boundary
FirmwareKit.Comm focuses on cross-platform USB transport primitives:
- Device discovery and filtering
- Session management (open / read / write / control / interrupt / reset)
- Unified read/write with timeout control
- Transport-level reset
Discovery prefers metadata-first paths so simple enumeration does not require long-lived read/write sessions. Actual payload I/O starts after calling OpenUsbDeviceSessions.
Backend matrix:
| Backend | Platform | Transport | Notes |
|---|---|---|---|
native (WinUSB) |
Windows | WinUSB API | Overlapped (true async) I/O; requires a WinUSB-bound interface (Zadig etc.) |
native (legacy) |
Windows | DeviceIoControl | Fallback for legacy USB drivers; no ZLP, no native async |
native (usbfs) |
Linux | usbfs ioctl / URB | True async via URB + poll; multi-interface claim supported |
native (IOUSBLib) |
macOS | IOUSBHost.framework | Requires macOS 10.15+; pipe-level reset |
native (HarmonyOS) |
HarmonyOS | USBManager DDK | Opt-in via FIRMWAREKIT_USB_ENABLE_HARMONY=1; requires OH_Usb_Init() to succeed |
libusb |
all | LibUsbDotNet | Needs the native libusb runtime (bundled per-RID in the package); degrades gracefully when absent |
HarmonyOS is hidden from GetAvailableApis() by default because it cannot be detected reliably with file probes. On macOS below 10.15, use the libusb backend.
Transfer Timeout Semantics
- The timeout passed to
Read/Write(and theirReadIntovariants) applies per chunk, not to the whole operation: a transfer larger than the backend chunk size is split into multiple chunks, so the total time can reachchunkCount × timeoutMs. - When an exact number of bytes must be read within a total budget (e.g. fixed-size fastboot/EDL responses), use
ReadExact(length, timeoutMs)/ReadExactAsync— they loop over short reads with a total deadline and return the bytes actually received on timeout. - Short reads/writes stop the transfer and return the partial byte count; a disconnected device throws
UsbDeviceDisconnectedException(distinct from ordinaryIOException/UsbTransferException).
Default timeouts differ per backend. Omit timeoutMs only when the default is acceptable for your use case:
| Backend | Default timeout |
|---|---|
| Windows WinUSB | 60 000 ms |
| Windows legacy / Linux usbfs / libusb / macOS / HarmonyOS | 5 000 ms |
Pass an explicit timeoutMs (or UsbTransferPolicies.InfiniteTimeoutMs = -1 for an unbounded wait) when the operation must not depend on the backend default.
On the Linux usbfs backend, interrupt-endpoint reads/writes (ReadInterrupt/WriteInterrupt) wait on poll() from a thread-pool thread; with InfiniteTimeoutMs and an unresponsive device the waiting thread is held until the device responds or disconnects.
Retries for recoverable errors are configurable process-wide via UsbTransferPolicies.DefaultRetryPolicy (a UsbTransferRetryPolicy with MaxRetries and RetryDelayMs).
Zero-Length Packet (ZLP) Handling
Bulk transfers whose payload length is an exact multiple of the endpoint's max packet size (typically 512 or 1024 bytes) must be terminated with a zero-length packet so the device knows the transfer ended. This matters for protocol downloads (adb push, fastboot download:, EDL firehose) and for bootrom loaders.
- Check whether a ZLP is needed:
payloadLength % maxPacketSize == 0(readMaxPacketSizefrom the device'sInterfaces[i].Endpointsmetadata). - After such a write, call
session.WriteZlp(timeoutMs)(orWriteZlpAsync) to send the terminating zero-length packet. - Backends that cannot perform an explicit ZLP write (legacy Windows drivers) throw
NotSupportedException; checkUsbApiCapabilities/ backend capabilities first.
// Example: writing a 512 KiB block to a device whose OUT max packet size is 512.
long written = session.Write(block, 0, block.Length, timeoutMs);
if (block.Length % 512 == 0)
session.WriteZlp(timeoutMs);
Reset Semantics
IUsbDeviceSession.Reset() semantics differ per backend. Query UsbApiCapabilities.ResetReenumeratesDevice (or UsbBackendCapability.ResetReenumeratesDevice for the concrete backend) before relying on the session after a reset:
| Backend | Reset effect | Session after Reset | ResetReenumeratesDevice |
|---|---|---|---|
| Windows WinUSB | WinUsb_ResetPipe (pipe-level) |
still usable | false |
| Windows legacy | no-op | still usable | false |
| macOS IOUSBHost | pipe abort + clear stall | still usable | false |
| Linux usbfs | USBDEVFS_RESET (device reset) |
invalid — re-enumerate and re-open | true |
| libusb | libusb_reset_device (device reset) |
invalid — re-enumerate and re-open | true |
| HarmonyOS DDK | re-init DDK session + re-claim | invalid — re-open | true |
For device-level resets, use WaitForUsbDeviceDisappearAsync / WaitForUsbDeviceAppearAsync (or WaitForUsbDeviceModeSwitchAsync) and then OpenUsbDeviceSession / OpenUsbDeviceSessionByKey to obtain a fresh session.
Async I/O Semantics
True asynchronous (non-blocking) I/O is implemented natively by WinUSB (overlapped I/O) and Linux usbfs (URB + poll). All other backends — macOS IOUSBHost, libusb fallback paths, HarmonyOS DDK, and the AsAsync() adapter — execute the underlying synchronous transfer on a thread-pool thread (UsbAsyncExecution.Run).
Check UsbApiCapabilities.SupportsNativeAsyncIo (or UsbBackendCapability.SupportsNativeAsyncIo per backend) to know whether ReadAsync/WriteAsync are truly non-blocking or just offloaded. Protocol layers that must not block the caller's thread should treat SupportsNativeAsyncIo == false backends as synchronous-with-offload.
Enumeration Diagnostics (device-less CI)
When no device is attached (hosted CI runners), an empty enumeration can mean either "no devices" or "the enumeration mechanism failed". Each finder exposes observability state, and UsbCommunicationLayer.GetEnumerationDiagnostics() summarizes it in a key=value; ... string:
| Backend | Mechanism diagnostic | Scan proof | Counters |
|---|---|---|---|
| Linux usbfs | LastUsbfsRootExists (usbfs mounted?) |
LastScannedNodes |
LastMatchedDeviceCount, PermissionDeniedCount, BusyCount |
| Windows | LastSetupDiSucceeded (SetupDi handle opened?) |
LastScannedNodeCount |
LastMatchedDeviceCount |
| macOS | LastCopyDevicesSucceeded (IOUSBLib copy returned?) |
LastScannedDeviceCount |
LastMatchedDeviceCount |
| libusb | IsRuntimeAvailable(out reason) |
device list | device list |
The CLI prints [enum-diagnostics] <summary> on every all-devices run; CI asserts on it instead of only checking "exit 0". The library also ships unit tests that feed constructed USB descriptor bytes into the pure parser (LinuxUsbFinder.TryParseDescriptor) to verify enumeration correctness without hardware.
Hot-Plug Monitoring
MonitorUsbDevices prefers event-driven notifications where available: WM_DEVICECHANGE (Windows native backend) and libusb hotplug (Linux/macOS, UsbApiKind.LibUsbDotNet); unsupported platforms fall back to polling (pollInterval, default 1 s). Change events include Added, Removed and Changed (metadata changed while keeping the same physical identity). Pass a CancellationToken to auto-dispose the monitor handle, or use the WaitForUsbDeviceAppearAsync / WaitForUsbDeviceDisappearAsync / WaitForUsbDeviceModeSwitchAsync helpers for mode-switch workflows.
Installation
Install via NuGet:
dotnet add package FirmwareKit.Comm
The package targets net10.0, net8.0 and netstandard2.0, and bundles the native libusb runtime for each supported RID (win-x64 / win-arm64 / osx-x64 / osx-arm64 / linux-x64 / linux-arm64 / linux-riscv64 / linux-loong64). Requires the .NET 10 SDK to build the solution (.slnx).
Quick Start
Use the FirmwareKitComm facade to enumerate APIs and devices:
using FirmwareKit.Comm;
using FirmwareKit.Comm.Abstractions;
var comm = new FirmwareKitComm();
// List registered USB APIs
foreach (var api in comm.GetAvailableUsbApis())
Console.WriteLine(api);
// Print backend capability summary
foreach (var capability in comm.GetAvailableUsbApiCapabilities())
{
Console.WriteLine($"api={capability.ApiName} nativeDiscovery={capability.SupportsNativeDiscovery} nativeAsync={capability.SupportsNativeAsyncIo} hotplug={capability.SupportsNativeHotPlugMonitoring} externalRuntime={capability.RequiresExternalRuntime}");
}
// Sync device enumeration with VendorId filter (example: 0x18D1)
var devices = comm.EnumerateUsbDevices(UsbApiKind.Auto, new UsbDeviceFilter { VendorId = 0x18D1 });
foreach (var d in devices)
{
var ifClass = d.InterfaceClass.HasValue ? $"0x{d.InterfaceClass.Value:X2}" : "--";
var ifSubClass = d.InterfaceSubClass.HasValue ? $"0x{d.InterfaceSubClass.Value:X2}" : "--";
var ifProto = d.InterfaceProtocol.HasValue ? $"0x{d.InterfaceProtocol.Value:X2}" : "--";
Console.WriteLine($"api={d.ApiName} vid=0x{d.VendorId:X4} pid=0x{d.ProductId:X4} if={ifClass}/{ifSubClass}/{ifProto} serial={d.SerialNumber ?? "<null>"} path={d.DevicePath}");
}
// Optional: filter by USB interface class (for example Qualcomm EDL often uses 0xFF/0xFF/0xFF)
var edlLikeDevices = comm.EnumerateUsbDevices(UsbApiKind.Auto, new UsbDeviceFilter
{
VendorId = 0x05C6,
InterfaceClass = 0xFF,
InterfaceSubClass = 0xFF,
InterfaceProtocol = 0xFF
});
// Multi-interface claim (e.g. CDC-ACM control + data): every listed interface must exist
var multiIf = comm.EnumerateUsbDevices(UsbApiKind.Auto, new UsbDeviceFilter
{
InterfaceNumber = 0, // primary data interface
InterfaceNumbers = new byte[] { 1 } // additional interface to claim (Linux/libusb)
});
// Async enumeration
var asyncDevices = await comm.EnumerateUsbDevicesAsync(UsbApiKind.LibUsbDotNet);
// Open sessions and do unified read/write (protocol parsing is caller-defined)
using var sessions = comm.OpenUsbDeviceSessions(UsbApiKind.Auto, new UsbDeviceFilter
{
VendorId = 0x05C6,
ProductId = 0x9008,
InterfaceClass = 0xFF,
InterfaceSubClass = 0xFF,
InterfaceProtocol = 0xFF
});
var session = sessions.Sessions.FirstOrDefault();
if (session != null)
{
// Example only: command/protocol payload is app-specific
_ = session.Write(new byte[] { 0x7E, 0x00 }, 2, 3000);
var response = session.Read(512, 3000);
Console.WriteLine($"response bytes: {response.Length}");
// Control transfer example: read current alternate setting
var setup = new UsbSetupPacket
{
RequestType = 0x81,
Request = 0x0A,
Value = 0,
Index = 0,
Length = 1
};
var ctrlBuffer = new byte[1];
var ctrlCount = session.ControlTransfer(setup, ctrlBuffer, 0, ctrlBuffer.Length, 3000);
Console.WriteLine($"control bytes: {ctrlCount}, alt={ctrlBuffer[0]}");
// Async session (if backend does not implement async natively, use AsAsync())
var asyncSession = session.AsAsync();
var asyncResponse = await asyncSession.ReadAsync(512, 3000);
Console.WriteLine($"async response bytes: {asyncResponse.Length}");
// Packet-aware read: short packet vs timeout (fastboot/EDL message boundary)
var buf = new byte[512];
var result = session.ReadPacket(buf, 0, buf.Length, 3000);
Console.WriteLine($"packet bytes={result.Count} timeout={result.IsTimeout} short={result.IsShortPacket}");
// Progress reporting while flashing a large image
var progress = new Progress<long>(total => Console.WriteLine($"written: {total}"));
await asyncSession.WriteAsync(image, 0, image.Length, 3000, progress);
}
// Device change monitoring (dispose when appropriate)
using var monitor = comm.MonitorUsbDevices(
changes =>
{
foreach (var change in changes)
{
Console.WriteLine($"device {change.Kind}: {change.Device.ApiName} {change.Device.DevicePath}");
}
},
UsbApiKind.Auto,
pollInterval: TimeSpan.FromSeconds(1),
fireInitialSnapshot: false,
onError: ex => Console.WriteLine($"monitor error: {ex.Message}"));
// Structured diagnostics event (for metrics/log aggregation)
UsbTrace.TransferObserved += evt =>
{
Console.WriteLine($"usb {evt.Operation} backend={evt.Backend} outcome={evt.Outcome} bytes={evt.TransferredBytes}/{evt.RequestedBytes} retry={evt.RetryCount} err={evt.NativeErrorCode}");
};
Register a custom USB API provider:
comm.RegisterUsbApi("my-custom", () => new MyCustomUsbApiProvider());
CLI
FirmwareKit.Comm.CLI provides these commands:
apis: list available USB APIs.devices: enumerate devices with optional filters.all-devices: list all USB devices recognized by the current platform (native backend by default); prints[enum-diagnostics]even when empty.monitor: run a device-change monitor (options:--api,--vid,--pid,--interval <seconds>).selftest: read-only smoke test — open session, GET_DESCRIPTOR control transfer, shortReadExact(no writes/resets);--duration <seconds>loops it (0 = single pass).io-test: write/reset smoke test (bulk OUT write, reset, endpoint-open, concurrent-io, offset-write); options include--ep-in,--ep-out,--pattern-size,--concurrent,--offset-write.interrupt-test: interrupt-IN endpoint read test (options:--ep <addr>).
Examples:
# List APIs
dotnet run --project FirmwareKit.Comm.CLI -- apis
# List devices (libusb backend, filtered by VID/PID)
dotnet run --project FirmwareKit.Comm.CLI -- devices --api libusb --vid 0x18D1 --pid 0x4E11
# List all USB devices recognized by current platform (JSON output)
dotnet run --project FirmwareKit.Comm.CLI -- all-devices --json
# Read-only selftest against a specific device
dotnet run --project FirmwareKit.Comm.CLI -- selftest --api native --vid 0x18D1 --duration 10
Supported devices / all-devices options:
--api auto|native|libusb: select backend API.--vid <hex>: vendor ID (hex or decimal).--pid <hex>: product ID (hex or decimal).--serial <text>: device serial number.--path-contains <text>: substring filter on device path.--if-class <hex|dec>: interface class filter.--if-subclass <hex|dec>: interface subclass filter.--if-protocol <hex|dec>: interface protocol filter.--json: JSON output (devices / all-devices).
Diagnostics are opt-in via environment variables: FIRMWAREKIT_USB_DEBUG=1 (plain-text logs), FIRMWAREKIT_USB_CAPTURE_FRAMES=1 (raw frame capture into transfer events, capped at 256 bytes per event).
Build, Test and CI
dotnet restore
dotnet build -c Release # CI validates Release
dotnet test -c Release # xunit v3; empty enumeration is the expected CI outcome
- The solution is
.slnxand requires the .NET 10 SDK. GeneratePackageOnBuild=trueemits nupkg/snupkg on every build — never treatbin//obj/artifacts as sources of truth.tools/fetch-libusb.shdownloads native libusb runtimes intonative/<target>/(mirror flags:--mirror tuna|ustc,--github-mirror,--ghcr-mirror).tools/test-windows.ps1/tools/test-linux.sh/tools/test-macos.share real-device automated test scripts (build + unit tests + CLI smoke + read-only selftest + 3 s monitor hot-plug smoke); they exit 0 withSKIPwhen no device matches.- CI workflows:
dotnet-ci.yml(build/test/behavior observation on win/ubuntu/macos, including enumeration-mechanism assertions),cross-publish.yml(RID publish graph check),fetch-libusb.yml(native runtime fetch),qemu-linux-guest.yml(real emulated USB inside a QEMU Linux guest).
License
MIT
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- LibUsbDotNet (>= 3.0.224)
-
net10.0
- LibUsbDotNet (>= 3.0.224)
-
net8.0
- LibUsbDotNet (>= 3.0.224)
NuGet packages (3)
Showing the top 3 NuGet packages that depend on FirmwareKit.Comm:
| Package | Downloads |
|---|---|
|
FirmwareKit.Comm.Fastboot
A .NET library implementing the Android Fastboot protocol, aligned with AOSP semantics, for firmware flashing and device automation. Part of the FirmwareKit ecosystem. |
|
|
FirmwareKit.Comm.EDL.Backend
EDL transport abstractions and USB/Serial/HSUART/HDLC transport implementations. |
|
|
FirmwareKit.Comm.ADB
A high-performance .NET implementation of the Android Debug Bridge (ADB) protocol over USB and TCP, strictly aligned with AOSP logic. Part of the FirmwareKit ecosystem: direct-write to adbd (no client/server split), with shell v2, sync (file transfer), reverse, mDNS discovery, and an adb-compatible CLI. |
GitHub repositories
This package is not used by any popular GitHub repositories.