Skip to content

Device classes

Ready-made, reusable USB device classes - each an Interface or Function subclass built only on the public device API, and each added the one way anything is added: dev.add(CLASS(...)), which returns the class's data-plane handle. Only the user-facing entry points are shown; see Device Classes for usage.

HID - Human Interface Device

The generic HID interface plus ready-made Report-descriptor builders.

hid

HID (class 0x03) - generic device class (USB HID 1.11).

A generic HID function: hand it any Report descriptor and it presents a proper HID interface - keyboard, mouse, consumer control, or a vendor-defined raw device. It speaks the full HID 1.11 request set over EP0 (GET/SET_REPORT, GET/SET_IDLE, GET/SET_PROTOCOL) plus the required interrupt IN and an optional interrupt OUT for Output reports (HID 1.11 Sec.4.4, Sec.7.2, Appendix G).

Quick start::

from usbip.classes.device import hid
iface = hid.HID(hid.mouse_report_descriptor(),
                subclass=hid.SUBCLASS_BOOT,
                protocol=hid.PROTOCOL_MOUSE)
dev.add(iface)
iface.send_report(bytes([0, 5, 0, 0]))          # buttons, dx, dy, wheel

Subclassing still works::

class Keyboard(hid.HID):
    report_descriptor = hid.keyboard_report_descriptor()

HID

HID(report_descriptor=None, *, subclass=None, protocol=None, in_ep=129, in_mps=64, in_interval=10, out_ep=None, out_mps=64, out_interval=10, country=0, bcd_hid=273, get_report=None, set_report=None, on_output=None)

Bases: Interface

A generic HID interface. Pass a report descriptor (or set the class attribute) and, optionally, callbacks for Get/Set_Report and Output reports.

Source code in usbip/classes/device/hid.py
def __init__(
    self,
    report_descriptor=None,
    *,
    subclass=None,
    protocol=None,
    in_ep=0x81,
    in_mps=64,
    in_interval=10,
    out_ep=None,
    out_mps=64,
    out_interval=10,
    country=0,
    bcd_hid=0x0111,
    get_report=None,
    set_report=None,
    on_output=None,
):
    super().__init__()
    # endpoints are per-instance: their addresses are constructor arguments,
    # not class-level In/Out declarations
    self.in_ep = self._add_endpoint(In(in_ep, "interrupt", in_mps, in_interval))
    self.out_ep = None
    if out_ep is not None:
        self.out_ep = self._add_endpoint(Out(out_ep, "interrupt", out_mps, out_interval))

    if subclass is not None:
        self.bInterfaceSubClass = subclass
    if protocol is not None:
        self.bInterfaceProtocol = protocol
    rd = report_descriptor if report_descriptor is not None else type(self).report_descriptor
    self.report_descriptor = bytes(rd)
    self.country = country
    self.bcd_hid = bcd_hid

    self._get_report_cb = get_report
    self._set_report_cb = set_report
    self._on_output_cb = on_output

    self.protocol = REPORT_PROTOCOL  # default protocol after enumeration
    self._idle = {}  # report-id -> idle duration (x4 ms)
    self._last_input = b""  # last Input report sent (for GET_REPORT)
    self._features = {}  # report-id -> last Feature report
    self.last_output = b""  # last Output report received

bInterfaceClass class-attribute instance-attribute

bInterfaceClass = 3

bInterfaceSubClass class-attribute instance-attribute

bInterfaceSubClass = SUBCLASS_NONE

bInterfaceProtocol class-attribute instance-attribute

bInterfaceProtocol = PROTOCOL_NONE

in_ep instance-attribute

in_ep = self._add_endpoint(In(in_ep, 'interrupt', in_mps, in_interval))

out_ep instance-attribute

out_ep = None

report_descriptor class-attribute instance-attribute

report_descriptor = bytes(rd)

country instance-attribute

country = country

bcd_hid instance-attribute

bcd_hid = bcd_hid

protocol instance-attribute

protocol = REPORT_PROTOCOL

last_output instance-attribute

last_output = b''

extra_descriptors

extra_descriptors()
Source code in usbip/classes/device/hid.py
def extra_descriptors(self) -> bytes:
    return hid_descriptor(
        len(self.report_descriptor), bcd_hid=self.bcd_hid, country=self.country
    )

on_control

on_control(setup, data=b'')
Source code in usbip/classes/device/hid.py
def on_control(self, setup, data=b""):
    if setup.bRequest == 0x06 and setup.type == core.STANDARD:  # GET_DESCRIPTOR
        dt = setup.wValue >> 8
        if dt == HID_DT_REPORT:
            return self.report_descriptor
        if dt == HID_DT_HID:
            return self.extra_descriptors()
        raise Stall  # e.g. Physical: none

    if setup.type != core.CLASS:
        raise Stall

    req, rtype, rid = setup.bRequest, setup.wValue >> 8, setup.wValue & 0xFF
    if req == GET_REPORT:
        return self._get_report(rtype, rid, setup.wLength)
    if req == SET_REPORT:
        self._set_report(rtype, rid, bytes(data))
        return b""
    if req == GET_IDLE:
        return bytes([self._idle.get(rid, 0)])
    if req == SET_IDLE:  # wValue hi = duration
        self._idle[rid] = setup.wValue >> 8
        return b""
    if req == GET_PROTOCOL:
        return bytes([self.protocol])
    if req == SET_PROTOCOL:
        self.protocol = setup.wValue & 0xFF
        return b""
    raise Stall

on_out

on_out(ep, data)
Source code in usbip/classes/device/hid.py
def on_out(self, ep, data):
    self._handle_output(bytes(data))

send_report

send_report(report)
Source code in usbip/classes/device/hid.py
def send_report(self, report: bytes):
    self._last_input = bytes(report)
    self.in_ep.write(report)

keyboard_report_descriptor

keyboard_report_descriptor()

Boot-protocol keyboard: 8-byte Input report (modifier bitmap, reserved byte, six key codes) + 1-byte Output report (Num/Caps/Scroll Lock + Compose/Kana LEDs).

Source code in usbip/classes/device/hid.py
def keyboard_report_descriptor() -> bytes:
    """Boot-protocol keyboard: 8-byte Input report (modifier bitmap, reserved byte,
    six key codes) + 1-byte Output report (Num/Caps/Scroll Lock + Compose/Kana LEDs)."""
    return b"".join([
        usage_page(0x01), usage(0x06),          # Generic Desktop, Keyboard
        collection(0x01),                       # Application
        usage_page(0x07),                       # Keyboard/Keypad
        usage_min(0xE0), usage_max(0xE7),       # Left Control .. Right GUI
        logical_min(0), logical_max(1),
        report_size(1), report_count(8), input_(0x02),    # 8 modifier bits
        report_count(1), report_size(8), input_(0x03),    # reserved byte
        report_count(5), report_size(1),
        usage_page(0x08), usage_min(1), usage_max(5),      # LED page: NumLock .. Kana
        output(0x02),                                      # 5 LED bits
        report_count(1), report_size(3), output(0x03),     # 3 bits padding
        report_count(6), report_size(8),
        logical_min(0), logical_max(101),
        usage_page(0x07), usage_min(0), usage_max(101),
        input_(0x00),                                      # 6 key codes (array)
        end_collection(),
    ])

mouse_report_descriptor

mouse_report_descriptor()

Boot-compatible mouse: 4-byte report = [buttons, dx, dy, wheel].

Source code in usbip/classes/device/hid.py
def mouse_report_descriptor() -> bytes:
    """Boot-compatible mouse: 4-byte report = [buttons, dx, dy, wheel]."""
    return b"".join([
        usage_page(0x01), usage(0x02),          # Generic Desktop, Mouse
        collection(0x01),                       # Application
        usage(0x01),                            # Pointer
        collection(0x00),                       # Physical
        usage_page(0x09),                       # Buttons
        usage_min(1), usage_max(3),
        logical_min(0), logical_max(1),
        report_count(3), report_size(1), input_(0x02),   # 3 button bits
        report_count(1), report_size(5), input_(0x03),   # 5 bits padding
        usage_page(0x01),                       # Generic Desktop
        usage(0x30), usage(0x31), usage(0x38),  # X, Y, Wheel
        logical_min(-127), logical_max(127),
        report_size(8), report_count(3), input_(0x06),   # relative X/Y/wheel
        end_collection(),
        end_collection(),
    ])

consumer_report_descriptor

consumer_report_descriptor()

Consumer control: 1-byte report, 8 media-key bits (play/next/prev/stop/ mute/vol+/vol-/eject).

Source code in usbip/classes/device/hid.py
def consumer_report_descriptor() -> bytes:
    """Consumer control: 1-byte report, 8 media-key bits (play/next/prev/stop/
    mute/vol+/vol-/eject)."""
    keys = [0xCD, 0xB5, 0xB6, 0xB7, 0xE2, 0xE9, 0xEA, 0xB8]
    return b"".join([
        usage_page(0x0C), usage(0x01),          # Consumer, Consumer Control
        collection(0x01),                       # Application
        logical_min(0), logical_max(1),
        report_size(1), report_count(8),
        *[usage(k) for k in keys],
        input_(0x02),                           # 8 momentary bits
        end_collection(),
    ])

vendor_report_descriptor

vendor_report_descriptor(in_size=8, out_size=8, usage_page_id=65280)

A vendor-defined raw HID device: in_size-byte Input report + out_size-byte Output report on a vendor usage page. Round-trips bytes via interrupt IN/OUT and Get/Set_Report; no OS HID consumer claims it.

Source code in usbip/classes/device/hid.py
def vendor_report_descriptor(in_size=8, out_size=8, usage_page_id=0xFF00) -> bytes:
    """A vendor-defined raw HID device: `in_size`-byte Input report + `out_size`-byte
    Output report on a vendor usage page. Round-trips bytes via interrupt IN/OUT
    and Get/Set_Report; no OS HID consumer claims it."""
    items = [
        usage_page(usage_page_id), usage(0x01),
        collection(0x01),                       # Application
        logical_min(0), logical_max(255),       # 0..255 -> 2-byte logical_max
        report_size(8),
        usage(0x01), report_count(in_size), input_(0x02),
    ]
    if out_size:
        items += [usage(0x01), report_count(out_size), output(0x02)]
    items.append(end_collection())
    return b"".join(items)

CDC-ACM - Virtual serial port

dev.add(CDCACM(...)) attaches a port and returns it; transmit with the port's write().

cdc_acm

CDC-ACM (virtual serial port) - device class (Communications + Data interfaces).

The class implements the CDC protocol; the app supplies callbacks for the events it cares about (port opened/closed, line coding changed, bytes received) and uses the port's write() to transmit. Mirrors the C library's src/classes/device/cdc_acm.c - protocol handling lives here, not in the example. Built only on the public device API.

CDCACM

CDCACM(on_rx=None, on_open=None, on_close=None, on_line_coding=None, name=None)

Bases: Function

A CDC-ACM serial port: a two-interface function (Communications + Data) bound by a CDC Union functional descriptor. Mirrors the C cdc_acm function.

The pair must reach the host as ONE function or no COM port forms on Windows, so the function is named either by the device-descriptor triple (when it is alone on the device) or by an IAD (on a USBDevice.set_composite() device, where the triple is pinned to EF/02/01). Linux pairs the interfaces from the Union functional descriptor and needs neither.

dev.add(CDCACM(...)) returns the Data interface - the port you write to; the callbacks receive that same object as port, and name labels it (iInterface / IAD iFunction). Mirrors C cdc_acm_add(), which returns a cdc_port *.

Source code in usbip/classes/device/cdc_acm.py
def __init__(self, on_rx=None, on_open=None, on_close=None, on_line_coding=None, name=None):
    self.data = CDCData(on_rx)
    self.comm = CDCComm(
        self.data, on_open=on_open, on_close=on_close, on_line_coding=on_line_coding
    )
    self.interfaces = (self.comm, self.data)  # interface 0: Comm, interface 1: Data
    self.name = name
    super().__init__()

iad_on_composite class-attribute instance-attribute

iad_on_composite = True

data instance-attribute

data = CDCData(on_rx)

comm instance-attribute

comm = CDCComm(self.data, on_open=on_open, on_close=on_close, on_line_coding=on_line_coding)

interfaces instance-attribute

interfaces = (self.comm, self.data)

name instance-attribute

name = name

primary property

primary

CDCData

CDCData(on_rx=None)

Bases: Interface

CDC Data interface: bulk in/out. Received bytes go to on_rx, or echo if none.

Source code in usbip/classes/device/cdc_acm.py
def __init__(self, on_rx=None):
    super().__init__()
    self._on_rx = on_rx

bInterfaceClass class-attribute instance-attribute

bInterfaceClass = 10

in_ep class-attribute instance-attribute

in_ep = In(129, 'bulk', mps=64)

out_ep class-attribute instance-attribute

out_ep = Out(1, 'bulk', mps=64)

adjust_for_speed

adjust_for_speed(speed)
Source code in usbip/classes/device/cdc_acm.py
def adjust_for_speed(self, speed):
    self.in_ep.mps = self.out_ep.mps = 512 if speed >= SPEED_HIGH else 64  # HS bulk = 512

on_out

on_out(ep, data)
Source code in usbip/classes/device/cdc_acm.py
def on_out(self, ep, data: bytes):
    if self._on_rx:
        self._on_rx(self, data)
    else:
        self.in_ep.write(data)  # default: echo

write

write(data)

Transmit bytes to the host (device -> host).

Source code in usbip/classes/device/cdc_acm.py
def write(self, data: bytes):
    """Transmit bytes to the host (device -> host)."""
    self.in_ep.write(data)

MSC - Mass storage (SCSI / Bulk-Only Transport)

The disk is backed by an application-supplied block store (see the FileStore in examples/msc_device.py); the class itself does no filesystem access. Pass a list of stores for a multi-LUN disk - one logical unit each, each its own drive on the host.

msc

Mass Storage Class - device class: Bulk-Only Transport + SCSI (SBC/MMC).

The class is generic: it runs the BOT state machine (CBW -> data -> CSW) and a SCSI command set, delegating block I/O to a duck-typed store:

store.num_blocks    -> int
store.block_size    -> int
store.read(lba, count)        -> bytes
store.write(lba, count, data) -> None

Hand it several stores and the interface carries several logical units - one pair of bulk pipes, one command at a time, but a separate medium behind each LUN (Linux gives every unit its own /dev/sd*, Windows its own drive letter). A store may also carry medium, read_only, product or serial attributes, which override for that unit alone what the interface was built with.

No store ships with the class - no filesystem access happens here; the application supplies one (see examples/device/msc_device.py for FileStore, an image-file backend). The app also supplies an on_command(text) callback for human-readable logging. Per USB MSC BOT 1.0 and SCSI SPC/SBC.

MSC

MSC(stores, *, read_only=False, medium=MEDIUM_DISK, ufi=False, on_command=None, vendor='USB-IP', product=None, revision='0001', serial='0123456789ABCDEF', name=None)

Bases: Interface

stores is one block store, or a list of them - one logical unit each. medium is "disk" (the default), "cdrom" or "floppy", and with several units it is the default a store can override with a medium attribute of its own (as it can read_only, product and serial). ufi declares the UFI command set (bInterfaceSubClass 0x04, SFF-8070i) instead of SCSI transparent (0x06) - the subclass a real USB floppy drive reports, and what makes Windows show the drive as a floppy rather than a removable disk. It describes the interface, so it covers every unit. The transport stays Bulk-Only either way; UFI-over-CBI is not offered. The UFI commands themselves (FORMAT UNIT, READ/WRITE(12), VERIFY, SEEK, REZERO UNIT) are answered whatever the subclass, as they are legal SCSI too.

Source code in usbip/classes/device/msc.py
def __init__(
    self,
    stores,
    *,
    read_only=False,
    medium=MEDIUM_DISK,
    ufi=False,
    on_command=None,
    vendor="USB-IP",
    product=None,
    revision="0001",
    serial="0123456789ABCDEF",
    name=None,
):
    """`stores` is one block store, or a list of them - one logical unit each.
    `medium` is "disk" (the default), "cdrom" or "floppy", and with several
    units it is the default a store can override with a `medium` attribute of
    its own (as it can `read_only`, `product` and `serial`). `ufi` declares
    the UFI command set (bInterfaceSubClass 0x04, SFF-8070i) instead of SCSI
    transparent (0x06) - the subclass a real USB floppy drive reports, and
    what makes Windows show the drive as a floppy rather than a removable
    disk. It describes the interface, so it covers every unit. The transport
    stays Bulk-Only either way; UFI-over-CBI is not offered. The UFI commands
    themselves (FORMAT UNIT, READ/WRITE(12), VERIFY, SEEK, REZERO UNIT) are
    answered whatever the subclass, as they are legal SCSI too."""
    super().__init__()
    if medium not in _MEDIA:
        raise ValueError(f"medium must be one of {_MEDIA}, not {medium!r}")
    self.medium = medium
    self.ufi = ufi
    if ufi:
        self.bInterfaceSubClass = 0x04
    self.read_only = read_only
    self.on_command = on_command or (lambda text: None)
    self.vendor = vendor
    self.product = product
    self.revision = revision
    self.serial = serial  # INQUIRY EVPD page 0x80, which Windows asks for
    self.name = name  # iInterface label, mirrors C msc_opts.name
    stores = list(stores) if isinstance(stores, (list, tuple)) else [stores]
    if not 1 <= len(stores) <= MAX_LUNS:
        raise ValueError(f"a mass-storage interface carries 1..{MAX_LUNS} units")
    self.luns = [Lun(store, self) for store in stores]
    self._cbw_in = False  # the current command's data phase is device->host
    # The transport is shared by every unit, so a write in flight names the
    # unit it lands on: dict(lun,tag,lba,count,need,buf)
    self._write = None

bInterfaceClass class-attribute instance-attribute

bInterfaceClass = 8

bInterfaceSubClass class-attribute instance-attribute

bInterfaceSubClass = 6

bInterfaceProtocol class-attribute instance-attribute

bInterfaceProtocol = 80

in_ep class-attribute instance-attribute

in_ep = In(130, 'bulk', mps=64)

out_ep class-attribute instance-attribute

out_ep = Out(1, 'bulk', mps=64)

medium instance-attribute

medium = medium

ufi instance-attribute

ufi = ufi

read_only instance-attribute

read_only = read_only

on_command instance-attribute

on_command = on_command or (lambda text: None)

vendor instance-attribute

vendor = vendor

product instance-attribute

product = product

revision instance-attribute

revision = revision

serial instance-attribute

serial = serial

name instance-attribute

name = name

luns instance-attribute

luns = [Lun(store, self) for store in stores]

adjust_for_speed

adjust_for_speed(speed)
Source code in usbip/classes/device/msc.py
def adjust_for_speed(self, speed):
    self.in_ep.mps = self.out_ep.mps = 512 if speed >= SPEED_HIGH else 64  # HS bulk = 512

on_control

on_control(setup, data=b'')
Source code in usbip/classes/device/msc.py
def on_control(self, setup, data=b""):
    if setup.bRequest == GET_MAX_LUN:  # the LAST unit's number
        return bytes([len(self.luns) - 1])
    if setup.bRequest == 0xFF:  # Bulk-Only Mass Storage Reset
        self._write = None
        return b""
    raise Stall

on_out

on_out(ep, data)
Source code in usbip/classes/device/msc.py
def on_out(self, ep, data):
    if self._write is not None:
        self._recv_write(data)
    else:
        self._handle_cbw(bytes(data))

Lun

Lun(store, iface)

One logical unit: its store, how it presents itself, and its own sense data.

Each presentation attribute is taken from the store when it carries one and from the interface otherwise, so a device can mix media - a disk beside its install CD - without a second interface. Sense is per unit, as SCSI requires: a failure on one LUN must not answer another LUN's REQUEST SENSE.

Source code in usbip/classes/device/msc.py
def __init__(self, store, iface):
    self.store = store
    self.medium = getattr(store, "medium", None) or iface.medium
    self.read_only = (
        bool(getattr(store, "read_only", False))
        or iface.read_only
        or self.medium == MEDIUM_CDROM  # a CD-ROM is read-only by definition
    )
    self.product = (
        getattr(store, "product", None)
        or iface.product
        or {MEDIUM_CDROM: "CD-ROM", MEDIUM_FLOPPY: "FLOPPY"}.get(self.medium, "DISK")
    )
    self.serial = getattr(store, "serial", None) or iface.serial
    self.sense = SENSE_OK

store instance-attribute

store = store

medium instance-attribute

medium = getattr(store, 'medium', None) or iface.medium

read_only instance-attribute

read_only = bool(getattr(store, 'read_only', False)) or iface.read_only or self.medium == MEDIUM_CDROM

product instance-attribute

product = getattr(store, 'product', None) or iface.product or {MEDIUM_CDROM: 'CD-ROM', MEDIUM_FLOPPY: 'FLOPPY'}.get(self.medium, 'DISK')

serial instance-attribute

serial = getattr(store, 'serial', None) or iface.serial

sense instance-attribute

sense = SENSE_OK

MTP - Media Transfer Protocol

dev.add(MTP(...)) attaches one or more storages, each an application-supplied backend (see the FilesystemStore in examples/mtp_device.py).

mtp

USB MTP (Media Transfer Protocol) v1.1 - device class.

Exports a host directory tree as an MTP storage: Windows Explorer, libmtp (mtp-detect/mtp-files) and gphoto2 can browse it, download files, and - unless read-only - upload and delete. MTP is layered on PTP: it rides the Still Image interface (class 0x06 / sub 0x01 / proto 0x01) and a bulk container protocol (command -> optional data -> response), with an interrupt IN endpoint for events. Everything is bulk/interrupt, so no USB/IP wire support is needed.

With winusb=True (the default) the function advertises the Microsoft OS "MTP" Compatible ID, which makes Windows bind its MTP/WPD driver with no INF.

Mirrors the C classes/device/mtp.c. The MTP protocol and object model live here; the actual storage is a pluggable backend so no filesystem access happens in this class - dev.add(MTP(store)) takes the application's own store, e.g. the FilesystemStore in examples/device/mtp_device.py.

MTP

MTP(store, *, name='USBIP MTP', manufacturer='USB over IP', serial=None, winusb=True, on_event=None)

Bases: Interface

An MTP interface backed by store - an application-supplied storage backend (the example's FilesystemStore) or an already-built :class:MtpStorage. With winusb=True (default) the function advertises the Microsoft OS "MTP" Compatible ID so Windows binds its MTP driver automatically.

Source code in usbip/classes/device/mtp.py
def __init__(
    self,
    store,
    *,
    name="USBIP MTP",
    manufacturer="USB over IP",
    serial=None,
    winusb=True,
    on_event=None,
):
    super().__init__()
    self.storage = store if isinstance(store, MtpStorage) else MtpStorage(store)
    self.winusb = winusb
    self.model = name
    self.manufacturer = manufacturer
    self.serial = (serial or "0123456789ABCDEF0123456789ABCDEF")[:32].ljust(32, "0")
    self.friendly_name = name
    self.sync_partner = ""
    self.on_event = on_event or (lambda text: None)
    self.session = 0
    self._rx = None  # pending data-out phase, or None
    self._send_target = None  # handle reserved by SendObjectInfo for SendObject

bInterfaceClass class-attribute instance-attribute

bInterfaceClass = 6

bInterfaceSubClass class-attribute instance-attribute

bInterfaceSubClass = 1

bInterfaceProtocol class-attribute instance-attribute

bInterfaceProtocol = 1

out_ep class-attribute instance-attribute

out_ep = Out(1, 'bulk', mps=64)

in_ep class-attribute instance-attribute

in_ep = In(129, 'bulk', mps=64)

intr_ep class-attribute instance-attribute

intr_ep = In(130, 'interrupt', mps=28, interval=6)

storage instance-attribute

storage = store if isinstance(store, MtpStorage) else MtpStorage(store)

winusb instance-attribute

winusb = winusb

model instance-attribute

model = name

manufacturer instance-attribute

manufacturer = manufacturer

serial instance-attribute

serial = (serial or '0123456789ABCDEF0123456789ABCDEF')[:32].ljust(32, '0')

friendly_name instance-attribute

friendly_name = name

sync_partner instance-attribute

sync_partner = ''

on_event instance-attribute

on_event = on_event or (lambda text: None)

session instance-attribute

session = 0

adjust_for_speed

adjust_for_speed(speed)
Source code in usbip/classes/device/mtp.py
def adjust_for_speed(self, speed):
    self.in_ep.mps = self.out_ep.mps = 512 if speed >= SPEED_HIGH else 64  # HS bulk = 512

on_out

on_out(ep, data)
Source code in usbip/classes/device/mtp.py
def on_out(self, ep, data):
    data = bytes(data)
    if self._rx is None:
        self._dispatch(data)
        return
    rx = self._rx
    rx["buf"] += data
    if rx["need"] is None and len(rx["buf"]) >= 12:
        rx["need"] = int.from_bytes(rx["buf"][0:4], "little")
    if rx["need"] is not None and len(rx["buf"]) >= rx["need"]:
        self._rx = None
        self._complete_rx(rx, bytes(rx["buf"][12 : rx["need"]]))

UAC - USB Audio Class 1.0

dev.add(UAC(...)) attaches a speaker + microphone audio interface.

uac

USB Audio Class 1.0 (UAC1) - device class: speaker + microphone.

Mirrors the C classes/device/uac.c. Presents an AudioControl interface (a USB->Speaker playback chain and a Microphone->USB capture chain, each with a master mute+volume Feature Unit) plus two AudioStreaming interfaces: a stereo speaker over an isochronous OUT endpoint and a mono microphone over an isochronous IN endpoint, both 48 kHz / 16-bit PCM. The microphone streams a built-in 440 Hz synthetic tone (or app-supplied frames); the speaker is a sink that meters the level and hands the PCM to the app.

Descriptor layout follows the UAC1 spec and the kernel UAC1 gadget (drivers/usb/gadget/function/f_uac1.c). Use dev.add(UAC(...)).

UAC

UAC(mic_source=None, spk_sink=None, on_event=None)

Bases: Function

A UAC1 audio device: AudioControl + AudioStreaming (out/in) interfaces, bound by the AudioControl interface's collection - no IAD, no device triple (UAC1 predates the IAD). Mirrors the C uac function.

dev.add(UAC(...)) returns the AudioControl interface, with .speaker / .microphone attached for convenience.

Source code in usbip/classes/device/uac.py
def __init__(self, mic_source=None, spk_sink=None, on_event=None):
    on_event = on_event or (lambda text: None)
    self.control = AudioControl(on_event=on_event)
    self.speaker = AudioStreamingOut(self.control, sink=spk_sink, on_event=on_event)
    self.microphone = AudioStreamingIn(self.control, source=mic_source, on_event=on_event)
    self.control.speaker, self.control.microphone = self.speaker, self.microphone
    self.interfaces = (self.control, self.speaker, self.microphone)  # interfaces 0, 1, 2
    super().__init__()

control instance-attribute

control = AudioControl(on_event=on_event)

speaker instance-attribute

speaker = AudioStreamingOut(self.control, sink=spk_sink, on_event=on_event)

microphone instance-attribute

microphone = AudioStreamingIn(self.control, source=mic_source, on_event=on_event)

interfaces instance-attribute

interfaces = (self.control, self.speaker, self.microphone)

primary property

primary

UVC - USB Video Class (webcam)

dev.add(UVC(...)) attaches a camera (YUYV and/or MJPEG).

uvc

USB Video Class (webcam) - device class: isochronous streaming.

Mirrors the C classes/device/uvc.c. Presents a VideoControl + VideoStreaming interface pair (grouped by an IAD), advertising YUY2 and/or MJPEG at one resolution, runs Probe/Commit negotiation, and streams UVC payloads over an isochronous IN endpoint. The frame source is the built-in animated color-bar generator unless a source callback is supplied.

Descriptor layout follows the kernel UVC gadget (drivers/usb/gadget/legacy/ webcam.c) and uapi/linux/usb/video.h. Use dev.add(UVC(...)).

UVC

UVC(width=320, height=240, fps=15, formats=('yuyv',), source=None, on_event=None)

Bases: Function

A UVC camera: a composite function (VideoControl + VideoStreaming) grouped by an Interface Association Descriptor. The IAD and the 0xEF/0x02/0x01 device triple are emitted by the Function machinery (opt-in), not hand-rolled by the interfaces.

dev.add(UVC(...)) returns the VideoStreaming interface (the streaming endpoint owner).

Source code in usbip/classes/device/uvc.py
def __init__(
    self, width=320, height=240, fps=15, formats=("yuyv",), source=None, on_event=None
):
    self.vc = VideoControl()
    self.vs = VideoStreaming(width, height, fps, formats, source, on_event)
    self.interfaces = (self.vc, self.vs)
    super().__init__()

iad class-attribute instance-attribute

iad = True

device_triple class-attribute instance-attribute

device_triple = (239, 2, 1)

vc instance-attribute

vc = VideoControl()

vs instance-attribute

vs = VideoStreaming(width, height, fps, formats, source, on_event)

interfaces instance-attribute

interfaces = (self.vc, self.vs)

primary property

primary

DFU - Device Firmware Upgrade

dev.add(DFU(...)) attaches a DFU target table.

dfu

USB DFU (Device Firmware Upgrade), DFU 1.1 - device class.

Presents a DFU-mode device (bInterfaceProtocol 0x02, already in dfuIDLE) that dfu-util can UPLOAD from and DOWNLOAD to. One alternate setting per target. The class makes NO assumption about what backs a target - a file, a flash part, whatever: each target is an application-supplied object exposing a small linear-byte-store protocol (see examples/device/dfu_device.py for a file backend). DFU is entirely EP0 control transfers, so this needs no USB/IP transport support.

A target object must provide

name str, the iInterface label (dfu-util -l) i_string int, the string index (set for you when added) length int, bytes currently stored (read by the class) write(off, data) store bytes at byte offset off (raise IndexError if full) read(off, size) -> bytes up to size bytes at off (b"" at/after the end) begin_download() a fresh download is starting (discard the old image) finish_download() the download finished (commit/flush)

With winusb=True (the default) the function advertises WinUSB, so Windows auto-binds the WinUSB driver and dfu-util works without Zadig.

Mirrors the C classes/device/dfu.c. Use dev.add(DFU(targets=[...])).

DFU

DFU(targets, transfer_size=1024, attributes=ATTR_DEFAULT, detach_timeout=1000, winusb=True, on_event=None)

Bases: Interface

A DFU interface, one alternate setting per target. targets is a list of app-supplied target objects (see the module docstring for the protocol; the example provides a file backend). With winusb=True (default) the function advertises WinUSB so dfu-util works on Windows without Zadig.

Source code in usbip/classes/device/dfu.py
def __init__(
    self,
    targets,
    transfer_size=1024,
    attributes=ATTR_DEFAULT,
    detach_timeout=1000,
    winusb=True,
    on_event=None,
):
    super().__init__()
    self.targets = list(targets)
    self.transfer_size = transfer_size
    self.attributes = attributes
    self.detach_timeout = detach_timeout
    self.winusb = winusb
    self.on_event = on_event or (lambda text: None)
    self.cur = 0
    self.state = dfuIDLE
    self.status = OK

bInterfaceClass class-attribute instance-attribute

bInterfaceClass = 254

bInterfaceSubClass class-attribute instance-attribute

bInterfaceSubClass = 1

bInterfaceProtocol class-attribute instance-attribute

bInterfaceProtocol = 2

targets instance-attribute

targets = list(targets)

transfer_size instance-attribute

transfer_size = transfer_size

attributes instance-attribute

attributes = attributes

detach_timeout instance-attribute

detach_timeout = detach_timeout

winusb instance-attribute

winusb = winusb

on_event instance-attribute

on_event = on_event or (lambda text: None)

cur instance-attribute

cur = 0

state instance-attribute

state = dfuIDLE

status instance-attribute

status = OK

target property

target

descriptor_block

descriptor_block()
Source code in usbip/classes/device/dfu.py
def descriptor_block(self) -> bytes:
    from ... import core

    ifnum = self.interface_number
    blk = b"".join(
        core.InterfaceDescriptor(
            bInterfaceNumber=ifnum,
            bAlternateSetting=alt,
            bNumEndpoints=0,
            bInterfaceClass=self.bInterfaceClass,
            bInterfaceSubClass=self.bInterfaceSubClass,
            bInterfaceProtocol=self.bInterfaceProtocol,
            iInterface=tgt.i_string,
        ).pack()
        for alt, tgt in enumerate(self.targets)
    )
    return blk + self._functional()  # one functional descriptor after the alts

set_alt

set_alt(alt)
Source code in usbip/classes/device/dfu.py
def set_alt(self, alt: int):
    if 0 <= alt < len(self.targets):
        self.cur = alt
        self.state, self.status = dfuIDLE, OK
        self.on_event(f"SELECT {self.target.name} (alt {alt})")

on_control

on_control(setup, data=b'')
Source code in usbip/classes/device/dfu.py
def on_control(self, setup, data=b""):
    if (setup.bmRequestType & 0x60) != 0x20:  # DFU uses class requests only
        raise Stall
    req = setup.bRequest
    if req == DFU_DNLOAD:
        return self._dnload(setup, bytes(data))
    if req == DFU_UPLOAD:
        return self._upload(setup)
    if req == DFU_GETSTATUS:
        if self.state == dfuDNLOAD_SYNC:
            self.state = dfuDNLOAD_IDLE
        elif self.state == dfuMANIFEST_SYNC:  # tolerant: manifest is instant
            self.state = dfuIDLE
        return bytes((self.status, 0, 0, 0, self.state, 0))  # bwPollTimeout = 0
    if req == DFU_GETSTATE:
        return bytes((self.state,))
    if req in (DFU_CLRSTATUS, DFU_ABORT):
        self.state, self.status = dfuIDLE, OK
        return b""
    if req == DFU_DETACH:
        return b""  # no-op in DFU mode
    raise Stall

DFUError

DFUError(status=errWRITE)

Bases: Exception

Raised by a target's write()/read() to report a specific DFU bStatus code: the class catches it, enters dfuERROR, and returns that status on GETSTATUS. A plain IndexError from write() is the simple case and maps to errADDRESS.

Source code in usbip/classes/device/dfu.py
def __init__(self, status=errWRITE):
    super().__init__(f"DFU status 0x{status:02x}")
    self.status = status

status instance-attribute

status = status

Bluetooth - HCI transport

dev.add(Bluetooth(...)) attaches a USB Bluetooth controller transport.

bluetooth

USB Bluetooth (class 0xE0/0x01/0x01) - device-side HCI transport.

Mirrors the C classes/device/bluetooth.c. This is a minimal transport, nothing more: it ferries HCI commands (EP0 class control OUT), HCI events (interrupt IN) and ACL data (bulk OUT/IN) between the USB host and a controller the application supplies. No HCI logic lives here - the class never inspects a command or synthesizes an event.

This follows the Bluetooth USB Transport Layer (Core spec Vol 4 Part B): - device class 0xE0 / subclass 0x01 (RF) / protocol 0x01 (Bluetooth) - interface 0: interrupt-IN (events) + bulk-OUT (ACL out) + bulk-IN (ACL in) - interface 1: SCO isochronous (6 alt settings) - descriptor-only / no-op

Use dev.add(Bluetooth(on_command=..., on_acl=...)).

Bluetooth

Bluetooth(*, on_command=None, on_acl=None, with_sco=True)

Bases: Function

A Bluetooth dongle (HCI transport): the HCI interface plus (for Windows' bthusb) the SCO isochronous interface. The two interfaces are bound by the device class 0xE0/0x01/0x01, not an IAD. Mirrors the C bluetooth function.

dev.add(Bluetooth(...)) sets the device class to 0xE0/0x01/0x01 and returns the BluetoothInterface: call send_event/send_acl to push toward the host, and wire on_command/on_acl to your controller.

Source code in usbip/classes/device/bluetooth.py
def __init__(self, *, on_command=None, on_acl=None, with_sco=True):
    self.hci = BluetoothInterface(on_command, on_acl)
    self.interfaces = (self.hci, _ScoInterface()) if with_sco else (self.hci,)
    super().__init__()

device_triple class-attribute instance-attribute

device_triple = (BT_CLASS, BT_SUBCLASS, BT_PROTOCOL)

hci instance-attribute

hci = BluetoothInterface(on_command, on_acl)

interfaces instance-attribute

interfaces = (self.hci, _ScoInterface()) if with_sco else (self.hci,)

primary property

primary

BluetoothInterface

BluetoothInterface(on_command=None, on_acl=None)

Bases: Interface

Interface 0 - the HCI/ACL transport.

The host sends HCI commands on EP0 (class control OUT) and ACL on the bulk OUT endpoint; the controller sends HCI events on the interrupt IN and ACL on the bulk IN. Wire on_command/on_acl to a controller and call send_event/send_acl to push toward the host.

Source code in usbip/classes/device/bluetooth.py
def __init__(self, on_command=None, on_acl=None):
    super().__init__()
    self.on_command = on_command or (lambda cmd: None)
    self.on_acl = on_acl or (lambda pdu: None)
    self._acl_rx = bytearray()

bInterfaceClass class-attribute instance-attribute

bInterfaceClass = BT_CLASS

bInterfaceSubClass class-attribute instance-attribute

bInterfaceSubClass = BT_SUBCLASS

bInterfaceProtocol class-attribute instance-attribute

bInterfaceProtocol = BT_PROTOCOL

event_in class-attribute instance-attribute

event_in = In(EP_EVENT, 'interrupt', mps=16, interval=1)

acl_out class-attribute instance-attribute

acl_out = Out(EP_ACL_OUT, 'bulk', mps=64)

acl_in class-attribute instance-attribute

acl_in = In(EP_ACL_IN, 'bulk', mps=64)

on_command instance-attribute

on_command = on_command or (lambda cmd: None)

on_acl instance-attribute

on_acl = on_acl or (lambda pdu: None)

on_control

on_control(setup, data=b'')

An HCI command arrives as a class control OUT (bmRequestType 0x20).

Source code in usbip/classes/device/bluetooth.py
def on_control(self, setup, data=b""):
    """An HCI command arrives as a class control OUT (bmRequestType 0x20)."""
    if setup.type == core.CLASS and setup.direction == core.OUT:
        self.on_command(bytes(data))
        return b""
    raise Stall

on_out

on_out(ep, data)

ACL OUT. The host may split one PDU across 64-byte URBs, so reassemble by the 2-byte little-endian data length in the ACL header (offset 2).

Source code in usbip/classes/device/bluetooth.py
def on_out(self, ep, data):
    """ACL OUT. The host may split one PDU across 64-byte URBs, so reassemble
    by the 2-byte little-endian data length in the ACL header (offset 2)."""
    self._acl_rx += bytes(data)
    while len(self._acl_rx) >= 4:
        plen = self._acl_rx[2] | (self._acl_rx[3] << 8)
        if len(self._acl_rx) < 4 + plen:
            break
        pdu = bytes(self._acl_rx[: 4 + plen])
        del self._acl_rx[: 4 + plen]
        self.on_acl(pdu)

on_reset

on_reset()

A host (re)attached - drop any half-assembled ACL so it can't bleed into the new session (the endpoint queues are flushed by the core).

Source code in usbip/classes/device/bluetooth.py
def on_reset(self):
    """A host (re)attached - drop any half-assembled ACL so it can't bleed
    into the new session (the endpoint queues are flushed by the core)."""
    self._acl_rx.clear()

send_event

send_event(evt)

Queue a raw HCI event for the interrupt-IN endpoint.

Source code in usbip/classes/device/bluetooth.py
def send_event(self, evt):
    """Queue a raw HCI event for the interrupt-IN endpoint."""
    self.event_in.write(bytes(evt))

send_acl

send_acl(pdu)

Queue an ACL PDU (4-byte header + payload) for the bulk-IN endpoint.

Source code in usbip/classes/device/bluetooth.py
def send_acl(self, pdu):
    """Queue an ACL PDU (4-byte header + payload) for the bulk-IN endpoint."""
    self.acl_in.write(bytes(pdu))