diff --git a/facedancer/backends/MAXUSBApp.py b/facedancer/backends/MAXUSBApp.py index e586c399..aa160eaa 100644 --- a/facedancer/backends/MAXUSBApp.py +++ b/facedancer/backends/MAXUSBApp.py @@ -6,7 +6,10 @@ from ..core import FacedancerApp -class MAXUSBApp(FacedancerApp): +from .base import FacedancerBackend + + +class MAXUSBApp(FacedancerApp, FacedancerBackend): app_name = "MAXUSB" reg_ep0_fifo = 0x00 diff --git a/facedancer/backends/base.py b/facedancer/backends/base.py index 8aac9430..ee3e0038 100644 --- a/facedancer/backends/base.py +++ b/facedancer/backends/base.py @@ -93,6 +93,21 @@ def read_from_endpoint(self, endpoint_number: int) -> bytes: raise NotImplementedError + def send_on_control_endpoint(self, endpoint_number: int, in_request: USBControlRequest, data: bytes, blocking: bool=True): + """ + Sends a collection of USB data in response to a IN control request by the host. + + Args: + endpoint_number : The number of the IN endpoint on which data should be sent. + in_request : The control request being responded to. + data : The data to be sent. + blocking : If true, this function should wait for the transfer to complete. + """ + # Truncate data to requested length and forward to `send_on_endpoint()` for backends + # that do not need to support this method. + return self.send_on_endpoint(endpoint_number, data[:in_request.length], blocking) + + def send_on_endpoint(self, endpoint_number: int, data: bytes, blocking: bool=True): """ Sends a collection of USB data on a given endpoint. diff --git a/facedancer/backends/greatdancer.py b/facedancer/backends/greatdancer.py index 7ffe677f..f202520b 100644 --- a/facedancer/backends/greatdancer.py +++ b/facedancer/backends/greatdancer.py @@ -10,8 +10,10 @@ from ..logging import log +from .base import FacedancerBackend -class GreatDancerApp(FacedancerApp): + +class GreatDancerApp(FacedancerApp, FacedancerBackend): """ Backend for using GreatFET devices as Facedancers. """ diff --git a/facedancer/backends/moondancer.py b/facedancer/backends/moondancer.py index f018412e..fdd48da3 100644 --- a/facedancer/backends/moondancer.py +++ b/facedancer/backends/moondancer.py @@ -11,6 +11,7 @@ from ..core import * from ..device import USBDevice from ..configuration import USBConfiguration +from ..request import USBControlRequest from ..types import DeviceSpeed, USBDirection from ..logging import log @@ -312,8 +313,23 @@ def read_from_endpoint(self, endpoint_number: int) -> bytes: return data - # TODO greatdancer does blocking=True by default, should we too given that everthing else sets it to False - # by default? + def send_on_control_endpoint(self, endpoint_number: int, in_request: USBControlRequest, data: bytes, blocking: bool=True): + """ + Sends a collection of USB data in response to a IN control request by the host. + + Args: + endpoint_number : The number of the IN endpoint on which data should be sent. + requested_length : The number of bytes requested by the host. + data : The data to be sent. + blocking : If true, this function should wait for the transfer to complete. + """ + requested_length = in_request.length + self.api.write_control_endpoint(endpoint_number, requested_length, blocking, bytes(data)) + + log.debug(f"moondancer.send_on_control_endpoint({endpoint_number}, {requested_length}, {len(data)}, {blocking})") + log.trace(f" moondancer.api.write_control_endpoint({endpoint_number}, {requested_length}, {blocking}, {len(data)})") + + def send_on_endpoint(self, endpoint_number: int, data: bytes, blocking: bool=True): """ Sends a collection of USB data on a given endpoint. @@ -324,8 +340,6 @@ def send_on_endpoint(self, endpoint_number: int, data: bytes, blocking: bool=Tru blocking : If true, this function will wait for the transfer to complete. """ - # TODO possibly wait until endpoint is ready to send? - self.api.write_endpoint(endpoint_number, blocking, bytes(data)) log.debug(f"moondancer.send_on_endpoint({endpoint_number}, {len(data)}, {blocking})") diff --git a/facedancer/device.py b/facedancer/device.py index 90d62edb..2bda7e3d 100644 --- a/facedancer/device.py +++ b/facedancer/device.py @@ -257,6 +257,21 @@ def clear_halt(self, endpoint_number: int, direction: USBDirection): self.backend.clear_halt(endpoint_number, direction) + def control_send(self, endpoint_number: int, in_request: USBControlRequest, data: bytes, *, blocking: bool = False): + """ Queues sending data on the provided control endpoint in + response to a IN control request. + + Args: + endpoint_number : The endpoint number to send data upon. + in_request : The control request being responded to. + data : The data to send. + blocking : If provided and true, this function will block + until the backend indicates the send is complete. + + """ + self.backend.send_on_control_endpoint(endpoint_number, in_request, data, blocking=blocking) + + def send(self, endpoint_number: int, data: bytes, *, blocking: bool = False): """ Queues sending data on the IN endpoint with the provided number. @@ -267,13 +282,9 @@ def send(self, endpoint_number: int, data: bytes, *, blocking: bool = False): until the backend indicates the send is complete. """ - # EP0 is special, as it conceptually belongs to the whole device, as it's used - # for control requests and configuration. We'll handle it directly, here. - # - # All of our backends currently automatically handle packetization and ZLPs for - # the control endpoint, so we'll skip packetizing it (which would generate spurious ZLPs). if endpoint_number == 0: - self.backend.send_on_endpoint(0, data, blocking=blocking) + # TODO upgrade to an exception with the release of Facedancer 3.1.0 + log.warning("Please use USBDevice::control_send() for control transfers") elif self.configuration: endpoint = self.configuration.get_endpoint(endpoint_number, USBDirection.IN) endpoint.send(data, blocking=blocking) diff --git a/facedancer/endpoint.py b/facedancer/endpoint.py index ed8c83b8..96f05ad4 100644 --- a/facedancer/endpoint.py +++ b/facedancer/endpoint.py @@ -100,7 +100,7 @@ def get_device(self): return self.parent.get_device() - def send(self, data: bytes, *, blocking: bool =False): + def send(self, data: bytes, *, blocking: bool = False): """ Sends data on this endpoint. Valid only for IN endpoints. Args: diff --git a/facedancer/proxy.py b/facedancer/proxy.py index 2e55cdb2..f37fc308 100644 --- a/facedancer/proxy.py +++ b/facedancer/proxy.py @@ -245,7 +245,7 @@ def _proxy_in_control_request(self, request: USBControlRequest): self.backend.stall_endpoint(0, USBDirection.IN) else: # TODO: support control endpoints other than 0 - self.send(0, data) + self.control_send(0, request, data) def _proxy_out_control_request(self, request: USBControlRequest): diff --git a/facedancer/request.py b/facedancer/request.py index 21d21966..822d5a45 100644 --- a/facedancer/request.py +++ b/facedancer/request.py @@ -251,7 +251,7 @@ def from_raw_bytes(cls, raw_bytes: bytes, *, device = None): def reply(self, data: bytes): """ Replies to the given request with a given set of bytes. """ - self.device.send(endpoint_number=0, data=data) + self.device.control_send(endpoint_number=0, in_request=self, data=data) def acknowledge(self, *, blocking: bool = False): @@ -260,7 +260,7 @@ def acknowledge(self, *, blocking: bool = False): Args: blocking : If true, the relevant control request will complete before returning. """ - self.device.send(endpoint_number=0, data=b"", blocking=blocking) + self.device.control_send(endpoint_number=0, in_request=self, data=b"", blocking=blocking) def ack(self, *, blocking: bool = False):