# Facedancer Documentation

## Getting started with Facedancer

Warning

Facedancer and USBProxy are not currently supported in a Control Host role on Windows with GreatFET.

For more information please see the tracking issue: #170

### Install the Facedancer library

You can install the Facedancer library from the Python Package Index (PyPI), a release archive or directly from source.

#### Install From PyPI

You can use the pip tool to install the Facedancer library from PyPI using the following command:

    pip install facedancer

For more information on installing Python packages from PyPI please refer to the “Installing Packages” section of the Python Packaging User Guide.

#### Install From Source

    git clone https://github.com/greatscottgadgets/facedancer.git
    cd facedancer/

Once you have the source code downloaded you can install the Facedancer library with:

    pip install .

### Run a Facedancer example

Create a new Python file called rubber-ducky.py with the following content:

    import asyncio
    import logging

    from facedancer import main
    from facedancer.devices.keyboard     import USBKeyboardDevice
    from facedancer.classes.hid.keyboard import KeyboardModifiers

    device = USBKeyboardDevice()

    async def type_letters():
        # Wait for device to connect
        await asyncio.sleep(2)

        # Type a string with the device
        await device.type_string("echo hello, facedancer\n")

    main(device, type_letters())

Open a terminal and run:

    python ./rubber-ducky.py

## Library Overview

The Facedancer library may be somewhat overwhelming at first but the modules can be broken down into a number of clearly delineated categories:

### Core USB Device Model

These packages contain the functionality used to define devices and their organisation closely mirrors the hierarchical USB device model:

    +--------------------------------+
    | USB Device                     |
    |   - Device Descriptor          |
    |   - Configuration Descriptor   |
    |      - Interface Descriptor    |     +----------------------------------+
    |         - Endpoint Descriptor  |     | Host                             |
    |           - Request Handler    | --> |   - Function                     |
    |         - Endpoint Descriptor  |     |                                  |
    |           - Request Handler    | <-- |   - Function                     |
    |   - Control Interface          |     |                                  |
    |      - Request Handlers        | <-> |   - Enumeration, Status, Command |
    +--------------------------------+     +----------------------------------+

    (simplified diagram for didactic purposes, not drawn to scale)

- `facedancer.device`

    – `USBDevice` is the device root. It is responsible for managing the device’s descriptors and marshalling host requests.

- `facedancer.configuration`

    – `USBConfiguration` is responsible for managing the device’s configuration descriptor(s).

- `facedancer.interface`

    – `USBInterface` is responsible for managing the device’s interface descriptor(s).

- `facedancer.endpoint`

    – `USBEndpoint` is responsible for managing the device’s endpoints.

- `facedancer.request`

    – `USBControlRequest` is responsible for managing USB control transfers.

In addition to the core device model there are also two modules containing support functionality:

- `facedancer.descriptor`

    – contains functionality for working with USB descriptors.

- `facedancer.magic`

    – contains functionality for Facedancer’s declarative device definition syntax.

### Device Emulation Support

These modules contain a small selection of example USB device classes and device emulations.

- `facedancer.classes`

- `facedancer.devices`

### USB Proxy

These modules contain the USB Proxy implementation.

- `facedancer.proxy`

    – contains the `USBProxyDevice` implementation.

- `facedancer.filters`

    – contains a selection of filters to intercept, view or modify proxied USB transfers.

### Facedancer Board Backends

Contains backend implementations for the various supported Facedancer boards.

- `facedancer.backends`

### Supporting Functionality

- `facedancer.core`

    – the Facedancer scheduler and execution core.

- `facedancer.errors`

    – an error type, there should probably be more.

- `facedancer.types`

    – various type definitions and constants.

- `facedancer.logging`

    – logging boilerplate.

## Using Facedancer

### Introduction

Facedancer allows you to easily define emulations using a simple declarative DSL that mirrors the hierarchical structure of the abstract USB device model.

Let’s look at a simple example that defines a USB device with two endpoints and a control interface:

     7import logging
     8
     9from facedancer import *
    10from facedancer import main
    11
    12@use_inner_classes_automatically
    13class MyDevice(USBDevice):
    14    product_string      : str = "Example USB Device"
    15    manufacturer_string : str = "Facedancer"
    16    vendor_id           : int = 0x1209
    17    product_id          : int = 0x0001
    18    device_speed        : DeviceSpeed = DeviceSpeed.FULL
    19
    20    class MyConfiguration(USBConfiguration):
    21
    22        class MyInterface(USBInterface):
    23
    24            class MyInEndpoint(USBEndpoint):
    25                number          : int          = 1
    26                direction       : USBDirection = USBDirection.IN
    27                max_packet_size : int          = 64
    28
    29                def handle_data_requested(self: USBEndpoint):
    30                    logging.info("handle_data_requested")
    31                    self.send(b"device on bulk endpoint")
    32
    33            class MyOutEndpoint(USBEndpoint):
    34                number          : int          = 1
    35                direction       : USBDirection = USBDirection.OUT
    36                max_packet_size : int          = 64
    37
    38                def handle_data_received(self: USBEndpoint, data):
    39                    logging.info(f"device received {data} on bulk endpoint")
    40
    41    @vendor_request_handler(number=1, direction=USBDirection.IN)
    42    @to_device
    43    def my_in_vendor_request_handler(self: USBDevice, request: USBControlRequest):
    44        logging.info("my_in_vendor_request_handler")
    45        request.reply(b"device on control endpoint")
    46
    47    @vendor_request_handler(number=2, direction=USBDirection.OUT)
    48    @to_device
    49    def my_out_vendor_request_handler(self: USBDevice, request: USBControlRequest):
    50        logging.info(f"device received {request.index} {request.value} {bytes(request.data)} on control endpoint")
    51        request.ack()
    52
    53
    54if __name__ == "__main__":
    55    main(MyDevice)

### Device Descriptor

The entry-point for most Facedancer emulations is the `USBDevice` class which maintains the configuration as well as the transfer handling implementation of the device under emulation.

Note

In some cases you may want to use the `USBBaseDevice` class if you’d like to provide your own implementation of the standard request handlers.

See, for example, `USBProxyDevice`.

Starting with the initial class declaration we can define our device as:

    from facedancer import *

    @use_inner_classes_automatically
    class MyDevice(USBDevice):
        product_string      : str = "Example USB Device"
        manufacturer_string : str = "Facedancer"
        vendor_id           : int = 0x1209 # https://pid.codes/1209/
        product_id          : int = 0x0001

We start by importing the Facedancer library and declaring a class MyDevice derived from `USBDevice`.

We also annotate our class with the `@use_inner_classes_automatically` decorator which allows us to use a declarative style when including our devices configuration, interface and endpoints. It’s `magic`!

Finally, we fill in some basic fields Facedancer will use to populate the device descriptor: `product_string`, `manufacturer_string`, `vendor_id` and `product_id`.

Note

You can find a full list of supported fields in the `USBDevice` API documentation.

### Configuration Descriptor

Once we have provided Facedancer with the basic information it needs to build a device descriptor we can move on to declare and define our device’s configuration descriptor.

Most devices consist of a single configuration managed by the `USBConfiguration` class containing at least one `USBInterface` class containing zero or more `USBEndpoint` class.

Here we define a configuration with a single interface containing two endpoints. The first endpoint has direction `IN` and will be responsible for responding to data requests from the host. The second endpoint has direction `OUT` and will be responsible for receiving data from the host.

    ...
    class MyDevice(USBDevice):
        ...

        class MyConfiguration(USBConfiguration):
            class MyInterface(USBInterface):
                class MyInEndpoint(USBEndpoint):
                    number    : int          = 1
                    direction : USBDirection = USBDirection.IN
                class MyOutEndpoint(USBEndpoint):
                    number    : int          = 1
                    direction : USBDirection = USBDirection.OUT

We’ve now provided enough information in our emulation for it to be successfully enumerated and recognized by the host but there is still one thing missing!

### Request Handlers

For our device to actually do something we also need a way to:

- Respond to a request for data from the host.

- Receive data sent by the host.

Note

USB is a polled protocol where the host always initiates all transactions. Data will only ever be sent from the device if the host has first requested it from the device.

The Facedancer `facedancer.endpoint` and `facedancer.request` modules provides the functionality for responding to requests on the device’s endpoints and the control interface. (All USB devices support a control endpoint – usually endpoint zero.)

#### Endpoint Request Handlers

Endpoint request handlers are usually either class-specific or vendor-defined and can be declared inside the device’s endpoint declaration.

Here we will define two simple handlers for each endpoint.

For our IN endpoint we will reply to any data request from the host with a fixed message and for our OUT endpoint we will just print the received data to the terminal.

    ...
    class MyDevice(USBDevice):
        ...

        class MyConfiguration(USBConfiguration):
            class MyInterface(USBInterface):
                class MyInEndpoint(USBEndpoint):
                    number    : int          = 1
                    direction : USBDirection = USBDirection.IN

                    # called when the host requested data from the device on endpoint 0x81
                    def handle_data_requested(self: USBEndpoint):
                        self.send(b"device sent response on bulk endpoint", blocking=True)

                class MyOutEndpoint(USBEndpoint):
                    number    : int          = 1
                    direction : USBDirection = USBDirection.OUT

                    # called when the host sent data to the device on endpoint 0x01
                    def handle_data_received(self: USBEndpoint, data):
                        logging.info(f"device received '{data}' on bulk endpoint")

For more information on supported endpoint operations and fields see the `USBEndpoint` documentation.

#### Control Request Handlers

Control Requests are typically used for command and status operations. While Facedancer will take care of responding to standard control requests used for device enumeration you may also want to implement custom vendor requests or even override standard control request handling.

To this end, Facedancer provides two sets of decorators to be used when defining a device’s control interface:

The first set of decorators allows you to specify the type of control request to be handled:

- `@control_request_handler`

- `@standard_request_handler`

- `@vendor_request_handler`

- `@class_request_handler`

- `@reserved_request_handler`

The second set defines the target for the control request:

- `@to_device`

- `@to_this_endpoint`

- `@to_any_endpoint`

- `@to_this_interface`

- `@to_any_interface`

- `@to_other`

For instance, to define some vendor request handlers you can do:

    ...
    class MyDevice(USBDevice):
        ...
        class MyConfiguration(USBConfiguration):
        ...

        @vendor_request_handler(request_number=1, direction=USBDirection.IN)
        @to_device
        def my_vendor_request_handler(self: USBDevice, request: USBControlRequest):
            request.reply(b"device sent response on control endpoint")

        @vendor_request_handler(request_number=2, direction=USBDirection.OUT)
        @to_device
        def my_other_vendor_request_handler(self: USBDevice, request: USBControlRequest):
            logging.info(f"device received '{request.index}' '{request.value}' '{request.data}' on control endpoint")

            # acknowledge the request
            request.ack()

More information on the `request` parameter can be found in the `USBControlRequest` documentation.

### Testing The Emulation

We now have a full USB device emulation that will enumerate and respond to requests from the host.

Give it a try!

     1import logging
     2
     3def main():
     4    import asyncio
     5    import usb1
     6
     7    VENDOR_REQUEST    = 0x65
     8    MAX_TRANSFER_SIZE = 64
     9
    10    with usb1.USBContext() as context:
    11        #logging.info("Host: waiting for device to connect")
    12        #await asyncio.sleep(1)
    13
    14        device_handle = context.openByVendorIDAndProductID(0x1209, 0x0001)
    15        if device_handle is None:
    16            raise Exception("device not found")
    17        device_handle.claimInterface(0)
    18
    19        # test IN endpoint
    20        logging.info("Testing bulk IN endpoint")
    21        response = device_handle.bulkRead(
    22            endpoint = 0x81,
    23            length   = MAX_TRANSFER_SIZE,
    24            timeout  = 1000,
    25        )
    26        logging.info(f"[host] received '{response}' from bulk endpoint")
    27        print("")
    28
    29        # test OUT endpoint
    30        logging.info("Testing bulk OUT endpoint")
    31        response = device_handle.bulkWrite(
    32            endpoint = 0x01,
    33            data     = b"host say oh hai on bulk endpoint",
    34            timeout  = 1000,
    35        )
    36        print(f"sent {response} bytes\n")
    37
    38        # test IN vendor request handler
    39        logging.info("Testing IN control transfer")
    40        response = device_handle.controlRead(
    41            request_type = usb1.TYPE_VENDOR | usb1.RECIPIENT_DEVICE,
    42            request      = 1,
    43            index        = 2,
    44            value        = 3,
    45            length       = MAX_TRANSFER_SIZE,
    46            timeout      = 1000,
    47        )
    48        logging.info(f"[host] received '{response}' from control endpoint")
    49        print("")
    50
    51        # test OUT vendor request handler
    52        logging.info("Testing OUT control transfer")
    53        response = device_handle.controlWrite(
    54            request_type = usb1.TYPE_VENDOR | usb1.RECIPIENT_DEVICE,
    55            request      = 2,
    56            index        = 3,
    57            value        = 4,
    58            data         = b"host say oh hai on control endpoint",
    59            timeout      = 1000,
    60        )
    61        print(f"sent {response} bytes\n")
    62
    63
    64if __name__ == "__main__":
    65    logging.getLogger().setLevel(logging.DEBUG)
    66    main()

### Suggestion Engine

Facedancer provides a suggestion engine that can help when trying to map an undocumented device’s control interface.

It works by monitoring the control requests from the host and tracking any which are not supported by your emulation.

You can enable it by passing the –suggest flag when running an emulation:

    python ./emulation.py --suggest

When you exit the emulation it can then suggest the handler functions you still need to implement in order to support the emulated device’s control interface:

    Automatic Suggestions
    ---------------------
    These suggestions are based on simple observed behavior;
    not all of these suggestions may be useful / desirable.

    Request handler code:

    @vendor_request_handler(number=1, direction=USBDirection.IN)
    @to_device
    def handle_control_request_1(self, request):
        # Most recent request was for 64B of data.
        # Replace me with your handler.
        request.stall()

### Annotated template

The Facedancer repository contains an annotated template which provides an excellent reference source when building your own devices:

      8import logging
      9
     10from facedancer         import main
     11from facedancer         import *
     12from facedancer.classes import USBDeviceClass
     13
     14@use_inner_classes_automatically
     15class TemplateDevice(USBDevice):
     16    """ This class is meant to act as a template to help you get acquainted with Facedancer."""
     17
     18    #
     19    # Core 'dataclass' definitions.
     20    # These define the basic way that a Facedancer device advertises itself to the host.
     21    #
     22    # Every one of these is optional. The defaults are relatively sane, so you can mostly
     23    # ignore these unless you want to change them! See the other examples for more minimal
     24    # data definitions.
     25    #
     26
     27    # The USB device class, subclass, and protocol for the given device.
     28    # Often, we'll leave these all set to 0, which means the actual class is read
     29    # from the interface.
     30    #
     31    # Note that we _need_ the type annotations on these. Without them, Python doesn't
     32    # consider them valid dataclass members, and ignores them. (This is a detail of python3.7+
     33    # dataclasses.)
     34    #
     35    device_class             : int  = 0
     36    device_subclass          : int  = 0
     37    protocol_revision_number : int  = 0
     38
     39    # The maximum packet size on EP0. For most devices, the default value of 64 is fine.
     40    max_packet_size_ep0      : int  = 64
     41
     42    # The vendor ID and product ID that we want to give our device.
     43    vendor_id                : int  = 0x610b
     44    product_id               : int  = 0x4653
     45
     46    # The string descriptors we'll provide for our device.
     47    # Note that these should be Python strings, and _not_ bytes.
     48    manufacturer_string      : str  = "Facedancer"
     49    product_string           : str  = "Generic USB Device"
     50    serial_number_string     : str  = "S/N 3420E"
     51
     52    # This tuple is a list of languages we're choosing to support.
     53    # This gives us an opportunity to provide strings in various languages.
     54    # We don't typically use this; so we can leave this set to a language of
     55    # your choice.
     56    supported_languages      : tuple = (LanguageIDs.ENGLISH_US,)
     57
     58    # The revision of the device hardware. This doesn't matter to the USB specification,
     59    # but it's sometimes read by drivers. 0x0001 represents "0.1" in BCD.
     60    device_revision          : int  = 0x0001
     61
     62    # The revision of the USB specification that this device adheres to.
     63    # Typically, you'll leave this at 0x0200 which represents "2.0" in BCD.
     64    usb_spec_version         : int  = 0x0200
     65
     66
     67    #
     68    # We'll define a single configuration on our device. To be compliant,
     69    # every device needs at least a configuration and an interface.
     70    #
     71    # Note that we don't need to do anything special to have this be used.
     72    # As long as we're using the @use_inner_classes_automatically decorator,
     73    # this configuration will automatically be instantiated and used.
     74    #
     75    class TemplateConfiguration(USBConfiguration):
     76
     77        #
     78        # Configuration fields.
     79        #
     80        # Again, all of these are optional; and the default values
     81        # are sane and useful.
     82        #
     83
     84        # Configuration number. Every configuration should have a unique
     85        # number, which should count up from one. Note that a configuration
     86        # shouldn't have a number of 0, as that's USB for "unconfigured".
     87        configuration_number : int            = 1
     88
     89        # A simple, optional descriptive name for the configuration. If provided,
     90        # this is referenced in the configuration's descriptor.
     91        configuration_string : str            = None
     92
     93        # This setting is set to true if the device can run without bus power,
     94        # or false if it pulls its power from the USB bus.
     95        self_powered           : bool         = False
     96
     97        # This setting is set to true if the device can ask that the host
     98        # wake it up from sleep. If set to true, the host may choose to
     99        # leave power on to the device when the host is suspended.
    100        supports_remote_wakeup : bool         = True
    101
    102        # The maximum power the device will use in this configuration, in mA.
    103        # Typically, most devices will request 500mA, the maximum allowed.
    104        max_power              : int            = 500
    105
    106
    107        class TemplateInterface(USBInterface):
    108
    109            #
    110            # Interface fields.
    111            # Again, all optional and with useful defaults.
    112            #
    113
    114            # The interface index. Each interface should have a unique index,
    115            # starting from 0.
    116            number                 : int = 0
    117
    118            # The information about the USB class implemented by this interface.
    119            # This is the place where you'd specify if this is e.g. a HID device.
    120            class_number           : int = USBDeviceClass.VENDOR_SPECIFIC
    121            subclass_number        : int = 0
    122            protocol_number        : int = 0
    123
    124            # A short description of the interface. Optional and typically only informational.
    125            interface_string       : str = None
    126
    127
    128            #
    129            # Here's where we define any endpoints we want to add to the device.
    130            # These behave essentially the same way as the above.
    131            #
    132            class TemplateInEndpoint(USBEndpoint):
    133
    134                #
    135                # Endpoints are unique in that they have two _required_
    136                # properties -- their number and direction.
    137                #
    138                # Together, these two fields form the endpoint's address.
    139                # Endpoint numbers should be > 0, since endpoint 0 is reserved as the default pipe by the spec.
    140                number               : int                    = 1
    141                direction            : USBDirection           = USBDirection.IN
    142
    143                #
    144                # The remainder of the fields are optional and have useful defaults.
    145                #
    146
    147                # The transfer type selects how data will be transferred over the endpoints.
    148                # The currently supported types are BULK and INTERRUPT.
    149                transfer_type        : USBTransferType        = USBTransferType.BULK
    150
    151                # The maximum packet size determines how large packets are allowed to be.
    152                # For a full speed device, a max-size value of 64 is typical.
    153                max_packet_size      : int = 64
    154
    155                # For interrupt endpoints, the interval specifies how often the host should
    156                # poll the endpoint, in milliseconds. 10ms is a typical value.
    157                interval             : int = 0
    158
    159
    160                #
    161                # Let's add an event handler. This one is called whenever the host
    162                # wants to read data from the device.
    163                #
    164                def handle_data_requested(self):
    165
    166                    # We can reply to this request using the .send() method on this
    167                    # endpoint, like so:
    168                    self.send(b"Hello!")
    169
    170                    # We can also get our parent interface using .parent;
    171                    # or a reference to our device using .get_device().
    172
    173
    174            class TemplateOutEndpoint(USBEndpoint):
    175                #
    176                # We'll use a more typical set of properties for our OUT endpoint.
    177                #
    178                number               : int                    = 1
    179                direction            : USBDirection           = USBDirection.OUT
    180
    181
    182                #
    183                # We'll also demonstrate use of another event handler.
    184                # This one is called whenever data is sent to this endpoint.
    185                #
    186                def handle_data_received(self, data):
    187                    logging.info(f"Received data: {data}")
    188
    189
    190    #
    191    # Any of our components can use callback functions -- not just our endpoints!
    192    # The callback names are the same no matter where we use them.
    193    #
    194    def handle_data_received(self, endpoint, data):
    195
    196        #
    197        # When using a callback on something other than an endpoint, our function's
    198        # signature is slightly different -- it takes the relevant endpoint as an
    199        # argument, as well.
    200        #
    201
    202        # We'll delegate this back to the core handler, here, so it propagates to our subordinate
    203        # endpoints -- but we don't have to! If we wanted to, we could call functions on the
    204        # endpoint itself. This is especially useful if we're hooking handle_data_requested(),
    205        # where we can use endpoint.send() to provide the relevant data.
    206        super().handle_data_received(endpoint, data)
    207
    208        # Note that non-endpoints have a get_endpoint() method, which you can use to get references
    209        # to endpoints by their endpoint numbers / directions. This is useful if you want to
    210        # send something on another endpoint in response to data received.
    211        #
    212        # The device also has a .send() method, which accepts an endpoint number and the data to
    213        # be sent. This is equivalent to calling .send() on the relevant endpoint.
    214
    215
    216    #
    217    # We can very, very easily add request handlers to our devices.
    218    #
    219    @vendor_request_handler(number=12)
    220    def handle_my_request(self, request):
    221
    222        #
    223        # By decorating this function with "vendor_request_handler", we've ensured this
    224        # function is called to handle vendor request 12. We can also add other arguments to
    225        # the vendor_request_handler function -- it'll accept a keyword argument for every
    226        # property on the request. If you provide these, the handler will only be called
    227        # if the request matches the relevant constraint.
    228        #
    229        # For example, @vendor_request_handler(number=14, direction=USBDirection.IN, index_low=3)
    230        # means the decorated function is only called to handle vendor request 14 for IN requests
    231        # where the low byte of the index is 3.
    232        #
    233        # Other handler decorators exist -- like "class_request_handler" or "standard_request_handler"
    234        #
    235
    236        # Replying to an IN request is easy -- you just provide the reply data using request.reply().
    237        request.reply(b"Hello, there!")
    238
    239
    240    @vendor_request_handler(number=1, direction=USBDirection.OUT)
    241    @to_device
    242    def handle_another_request(self, request):
    243
    244        #
    245        # Another set of convenience decorators exist to refine requests.
    246        # Decorators like `to_device` or `to_any_endpoint` chain with our
    247        # request decorators, and are syntax sugar for having an argument like
    248        # ``recipient=USBRequestRecipient.DEVICE`` in the handler decorator.
    249        #
    250
    251        # For out requests, in lieu of a response, we typically want to acknowledge
    252        # the request. This can be accomplished by calling .acknowledge() or .ack()
    253        # on the request.
    254        request.ack()
    255
    256        # Of course, if we want to let the host know we can't handle a request, we
    257        # may also choose to stall it. This is as simple as calling request.stall().
    258
    259
    260    #
    261    # Note that request handlers can be used on configurations, interfaces, and
    262    # endpoints as well. For the latter two cases, the decorators `to_this_interface`
    263    # and `to_this_endpoint` are convenient -- they tell a request to run only if
    264    # it's directed at that endpoint in particular, as selected by its ``index`` parameter.
    265    #
    266
    267
    268# Facedancer ships with a default main() function that you can use to set up and run
    269# your device. It ships with some nice features -- including a ``--suggest`` function
    270# that can suggest pieces of boilerplate code that might be useful in device emulation.
    271#
    272# main() will accept either the type of device to emulate, or an device instance.
    273# It'll also accept asyncio coroutines, in case you want to run things alongside the
    274# relevant device code. See e.g. `examples/rubber-ducky.py` for an example.
    275#
    276main(TemplateDevice)

## Using USB Proxy

### Introduction

A major new feature of the newer Facedancer codebase is the ability to MITM (Meddler-In-The-Middle) USB connections – replacing the authors’ original USBProxy project. This opens up a whole new realm of applications – including protocol analysis and live manipulation of USB packets – and is especially useful when you don’t control the software running on the Target Host (e.g. on embedded systems or games consoles).

                     +-----------------------------------------------------------------------+
    +------------+   |  +--------------------------------+   +---------------------------+   |  +--------------+
    |            |   |  |                                |   |                           |   |  |              |
    |  PROXIED   |   |  |         CONTROL HOST           |   |    FACEDANCER DEVICE      |   |  |    TARGET    |
    |    USB     <------>  running Facedancer software   <--->  acts as USB-Controlled   <------>     HOST     |
    |  DEVICE    |   |  |                                |   |      USB Controller       |   |  |              |
    |            |   |  |                                |   |                           |   |  |              |
    +------------+   |  +--------------------------------+   +---------------------------+   |  +--------------+
                     |                                                                       |
                     |                    MITM Setup (HOST + FACEDANCER)                     |
                     +-----------------------------------------------------------------------+

### The Simplest USB Proxy

Note

On macOS USBProxy needs to run as root in order to claim the device being proxied from the operating system.

The simplest use for USB Proxy is to transparently forward USB transactions between the target computer and the proxied device while logging them to the console.

     7from facedancer          import *
     8from facedancer          import main
     9
    10from facedancer.proxy    import USBProxyDevice
    11from facedancer.filters  import USBProxySetupFilters, USBProxyPrettyPrintFilter
    12
    13# replace with the proxied device's information
    14ID_VENDOR=0x09e8
    15ID_PRODUCT=0x0031
    16
    17
    18if __name__ == "__main__":
    19    # create a USB Proxy Device
    20    proxy = USBProxyDevice(idVendor=ID_VENDOR, idProduct=ID_PRODUCT)
    21
    22    # add a filter to forward control transfers between the target host and
    23    # proxied device
    24    proxy.add_filter(USBProxySetupFilters(proxy, verbose=0))
    25
    26    # add a filter to log USB transactions to the console
    27    proxy.add_filter(USBProxyPrettyPrintFilter(verbose=5))
    28
    29    main(proxy)

Setting up a USB Proxy begins by creating an instance of the `USBProxyDevice` with the vendor and product id’s of the proxied device as arguments.

The actual behaviour of USB Proxy is governed by adding `filters` to the proxy that can intercept, read, modify and forward USB transactions between the target computer and proxied device.

The first filter is a `USBProxySetupFilters` which is a simple forwarding filter that ensures all control transfers are forwarded between the target computer and the proxied device. Without the presence of this script the target computer will detect your proxied device but all attempts at enumeration would fail.

The second filter is a `USBProxyPrettyPrintFilter` which will intercept all transactions and then log them to the console.

### Writing USB Proxy Filters

To write your own proxy filter you’d derive a new filter from `USBProxyFilter` and override the request handlers for the transactions you want to intercept.

For example, a simple filter to intercept and modify data from a MIDI controller could look like this:

    from facedancer.filters import USBProxyFilter

    class MyFilter(USBProxyFilter):

        # intercept the midi controllers IN endpoint
        def filter_in(self, ep_num, data):

            # check if the data is from the correct endpoint and a midi message
            if ep_num == (0x82 & 0x7f) and len(data) == 4:

                # check if it is a midi note-on/off message
                if data[1] in [0x80, 0x90]:
                    # transpose the note up by an octave - 7f
                    data[2] += 12

            # return the endpoint number and modified data
            return ep_num, data

Which you can then add to the proxy using `USBProxyDevice`’s `add_filter()` method:

    # add my filter to the proxy
    proxy.add_filter(MyFilter())

You can find more information about the supported handlers in the `USBProxyFilter` documentation.

## Facedancer Examples

Warning

Facedancer and GreatFET are not currently supported with Windows as the Control Host.

Windows is however supported as the Target Host when using Linux or macOS for the Control Host.

For more information please see the tracking issue: #170

There are a number of Facedancer examples available that demonstrate emulation of various USB device functions.

### rubber-ducky.py

The canonical “Hello World” of USB emulation, the rubber-ducky example implements a minimal subset of the USB HID class specification in order to emulate a USB keyboard.

Target Host Compatibility
Linux | macOS | Windows
--- | --- | ---
✅ | ✅ | ✅

### ftdi-echo.py

An emulation of an FTDI USB-to-serial converter, the ftdi-echo example converts input received from a connected terminal to uppercase and echoes the result back to the sender.

Target Host Compatibility
Linux | macOS | Windows
--- | --- | ---
✅ | ❌ | ✅

### mass-storage.py

An emulation of a USB Mass Storage device, the mass-storage example can take a raw disk image file as input and present it to a target host as drive that can be mounted, read and written to.

You can create an empty disk image for use with the emulation using:

    dd if=/dev/zero of=disk.img bs=1M count=100
    mkfs -t ext4 disk.img

You can also test or modify the disk image locally by mounting it with:

    mount -t auto -o loop disk.img /mnt

Remember to unmount it before using it with the device emulation!

Target Host Compatibility
Linux | macOS | Windows
--- | --- | ---
✅ | ✅ | ❌

## How to write a new Facedancer Backend

Facedancer board backends can be found in the facedancer/backends/ directory.

To create a new backend, follow these steps:

### 1. Derive a new backend class

All Facedancer board backends inherit from the `FacedancerApp` and `FacedancerBackend` classes. Begin by deriving your new backend class from these base classes, as shown below:

    from facedancer.core           import FacedancerApp
    from facedancer.backends.base  import FacedancerBackend

    class MydancerBackend(FacedancerApp, FacedancerBackend):

        app_name = "Mydancer"

### 2. Implement backend callback methods

Your new backend must implement the required callback methods defined in the `FacedancerBackend` class. These methods contain the functionality specific to your Facedancer board:

      1from typing    import List
      2from ..        import *
      3
      4
      5class FacedancerBackend:
      6    def __init__(self, device: USBDevice=None, verbose: int=0, quirks: List[str]=[]):
      7        """
      8        Initializes the backend.
      9
     10        Args:
     11            device  :  The device that will act as our Facedancer.   (Optional)
     12            verbose : The verbosity level of the given application. (Optional)
     13            quirks  :  List of USB platform quirks.                  (Optional)
     14        """
     15        raise NotImplementedError
     16
     17
     18    @classmethod
     19    def appropriate_for_environment(cls, backend_name: str) -> bool:
     20        """
     21        Determines if the current environment seems appropriate
     22        for using this backend.
     23
     24        Args:
     25            backend_name : Backend name being requested. (Optional)
     26        """
     27        raise NotImplementedError
     28
     29
     30    def get_version(self):
     31        """
     32        Returns information about the active Facedancer version.
     33        """
     34        raise NotImplementedError
     35
     36
     37    def connect(self, usb_device: USBDevice, max_packet_size_ep0: int=64, device_speed: DeviceSpeed=DeviceSpeed.FULL):
     38        """
     39        Prepares backend to connect to the target host and emulate
     40        a given device.
     41
     42        Args:
     43            usb_device : The USBDevice object that represents the emulated device.
     44            max_packet_size_ep0 : Max packet size for control endpoint.
     45            device_speed : Requested usb speed for the Facedancer board.
     46        """
     47        raise NotImplementedError
     48
     49
     50    def disconnect(self):
     51        """ Disconnects Facedancer from the target host. """
     52        raise NotImplementedError
     53
     54
     55    def reset(self):
     56        """
     57        Triggers the Facedancer to handle its side of a bus reset.
     58        """
     59        raise NotImplementedError
     60
     61
     62    def set_address(self, address: int, defer: bool=False):
     63        """
     64        Sets the device address of the Facedancer. Usually only used during
     65        initial configuration.
     66
     67        Args:
     68            address : The address the Facedancer should assume.
     69            defer   : True iff the set_address request should wait for an active transaction to
     70                      finish.
     71        """
     72        raise NotImplementedError
     73
     74
     75    def configured(self, configuration: USBConfiguration):
     76        """
     77        Callback that's issued when a USBDevice is configured, e.g. by the
     78        SET_CONFIGURATION request. Allows us to apply the new configuration.
     79
     80        Args:
     81            configuration : The USBConfiguration object applied by the SET_CONFIG request.
     82        """
     83        raise NotImplementedError
     84
     85
     86    def read_from_endpoint(self, endpoint_number: int) -> bytes:
     87        """
     88        Reads a block of data from the given endpoint.
     89
     90        Args:
     91            endpoint_number : The number of the OUT endpoint on which data is to be rx'd.
     92        """
     93        raise NotImplementedError
     94
     95
     96    def send_on_control_endpoint(self, endpoint_number: int, in_request: USBControlRequest, data: bytes, blocking: bool=True):
     97        """
     98        Sends a collection of USB data in response to a IN control request by the host.
     99
    100        Args:
    101            endpoint_number  : The number of the IN endpoint on which data should be sent.
    102            in_request       : The control request being responded to.
    103            data             : The data to be sent.
    104            blocking         : If true, this function should wait for the transfer to complete.
    105        """
    106        # Truncate data to requested length and forward to `send_on_endpoint()` for backends
    107        # that do not need to support this method.
    108        return self.send_on_endpoint(endpoint_number, data[:in_request.length], blocking)
    109
    110
    111    def send_on_endpoint(self, endpoint_number: int, data: bytes, blocking: bool=True):
    112        """
    113        Sends a collection of USB data on a given endpoint.
    114
    115        Args:
    116            endpoint_number : The number of the IN endpoint on which data should be sent.
    117            data : The data to be sent.
    118            blocking : If true, this function should wait for the transfer to complete.
    119        """
    120        raise NotImplementedError
    121
    122
    123    def ack_status_stage(self, direction: USBDirection=USBDirection.OUT, endpoint_number:int =0, blocking: bool=False):
    124        """
    125        Handles the status stage of a correctly completed control request,
    126        by priming the appropriate endpoint to handle the status phase.
    127
    128        Args:
    129            direction : Determines if we're ACK'ing an IN or OUT vendor request.
    130                       (This should match the direction of the DATA stage.)
    131            endpoint_number : The endpoint number on which the control request
    132                              occurred.
    133            blocking : True if we should wait for the ACK to be fully issued
    134                       before returning.
    135        """
    136
    137
    138    def stall_endpoint(self, endpoint_number:int, direction: USBDirection=USBDirection.OUT):
    139        """
    140        Stalls the provided endpoint, as defined in the USB spec.
    141
    142        Args:
    143            endpoint_number : The number of the endpoint to be stalled.
    144        """
    145        raise NotImplementedError
    146
    147
    148    def clear_halt(self, endpoint_number:int, direction: USBDirection):
    149        """ Clears a halt condition on the provided non-control endpoint.
    150
    151        Args:
    152            endpoint_number : The endpoint number
    153            direction       : The endpoint direction; or OUT if not provided.
    154        """
    155        # FIXME do nothing as only the moondancer backend supports this for now
    156        # raise NotImplementedError
    157        pass
    158
    159
    160    def service_irqs(self):
    161        """
    162        Core routine of the Facedancer execution/event loop. Continuously monitors the
    163        Facedancer's execution status, and reacts as events occur.
    164        """
    165        raise NotImplementedError
    166
    167
    168    def validate_configuration(self, configuration: USBConfiguration):
    169        """
    170        Check if this backend is able to support this configuration.
    171        Raises an exception if it is not.
    172
    173        Args:
    174            configuration : The configuration to validate.
    175        """
    176        if configuration is None:
    177            return
    178
    179        # Currently, endpoints are only set up in the configured() method, and
    180        # cannot be changed on the fly by SET_INTERFACE requests.
    181        #
    182        # Therefore, no backends are able to support configurations which
    183        # re-use endpoint addresses between alternate interface settings.
    184        used_addresses = set()
    185        for interface in configuration.get_interfaces():
    186            for endpoint in interface.get_endpoints():
    187                address = endpoint.get_identifier()
    188                if address in used_addresses:
    189                    raise Exception(
    190                        f"This configuration cannot currently be supported, "
    191                        f"because it re-uses endpoint address 0x{address:02X} "
    192                        f"between multiple interface definitions.")
    193                used_addresses.add(address)

### 3. Implement the backend event loop

Facedancer uses a polling approach to service events originating from the Facedancer board.

The actual events that need to be serviced will be specific to your Facedancer board but will generally include at least the following:

- Receiving a setup packet.

- Receiving data on an endpoint.

- Receiving NAK events (e.g. host requested data from an IN endpoint)

Facedancer will take care of scheduling execution of the `service_irqs()` callback but it is up to you to dispatch any events generated by your board to the corresponding methods of the Facedancer `USBDevice` object obtained in the `FacedancerBackend.connect()` callback.

That said, most backend implementations will follow a pattern similiar to the pseudo-code below:

    class MydancerBackend(FacedancerApp, FacedancerBackend):

        ...

        def service_irqs(self):
            """
            Core routine of the Facedancer execution/event loop. Continuously monitors the
            Moondancer's execution status, and reacts as events occur.
            """

            # obtain latest events and handle them
            for event in self.mydancer.get_events():
                match event:
                    case USB_RECEIVE_SETUP:
                        self.usb_device.create_request(event.data)
                    case USB_RECEIVE_PACKET:
                        self.usb_device.handle_data_available(event.endpoint_number, event.data)
                    case USB_EP_IN_NAK:
                        self.usb_device.handle_nak(event.endpoint_number)

Additionally, referencing the `service_irqs` methods of the other backend implementations can provide valuable insights into handling events specific to your implementation.

## facedancer

### facedancer package

#### Subpackages

##### facedancer.backends package

###### Submodules

###### facedancer.backends.MAXUSBApp module

###### facedancer.backends.base module

###### facedancer.backends.goodfet module

###### facedancer.backends.greatdancer module

###### facedancer.backends.greathost module

###### facedancer.backends.hydradancer module

###### facedancer.backends.libusbhost module

###### facedancer.backends.moondancer module

###### facedancer.backends.raspdancer module

###### Module contents

##### facedancer.classes package

###### Subpackages

###### facedancer.classes.hid package

###### Submodules

###### facedancer.classes.hid.descriptor module

###### facedancer.classes.hid.keyboard module

###### facedancer.classes.hid.usage module

###### Module contents

##### facedancer.devices package

###### Subpackages

###### facedancer.devices.umass package

###### Submodules

###### facedancer.devices.umass.disk_image module

###### facedancer.devices.umass.umass module

###### Module contents

###### Submodules

###### facedancer.devices.ftdi module

###### facedancer.devices.keyboard module

###### Module contents

##### facedancer.filters package

###### Submodules

###### facedancer.filters.base module

###### facedancer.filters.logging module

###### facedancer.filters.standard module

###### Module contents

#### Submodules

##### facedancer.configuration module

##### facedancer.core module

class facedancer.core.FacedancerApp(device, verbose=0)

  Bases: `object`

  app_name = 'override this'
  app_num = 0
  classmethod appropriate_for_environment(backend_name=None)

    Returns true if the current class is likely to be the appropriate class to connect to a facedancer given the board_name and other environmental factors.

    Parameters:

      backend_name – The name of the backend, as typically retrieved from the BACKEND environment variable, or None to try figuring things out based on other environmental factors.

  classmethod autodetect(verbose=0, quirks=None)

    Convenience function that automatically creates the appropriate subclass based on the BOARD environment variable and some crude internal automagic.

    Parameters:

      verbose – Sets the verbosity level of the relevant app. Increasing this from zero yields progressively more output.

  enable()
  init_commands()

class facedancer.core.FacedancerBasicScheduler

  Bases: `object`

  Most basic scheduler for Facedancer devices– and the schedule which is created implicitly if no other scheduler is provided. Executes each of its tasks in order, over and over.

  add_task(callback)

    Adds a facedancer task to the scheduler, which will be called repeatedly according to the internal scheduling algorithm

    callback: The callback to be scheduled.

  do_exit = False
  run()

    Run the main scheduler stack.

  stop()

    Stop the scheduler on next loop.

facedancer.core.FacedancerUSBApp(verbose=0, quirks=None)

  Convenience function that automatically creates a FacedancerApp based on the BOARD environment variable and some crude internal automagic.

  Parameters:

    verbose – Sets the verbosity level of the relevant app. Increasing this from zero yields progressively more output.

class facedancer.core.FacedancerUSBHost

  Bases: `object`

  Base class for Facedancer host connections– extended to provide actual connections to each host.

  ENDPOINT_DIRECTION_IN = 128
  ENDPOINT_DIRECTION_OUT = 0
  ENDPOINT_TYPE_CONTROL = 0
  PID_IN = 1
  PID_OUT = 0
  PID_SETUP = 2
  REQUEST_RECIPIENT_DEVICE = 0
  REQUEST_RECIPIENT_ENDPOINT = 2
  REQUEST_RECIPIENT_INTERFACE = 1
  REQUEST_RECIPIENT_OTHER = 3
  REQUEST_TYPE_CLASS = 1
  REQUEST_TYPE_RESERVED = 3
  REQUEST_TYPE_STANDARD = 0
  REQUEST_TYPE_VENDOR = 2
  STANDARD_REQUEST_GET_DESCRIPTOR = 6
  STANDARD_REQUEST_GET_STATUS = 0
  STANDARD_REQUEST_SET_ADDRESS = 5
  STANDARD_REQUEST_SET_CONFIGURATION = 9
  apply_configuration(index, set_configuration=True)
    Applies a device’s configuration. Necessary to use endpoints other

      than the control endpoint.

    Parameters:

      - index – The configuration index to apply.

      - set_configuration – If true, also informs the device of the change. Setting this to false can allow the host to update its view of all endpoints without communicating with the device – e.g. to update the device’s address.

  classmethod appropriate_for_environment(backend_name=None)

    Returns true if the current class is likely to be the appropriate class to connect to a facedancer given the board_name and other environmental factors.

    Parameters:

      backend_name – The name of the backend, as typically retrieved from the BACKEND environment variable, or None to try figuring things out based on other environmental factors.

  classmethod autodetect(verbose=0, quirks=None)

    Convenience function that automatically creates the appropriate subclass based on the BOARD environment variable and some crude internal automagic.

    Parameters:

      verbose – Sets the verbosity level of the relevant app. Increasing this from zero yields progressively more output.

  control_request_in(request_type, recipient, request, value=0, index=0, length=0)

    Performs an IN control request.

    Parameters:

      - request_type – Determines if this is a standard, class, or vendor request. Accepts a REQUEST_TYPE_* constant.

      - recipient – Determines the context in which this command is interpreted. Accepts a REQUEST_RECIPIENT_* constant.

      - request – The request number to be performed.

      - value, index – The standard USB request arguments, to be included in the setup packet. Their meaning varies depending on the request.

      - length – The maximum length of data expected in response, or 0 if we don’t expect any data back.

  control_request_out(request_type, recipient, request, value=0, index=0, data=[])

    Performs an OUT control request.

    Parameters:

      - request_type – Determines if this is a standard, class, or vendor request. Accepts a REQUEST_TYPE_* constant.

      - recipient – Determines the context in which this command is interpreted. Accepts a REQUEST_RECIPIENT_* constant.

      - request – The request number to be performed.

      - value, index – The standard USB request arguments, to be included in the setup packet. Their meaning varies depending on the request.

      - data – The data to be transmitted with this control request.

  get_configuration_descriptor(index=0, include_subordinates=True)

    Returns the device’s configuration descriptor.

    Parameters:

      include_subordinate – if true, subordinate descriptors will also be returned

  get_descriptor(descriptor_type, descriptor_index, language_id, max_length)

    Reads up to max_length bytes of a device’s descriptors.

  get_device_descriptor(max_length=18)

    Returns the device’s device descriptor.

  handle_events()
  initialize_device(apply_configuration=0, assign_address=0)

    Sets up a connection to a directly-attached USB device.

    Parameters:

      - apply_configuration – If non-zero, the configuration with the given index will be applied to the relevant device.

      - assign_address – If non-zero, the device will be assigned the given address as part of the enumeration/initialization process.

  read_ep0_max_packet_size()

    Returns the device’s reported maximum packet size on EP0, in a way appropriate for an barely-configured endpoint.

  set_address(device_address)

    Sets the device’s address.

    Note that all endpoints must be set up again after issuing the new address; the easiest way to do this is to call apply_configuration().

    Parameters:

      device_address – the address to apply to the given device

  set_configuration(index)

    Sets the device’s active configuration.

    Note that this does not configure the host for the given configuration. Most of the time, you probably want apply_configuration, which does.

    Parameters:

      index – the index of the configuration to apply

facedancer.core.FacedancerUSBHostApp(verbose=0, quirks=None)

  Convenience function that automatically creates a FacedancerApp based on the BOARD environment variable and some crude internal automagic.

  verbose: Sets the verbosity level of the relevant app. Increasing

    this from zero yields progressively more output.

##### facedancer.descriptor module

Functionality for working with objects with associated USB descriptors.

class facedancer.descriptor.StringDescriptorManager

  Bases: `object`

  Manager class that collects active string descriptors.

  __getitem__(index)

    Gets the relevant string descriptor.

  add_string(string, index=None)

    Add a Python string as a new string descriptor, and return an index.

    The specified index is used for the new string descriptor, overwriting any previous descriptor with the same index. If an index is not specified, a new, unique, incrementing index is allocated.

  get_index(string)

    Returns the index of the given string; creating it if the string isn’t already known.

class facedancer.descriptor.StringRef(index: int = None, string: str = None)

  Bases: `object`

  Class representing a reference to a USB string descriptor.

  classmethod ensure(value)

    Turn a value into a StringRef it is not one already.

  classmethod field(**kwargs)

    Used to create StringRef fields in dataclasses.

  generate_code()

    Generate input that will produce this StringRef when passed to ensure()

  classmethod lookup(strings: Dict[int, str], index: int)

    Try to construct a StringRef given an index and a mapping

class facedancer.descriptor.USBClassDescriptor

  Bases: `USBDescriptor`

  Class for arbitrary USB Class descriptors.

  include_in_config: bool = True

class facedancer.descriptor.USBDescribable

  Bases: `object`

  Abstract base class for objects that can be created from USB descriptors.

  DESCRIPTOR_TYPE_NUMBER = None
  classmethod from_binary_descriptor(data, strings={})

    Attempts to create a USBDescriptor subclass from the given raw descriptor data.

  classmethod handles_binary_descriptor(data)

    Returns true iff this class handles the given descriptor. By default, this is based on the class’s DESCRIPTOR_TYPE_NUMBER declaration.

class facedancer.descriptor.USBDescriptor

  Bases: `USBDescribable`, `AutoInstantiable`

  Class for arbitrary USB descriptors; minimal concrete implementation of USBDescribable.

  __call__(index=0)

    Converts the descriptor object into raw bytes.

  classmethod from_binary_descriptor(data, strings={})

    Attempts to create a USBDescriptor subclass from the given raw descriptor data.

  generate_code(name=None, indent=0)
  get_identifier()

    Returns a unique integer identifier for this object.

    This is usually the index or address of the relevant USB object.

  include_in_config: bool = False
  number: int = None

    Parent object which this descriptor is associated with.

  parent: USBDescribable = None

    Whether this descriptor should be included in a GET_CONFIGURATION response.

  raw: bytes

    The bDescriptorType of the descriptor.

  type_number: int = None

    Number to request this descriptor with a GET_DESCRIPTOR request.

class facedancer.descriptor.USBDescriptorTypeNumber(*values)

  Bases: `IntEnum`

  CONFIGURATION = 2
  DEVICE = 1
  DEVICE_QUALIFIER = 6
  ENDPOINT = 5
  HID = 33
  INTERFACE = 4
  INTERFACE_POWER = 8
  OTHER_SPEED_CONFIGURATION = 7
  REPORT = 34
  STRING = 3

class facedancer.descriptor.USBStringDescriptor

  Bases: `USBDescriptor`

  Class representing a USB string descriptor.

  DESCRIPTOR_TYPE_NUMBER = 3
  classmethod from_string(string, *, index=None)
  python_string: str = None

facedancer.descriptor.include_in_config(cls)

  Decorator that marks a descriptor to be included in configuration data.

facedancer.descriptor.requestable(type_number, number)

  Decorator that marks a descriptor as requestable.

##### facedancer.device module

##### facedancer.endpoint module

Functionality for describing USB endpoints.

class facedancer.endpoint.USBEndpoint(*, number: int, direction: ~facedancer.types.USBDirection, transfer_type: ~facedancer.types.USBTransferType = USBTransferType.BULK, synchronization_type: ~facedancer.types.USBSynchronizationType = USBSynchronizationType.NONE, usage_type: ~facedancer.types.USBUsageType = USBUsageType.DATA, max_packet_size: int = 64, interval: int = 0, extra_bytes: bytes = b'', attached_descriptors: ~typing.List[~facedancer.descriptor.USBDescriptor] = <factory>, requestable_descriptors: ~typing.Dict[tuple[int, int], ~facedancer.descriptor.USBDescriptor] = <factory>, parent: ~facedancer.descriptor.USBDescribable = None)

  Bases: `USBDescribable`, `AutoInstantiable`, `USBRequestHandler`

  Class representing a USBEndpoint object.

  Field:
    number:

      The endpoint number (without the direction bit) for this endpoint.

    direction:

      A USBDirection constant indicating this endpoint’s direction.

    transfer_type:

      A USBTransferType constant indicating the type of communications used.

    max_packet_size:

      The maximum packet size for this endpoint.

    interval:

      The polling interval, for an INTERRUPT endpoint.

  DESCRIPTOR_TYPE_NUMBER = 5
  add_descriptor(descriptor: USBDescriptor)

    Adds the provided descriptor to the endpoint.

  property address

    Fetches the address for the given endpoint.

  static address_for_number(endpoint_number: int, direction: USBDirection) → int

    Computes the endpoint address for a given number + direction.

  attached_descriptors: List[USBDescriptor]
  property attributes

    Fetches the attributes for the given endpoint, as a single byte.

  direction: USBDirection
  extra_bytes: bytes = b''
  classmethod from_binary_descriptor(data, strings={})

    Creates an endpoint object from a description of that endpoint.

  generate_code(name=None, indent=0)
  get_address()

    Method alias for the address property. For backend support.

  get_descriptor() → bytes

    Get a descriptor string for this endpoint.

  get_device()

    Returns the device associated with the given descriptor.

  get_identifier() → int

    Returns a unique integer identifier for this object.

    This is usually the index or address of the relevant USB object.

  handle_buffer_empty()

    Handler called when this endpoint first has an empty buffer.

  handle_clear_feature_request = <ControlRequestHandler wrapping USBEndpoint.handle_clear_feature_request at 0x7fe246c8a270
  handle_data_received(data: bytes)

    Handler for receipt of non-control request data.

    Parameters:

      data – The raw bytes received.

  handle_data_requested()

    Handler called when the host requests data on this endpoint.

  interval: int = 0
  matches_identifier(other: int) → bool
  max_packet_size: int = 64
  number: int
  parent: USBDescribable = None
  requestable_descriptors: Dict[tuple[int, int], USBDescriptor]
  send(data: bytes, *, blocking: bool = False)

    Sends data on this endpoint. Valid only for IN endpoints.

    Parameters:

      - data – The data to be sent.

      - blocking – True if we should block until the backend reports the transmission to be complete.

  synchronization_type: USBSynchronizationType = 0
  transfer_type: USBTransferType = 2
  usage_type: USBUsageType = 0

##### facedancer.errors module

exception facedancer.errors.DeviceNotFoundError

  Bases: `OSError`

  Error indicating a device was not found.

exception facedancer.errors.EndEmulation

  Bases: `Exception`

  When an EndEmulation exception is thrown the emulation will shutdown and exit.

##### facedancer.interface module

Functionality for defining USB interfaces.

class facedancer.interface.USBInterface(*, name: ~facedancer.descriptor.StringRef = <factory>, number: int = 0, alternate: int = 0, class_number: int = 0, subclass_number: int = 0, protocol_number: int = 0, interface_string: str = None, attached_descriptors: ~typing.List[~facedancer.descriptor.USBDescriptor] = <factory>, requestable_descriptors: ~typing.Dict[tuple[int, int], ~facedancer.descriptor.USBDescriptor] = <factory>, endpoints: ~typing.Dict[int, ~facedancer.endpoint.USBEndpoint] = <factory>, parent: ~facedancer.descriptor.USBDescribable = None)

  Bases: `USBDescribable`, `AutoInstantiable`, `USBRequestHandler`

  Class representing a USBDevice interface.

  Fields:
    number :

      The interface’s index. Zero indexed.

    class_number, subclass_number, protocol_number :

      The USB class adhered to on this interface; usually a USBDeviceClass constant.

    interface_string :

      A short, descriptive string used to identify the endpoint; or None if not provided.

  DESCRIPTOR_TYPE_NUMBER = 4
  add_descriptor(descriptor: USBDescriptor)

    Adds the provided descriptor to the interface.

  add_endpoint(endpoint: USBEndpoint)

    Adds the provided endpoint to the interface.

  alternate: int = 0
  attached_descriptors: List[USBDescriptor]
  class_number: int = 0
  endpoints: Dict[int, USBEndpoint]
  classmethod from_binary_descriptor(data, strings={})

    Generates an interface object from a descriptor.

  generate_code(name=None, indent=0)
  get_descriptor() → bytes

    Retrieves the given interface’s interface descriptor, with subordinates.

  get_device()

    Returns the device associated with the given descriptor.

  get_endpoint(endpoint_number: int, direction: USBDirection) → USBEndpoint

    Attempts to find a subordinate endpoint matching the given number/direction.

    Parameters:

      - endpoint_number – The endpoint number to search for.

      - direction – The endpoint direction to be matched.

    Returns:

      The matching endpoint; or None if no matching endpoint existed.

  get_endpoints()

    Returns an iterable over all endpoints in this interface.

  get_identifier() -> (<class 'int'>, <class 'int'>)

    Returns a unique integer identifier for this object.

    This is usually the index or address of the relevant USB object.

  handle_buffer_empty(endpoint: USBEndpoint)

    Handler called when a given endpoint first has an empty buffer.

    Often, an empty buffer indicates an opportunity to queue data for sending (‘prime an endpoint’), but doesn’t necessarily mean that the host is planning on reading the data.

    This function is called only once per buffer.

  handle_data_received(endpoint: USBEndpoint, data: bytes)

    Handler for receipt of non-control request data.

    Typically, this method will delegate any data received to the appropriate configuration/interface/endpoint. If overridden, the overriding function will receive all data; and can delegate it by calling the .handle_data_received method on self.configuration.

    Parameters:

      - endpoint_number – The endpoint number on which the data was received.

      - data – The raw bytes received on the relevant endpoint.

  handle_data_requested(endpoint: USBEndpoint)

    Handler called when the host requests data on a non-control endpoint.

    Typically, this method will delegate the request to the appropriate interface+endpoint. If overridden, the overriding function will receive all data.

    Parameters:

      endpoint_number – The endpoint number on which the host requested data.

  handle_get_descriptor_request = <ControlRequestHandler wrapping USBInterface.handle_get_descriptor_request at 0x7fe246c3f110
  handle_get_interface_request = <ControlRequestHandler wrapping USBInterface.handle_get_interface_request at 0x7fe246c162c0
  handle_set_interface_request = <ControlRequestHandler wrapping USBInterface.handle_set_interface_request at 0x7fe246c3f390
  has_endpoint(endpoint_number: int, direction: USBDirection) → USBEndpoint

    Returns true iff we have matching subordinate endpoint.

    Parameters:

      - endpoint_number – The endpoint number to search for.

      - direction – The endpoint direction to be matched.

  interface_string: str = None
  matches_identifier(other: int) → bool
  name: StringRef
  number: int = 0
  parent: USBDescribable = None
  protocol_number: int = 0
  requestable_descriptors: Dict[tuple[int, int], USBDescriptor]
  subclass_number: int = 0

##### facedancer.logging module

facedancer.logging.configure_default_logging(level=20, logger=<module 'logging' from '/usr/lib/python3.14/logging/__init__.py'>)

##### facedancer.magic module

Functionally for automatic instantiations / tracking via decorators.

class facedancer.magic.AutoInstantiable

  Bases: `object`

  Base class for methods that can be decorated with use_automatically.

  abstractmethod get_identifier() → int

    Returns a unique integer identifier for this object.

    This is usually the index or address of the relevant USB object.

  matches_identifier(other: int) → bool

class facedancer.magic.AutoInstantiator(target_type)

  Bases: `object`

  Simple wrapper class annotated on objects that can be instantiated automatically.

  Used for the @use_automatically decorator; which removes a lot of the Facedancer boilerplate at the cost of being somewhat cryptic.

  creates_instance_of(expected_type)

class facedancer.magic.DescribableMeta(name, bases, classdict)

  Bases: `ABCMeta`

  Metaclass for USBDescribable subclasses.

facedancer.magic.adjust_defaults(cls, **kwargs)

  Adjusts the defaults of an existing dataclass.

facedancer.magic.instantiate_subordinates(obj, expected_type)

  Automatically instantiates any inner classes with a matching type.

  This is used by objects that represent USB hardware behaviors (e.g. USBDevice, USBConfiguration, USBInterface, USBEndpoint) in order to automatically create objects of any inner class decorated with `@use_automatically`.

facedancer.magic.use_automatically(cls)

  Class decorator used to annotate Facedancer inner classes. Implies @dataclass.

  This decorator can be placed on inner classes that describe “subordinate” objects on USB devices. For example, a USBDevice can have several subordinate USBConfigurations; which select the various configurations for that class.

  When placed on a subordinate class, this allows the parent class to automatically instantiate the relevant given class during its creation; automatically populating the subordinate properties of the relevant device.

  For example, assume we have a Facedancer class representing a custom USB device:

      class ExampleDevice(USBDevice):
          product_string : str = "My Example Device"

          @use_automatically
          class DefaultConfiguration(USBConfiguration):
              number : int = 1

  In this case, when an ExampleDevice is instantiated, the USBDevice code knows how to instantiate DefaultConfiguration, and will do so automatically.

  Note that this decorator should _only_ be used for subordinate types; and expects that the decorated class has no explicitly-declared __init__ method. The __post_init__ mechanism of python dataclasses can be overridden to perform any needed initialization.

facedancer.magic.use_inner_classes_automatically(cls)

  Decorator that acts as if all inner classes were defined with use_automatically.

##### facedancer.proxy module

##### facedancer.request module

Functionality for declaring and working with USB control requests.

class facedancer.request.ControlRequestHandler(handler_function, execution_condition)

  Bases: `object`

  Class representing a control request handler.

  Instances of this class are generated automatically each time a control request is defined using decorator syntax; and track the association between the relevant handler function and the condition under which it’s executed.

  __call__(caller, request)

    Primary execution; calls the relevant handler if our conditions are met.

  add_condition(condition)

    Refines a control request handler such that it’s only called when the added condition is true.

  add_field_matcher(field_name, field_value)

    Refines a control request handler such that it’s only called when one of its fields matches a given value.

    Parameters:

      - field_name – The property of the USBControlRequest object to be checked.

      - field_value – The value the relevant property must match to be called.

class facedancer.request.USBControlRequest(direction: USBDirection, type: USBRequestType, recipient: USBRequestRecipient, number: int, value: int, index: int, length: int, data: bytes = b'', device: USBDescribable = None)

  Bases: `object`

  Class encapsulating a USB control request.

  TODO: document parameters

  ack(*, blocking: bool = False)

    Acknowledge the given request without replying.

    Convenience alias for .acknowledge().

    Parameters:

      blocking – If true, the relevant control request will complete before returning.

  acknowledge(*, blocking: bool = False)

    Acknowledge the given request without replying.

    Parameters:

      blocking – If true, the relevant control request will complete before returning.

  data: bytes = b''
  device: USBDescribable = None
  direction: USBDirection
  classmethod from_raw_bytes(raw_bytes: bytes, *, device=None)

    Creates a request object from a sequence of raw bytes.

    Parameters:

      - raw_bytes – The raw bytes to create the object from.

      - device – The USBDevice to associate with the given request. Optional, but necessary to use the .reply() / .acknowledge() methods.

  get_direction() → USBDirection
  get_recipient() → USBRequestRecipient
  get_type() → USBRequestType
  index: int
  property index_high: int
  property index_low: int
  length: int
  number: int
  raw() → bytes

    Returns the raw bytes that compose the request.

  recipient: USBRequestRecipient
  reply(data: bytes)

    Replies to the given request with a given set of bytes.

  property request: int
  property request_type: int

    Fetches the whole request_type byte.

  stall()

    Stalls the associated device’s control request.

    Used to indicate that a given request isn’t supported; or isn’t supported with the provided arguments.

  type: USBRequestType
  value: int
  property value_high: int
  property value_low: int

class facedancer.request.USBRequestHandler

  Bases: `object`

  Base class for any object that handles USB requests.

  handle_request(request: USBControlRequest) → bool

    Core control request handler.

    This function can be overridden by a subclass if desired; but the typical way to handle a specific control request is to the the `@control_request_handler` decorators.

    Parameters:

      request – the USBControlRequest object representing the relevant request

    Returns:

      true iff the request is handled

facedancer.request.class_request_handler(**kwargs)

  Decorator; declares a class request handler. See control_request_handler() for usage.

facedancer.request.control_request_handler(condition=<function <lambda>>, **kwargs)

  Decorator that declares a control request handler.

  Used while defining a USBDevice, USBInterface, USBEndpoint, or USBOtherRecipient class to declare handlers for that function.

  Parameters:

    condition – A function that, when evaluated on a USBControlRequest, evaluates true if and only if this function is an appropriate handler.

facedancer.request.get_request_handler_methods(cls) → List[callable]

  Returns a list of all handler methods on a given class or object.

  This is used to find all methods of an object decorated with the @*_request_handler decorators.

facedancer.request.reserved_request_handler(**kwargs)

  Decorator; declares a reserved-type request handler. Not typically used.

facedancer.request.standard_request_handler(**kwargs)

  Decorator; declares a standard request handler. See control_request_handler() for usage.

facedancer.request.to_any_endpoint(func)

  Decorator; refines a handler so it’s only called on requests with an endpoint recipient.

facedancer.request.to_any_interface(func)

  Decorator; refines a handler so it’s only called on requests with an interface recipient.

facedancer.request.to_device(func)

  Decorator; refines a handler so it’s only called on requests with a device recipient.

facedancer.request.to_other(func)

  Decorator; refines a handler so it’s only called on requests with an Other (TM) recipient.

facedancer.request.to_this_endpoint(func)

  Decorator; refines a handler so it’s only called on requests targeting this endpoint.

facedancer.request.to_this_interface(func)

  Decorator; refines a handler so it’s only called on requests targeting this interface.

facedancer.request.vendor_request_handler(**kwargs)

  Decorator; declares a vendor request handler. See control_request_handler() for usage.

##### facedancer.types module

USB types – defines enumerations that describe standard USB types

class facedancer.types.DescriptorTypes(*values)

  Bases: `IntEnum`

  CONFIGURATION = 2
  DEVICE = 1
  DEVICE_QUALIFIER = 6
  ENDPOINT = 5
  HID = 33
  INTERFACE = 4
  INTERFACE_POWER = 8
  OTHER_SPEED_CONFIGURATION = 7
  REPORT = 34
  STRING = 3

class facedancer.types.DeviceSpeed(*values)

  Bases: `IntEnum`

  FULL = 2
  HIGH = 3
  LOW = 1
  SUPER = 4
  SUPER_PLUS = 5
  UNKNOWN = 0

class facedancer.types.LanguageIDs(*values)

  Bases: `IntEnum`

  AFRIKAANS = 1078
  ALBANIAN = 1052
  ARABIC_ALGERIA = 5121
  ARABIC_BAHRAIN = 15361
  ARABIC_EGYPT = 3073
  ARABIC_IRAQ = 2049
  ARABIC_JORDAN = 11265
  ARABIC_KUWAIT = 13313
  ARABIC_LEBANON = 12289
  ARABIC_LIBYA = 4097
  ARABIC_MOROCCO = 6145
  ARABIC_OMAN = 8193
  ARABIC_QATAR = 16385
  ARABIC_SAUDI_ARABIA = 1025
  ARABIC_SYRIA = 10241
  ARABIC_TUNISIA = 7169
  ARABIC_UAE = 14337
  ARABIC_YEMEN = 9217
  ARMENIAN = 1067
  ASSAMESE = 1101
  AZERI_CYRILLIC = 2092
  AZERI_LATIN = 1068
  BASQUE = 1069
  BELARUSSIAN = 1059
  BENGALI = 1093
  BULGARIAN = 1026
  BURMESE = 1109
  CATALAN = 1027
  CHINESE_HONG_KONG = 3076
  CHINESE_MACAU_SAR = 5124
  CHINESE_PRC = 2052
  CHINESE_SINGAPORE = 4100
  CHINESE_TAIWAN = 1028
  CROATIAN = 1050
  CZECH = 1029
  DANISH = 1030
  DUTCH_BELGIUM = 2067
  DUTCH_NETHERLANDS = 1043
  ENGLISH_AUSTRALIAN = 3081
  ENGLISH_BELIZE = 10249
  ENGLISH_CANADIAN = 4105
  ENGLISH_CARIBBEAN = 9225
  ENGLISH_IRELAND = 6153
  ENGLISH_JAMAICA = 8201
  ENGLISH_NEW_ZEALAND = 5129
  ENGLISH_PHILIPPINES = 13321
  ENGLISH_SOUTH_AFRICA = 7177
  ENGLISH_TRINIDAD = 11273
  ENGLISH_UNITED_KINGDOM = 2057
  ENGLISH_US = 1033
  ENGLISH_ZIMBABWE = 12297
  ESTONIAN = 1061
  FAEROESE = 1080
  FARSI = 1065
  FINNISH = 1035
  FRENCH_BELGIAN = 2060
  FRENCH_CANADIAN = 3084
  FRENCH_LUXEMBOURG = 5132
  FRENCH_MONACO = 6156
  FRENCH_STANDARD = 1036
  FRENCH_SWITZERLAND = 4108
  GEORGIAN = 1079
  GERMAN_AUSTRIA = 3079
  GERMAN_LIECHTENSTEIN = 5127
  GERMAN_LUXEMBOURG = 4103
  GERMAN_STANDARD = 1031
  GERMAN_SWITZERLAND = 2055
  GREEK = 1032
  GUJARATI = 1095
  HEBREW = 1037
  HID_USAGE_DATA_DESCRIPTOR = 1279
  HID_VENDOR_DEFINED_1 = 61695
  HID_VENDOR_DEFINED_2 = 62719
  HID_VENDOR_DEFINED_3 = 63743
  HID_VENDOR_DEFINED_4 = 64767
  HINDI = 1081
  HUNGARIAN = 1038
  ICELANDIC = 1039
  INDONESIAN = 1057
  ITALIAN_STANDARD = 1040
  ITALIAN_SWITZERLAND = 2064
  JAPANESE = 1041
  KANNADA = 1099
  KASHMIRI_INDIA = 2144
  KAZAKH = 1087
  KONKANI = 1111
  KOREAN = 1042
  KOREAN_JOHAB = 2066
  LATVIAN = 1062
  LITHUANIAN = 1063
  LITHUANIAN_CLASSIC = 2087
  MACEDONIAN = 1071
  MALAYALAM = 1100
  MALAY_BRUNEI_DARUSSALAM = 2110
  MALAY_MALAYSIAN = 1086
  MANIPURI = 1112
  MARATHI = 1102
  NEPALI_INDIA = 2145
  NORWEGIAN_BOKMAL = 1044
  NORWEGIAN_NYNORSK = 2068
  ORIYA = 1096
  POLISH = 1045
  PORTUGUESE_BRAZIL = 1046
  PORTUGUESE_STANDARD = 2070
  PUNJABI = 1094
  ROMANIAN = 1048
  RUSSIAN = 1049
  SANSKRIT = 1103
  SERBIAN_CYRILLIC = 3098
  SERBIAN_LATIN = 2074
  SINDHI = 1113
  SLOVAK = 1051
  SLOVENIAN = 1060
  SPANISH_ARGENTINA = 11274
  SPANISH_BOLIVIA = 16394
  SPANISH_CHILE = 13322
  SPANISH_COLOMBIA = 9226
  SPANISH_COSTA_RICA = 5130
  SPANISH_DOMINICAN_REPUBLIC = 7178
  SPANISH_ECUADOR = 12298
  SPANISH_EL_SALVADOR = 17418
  SPANISH_GUATEMALA = 4106
  SPANISH_HONDURAS = 18442
  SPANISH_MEXICAN = 2058
  SPANISH_MODERN_SORT = 3082
  SPANISH_NICARAGUA = 19466
  SPANISH_PANAMA = 6154
  SPANISH_PARAGUAY = 15370
  SPANISH_PERU = 10250
  SPANISH_PUERTO_RICO = 20490
  SPANISH_TRADITIONAL_SORT = 1034
  SPANISH_URUGUAY = 14346
  SPANISH_VENEZUELA = 8202
  SUTU = 1072
  SWAHILI_KENYA = 1089
  SWEDISH = 1053
  SWEDISH_FINLAND = 2077
  TAMIL = 1097
  TATAR_TATARSTAN = 1092
  TELUGU = 1098
  THAI = 1054
  TURKISH = 1055
  UKRAINIAN = 1058
  URDU_INDIA = 2080
  URDU_PAKISTAN = 1056
  UZBEK_CYRILLIC = 2115
  UZBEK_LATIN = 1091
  VIETNAMESE = 1066

class facedancer.types.USB

  Bases: `object`

  desc_type_configuration = 2
  desc_type_device = 1
  desc_type_device_qualifier = 6
  desc_type_endpoint = 5
  desc_type_hid = 33
  desc_type_interface = 4
  desc_type_interface_power = 8
  desc_type_other_speed_configuration = 7
  desc_type_report = 34
  desc_type_string = 3
  feature_device_remote_wakeup = 1
  feature_endpoint_halt = 0
  feature_test_mode = 2
  if_class_to_desc_type = {3: 33}
  interface_class_to_descriptor_type()
  request_direction_device_to_host = 1
  request_direction_host_to_device = 0
  request_recipient_device = 0
  request_recipient_endpoint = 2
  request_recipient_interface = 1
  request_recipient_other = 3
  request_type_class = 1
  request_type_standard = 0
  request_type_vendor = 2
  state_address = 4
  state_attached = 1
  state_configured = 5
  state_default = 3
  state_detached = 0
  state_powered = 2
  state_suspended = 6

class facedancer.types.USBDirection(*values)

  Bases: `IntEnum`

  Class representing USB directions.

  IN = 1
  OUT = 0
  classmethod from_endpoint_address(address)

    Helper method that extracts the direction from an endpoint address.

  classmethod from_request_type(request_type_int)

    Helper method that extracts the direction from a request_type integer.

  is_in()
  is_out()
  classmethod parse(value)

    Helper that converts a numeric field into a direction.

  reverse()

    Returns the reverse of the given direction.

  to_endpoint_address(endpoint_number)

    Helper method that converts and endpoint_number to an address, given direction.

  token()

    Generates the token corresponding to the given direction.

class facedancer.types.USBPIDCategory(*values)

  Bases: `IntFlag`

  Category constants for each of the groups that PIDs can fall under.

  DATA = 3
  HANDSHAKE = 2
  MASK = 3
  SPECIAL = 0
  TOKEN = 1

class facedancer.types.USBPacketID(*values)

  Bases: `IntFlag`

  Enumeration specifying all of the valid USB PIDs we can handle.

  ACK = 2
  DATA0 = 3
  DATA1 = 11
  DATA2 = 7
  ERR = 12
  IN = 9
  MDATA = 15
  NAK = 10
  NYET = 6
  OUT = 1
  PID_CORE_MASK = 15
  PID_INVALID = 16
  PING = 4
  PRE = 12
  SETUP = 13
  SOF = 5
  SPLIT = 8
  STALL = 14
  category()

    Returns the USBPIDCategory that each given PID belongs to.

  direction()

    Get a USB direction from a PacketID.

  classmethod from_byte(byte, skip_checks=False)

    Creates a PID object from a byte.

  classmethod from_int(value, skip_checks=True)

    Create a PID object from an integer.

  classmethod from_name(name)

    Create a PID object from a string representation of its name.

  is_data()

    Returns true iff the given PID represents a DATA packet.

  is_handshake()

    Returns true iff the given PID represents a handshake packet.

  is_invalid()

    Returns true if this object is an attempt to encapsulate an invalid PID.

  is_token()

    Returns true iff the given PID represents a token packet.

  classmethod parse(value)

    Attempt to create a PID object from a number, byte, or string.

  summarize()

    Return a summary of the given packet.

class facedancer.types.USBRequestRecipient(*values)

  Bases: `IntEnum`

  Enumeration that describes each ‘recipient’ of a USB request field.

  DEVICE = 0
  ENDPOINT = 2
  INTERFACE = 1
  OTHER = 3
  RESERVED = 4
  classmethod from_integer(value)

    Special factory that correctly handles reserved values.

  classmethod from_request_type(request_type_int)

    Helper method that extracts the type from a request_type integer.

class facedancer.types.USBRequestType(*values)

  Bases: `IntEnum`

  Enumeration that describes each possible Type field for a USB request.

  CLASS = 1
  RESERVED = 3
  STANDARD = 0
  VENDOR = 2
  classmethod from_request_type(request_type_int)

    Helper method that extracts the type from a request_type integer.

class facedancer.types.USBStandardRequests(*values)

  Bases: `IntEnum`

  CLEAR_FEATURE = 1
  GET_CONFIGURATION = 8
  GET_DESCRIPTOR = 6
  GET_INTERFACE = 10
  GET_STATUS = 0
  SET_ADDRESS = 5
  SET_CONFIGURATION = 9
  SET_DESCRIPTOR = 7
  SET_FEATURE = 3
  SET_INTERFACE = 11
  SYNCH_FRAME = 12

class facedancer.types.USBSynchronizationType(*values)

  Bases: `IntEnum`

  ADAPTIVE = 2
  ASYNC = 1
  NONE = 0
  SYNCHRONOUS = 3

class facedancer.types.USBTransferType(*values)

  Bases: `IntEnum`

  BULK = 2
  CONTROL = 0
  INTERRUPT = 3
  ISOCHRONOUS = 1

class facedancer.types.USBUsageType(*values)

  Bases: `IntEnum`

  DATA = 0
  FEEDBACK = 1
  IMPLICIT_FEEDBACK = 2

facedancer.types.endpoint_number_from_address(number)

#### Module contents

Index | Module Index