CosmoUsb 0.2.0
dotnet add package CosmoUsb --version 0.2.0
NuGet\Install-Package CosmoUsb -Version 0.2.0
<PackageReference Include="CosmoUsb" Version="0.2.0" />
<PackageVersion Include="CosmoUsb" Version="0.2.0" />
<PackageReference Include="CosmoUsb" />
paket add CosmoUsb --version 0.2.0
#r "nuget: CosmoUsb, 0.2.0"
#:package CosmoUsb@0.2.0
#addin nuget:?package=CosmoUsb&version=0.2.0
#tool nuget:?package=CosmoUsb&version=0.2.0
CosmoUsb
Cross-platform, low-level USB device access for .NET 10 — a native C# reimplementation of the Rust nusb library. CosmoUsb talks to the operating system's USB stack directly:
| Platform | Backend | Enumeration | Transfers | Hotplug |
|---|---|---|---|---|
| Linux | usbfs | sysfs | URBs + epoll event loop | udev netlink |
| Windows | WinUSB | cfgmgr32 + hub IOCTLs | overlapped I/O | CM_Register_Notification |
| macOS | IOKit / IOUSBLib | IORegistry | async pipe calls + CFRunLoop | IOServiceAddMatchingNotification |
No libusb. No shelling out. No context object to manage — a shared event loop thread starts lazily when the first device is opened.
All three backends are verified against real hardware — enumeration, device
open, interface claim, control transfers, and bulk transfers
(submit/timeout/cancel) — on macOS (IOKit), Windows 11 (WinUSB, ARM64 and x64),
and Debian 13 (usbfs). Hotplug arrival/removal delivery is exercised end to end
on macOS and Windows; on Linux the netlink watcher's setup/teardown is verified.
See docs/limitations.md for the per-backend details.
Like nusb (and libusb), CosmoUsb is for devices you talk to directly — vendor-class hardware, firmware tools, instruments. Devices with standard-class kernel drivers (mass storage, HID keyboards, audio, ...) belong to the OS.
Quick start
using CosmoUsb;
// Enumerate (no device IO; data comes from the OS cache)
foreach (var device in Usb.ListDevices())
Console.WriteLine($"{device.VendorId:X4}:{device.ProductId:X4} {device.Product}");
// Find and open
var info = Usb.FindDevice(new UsbDeviceSelector { VendorId = 0x1234, ProductId = 0x5678 });
await using var device = await info!.OpenAsync();
// Claim an interface (detaching a kernel driver first on Linux)
await using var iface = await device.DetachAndClaimInterfaceAsync(0);
// Simple transfer API
await using var outEp = iface.OpenEndpoint(0x01, UsbTransferType.Bulk, UsbDirection.Out);
await using var inEp = iface.OpenEndpoint(0x81, UsbTransferType.Bulk, UsbDirection.In);
await outEp.WriteAsync(new byte[] { 1, 2, 3 });
var buffer = new byte[4096];
int received = await inEp.ReadAsync(buffer);
Every blocking operation has both a synchronous form (Open, ClaimInterface,
ControlIn, ...) and an async form (OpenAsync, ...), mirroring nusb's
MaybeFuture design. Transfers are natively asynchronous on every platform.
The transfer queue
For streaming, use the submit/complete queue directly — it keeps multiple transfers in flight so the host controller never goes idle, and completions are always delivered in submission order:
for (int i = 0; i < 4; i++)
inEp.Submit(inEp.AllocateBuffer(16384)); // never throws; errors surface below
while (true)
{
var completion = await inEp.NextCompleteAsync(ct); // cancel-safe
completion.ThrowIfFailed();
Process(completion.Buffer.AsSpan());
completion.Buffer.SetRequestedLength(completion.Buffer.Capacity);
inEp.Submit(completion.Buffer); // reuse the buffer: no allocation
}
AllocateBuffer returns kernel-mapped (DMA-capable) memory on Linux and
pinned-object-heap memory elsewhere, so no copying or pinning happens per
transfer. IN transfer lengths must be a nonzero multiple of
endpoint.MaxPacketSize.
Or wrap an endpoint in a buffered Stream:
using var reader = inEp.ToReader(16384); // NumTransfers = 2+ for continuous streaming
reader.NumTransfers = 4;
int n = await reader.ReadAsync(buffer);
using var writer = outEp.ToWriter(16384);
await writer.WriteAsync(payload);
writer.FlushEnd(); // terminates with a short/zero-length packet
Control transfers
var data = await iface.ControlInAsync(
new UsbControlRequest(UsbControlType.Vendor, UsbRecipient.Interface,
request: 0x01, value: 0x0001, index: iface.InterfaceNumber),
length: 64, timeout: TimeSpan.FromSeconds(1));
On Windows, control transfers are only available on a claimed UsbInterface
(a WinUSB restriction), and interface-recipient requests must carry the
interface number in Index.
Hotplug monitoring
using var watcher = Usb.WatchDevices(); // create first...
var present = Usb.ListDevices(); // ...then list, so nothing is missed
await foreach (var change in watcher)
{
if (change.Type == UsbDeviceChangeType.Arrived)
Console.WriteLine($"arrived: {change.Device}");
else
Console.WriteLine($"removed: {change.DeviceId}");
}
Removal events carry only the UsbDeviceId; keep a dictionary if you need
details at removal time.
Platform requirements
Linux
Users need write access to the device node /dev/bus/usb/BBB/DDD. Typical
udev rule (/etc/udev/rules.d/70-myproduct.rules):
SUBSYSTEM=="usb", ATTRS{idVendor}=="1234", ATTRS{idProduct}=="5678", MODE="0660", TAG+="uaccess"
DetachAndClaimInterface detaches a bound kernel driver (e.g. usbhid)
atomically; the driver is reattached on release.
Windows
The device (or, for composite devices, the target function) must be bound to
the WinUSB driver — via a WCID/MS OS descriptor in the firmware, an INF,
or a tool like Zadig. Devices with other drivers (or none) can be enumerated
but not opened. SetConfiguration and Reset are not available on Windows,
and the manufacturer string is never cached by the OS.
macOS
Users can access USB devices by default; devices claimed by a kernel driver are not accessible. macOS only auto-configures composite-class devices — for vendor-class devices, run:
try { device.GetActiveConfiguration(); }
catch (UsbException) { device.SetConfiguration(1); }
Error model
Control-plane failures (open, claim, configure) throw UsbException with a
coarse UsbErrorKind (Disconnected, Busy, PermissionDenied, NotFound,
Unsupported, Other) and the raw OS code in NativeErrorCode (errno,
Win32 error, or IOReturn). Data-plane failures are reported per transfer as
UsbTransferStatus (Cancelled, Stall, Disconnected, Fault,
InvalidArgument, Unknown) on the completion, or as UsbTransferException
from the convenience methods. After a Stall, call endpoint.ClearHalt().
NativeAOT
The library uses source-generated P/Invoke throughout, no reflection, and no
runtime code generation. It compiles under NativeAOT with zero trim/AOT
warnings (verified; the UsbList sample builds to a ~1.4 MB self-contained
binary). See docs/nativeaot.md.
Diagnostics
Diagnostics flow through a System.Diagnostics.TraceSource named
"CosmoUsb"; attach a TraceListener and raise the switch level to see
enumeration, transfer, and native-error details. Payload data is never logged.
Documentation
docs/architecture.md— layering and the per-platform event modelsdocs/transfers.md— the transfer queue, buffers, cancellation, timeoutsdocs/linux.md,docs/windows.md,docs/macos.md— backend detailsdocs/error-handling.md— error taxonomy and native code mappingdocs/api-mapping.md— nusb ↔ CosmoUsb compatibility matrixdocs/nativeaot.md— trimming/AOT compatibility reportdocs/performance.md— benchmarks and allocation behaviordocs/limitations.md— known gaps relative to nusbdocs/windows-scanner-setup.md— headless WinUSB binding walkthrough (worked example)
Samples
samples/ contains UsbList, UsbMonitor, UsbControlTransfer,
UsbBulkTransfer, and UsbInterruptTransfer, plus FingerprintCapture — a
real-world demo that drives a ZKTeco Live20R optical fingerprint scanner
(1B55:0120) entirely through CosmoUsb, reproducing the vendor's reverse-
engineered register protocol over control transfers and streaming a genuine
300×400 fingerprint image over bulk IN (verified on Linux and Windows hardware).
License
MIT OR Apache-2.0, matching nusb's dual license.
| 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 was computed. 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
- Microsoft.Bcl.AsyncInterfaces (>= 8.0.0)
- System.Memory (>= 4.5.5)
- System.Runtime.CompilerServices.Unsafe (>= 6.0.0)
- System.Threading.Channels (>= 8.0.0)
- System.Threading.Tasks.Extensions (>= 4.5.4)
-
net10.0
- No dependencies.
NuGet packages (1)
Showing the top 1 NuGet packages that depend on CosmoUsb:
| Package | Downloads |
|---|---|
|
CosmoBiometric.Fingerprint.Zkt
ZKTeco Live20R/SLK20R capture adapter for the CosmoBiometric fingerprint engine, driving the scanner over CosmoUsb (no vendor SDK, no libusb). |
GitHub repositories
This package is not used by any popular GitHub repositories.