FirmwareKit.Comm 1.2.1

dotnet add package FirmwareKit.Comm --version 1.2.1
                    
NuGet\Install-Package FirmwareKit.Comm -Version 1.2.1
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="FirmwareKit.Comm" Version="1.2.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="FirmwareKit.Comm" Version="1.2.1" />
                    
Directory.Packages.props
<PackageReference Include="FirmwareKit.Comm" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add FirmwareKit.Comm --version 1.2.1
                    
#r "nuget: FirmwareKit.Comm, 1.2.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package FirmwareKit.Comm@1.2.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=FirmwareKit.Comm&version=1.2.1
                    
Install as a Cake Addin
#tool nuget:?package=FirmwareKit.Comm&version=1.2.1
                    
Install as a Cake Tool

FirmwareKit.Comm

NuGet version License: MIT

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 — vendor-specific protocol layers 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 IOKit.framework classic API, 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 / ReadPacketAsync return a UsbReadResult that distinguishes a short packet (USB message boundary) from a timeout — what packet-framed bootloader protocols need. ReadExact / ReadExactAsync read a fixed length within a total deadline.
  • Zero-length packet (ZLP) control: WriteZlp / WriteZlpAsync terminate transfers whose payload is an exact multiple of the endpoint max packet size.
  • Progress reporting: ReadPacketAsync / WriteAsync overloads accept IProgress<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 by DeviceKey via OpenDeviceSessionByKey.
  • Hot-plug monitoring: event-driven where available (WM_DEVICECHANGE on Windows native, libusb hotplug), polling fallback otherwise; Added / Removed / Changed change events.
  • Backend capability model: UsbApiCapabilities (and per-backend UsbBackendCapability) exposes async support, hot-plug support, external-runtime requirements, and ResetReenumeratesDevice.
  • Diagnostics: UsbTrace structured transfer events (TransferObserved) and opt-in frame capture; LogFormatted defers string interpolation until logging is enabled.
  • Extensibility: register custom providers via RegisterUsbApi / UsbApiRegistry.
  • Built-in CLI: FirmwareKit.Comm.CLI with apis, devices, all-devices, monitor, selftest, io-test, and interrupt-test commands.

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 (IOKit) macOS IOKit.framework classic API Works on every macOS; device open follows the reference implementation (interface-level only)
native (HarmonyOS) HarmonyOS USBManager DDK Opt-in via FIRMWAREKIT_USB_ENABLE_HARMONY=1; requires OH_Usb_Init() to succeed
libusb all LibUsbDotNet Native async transfers; needs the native libusb runtime (bundled per-RID in the package, or pass an explicit path via UsbCommunicationLayer.SetLibusbLibraryPath); degrades gracefully when absent

HarmonyOS is hidden from GetAvailableApis() by default because it cannot be detected reliably with file probes. On macOS the IOKit native backend works on every release; use the libusb backend when the native path is not desired.

Transfer Timeout Semantics

  • The timeout passed to Read / Write (and their ReadInto variants) 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 reach chunkCount × timeoutMs.
  • When an exact number of bytes must be read within a total budget (e.g. fixed-size bootloader 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 ordinary IOException/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.

The async extension overloads that omit the timeout (ReadAsync(length), ReadIntoAsync(...), ReadPacketAsync(...), WriteAsync(...), WriteZlpAsync(), ControlTransferAsync(...), ReadInterruptAsync(...), WriteInterruptAsync(...)) use the session's DefaultTimeoutMs.

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 (pushing payloads over the bulk pipes) and for bootloader loaders.

  • Check whether a ZLP is needed: payloadLength % maxPacketSize == 0 (read MaxPacketSize from the device's Interfaces[i].Endpoints metadata).
  • After such a write, call session.WriteZlp(timeoutMs) (or WriteZlpAsync) to send the terminating zero-length packet.
  • Backends that cannot perform an explicit ZLP write (legacy Windows drivers) throw NotSupportedException; check UsbApiCapabilities / 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 IOKit (native) 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), Linux usbfs (URB + poll) and libusb (LibUsbDotNet async transfers). All other backends — macOS IOKit, 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 (IOKit IOServiceGetMatchingServices 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 vendor bootloader interfaces often use 0xFF/0xFF/0xFF)
var bootloaderLikeDevices = 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 (packet 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, short ReadExact (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 .slnx and requires the .NET 10 SDK.
  • GeneratePackageOnBuild=true emits nupkg/snupkg on every build — never treat bin//obj/ artifacts as sources of truth.
  • tools/fetch-libusb.sh downloads native libusb runtimes into native/<target>/ (mirror flags: --mirror tuna|ustc, --github-mirror, --ghcr-mirror).
  • tools/test-windows.ps1 / tools/test-linux.sh / tools/test-macos.sh are real-device automated test scripts (build + unit tests + CLI smoke + read-only selftest + 3 s monitor hot-plug smoke); they exit 0 with SKIP when 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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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.

Version Downloads Last Updated
1.2.1 65 8/16/2026
1.2.0 97 8/11/2026
1.1.3 80 8/10/2026
1.1.2 85 8/9/2026
1.1.1 85 8/9/2026
1.1.0 84 8/9/2026
1.0.0 609 4/22/2026