Skip to content

Class layer

The object-oriented authoring layer over the device core: a Function is one class instance grouping one or more interfaces, and an Interface is one bInterfaceNumber. Single-interface classes need no explicit Function - add() wraps a bare Interface in one. Components → USBDevice explains the model; a new device class is just an Interface subclass (Components → Device classes).

function

The class layer - author reusable USB device classes as objects.

An :class:Interface subclass declares endpoints (:func:~usbip.device.In / :func:~usbip.device.Out), emits class-specific descriptors (extra_descriptors) and handles class/vendor control requests (on_control); a :class:Function groups one or more interfaces into the unit Windows binds a driver to, optionally with an Interface Association Descriptor. Both compose themselves onto a :class:~usbip.device.USBDevice through its public primitives alone (add_group / claim_interface / route_endpoint / on_control / ...) - the device core knows nothing about them. Mirrors the C class layer (classes/usb_class.c) over the class-free device core.

Function

Function(interfaces=None)

A function: one class instance grouping one or more USB :class:Interface\ s, bound as a unit (the USB-IF Interface Association Descriptor model). Owns the child interfaces; a composite function optionally emits an IAD (iad = True) and sets the device-descriptor class triple (device_triple). Most classes are a single interface and need no explicit Function - :meth:USBDevice.add wraps a bare :class:Interface in an anonymous one. Mirrors the C usbip_function.

A subclass either sets self.interfaces = (...) before super().__init__() or passes the list to Function.__init__. Set iad = True plus iad_class / iad_subclass / iad_protocol (and usually device_triple = (0xEF, 0x02, 0x01)) only for a true composite function; CDC/Bluetooth/audio group their interfaces by class-specific means and must NOT emit an IAD.

Source code in usbip/function.py
def __init__(self, interfaces=None):
    self.dev = None
    ifaces = interfaces if interfaces is not None else getattr(self, "interfaces", ())
    self._interfaces = list(ifaces)
    for iface in self._interfaces:
        iface.func = self

iad class-attribute instance-attribute

iad = False

iad_on_composite class-attribute instance-attribute

iad_on_composite = False

iad_class class-attribute instance-attribute

iad_class = 0

iad_subclass class-attribute instance-attribute

iad_subclass = 0

iad_protocol class-attribute instance-attribute

iad_protocol = 0

iad_function_str class-attribute instance-attribute

iad_function_str = 0

device_triple class-attribute instance-attribute

device_triple = None

dev instance-attribute

dev = None

primary property

primary

The handle :meth:USBDevice.add gives back: this class's data-plane object. Default is the function itself; a multi-interface class points at the child that carries the data-plane API (CDCACM -> the Data port, UVC -> the streaming interface, ...), so one line both adds the class and names what you talk to. Mirrors the C <class>_add() return type.

enable_msos

enable_msos(compatible=b'WINUSB', guid=None)

Advertise Microsoft OS descriptors for THIS function (see :meth:USBDevice.enable_msos). On a composite this is what scopes the Compatible ID to this function's interfaces instead of the whole device. Call it after the function has been added to a device.

Source code in usbip/function.py
def enable_msos(self, compatible=b"WINUSB", guid=None):
    """Advertise Microsoft OS descriptors for THIS function (see
    :meth:`USBDevice.enable_msos`). On a composite this is what scopes the
    Compatible ID to this function's interfaces instead of the whole device.
    Call it after the function has been added to a device."""
    self.dev.enable_msos(compatible, guid, function=self)

enable_winusb

enable_winusb(guid=None)

:meth:enable_msos with the "WINUSB" Compatible ID.

Source code in usbip/function.py
def enable_winusb(self, guid=None):
    """:meth:`enable_msos` with the "WINUSB" Compatible ID."""
    self.enable_msos(b"WINUSB", guid or core.DEFAULT_WINUSB_GUID)

association_descriptor

association_descriptor()

The 8-byte Interface Association Descriptor, or b"" when this function emits none - see iad / iad_on_composite.

Source code in usbip/function.py
def association_descriptor(self) -> bytes:
    """The 8-byte Interface Association Descriptor, or ``b""`` when this function
    emits none - see ``iad`` / ``iad_on_composite``."""
    composite = self.dev is not None and self.dev.composite
    if not (self.iad or (self.iad_on_composite and composite)):
        return b""
    first = self._interfaces[0].interface_number
    return struct.pack(
        "<BBBBBBBB",
        8,
        core.DT_INTERFACE_ASSOCIATION,
        first,
        len(self._interfaces),
        self.iad_class,
        self.iad_subclass,
        self.iad_protocol,
        self.iad_function_str,
    )

descriptor_block

descriptor_block()
Source code in usbip/function.py
def descriptor_block(self) -> bytes:
    return self.association_descriptor() + b"".join(
        iface.descriptor_block() for iface in self._interfaces
    )

Interface

Interface()
Source code in usbip/function.py
def __init__(self):
    self.interface_number = 0
    self.dev = None  # set by USBDevice.add()
    self.func = None  # owning Function (set by USBDevice.add())
    self._endpoints = {}
    for name, spec in self._collect_specs().items():
        ep = Endpoint(spec)
        setattr(self, name, ep)  # shadow the class-level spec
        self._endpoints[(ep.number, ep.dir)] = ep

bInterfaceClass class-attribute instance-attribute

bInterfaceClass = 255

bInterfaceSubClass class-attribute instance-attribute

bInterfaceSubClass = 0

bInterfaceProtocol class-attribute instance-attribute

bInterfaceProtocol = 0

string_index class-attribute instance-attribute

string_index = 0

interface_number instance-attribute

interface_number = 0

dev instance-attribute

dev = None

func instance-attribute

func = None

extra_descriptors

extra_descriptors()

Class-specific descriptor bytes appended after the interface desc.

Source code in usbip/function.py
def extra_descriptors(self) -> bytes:
    """Class-specific descriptor bytes appended after the interface desc."""
    return b""

on_control

on_control(setup, data=b'')

Handle a class/vendor control request. data is the OUT payload (for host->device requests). Return bytes (IN) or b'' (OUT ack); raise Stall to reject. Default rejects everything.

Source code in usbip/function.py
def on_control(self, setup: Setup, data: bytes = b""):
    """Handle a class/vendor control request. `data` is the OUT payload (for
    host->device requests). Return bytes (IN) or b'' (OUT ack); raise Stall to
    reject. Default rejects everything."""
    raise Stall

set_alt

set_alt(alt)
Source code in usbip/function.py
def set_alt(self, alt: int):
    pass

adjust_for_speed

adjust_for_speed(speed)

Resize speed-dependent endpoints for the device's reported speed. Called by USBDevice.add() (and USBDevice.set_speed()). Default: no change. Speed-aware classes override this to size bulk endpoints (512 B at HS) or iso endpoints (per-microframe at HS) - both the descriptor and the runtime mps follow from the endpoint's .mps.

Source code in usbip/function.py
def adjust_for_speed(self, speed: int):
    """Resize speed-dependent endpoints for the device's reported speed. Called by
    USBDevice.add() (and USBDevice.set_speed()). Default: no change. Speed-aware classes
    override this to size bulk endpoints (512 B at HS) or iso endpoints (per-microframe
    at HS) - both the descriptor and the runtime mps follow from the endpoint's .mps."""

on_iso

on_iso(ep, packets)

Isochronous data: one call handles one transfer's packets. On an OUT endpoint the host sent data - packets is a list of bytes objects, one per packet; read them (the return value is ignored). On an IN endpoint the host wants data - packets is a list of ints, the size it asked for per packet; return a list of bytes objects, each up to its requested size. An endpoint's direction never changes, so an override only ever sees one of the two cases. Default: no data (IN) / discard (OUT).

Source code in usbip/function.py
def on_iso(self, ep, packets):
    """Isochronous data: one call handles one transfer's packets. On an OUT
    endpoint the host sent data - `packets` is a list of bytes objects, one
    per packet; read them (the return value is ignored). On an IN endpoint
    the host wants data - `packets` is a list of ints, the size it asked for
    per packet; return a list of bytes objects, each up to its requested
    size. An endpoint's direction never changes, so an override only ever
    sees one of the two cases. Default: no data (IN) / discard (OUT)."""
    return []

on_out

on_out(ep, data)

Bulk/interrupt OUT data from the host. Override to consume it as it arrives; the default queues it on the endpoint for :meth:Endpoint.read.

Source code in usbip/function.py
def on_out(self, ep, data):
    """Bulk/interrupt OUT data from the host. Override to consume it as it
    arrives; the default queues it on the endpoint for :meth:`Endpoint.read`."""
    ep._put_out(data)

on_reset

on_reset()

Called when a host attaches (bus reset): endpoint queues are already flushed - override to reset class-level state. Default: nothing.

Source code in usbip/function.py
def on_reset(self):
    """Called when a host attaches (bus reset): endpoint queues are already
    flushed - override to reset class-level state. Default: nothing."""

descriptor_block

descriptor_block()
Source code in usbip/function.py
def descriptor_block(self) -> bytes:
    eps = list(self._endpoints.values())
    blk = core.InterfaceDescriptor(
        bInterfaceNumber=self.interface_number,
        bAlternateSetting=0,
        bNumEndpoints=len(eps),
        bInterfaceClass=self.bInterfaceClass,
        bInterfaceSubClass=self.bInterfaceSubClass,
        bInterfaceProtocol=self.bInterfaceProtocol,
        iInterface=self.string_index,
    ).pack()
    blk += self.extra_descriptors()
    for ep in eps:
        blk += core.EndpointDescriptor(
            bEndpointAddress=ep.addr,
            bmAttributes=core.XFER_BY_NAME[ep.type],
            wMaxPacketSize=ep.mps,
            bInterval=ep.interval,
        ).pack()
    return blk