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_endpoint(self, endpoint_number: int, data: bytes, blocking: bool=True):
97 """
98 Sends a collection of USB data on a given endpoint.
99
100 Args:
101 endpoint_number : The number of the IN endpoint on which data should be sent.
102 data : The data to be sent.
103 blocking : If true, this function should wait for the transfer to complete.
104 """
105 raise NotImplementedError
106
107
108 def ack_status_stage(self, direction: USBDirection=USBDirection.OUT, endpoint_number:int =0, blocking: bool=False):
109 """
110 Handles the status stage of a correctly completed control request,
111 by priming the appropriate endpoint to handle the status phase.
112
113 Args:
114 direction : Determines if we're ACK'ing an IN or OUT vendor request.
115 (This should match the direction of the DATA stage.)
116 endpoint_number : The endpoint number on which the control request
117 occurred.
118 blocking : True if we should wait for the ACK to be fully issued
119 before returning.
120 """
121
122
123 def stall_endpoint(self, endpoint_number:int, direction: USBDirection=USBDirection.OUT):
124 """
125 Stalls the provided endpoint, as defined in the USB spec.
126
127 Args:
128 endpoint_number : The number of the endpoint to be stalled.
129 """
130 raise NotImplementedError
131
132
133 def service_irqs(self):
134 """
135 Core routine of the Facedancer execution/event loop. Continuously monitors the
136 Facedancer's execution status, and reacts as events occur.
137 """
138 raise NotImplementedError
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.