Showing preview only (524K chars total). Download the full file or copy to clipboard to get everything.
Repository: greatscottgadgets/Facedancer
Branch: main
Commit: 5e4ff94aff49
Files: 77
Total size: 498.6 KB
Directory structure:
gitextract_tf2gfbvd/
├── .editorconfig
├── .gitignore
├── .readthedocs.yaml
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── LICENSE
├── README.md
├── Release.make
├── docs/
│ ├── Makefile
│ ├── make.bat
│ ├── requirements.txt
│ └── source/
│ ├── conf.py
│ ├── facedancer_examples.rst
│ ├── getting_started.rst
│ ├── howto_facedancer_backend.rst
│ ├── index.rst
│ ├── library_overview.rst
│ ├── using_facedancer.rst
│ └── using_usb_proxy.rst
├── examples/
│ ├── coroutine.py
│ ├── ftdi-echo.py
│ ├── hackrf-info.py
│ ├── imperative.py
│ ├── mass-storage.py
│ ├── minimal.py
│ ├── rubber-ducky.py
│ ├── template.py
│ ├── test_minimal.py
│ └── usbproxy.py
├── facedancer/
│ ├── __init__.py
│ ├── backends/
│ │ ├── MAXUSBApp.py
│ │ ├── __init__.py
│ │ ├── base.py
│ │ ├── goodfet.py
│ │ ├── greatdancer.py
│ │ ├── greathost.py
│ │ ├── hydradancer.py
│ │ ├── libusbhost.py
│ │ ├── moondancer.py
│ │ └── raspdancer.py
│ ├── classes/
│ │ ├── __init__.py
│ │ └── hid/
│ │ ├── __init__.py
│ │ ├── descriptor.py
│ │ ├── keyboard.py
│ │ └── usage.py
│ ├── configuration.py
│ ├── core.py
│ ├── descriptor.py
│ ├── device.py
│ ├── devices/
│ │ ├── __init__.py
│ │ ├── ftdi.py
│ │ ├── keyboard.py
│ │ └── umass/
│ │ ├── __init__.py
│ │ ├── disk_image.py
│ │ └── umass.py
│ ├── endpoint.py
│ ├── errors.py
│ ├── filters/
│ │ ├── __init__.py
│ │ ├── base.py
│ │ ├── hid.py
│ │ ├── logging.py
│ │ └── standard.py
│ ├── interface.py
│ ├── logging.py
│ ├── magic.py
│ ├── proxy.py
│ ├── request.py
│ └── types.py
├── pyproject.toml
└── test/
├── README.md
├── __init__.py
├── base.py
├── device.py
├── test_alternate.py
├── test_descriptors.py
├── test_stress.py
└── test_transfers.py
================================================
FILE CONTENTS
================================================
================================================
FILE: .editorconfig
================================================
# top-most EditorConfig file
root = true
# Set our default format parameters.
[*]
charset = utf-8
indent_size = 4
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
max_line_length = 120
# Use python standard indentation for python files.
[*.py]
indent_style = space
================================================
FILE: .gitignore
================================================
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*,cover
.hypothesis/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# pyenv
.python-version
# celery beat schedule file
celerybeat-schedule
# dotenv
.env
# virtualenv
.venv/
venv/
ENV/
# Spyder project settings
.spyderproject
# Rope project settings
.ropeproject
tags
# IntelliJ Project
.idea/
# Release files
VERSION
release-files
host-packages
================================================
FILE: .readthedocs.yaml
================================================
# .readthedocs.yaml
# Read the Docs configuration file
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
# Required
version: 2
# Set the OS, Python version and other tools
build:
os: ubuntu-22.04
tools:
python: "3.12"
# Build documentation in the "docs/" directory with Sphinx
sphinx:
configuration: docs/source/conf.py
# Build PDF for docs
formats:
- pdf
python:
install:
- requirements: docs/requirements.txt
================================================
FILE: CHANGELOG.md
================================================
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
<!--
## [Unreleased]
-->
## [3.1.2] - 2025-12-05
### Fixed
* FTDI emulation stopped working with recent Windows releases. (tx @gniezen!)
### Added
* Added a usbproxy filter for logging high-level HID requests. (tx @akvadrako!)
### Security
* Updated `jinja2` from 3.1.5 to 3.1.6.
## [3.1.1] - 2025-08-01
### Added
* Hydradancer: Handle `clear_halt` (tx @kauwua!)
* Add `parent` field to all descriptors. (tx @kauwua!)
* Extend mass storage device constructor to be more configurable. (tx @gniezen!)
### Fixed
* Mass storage device was unable to use custom descriptors. (tx @gniezen!)
## [3.1.0] - 2025-01-08
> This is a breaking release which may require updates to your usage of Facedancer API's.
### Changed
* Dropped support for Python 3.8 and 3.9. The minimum supported Python version is now Python 3.10.
* The descriptor API has been changed and expanded to handle more complex device definitions:
- New USBDescriptor property: `include_in_config`, which specifies whether the descriptor should be included in a GET_CONFIGURATION response.
- Descriptors attached to endpoints are now instantiated (replaces #139)
- The `instantiate_subordinates` function is redesigned to avoid silent dropping of subordinates with duplicate identifiers.
- Orderings of declaration/insertion of subordinates are preserved, allowing control of ordering in binary configurations.
- Fixes to convert some fields to the right types in `from_binary_descriptor` methods.
- A dictionary of known strings and their indexes may be passed to `from_binary_descriptor` methods.
- The `number` field of `USBDescriptor` is made optional, as it is not required for descriptors attached in a configuration.
- The `type_number` field will now be inferred from the `raw` bytes if not otherwise specified.
- Add `@include_in_config` and `@requestable(number=N)` decorators for use on declared descriptor classes.
- Add docstrings for all `USBDescriptor` fields.
- More information: #126 #141
### Fixed
* USBProxy errors after changes for alternate interface settings.
### Added
* Round-trip support for creating Facedancer devices, configurations, interfaces, endpoints and descriptors from binary data to objects, to code, to objects and back to binary data.
* New backend method: `validate_configuration` for rejecting USB device configurations that are not supported by a given backend.
## [3.0.6] - 2024-11-27
### Fixed
* Updated Keyboard device / rubber-ducky to work with new descriptor handling features.
## [3.0.5] - 2024-11-25
### Added
* Support switching between alternate interface settings.
* Improved Facedancer descriptor functionality.
* Log a warning when Moondancer needs system permissions for the interface.
* Group Facedancer request handler suggestions by their recipients.
* Implement the `raw` field in `HIDReportDescriptor`. (tx @jalmeroth!)
### Fixed
* Moondancer: Only prime control endpoints on receipt of a setup packet.
* Moondancer: Use `ep_out_interface_enable` instead of `ep_out_prime_endpoint` where appropriate.
## [3.0.4] - 2024-10-10
### Added
* Example: `examples/coroutine.py` demonstrates how to create a custom main function and the use of coroutines.
* Keyboard shortcut: `Ctrl-C` will now gracefully exit a Facedancer emulation.
## [3.0.3] - 2024-09-19
### Added
* Support for specifying string descriptor indices.
* Allow `supported_languages = None` for device definitions.
* Provide an error message when device claim/release fails.
* New backend method: `clear_halt()`
* New backend method: `send_on_control_endpoint()`
* [HydraDancer](https://github.com/HydraDancer) backend. (tx @kauwua!)
### Fixed
* Correct byteorder for bcdUSB and bcdDevice.
* Older facedancer backends were not derived from `FacedancerBackend`.
* Log message in `handle_set_interface_request` was using the incorrect logging method. (tx @kawua!)
## [3.0.2] - 2024-08-20
### Changed
* Added support for Cynthion on Windows.
* Update docs to reflect current status of GreatFET support on Windows.
## [3.0.1] - 2024-08-19
### Changed
* USBProxy now auto-detaches kernel drivers for the device being proxied.
* Updated documentation with current status of Facedancer support on Windows.
### Fixed
* Clarify the explanatory text for endpoint numbers in the app template. (tx @salcho!)
* Shutting down Facedancer proxy devices could result in a `LIBUSB_ERROR_BUSY` (tx @mipek!)
* Facedancer devices would be incorrectly identified as `goodfet` when `/dev/ttyUSB0` exists on the host device.
* Fixed ambiguous documentation terminology to always use one of "Target Host", "Control Host".
## [3.0.0] - 2024-06-18
### Added
- Facedancer documentation has been updated and can be found at: [https://facedancer.readthedocs.io](https://facedancer.readthedocs.io)
- A new backend has been added for the Great Scott Gadgets Cynthion.
- Emulations can now set USB device speed on supported boards.
### Changed
- The Facedancer core API has been rewritten. See the Facedancer documentation for details.
- Some legacy applets have been replaced with new examples based on the modern Facedancer core:
- `facedancer-ftdi.py` => `ftdi-echo.py`
- `facedancer-keyboard.py` => `rubber-ducky.py`
- `facedancer-umass.py` => `mass-storage.py`
### Fixed
- 64bit LBA support has been added to the `mass-storage.py` example. (Tx @shutingrz!)
### Removed
- The legacy Facedancer core has been removed. If you're using scripts or training materials that depend on features or APIs removed in `v3.0.x` please use `v2.9.x`.
- All legacy applets not ported to the modern Facedancer core have been removed.
## [2.9.0] - 2024-02-09
This release is intended as a reference point for anyone who has scripts, training materials etc. that are based on Facedancer `v2.x` features or API's that have been deprecated from `v3` onwards.
Any future bug-fixes or backports to Facedancer `2.9.x` should use the [`v2.9.x branch`](https://github.com/greatscottgadgets/facedancer/tree/v2.9.x) as the starting point for forks or PR's.
### Deprecated
- The current Facedancer core will be supersed by the implementation in `future/` with the `v3.0` release.
[Unreleased]: https://github.com/greatscottgadgets/facedancer/compare/3.1.2...HEAD
[3.1.2]: https://github.com/greatscottgadgets/facedancer/compare/3.1.1...3.1.2
[3.1.1]: https://github.com/greatscottgadgets/facedancer/compare/3.1.0...3.1.1
[3.1.0]: https://github.com/greatscottgadgets/facedancer/compare/3.0.6...3.1.0
[3.0.6]: https://github.com/greatscottgadgets/facedancer/compare/3.0.5...3.0.6
[3.0.5]: https://github.com/greatscottgadgets/facedancer/compare/3.0.4...3.0.5
[3.0.4]: https://github.com/greatscottgadgets/facedancer/compare/3.0.3...3.0.4
[3.0.3]: https://github.com/greatscottgadgets/facedancer/compare/3.0.2...3.0.3
[3.0.2]: https://github.com/greatscottgadgets/facedancer/compare/3.0.1...3.0.2
[3.0.1]: https://github.com/greatscottgadgets/facedancer/compare/3.0.0...3.0.1
[3.0.0]: https://github.com/greatscottgadgets/facedancer/compare/2.9.0...3.0.0
[2.9.0]: https://github.com/greatscottgadgets/facedancer/releases/tag/2.9.0
================================================
FILE: CODE_OF_CONDUCT.md
================================================
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
straithe@greatscottgadgets.com.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.
================================================
FILE: LICENSE
================================================
Copyright (c) 2019 Katherine J. Temkin <k@ktemkin.com>
Copyright (c) 2018 Dominic Spill <dominicgs@gmail.com>
Copyright (c) 2018 Travis Goodspeed <travis@radiantmachines.com>
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation and/or
other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors
may be used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
================================================
FILE: README.md
================================================
# Facedancer 3.0
This repository houses the next generation of Facedancer software. Descended from
the original GoodFET-based Facedancer, this repository provides a python module
that provides expanded Facedancer support -- including support for multiple boards
and some pretty significant new features.
## Project Documentation
Facedancer's documentation is captured on [Read the Docs](https://facedancer.readthedocs.io/en/latest/). Raw documentation sources are in the [`docs/`](docs/) folder.
## Installation
Install this package with the following command:
pip install facedancer
After that you can import the facedancer package as usual:
$ python
>>> import facedancer
## Where are my scripts?
Facedancer 3.0 is a ground-up rewrite of the original emulation core
and does not support legacy scripts.
If you're using scripts or training materials that depend on features
or APIs deprecated in `v3.0.x` you can install the latest `v2.9.x`
release of Facedancer with:
pip install "facedancer<=3"
Legacy applets and examples can be found in the [`v2.9.x`](https://github.com/greatscottgadgets/facedancer/tree/v2.9.x)
branch.
## What is a Facedancer?
Facedancer boards are simple hardware devices that act as "remote-controlled" USB
controllers. With the proper software, you can use these boards to quickly and
easily emulate USB devices -- and to fuzz USB host controllers!
This particular software repository currently allows you to easily create emulations
of USB devices in Python. Control is fine-grained enough that you can cause all
kinds of USB misbehaviors. :)
For more information, see:
* [Travis Goodspeed's blog post on Facedancer](http://travisgoodspeed.blogspot.com/2012/07/emulating-usb-devices-with-python.html)
* [The Facedancer 21, the original supported board](http://goodfet.sourceforge.net/hardware/facedancer21/)
## USBProxy 'Nouveau' and Protocol Analysis
A major new feature of the newer Facedancer codebase is the ability to MITM (Meddler-In-The-Middle) USB connections -- replacing one of the authors' original [USBProxy](https://github.com/dominicgs/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 device (e.g. on embedded systems or games consoles).
```
+-----------------------------------------------------------------------+
+------------+ | +--------------------------------+ +---------------------------+ | +--------------+
| | | | | | | | | |
| PROXIED | | | HOST COMPUTER | | FACEDANCER DEVICE | | | TARGET USB |
| DEVICE <------> running Facedancer software <---> acts as USB-Controlled <------> HOST |
| | | | | | USB Controller | | | |
| | | | | | | | | |
+------------+ | +--------------------------------+ +---------------------------+ | +--------------+
| |
| MITM Setup (HOST + FACEDANCER) |
+-----------------------------------------------------------------------+
```
This feature is complete, but could use more documentation. Pull requests are welcome. :)
## How do I use this repository?
First, you'll likely want to set the ```BACKEND``` environment variable, which lets
the software know which type of Facedancer board you'd like to use. If this variable
isn't set, the software will try to guess for you based on what's connected. It doesn't
always make the best guesses, so you're probably better off setting it yourself.
Next, you'll probably want to check out one of the examples, or one of the pre-made scripts.
Examples in the new syntax are located under `examples`. The core Facedancer scripts in the
"old" syntax are located in `legacy-applets`.
For example:
```sh
export BACKEND=greatfet
./examples/rubber-ducky.py
```
## What boards are currently supported?
* The [Cynthion USB Test Instrument](http://greatscottgadgets.com/cynthion/) (```BACKEND=cynthion```)
* The [GreatFET One](http://greatscottgadgets.com/greatfet/) (```BACKEND=greatfet```)
* The NXP LPC4330 Xplorer board. (```BACKEND=greatfet```)
* The CCCamp 2015 rad1o badge with GreatFET l0adable (```BACKEND=greatfet```)
* All GoodFET-based Facedancers, including the common Facedancer21 (```BACKEND=goodfet```)
* RPi + Max3241 Raspdancer boards (```BACKEND=raspdancer```)
* HydraDancer and HydraUSB3 boards (```BACKEND=hydradancer```)
Note that hardware restrictions prevent the MAX3420/MAX3421 boards from emulating
more complex devices -- there's limitation on the number/type of endpoints that can be
set up. The LPC4330 boards -- such as the GreatFET -- have fewer limitations.
For a similar reason, the MAX3420/MAX3421 boards (`BACKEND=goodfet` or `BACKEND=raspdancer`)
currently cannot be used as USBProxy-nv MITM devices. All modern boards (`BACKEND=greatfet`, `BACKEND=hydradancer`)
should be fully functional.
Note that the HydraDancer and HydraUSB3 boards (`BACKEND=hydradancer`) do not currently support host-mode.
Note actual FaceDancer 3.0 does not work on Windows(some issues in pyusb...) and only GNU/Linux
## What boards could be supported soon?
* Any Linux computer with gadgetfs support (e.g. the Pi Zero or Beaglebone Black)
* Anything supporting USB-over-IP.
## What features do you plan on adding?
The roadmap is under development, but in addition to multi-board support, this repository
will eventually be home to some cool new features, including:
* High-speed ("USB 2.0") device emulation on devices with USB 2.0 PHYs.
* On-the-fly generation of USB device controllers in gateware.
## Whose fault _is_ this?
There are a lot of people to blame for the awesomeness that is this repo,
including:
* Kate Temkin (@ktemkin)
* Travis Goodspeed (@travisgoodspeed)
* Sergey Bratus (@sergeybratus)
* Dominic Spill (@dominicgs)
* Michael Ossmann (@michaelossmann)
* Mikaela Szekely (@Qyriad)
* anyone whose name appears in the git history :)
## Contributions?
... are always welcome. Shoot us a PR!
================================================
FILE: Release.make
================================================
#
# This file is part of Facedancer.
# Maintainer quick actions for generating releases.
#
# By default, use the system's "python3" binary; but note that some distros now
# correctly have 'python' as python3.
PYTHON ?= python3
all: prepare_release
.PHONY: prepare_release
PROJECT = facedancer
ifndef VERSION
$(error This Makefile is for release maintainers; and requires VERSION to be defined for a release.)
endif
# Flags for creating build archives.
# These effectively tell the release tool how to modify git-archive output to create a complete build.
ARCHIVE_FLAGS = \
--extra=VERSION $(HOST_PACKAGE_FLAGS) --prefix=$(PROJECT)-$(VERSION)/
#
# Prepares a Facedancer release based on the VERSION arguments.
# Currently, we don't yet have a RELEASENOTE filel or anything like that.
#
prepare_release:
@mkdir -p release-files/
@echo Tagging release $(VERSION).
@git tag -a v$(VERSION) $(TAG_FORCE) -m "release $(VERSION)"
@echo "$(VERSION)" > VERSION
@echo --- Creating our host-python distribution.
@rm -rf host-packages
@mkdir -p host-packages
@#Build the host libraries.
@$(PYTHON) setup.py sdist bdist_wheel -d host-packages
@echo --- Preparing the release archives.
$(eval HOST_PACKAGE_FLAGS := $(addprefix --extra=, $(wildcard host-packages/*)))
@git-archive-all $(ARCHIVE_FLAGS) release-files/$(PROJECT)-$(VERSION).tar.xz
@git-archive-all $(ARCHIVE_FLAGS) release-files/$(PROJECT)-$(VERSION).zip
@echo
@echo Archives seem to be ready in ./release-files.
@echo If everything seems okay, you probably should push the relevant tag:
@echo " git push origin v$(VERSION)"
@echo
@echo And push the relevant packages to Pypi:
@echo " python3 setup.py dsit bdist_wheel register upload"
================================================
FILE: docs/Makefile
================================================
# Minimal makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
SOURCEDIR = source
BUILDDIR = build
# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
.PHONY: help Makefile
# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
================================================
FILE: docs/make.bat
================================================
@ECHO OFF
pushd %~dp0
REM Command file for Sphinx documentation
if "%SPHINXBUILD%" == "" (
set SPHINXBUILD=sphinx-build
)
set SOURCEDIR=source
set BUILDDIR=build
if "%1" == "" goto help
%SPHINXBUILD% >NUL 2>NUL
if errorlevel 9009 (
echo.
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
echo.installed, then set the SPHINXBUILD environment variable to point
echo.to the full path of the 'sphinx-build' executable. Alternatively you
echo.may add the Sphinx directory to PATH.
echo.
echo.If you don't have Sphinx installed, grab it from
echo.http://sphinx-doc.org/
exit /b 1
)
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
goto end
:help
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
:end
popd
================================================
FILE: docs/requirements.txt
================================================
setuptools
sphinx==7.2.6
sphinx_rtd_theme==2.0.0
sphinxcontrib-apidoc
readthedocs-sphinx-search==0.3.2
jinja2==3.1.6
# needed to build api docs
facedancer @ git+https://github.com/greatscottgadgets/facedancer
================================================
FILE: docs/source/conf.py
================================================
import os, pkg_resources, sys, time
sys.path.insert(0, os.path.abspath("../../"))
sys.path.insert(0, os.path.abspath('../../facedancer'))
import sphinx_rtd_theme
extensions = [
'sphinx_rtd_theme'
]
# -- Project information -----------------------------------------------------
project = 'Facedancer'
copyright = time.strftime('2018-%Y, Great Scott Gadgets')
author = 'Great Scott Gadget'
version = pkg_resources.get_distribution('facedancer').version
release = ''
# -- General configuration ---------------------------------------------------
templates_path = ['_templates']
exclude_patterns = ['_build']
source_suffix = '.rst'
master_doc = 'index'
language = "en"
exclude_patterns = []
pygments_style = None
extensions = [
'sphinx.ext.autodoc',
'sphinx.ext.extlinks',
'sphinx.ext.napoleon',
'sphinx.ext.viewcode',
'sphinxcontrib.apidoc',
]
# configure extension: sphinxcontrib.apidoc
apidoc_module_dir = '../../facedancer'
apidoc_output_dir = 'api_docs'
apidoc_excluded_paths = ['test']
apidoc_separate_modules = True
# configure extension: extlinks
extlinks = {
'repo': ('https://github.com/greatscottgadgets/facedancer/blob/main/%s', '%s'),
'example': ('https://github.com/greatscottgadgets/facedancer/blob/main/examples/%s', '%s'),
}
# configure extension: napoleon
napoleon_google_docstring = True
napoleon_numpy_docstring = False
napoleon_include_init_with_doc = True
napoleon_use_ivar = True
napoleon_include_private_with_doc = False
napoleon_include_special_with_doc = True
napoleon_use_param = False
# -- Options for HTML output -------------------------------------------------
# run pip install sphinx_rtd_theme if you get sphinx_rtd_theme errors
html_theme = "sphinx_rtd_theme"
html_css_files = ['status.css']
================================================
FILE: docs/source/facedancer_examples.rst
================================================
===================
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 <https://github.com/greatscottgadgets/cynthion/issues/170>`__
There are a number of :repo:`Facedancer examples<examples/>` available that demonstrate emulation of various USB device functions.
:example:`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.
.. list-table:: Target Host Compatibility
:widths: 30 30 30
:header-rows: 1
* - Linux
- macOS
- Windows
* - ✅
- ✅
- ✅
:example:`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.
.. list-table:: Target Host Compatibility
:widths: 30 30 30
:header-rows: 1
* - Linux
- macOS
- Windows
* - ✅
- ❌
- ✅
:example:`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:
.. code-block :: sh
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:
.. code-block :: sh
mount -t auto -o loop disk.img /mnt
Remember to unmount it before using it with the device emulation!
.. list-table:: Target Host Compatibility
:widths: 30 30 30
:header-rows: 1
* - Linux
- macOS
- Windows
* - ✅
- ✅
- ❌
================================================
FILE: docs/source/getting_started.rst
================================================
================================================
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 <https://github.com/greatscottgadgets/cynthion/issues/170>`__
Install the Facedancer library
------------------------------
You can install the Facedancer library from the `Python Package Index (PyPI) <https://pypi.org/project/facedancer/>`__, a `release archive <https://github.com/greatscottgadgets/Facedancer/releases>`__ or directly from `source <https://github.com/greatscottgadgets/Facedancer/>`__.
Install From PyPI
^^^^^^^^^^^^^^^^^
You can use the `pip <https://pypi.org/project/pip/>`__ tool to install the Facedancer library from PyPI using the following command:
.. code-block :: sh
pip install facedancer
For more information on installing Python packages from PyPI please refer to the `"Installing Packages" <https://packaging.python.org/en/latest/tutorials/installing-packages/>`__ section of the Python Packaging User Guide.
Install From Source
^^^^^^^^^^^^^^^^^^^
.. code-block :: sh
git clone https://github.com/greatscottgadgets/facedancer.git
cd facedancer/
Once you have the source code downloaded you can install the Facedancer library with:
.. code-block :: sh
pip install .
Run a Facedancer example
------------------------
Create a new Python file called `rubber-ducky.py` with the following content:
.. code-block :: python
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:
.. code-block :: sh
python ./rubber-ducky.py
================================================
FILE: docs/source/howto_facedancer_backend.rst
================================================
=====================================
How to write a new Facedancer Backend
=====================================
Facedancer board backends can be found in the :repo:`facedancer/backends/<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:
.. code-block :: python
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:
.. literalinclude:: ../../facedancer/backends/base.py
:language: python
:emphasize-lines: 0
:linenos:
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:
.. code-block :: python
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.
================================================
FILE: docs/source/index.rst
================================================
========================
Facedancer Documentation
========================
.. toctree::
:maxdepth: 2
:caption: User Documentation
getting_started
library_overview
using_facedancer
using_usb_proxy
facedancer_examples
.. toctree::
:maxdepth: 2
:caption: Developer Documentation
howto_facedancer_backend
.. toctree::
:maxdepth: 1
:caption: API Documentation
api_docs/modules
:ref:`genindex` | :ref:`modindex`
================================================
FILE: docs/source/library_overview.rst
================================================
================================================
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:
.. code-block:: text
+--------------------------------+
| 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)
* :mod:`facedancer.device`
-- :class:`~facedancer.device.USBDevice` is the device root. It is responsible for managing the device's descriptors and marshalling host requests.
* :mod:`facedancer.configuration`
-- :class:`~facedancer.configuration.USBConfiguration` is responsible for managing the device's configuration descriptor(s).
* :mod:`facedancer.interface`
-- :class:`~facedancer.interface.USBInterface` is responsible for managing the device's interface descriptor(s).
* :mod:`facedancer.endpoint`
-- :class:`~facedancer.endpoint.USBEndpoint` is responsible for managing the device's endpoints.
* :mod:`facedancer.request`
-- :class:`~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:
* :mod:`facedancer.descriptor`
-- contains functionality for working with USB descriptors.
* :mod:`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.
* :mod:`facedancer.classes`
* :mod:`facedancer.devices`
USB Proxy
~~~~~~~~~
These modules contain the USB Proxy implementation.
* :mod:`facedancer.proxy`
-- contains the :class:`~facedancer.proxy.USBProxyDevice` implementation.
* :mod:`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.
* :mod:`facedancer.backends`
Supporting Functionality
~~~~~~~~~~~~~~~~~~~~~~~~
* :mod:`facedancer.core`
-- the Facedancer scheduler and execution core.
* :mod:`facedancer.errors`
-- an error type, there should probably be more.
* :mod:`facedancer.types`
-- various type definitions and constants.
* :mod:`facedancer.logging`
-- logging boilerplate.
================================================
FILE: docs/source/using_facedancer.rst
================================================
================================================
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:
.. literalinclude:: ../../examples/minimal.py
:language: python
:lines: 7-
:lineno-start: 7
:linenos:
Device Descriptor
-----------------
The entry-point for most Facedancer emulations is the :class:`~facedancer.device.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 :class:`~facedancer.device.USBBaseDevice` class if you'd like to
provide your own implementation of the standard request handlers.
See, for example, :class:`~facedancer.proxy.USBProxyDevice`.
Starting with the initial class declaration we can define our device as:
.. code-block:: python
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 :class:`~facedancer.device.USBDevice`.
We also annotate our class with the :func:`@use_inner_classes_automatically <facedancer.magic.use_inner_classes_automatically>` decorator which allows us to use a declarative style when including our devices configuration, interface and endpoints. It's :mod:`~facedancer.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 :class:`~facedancer.device.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 :class:`~facedancer.configuration.USBConfiguration` class containing at least one :class:`~facedancer.interface.USBInterface` class containing zero or more :class:`~facedancer.endpoint.USBEndpoint` class.
Here we define a configuration with a single interface containing two endpoints. The first endpoint has direction :class:`~facedancer.types.USBDirection.IN` and will be responsible for responding to data requests from the host. The second endpoint has direction :class:`~facedancer.types.USBDirection.OUT` and will be responsible for receiving data from the host.
.. code-block:: python
:emphasize-lines: 5-
...
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 :mod:`facedancer.endpoint` and :mod:`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.
.. code-block:: python
:emphasize-lines: 11-13, 19-21
...
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 :class:`~facedancer.endpoint.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:
* :func:`@control_request_handler <facedancer.request.control_request_handler>`
* :func:`@standard_request_handler <facedancer.request.standard_request_handler>`
* :func:`@vendor_request_handler <facedancer.request.vendor_request_handler>`
* :func:`@class_request_handler <facedancer.request.class_request_handler>`
* :func:`@reserved_request_handler <facedancer.request.reserved_request_handler>`
The second set defines the target for the control request:
* :func:`@to_device <facedancer.request.to_device>`
* :func:`@to_this_endpoint <facedancer.request.to_this_endpoint>`
* :func:`@to_any_endpoint <facedancer.request.to_any_endpoint>`
* :func:`@to_this_interface <facedancer.request.to_this_interface>`
* :func:`@to_any_interface <facedancer.request.to_any_interface>`
* :func:`@to_other <facedancer.request.to_other>`
For instance, to define some vendor request handlers you can do:
.. code-block:: python
:emphasize-lines: 7-
...
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 :class:`~facedancer.request.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!
.. literalinclude:: ../../examples/test_minimal.py
:language: python
:linenos:
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:
.. code-block:: shell
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:
.. code-block:: text
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 :example:`annotated template <template.py>` which provides an excellent reference source when building your own devices:
.. literalinclude:: ../../examples/template.py
:language: python
:lines: 8-276
:lineno-start: 8
:linenos:
================================================
FILE: docs/source/using_usb_proxy.rst
================================================
================================================
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 <https://github.com/dominicgs/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).
.. code-block:: text
+-----------------------------------------------------------------------+
+------------+ | +--------------------------------+ +---------------------------+ | +--------------+
| | | | | | | | | |
| 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.
.. literalinclude:: ../../examples/usbproxy.py
:language: python
:lines: 7-
:lineno-start: 7
:linenos:
Setting up a USB Proxy begins by creating an instance of the :class:`~facedancer.proxy.USBProxyDevice` with the vendor and product id's of the proxied device as arguments.
The actual behaviour of USB Proxy is governed by adding :mod:`~facedancer.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 :class:`~facedancer.filters.standard.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 :class:`~facedancer.filters.logging.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 :class:`~facedancer.filters.base.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:
.. code-block:: python
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 :class:`~facedancer.proxy.USBProxyDevice`'s :meth:`~facedancer.proxy.USBProxyDevice.add_filter` method:
.. code-block:: python
# add my filter to the proxy
proxy.add_filter(MyFilter())
You can find more information about the supported handlers in the :class:`~facedancer.filters.base.USBProxyFilter` documentation.
================================================
FILE: examples/coroutine.py
================================================
#!/usr/bin/env python3
# pylint: disable=unused-wildcard-import, wildcard-import
#
# This file is part of Facedancer.
#
import asyncio
import sys
from facedancer import *
from facedancer.errors import EndEmulation
from facedancer.logging import configure_default_logging, log
from minimal import MyDevice
async def my_exit_handler(bindkey: bytes):
"""A custom exit handler that will gracefully shut down the
emulation when the user presses the given key combination.
"""
import platform
if platform.system() == "Windows":
import msvcrt
def get_key():
key = msvcrt.getch()
# check for, and propagate Control-C
if key == b'\x03':
raise KeyboardInterrupt
return key
else:
import termios, tty
def get_key():
fd = sys.stdin.fileno()
restore = termios.tcgetattr(fd)
try:
tty.setcbreak(fd)
key = sys.stdin.read(1)
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, restore)
return str.encode(key)
while True:
key = get_key()
if key == bindkey:
raise EndEmulation("User quit the emulation.")
await asyncio.sleep(0)
def my_main_function(device, *coroutines):
"""
A custom main function for emulating a Facedancer device.
"""
# Set up our logging output.
configure_default_logging(level=20)
# Add a custom exit handler to our coroutines.
coroutines = (*coroutines, my_exit_handler(b'\x05'))
# Run the relevant code, along with any added coroutines.
log.info("Starting emulation, press 'Control-E' to disconnect and exit.")
device.emulate(*coroutines)
if __name__ == "__main__":
my_main_function(MyDevice())
================================================
FILE: examples/ftdi-echo.py
================================================
#!/usr/bin/env python3
#
# This file is part of Facedancer.
#
import logging
from facedancer import main
from facedancer.devices.ftdi import FTDIDevice
device = FTDIDevice()
async def send_hello():
""" Waits for the host to connect, and then says hello. """
logging.info("Waiting for the host to connect.")
await device.wait_for_host()
logging.info("Host connected!")
logging.info("Telling the user hello...")
device.transmit("Hello! Welcome to the FTDI demo.\n")
device.transmit("Enter any text you'd like, and we'll send it back in UPPERCASE.\n")
def uppercasize(data):
""" Convert any received data to uppercase. """
# Convert the data to uppercase...
uppercase = data.decode('utf-8').upper()
# ... convert serial line endings to Python line endings...
uppercase = uppercase.replace('\r', '\n')
# ... and transmit our response.
device.transmit(uppercase)
# Override the serial data handler by adding a singleton method on our object.
# This is an easy way to create one-off objects. :)
device.handle_serial_data_received = uppercasize
main(device, send_hello())
================================================
FILE: examples/hackrf-info.py
================================================
#!/usr/bin/env python3
# pylint: disable=unused-wildcard-import, wildcard-import
#
# This file is part of Facedancer.
#
from facedancer import *
from facedancer import main
@use_inner_classes_automatically
class HackRF(USBDevice):
""" Device that emulates a HackRF enough to appear in ``hackrf_info``.
You can try to create this script yourself! It's relatively easy using the
--suggest option and the ``template.py`` example.
"""
# Show up as a HackRF.
product_string : str = "HackRF One (Emulated)"
manufacturer_string : str = "Great Scott Gadgets"
vendor_id : int = 0x1d50
product_id : int = 0x6089
# Most hosts won't accept a device unless it has a configuration
# and an interface. We'll add some default/empty ones. Facedancer
# provides sane defaults, so we don't need to do anything else!
class DefaultConfiguration(USBConfiguration):
class DefaultInterface(USBInterface):
pass
#
# Vendor requests.
#
# These templates were generated using --suggest, and then modified
# by the author to get the functionality she wanted.
#
@vendor_request_handler(number=14, direction=USBDirection.IN)
@to_device
def handle_control_request_14(self, request):
# The --suggest command gives us the following info:
# Most recent request was for 1B of data.
# Theoretically, this is the point where you'd experiment
# with providing one-byte responses and see what `hackrf_info` does.
request.reply([2])
#
# From here on out, we'll give these requests more descriptive names,
# rather than using the ones from --suggest. When creating this, we'd
# theoretically do our reverse engineering, and then rename the request.
#
# Because the decorator indicates to the backend that this is a vendor
# request handler, these names can be whatever we'd like -- and we don't
# have to update anything when we change them!
#
@vendor_request_handler(number=15, direction=USBDirection.IN)
@to_device
def handle_get_version_request(self, request):
# Most recent request was for 255B of data.
# When hackrf_info gets to this point, we can see that it's
# failing with "hackrf_version_string_read() failed: Pipe error (-1000)."
#
# That's a pretty good hint of what it expects.
request.reply(b"Sekret Facedancer Version")
@vendor_request_handler(number=18, direction=USBDirection.IN)
@to_device
def handle_get_serial_request(self, request):
# Most recent request was for 24B of data.
request.reply(b'A' * 24)
#
# There's one last thing to do -- we'll need to implement one more
# simple request. We'll leave this last one as an exercise to the reader. :)
#
main(HackRF)
================================================
FILE: examples/imperative.py
================================================
#!/usr/bin/env python3
# pylint: disable=unused-wildcard-import, wildcard-import
#
# This file is part of Facedancer.
#
""" Example for using the imperative API. """
#
# The other Facedancer examples tend to use the declarative API,
# as it's more succinct, and typically can be created faster.
#
# However, the new API still supports an imperative syntax,
# which may be useful in some circumstances.
#
import logging
from facedancer import main
from facedancer import *
class ImperativeDevice(USBDevice):
def __init__(self):
# We can still implement our types imperatively, like in the old API.
super().__init__(
vendor_id=0x1234,
product_string="Imperatively-created Device"
)
# The constructor arguments to each type accept the same fields as the declarative
# API -- and like the declarative API, parameters have sane defaults...
configuration = USBConfiguration()
self.add_configuration(configuration)
# ... which means we don't really need to do much to create the various components.
interface = USBInterface()
configuration.add_interface(interface)
# Like the declarative APIs, endpoints require a number and direction.
out_endpoint = USBEndpoint(number=3, direction=USBDirection.OUT)
interface.add_endpoint(out_endpoint)
#
# We'll still use our request decorators to declare request handlers
# on the relevant objects...
#
@vendor_request_handler(number=13)
def handle_my_request(self, request):
request.acknowledge()
#
# ... and callbacks continue to work the same way.
#
def handle_data_received(self, endpoint, data):
logging.info(f"New data: {data} on {endpoint}.")
main(ImperativeDevice())
================================================
FILE: examples/mass-storage.py
================================================
#!/usr/bin/env python3
#
# This file is part of Facedancer.
#
import sys
import logging
from facedancer import main
from facedancer.devices.umass import RawDiskImage
from facedancer.devices.umass import USBMassStorageDevice
# usage instructions
if len(sys.argv)==1:
print("Usage: mass-storage.py disk.img")
sys.exit(1)
# get disk image filename and clear arguments
filename = sys.argv[1]
sys.argv = [sys.argv[0]]
# open our disk image
disk_image = RawDiskImage(filename, 512, verbose=3)
# create the device
device = USBMassStorageDevice(disk_image)
async def hello():
""" Waits for the host to connect, and then says hello. """
logging.info("Waiting for the host to connect.")
await device.wait_for_host()
logging.info("Host connected!")
main(device, hello())
# Creating a disk image for testing:
#
# dd if=/dev/zero of=disk.img bs=1M count=100
# mkfs -t ext4 disk.img
# mount -t auto -o loop disk.img /mnt
================================================
FILE: examples/minimal.py
================================================
#!/usr/bin/env python3
# pylint: disable=unused-wildcard-import, wildcard-import
#
# This file is part of Facedancer.
#
import logging
from facedancer import *
from facedancer import main
@use_inner_classes_automatically
class MyDevice(USBDevice):
product_string : str = "Example USB Device"
manufacturer_string : str = "Facedancer"
vendor_id : int = 0x1209
product_id : int = 0x0001
device_speed : DeviceSpeed = DeviceSpeed.FULL
class MyConfiguration(USBConfiguration):
class MyInterface(USBInterface):
class MyInEndpoint(USBEndpoint):
number : int = 1
direction : USBDirection = USBDirection.IN
max_packet_size : int = 64
def handle_data_requested(self: USBEndpoint):
logging.info("handle_data_requested")
self.send(b"device on bulk endpoint")
class MyOutEndpoint(USBEndpoint):
number : int = 1
direction : USBDirection = USBDirection.OUT
max_packet_size : int = 64
def handle_data_received(self: USBEndpoint, data):
logging.info(f"device received {data} on bulk endpoint")
@vendor_request_handler(number=1, direction=USBDirection.IN)
@to_device
def my_in_vendor_request_handler(self: USBDevice, request: USBControlRequest):
logging.info("my_in_vendor_request_handler")
request.reply(b"device on control endpoint")
@vendor_request_handler(number=2, direction=USBDirection.OUT)
@to_device
def my_out_vendor_request_handler(self: USBDevice, request: USBControlRequest):
logging.info(f"device received {request.index} {request.value} {bytes(request.data)} on control endpoint")
request.ack()
if __name__ == "__main__":
main(MyDevice)
================================================
FILE: examples/rubber-ducky.py
================================================
#!/usr/bin/env python3
#
# This file is part of Facedancer.
#
""" USB 'Rubber Ducky' example; enters some text via the keyboard module. """
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():
logging.info("Beginning message typing demo...")
await asyncio.sleep(2)
await device.type_string("echo Hello, Facedancer!\n")
logging.info("Typing complete. Idly handling USB requests.")
main(device, type_letters())
================================================
FILE: examples/template.py
================================================
#!/usr/bin/env python3
# pylint: disable=unused-wildcard-import, wildcard-import
#
# This file is part of Facedancer.
#
""" Example template for creating new Facedancer devices. """
import logging
from facedancer import main
from facedancer import *
from facedancer.classes import USBDeviceClass
@use_inner_classes_automatically
class TemplateDevice(USBDevice):
""" This class is meant to act as a template to help you get acquainted with Facedancer."""
#
# Core 'dataclass' definitions.
# These define the basic way that a Facedancer device advertises itself to the host.
#
# Every one of these is optional. The defaults are relatively sane, so you can mostly
# ignore these unless you want to change them! See the other examples for more minimal
# data definitions.
#
# The USB device class, subclass, and protocol for the given device.
# Often, we'll leave these all set to 0, which means the actual class is read
# from the interface.
#
# Note that we _need_ the type annotations on these. Without them, Python doesn't
# consider them valid dataclass members, and ignores them. (This is a detail of python3.7+
# dataclasses.)
#
device_class : int = 0
device_subclass : int = 0
protocol_revision_number : int = 0
# The maximum packet size on EP0. For most devices, the default value of 64 is fine.
max_packet_size_ep0 : int = 64
# The vendor ID and product ID that we want to give our device.
vendor_id : int = 0x610b
product_id : int = 0x4653
# The string descriptors we'll provide for our device.
# Note that these should be Python strings, and _not_ bytes.
manufacturer_string : str = "Facedancer"
product_string : str = "Generic USB Device"
serial_number_string : str = "S/N 3420E"
# This tuple is a list of languages we're choosing to support.
# This gives us an opportunity to provide strings in various languages.
# We don't typically use this; so we can leave this set to a language of
# your choice.
supported_languages : tuple = (LanguageIDs.ENGLISH_US,)
# The revision of the device hardware. This doesn't matter to the USB specification,
# but it's sometimes read by drivers. 0x0001 represents "0.1" in BCD.
device_revision : int = 0x0001
# The revision of the USB specification that this device adheres to.
# Typically, you'll leave this at 0x0200 which represents "2.0" in BCD.
usb_spec_version : int = 0x0200
#
# We'll define a single configuration on our device. To be compliant,
# every device needs at least a configuration and an interface.
#
# Note that we don't need to do anything special to have this be used.
# As long as we're using the @use_inner_classes_automatically decorator,
# this configuration will automatically be instantiated and used.
#
class TemplateConfiguration(USBConfiguration):
#
# Configuration fields.
#
# Again, all of these are optional; and the default values
# are sane and useful.
#
# Configuration number. Every configuration should have a unique
# number, which should count up from one. Note that a configuration
# shouldn't have a number of 0, as that's USB for "unconfigured".
configuration_number : int = 1
# A simple, optional descriptive name for the configuration. If provided,
# this is referenced in the configuration's descriptor.
configuration_string : str = None
# This setting is set to true if the device can run without bus power,
# or false if it pulls its power from the USB bus.
self_powered : bool = False
# This setting is set to true if the device can ask that the host
# wake it up from sleep. If set to true, the host may choose to
# leave power on to the device when the host is suspended.
supports_remote_wakeup : bool = True
# The maximum power the device will use in this configuration, in mA.
# Typically, most devices will request 500mA, the maximum allowed.
max_power : int = 500
class TemplateInterface(USBInterface):
#
# Interface fields.
# Again, all optional and with useful defaults.
#
# The interface index. Each interface should have a unique index,
# starting from 0.
number : int = 0
# The information about the USB class implemented by this interface.
# This is the place where you'd specify if this is e.g. a HID device.
class_number : int = USBDeviceClass.VENDOR_SPECIFIC
subclass_number : int = 0
protocol_number : int = 0
# A short description of the interface. Optional and typically only informational.
interface_string : str = None
#
# Here's where we define any endpoints we want to add to the device.
# These behave essentially the same way as the above.
#
class TemplateInEndpoint(USBEndpoint):
#
# Endpoints are unique in that they have two _required_
# properties -- their number and direction.
#
# Together, these two fields form the endpoint's address.
# Endpoint numbers should be > 0, since endpoint 0 is reserved as the default pipe by the spec.
number : int = 1
direction : USBDirection = USBDirection.IN
#
# The remainder of the fields are optional and have useful defaults.
#
# The transfer type selects how data will be transferred over the endpoints.
# The currently supported types are BULK and INTERRUPT.
transfer_type : USBTransferType = USBTransferType.BULK
# The maximum packet size determines how large packets are allowed to be.
# For a full speed device, a max-size value of 64 is typical.
max_packet_size : int = 64
# For interrupt endpoints, the interval specifies how often the host should
# poll the endpoint, in milliseconds. 10ms is a typical value.
interval : int = 0
#
# Let's add an event handler. This one is called whenever the host
# wants to read data from the device.
#
def handle_data_requested(self):
# We can reply to this request using the .send() method on this
# endpoint, like so:
self.send(b"Hello!")
# We can also get our parent interface using .parent;
# or a reference to our device using .get_device().
class TemplateOutEndpoint(USBEndpoint):
#
# We'll use a more typical set of properties for our OUT endpoint.
#
number : int = 1
direction : USBDirection = USBDirection.OUT
#
# We'll also demonstrate use of another event handler.
# This one is called whenever data is sent to this endpoint.
#
def handle_data_received(self, data):
logging.info(f"Received data: {data}")
#
# Any of our components can use callback functions -- not just our endpoints!
# The callback names are the same no matter where we use them.
#
def handle_data_received(self, endpoint, data):
#
# When using a callback on something other than an endpoint, our function's
# signature is slightly different -- it takes the relevant endpoint as an
# argument, as well.
#
# We'll delegate this back to the core handler, here, so it propagates to our subordinate
# endpoints -- but we don't have to! If we wanted to, we could call functions on the
# endpoint itself. This is especially useful if we're hooking handle_data_requested(),
# where we can use endpoint.send() to provide the relevant data.
super().handle_data_received(endpoint, data)
# Note that non-endpoints have a get_endpoint() method, which you can use to get references
# to endpoints by their endpoint numbers / directions. This is useful if you want to
# send something on another endpoint in response to data received.
#
# The device also has a .send() method, which accepts an endpoint number and the data to
# be sent. This is equivalent to calling .send() on the relevant endpoint.
#
# We can very, very easily add request handlers to our devices.
#
@vendor_request_handler(number=12)
def handle_my_request(self, request):
#
# By decorating this function with "vendor_request_handler", we've ensured this
# function is called to handle vendor request 12. We can also add other arguments to
# the vendor_request_handler function -- it'll accept a keyword argument for every
# property on the request. If you provide these, the handler will only be called
# if the request matches the relevant constraint.
#
# For example, @vendor_request_handler(number=14, direction=USBDirection.IN, index_low=3)
# means the decorated function is only called to handle vendor request 14 for IN requests
# where the low byte of the index is 3.
#
# Other handler decorators exist -- like "class_request_handler" or "standard_request_handler"
#
# Replying to an IN request is easy -- you just provide the reply data using request.reply().
request.reply(b"Hello, there!")
@vendor_request_handler(number=1, direction=USBDirection.OUT)
@to_device
def handle_another_request(self, request):
#
# Another set of convenience decorators exist to refine requests.
# Decorators like `to_device` or `to_any_endpoint` chain with our
# request decorators, and are syntax sugar for having an argument like
# ``recipient=USBRequestRecipient.DEVICE`` in the handler decorator.
#
# For out requests, in lieu of a response, we typically want to acknowledge
# the request. This can be accomplished by calling .acknowledge() or .ack()
# on the request.
request.ack()
# Of course, if we want to let the host know we can't handle a request, we
# may also choose to stall it. This is as simple as calling request.stall().
#
# Note that request handlers can be used on configurations, interfaces, and
# endpoints as well. For the latter two cases, the decorators `to_this_interface`
# and `to_this_endpoint` are convenient -- they tell a request to run only if
# it's directed at that endpoint in particular, as selected by its ``index`` parameter.
#
# Facedancer ships with a default main() function that you can use to set up and run
# your device. It ships with some nice features -- including a ``--suggest`` function
# that can suggest pieces of boilerplate code that might be useful in device emulation.
#
# main() will accept either the type of device to emulate, or an device instance.
# It'll also accept asyncio coroutines, in case you want to run things alongside the
# relevant device code. See e.g. `examples/rubber-ducky.py` for an example.
#
main(TemplateDevice)
#
# Of course, this template looks verbose as heck.
# For an example that's much less verbose, check out `examples/hackrf-info.py`.
#
================================================
FILE: examples/test_minimal.py
================================================
import logging
def main():
import asyncio
import usb1
VENDOR_REQUEST = 0x65
MAX_TRANSFER_SIZE = 64
with usb1.USBContext() as context:
#logging.info("Host: waiting for device to connect")
#await asyncio.sleep(1)
device_handle = context.openByVendorIDAndProductID(0x1209, 0x0001)
if device_handle is None:
raise Exception("device not found")
device_handle.claimInterface(0)
# test IN endpoint
logging.info("Testing bulk IN endpoint")
response = device_handle.bulkRead(
endpoint = 0x81,
length = MAX_TRANSFER_SIZE,
timeout = 1000,
)
logging.info(f"[host] received '{response}' from bulk endpoint")
print("")
# test OUT endpoint
logging.info("Testing bulk OUT endpoint")
response = device_handle.bulkWrite(
endpoint = 0x01,
data = b"host say oh hai on bulk endpoint",
timeout = 1000,
)
print(f"sent {response} bytes\n")
# test IN vendor request handler
logging.info("Testing IN control transfer")
response = device_handle.controlRead(
request_type = usb1.TYPE_VENDOR | usb1.RECIPIENT_DEVICE,
request = 1,
index = 2,
value = 3,
length = MAX_TRANSFER_SIZE,
timeout = 1000,
)
logging.info(f"[host] received '{response}' from control endpoint")
print("")
# test OUT vendor request handler
logging.info("Testing OUT control transfer")
response = device_handle.controlWrite(
request_type = usb1.TYPE_VENDOR | usb1.RECIPIENT_DEVICE,
request = 2,
index = 3,
value = 4,
data = b"host say oh hai on control endpoint",
timeout = 1000,
)
print(f"sent {response} bytes\n")
if __name__ == "__main__":
logging.getLogger().setLevel(logging.DEBUG)
main()
================================================
FILE: examples/usbproxy.py
================================================
#!/usr/bin/env python3
#
# This file is part of Facedancer.
#
""" USB Proxy example; forwards all USB transactions and logs them to the console. """
from facedancer import *
from facedancer import main
from facedancer.proxy import USBProxyDevice
from facedancer.filters import USBProxySetupFilters, USBProxyPrettyPrintFilter
# replace with the proxied device's information
ID_VENDOR=0x09e8
ID_PRODUCT=0x0031
if __name__ == "__main__":
# create a USB Proxy Device
proxy = USBProxyDevice(idVendor=ID_VENDOR, idProduct=ID_PRODUCT)
# add a filter to forward control transfers between the target host and
# proxied device
proxy.add_filter(USBProxySetupFilters(proxy, verbose=0))
# add a filter to log USB transactions to the console
proxy.add_filter(USBProxyPrettyPrintFilter(verbose=5))
main(proxy)
================================================
FILE: facedancer/__init__.py
================================================
# Standard types.
from .device import USBDevice
from .configuration import USBConfiguration
from .interface import USBInterface
from .endpoint import USBEndpoint
from .descriptor import USBDescriptor, USBClassDescriptor, USBDescriptorTypeNumber, StringRef
# Control request handlers.
from .request import standard_request_handler, class_request_handler, vendor_request_handler
from .request import to_device, to_this_endpoint, to_this_interface, to_other
from .request import to_any_endpoint, to_any_interface
from .request import USBControlRequest
# Raw types.
from .types import USBDirection, USBTransferType, USBUsageType, USBSynchronizationType
from .types import USBRequestType, USBRequestRecipient, USBStandardRequests, LanguageIDs
from .types import DeviceSpeed
# Decorators.
from .magic import use_automatically, use_inner_classes_automatically
from .descriptor import include_in_config, requestable
# Alias objects to make them easier to import.
from .backends import *
from .core import FacedancerUSBApp, FacedancerUSBHostApp, FacedancerBasicScheduler
from .devices import default_main as main
# Wildcard import.
__all__ = [
'USBDevice', 'USBConfiguration', 'USBInterface', 'USBEndpoint', 'USBDescriptor',
'USBClassDescriptor', 'USBDescriptorTypeNumber', 'standard_request_handler',
'class_request_handler', 'vendor_request_handler', 'to_device', 'to_this_endpoint',
'to_any_endpoint', 'to_this_interface', 'to_any_interface', 'to_other',
'USBDirection', 'USBTransferType', 'USBUsageType', 'USBSynchronizationType',
'USBRequestType', 'USBRequestRecipient', 'USBStandardRequests', 'LanguageIDs',
'DeviceSpeed', 'use_automatically', 'use_inner_classes_automatically',
'USBControlRequest', 'include_in_config', 'requestable', 'StringRef',
]
================================================
FILE: facedancer/backends/MAXUSBApp.py
================================================
# MAXUSBApp.py
#
# Contains class definition for MAXUSBApp.
import time
from ..core import FacedancerApp
from .base import FacedancerBackend
class MAXUSBApp(FacedancerApp, FacedancerBackend):
app_name = "MAXUSB"
reg_ep0_fifo = 0x00
reg_ep1_out_fifo = 0x01
reg_ep2_in_fifo = 0x02
reg_ep3_in_fifo = 0x03
reg_setup_data_fifo = 0x04
reg_ep0_byte_count = 0x05
reg_ep1_out_byte_count = 0x06
reg_ep2_in_byte_count = 0x07
reg_ep3_in_byte_count = 0x08
reg_ep_stalls = 0x09
reg_clr_togs = 0x0a
reg_endpoint_irq = 0x0b
reg_endpoint_interrupt_enable = 0x0c
reg_usb_irq = 0x0d
reg_usb_interrupt_enable = 0x0e
reg_usb_control = 0x0f
reg_cpu_control = 0x10
reg_pin_control = 0x11
reg_revision = 0x12
reg_function_address = 0x13
reg_io_pins = 0x14
# bitmask values for reg_endpoint_irq = 0x0b
is_setup_data_avail = 0x20 # SUDAVIRQ
is_in3_buffer_avail = 0x10 # IN3BAVIRQ
is_in2_buffer_avail = 0x08 # IN2BAVIRQ
is_out1_data_avail = 0x04 # OUT1DAVIRQ
is_out0_data_avail = 0x02 # OUT0DAVIRQ
is_in0_buffer_avail = 0x01 # IN0BAVIRQ
# bitmask values for reg_usb_control = 0x0f
usb_control_vbgate = 0x40
usb_control_connect = 0x08
# bitmask values for reg_pin_control = 0x11
interrupt_level = 0x08
full_duplex = 0x10
ep0_in_nak = (1 << 5)
ep2_in_nak = (1 << 6)
ep3_in_nak = (1 << 7)
# TODO: Support a generic MaxUSB interface that doesn't
# depend on any GoodFET details.
@staticmethod
def bytes_as_hex(b, delim=" "):
return delim.join(["%02x" % x for x in b])
# HACK: but given the limitations of the MAX chips, it seems necessary
def send_on_endpoint(self, ep_num, data, blocking=False):
if ep_num == 0:
fifo_reg = self.reg_ep0_fifo
bc_reg = self.reg_ep0_byte_count
elif ep_num == 2:
fifo_reg = self.reg_ep2_in_fifo
bc_reg = self.reg_ep2_in_byte_count
elif ep_num == 3:
fifo_reg = self.reg_ep3_in_fifo
bc_reg = self.reg_ep3_in_byte_count
else:
raise ValueError('endpoint ' + str(ep_num) + ' not supported')
# FIFO buffer is only 64 bytes, must loop
while len(data) > 64:
self.write_bytes(fifo_reg, data[:64])
self.write_register(bc_reg, 64, ack=True)
data = data[64:]
self.write_bytes(fifo_reg, data)
self.write_register(bc_reg, len(data), ack=True)
if self.verbose > 1:
print(self.app_name, "wrote", self.bytes_as_hex(data), "to endpoint",
ep_num)
# HACK: but given the limitations of the MAX chips, it seems necessary
def read_from_endpoint(self, ep_num):
if ep_num != 1:
return b''
byte_count = self.read_register(self.reg_ep1_out_byte_count)
if byte_count == 0:
return b''
data = self.read_bytes(self.reg_ep1_out_fifo, byte_count)
if self.verbose > 1:
print(self.app_name, "read", self.bytes_as_hex(data), "from endpoint",
ep_num)
return data
def stall_endpoint(self, ep_number, direction=0):
"""
Stalls an arbitrary endpoint.
Args:
ep_number : The endpoint number to be stalled
direction : 0 for out, 1 for in
"""
if self.verbose > 0:
print(self.app_name, "stalling endpoint {}".format(ep_number))
# TODO: Verify our behavior, here. The original facedancer code stalls
# EP0 both _in_ and out, as well as uses the special STALL SETUP bit.
# Is this really what we want?
if ep_number == 0:
self.write_register(self.reg_ep_stalls, 0x23)
elif ep_number < 4:
self.write_writer(self.reg_ep_stalls, 1 << (ep_num + 1))
else:
raise ValueError("Invalid endpoint for MAXUSB device!")
def stall_ep0(self, direction=0):
return self.stall_endpoint(0, direction)
def get_version(self):
return self.read_register(self.reg_revision)
def connect(self, usb_device, max_packet_size_ep0=64, device_speed=None):
if self.read_register(self.reg_usb_control) & self.usb_control_connect:
self.write_register(self.reg_usb_control, self.usb_control_vbgate)
time.sleep(.1)
self.write_register(self.reg_usb_control, self.usb_control_vbgate |
self.usb_control_connect)
self.connected_device = usb_device
if self.verbose > 0:
print(self.app_name, "connected device", self.connected_device.name)
def disconnect(self):
self.write_register(self.reg_usb_control, self.usb_control_vbgate)
if self.verbose > 0:
print(self.app_name, "disconnected device", self.connected_device.name)
self.connected_device = None
def clear_irq_bit(self, reg, bit):
self.write_register(reg, bit)
def service_irqs(self):
irq = self.read_register(self.reg_endpoint_irq)
in_nak = self.read_register(self.reg_pin_control)
if self.verbose > 3:
print(self.app_name, "read endpoint irq: 0x%02x" % irq)
print(self.app_name, "read pin control: 0x%02x" % in_nak)
if self.verbose > 2:
if irq & ~ (self.is_in0_buffer_avail \
| self.is_in2_buffer_avail | self.is_in3_buffer_avail):
print(self.app_name, "notable irq: 0x%02x" % irq)
if irq & self.is_setup_data_avail:
self.clear_irq_bit(self.reg_endpoint_irq, self.is_setup_data_avail)
b = self.read_bytes(self.reg_setup_data_fifo, 8)
if (irq & self.is_out0_data_avail) and (b[0] & 0x80 == 0x00):
data_bytes_len = b[6] + (b[7] << 8)
b += self.read_bytes(self.reg_ep0_fifo, data_bytes_len)
req = self.connected_device.create_request(b)
self.connected_device.handle_request(req)
if irq & self.is_out1_data_avail:
data = self.read_from_endpoint(1)
if data:
self.connected_device.handle_data_available(1, data)
self.clear_irq_bit(self.reg_endpoint_irq, self.is_out1_data_avail)
if irq & self.is_in2_buffer_avail:
self.connected_device.handle_buffer_available(2)
if irq & self.is_in3_buffer_avail:
self.connected_device.handle_buffer_available(3)
# Check to see if we've NAK'd on either of our IN endpoints,
# and generate the relevant events.
if in_nak & self.ep2_in_nak:
self.connected_device.handle_nak(2)
self.clear_irq_bit(self.reg_pin_control, in_nak | self.ep2_in_nak)
if in_nak & self.ep3_in_nak:
self.connected_device.handle_nak(3)
self.clear_irq_bit(self.reg_pin_control, in_nak | self.ep3_in_nak)
def set_address(self, address, defer=False):
"""
Sets the device address of the Facedancer. Usually only used during
initial configuration.
Args:
address : The address that the Facedancer should assume.
"""
# The MAXUSB chip handles this for us, so we don't need to do anything.
pass
def configured(self, configuration):
"""
Callback that's issued when a USBDevice is configured, e.g. by the
SET_CONFIGURATION request. Allows us to apply the new configuration.
Args:
configuration : The configuration applied by the SET_CONFIG request.
"""
self.validate_configuration(configuration)
# For the MAXUSB case, we don't need to do anything, though it might
# be nice to print a message or store the active configuration for
# use by the USBDevice, etc. etc.
================================================
FILE: facedancer/backends/__init__.py
================================================
__all__ = [
"goodfet",
"MAXUSBApp",
"greatdancer",
"raspdancer",
"greathost",
"libusbhost",
"moondancer",
"hydradancer"
]
================================================
FILE: facedancer/backends/base.py
================================================
from typing import List
from .. import *
class FacedancerBackend:
def __init__(self, device: USBDevice=None, verbose: int=0, quirks: List[str]=[]):
"""
Initializes the backend.
Args:
device : The device that will act as our Facedancer. (Optional)
verbose : The verbosity level of the given application. (Optional)
quirks : List of USB platform quirks. (Optional)
"""
raise NotImplementedError
@classmethod
def appropriate_for_environment(cls, backend_name: str) -> bool:
"""
Determines if the current environment seems appropriate
for using this backend.
Args:
backend_name : Backend name being requested. (Optional)
"""
raise NotImplementedError
def get_version(self):
"""
Returns information about the active Facedancer version.
"""
raise NotImplementedError
def connect(self, usb_device: USBDevice, max_packet_size_ep0: int=64, device_speed: DeviceSpeed=DeviceSpeed.FULL):
"""
Prepares backend to connect to the target host and emulate
a given device.
Args:
usb_device : The USBDevice object that represents the emulated device.
max_packet_size_ep0 : Max packet size for control endpoint.
device_speed : Requested usb speed for the Facedancer board.
"""
raise NotImplementedError
def disconnect(self):
""" Disconnects Facedancer from the target host. """
raise NotImplementedError
def reset(self):
"""
Triggers the Facedancer to handle its side of a bus reset.
"""
raise NotImplementedError
def set_address(self, address: int, defer: bool=False):
"""
Sets the device address of the Facedancer. Usually only used during
initial configuration.
Args:
address : The address the Facedancer should assume.
defer : True iff the set_address request should wait for an active transaction to
finish.
"""
raise NotImplementedError
def configured(self, configuration: USBConfiguration):
"""
Callback that's issued when a USBDevice is configured, e.g. by the
SET_CONFIGURATION request. Allows us to apply the new configuration.
Args:
configuration : The USBConfiguration object applied by the SET_CONFIG request.
"""
raise NotImplementedError
def read_from_endpoint(self, endpoint_number: int) -> bytes:
"""
Reads a block of data from the given endpoint.
Args:
endpoint_number : The number of the OUT endpoint on which data is to be rx'd.
"""
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.
Args:
endpoint_number : The number of the IN endpoint on which data should be sent.
data : The data to be sent.
blocking : If true, this function should wait for the transfer to complete.
"""
raise NotImplementedError
def ack_status_stage(self, direction: USBDirection=USBDirection.OUT, endpoint_number:int =0, blocking: bool=False):
"""
Handles the status stage of a correctly completed control request,
by priming the appropriate endpoint to handle the status phase.
Args:
direction : Determines if we're ACK'ing an IN or OUT vendor request.
(This should match the direction of the DATA stage.)
endpoint_number : The endpoint number on which the control request
occurred.
blocking : True if we should wait for the ACK to be fully issued
before returning.
"""
def stall_endpoint(self, endpoint_number:int, direction: USBDirection=USBDirection.OUT):
"""
Stalls the provided endpoint, as defined in the USB spec.
Args:
endpoint_number : The number of the endpoint to be stalled.
"""
raise NotImplementedError
def clear_halt(self, endpoint_number:int, direction: USBDirection):
""" Clears a halt condition on the provided non-control endpoint.
Args:
endpoint_number : The endpoint number
direction : The endpoint direction; or OUT if not provided.
"""
# FIXME do nothing as only the moondancer backend supports this for now
# raise NotImplementedError
pass
def service_irqs(self):
"""
Core routine of the Facedancer execution/event loop. Continuously monitors the
Facedancer's execution status, and reacts as events occur.
"""
raise NotImplementedError
def validate_configuration(self, configuration: USBConfiguration):
"""
Check if this backend is able to support this configuration.
Raises an exception if it is not.
Args:
configuration : The configuration to validate.
"""
if configuration is None:
return
# Currently, endpoints are only set up in the configured() method, and
# cannot be changed on the fly by SET_INTERFACE requests.
#
# Therefore, no backends are able to support configurations which
# re-use endpoint addresses between alternate interface settings.
used_addresses = set()
for interface in configuration.get_interfaces():
for endpoint in interface.get_endpoints():
address = endpoint.get_identifier()
if address in used_addresses:
raise Exception(
f"This configuration cannot currently be supported, "
f"because it re-uses endpoint address 0x{address:02X} "
f"between multiple interface definitions.")
used_addresses.add(address)
================================================
FILE: facedancer/backends/goodfet.py
================================================
import os
import serial
import sys
import time
from ..core import FacedancerApp
from ..backends.MAXUSBApp import MAXUSBApp
from ..logging import log
class GoodfetMaxUSBApp(MAXUSBApp):
app_name = "MAXUSB"
app_num = 0x40
@classmethod
def appropriate_for_environment(cls, backend_name):
"""
Determines if the current environment seems appropriate
for using the GoodFET::MaxUSB backend.
"""
# Check: Only proceed if the user has specified "goodfet" as
# the backend name, we don't want a false positive because the
# user has another USB serial device connected.
if backend_name is None or backend_name != "goodfet":
return False
# See if there's a connected GoodFET.
try:
gf = GoodFETSerialPort()
gf.close()
return True
except ImportError:
log.info("Skipping GoodFET-based devices, as pyserial isn't installed.")
return False
except:
return False
def __init__(self, device=None, verbose=0, quirks=None):
if device is None:
serial = GoodFETSerialPort()
device = Facedancer(serial, verbose=verbose)
FacedancerApp.__init__(self, device, verbose)
self.connected_device = None
self.enable()
if verbose > 0:
rev = self.read_register(self.reg_revision)
print(self.app_name, "revision", rev)
# set duplex and negative INT level (from GoodFEDMAXUSB.py)
self.write_register(self.reg_pin_control,
self.full_duplex | self.interrupt_level)
def init_commands(self):
self.read_register_cmd = FacedancerCommand(self.app_num, 0x00, b'')
self.write_register_cmd = FacedancerCommand(self.app_num, 0x00, b'')
self.enable_app_cmd = FacedancerCommand(self.app_num, 0x10, b'')
self.ack_cmd = FacedancerCommand(self.app_num, 0x00, b'\x01')
def enable(self):
for i in range(3):
self.device.writecmd(self.enable_app_cmd)
self.device.readcmd()
if self.verbose > 0:
print(self.app_name, "enabled")
def ack_status_stage(self, blocking=False):
if self.verbose > 5:
print(self.app_name, "sending ack!")
self.device.writecmd(self.ack_cmd)
self.device.readcmd()
def read_register(self, reg_num, ack=False):
if self.verbose > 1:
print(self.app_name, "reading register 0x%02x" % reg_num)
self.read_register_cmd.data = bytearray([ reg_num << 3, 0 ])
if ack:
self.read_register_cmd.data[0] |= 1
self.device.writecmd(self.read_register_cmd)
resp = self.device.readcmd()
if self.verbose > 2:
print(self.app_name, "read register 0x%02x has value 0x%02x" %
(reg_num, resp.data[1]))
return resp.data[1]
def write_register(self, reg_num, value, ack=False):
if self.verbose > 2:
print(self.app_name, "writing register 0x%02x with value 0x%02x" %
(reg_num, value))
self.write_register_cmd.data = bytearray([ (reg_num << 3) | 2, value ])
if ack:
self.write_register_cmd.data[0] |= 1
self.device.writecmd(self.write_register_cmd)
self.device.readcmd()
def read_bytes(self, reg, n):
if self.verbose > 2:
print(self.app_name, "reading", n, "bytes from register", reg)
data = bytes([ (reg << 3) ] + ([0] * n))
cmd = FacedancerCommand(self.app_num, 0x00, data)
self.device.writecmd(cmd)
resp = self.device.readcmd()
if self.verbose > 3:
print(self.app_name, "read", len(resp.data) - 1, "bytes from register", reg)
return resp.data[1:]
def write_bytes(self, reg, data):
data = bytes([ (reg << 3) | 3 ]) + data
cmd = FacedancerCommand(self.app_num, 0x00, data)
self.device.writecmd(cmd)
self.device.readcmd() # null response
if self.verbose > 3:
print(self.app_name, "wrote", len(data) - 1, "bytes to register", reg)
class Facedancer:
def __init__(self, serialport, verbose=0):
self.serialport = serialport
self.verbose = verbose
self.reset()
self.monitor_app = GoodFETMonitorApp(self, verbose=self.verbose)
self.monitor_app.announce_connected()
def halt(self):
self.serialport.setRTS(1)
self.serialport.setDTR(1)
def reset(self):
if self.verbose > 1:
print("Facedancer resetting...")
self.halt()
self.serialport.setDTR(0)
c = self.readcmd()
if self.verbose > 0:
print("Facedancer reset")
def read(self, n):
"""Read raw bytes."""
b = self.serialport.read(n)
if self.verbose > 3:
print("Facedancer received", len(b), "bytes;",
self.serialport.inWaiting(), "bytes remaining")
if self.verbose > 2:
print("Facedancer Rx:", MAXUSBApp.bytes_as_hex(b))
return b
def readcmd(self):
"""Read a single command."""
b = self.read(4)
app = b[0]
verb = b[1]
n = b[2] + (b[3] << 8)
if n > 0:
data = self.read(n)
else:
data = b''
if len(data) != n:
raise ValueError('Facedancer expected ' + str(n) \
+ ' bytes but received only ' + str(len(data)))
cmd = FacedancerCommand(app, verb, data)
if self.verbose > 1:
print("Facedancer Rx command:", cmd)
return cmd
def write(self, b):
"""Write raw bytes."""
if self.verbose > 2:
print("Facedancer Tx:", MAXUSBApp.bytes_as_hex(b))
self.serialport.write(b)
def writecmd(self, c):
"""Write a single command."""
self.write(c.as_bytestring())
if self.verbose > 1:
print("Facedancer Tx command:", c)
class FacedancerCommand:
def __init__(self, app=None, verb=None, data=None):
self.app = app
self.verb = verb
self.data = data
def __str__(self):
s = "app 0x%02x, verb 0x%02x, len %d" % (self.app, self.verb,
len(self.data))
if len(self.data) > 0:
s += ", data " + MAXUSBApp.bytes_as_hex(self.data)
return s
def long_string(self):
s = "app: " + str(self.app) + "\n" \
+ "verb: " + str(self.verb) + "\n" \
+ "len: " + str(len(self.data))
if len(self.data) > 0:
try:
s += "\n" + self.data.decode("utf-8")
except UnicodeDecodeError:
s += "\n" + MAXUSBApp.bytes_as_hex(self.data)
return s
def as_bytestring(self):
n = len(self.data)
b = bytearray(n + 4)
b[0] = self.app
b[1] = self.verb
b[2] = n & 0xff
b[3] = n >> 8
b[4:] = self.data
return b
class GoodFETMonitorApp(FacedancerApp):
app_name = "GoodFET monitor"
app_num = 0x00
def read_byte(self, addr):
d = [ addr & 0xff, addr >> 8 ]
cmd = FacedancerCommand(self.app_num, 2, d)
self.device.writecmd(cmd)
resp = self.device.readcmd()
return resp.data[0]
def get_infostring(self):
return bytes([ self.read_byte(0xff0), self.read_byte(0xff1) ])
def get_clocking(self):
return bytes([ self.read_byte(0x57), self.read_byte(0x56) ])
def print_info(self):
infostring = self.get_infostring()
clocking = self.get_clocking()
print("MCU", MAXUSBApp.bytes_as_hex(infostring, delim=""))
print("clocked at", MAXUSBApp.bytes_as_hex(clocking, delim=""))
def list_apps(self):
cmd = FacedancerCommand(self.app_num, 0x82, b'\x01')
self.device.writecmd(cmd)
resp = self.device.readcmd()
print("build date:", resp.data.decode("utf-8"))
print("firmware apps:")
while True:
resp = self.device.readcmd()
if len(resp.data) == 0:
break
print(resp.data.decode("utf-8"))
def echo(self, s):
b = bytes(s, encoding="utf-8")
cmd = FacedancerCommand(self.app_num, 0x81, b)
self.device.writecmd(cmd)
resp = self.device.readcmd()
return resp.data == b
def announce_connected(self):
cmd = FacedancerCommand(self.app_num, 0xb1, b'')
self.device.writecmd(cmd)
resp = self.device.readcmd()
def GoodFETSerialPort(**kwargs):
"Return a Serial port using default values possibly overriden by caller"
port = os.environ.get('GOODFET') or "/dev/ttyUSB0"
args = dict(port=port, baudrate=115200,
parity=serial.PARITY_NONE, timeout=2)
args.update(kwargs)
return serial.Serial(**args)
================================================
FILE: facedancer/backends/greatdancer.py
================================================
# GreatDancerApp.py
import sys
import time
import codecs
import traceback
from ..core import *
from ..types import *
from ..logging import log
from .base import FacedancerBackend
class GreatDancerApp(FacedancerApp, FacedancerBackend):
"""
Backend for using GreatFET devices as Facedancers.
"""
app_name = "GreatDancer"
app_num = 0x00 # This doesn't have any meaning for us.
# Interrupt register (USBSTS) bits masks.
USBSTS_D_UI = (1 << 0)
USBSTS_D_URI = (1 << 6)
USBSTS_D_NAKI = (1 << 16)
# Number of supported USB endpoints.
# TODO: bump this up when we develop support using USB0 (cables flipped)
SUPPORTED_ENDPOINTS = 4
# USB directions
HOST_TO_DEVICE = 0
DEVICE_TO_HOST = 1
# Get status command indexes
GET_USBSTS = 0
GET_ENDPTSETUPSTAT = 1
GET_ENDPTCOMPLETE = 2
GET_ENDPTSTATUS = 3
GET_ENDPTNAK = 4
# Quirk flags
QUIRK_MANUAL_SET_ADDRESS = 0x01
@classmethod
def appropriate_for_environment(cls, backend_name):
"""
Determines if the current environment seems appropriate
for using the GreatDancer backend.
"""
# Check: if we have a backend name other than greatfet,
# the user is trying to use something else. Abort!
if backend_name and backend_name != "greatfet":
return False
# If we're not explicitly trying to use something else,
# see if there's a connected GreatFET.
try:
import greatfet
gf = greatfet.GreatFET()
return gf.supports_api('greatdancer')
except ImportError:
log.info("Skipping GreatFET-based devices, as the greatfet python module isn't installed.")
return False
except:
return False
def __init__(self, device=None, verbose=0, quirks=None):
"""
Sets up a new GreatFET-backed Facedancer (GreatDancer) application.
device: The GreatFET device that will act as our GreatDancer.
verbose: The verbosity level of the given application.
"""
import greatfet
if device is None:
device = greatfet.GreatFET()
self.device = device
self.device.comms.get_exclusive_access()
FacedancerApp.__init__(self, device, verbose)
self.connected_device = None
# Grab the raw API object from the GreatFET object.
# This has the low-level RPCs used for raw USB control.
self.api = self.device.apis.greatdancer
# Initialize a dictionary that will store the last setup
# whether each endpoint is currently stalled.
self.endpoint_stalled = {}
for i in range(self.SUPPORTED_ENDPOINTS):
self.endpoint_stalled[i] = False
# Assume a max packet size of 64 until configured otherwise.
self.max_packet_size_ep0 = 64
# Start off by assuming we're not waiting for an OUT control transfer's
# data stage. # See _handle_setup_complete_on_endpoint for details.
self.pending_control_request = None
# Store a reference to the device's active configuration,
# which we'll use to know which endpoints we'll need to check
# for data transfer readiness.
self.configuration = None
#
# Store our list of quirks to handle.
#
if quirks:
self.quirks = quirks
else:
self.quirks = []
def init_commands(self):
"""
API compatibility function; not necessary for GreatDancer.
"""
pass
def get_version(self):
"""
Returns information about the active GreatDancer version.
"""
# TODO: Return the GreatFET software version, or something indicating
# the GreatFET API number?
raise NotImplementedError()
def ack_status_stage(self, direction=HOST_TO_DEVICE, endpoint_number=0, blocking=False):
"""
Handles the status stage of a correctly completed control request,
by priming the appropriate endpoint to handle the status phase.
Args:
direction : Determines if we're ACK'ing an IN or OUT vendor request.
(This should match the direction of the DATA stage.)
endpoint_number : The endpoint number on which the control request
occurred.
blocking : True if we should wait for the ACK to be fully issued
before returning.
"""
if direction == self.HOST_TO_DEVICE:
# If this was an OUT request, we'll prime the output buffer to
# respond with the ZLP expected during the status stage.
self.send_on_endpoint(endpoint_number, data=[], blocking=blocking)
else:
# If this was an IN request, we'll need to set up a transfer descriptor
# so the status phase can operate correctly. This effectively reads the
# zero length packet from the STATUS phase.
self.read_from_endpoint(endpoint_number)
def _generate_endpoint_config_arguments(self, config):
"""
Generates the data content for an Endpoint Configuration command that will
set up the GreatDancer's endpoints to match the active configuration.
Args:
config : A USBConfiguration object that represents the configuration being
applied to the GreatDancer.
"""
arguments = []
# If our configuration is None, there's nothing to configure; bail out.
if config is None:
return arguments
for interface in config.get_interfaces():
for endpoint in interface.get_endpoints():
log.info(f"Configuring {endpoint}.")
triple = (endpoint.get_address(), endpoint.max_packet_size, endpoint.transfer_type,)
arguments.append(triple)
return arguments
def connect(self, usb_device, max_packet_size_ep0=64, device_speed=DeviceSpeed.FULL):
"""
Prepares the GreatDancer to connect to the target host and emulate
a given device.
Args:
usb_device : The USBDevice object that represents the device to be
emulated.
"""
if device_speed != DeviceSpeed.FULL:
log.warning(f"GreatFET only supports USB Full Speed. Ignoring requested speed: {device_speed.name}")
self.max_packet_size_ep0 = max_packet_size_ep0
quirks = 0
# Compute our quirk flags.
if 'manual_set_address' in self.quirks:
log.info("Handling SET_ADDRESS on the host side!")
quirks |= self.QUIRK_MANUAL_SET_ADDRESS
self.api.connect(self.max_packet_size_ep0, quirks)
self.connected_device = usb_device
log.info("Connecting to host.")
def disconnect(self):
""" Disconnects the GreatDancer from its target host. """
log.info("Disconnecting from host.")
self.device.comms.release_exclusive_access()
self.api.disconnect()
def _wait_until_ready_to_send(self, ep_num):
# If we're already ready, we don't need to do anything. Abort.
if self._is_ready_for_priming(ep_num, self.DEVICE_TO_HOST):
return
# Otherwise, wait until we're ready to send...
while not self._is_ready_for_priming(ep_num, self.DEVICE_TO_HOST):
pass
# ... and since we've blocked the app from cleaning up any transfer
# descriptors automatically by spinning in this thread, we'll clean up
# the relevant transfers here.
self._clean_up_transfers_for_endpoint(ep_num, self.DEVICE_TO_HOST)
def send_on_endpoint(self, ep_num, data, blocking=True):
"""
Sends a collection of USB data on a given endpoint.
Args:
ep_num : The number of the IN endpoint on which data should be sent.
data : The data to be sent.
blocking : If true, this function will wait for the transfer to complete.
"""
log.trace(f"EP{ep_num}/IN: <- {bytes(data)}")
self._wait_until_ready_to_send(ep_num)
self.api.send_on_endpoint(ep_num, bytes(data))
# If we're blocking, wait until the transfer completes.
if blocking:
while not self._transfer_is_complete(ep_num, self.DEVICE_TO_HOST):
pass
self._clean_up_transfers_for_endpoint(ep_num, self.DEVICE_TO_HOST)
def read_from_endpoint(self, ep_num):
"""
Reads a block of data from the given endpoint.
Args:
ep_num : The number of the OUT endpoint on which data is to be rx'd.
"""
# Start a nonblocking read from the given endpoint...
self._prime_out_endpoint(ep_num)
# ... and wait for the transfer to complete.
while not self._transfer_is_complete(ep_num, self.HOST_TO_DEVICE):
pass
# Finally, return the result.
return self._finish_primed_read_on_endpoint(ep_num)
@staticmethod
def _endpoint_address(ep_num, direction):
"""
Returns the endpoint number that corresponds to a given direction
and address.
"""
if direction:
return ep_num | 0x80
else:
return ep_num
def stall_endpoint(self, ep_num, direction=0):
"""
Stalls the provided endpoint, as defined in the USB spec.
Args:
ep_num : The number of the endpoint to be stalled.
"""
in_vs_out = "IN" if direction else "OUT"
log.trace("Stalling EP{} {}".format(ep_num, in_vs_out))
self.endpoint_stalled[ep_num] = True
self.api.stall_endpoint(self._endpoint_address(ep_num, direction))
def stall_ep0(self, direction=0):
"""
Convenience function that stalls the control endpoint zero.
"""
self.stall_endpoint(0, direction)
def set_address(self, address, defer=False):
"""
Sets the device address of the GreatDancer. Usually only used during
initial configuration.
Args:
address : The address that the GreatDancer should assume.
defer : True iff the set_address request should wait for an active
transaction to finish.
"""
self.api.set_address(address, 1 if defer else 0)
@staticmethod
def _decode_usb_register(transfer_result):
"""
Decodes a raw 32-bit register value from a form encoded
for transit as a USB control request.
Args:
transfer_result : The value returned by the vendor request.
Returns: The raw integer value of the given register.
"""
status_hex = codecs.encode(transfer_result[::-1], 'hex')
return int(status_hex, 16)
def _fetch_irq_status(self):
"""
Fetch the USB controller's pending-IRQ bitmask, which indicates
which interrupts need to be serviced.
Returns: A raw integer bitmap.
"""
return self.api.get_status(self.GET_USBSTS)
def _fetch_setup_status(self):
"""
Fetch the USB controller's "pending setup packet" bitmask, which
indicates which endpoints have setup packets to be read.
Returns: A raw integer bitmap.
"""
return self.api.get_status(self.GET_ENDPTSETUPSTAT)
def _handle_setup_events(self):
"""
Handles any outstanding setup events on the USB controller.
"""
# Determine if we have setup packets on any of our endpoints.
status = self._fetch_setup_status()
# If we don't, abort.
if not status:
return
# Otherwise, figure out which endpoints have outstanding setup events,
# and handle them.
for i in range(self.SUPPORTED_ENDPOINTS):
if status & (1 << i):
self._handle_setup_event_on_endpoint(i)
def _handle_setup_event_on_endpoint(self, endpoint_number):
"""
Handles a known outstanding setup event on a given endpoint.
Args:
endpoint_number : The endpoint number for which a setup event should be serviced.
"""
# HACK: to maintain API compatibility with the existing facedancer API,
# we need to know if a stall happens at any point during our handler.
self.endpoint_stalled[endpoint_number] = False
# Read the data from the SETUP stage...
data = bytearray(self.api.read_setup(endpoint_number))
request = self.connected_device.create_request(data)
# If this is an OUT request, handle the data stage,
# and add it to the request.
is_out = request.get_direction() == self.HOST_TO_DEVICE
has_data = (request.length > 0)
# Special case: if this is an OUT request with a data stage, we won't
# handle the request until the data stage has been completed. Instead,
# we'll stash away the data received in the setup stage, prime the
# endpoint for the data stage, and then wait for the data stage to
# complete, triggering a corresponding code path in
# in _handle_transfer_complete_on_endpoint.
if is_out and has_data:
self._prime_out_endpoint(endpoint_number)
self.pending_control_request = request
return
self.connected_device.handle_request(request)
if not is_out and not self.endpoint_stalled[endpoint_number]:
self.ack_status_stage(direction=self.DEVICE_TO_HOST)
def _fetch_transfer_status(self):
"""
Fetch the USB controller's "completed transfer" bitmask, which
indicates which endpoints have recently completed transactions.
Returns : A raw integer bitmap.
"""
return self.api.get_status(self.GET_ENDPTCOMPLETE)
def _transfer_is_complete(self, endpoint_number, direction):
"""
Returns true iff a given endpoint has just completed a transfer.
Can be used to check for completion of a non-blocking transfer.
Args:
endpoint_number : The endpoint number to be queried.
direction : The direction of the transfer. Should be self.HOST_TO_DEVICE or
self.DEVICE_TO_HOST.
"""
status = self._fetch_transfer_status()
# From the LPC43xx manual: out endpoint completions start at bit zero,
# while in endpoint completions start at bit 16.
out_is_ready = (status & (1 << endpoint_number))
in_is_ready = (status & (1 << (endpoint_number + 16)))
if direction == self.HOST_TO_DEVICE:
return out_is_ready
else:
return in_is_ready
def _handle_transfer_events(self):
"""
Handles any outstanding setup events on the USB controller.
"""
# Determine if we have ready packets on any of our endpoints.
status = self._fetch_transfer_status()
# If we don't, abort.
if not status:
return
# Figure out which endpoints have recently completed transfers,
# and clean up any transactions on those endpoints. It's important
# that this be done /before/ the _handle_transfer_complete... section
# below, as those can generate further events which will need the freed
# transfer descriptors.
# [Note that it's safe to clean up the transfer descriptors before reading,
# here-- the GreatFET's USB controller has transparently moved any data
# from OUT transactions into a holding buffer for us. Nice of it!]
for i in range(self.SUPPORTED_ENDPOINTS):
if status & (1 << i):
self._clean_up_transfers_for_endpoint(i, self.HOST_TO_DEVICE)
if status & (1 << (i + 16)):
self._clean_up_transfers_for_endpoint(i, self.DEVICE_TO_HOST)
# Now that we've cleaned up all relevant transfer descriptors, trigger
# any events that should occur due to the completed transaction.
for i in range(self.SUPPORTED_ENDPOINTS):
if status & (1 << i):
self._handle_transfer_complete_on_endpoint(i, self.HOST_TO_DEVICE)
if status & (1 << (i + 16)):
self._handle_transfer_complete_on_endpoint(i, self.DEVICE_TO_HOST)
# Finally, after completing all of the above, we may now have idle
# (unprimed) endpoints. For OUT endpoints, we'll need to re-prime them
# so we're ready for receipt; for IN endpoints, we'll want to give the
# emulated device a chance to provide new data.
self._handle_transfer_readiness()
def _finish_primed_read_on_endpoint(self, endpoint_number):
"""
Completes a non-blocking (primed) read on an OUT endpoint by reading any data
received since the endpoint was primed. See read_from_endpoint for an example
of proper use.
Args:
endpoint_number : The endpoint to read from.
"""
return self.api.finish_nonblocking_read(endpoint_number)
def _clean_up_transfers_for_endpoint(self, endpoint_number, direction):
"""
Cleans up any outstanding transfers on the given endpoint. This must be
called for each completed transaction so the relevant transfer descriptors
can be re-used.
There's no harm in calling this if a transaction isn't complete, but it _must_
be called at least once for each completed transaction.
Args:
endpoint_number : The endpoint number whose transfer descriptors should be cleaned
up.
direction : The endpoint direction for which TD's should be cleaned.
"""
# Ask the device to clean up any transaction descriptors related to the transfer.
self.api.clean_up_transfer(self._endpoint_address(endpoint_number, direction))
def _is_control_endpoint(self, endpoint_number):
"""
Returns true iff the given endpoint number corresponds to a control endpoint.
"""
# FIXME: Support control endpoints other than EP0.
return endpoint_number == 0
def _handle_transfer_complete_on_endpoint(self, endpoint_number, direction):
"""
Handles a known-completed transfer on a given endpoint.
Args:
endpoint_number : The endpoint number for which a setup event should be serviced.
"""
# If a transfer has just completed on an OUT endpoint, we've just received data
# that we need to handle.
if direction == self.HOST_TO_DEVICE:
# Special case: if we've just received data on a control endpoint,
# we're completing a control request.
if self._is_control_endpoint(endpoint_number):
# If we received a setup packet to handle, handle it.
if self.pending_control_request:
# Read the rest of the data from the endpoint, completing
# the control request.
new_data = self._finish_primed_read_on_endpoint(endpoint_number)
# Append our new data to the pending control request.
self.pending_control_request.data.extend(new_data)
all_data_received = len(self.pending_control_request.data) == self.pending_control_request.length
is_short_packet = len(new_data) < self.max_packet_size_ep0
if all_data_received or is_short_packet:
# Handle the completed setup request...
self.connected_device.handle_request(self.pending_control_request)
# And clear our pending setup data.
self.pending_control_request = None
else:
# Otherwise, re-prime the endpoint to grab the next packet.
self._prime_out_endpoint(endpoint_number)
# Typical case: this isn't a control endpoint, so we don't have a
# defined packet format. Read the data and issue the corresponding
# callback.
else:
data = self._finish_primed_read_on_endpoint(endpoint_number)
log.trace(f"EP{endpoint_number}/OUT -> {data}")
self.connected_device.handle_data_available(endpoint_number, data)
def _fetch_transfer_readiness(self):
"""
Queries the GreatFET for a bitmap describing the endpoints that are not
currently primed, and thus ready to be primed again.
"""
return self.api.get_status(self.GET_ENDPTSTATUS)
def _fetch_endpoint_nak_status(self):
"""
Queries the GreatFET for a bitmap describing the endpoints that have issued
a NAK since the last time this was checked.
"""
return self.api.get_status(self.GET_ENDPTNAK)
def _prime_out_endpoint(self, endpoint_number):
"""
Primes an out endpoint, allowing it to receive data the next time the host chooses to send it.
Args:
endpoint_number : The endpoint that should be primed.
"""
self.api.start_nonblocking_read(endpoint_number)
def _handle_transfer_readiness(self):
"""
Check to see if any non-control IN endpoints are ready to
accept data from our device, and handle if they are.
"""
# If we haven't been configured yet, we can't have any
# endpoints other than the control endpoint, and we don't n
if not self.configuration:
return
# Fetch the endpoint status.
status = self._fetch_transfer_readiness()
# Check the status of every endpoint /except/ endpoint zero,
# which is always a control endpoint and set handled by our
# control transfer handler.
for interface in self.configuration.get_interfaces():
for endpoint in interface.get_endpoints():
# Check to see if the endpoint is ready to be primed.
if self._is_ready_for_priming(endpoint.number, endpoint.direction):
# If this is an IN endpoint, we're ready to accept data to be
# presented on the next IN token.
if endpoint.direction == USBDirection.IN:
self.connected_device.handle_buffer_available(endpoint.number)
# If this is an OUT endpoint, we'll need to prime the endpoint to
# accept new data. This provides a place for data to go once the
# host sends an OUT token.
else:
self._prime_out_endpoint(endpoint.number)
def _is_ready_for_priming(self, ep_num, direction):
"""
Returns true iff the endpoint is ready to be primed.
Args:
ep_num : The endpoint number in question.
direction : The endpoint direction in question.
"""
# Fetch the endpoint status.
status = self._fetch_transfer_readiness()
ready_for_in = (not status & (1 << (ep_num + 16)))
ready_for_out = (not status & (1 << (ep_num)))
if direction == self.HOST_TO_DEVICE:
return ready_for_out
else:
return ready_for_in
@classmethod
def _has_issued_nak(cls, ep_nak, ep_num, direction):
"""
Interprets an ENDPTNAK status result to determine
whether a given endpoint has NAK'd.
Args:
ep_nak : The status work read from the ENDPTNAK register
ep_num : The endpoint number in question.
direction : The endpoint direction in question.
"""
in_nak = (ep_nak & (1 << (ep_num + 16)))
out_nak = (ep_nak & (1 << (ep_num)))
if direction == cls.HOST_TO_DEVICE:
return out_nak
else:
return in_nak
def _bus_reset(self):
"""
Triggers the GreatDancer to perform its side of a bus reset.
"""
log.debug("Host issued bus reset.")
if self.connected_device:
self.connected_device.handle_bus_reset()
else:
self.api.bus_reset()
def reset(self):
"""
Triggers the GreatFET to handle its side of a bus reset.
"""
self.api.bus_reset()
def _handle_nak_events(self):
"""
Handles an event in which the GreatDancer has NAK'd an IN token.
"""
# If we haven't been configured yet, we can't have any
# endpoints other than the control endpoint, and we don't need to
# handle any NAKs.
if not self.configuration:
return
# Fetch the endpoint status.
status = self._fetch_endpoint_nak_status()
# Iterate over each usable endpoint.
for interface in self.configuration.get_interfaces():
for endpoint in interface.get_endpoints():
# Skip OUT endpoints
if endpoint.direction == self.HOST_TO_DEVICE:
continue
# If the endpoint has NAK'd, issued the relevant callback.
if self._has_issued_nak(status, endpoint.number, endpoint.direction):
self.connected_device.handle_nak(endpoint.number)
def _configure_endpoints(self, configuration):
"""
Configures the GreatDancer's endpoints to match the provided configuration.
Args:
configuration : The USBConfiguration object that describes the endpoints provided.
"""
endpoint_triplets = self._generate_endpoint_config_arguments(configuration)
# If we need to issue a configuration command, issue one.
# (If there are no endpoints other than control, this command will be
# empty, and we can skip this.)
if endpoint_triplets:
self.api.set_up_endpoints(*endpoint_triplets)
def configured(self, configuration):
"""
Callback that's issued when a USBDevice is configured, e.g. by the
SET_CONFIGURATION request. Allows us to apply the new configuration.
Args:
configuration: The configuration applied by the SET_CONFIG request.
"""
self.validate_configuration(configuration)
self._configure_endpoints(configuration)
self.configuration = configuration
# If we've just set up endpoints, check to see if any of them
# need to be primed, or have NAKs waiting.
self._handle_transfer_readiness()
self._handle_nak_events()
def service_irqs(self):
"""
Core routine of the Facedancer execution/event loop. Continuously monitors the
GreatDancer's execution status, and reacts as events occur.
"""
status = self._fetch_irq_status()
# Other bits that may be of interest:
# D_SRI = start of frame received
# D_PCI = port change detect (switched between low, full, high speed state)
# D_SLI = device controller suspend
# D_UEI = USB error; completion of transaction caused error, see usb1_isr in firmware
# D_NAKI = both the tx/rx NAK bit and corresponding endpoint NAK enable are set
if status & self.USBSTS_D_UI:
self._handle_setup_events()
self._handle_transfer_events()
if status & self.USBSTS_D_URI:
self._bus_reset()
if status & self.USBSTS_D_NAKI:
self._handle_nak_events()
================================================
FILE: facedancer/backends/greathost.py
================================================
#
# This file is part of Facedancer.
#
""" Host support for GreatFET-base devices. """
import sys
import time
import codecs
import struct
from ..core import *
from ..endpoint import USBEndpoint
from ..types import *
from ..logging import log
class GreatDancerHostApp(FacedancerUSBHost):
"""
Class that represents a GreatFET-based USB host.
"""
app_name = "GreatDancer Host"
PORT_STATUS_REG = 0
READ_STATUS_REG = 1
WRITE_STATUS_REG = 2
PORT_STATUS_REGISTER_CONNECTED_MASK = (1 << 0)
PORT_STATUS_REGISTER_ENABLED_MASK = (1 << 2)
PORT_STATUS_REGISTER_POWERED_MASK = (1 << 12)
PORT_STATUS_REGISTER_SPEED_SHIFT = 26
PORT_STATUS_REGISTER_SPEED_MASK = 0b11
PORT_STATUS_REGISTER_LINE_STATE_SHIFT = 10
PORT_STATUS_REGISTER_LINE_STATE_MASK = 0b11
LINE_STATE_NAMES = {
0: "SE0",
1: "J",
2: "K",
3: "No device / SE1"
}
LINE_STATE_SE0 = 0
LINE_STATE_J = 1
LINE_STATE_K = 2
LINE_STATE_SE1 = 3
DEVICE_SPEED_LOW = 0
DEVICE_SPEED_FULL = 1
DEVICE_SPEED_HIGH = 2
DEVICE_SPEED_NONE = 3
STATUS_REG_SPEED_VALUES = {
0: DEVICE_SPEED_FULL,
1: DEVICE_SPEED_LOW,
2: DEVICE_SPEED_HIGH,
3: DEVICE_SPEED_NONE
}
DEVICE_SPEED_NAMES = {
DEVICE_SPEED_FULL: "Full speed",
DEVICE_SPEED_LOW: "Low speed",
DEVICE_SPEED_HIGH: "High speed",
DEVICE_SPEED_NONE: "Disconnected"
}
SPEED_REQUESTS = {
0: 1,
1: 0,
2: 2,
3: 3
}
# Endpoint directions
DIRECTION_IN = 0x00
DIRECTION_OUT = 0x80
# Endpoint types
ENDPOINT_TYPE_CONTROL = 0
# Packet IDs
PID_SETUP = 2
PID_OUT = 0
PID_IN = 1
@classmethod
def appropriate_for_environment(cls, backend_name):
"""
Determines if the current environment seems appropriate
for using the GreatDancer backend.
"""
# Check: if we have a backend name other than greatfet,
# the user is trying to use something else. Abort!
if backend_name and backend_name != "greatfet":
return False
# If we're not explicitly trying to use something else,
# see if there's a connected GreatFET.
try:
import greatfet
greatfet.GreatFET()
return True
except ImportError:
log.info("Skipping GreatFET-based devices, as the greatfet python module isn't installed.")
return False
except:
return False
def __init__(self, verbose=0, quirks=[], autoconnect=True, device=None):
"""
Sets up a GreatFET-based host connection.
"""
import greatfet
if device is None:
device = greatfet.GreatFET()
# Store our input args.
# TODO: pull into base class
self.device = device
self.verbose = verbose
# Grab a reference to our protocol definitions.
self.vendor_requests = greatfet.protocol.vendor_requests
if autoconnect:
self.connect()
def connect(self, device_speed=None):
"""
Sets up our host to talk to the device, including turning on VBUS.
"""
self.device.comms._vendor_request_out(self.vendor_requests.USBHOST_CONNECT)
def bus_reset(self, delay=0.500):
"""
Issues a "bus reset", requesting that the downstream device reset itself.
Args:
delay : The amount of time, in seconds, to wait before or after the
reset request. To be compliant, this should be omitted, or set
to 0.1s.
"""
# Note: we need to wait a reset delay before and after the bus reset.
# This allows the host to initialize _and_ then allows the device to settle.
time.sleep(delay)
self.device.comms._vendor_request_out(self.vendor_requests.USBHOST_BUS_RESET)
time.sleep(delay)
@staticmethod
def _decode_usb_register(transfer_result):
"""
Decodes a raw 32-bit register value from a form encoded
for transit as a USB control request.
Args:
transfer_result : The value returned by the vendor request.
returns : The raw integer value of the given register.
"""
status_hex = codecs.encode(transfer_result[::-1], 'hex')
return int(status_hex, 16)
def _fetch_status_register(self, register_number):
"""
Fetches a status register from the GreatDacner, and returns it
as an integer.
"""
raw_status = self.device.comms._vendor_request_in(self.vendor_requests.USBHOST_GET_STATUS, index=register_number, length=4)
return self._decode_usb_register(raw_status)
def _port_status(self):
""" Returns the raw state of the port status register. """
return self._fetch_status_register(self.PORT_STATUS_REG)
def _get_read_status(self):
""" Returns the raw state of the read status word. """
return self._fetch_status_register(self.READ_STATUS_REG)
def _get_write_status(self):
""" Returns the raw state of the read status word. """
return self._fetch_status_register(self.WRITE_STATUS_REG)
def device_is_connected(self):
""" Returns true iff a given device is connected. """
status = self._port_status()
return bool(status & self.PORT_STATUS_REGISTER_CONNECTED_MASK)
def port_is_enabled(self):
""" Returns true iff the Facedancer host port's enabled. """
status = self._port_status()
return bool(status & self.PORT_STATUS_REGISTER_ENABLED_MASK)
def port_is_powered(self):
""" Returns true iff the Facedancer host port's enabled. """
status = self._port_status()
return bool(status & self.PORT_STATUS_REGISTER_POWERED_MASK)
def current_device_speed(self, as_string=False):
""" Returns the speed of the connected device
Args:
as_string : If true, returns the speed as a string for printing; otherwise
returns a DEVICE_SPEED_* constant.
"""
port_speed_raw = \
(self._port_status() >> self.PORT_STATUS_REGISTER_SPEED_SHIFT) & \
self.PORT_STATUS_REGISTER_SPEED_MASK
# Translate from a GreatFET format device speed to a Facedancer one.
port_speed = self.STATUS_REG_SPEED_VALUES[port_speed_raw]
if as_string:
port_speed = self.DEVICE_SPEED_NAMES[port_speed]
return port_speed
def current_line_state(self, as_string=False):
""" Returns the current state of the USB differential pair
Args:
as_string : If true, returns the speed as a string for printing; otherwise
returns a LINE_STATE_* constant.
"""
line_state = \
(self._port_status() >> self.PORT_STATUS_REGISTER_LINE_STATE_SHIFT) & \
self.PORT_STATUS_REGISTER_LINE_STATE_MASK
if as_string:
line_state = self.LINE_STATE_NAMES[line_state]
return line_state
def set_up_endpoint(self, endpoint_address_or_object, endpoint_type=None, max_packet_size=None,
device_address=None, endpoint_speed=None, handle_data_toggle=None,
is_control_endpoint=None):
"""
Sets up an endpoint for use. Can be used to initialize an endpoint or to update
its parameters. Two forms exist:
Args:
endpoint_object : a USBEndpoint object with the parameters to be populated
or
Args:
endpoint_address : the address of the endpoint to be setup; including the direction bit
endpoint_type : one of the ENDPOINT_TYPE constants that specifies the transfer mode on
the endpoint_address
max_packet_size : the maximum packet size to be communicated on the given endpoint
device_address : the address of the device to be communicated with; if not provided, the last
address will be used.
endpoint_speed : the speed of the packets to be communicated on the endpoint; should be a
DEVICE_SPEED_* constant; if not provided, the last device's speed will be used.
handle_data_toggle : true iff the hardware should automatically handle selection of data packet PIDs
is_control_endpoint : true iff the given packet is a for a control endpoint
"""
if isinstance(endpoint_address_or_object, USBEndpoint):
endpoint = endpoint_address_or_object
# Figure out the endpoint address from its direction and number.
endpoint_address = endpoint.number
if endpoint.direction == USBDirection.IN:
endpoint_address |= self.DIRECTION_IN
self.set_up_endpoint(endpoint_address, endpoint.transfer_type, endpoint.max_packet_size)
return
endpoint_address = endpoint_address_or_object
endpoint_number = endpoint_address & 0x7f
if endpoint_number > 15:
raise ValueError("cannot have an endpoint with a number > 15!")
# Figure out defaults for any arguments not provided.
if device_address is None:
device_address = self.last_device_address
if endpoint_speed is None:
endpoint_speed = self.last_device_speed
if is_control_endpoint is None:
is_control_endpoint = (endpoint_number == 0)
if handle_data_toggle is None:
handle_data_toggle = True if not is_control_endpoint else False
# Figure out which endpoint schedule to use.
# FIXME: support more than the asynchronous schedule
endpoint_schedule = 0
# TODO: do we translate speed requests, here?
# Issue the configuration packet.
packet = struct.pack("<BBBBBHB", endpoint_schedule, device_address, endpoint_address,
endpoint_speed, is_control_endpoint, max_packet_size, handle_data_toggle)
self.device.comms._vendor_request_out(self.vendor_requests.USBHOST_SET_UP_ENDPOINT, data=packet)
def initialize_control_endpoint(self, device_address=None, device_speed=None, max_packet_size=None):
"""
Set up the device's control endpoint, so we can use it for e.g. enumeration.
"""
# If not overridden, apply the specification default maximum packet size.
# TODO: support high speed devices, here?
if max_packet_size is None:
max_packet_size = 8 if device_speed == self.DEVICE_SPEED_LOW else 64
# Set up both directions on the control endpoint.
self.set_up_endpoint(0 | self.DIRECTION_OUT, self.ENDPOINT_TYPE_CONTROL, max_packet_size)
self.set_up_endpoint(0 | self.DIRECTION_IN, self.ENDPOINT_TYPE_CONTROL, max_packet_size)
def send_on_endpoint(self, endpoint_number, data, is_setup=False,
blocking=True, data_packet_pid=0):
"""
Sends a block of data on the provided endpoints.
Args:
endpoint_number : The endpoint number on which to send.
data : The data to be transmitted.
is_setup : True iff this transfer should begin with a SETUP token.
blocking : True iff this transaction should wait for the transaction to
complete.
data_packet_pid : The data packet PID to use (1 or 0). Ignored if the endpoint is
set to automatically alternate data PIDs.
Raises an IOError on a communications error or stall.
"""
# Determine the PID token with which to start the request...
pid_token = self.PID_SETUP if is_setup else self.PID_OUT
# Issue the actual send itself.
# TODO: validate length
self.device.comms._vendor_request_out(self.vendor_requests.USBHOST_SEND_ON_ENDPOINT,
index=endpoint_number, value=(data_packet_pid << 8) | pid_token,
data=data)
# ... and if we're blocking, also finish it.
if blocking:
complete = False
stalled = False
# Wait until we get a complete flag in the status register.
# XXX: This isn't entirely correct-- it'll clear too much status.
while not complete:
status = self._get_write_status()
stalled = (status >> endpoint_number) & 0x1
complete = (status >> (endpoint_number + 16)) & 0x1
if stalled:
raise IOError("Stalled!")
def read_from_endpoint(self, endpoint_number, expected_read_size=64, data_packet_pid=0):
"""
Sends a block of data on the provided endpoints.
Args:
endpoint_number : The endpoint number on which to send.
expected_read_size : The expected amount of data to be read.
data_packet_pid : The data packet PID to use (1 or 0).
Ignored if the endpoint is set to automatically alternate data PIDs.
Raises an IOError on a communications error or stall.
"""
# Start the request...
self.device.comms._vendor_request_out(self.vendor_requests.USBHOST_START_NONBLOCKING_READ,
index=(data_packet_pid << 8) | endpoint_number, value=expected_read_size)
# ... and if we're blocking, also finish it.
complete = False
stalled = False
# Wait until we get a complete flag in the status register.
# XXX: This isn't entirely correct-- it'll clear too much status.
while not complete:
status = self._get_read_status()
stalled = (status >> endpoint_number) & 0x1
complete = (status >> (endpoint_number + 16)) & 0x1
if stalled:
raise IOError("Stalled!")
# Figure out how muhc to read.
raw_length = self.device.comms._vendor_request_in(self.vendor_requests.USBHOST_GET_NONBLOCKING_LENGTH,
index=endpoint_number, length=4)
length = self._decode_usb_register(raw_length)
if self.verbose > 4:
print("Supposedly, we've got {} bytes of data to read".format(length))
# If there's no data available, we don't need to waste time reading anything.
if length == 0:
return b''
# Otherwise, read the data from the endpoint and return it.
data = self.device.comms._vendor_request_in(self.vendor_requests.USBHOST_FINISH_NONBLOCKING_READ,
index=endpoint_number, length=length)
return data.tobytes()
================================================
FILE: facedancer/backends/hydradancer.py
================================================
"""
Backend for the Hydradancer boards.
Supports 5 endpoints, with addresses between 0 and 7. Supports low, full and high-speed.
"""
import sys
import logging
import time
from array import array
from time import time_ns
from dataclasses import dataclass
from typing import List, Dict, Any
import usb
from usb.util import CTRL_TYPE_VENDOR, CTRL_RECIPIENT_DEVICE, CTRL_IN, CTRL_OUT
from ..core import *
from ..device import USBDevice, USBConfiguration, USBDirection, USBEndpoint
from ..types import DeviceSpeed
from ..logging import log
from .base import FacedancerBackend
@dataclass
class HydradancerEvent:
# Events
EVENT_BUS_RESET = 0x0
EVENT_IN_BUFFER_AVAILABLE = 0x1
EVENT_OUT_BUFFER_AVAILABLE = 0x2
EVENT_NAK = 0x3
event_type : int = -1
value : int = -1
@staticmethod
def from_bytes(data : bytes):
return HydradancerEvent(event_type = data[0], value = data[1])
def __repr__(self):
return f"event_type {self.event_type} value {self.value}"
class HydradancerHostApp(FacedancerApp, FacedancerBackend):
"""
Backend for the HydraUSB3 boards.
"""
app_name = "Hydradancer Host"
MANUFACTURER_STRING = "Quarkslab https://www.quarkslab.com/ & HydraBus https://hydrabus.com/"
# USB directions
HOST_TO_DEVICE = 0
DEVICE_TO_HOST = 1
USB2_MAX_EP_IN = 16
current_setup_req = None
def __init__(self, device: USBDevice=None, verbose: int=0, quirks: List[str]=[]):
"""
Initializes the backend.
Args:
device : The device that will act as our Facedancer. (Optional)
verbose : The verbosity level of the given application. (Optional)
quirks : List of USB platform quirks. (Optional)
"""
super().__init__(self)
self.configuration = None
self.pending_control_out_request = None
self.connected_device = None
self.max_ep0_packet_size = None
self.ep_transfer_queue : List[List[Any]] = [[]] * self.USB2_MAX_EP_IN
self.ep_in : Dict[int, USBEndpoint] = {}
self.ep_out : Dict[int, USBEndpoint] = {}
self.api = HydradancerBoard()
self.verbose = verbose
self.api.wait_board_ready()
@classmethod
def appropriate_for_environment(cls, backend_name: str) -> bool:
"""
Determines if the current environment seems appropriate
for using this backend.
Args:
backend_name : Backend name being requested. (Optional)
"""
logging.info("this is hydradancer hi")
# Open a connection to the target device...
device = usb.core.find(idVendor=0x16c0, idProduct=0x27d8)
if device is not None and device.manufacturer == cls.MANUFACTURER_STRING and backend_name == "hydradancer":
return True
return False
def get_version(self):
"""
Returns information about the active Facedancer version.
"""
raise NotImplementedError
def connect(self, usb_device: USBDevice, max_packet_size_ep0: int=64, device_speed: DeviceSpeed=DeviceSpeed.FULL):
"""
Prepares backend to connect to the target host and emulate
a given device.
Args:
usb_device : The USBDevice object that represents the emulated device.
max_packet_size_ep0 : Max packet size for control endpoint.
device_speed : Requested usb speed for the Facedancer board.
"""
self.api.set_endpoint_mapping(0)
if device_speed not in [DeviceSpeed.LOW, DeviceSpeed.FULL, DeviceSpeed.HIGH]:
log.warning(f"Hydradancer only supports USB Low, Full and High Speed. Ignoring requested speed: {device_speed.name}")
self.api.set_usb2_speed(device_speed)
logging.info("connect ...")
self.api.connect()
self.connected_device = usb_device
self.max_ep0_packet_size = max_packet_size_ep0
def disconnect(self):
""" Disconnects Facedancer from the target host. """
logging.info("disconnect")
self.configuration = None
self.pending_control_out_request = None
self.connected_device = None
self.max_ep0_packet_size = 0
self.ep_transfer_queue = [[]] * self.USB2_MAX_EP_IN
self.api.disconnect()
def reset(self):
"""
Triggers the Facedancer to handle its side of a bus reset.
"""
logging.info("bus reset")
def set_address(self, address: int, defer: bool=False):
"""
Sets the device address of the Facedancer. Usually only used during
initial configuration.
Args:
address : The address the Facedancer should assume.
defer : True iff the set_address request should wait for an active transaction to
finish.
"""
logging.info("set address")
self.api.set_address(address, defer)
def configured(self, configuration: USBConfiguration):
"""
Callback that's issued when a USBDevice is configured, e.g. by the
SET_CONFIGURATION request. Allows us to apply the new configuration.
Args:
configuration : The USBConfiguration object applied by the SET_CONFIG request.
"""
self.validate_configuration(configuration)
if configuration is None:
self.configuration = None
self.api.configured = False
logging.debug("unconfigured")
return
self.api.reinit(keep_ep0=True)
endpoint_numbers = []
for interface in configuration.get_interfaces():
for endpoint in interface.get_endpoints():
ep_num = endpoint.number
is_ep_in = endpoint.direction == 1
if ep_num not in endpoint_numbers:
endpoint_numbers.append(ep_num)
if is_ep_in:
self.ep_in[ep_num] = endpoint
else:
self.ep_out[ep_num] = endpoint
self.api.configure(endpoint_numbers)
self.configuration = configuration
logging.debug("configured")
def read_from_endpoint(self, endpoint_number: int) -> bytes:
"""
Reads a block of data from the given endpoint.
Args:
endpoint_number : The number of the OUT endpoint on which data is to be rx'd.
"""
return self.api.read(endpoint_number, blocking=True)
def send_on_endpoint(self, endpoint_number: int, data: bytes, blocking: bool=True):
"""
Sends a collection of USB data on a given endpoint.
Args:
endpoint_number : The number of the IN endpoint on which data should be sent.
data : The data to be sent.
blocking : If true, this function should wait for the transfer to complete.
"""
if endpoint_number != 0 and not blocking and not self.api.in_buffer_empty(endpoint_number):
logging.debug(f"Storing {len(data)} on ep {endpoint_number} for later")
self.ep_transfer_queue[endpoint_number].append(data)
return
backup_len = len(data)
max_packet_size = self.max_ep0_packet_size if endpoint_number == 0 else self.ep_in[endpoint_number].max_packet_size
if not data:
self.api.send(endpoint_number, data)
while data:
packet = data[0:max_packet_size]
data = data[len(packet):]
logging.debug(f"Sending {len(packet)} on ep {endpoint_number}")
self.api.send(endpoint_number, packet)
# Many things to take into account here ...
# first, if the len we are sending is a multiple of the max_packet_size, the host will request a ZLP (otherwise, it can't know when the transfer ends)
# however, if the endpoint is endpoint 0, the host knows the size of the transfer in advance so it might not request the ZLP
# this could be solved by using NAKs for EP0 as well (answering by a ZLP if a NAK is received but we already sent everything)
# however, this could add too much latency and make enumeration fail
if endpoint_number == 0 and (backup_len % max_packet_size) == 0 and backup_len > 0 and backup_len != self.current_setup_req.length:
logging.debug(f"Sending ZLP")
self.api.send(endpoint_number, b"") # Sending ZLP
def ack_status_stage(self, direction: USBDirection=USBDirection.OUT, endpoint_number:int =0, blocking: bool=False):
"""
Handles the status stage of a correctly completed control request,
by priming the appropriate endpoint to handle the status phase.
Args:
direction : Determines if we're ACK'ing an IN or OUT vendor request.
(This should match the direction of the DATA stage.)
endpoint_number : The endpoint number on which the control request
occurred.
blocking : True if we should wait for the ACK to be fully issued
before returning.
"""
if direction == USBDirection.OUT:
# If this was an OUT request, we'll prime the output buffer to
# respond with the ZLP expected during the status stage.
self.send_on_endpoint(endpoint_number, data=b"", blocking=blocking)
else:
# If this was an IN request, we'll need to set up a transfer descriptor
# so the status phase can operate correctly. This effectively reads the
# zero length packet from the STATUS phase.
self.read_from_endpoint(endpoint_number)
def stall_endpoint(self, endpoint_number:int, direction: USBDirection=USBDirection.OUT):
"""
Stalls the provided endpoint, as defined in the USB spec.
Args:
endpoint_number : The number of the endpoint to be stalled.
"""
in_vs_out = "IN" if direction else "OUT"
logging.info(f"Stalling EP {endpoint_number} {in_vs_out}")
self.api.stall_endpoint(endpoint_number, direction)
def clear_halt(self, endpoint_number:int, direction: USBDirection):
""" Clears a halt condition on the provided non-control endpoint.
Args:
endpoint_number : The endpoint number
direction : The endpoint direction; or OUT if not provided.
"""
logging.debug(f"Clearing halt for endpoint {endpoint_number}")
self.api.clear_halt(endpoint_number, direction)
def service_irqs(self):
"""
Core routine of the Facedancer execution/event loop. Continuously monitors the
Facedancer's execution status, and reacts as events occur.
"""
events = self.api.fetch_events()
if events is not None:
for event in events:
if event is None:
continue
if event.event_type == HydradancerEvent.EVENT_BUS_RESET:
self.handle_bus_reset()
if event.event_type == HydradancerEvent.EVENT_IN_BUFFER_AVAILABLE and event.value != 0 and (event.value in self.ep_in.keys()):
self.connected_device.handle_buffer_empty(self.ep_in[event.value])
self.handle_control_request()
self.handle_data_endpoints()
def handle_bus_reset(self):
"""
Triggers Hydradancer to perform its side of a bus reset.
"""
if self.connected_device:
self.connected_device.handle_bus_reset()
else:
self.reset()
def handle_data_endpoints(self):
"""
Handle IN or OUT requests on non-control endpoints.
"""
# process ep OUT firsts, transfer is dictated by the host, if there is data available on an ep OUT,
# it should be processed before setting new IN data
for ep_num in self.ep_out:
if self.api.out_buffer_available(ep_num):
data = self.api.read(ep_num)
if data is not None:
self.connected_device.handle_data_available(
ep_num, data.tobytes())
for ep_num, ep in self.ep_in.items():
if self.api.in_buffer_empty(ep_num) and self.api.nak_on_endpoint(ep_num):
if len(self.ep_transfer_queue[ep_num]) != 0:
max_packet_size = ep.max_packet_size
packet = self.ep_transfer_queue[ep_num][0][0:max_packet_size]
self.ep_transfer_queue[ep_num][0] = self.ep_transfer_queue[ep_num][0][len(packet):]
self.api.send(ep_num, packet)
if len(self.ep_transfer_queue[ep_num][0]) == 0:
self.ep_transfer_queue[ep_num].pop(0)
else:
self.connected_device.handle_nak(ep_num)
def handle_control_request(self):
if not self.api.control_buffer_available():
return
data = self.api.read(0)
if data is None:
return
logging.debug(
f"CONTROL EP/OUT: -> size {len(data)} {bytes(data)}")
# inspired from moondancer and greatdancer backends
if self.pending_control_out_request is not None:
self.pending_control_out_request.data.extend(data)
all_data_received = len(
self.pending_control_out_request.data) == self.pending_control_out_request.length
is_short_packet = len(data) < self.max_ep0_packet_size
if all_data_received or is_short_packet:
self.connected_device.handle_request(
self.pending_control_out_request)
self.pending_control_out_request = None
elif len(data) > 0:
request = self.connected_device.create_request(data)
is_out = request.get_direction() == self.HOST_TO_DEVICE
has_data = (request.length > 0)
self.current_setup_req = request
if is_out and has_data:
logging.debug("queuing Control OUT req, waiting for more data")
self.pending_control_out_request = request
return
self.connected_device.handle_request(request)
# handle status stage of IN transfer
elif len(data) == 0:
logging.debug("Received ACK for IN Ctrl req")
class HydradancerBoardFatalError(Exception):
pass
class HydradancerBoard():
"""
Handles the communication with the Hydradancer control board and manages the events it sends.
"""
MAX_PACKET_SIZE = 1024
# USB Vendor Requests codes
ENABLE_USB_CONNECTION = 50
SET_ADDRESS = 51
GET_EVENT = 52
SET_ENDPOINT_MAPPING = 53
DISABLE_USB = 54
SET_SPEED = 55
SET_EP_RESPONSE = 56
CHECK_HYDRADANCER_READY = 57
DO_BUS_RESET = 58
CONFIGURED = 59
CLEAR_HALT = 60
# Facedancer USB2 speed to Hydradancer USB2 speed
facedancer_to_hydradancer_speed = {
DeviceSpeed.LOW : 0,
DeviceSpeed.FULL : 1,
DeviceSpeed.HIGH : 2
}
# Max number of events that can be sent by the board
# This must not be less than what is defined in the firmware
EVENT_QUEUE_SIZE = 100
# Endpoint states on the emulation board
ENDP_STATE_ACK = 0x00
ENDP_STATE_NAK = 0x02
ENDP_STATE_STALL = 0x03
# USB endpoints direction
HOST_TO_DEVICE = 0
DEVICE_TO_HOST = 1
EP_POLL_NUMBER = 1
SUPPORTED_EP_NUM = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
INCOMPATIBLE_EP = [[], [9], [10], [11], [8, 12], [13], [14], [15], [4], [1], [2], [3], [4], [5], [6], [7]]
timeout_ms_poll = 1
def reinit(self, keep_ep0:bool = False):
if keep_ep0 and 0 in self.endpoints_mapping:
old_control_ep = self.endpoints_mapping[0]
self.endpoints_mapping = {0: self.endpoints_mapping[0]}
self.reverse_endpoints_mapping = {old_control_ep:0}
else:
self.endpoints_mapping = {} # emulated endpoint -> control board endpoint
self.reverse_endpoints_mapping = {} # control_board_endpoint -> emulated_endpoint
self.events = array('B', [0] * 2 * self.EVENT_QUEUE_SIZE)
# True when SET_CONFIGURATION has been received and the Hydradancer boards are configured
self.configured = False
# 0x00ff (IN status mask, 1 = emulated ep ready for priming), 0xff00 (OUT mask, data received on emulated ep)
self._hydradancer_status_bytes = array('B', [0] * 4)
self.hydradancer_status = {}
self.hydradancer_status["ep_in_status"] = (1 << 0) & 0xff
self.hydradancer_status["ep_out_status"] = 0x00
self.hydradancer_status["ep_in_nak"] = 0x00
def __init__(self):
"""
Get handles on the USB control board, and wait for Hydradancer to be ready
"""
self.configured = False
self.endpoints_mapping : Dict[int,int] = {}
self.reinit()
# Open a connection to the target device...
self.device = usb.core.find(idVendor=0x16c0, idProduct=0x27d8)
if self.device is None:
raise HydradancerBoardFatalError("Hydradancer board not found")
if self.device.speed != usb.util.SPEED_SUPER:
raise HydradancerBoardFatalError(
"Hydradancer not detected as USB3 Superspeed")
cfg = self.device.get_active_configuration()
intf = cfg[(0, 0)]
# Detach the device from any kernel driver
for intf in cfg:
if self.device.is_kernel_driver_active(intf.bInterfaceNumber):
try:
self.device.detach_kernel_driver(intf.bInterfaceNumber)
except usb.core.USBError as e:
sys.exit("Could not detach kernel driver from interface({0}): {1}".format(
intf.bInterfaceNumber, str(e)))
# store the different endpoints handles we need
self.ep_in = list(usb.util.find_descriptor(
intf,
find_all=True,
custom_match=lambda e:
usb.util.endpoint_direction(e.bEndpointAddress) ==
usb.util.ENDPOINT_IN and usb.util.endpoint_address(e.bEndpointAddress) !=
self.EP_POLL_NUMBER))
self.ep_in = {usb.util.endpoint_address(
e.bEndpointAddress): e for e in self.ep_in}
self.ep_out = list(usb.util.find_descriptor(
intf,
find_all=True,
custom_match=lambda e:
usb.util.endpoint_direction(e.bEndpointAddress) ==
usb.util.ENDPOINT_OUT and usb.util.endpoint_address(e.bEndpointAddress) !=
self.EP_POLL_NUMBER))
self.ep_out = {usb.util.endpoint_address(
ep.bEndpointAddress): ep for ep in self.ep_out}
# the endpoint on which status information is received
self.ep_poll = usb.util.find_descriptor(
intf,
custom_match=lambda e:
usb.util.endpoint_direction(e.bEndpointAddress) ==
usb.util.ENDPOINT_IN and usb.util.endpoint_address(e.bEndpointAddress) ==
self.EP_POLL_NUMBER)
if len(self.ep_in.keys()) == 0 and len(self.ep_out.keys()) == 0:
logging.info("Dumping device configuration \r\n" + str(cfg))
raise HydradancerBoardFatalError(
"Could not fetch Hydradancer IN and OUT endpoints list")
if len(self.ep_in.keys()) == 0:
logging.info("Dumping device configuration \r\n" + str(cfg))
raise HydradancerBoardFatalError(
"Could not fetch Hydradancer IN endpoints list")
if len(self.ep_out.keys()) == 0:
logging.info("Dumping device configuration \r\n" + str(cfg))
raise HydradancerBoardFatalError(
"Could not fetch Hydradancer OUT endpoints list")
if self.ep_out.keys() != self.ep_in.keys():
logging.info("Dumping device configuration \r\n" + str(cfg))
raise HydradancerBoardFatalError(
f"Hydradancer IN/OUT endpoints pair incomplete \r\nep_in {self.ep_in} \r\nep_out {self.ep_out}")
if self.ep_poll is None:
logging.info("Dumping device configuration \r\n" + str(cfg))
raise HydradancerBoardFatalError(
f"Could not get handle on Hydradancer events endpoint (EP {self.EP_POLL_NUMBER})")
self.endpoints_pool = set(self.ep_in.keys())
# wait until the board is ready, for instance if a disconnect was previously issued
self.wait_board_ready()
def connect(self):
"""
Enable the USB2 connection on the emulation board
"""
try:
self.device.ctrl_transfer(
CTRL_TYPE_VENDOR | CTRL_RECIPIENT_DEVICE | CTRL_OUT, self.ENABLE_USB_CONNECTION)
except (usb.core.USBTimeoutError, usb.core.USBError) as exception:
logging.error(exception)
raise HydradancerBoardFatalError("Error, unable to connect") from exception
def disconnect(self):
"""
Disable the USB2 connection on the emulation board,
and reset internal states on both control and emulation boards.
"""
try:
self.device.ctrl_transfer(
CTRL_TYPE_VENDOR | CTRL_RECIPIENT_DEVICE | CTRL_OUT, self.DISABLE_USB)
usb.util.dispose_resources(self.device)
except (usb.core.USBTimeoutError, usb.core.USBError) as exception:
logging.error(exception)
raise HydradancerBoardFatalError("Error, unable to disconnect") from exception
def wait_board_ready(self):
"""
Wait until the Hydradancer boards are ready, try to disconnect at some point to reset the internal states,
hoping it will be ready next time.
"""
# num of checks before trying to disconnect
max_num_status_ready_before_disconnect = 100
count_status_ready = 0
max_disconnect = 2
count_disconnect = 2
time_after_disconnect_sec = 1
time_between_checks_sec = 0.01
try:
# check if the board is ready a first time
hydradancer_ready = self.device.ctrl_transfer(
CTRL_TYPE_VENDOR | CTRL_RECIPIENT_DEVICE | CTRL_IN, self.CHECK_HYDRADANCER_READY,
data_or_wLength=1, timeout=5)
# repeat max_num_status_ready_before_disconnect times
while (hydradancer_ready is None or hydradancer_ready == 0) and count_disconnect < max_disconnect:
count_status_ready += 1
if count_status_ready % max_num_status_ready_before_disconnect == 0 and \
count_disconnect < max_disconnect:
logging.info(
"This is taking too long, disconnecting again ...")
self.disconnect()
time.sleep(time_after_disconnect_sec)
count_disconnect += 1
hydradancer_ready = self.device.ctrl_transfer(
CTRL_TYPE_VENDOR | CTRL_RECIPIENT_DEVICE | CTRL_IN, self.CHECK_HYDRADANCER_READY,
data_or_wLength=1, timeout=5)
time.sleep(time_between_checks_sec)
# if hydradancer is still not ready
if hydradancer_ready == 0:
raise HydradancerBoardFatalError(
"Hydradancer is not ready, please reset the board")
except (usb.core.USBTimeoutError, usb.core.USBError) as exception:
logging.error(exception)
raise HydradancerBoardFatalError(
"USB Error while waiting for Hydradancer go-ahead") from exception
def set_endpoint_mapping(self, ep_num):
"""
Maps emulated endpoints (endpoints facing the target) to Facedancer's host endpoints (control board endpoints)
"""
if ep_num not in self.SUPPORTED_EP_NUM:
raise HydradancerBoardFatalError(
f"Endpoint number {ep_num} not supported, supported numbers : {self.SUPPORTED_EP_NUM}")
if len(self.endpoints_mapping.values()) >= len(self.endpoints_pool):
raise HydradancerBoardFatalError(
f"All {len(self.endpoints_pool)} endpoints are already in use (for EP0 included)")
if ep_num not in self.endpoints_mapping:
self.endpoints_mapping[ep_num] = list(
self.endpoints_pool - set(self.endpoints_mapping.values()))[0]
self.reverse_endpoints_mapping[self.endpoints_mapping[ep_num]] = ep_num
try:
self.device.ctrl_transfer(CTRL_TYPE_VENDOR | CTRL_RECIPIENT_DEVICE | CTRL_OUT,
self.SET_ENDPOINT_MAPPING, wValue=(
ep_num & 0x00ff) | ((self.endpoints_mapping[ep_num] << 8) & 0xff00))
except (usb.core.USBTimeoutError, usb.core.USBError) as exception:
logging.error(exception)
raise HydradancerBoardFatalError(
f"Could not set mapping for ep {ep_num}") from exception
def set_usb2_speed(self, device_speed: DeviceSpeed=DeviceSpeed.FULL):
"""
Set the speed of the USB2 device. Speed is physically determined by the host,
so the emulation board must be configured.
"""
try:
self.device.ctrl_transfer(
CTRL_TYPE_VENDOR | CTRL_RECIPIENT_DEVICE | CTRL_OUT, self.SET_SPEED, wValue=self.facedancer_to_hydradancer_speed[device_speed] & 0x00ff)
except (usb.core.USBTimeoutError, usb.core.USBError) as exception:
logging.error(exception)
raise HydradancerBoardFatalError("Error, unable to set speed") from exception
def clear_halt(self, endpoint_number:int, direction: USBDirection):
try:
self.device.ctrl_transfer(
CTRL_TYPE_VENDOR | CTRL_RECIPIENT_DEVICE | CTRL_OUT, self.CLEAR_HALT, wValue=((endpoint_number & 0xff) | ((direction & 0xff) << 8)))
except (usb.core.USBTimeoutError, usb.core.USBError) as exception:
logging.error(exception)
raise HydradancerBoardFatalError("Error, unable to clear halt on endpoint {endpoint_number} direction {direction}") from exception
def set_address(self, address, defer=False):
"""
Set the USB address on the emulation board
"""
try:
self.device.ctrl_transfer(
CTRL_TYPE_VENDOR | CTRL_RECIPIENT_DEVICE | CTRL_OUT, self.SET_ADDRESS, address)
except (usb.core.USBTimeoutError, usb.core.USBError) as exception:
logging.error(exception)
raise HydradancerBoardFatalError(
"Error, unable to set address on emulated device") from exception
def stall_endpoint(self, ep_num, direction=0):
"""
Stall the ep_num endpoint on the emulation board.
STALL will be cleared automatically after next SETUP packet received.
"""
# Stall EP
try:
if ep_num == 0:
self.device.ctrl_transfer(
CTRL_TYPE_VENDOR | CTRL_RECIPIENT_DEVICE | CTRL_OUT, self.SET_EP_RESPONSE, wValue=(ep_num | 0 << 7) | (self.ENDP_STATE_STALL << 8) & 0xff00)
self.device.ctrl_transfer(
CTRL_TYPE_VENDOR | CTRL_RECIPIENT_DEVICE | CTRL_OUT, self.SET_EP_RESPONSE, wValue=(ep_num | 1 << 7) | (self.ENDP_STATE_STALL << 8) & 0xff00)
else:
self.device.ctrl_transfer(
CTRL_TYPE_VENDOR | CTRL_RECIPIENT_DEVICE | CTRL_OUT, self.SET_EP_RESPONSE, wValue=(ep_num | direction << 7) | (self.ENDP_STATE_STALL << 8) & 0xff00)
except (usb.core.USBTimeoutError, usb.core.USBError) as exception:
logging.error(exception)
raise HydradancerBoardFatalError(f"Could not stall ep {ep_num}") from exception
def send(self, ep_num, data):
"""
Prime target endpoint ep_num.
"""
try:
while not self.in_buffer_empty(ep_num):
events = self.fetch_events()
logging.debug(f"Sending len {len(data)} {data} on ep {ep_num}")
self.ep_out[self.endpoints_mapping[ep_num]].write(
data)
self.hydradancer_status["ep_in_status"] &= ~(0x01 << ep_num)
self.hydradancer_status["ep_in_nak"] &= ~(0x01 << ep_num)
except (usb.core.USBTimeoutError, usb.core.USBError):
logging.error(f"could not send data on ep {ep_num}")
def read(self, ep_num, blocking=False):
"""
Read from target endpoint ep_num. If blocking=True, wait until the endpoint's buffer is full.
"""
logging.debug(f"reading from ep {ep_num}")
try:
if blocking:
while not self.out_buffer_available(ep_num):
self.fetch_events()
if self.out_buffer_available(ep_num):
read = self.ep_in[self.endpoints_mapping[ep_num]].read(
self.MAX_PACKET_SIZE)
logging.debug(
f"EP{ep_num}/OUT: <- size {len(read)} {bytes(read)}")
self.hydradancer_status["ep_out_status"] &= ~(0x01 << ep_num)
return read
return None
except (usb.core.USBTimeoutError, usb.core.USBError):
logging.error(f"could not read data from ep {ep_num}")
return None
def configure(self, endpoint_numbers):
if len(endpoint_numbers) > len(self.endpoints_pool):
raise HydradancerBoardFatalError(
f"Hydradancer cannot handle {len(endpoint_numbers)} endpoints, only {len(self.endpoints_pool)}")
try:
for number in endpoint_numbers:
if self.INCOMPATIBLE_EP[number] in endpoint_numbers:
raise HydradancerBoardFatalError(
f"EP {number} can't be used at the same time as EPs {','.join([endpoint_numbers])}") from exception
self.set_endpoint_mapping(number)
self.device.ctrl_transfer(CTRL_TYPE_VENDOR | CTRL_RECIPIENT_DEVICE | CTRL_OUT,
self.CONFIGURED)
logging.info(f"Endpoints mapping {self.endpoints_mapping}")
self.configured = True
except (usb.core.USBTimeoutError, usb.core.USBError) as exception:
logging.error(exception)
raise HydradancerBoardFatalError(
"Could not pass configured step on board") from exception
def fetch_events(self):
"""
Poll the status of the endpoints. The state are accumulated (like on the boards),
and cleared when sending or reading data (which will trigger a similar clear on the boards).
Thus, self.ep_status should always be in sync with the endpoint's status on the boards.
"""
try:
# Use the endpoint type that best fits the type of request :
# -> for control requests, polling using ctrl transfers garanties the fastest status update.
# Latency is key in the enumeration phase
# -> for bulk requests, polling using bulk transfers allows for more status updates to be sent,
# thus increasing the speed
# TODO : what about interrupt or isochronous transfers ?
if not self.configured:
read = self.device.ctrl_transfer(
CTRL_TYPE_VENDOR | CTRL_RECIPIENT_DEVICE | CTRL_IN, self.GET_EVENT, data_or_wLength=self.events, timeout=self.timeout_ms_poll)
else:
read = self.ep_poll.read(
self.events, timeout=self.timeout_ms_poll*10)
if read >= 2:
events = []
for i in range(0, read, 2):
event = HydradancerEvent.from_bytes(self.events[i:i+2])
events.append(event)
logging.debug(event)
if event.event_type == HydradancerEvent.EVENT_IN_BUFFER_AVAILABLE:
self.hydradancer_status["ep_in_status"] |= (0x1 << event.value) & 0xff
elif event.event_type == HydradancerEvent.EVENT_OUT_BUFFER_AVAILABLE:
self.hydradancer_status["ep_out_status"] |= (0x1 << event.value) & 0xff
elif event.event_type == HydradancerEvent.EVENT_NAK:
self.hydradancer_status["ep_in_nak"] |= (0x1 << event.value) & 0xff
logging.debug(f"Hydradancer status {self.hydradancer_status}")
return events
return None
except usb.core.USBTimeoutError:
return None
except usb.core.USBError as exception:
logging.error(exception)
raise HydradancerBoardFatalError("USB Error while fetching events") from exception
def in_buffer_empty(self, ep_num):
"""
Returns True if the IN buffer for target endpoint ep_num is ready for priming
"""
return self.hydradancer_status["ep_in_status"] & (0x1 << ep_num)
def nak_on_endpoint(self, ep_num):
"""
Returns True if the IN Endpoint has sent a NAK (meaning a host has sent an IN request)
"""
return self.hydradancer_status["ep_in_nak"] & (0x1 << ep_num)
def out_buffer_available(self, ep_num):
"""
Returns True if the OUT buffer for target endpoint ep_num is full
"""
return self.hydradancer_status["ep_out_status"] & (0x1 << ep_num)
def control_buffer_available(self):
"""
Returns True if the control buffer is available. Since this buffer is shared between EP0 IN/EP0 OUT, only the OUT status is used for both.
"""
return self.out_buffer_available(0)
================================================
FILE: facedancer/backends/libusbhost.py
================================================
#
# This file is part of Facedancer.
#
""" Host support for accessing libusb with a Facedancer-like syntax. """
import sys
import time
import codecs
import struct
import usb
from ..core import *
class LibUSBHostApp(FacedancerUSBHost):
"""
Class that represents a libusb-based USB host.
"""
app_name = "LibUSB Host"
@classmethod
def appropriate_for_environment(cls, backend_name):
"""
Determines if the current environment seems appropriate
for using the libusb backend.
"""
# For this to work, we need to somehow select a single port.
# The best way to do this is with BUS and PORT, so allow the user
# to select those using the environment.
# TODO: accept these via quirks?
if os.environ.get('LIBUSB_BUS') and os.environ.get('LIBUSB_PORT'):
return True
# As a stand-in, allow use if the user specifies a device's address.
if os.environ.get('LIBUSB_ADDRESS'):
return True
# Never automatically instantiate the libusb backend,
# as it's not a full implementation and requires host-OS oddities.
return False
def __init__(self, verbose=0, quirks=[], index=0, **kwargs):
"""
Creates a new libusb backend for communicating with a target device.
"""
self.verbose = verbose
# If we have a specified bus/port, accept them.
# TODO: accept these via quirks?
desired_bus = os.environ.get('LIBUSB_BUS')
desired_port = os.environ.get('LIBUSB_PORT')
if desired_bus and desired_port:
kwargs['bus'] = int(desired_bus)
kwargs['port_number'] = int(desired_port)
# If the user's searching by address, use that.
# TODO: accept these via quirks?
desired_address = os.environ.get('LIBUSB_ADDRESS')
if desired_address:
kwargs['address'] = int(desired_address)
# Open a connection to the target device...
usb_devices = list(usb.core.find(find_all=True, **kwargs))
if len(usb_devices) <= index:
raise DeviceNotFoundError("Could not find a device to connect to via libusb!")
self.device = usb_devices[index]
# Detach any existing drivers, where possible.
try:
index = self.device.get_active_configuration().index
self.device.detach_kernel_driver(index)
except:
# FIXME: note this here, with a warning?
pass
def connect(self, device_speed=None):
"""
Sets up our host to talk to the device, including turning on VBUS.
"""
pass
def bus_reset(self, delay=0):
"""
Issues a "bus reset", requesting that the downstream device reset itself.
Args:
delay : The amount of time, in seconds, to wait before or after the
reset request. To be compliant, this should be omitted, or set
to 0.1s.
"""
# Note: we need to wait a reset delay before and after the bus reset.
# This allows the host to initialize _and_ then allows the device to settle.
time.sleep(delay)
self.device.reset()
time.sleep(delay)
def current_device_speed(self, as_string=False):
""" Returns the speed of the connected device
Args:
as_string : If true, returns the speed as a string for printing; otherwise returns
a DEVICE_SPEED_* constant.
"""
return self.device.speed
def current_line_state(self, as_string=False):
""" Returns the current state of the USB differential pair
as_string : If true, returns the speed as a string for printing; otherwise
returns a LINE_STATE_* constant.
"""
return None
def device_is_connected(self):
""" Returns true iff a given device is connected. """
return True
def port_is_enabled(self):
""" Returns true iff a given device is connected. """
return True
def port_is_powered(self):
""" Returns true iff a given device is connected. """
return True
def set_up_endpoint(self, endpoint_address_or_object, endpoint_type=None, max_packet_size=None,
device_address=None, endpoint_speed=None, handle_data_toggle=None,
is_control_endpoint=None):
"""
Sets up an endpoint for use. Can be used to initialize an endpoint or to update
its parameters. Two forms exist:
Args:
endpoint_object : a USBEndpoint object with the parameters to be populated
or
Args:
endpoint_address : the address of the endpoint to be setup; including the direction bit
endpoint_type : one of the ENDPOINT_TYPE constants that specifies the transfer mode on
the endpoint_address
max_packet_size : the maximum packet size to be communicated on the given endpoint
device_address : the address of the device to be communicated with; if not provided, the
last address will be used
endpoint_speed : the speed of the packets to be communicated on the endpoint; should be a
DEVICE_SPEED_* constant; if not provided, the last device's speed will be used.
handle_data_toggle : true iff the hardware should automatically handle selection of data packet PIDs
is_control_endpoint : true iff the given packet is a for a control endpoint
"""
# TODO: eventually support hubs / more than one device?
pass
def initialize_control_endpoint(self, device_address=None, device_speed=None, max_packet_size=None):
"""
Set up the device's control endpoint, so we can use it for e.g. enumeration.
"""
pass
def send_on_endpoint(self, endpoint_number, data, is_setup=False,
blocking=True, data_packet_pid=0):
"""
Sends a block of data on the provided endpoints.
Args:
endpoint_number : The endpoint number on which to send.
data : The data to be transmitted.
is_setup : True iff this transfer should begin with a SETUP token.
blocking : True iff this transaction should wait for the transaction to complete.
data_packet_pid : The data packet PID to use (1 or 0). Ignored if the endpoint is set to automatically
alternate data PIDs.
raises an IOError on a communications error or stall
"""
self.device.write(endpoint_number, data)
def read_from_endpoint(self, endpoint_number, expected_read_size=64, data_packet_pid=0):
"""
Sends a block of data on the provided endpoints.
Args:
endpoint_number : The endpoint number on which to send.
expected_read_size : The expected amount of data to be read.
data_packet_pid : The data packet PID to use (1 or 0).
Ignored if the endpoint is set to automatically alternate data PIDs.
raises an IOError on a communications error or stall
"""
data = self.device.read(endpoint_number, expected_read_size)
return data.tobytes()
def control_request_in(self, request_type, recipient, request, value=0, index=0, length=0):
""" Performs an IN control request.
Args:
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.
"""
request_type = self._build_request_type(True, request_type, recipient)
data = self.device.ctrl_transfer(request_type, request,
value, index, length)
return data.tobytes()
def control_request_out(self, request_type, recipient, request, value=0, index=0, data=[]):
""" Performs an OUT control request.
Args:
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.
"""
request_type = self._build_request_type(True, request_type, recipient)
self.device.ctrl_transfer(request_type, request,
value, index, data)
================================================
FILE: facedancer/backends/moondancer.py
================================================
# MoondancerApp.py
import sys
import time
import codecs
import enum
import traceback
from typing import List, Tuple
from ..core import *
from ..device import USBDevice
from ..configuration import USBConfiguration
from ..request import USBControlRequest
from ..types import DeviceSpeed, USBDirection
from ..logging import log
from .base import FacedancerBackend
# Quirk flags
class QuirkFlag(enum.IntFlag):
MANUAL_SET_ADDRESS: int = 0x01
# Cynthion interrupt events
class InterruptEvent:
USB_BUS_RESET: int = 10
USB_RECEIVE_CONTROL: int = 11
USB_RECEIVE_PACKET: int = 12
USB_SEND_COMPLETE: int = 13
def __init__(self, data: Tuple[int, int]):
"""
Parses a tuple of two bytes representing an interrupt event into an InterruptEvent.
Args:
data : A tuple of two bytes. The first byte is the interrupt code, the second is the endpoint number.
"""
if len(data) != 2:
log.error(f"Invalid length for InterruptEvent: {len(data)}")
raise ValueError(f"Invalid length for InterruptEvent: {len(data)}")
event = data[0]
endpoint_number = data[1]
if event not in [
InterruptEvent.USB_BUS_RESET,
InterruptEvent.USB_RECEIVE_CONTROL,
InterruptEvent.USB_RECEIVE_PACKET,
InterruptEvent.USB_SEND_COMPLETE
]: raise ValueError(f"Unknown InterruptEvent id: {event}")
self.event = event
self.endpoint_number = endpoint_number
def __eq__(self, rhs):
return self.event == rhs
def __repr__(self):
name = "UNKNOWN"
if self.event == InterruptEvent.USB_BUS_RESET:
name = "USB_BUS_RESET"
elif self.event == InterruptEvent.USB_RECEIVE_CONTROL:
name = "USB_RECEIVE_CONTROL"
elif self.event == InterruptEvent.USB_RECEIVE_PACKET:
name = "USB_RECEIVE_PACKET"
elif self.event == InterruptEvent.USB_SEND_COMPLETE:
name = "USB_SEND_COMPLETE"
return f"{name} {self.endpoint_number}"
#
# Moondancer backend implementation
#
class MoondancerApp(FacedancerApp, FacedancerBackend):
"""
Backend for using Cynthion devices as Facedancers.
"""
app_name = "Moondancer"
# Number of supported USB endpoints.
SUPPORTED_ENDPOINTS = 16
def __init__(self, device: USBDevice=None, verbose: int=0, quirks: List[str]=[]):
"""
Sets up a new Cynthion-backed Facedancer (Moondancer) application.
Args:
device : The Cynthion device that will act as our Moondancer.
verbose : The verbosity level of the given application.
"""
log.info("Using the Moondancer backend.")
import cynthion
if device is None:
device = cynthion.Cynthion()
self.device = device
self.device.comms.get_exclusive_
gitextract_tf2gfbvd/
├── .editorconfig
├── .gitignore
├── .readthedocs.yaml
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── LICENSE
├── README.md
├── Release.make
├── docs/
│ ├── Makefile
│ ├── make.bat
│ ├── requirements.txt
│ └── source/
│ ├── conf.py
│ ├── facedancer_examples.rst
│ ├── getting_started.rst
│ ├── howto_facedancer_backend.rst
│ ├── index.rst
│ ├── library_overview.rst
│ ├── using_facedancer.rst
│ └── using_usb_proxy.rst
├── examples/
│ ├── coroutine.py
│ ├── ftdi-echo.py
│ ├── hackrf-info.py
│ ├── imperative.py
│ ├── mass-storage.py
│ ├── minimal.py
│ ├── rubber-ducky.py
│ ├── template.py
│ ├── test_minimal.py
│ └── usbproxy.py
├── facedancer/
│ ├── __init__.py
│ ├── backends/
│ │ ├── MAXUSBApp.py
│ │ ├── __init__.py
│ │ ├── base.py
│ │ ├── goodfet.py
│ │ ├── greatdancer.py
│ │ ├── greathost.py
│ │ ├── hydradancer.py
│ │ ├── libusbhost.py
│ │ ├── moondancer.py
│ │ └── raspdancer.py
│ ├── classes/
│ │ ├── __init__.py
│ │ └── hid/
│ │ ├── __init__.py
│ │ ├── descriptor.py
│ │ ├── keyboard.py
│ │ └── usage.py
│ ├── configuration.py
│ ├── core.py
│ ├── descriptor.py
│ ├── device.py
│ ├── devices/
│ │ ├── __init__.py
│ │ ├── ftdi.py
│ │ ├── keyboard.py
│ │ └── umass/
│ │ ├── __init__.py
│ │ ├── disk_image.py
│ │ └── umass.py
│ ├── endpoint.py
│ ├── errors.py
│ ├── filters/
│ │ ├── __init__.py
│ │ ├── base.py
│ │ ├── hid.py
│ │ ├── logging.py
│ │ └── standard.py
│ ├── interface.py
│ ├── logging.py
│ ├── magic.py
│ ├── proxy.py
│ ├── request.py
│ └── types.py
├── pyproject.toml
└── test/
├── README.md
├── __init__.py
├── base.py
├── device.py
├── test_alternate.py
├── test_descriptors.py
├── test_stress.py
└── test_transfers.py
SYMBOL INDEX (763 symbols across 49 files)
FILE: examples/coroutine.py
function my_exit_handler (line 17) | async def my_exit_handler(bindkey: bytes):
function my_main_function (line 49) | def my_main_function(device, *coroutines):
FILE: examples/ftdi-echo.py
function send_hello (line 14) | async def send_hello():
function uppercasize (line 26) | def uppercasize(data):
FILE: examples/hackrf-info.py
class HackRF (line 12) | class HackRF(USBDevice):
class DefaultConfiguration (line 28) | class DefaultConfiguration(USBConfiguration):
class DefaultInterface (line 29) | class DefaultInterface(USBInterface):
method handle_control_request_14 (line 40) | def handle_control_request_14(self, request):
method handle_get_version_request (line 61) | def handle_get_version_request(self, request):
method handle_get_serial_request (line 73) | def handle_get_serial_request(self, request):
FILE: examples/imperative.py
class ImperativeDevice (line 22) | class ImperativeDevice(USBDevice):
method __init__ (line 24) | def __init__(self):
method handle_my_request (line 51) | def handle_my_request(self, request):
method handle_data_received (line 58) | def handle_data_received(self, endpoint, data):
FILE: examples/mass-storage.py
function hello (line 30) | async def hello():
FILE: examples/minimal.py
class MyDevice (line 13) | class MyDevice(USBDevice):
class MyConfiguration (line 20) | class MyConfiguration(USBConfiguration):
class MyInterface (line 22) | class MyInterface(USBInterface):
class MyInEndpoint (line 24) | class MyInEndpoint(USBEndpoint):
method handle_data_requested (line 29) | def handle_data_requested(self: USBEndpoint):
class MyOutEndpoint (line 33) | class MyOutEndpoint(USBEndpoint):
method handle_data_received (line 38) | def handle_data_received(self: USBEndpoint, data):
method my_in_vendor_request_handler (line 43) | def my_in_vendor_request_handler(self: USBDevice, request: USBControlR...
method my_out_vendor_request_handler (line 49) | def my_out_vendor_request_handler(self: USBDevice, request: USBControl...
FILE: examples/rubber-ducky.py
function type_letters (line 16) | async def type_letters():
FILE: examples/template.py
class TemplateDevice (line 15) | class TemplateDevice(USBDevice):
class TemplateConfiguration (line 75) | class TemplateConfiguration(USBConfiguration):
class TemplateInterface (line 107) | class TemplateInterface(USBInterface):
class TemplateInEndpoint (line 132) | class TemplateInEndpoint(USBEndpoint):
method handle_data_requested (line 164) | def handle_data_requested(self):
class TemplateOutEndpoint (line 174) | class TemplateOutEndpoint(USBEndpoint):
method handle_data_received (line 186) | def handle_data_received(self, data):
method handle_data_received (line 194) | def handle_data_received(self, endpoint, data):
method handle_my_request (line 220) | def handle_my_request(self, request):
method handle_another_request (line 242) | def handle_another_request(self, request):
FILE: examples/test_minimal.py
function main (line 3) | def main():
FILE: facedancer/backends/MAXUSBApp.py
class MAXUSBApp (line 12) | class MAXUSBApp(FacedancerApp, FacedancerBackend):
method bytes_as_hex (line 60) | def bytes_as_hex(b, delim=" "):
method send_on_endpoint (line 65) | def send_on_endpoint(self, ep_num, data, blocking=False):
method read_from_endpoint (line 93) | def read_from_endpoint(self, ep_num):
method stall_endpoint (line 110) | def stall_endpoint(self, ep_number, direction=0):
method stall_ep0 (line 132) | def stall_ep0(self, direction=0):
method get_version (line 136) | def get_version(self):
method connect (line 140) | def connect(self, usb_device, max_packet_size_ep0=64, device_speed=None):
method disconnect (line 154) | def disconnect(self):
method clear_irq_bit (line 162) | def clear_irq_bit(self, reg, bit):
method service_irqs (line 166) | def service_irqs(self):
method set_address (line 214) | def set_address(self, address, defer=False):
method configured (line 227) | def configured(self, configuration):
FILE: facedancer/backends/base.py
class FacedancerBackend (line 5) | class FacedancerBackend:
method __init__ (line 6) | def __init__(self, device: USBDevice=None, verbose: int=0, quirks: Lis...
method appropriate_for_environment (line 19) | def appropriate_for_environment(cls, backend_name: str) -> bool:
method get_version (line 30) | def get_version(self):
method connect (line 37) | def connect(self, usb_device: USBDevice, max_packet_size_ep0: int=64, ...
method disconnect (line 50) | def disconnect(self):
method reset (line 55) | def reset(self):
method set_address (line 62) | def set_address(self, address: int, defer: bool=False):
method configured (line 75) | def configured(self, configuration: USBConfiguration):
method read_from_endpoint (line 86) | def read_from_endpoint(self, endpoint_number: int) -> bytes:
method send_on_control_endpoint (line 96) | def send_on_control_endpoint(self, endpoint_number: int, in_request: U...
method send_on_endpoint (line 111) | def send_on_endpoint(self, endpoint_number: int, data: bytes, blocking...
method ack_status_stage (line 123) | def ack_status_stage(self, direction: USBDirection=USBDirection.OUT, e...
method stall_endpoint (line 138) | def stall_endpoint(self, endpoint_number:int, direction: USBDirection=...
method clear_halt (line 148) | def clear_halt(self, endpoint_number:int, direction: USBDirection):
method service_irqs (line 160) | def service_irqs(self):
method validate_configuration (line 168) | def validate_configuration(self, configuration: USBConfiguration):
FILE: facedancer/backends/goodfet.py
class GoodfetMaxUSBApp (line 11) | class GoodfetMaxUSBApp(MAXUSBApp):
method appropriate_for_environment (line 17) | def appropriate_for_environment(cls, backend_name):
method __init__ (line 41) | def __init__(self, device=None, verbose=0, quirks=None):
method init_commands (line 61) | def init_commands(self):
method enable (line 67) | def enable(self):
method ack_status_stage (line 75) | def ack_status_stage(self, blocking=False):
method read_register (line 82) | def read_register(self, reg_num, ack=False):
method write_register (line 100) | def write_register(self, reg_num, value, ack=False):
method read_bytes (line 113) | def read_bytes(self, reg, n):
method write_bytes (line 128) | def write_bytes(self, reg, data):
class Facedancer (line 141) | class Facedancer:
method __init__ (line 142) | def __init__(self, serialport, verbose=0):
method halt (line 150) | def halt(self):
method reset (line 154) | def reset(self):
method read (line 166) | def read(self, n):
method readcmd (line 180) | def readcmd(self):
method write (line 205) | def write(self, b):
method writecmd (line 213) | def writecmd(self, c):
class FacedancerCommand (line 221) | class FacedancerCommand:
method __init__ (line 222) | def __init__(self, app=None, verb=None, data=None):
method __str__ (line 227) | def __str__(self):
method long_string (line 236) | def long_string(self):
method as_bytestring (line 249) | def as_bytestring(self):
class GoodFETMonitorApp (line 262) | class GoodFETMonitorApp(FacedancerApp):
method read_byte (line 266) | def read_byte(self, addr):
method get_infostring (line 275) | def get_infostring(self):
method get_clocking (line 278) | def get_clocking(self):
method print_info (line 281) | def print_info(self):
method list_apps (line 288) | def list_apps(self):
method echo (line 302) | def echo(self, s):
method announce_connected (line 312) | def announce_connected(self):
function GoodFETSerialPort (line 318) | def GoodFETSerialPort(**kwargs):
FILE: facedancer/backends/greatdancer.py
class GreatDancerApp (line 16) | class GreatDancerApp(FacedancerApp, FacedancerBackend):
method appropriate_for_environment (line 48) | def appropriate_for_environment(cls, backend_name):
method __init__ (line 72) | def __init__(self, device=None, verbose=0, quirks=None):
method init_commands (line 123) | def init_commands(self):
method get_version (line 130) | def get_version(self):
method ack_status_stage (line 139) | def ack_status_stage(self, direction=HOST_TO_DEVICE, endpoint_number=0...
method _generate_endpoint_config_arguments (line 164) | def _generate_endpoint_config_arguments(self, config):
method connect (line 189) | def connect(self, usb_device, max_packet_size_ep0=64, device_speed=Dev...
method disconnect (line 218) | def disconnect(self):
method _wait_until_ready_to_send (line 225) | def _wait_until_ready_to_send(self, ep_num):
method send_on_endpoint (line 241) | def send_on_endpoint(self, ep_num, data, blocking=True):
method read_from_endpoint (line 263) | def read_from_endpoint(self, ep_num):
method _endpoint_address (line 283) | def _endpoint_address(ep_num, direction):
method stall_endpoint (line 294) | def stall_endpoint(self, ep_num, direction=0):
method stall_ep0 (line 309) | def stall_ep0(self, direction=0):
method set_address (line 316) | def set_address(self, address, defer=False):
method _decode_usb_register (line 331) | def _decode_usb_register(transfer_result):
method _fetch_irq_status (line 345) | def _fetch_irq_status(self):
method _fetch_setup_status (line 355) | def _fetch_setup_status(self):
method _handle_setup_events (line 365) | def _handle_setup_events(self):
method _handle_setup_event_on_endpoint (line 384) | def _handle_setup_event_on_endpoint(self, endpoint_number):
method _fetch_transfer_status (line 422) | def _fetch_transfer_status(self):
method _transfer_is_complete (line 432) | def _transfer_is_complete(self, endpoint_number, direction):
method _handle_transfer_events (line 455) | def _handle_transfer_events(self):
method _finish_primed_read_on_endpoint (line 499) | def _finish_primed_read_on_endpoint(self, endpoint_number):
method _clean_up_transfers_for_endpoint (line 512) | def _clean_up_transfers_for_endpoint(self, endpoint_number, direction):
method _is_control_endpoint (line 531) | def _is_control_endpoint(self, endpoint_number):
method _handle_transfer_complete_on_endpoint (line 540) | def _handle_transfer_complete_on_endpoint(self, endpoint_number, direc...
method _fetch_transfer_readiness (line 590) | def _fetch_transfer_readiness(self):
method _fetch_endpoint_nak_status (line 598) | def _fetch_endpoint_nak_status(self):
method _prime_out_endpoint (line 606) | def _prime_out_endpoint(self, endpoint_number):
method _handle_transfer_readiness (line 616) | def _handle_transfer_readiness(self):
method _is_ready_for_priming (line 651) | def _is_ready_for_priming(self, ep_num, direction):
method _has_issued_nak (line 673) | def _has_issued_nak(cls, ep_nak, ep_num, direction):
method _bus_reset (line 693) | def _bus_reset(self):
method reset (line 706) | def reset(self):
method _handle_nak_events (line 714) | def _handle_nak_events(self):
method _configure_endpoints (line 742) | def _configure_endpoints(self, configuration):
method configured (line 758) | def configured(self, configuration):
method service_irqs (line 776) | def service_irqs(self):
FILE: facedancer/backends/greathost.py
class GreatDancerHostApp (line 18) | class GreatDancerHostApp(FacedancerUSBHost):
method appropriate_for_environment (line 90) | def appropriate_for_environment(cls, backend_name):
method __init__ (line 114) | def __init__(self, verbose=0, quirks=[], autoconnect=True, device=None):
method connect (line 136) | def connect(self, device_speed=None):
method bus_reset (line 143) | def bus_reset(self, delay=0.500):
method _decode_usb_register (line 161) | def _decode_usb_register(transfer_result):
method _fetch_status_register (line 174) | def _fetch_status_register(self, register_number):
method _port_status (line 183) | def _port_status(self):
method _get_read_status (line 188) | def _get_read_status(self):
method _get_write_status (line 193) | def _get_write_status(self):
method device_is_connected (line 198) | def device_is_connected(self):
method port_is_enabled (line 204) | def port_is_enabled(self):
method port_is_powered (line 210) | def port_is_powered(self):
method current_device_speed (line 216) | def current_device_speed(self, as_string=False):
method current_line_state (line 238) | def current_line_state(self, as_string=False):
method set_up_endpoint (line 256) | def set_up_endpoint(self, endpoint_address_or_object, endpoint_type=No...
method initialize_control_endpoint (line 320) | def initialize_control_endpoint(self, device_address=None, device_spee...
method send_on_endpoint (line 337) | def send_on_endpoint(self, endpoint_number, data, is_setup=False,
method read_from_endpoint (line 382) | def read_from_endpoint(self, endpoint_number, expected_read_size=64, d...
FILE: facedancer/backends/hydradancer.py
class HydradancerEvent (line 26) | class HydradancerEvent:
method from_bytes (line 37) | def from_bytes(data : bytes):
method __repr__ (line 40) | def __repr__(self):
class HydradancerHostApp (line 44) | class HydradancerHostApp(FacedancerApp, FacedancerBackend):
method __init__ (line 60) | def __init__(self, device: USBDevice=None, verbose: int=0, quirks: Lis...
method appropriate_for_environment (line 86) | def appropriate_for_environment(cls, backend_name: str) -> bool:
method get_version (line 104) | def get_version(self):
method connect (line 110) | def connect(self, usb_device: USBDevice, max_packet_size_ep0: int=64, ...
method disconnect (line 134) | def disconnect(self):
method reset (line 144) | def reset(self):
method set_address (line 150) | def set_address(self, address: int, defer: bool=False):
method configured (line 163) | def configured(self, configuration: USBConfiguration):
method read_from_endpoint (line 198) | def read_from_endpoint(self, endpoint_number: int) -> bytes:
method send_on_endpoint (line 207) | def send_on_endpoint(self, endpoint_number: int, data: bytes, blocking...
method ack_status_stage (line 242) | def ack_status_stage(self, direction: USBDirection=USBDirection.OUT, e...
method stall_endpoint (line 266) | def stall_endpoint(self, endpoint_number:int, direction: USBDirection=...
method clear_halt (line 278) | def clear_halt(self, endpoint_number:int, direction: USBDirection):
method service_irqs (line 288) | def service_irqs(self):
method handle_bus_reset (line 307) | def handle_bus_reset(self):
method handle_data_endpoints (line 316) | def handle_data_endpoints(self):
method handle_control_request (line 344) | def handle_control_request(self):
class HydradancerBoardFatalError (line 387) | class HydradancerBoardFatalError(Exception):
class HydradancerBoard (line 391) | class HydradancerBoard():
method reinit (line 436) | def reinit(self, keep_ep0:bool = False):
method __init__ (line 456) | def __init__(self):
method connect (line 544) | def connect(self):
method disconnect (line 555) | def disconnect(self):
method wait_board_ready (line 569) | def wait_board_ready(self):
method set_endpoint_mapping (line 612) | def set_endpoint_mapping(self, ep_num):
method set_usb2_speed (line 638) | def set_usb2_speed(self, device_speed: DeviceSpeed=DeviceSpeed.FULL):
method clear_halt (line 650) | def clear_halt(self, endpoint_number:int, direction: USBDirection):
method set_address (line 658) | def set_address(self, address, defer=False):
method stall_endpoint (line 670) | def stall_endpoint(self, ep_num, direction=0):
method send (line 690) | def send(self, ep_num, data):
method read (line 705) | def read(self, ep_num, blocking=False):
method configure (line 726) | def configure(self, endpoint_numbers):
method fetch_events (line 746) | def fetch_events(self):
method in_buffer_empty (line 787) | def in_buffer_empty(self, ep_num):
method nak_on_endpoint (line 793) | def nak_on_endpoint(self, ep_num):
method out_buffer_available (line 799) | def out_buffer_available(self, ep_num):
method control_buffer_available (line 805) | def control_buffer_available(self):
FILE: facedancer/backends/libusbhost.py
class LibUSBHostApp (line 15) | class LibUSBHostApp(FacedancerUSBHost):
method appropriate_for_environment (line 23) | def appropriate_for_environment(cls, backend_name):
method __init__ (line 45) | def __init__(self, verbose=0, quirks=[], index=0, **kwargs):
method connect (line 81) | def connect(self, device_speed=None):
method bus_reset (line 88) | def bus_reset(self, delay=0):
method current_device_speed (line 105) | def current_device_speed(self, as_string=False):
method current_line_state (line 115) | def current_line_state(self, as_string=False):
method device_is_connected (line 124) | def device_is_connected(self):
method port_is_enabled (line 129) | def port_is_enabled(self):
method port_is_powered (line 134) | def port_is_powered(self):
method set_up_endpoint (line 139) | def set_up_endpoint(self, endpoint_address_or_object, endpoint_type=No...
method initialize_control_endpoint (line 169) | def initialize_control_endpoint(self, device_address=None, device_spee...
method send_on_endpoint (line 176) | def send_on_endpoint(self, endpoint_number, data, is_setup=False,
method read_from_endpoint (line 194) | def read_from_endpoint(self, endpoint_number, expected_read_size=64, d...
method control_request_in (line 210) | def control_request_in(self, request_type, recipient, request, value=0...
method control_request_out (line 230) | def control_request_out(self, request_type, recipient, request, value=...
FILE: facedancer/backends/moondancer.py
class QuirkFlag (line 23) | class QuirkFlag(enum.IntFlag):
class InterruptEvent (line 28) | class InterruptEvent:
method __init__ (line 34) | def __init__(self, data: Tuple[int, int]):
method __eq__ (line 58) | def __eq__(self, rhs):
method __repr__ (line 61) | def __repr__(self):
class MoondancerApp (line 77) | class MoondancerApp(FacedancerApp, FacedancerBackend):
method __init__ (line 87) | def __init__(self, device: USBDevice=None, verbose: int=0, quirks: Lis...
method appropriate_for_environment (line 147) | def appropriate_for_environment(cls, backend_name: str) -> bool:
method get_version (line 174) | def get_version(self):
method connect (line 183) | def connect(self, usb_device: USBDevice, max_packet_size_ep0: int=64, ...
method disconnect (line 216) | def disconnect(self):
method reset (line 228) | def reset(self):
method set_address (line 238) | def set_address(self, address: int, defer: bool=False):
method configured (line 253) | def configured(self, configuration: USBConfiguration):
method read_from_endpoint (line 298) | def read_from_endpoint(self, endpoint_number: int) -> bytes:
method send_on_control_endpoint (line 320) | def send_on_control_endpoint(self, endpoint_number: int, in_request: U...
method send_on_endpoint (line 337) | def send_on_endpoint(self, endpoint_number: int, data: bytes, blocking...
method ack_status_stage (line 354) | def ack_status_stage(self, direction: USBDirection=USBDirection.OUT, e...
method stall_endpoint (line 386) | def stall_endpoint(self, endpoint_number:int, direction: USBDirection=...
method clear_halt (line 409) | def clear_halt(self, endpoint_number: int, direction: USBDirection):
method service_irqs (line 424) | def service_irqs(self):
method handle_bus_reset (line 467) | def handle_bus_reset(self):
method handle_receive_control (line 479) | def handle_receive_control(self, endpoint_number: int):
method handle_receive_control_packet (line 527) | def handle_receive_control_packet(self, endpoint_number: int):
method handle_receive_packet (line 576) | def handle_receive_packet(self, endpoint_number: int):
method handle_send_complete (line 606) | def handle_send_complete(self, endpoint_number: int):
method handle_ep_in_nak_status (line 611) | def handle_ep_in_nak_status(self, nak_status: int):
FILE: facedancer/backends/raspdancer.py
class RaspdancerMaxUSBApp (line 21) | class RaspdancerMaxUSBApp(MAXUSBApp):
method appropriate_for_environment (line 26) | def appropriate_for_environment(cls, backend_name):
method __init__ (line 49) | def __init__(self, device=None, verbose=0, quirks=None):
method init_commands (line 68) | def init_commands(self):
method enable (line 71) | def enable(self):
method ack_status_stage (line 78) | def ack_status_stage(self, blocking=False):
method read_register (line 85) | def read_register(self, reg_num, ack=False):
method write_register (line 101) | def write_register(self, reg_num, value, ack=False):
method read_bytes (line 113) | def read_bytes(self, reg, n):
method write_bytes (line 125) | def write_bytes(self, reg, data):
class Raspdancer (line 134) | class Raspdancer(object):
method __init__ (line 140) | def __init__(self, verbose=0):
method reset (line 159) | def reset(self):
method set_up_comms (line 168) | def set_up_comms(self):
method transfer (line 180) | def transfer(self, data):
FILE: facedancer/classes/__init__.py
class USBDeviceClass (line 9) | class USBDeviceClass(IntEnum):
FILE: facedancer/classes/hid/descriptor.py
function _hid_item_generator (line 19) | def _hid_item_generator(constant) -> Tuple[int]:
function _io_item_generator (line 46) | def _io_item_generator(type_constant) -> Tuple[int]:
class HIDCollection (line 124) | class HIDCollection(IntEnum):
class HIDReportDescriptor (line 136) | class HIDReportDescriptor(USBDescriptor):
method __call__ (line 146) | def __call__(self, index=0):
FILE: facedancer/classes/hid/keyboard.py
class KeyboardModifiers (line 149) | class KeyboardModifiers(IntFlag):
class KeyboardKeys (line 160) | class KeyboardKeys(IntEnum):
method get_scancode_for_ascii (line 336) | def get_scancode_for_ascii(cls, letter_or_code):
FILE: facedancer/classes/hid/usage.py
class HIDUsagePage (line 10) | class HIDUsagePage(IntEnum):
class HIDGenericDesktopUsage (line 37) | class HIDGenericDesktopUsage(IntEnum):
FILE: facedancer/configuration.py
class USBConfiguration (line 21) | class USBConfiguration(USBDescribable, AutoInstantiable, USBRequestHandl...
method from_binary_descriptor (line 51) | def from_binary_descriptor(cls, data, strings={}):
method __post_init__ (line 106) | def __post_init__(self):
method attributes (line 116) | def attributes(self):
method get_device (line 133) | def get_device(self):
method add_interface (line 138) | def add_interface(self, interface: USBInterface):
method get_endpoint (line 157) | def get_endpoint(self, number: int, direction: USBDirection) -> USBEnd...
method handle_data_received (line 180) | def handle_data_received(self, endpoint: USBEndpoint, data: bytes):
method handle_data_requested (line 202) | def handle_data_requested(self, endpoint: USBEndpoint):
method handle_buffer_empty (line 222) | def handle_buffer_empty(self, endpoint: USBEndpoint):
method get_interfaces (line 243) | def get_interfaces(self) -> Iterable[USBInterface]:
method get_descriptor (line 248) | def get_descriptor(self) -> bytes:
method get_identifier (line 280) | def get_identifier(self) -> int:
method _request_handlers (line 288) | def _request_handlers(self) -> Iterable[callable]:
method _get_subordinate_handlers (line 291) | def _get_subordinate_handlers(self) -> Iterable[USBInterface]:
method generate_code (line 295) | def generate_code(self, name=None, indent=0):
FILE: facedancer/core.py
function FacedancerUSBApp (line 12) | def FacedancerUSBApp(verbose=0, quirks=None):
class FacedancerApp (line 25) | class FacedancerApp:
method autodetect (line 30) | def autodetect(cls, verbose=0, quirks=None):
method _find_appropriate_subclass (line 62) | def _find_appropriate_subclass(cls, backend_name):
method appropriate_for_environment (line 83) | def appropriate_for_environment(cls, backend_name=None):
method __init__ (line 97) | def __init__(self, device, verbose=0):
method init_commands (line 106) | def init_commands(self):
method enable (line 109) | def enable(self):
function FacedancerUSBHostApp (line 113) | def FacedancerUSBHostApp(verbose=0, quirks=None):
class FacedancerUSBHost (line 125) | class FacedancerUSBHost:
method autodetect (line 164) | def autodetect(cls, verbose=0, quirks=None):
method _find_appropriate_subclass (line 198) | def _find_appropriate_subclass(cls, backend_name):
method appropriate_for_environment (line 221) | def appropriate_for_environment(cls, backend_name=None):
method _build_request_type (line 236) | def _build_request_type(cls, is_in, req_type, recipient):
method _build_setup_request (line 259) | def _build_setup_request(cls, is_in, request_type, recipient, request,...
method control_request_in (line 281) | def control_request_in(self, request_type, recipient, request, value=0...
method control_request_out (line 327) | def control_request_out(self, request_type, recipient, request, value=...
method initialize_device (line 354) | def initialize_device(self, apply_configuration=0, assign_address=0):
method get_descriptor (line 398) | def get_descriptor(self, descriptor_type, descriptor_index,
method get_device_descriptor (line 408) | def get_device_descriptor(self, max_length=18):
method read_ep0_max_packet_size (line 417) | def read_ep0_max_packet_size(self):
method get_configuration_descriptor (line 423) | def get_configuration_descriptor(self, index=0, include_subordinates=T...
method set_address (line 446) | def set_address(self, device_address):
method set_configuration (line 461) | def set_configuration(self, index):
method apply_configuration (line 475) | def apply_configuration(self, index, set_configuration=True):
method handle_events (line 500) | def handle_events(self):
class FacedancerBasicScheduler (line 504) | class FacedancerBasicScheduler(object):
method __init__ (line 512) | def __init__(self):
method add_task (line 517) | def add_task(self, callback):
method run (line 527) | def run(self):
method stop (line 538) | def stop(self):
FILE: facedancer/descriptor.py
class USBDescribable (line 16) | class USBDescribable(metaclass=DescribableMeta):
method handles_binary_descriptor (line 25) | def handles_binary_descriptor(cls, data):
method from_binary_descriptor (line 35) | def from_binary_descriptor(cls, data, strings={}):
class USBDescriptor (line 49) | class USBDescriptor(USBDescribable, AutoInstantiable):
method __post_init__ (line 67) | def __post_init__(self):
method __call__ (line 72) | def __call__(self, index=0):
method get_identifier (line 76) | def get_identifier(self):
method from_binary_descriptor (line 80) | def from_binary_descriptor(cls, data, strings={}):
method generate_code (line 83) | def generate_code(self, name=None, indent=0):
class USBClassDescriptor (line 123) | class USBClassDescriptor(USBDescriptor):
method __init_subclass__ (line 128) | def __init_subclass__(cls, **kwargs):
class USBStringDescriptor (line 136) | class USBStringDescriptor(USBDescriptor):
method from_string (line 145) | def from_string(cls, string, *, index=None):
class StringRef (line 154) | class StringRef:
method __init__ (line 157) | def __init__(self, index: int = None, string : str = None):
method field (line 165) | def field(cls, **kwargs):
method lookup (line 171) | def lookup(cls, strings: Dict[int, str], index: int):
method ensure (line 182) | def ensure(cls, value):
method generate_code (line 199) | def generate_code(self):
class StringDescriptorManager (line 211) | class StringDescriptorManager:
method __init__ (line 214) | def __init__(self):
method add_string (line 224) | def add_string(self, string, index=None):
method get_index (line 252) | def get_index(self, string):
method __getitem__ (line 274) | def __getitem__(self, index):
class USBDescriptorTypeNumber (line 283) | class USBDescriptorTypeNumber(IntEnum):
function include_in_config (line 296) | def include_in_config(cls):
function requestable (line 301) | def requestable(type_number, number):
FILE: facedancer/device.py
class USBBaseDevice (line 38) | class USBBaseDevice(USBDescribable, USBRequestHandler):
method from_binary_descriptor (line 95) | def from_binary_descriptor(cls, data, strings={}):
method __post_init__ (line 138) | def __post_init__(self):
method add_configuration (line 177) | def add_configuration(self, configuration: USBConfiguration):
method add_descriptor (line 183) | def add_descriptor(self, descriptor: USBDescriptor):
method connect (line 208) | def connect(self, device_speed: DeviceSpeed=DeviceSpeed.FULL):
method disconnect (line 220) | def disconnect(self):
method run (line 225) | async def run(self):
method run_with (line 243) | def run_with(self, *coroutines: Iterable[Coroutine]):
method emulate (line 255) | def emulate(self, *coroutines: Iterable[Coroutine]):
method stall (line 278) | def stall(self, *, endpoint_number: int = 0, direction: USBDirection =...
method clear_halt (line 291) | def clear_halt(self, endpoint_number: int, direction: USBDirection):
method control_send (line 301) | def control_send(self, endpoint_number: int, in_request: USBControlReq...
method send (line 316) | def send(self, endpoint_number: int, data: bytes, *, blocking: bool = ...
method _send_in_packets (line 334) | def _send_in_packets(self, endpoint_number: int, data: bytes, *,
method get_endpoint (line 365) | def get_endpoint(self, endpoint_number: int, direction: USBDirection) ...
method create_request (line 389) | def create_request(self, raw_data: bytes) -> USBControlRequest:
method handle_nak (line 398) | def handle_nak(self, ep_num: int):
method handle_buffer_available (line 411) | def handle_buffer_available(self, ep_num):
method handle_data_available (line 422) | def handle_data_available(self, ep_num, data):
method handle_bus_reset (line 439) | def handle_bus_reset(self):
method handle_request (line 450) | def handle_request(self, request: USBControlRequest):
method handle_data_received (line 474) | def handle_data_received(self, endpoint: USBEndpoint, data: bytes):
method handle_unexpected_data_received (line 498) | def handle_unexpected_data_received(self, endpoint_number: int, data: ...
method handle_data_requested (line 512) | def handle_data_requested(self, endpoint: USBEndpoint):
method handle_unexpected_data_requested (line 535) | def handle_unexpected_data_requested(self, endpoint_number: int):
method handle_buffer_empty (line 548) | def handle_buffer_empty(self, endpoint: USBEndpoint):
method _request_handlers (line 567) | def _request_handlers(self) -> Iterable[callable]:
method _get_subordinate_handlers (line 571) | def _get_subordinate_handlers(self) -> Iterable[callable]:
method _add_request_suggestion (line 580) | def _add_request_suggestion(self, request: USBControlRequest):
method _print_suggested_requests (line 602) | def _print_suggested_requests(self):
method print_suggested_additions (line 711) | def print_suggested_additions(self):
method set_address (line 735) | def set_address(self, address: int, defer: bool = False):
method get_descriptor (line 749) | def get_descriptor(self) -> bytes:
method get_configuration_descriptor (line 775) | def get_configuration_descriptor(self, index: int) -> bytes:
method handle_get_supported_languages_descriptor (line 783) | def handle_get_supported_languages_descriptor(self) -> bytes:
method get_string_descriptor (line 800) | def get_string_descriptor(self, index:int) -> bytes:
method handle_generic_get_descriptor_request (line 809) | def handle_generic_get_descriptor_request(
class USBDevice (line 843) | class USBDevice(USBBaseDevice):
method handle_get_status_request (line 872) | def handle_get_status_request(self, request):
method handle_clear_feature_request (line 883) | def handle_clear_feature_request(self, request):
method handle_set_feature_request (line 891) | def handle_set_feature_request(self, request):
method handle_set_address_request (line 899) | def handle_set_address_request(self, request):
method handle_get_descriptor_request (line 907) | def handle_get_descriptor_request(self, request):
method handle_set_descriptor_request (line 917) | def handle_set_descriptor_request(self, request):
method handle_get_configuration_request (line 925) | def handle_get_configuration_request(self, request):
method handle_set_configuration_request (line 941) | def handle_set_configuration_request(self, request):
method handle_synch_frame_request (line 976) | def handle_synch_frame_request(self, request):
method generate_code (line 982) | def generate_code(self, name="Device"):
FILE: facedancer/devices/__init__.py
function default_main (line 14) | def default_main(device_or_type, *coroutines):
FILE: facedancer/devices/ftdi.py
class FTDIFlowControl (line 23) | class FTDIFlowControl(IntFlag):
class FTDIDevice (line 32) | class FTDIDevice(USBDevice):
class _Configuration (line 55) | class _Configuration(USBConfiguration):
class _Interface (line 58) | class _Interface(USBInterface):
class _OutEndpoint (line 65) | class _OutEndpoint(USBEndpoint):
class _InEndpoint (line 70) | class _InEndpoint(USBEndpoint):
method __post_init__ (line 76) | def __post_init__(self):
method reset_ftdi (line 83) | def reset_ftdi(self):
method handle_reset_request (line 110) | def handle_reset_request(self, request):
method handle_modem_ctrl_request (line 118) | def handle_modem_ctrl_request(self, req):
method handle_set_flow_ctrl_request (line 141) | def handle_set_flow_ctrl_request(self, request):
method handle_set_baud_rate_request (line 159) | def handle_set_baud_rate_request(self, request):
method handle_set_data_request (line 183) | def handle_set_data_request(self, request):
method handle_get_modem_status_request (line 189) | def handle_get_modem_status_request(self, request):
method handle_set_event_char_request (line 203) | def handle_set_event_char_request(self, request):
method handle_set_error_char_request (line 209) | def handle_set_error_char_request(self, request):
method handle_set_latency_timer_request (line 215) | def handle_set_latency_timer_request(self, request):
method handle_get_latency_timer_request (line 221) | def handle_get_latency_timer_request(self, request):
method handle_read_eeprom_request (line 228) | def handle_read_eeprom_request(self, request):
method handle_data_received (line 250) | def handle_data_received(self, endpoint, data):
method wait_for_host (line 259) | async def wait_for_host(self):
method handle_serial_data_received (line 267) | def handle_serial_data_received(self, data):
method transmit (line 275) | def transmit(self, data: Union[str, bytes], *, blocking: bool = False,...
method _transmit_packet (line 303) | def _transmit_packet(self, data: bytes, *, blocking: bool = False):
FILE: facedancer/devices/keyboard.py
class USBKeyboardDevice (line 23) | class USBKeyboardDevice(USBDevice):
class KeyboardConfiguration (line 30) | class KeyboardConfiguration(USBConfiguration):
class KeyboardInterface (line 34) | class KeyboardInterface(USBInterface):
class KeyEventEndpoint (line 41) | class KeyEventEndpoint(USBEndpoint):
class HIDDescriptor (line 53) | class HIDDescriptor(USBDescriptor):
class ReportDescriptor (line 60) | class ReportDescriptor(HIDReportDescriptor):
method handle_get_interface_request (line 112) | def handle_get_interface_request(self, request):
method __post_init__ (line 117) | def __post_init__(self):
method _generate_hid_report (line 125) | def _generate_hid_report(self) -> bytes:
method handle_data_requested (line 136) | def handle_data_requested(self, endpoint: USBEndpoint):
method key_down (line 147) | def key_down(self, code: KeyboardKeys):
method key_up (line 152) | def key_up(self, code: KeyboardKeys):
method modifier_down (line 157) | def modifier_down(self, code: KeyboardModifiers):
method modifier_up (line 163) | def modifier_up(self, code: KeyboardModifiers):
method type_scancode (line 169) | async def type_scancode(self, code: KeyboardKeys, duration: float = 0....
method type_scancodes (line 189) | async def type_scancodes(self, *codes: Iterable[KeyboardKeys], duratio...
method type_letter (line 200) | async def type_letter(self, letter: str, duration: float = 0.1, modifi...
method type_letters (line 214) | async def type_letters(self, *letters: Iterable[str], duration:float =...
method type_string (line 225) | async def type_string(self, to_type: str, *, duration:float = 0.1, mod...
method all_keys_up (line 242) | def all_keys_up(self, *, include_modifiers: bool = True):
method all_modifiers_up (line 254) | def all_modifiers_up(self):
FILE: facedancer/devices/umass/disk_image.py
class DiskImage (line 6) | class DiskImage:
method close (line 14) | def close(self):
method get_sector_size (line 18) | def get_sector_size(self):
method get_sector_count (line 21) | def get_sector_count(self):
method get_data (line 25) | def get_data(self, address, length):
method get_sector_data (line 39) | def get_sector_data(self, address):
method put_data (line 44) | def put_data(self, address, data):
method put_sector_data (line 56) | def put_sector_data(self, address, data):
class FAT32DiskImage (line 61) | class FAT32DiskImage(DiskImage):
method __init__ (line 76) | def __init__(self, size = 1024 * 1024 * 256, verbose=0):
method _register_sector_handler (line 84) | def _register_sector_handler(self, sector_or_lambda, name, handler=None):
method _initialize_sector_handlers (line 98) | def _initialize_sector_handlers(self):
method handle_mbr_read (line 109) | def handle_mbr_read(self, address):
method handle_bpb_read (line 124) | def handle_bpb_read(self, address):
method handle_fsinfo_read (line 171) | def handle_fsinfo_read(self, address):
method _generate_directory_entry (line 188) | def _generate_directory_entry(self, filename, file_size, cluster_numbe...
method _short_filename_checksum (line 210) | def _short_filename_checksum(self, short_filename):
method _is_valid_83_char (line 225) | def _is_valid_83_char(self, c):
method _is_valid_83_name (line 238) | def _is_valid_83_name(self, long_filename):
method _short_filename_from_long (line 248) | def _short_filename_from_long(self, long_filename):
method _generate_long_directory_entries (line 266) | def _generate_long_directory_entries(self, long_filename, short_filena...
method handle_root_dir_read (line 312) | def handle_root_dir_read(self, address):
method _generate_fat_partition_entry (line 323) | def _generate_fat_partition_entry(self):
method _sectors_per_cluster (line 342) | def _sectors_per_cluster(self):
method handle_fat_read (line 350) | def handle_fat_read(self, address):
method handle_unhandled_sector (line 362) | def handle_unhandled_sector(self, address):
method get_sector_count (line 373) | def get_sector_count(self):
method get_partition_sectors (line 380) | def get_partition_sectors(self):
method _find_sector_handler (line 389) | def _find_sector_handler(self, address):
method get_sector_data (line 410) | def get_sector_data(self, address):
class RawDiskImage (line 444) | class RawDiskImage(DiskImage):
method __init__ (line 449) | def __init__(self, filename, block_size, verbose=0):
method close (line 460) | def close(self):
method get_sector_count (line 464) | def get_sector_count(self):
method get_sector_data (line 467) | def get_sector_data(self, address):
method put_data (line 485) | def put_data(self, address, data):
method put_sector_data (line 493) | def put_sector_data(self, address, data):
FILE: facedancer/devices/umass/umass.py
class USBMassStorageDevice (line 29) | class USBMassStorageDevice(USBDevice):
class _Configuration (line 32) | class _Configuration(USBConfiguration):
class _Interface (line 35) | class _Interface(USBInterface):
class _OutEndpoint (line 42) | class _OutEndpoint(USBEndpoint):
class _InEndpoint (line 48) | class _InEndpoint(USBEndpoint):
method __init__ (line 54) | def __init__(self, disk_image,
method connect (line 84) | def connect(self):
method disconnect (line 91) | def disconnect(self):
method handle_data_received (line 98) | def handle_data_received(self, endpoint, data):
method handle_get_max_lun_request (line 112) | def handle_get_max_lun_request(self, request):
method handle_bulk_only_mass_storage_reset_request (line 117) | def handle_bulk_only_mass_storage_reset_request(self, request):
method wait_for_host (line 121) | async def wait_for_host(self):
function bytes_as_hex (line 129) | def bytes_as_hex(b, delim=" "):
class ScsiCommandHandler (line 132) | class ScsiCommandHandler:
method __init__ (line 139) | def __init__(self, device, disk_image, verbose=0, vendor="GoodFET "):
method handle_data_received (line 154) | def handle_data_received(self, data):
method handle_scsi_command (line 186) | def handle_scsi_command(self, cbw):
method handle_unknown_command (line 216) | def handle_unknown_command(self, cbw):
method handle_ignored_event (line 232) | def handle_ignored_event(self, cbw):
method handle_sense (line 241) | def handle_sense(self, cbw):
method handle_inquiry (line 249) | def handle_inquiry(self, cbw):
method handle_mode_sense_6 (line 278) | def handle_mode_sense_6(self, cbw):
method handle_mode_sense_10 (line 289) | def handle_mode_sense_10(self, cbw):
method handle_service_action_in (line 300) | def handle_service_action_in(self, cbw):
method handle_get_format_capacity (line 310) | def handle_get_format_capacity(self, cbw):
method handle_get_read_capacity (line 320) | def handle_get_read_capacity(self, cbw):
method handle_get_read_capacity_16 (line 335) | def handle_get_read_capacity_16(self, cbw):
method handle_read (line 352) | def handle_read(self, cbw):
method handle_read_16 (line 376) | def handle_read_16(self, cbw):
method handle_write (line 406) | def handle_write(self, cbw):
method handle_write_16 (line 429) | def handle_write_16(self, cbw):
method continue_write (line 458) | def continue_write(self, cbw, data):
method _register_scsi_commands (line 476) | def _register_scsi_commands(self):
method _register_scsi_command (line 495) | def _register_scsi_command(self, number, name, handler=None):
class CommandBlockWrapper (line 507) | class CommandBlockWrapper:
method __init__ (line 508) | def __init__(self, bytestring):
method __str__ (line 520) | def __str__(self):
FILE: facedancer/endpoint.py
class USBEndpoint (line 26) | class USBEndpoint(USBDescribable, AutoInstantiable, USBRequestHandler):
method from_binary_descriptor (line 69) | def from_binary_descriptor(cls, data, strings={}):
method __post_init__ (line 96) | def __post_init__(self):
method address_for_number (line 110) | def address_for_number(endpoint_number: int, direction: USBDirection) ...
method get_device (line 116) | def get_device(self):
method send (line 121) | def send(self, data: bytes, *, blocking: bool = False):
method handle_data_received (line 137) | def handle_data_received(self, data: bytes):
method handle_data_requested (line 147) | def handle_data_requested(self):
method handle_buffer_empty (line 151) | def handle_buffer_empty(self):
method handle_clear_feature_request (line 157) | def handle_clear_feature_request(self, request):
method address (line 168) | def address(self):
method get_address (line 173) | def get_address(self):
method attributes (line 179) | def attributes(self):
method add_descriptor (line 186) | def add_descriptor(self, descriptor: USBDescriptor):
method get_descriptor (line 217) | def get_descriptor(self) -> bytes:
method get_identifier (line 240) | def get_identifier(self) -> int:
method matches_identifier (line 243) | def matches_identifier(self, other:int) -> bool:
method _request_handlers (line 253) | def _request_handlers(self) -> Iterable[callable]:
method __str__ (line 260) | def __str__(self):
method generate_code (line 269) | def generate_code(self, name=None, indent=0):
FILE: facedancer/errors.py
class DeviceNotFoundError (line 5) | class DeviceNotFoundError(IOError):
class EndEmulation (line 9) | class EndEmulation(Exception):
FILE: facedancer/filters/base.py
class USBProxyFilter (line 6) | class USBProxyFilter:
method filter_control_in_setup (line 11) | def filter_control_in_setup(self, request, stalled):
method filter_control_in (line 29) | def filter_control_in(self, request, data, stalled):
method filter_control_out (line 49) | def filter_control_out(self, request, data):
method handle_out_request_stall (line 66) | def handle_out_request_stall(self, request, data, stalled):
method filter_in_token (line 79) | def filter_in_token(self, ep_num):
method filter_in (line 95) | def filter_in(self, ep_num, data):
method filter_out (line 112) | def filter_out(self, ep_num, data):
method handle_out_stall (line 127) | def handle_out_stall(self, ep_num, data, stalled):
FILE: facedancer/filters/hid.py
class HIDReportType (line 32) | class HIDReportType(IntEnum):
class USBProxyHIDFilter (line 38) | class USBProxyHIDFilter(USBProxyFilter):
method __init__ (line 45) | def __init__(self, device: USBBaseDevice, verbose=1):
method filter_control_in (line 50) | def filter_control_in(self, req: USBControlRequest | None, data, stall...
method _log_desc (line 61) | def _log_desc(self, req, data):
method _log_in (line 88) | def _log_in(self, req, data):
method filter_control_out (line 97) | def filter_control_out(self, req, data):
method _log_out (line 103) | def _log_out(self, req, data):
method filter_in (line 115) | def filter_in(self, ep_num, data):
method _find_interface (line 121) | def _find_interface(self, ep_num):
method _log_ep_in (line 130) | def _log_ep_in(self, iface, num, data):
method _report (line 134) | def _report(self, iface: int, kind: str, data: bytes):
function dump (line 148) | def dump(raw: bytes):
FILE: facedancer/filters/logging.py
class USBProxyPrettyPrintFilter (line 12) | class USBProxyPrettyPrintFilter(USBProxyFilter):
method __init__ (line 17) | def __init__(self, verbose=4, decoration=''):
method filter_control_in (line 26) | def filter_control_in(self, req, data, stalled):
method filter_control_out (line 48) | def filter_control_out(self, req, data):
method handle_out_request_stall (line 68) | def handle_out_request_stall(self, req, data, stalled):
method filter_in (line 81) | def filter_in(self, ep_num, data):
method filter_out (line 91) | def filter_out(self, ep_num, data):
method timestamp (line 102) | def timestamp(self):
method _magic_decode (line 106) | def _magic_decode(self, data):
method _pretty_print_data (line 114) | def _pretty_print_data(self, data, direction_marker, decoration='', is...
FILE: facedancer/filters/standard.py
class USBProxySetupFilters (line 14) | class USBProxySetupFilters(USBProxyFilter):
method __init__ (line 27) | def __init__(self, device, verbose=0):
method filter_control_in (line 32) | def filter_control_in(self, req, data, stalled):
method filter_control_out (line 67) | def filter_control_out(self, req, data):
FILE: facedancer/interface.py
class USBInterface (line 28) | class USBInterface(USBDescribable, AutoInstantiable, USBRequestHandler):
method from_binary_descriptor (line 63) | def from_binary_descriptor(cls, data, strings={}):
method __post_init__ (line 81) | def __post_init__(self):
method get_device (line 99) | def get_device(self):
method add_endpoint (line 104) | def add_endpoint(self, endpoint: USBEndpoint):
method get_endpoint (line 120) | def get_endpoint(self, endpoint_number: int, direction: USBDirection) ...
method has_endpoint (line 134) | def has_endpoint(self, endpoint_number: int, direction: USBDirection) ...
method add_descriptor (line 144) | def add_descriptor(self, descriptor: USBDescriptor):
method handle_data_received (line 179) | def handle_data_received(self, endpoint: USBEndpoint, data: bytes):
method handle_data_requested (line 198) | def handle_data_requested(self, endpoint: USBEndpoint):
method handle_buffer_empty (line 215) | def handle_buffer_empty(self, endpoint: USBEndpoint):
method get_endpoints (line 233) | def get_endpoints(self):
method handle_get_descriptor_request (line 244) | def handle_get_descriptor_request(self, request):
method get_descriptor (line 253) | def get_descriptor(self) -> bytes:
method handle_set_interface_request (line 296) | def handle_set_interface_request(self, request: USBControlRequest):
method handle_get_interface_request (line 323) | def handle_get_interface_request(self, request):
method get_identifier (line 345) | def get_identifier(self) -> (int, int):
method matches_identifier (line 353) | def matches_identifier(self, other: int) -> bool:
method _request_handlers (line 361) | def _request_handlers(self) -> Iterable[callable]:
method _get_subordinate_handlers (line 364) | def _get_subordinate_handlers(self) -> Iterable[callable]:
method generate_code (line 368) | def generate_code(self, name=None, indent=0):
FILE: facedancer/logging.py
function configure_default_logging (line 12) | def configure_default_logging(level=logging.INFO, logger=logging):
function _initialize_logging (line 22) | def _initialize_logging():
FILE: facedancer/magic.py
class DescribableMeta (line 12) | class DescribableMeta(ABCMeta):
method __new__ (line 14) | def __new__(cls, name, bases, classdict):
function adjust_defaults (line 26) | def adjust_defaults(cls, **kwargs):
class AutoInstantiable (line 35) | class AutoInstantiable(metaclass=DescribableMeta):
method get_identifier (line 39) | def get_identifier(self) -> int:
method matches_identifier (line 45) | def matches_identifier(self, other: int) -> bool:
class AutoInstantiator (line 49) | class AutoInstantiator:
method __init__ (line 56) | def __init__(self, target_type):
method creates_instance_of (line 59) | def creates_instance_of(self, expected_type):
method __call__ (line 62) | def __call__(self, parent):
function use_automatically (line 66) | def use_automatically(cls):
function _use_inner_classes_automatically (line 96) | def _use_inner_classes_automatically(cls):
function use_inner_classes_automatically (line 112) | def use_inner_classes_automatically(cls):
function instantiate_subordinates (line 117) | def instantiate_subordinates(obj, expected_type):
FILE: facedancer/proxy.py
class USBProxyDevice (line 21) | class USBProxyDevice(USBBaseDevice):
method __init__ (line 28) | def __init__(self, index=0, quirks=[], scheduler=None, **kwargs):
method add_filter (line 54) | def add_filter(self, filter_object, head=False):
method connect (line 64) | def connect(self):
method configured (line 95) | def configured(self, configuration: USBConfiguration):
method interface_changed (line 118) | def interface_changed(self, interface_number: int, alternate: int):
method handle_bus_reset (line 134) | def handle_bus_reset(self):
method handle_request (line 138) | def handle_request(self, request: USBControlRequest):
method handle_get_configuration_request (line 149) | def handle_get_configuration_request(self, request):
method handle_get_descriptor_request (line 153) | def handle_get_descriptor_request(self, request):
method handle_data_available (line 157) | def handle_data_available(self, ep_num, data):
method handle_nak (line 181) | def handle_nak(self, ep_num):
method _ack_status_stage (line 203) | def _ack_status_stage(self, blocking=False):
method _proxy_in_control_request (line 206) | def _proxy_in_control_request(self, request: USBControlRequest):
method _proxy_out_control_request (line 255) | def _proxy_out_control_request(self, request: USBControlRequest):
method _proxy_in_transfer (line 289) | def _proxy_in_transfer(self, endpoint):
class LibUSB1Device (line 329) | class LibUSB1Device:
method _get_libusb_context (line 341) | def _get_libusb_context(cls):
method _destroy_libusb_context (line 353) | def _destroy_libusb_context(cls):
method open (line 377) | def open(cls, device, detach=True):
method find (line 407) | def find(cls, idVendor, idProduct, find_all=True):
method device_speed (line 426) | def device_speed(cls):
method controlRead (line 431) | def controlRead(cls, request_type, request, value, index, length, time...
method controlWrite (line 436) | def controlWrite(cls, request_type, request, value, index, data, timeo...
method read (line 441) | def read(cls, endpoint_number, length, timeout=1000):
method write (line 450) | def write(cls, endpoint_number, data, timeout=1000):
method clear_halt (line 456) | def clear_halt(cls, endpoint_number, direction):
function configure_logging (line 479) | async def configure_logging():
FILE: facedancer/request.py
function _wrap_with_field_matcher (line 18) | def _wrap_with_field_matcher(func, field_name, field_value, match_index=...
class ControlRequestHandler (line 60) | class ControlRequestHandler:
method __init__ (line 68) | def __init__(self, handler_function, execution_condition):
method __call__ (line 73) | def __call__(self, caller, request):
method add_condition (line 84) | def add_condition(self, condition):
method add_field_matcher (line 90) | def add_field_matcher(self, field_name, field_value):
method __repr__ (line 101) | def __repr__(self):
function control_request_handler (line 106) | def control_request_handler(condition=lambda _ : True, **kwargs):
function standard_request_handler (line 133) | def standard_request_handler(**kwargs):
function vendor_request_handler (line 138) | def vendor_request_handler(**kwargs):
function class_request_handler (line 143) | def class_request_handler(**kwargs):
function reserved_request_handler (line 148) | def reserved_request_handler(**kwargs):
function to_device (line 157) | def to_device(func):
function to_this_endpoint (line 161) | def to_this_endpoint(func):
function to_any_endpoint (line 165) | def to_any_endpoint(func):
function to_this_interface (line 169) | def to_this_interface(func):
function to_any_interface (line 173) | def to_any_interface(func):
function to_other (line 177) | def to_other(func):
function get_request_handler_methods (line 186) | def get_request_handler_methods(cls) -> List[callable]:
class USBControlRequest (line 202) | class USBControlRequest:
method from_raw_bytes (line 222) | def from_raw_bytes(cls, raw_bytes: bytes, *, device = None):
method reply (line 252) | def reply(self, data: bytes):
method acknowledge (line 257) | def acknowledge(self, *, blocking: bool = False):
method ack (line 266) | def ack(self, *, blocking: bool = False):
method stall (line 278) | def stall(self):
method request (line 294) | def request(self) -> int:
method request_type (line 299) | def request_type(self) -> int:
method value_low (line 306) | def value_low(self) -> int:
method value_high (line 310) | def value_high(self) -> int:
method index_low (line 314) | def index_low(self) -> int:
method index_high (line 318) | def index_high(self) -> int:
method get_direction (line 321) | def get_direction(self) -> USBDirection:
method get_type (line 324) | def get_type(self) -> USBRequestType:
method get_recipient (line 327) | def get_recipient(self) -> USBRequestRecipient:
method raw (line 331) | def raw(self) -> bytes:
method __str__ (line 345) | def __str__(self):
class USBRequestHandler (line 364) | class USBRequestHandler(metaclass=ABCMeta):
method _request_handlers (line 369) | def _request_handlers(self) -> Iterable[callable]:
method _get_subordinate_handlers (line 373) | def _get_subordinate_handlers(self) -> Iterable[callable]:
method _call_subordinate_handlers (line 381) | def _call_subordinate_handlers(self, request: USBControlRequest) -> bool:
method handle_request (line 400) | def handle_request(self, request: USBControlRequest) -> bool:
FILE: facedancer/types.py
class USBDirection (line 8) | class USBDirection(IntEnum):
method is_in (line 13) | def is_in(self):
method is_out (line 16) | def is_out(self):
method parse (line 20) | def parse(cls, value):
method from_request_type (line 25) | def from_request_type(cls, request_type_int):
method from_endpoint_address (line 30) | def from_endpoint_address(cls, address):
method token (line 34) | def token(self):
method reverse (line 38) | def reverse(self):
method to_endpoint_address (line 43) | def to_endpoint_address(self, endpoint_number):
class USBPIDCategory (line 51) | class USBPIDCategory(IntFlag):
class USBPacketID (line 63) | class USBPacketID(IntFlag):
method from_byte (line 96) | def from_byte(cls, byte, skip_checks=False):
method from_int (line 105) | def from_int(cls, value, skip_checks=True):
method from_name (line 124) | def from_name(cls, name):
method parse (line 130) | def parse(cls, value):
method category (line 145) | def category(self):
method is_data (line 150) | def is_data(self):
method is_token (line 155) | def is_token(self):
method is_handshake (line 160) | def is_handshake(self):
method is_invalid (line 165) | def is_invalid(self):
method direction (line 169) | def direction(self):
method summarize (line 185) | def summarize(self):
class USBRequestRecipient (line 198) | class USBRequestRecipient(IntEnum):
method from_integer (line 209) | def from_integer(cls, value):
method from_request_type (line 221) | def from_request_type(cls, request_type_int):
class USBRequestType (line 228) | class USBRequestType(IntEnum):
method from_request_type (line 238) | def from_request_type(cls, request_type_int):
class USBTransferType (line 248) | class USBTransferType(IntEnum):
function endpoint_number_from_address (line 255) | def endpoint_number_from_address(number):
class LanguageIDs (line 258) | class LanguageIDs(IntEnum):
class DescriptorTypes (line 409) | class DescriptorTypes(IntEnum):
class USBSynchronizationType (line 422) | class USBSynchronizationType(IntEnum):
class USBUsageType (line 429) | class USBUsageType(IntEnum):
class USBStandardRequests (line 435) | class USBStandardRequests(IntEnum):
class DeviceSpeed (line 452) | class DeviceSpeed(IntEnum):
class USB (line 470) | class USB:
method interface_class_to_descriptor_type (line 512) | def interface_class_to_descriptor_type(interface_class):
FILE: test/base.py
class FacedancerTestCase (line 27) | class FacedancerTestCase(unittest.TestCase):
method setUpClass (line 32) | def setUpClass(cls):
method tearDownClass (line 42) | def tearDownClass(cls):
method bulk_out_transfer (line 49) | def bulk_out_transfer(self, ep, data):
method bulk_in_transfer (line 59) | def bulk_in_transfer(self, ep, length):
method control_out_transfer (line 69) | def control_out_transfer(self, data):
method control_in_transfer (line 83) | def control_in_transfer(self, length):
method set_in_transfer_length (line 99) | def set_in_transfer_length(self, length):
method get_last_out_transfer_data (line 112) | def get_last_out_transfer_data(self):
method reset_device_state (line 125) | def reset_device_state(self):
method set_interface (line 137) | def set_interface(self, interface_number, alternate):
method get_interface (line 141) | def get_interface(self, interface_number):
FILE: test/device.py
class StressTestDevice (line 19) | class StressTestDevice(USBDevice):
method __post_init__ (line 26) | def __post_init__(self):
class MyConfiguration (line 31) | class MyConfiguration(USBConfiguration):
class MyInterface (line 33) | class MyInterface(USBInterface):
class MyOutEndpoint (line 37) | class MyOutEndpoint(USBEndpoint):
method handle_data_received (line 42) | def handle_data_received(self: USBEndpoint, data):
class MyInEndpoint (line 46) | class MyInEndpoint(USBEndpoint):
method handle_data_requested (line 51) | def handle_data_requested(self: USBEndpoint):
class MyAlternateInterface (line 56) | class MyAlternateInterface(USBInterface):
class MyOutEndpoint (line 60) | class MyOutEndpoint(USBEndpoint):
method handle_data_received (line 65) | def handle_data_received(self: USBEndpoint, data):
class MyInEndpoint (line 69) | class MyInEndpoint(USBEndpoint):
method handle_data_requested (line 74) | def handle_data_requested(self: USBEndpoint):
method out_vendor_request (line 81) | def out_vendor_request(self: USBDevice, request: USBControlRequest):
method in_vendor_request (line 89) | def in_vendor_request(self: USBDevice, request: USBControlRequest):
method set_in_transfer_length (line 99) | def set_in_transfer_length(self: USBDevice, request: USBControlRequest):
method get_last_out_transfer_data (line 107) | def get_last_out_transfer_data(self: USBDevice, request: USBControlReq...
method reset_device_state (line 114) | def reset_device_state(self: USBDevice, request: USBControlRequest):
function generate_data (line 123) | def generate_data(length):
FILE: test/test_alternate.py
class TestAlternate (line 14) | class TestAlternate(FacedancerTestCase):
method setUp (line 18) | def setUp(self):
method test_alternate_interfaces (line 23) | def test_alternate_interfaces(self):
FILE: test/test_descriptors.py
class TestDescriptors (line 71) | class TestDescriptors(unittest.TestCase):
method test_device_descriptor_reconstruction (line 73) | def test_device_descriptor_reconstruction(self):
method test_config_descriptor_reconstruction (line 78) | def test_config_descriptor_reconstruction(self):
method test_code_generation (line 86) | def test_code_generation(self):
FILE: test/test_stress.py
function test_transfer_length (line 17) | def test_transfer_length():
class TestStress (line 21) | class TestStress(FacedancerTestCase):
method setUp (line 26) | def setUp(self):
method test_stress_test (line 33) | def test_stress_test(self):
function highly_stressed_edition (line 74) | def highly_stressed_edition():
FILE: test/test_transfers.py
function test_transfer_length (line 15) | def test_transfer_length():
class TestTransfers (line 26) | class TestTransfers(FacedancerTestCase):
method setUp (line 31) | def setUp(self):
method check_out_transfer (line 41) | def check_out_transfer(self, length, sent_data, bytes_sent):
method check_in_transfer (line 55) | def check_in_transfer(self, length, received_data):
method test_bulk_out_transfer (line 68) | def test_bulk_out_transfer(self):
method test_bulk_in_transfer (line 80) | def test_bulk_in_transfer(self):
method test_control_out_transfer (line 92) | def test_control_out_transfer(self):
method test_control_in_transfer (line 104) | def test_control_in_transfer(self):
Condensed preview — 77 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (534K chars).
[
{
"path": ".editorconfig",
"chars": 296,
"preview": "# top-most EditorConfig file\nroot = true\n\n# Set our default format parameters.\n[*]\ncharset = utf-8\nindent_size = 4\nend_o"
},
{
"path": ".gitignore",
"chars": 1144,
"preview": "# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packagi"
},
{
"path": ".readthedocs.yaml",
"chars": 462,
"preview": "# .readthedocs.yaml\n# Read the Docs configuration file\n# See https://docs.readthedocs.io/en/stable/config-file/v2.html f"
},
{
"path": "CHANGELOG.md",
"chars": 7357,
"preview": "# Changelog\n\nAll notable changes to this project will be documented in this file.\n\nThe format is based on [Keep a Change"
},
{
"path": "CODE_OF_CONDUCT.md",
"chars": 5232,
"preview": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nWe as members, contributors, and leaders pledge to make participa"
},
{
"path": "LICENSE",
"chars": 1604,
"preview": "Copyright (c) 2019 Katherine J. Temkin <k@ktemkin.com>\nCopyright (c) 2018 Dominic Spill <dominicgs@gmail.com>\nCopyright "
},
{
"path": "README.md",
"chars": 6492,
"preview": "# Facedancer 3.0\n\nThis repository houses the next generation of Facedancer software. Descended from\nthe original GoodFET"
},
{
"path": "Release.make",
"chars": 1726,
"preview": "#\n# This file is part of Facedancer.\n# Maintainer quick actions for generating releases.\n#\n\n\n# By default, use the syste"
},
{
"path": "docs/Makefile",
"chars": 579,
"preview": "# Minimal makefile for Sphinx documentation\n#\n\n# You can set these variables from the command line.\nSPHINXOPTS\t =\nSPHIN"
},
{
"path": "docs/make.bat",
"chars": 756,
"preview": "@ECHO OFF\n\npushd %~dp0\n\nREM Command file for Sphinx documentation\n\nif \"%SPHINXBUILD%\" == \"\" (\n\tset SPHINXBUILD=sphinx-bu"
},
{
"path": "docs/requirements.txt",
"chars": 210,
"preview": "setuptools\nsphinx==7.2.6\nsphinx_rtd_theme==2.0.0\nsphinxcontrib-apidoc\nreadthedocs-sphinx-search==0.3.2\njinja2==3.1.6\n\n# "
},
{
"path": "docs/source/conf.py",
"chars": 1780,
"preview": "import os, pkg_resources, sys, time\nsys.path.insert(0, os.path.abspath(\"../../\"))\nsys.path.insert(0, os.path.abspath('.."
},
{
"path": "docs/source/facedancer_examples.rst",
"chars": 2050,
"preview": "===================\nFacedancer Examples\n===================\n\n.. warning::\n\n Facedancer and GreatFET are not currently "
},
{
"path": "docs/source/getting_started.rst",
"chars": 2149,
"preview": "================================================\nGetting started with Facedancer\n======================================="
},
{
"path": "docs/source/howto_facedancer_backend.rst",
"chars": 2942,
"preview": "=====================================\nHow to write a new Facedancer Backend\n=====================================\n\nFaced"
},
{
"path": "docs/source/index.rst",
"chars": 439,
"preview": "========================\nFacedancer Documentation\n========================\n\n.. toctree::\n :maxdepth: 2\n :caption: User"
},
{
"path": "docs/source/library_overview.rst",
"chars": 3399,
"preview": "================================================\nLibrary Overview\n================================================\n\nThe "
},
{
"path": "docs/source/using_facedancer.rst",
"chars": 9832,
"preview": "================================================\nUsing Facedancer\n================================================\n\nIntr"
},
{
"path": "docs/source/using_usb_proxy.rst",
"chars": 4503,
"preview": "================================================\nUsing USB Proxy\n================================================\n\nIntro"
},
{
"path": "examples/coroutine.py",
"chars": 1808,
"preview": "#!/usr/bin/env python3\n# pylint: disable=unused-wildcard-import, wildcard-import\n#\n# This file is part of Facedancer.\n#\n"
},
{
"path": "examples/ftdi-echo.py",
"chars": 1137,
"preview": "#!/usr/bin/env python3\n#\n# This file is part of Facedancer.\n#\n\n\nimport logging\n\nfrom facedancer import main\nfrom facedan"
},
{
"path": "examples/hackrf-info.py",
"chars": 2872,
"preview": "#!/usr/bin/env python3\n# pylint: disable=unused-wildcard-import, wildcard-import\n#\n# This file is part of Facedancer.\n#\n"
},
{
"path": "examples/imperative.py",
"chars": 1810,
"preview": "#!/usr/bin/env python3\n# pylint: disable=unused-wildcard-import, wildcard-import\n#\n# This file is part of Facedancer.\n#\n"
},
{
"path": "examples/mass-storage.py",
"chars": 953,
"preview": "#!/usr/bin/env python3\n#\n# This file is part of Facedancer.\n#\n\nimport sys\nimport logging\n\nfrom facedancer import main\nfr"
},
{
"path": "examples/minimal.py",
"chars": 1947,
"preview": "#!/usr/bin/env python3\n# pylint: disable=unused-wildcard-import, wildcard-import\n#\n# This file is part of Facedancer.\n#\n"
},
{
"path": "examples/rubber-ducky.py",
"chars": 617,
"preview": "#!/usr/bin/env python3\n#\n# This file is part of Facedancer.\n#\n\"\"\" USB 'Rubber Ducky' example; enters some text via the k"
},
{
"path": "examples/template.py",
"chars": 12131,
"preview": "#!/usr/bin/env python3\n# pylint: disable=unused-wildcard-import, wildcard-import\n#\n# This file is part of Facedancer.\n#\n"
},
{
"path": "examples/test_minimal.py",
"chars": 2089,
"preview": "import logging\n\ndef main():\n import asyncio\n import usb1\n\n VENDOR_REQUEST = 0x65\n MAX_TRANSFER_SIZE = 64\n"
},
{
"path": "examples/usbproxy.py",
"chars": 857,
"preview": "#!/usr/bin/env python3\n#\n# This file is part of Facedancer.\n#\n\"\"\" USB Proxy example; forwards all USB transactions and l"
},
{
"path": "facedancer/__init__.py",
"chars": 1868,
"preview": "# Standard types.\nfrom .device import USBDevice\nfrom .configuration import USBConfiguration\nfrom .interface i"
},
{
"path": "facedancer/backends/MAXUSBApp.py",
"chars": 8408,
"preview": "# MAXUSBApp.py\n#\n# Contains class definition for MAXUSBApp.\n\nimport time\n\nfrom ..core import FacedancerApp\n\nfrom .base "
},
{
"path": "facedancer/backends/__init__.py",
"chars": 154,
"preview": "__all__ = [\n \"goodfet\",\n \"MAXUSBApp\",\n \"greatdancer\",\n \"raspdancer\",\n \"greathost\",\n \"libusbhost\",\n "
},
{
"path": "facedancer/backends/base.py",
"chars": 6974,
"preview": "from typing import List\nfrom .. import *\n\n\nclass FacedancerBackend:\n def __init__(self, device: USBDevice=N"
},
{
"path": "facedancer/backends/goodfet.py",
"chars": 9019,
"preview": "import os\nimport serial\nimport sys\nimport time\n\nfrom ..core import FacedancerApp\nfrom ..backends.MAXUSBApp"
},
{
"path": "facedancer/backends/greatdancer.py",
"chars": 27881,
"preview": "# GreatDancerApp.py\n\nimport sys\nimport time\nimport codecs\nimport traceback\n\nfrom ..core import *\nfrom ..types imp"
},
{
"path": "facedancer/backends/greathost.py",
"chars": 15016,
"preview": "#\n# This file is part of Facedancer.\n#\n\"\"\" Host support for GreatFET-base devices. \"\"\"\n\nimport sys\nimport time\nimport co"
},
{
"path": "facedancer/backends/hydradancer.py",
"chars": 33937,
"preview": "\"\"\"\nBackend for the Hydradancer boards.\n\nSupports 5 endpoints, with addresses between 0 and 7. Supports low, full and hi"
},
{
"path": "facedancer/backends/libusbhost.py",
"chars": 9522,
"preview": "#\n# This file is part of Facedancer.\n#\n\"\"\" Host support for accessing libusb with a Facedancer-like syntax. \"\"\"\n\nimport "
},
{
"path": "facedancer/backends/moondancer.py",
"chars": 23613,
"preview": "# MoondancerApp.py\n\nimport sys\nimport time\nimport codecs\nimport enum\nimport traceback\n\nfrom typing import List"
},
{
"path": "facedancer/backends/raspdancer.py",
"chars": 5216,
"preview": "# pylint: disable=import-error\n\n#\n# Raspdancer\n#\n# Implementation of the Facedancer API that supports direct access to t"
},
{
"path": "facedancer/classes/__init__.py",
"chars": 896,
"preview": "#\n# This file is part of Facedancer.\n#\n\"\"\" Support code for USB classes. \"\"\"\n\nfrom enum import IntEnum\n\n\nclass USBDevice"
},
{
"path": "facedancer/classes/hid/__init__.py",
"chars": 82,
"preview": "#\n# This file is part of Facedancer.\n#\n\"\"\" Code for implementing HID classes. \"\"\"\n"
},
{
"path": "facedancer/classes/hid/descriptor.py",
"chars": 4868,
"preview": "#\n# This file is part of Facedancer.\n#\n\"\"\" Code for implementing HID classes. \"\"\"\n\n# Support annotations on Python < 3.9"
},
{
"path": "facedancer/classes/hid/keyboard.py",
"chars": 12927,
"preview": "#\n# This file is part of Facedancer.\n#\n\"\"\" Helpers for HID keyboards. \"\"\"\n\nimport string\nfrom enum import IntEnum, IntFl"
},
{
"path": "facedancer/classes/hid/usage.py",
"chars": 3682,
"preview": "#\n# This file is part of Facedancer.\n#\n\"\"\" Code for working with HID usages. \"\"\"\n\n\nfrom enum import IntEnum\n\n\nclass HIDU"
},
{
"path": "facedancer/configuration.py",
"chars": 10680,
"preview": "#\n# This file is part of Facedancer.\n#\n\"\"\" Functionality for describing USB device configurations. \"\"\"\n\nimport struct\nim"
},
{
"path": "facedancer/core.py",
"chars": 18967,
"preview": "# Facedancer.py\n#\n# Contains the core methods for working with a facedancer, inclduing methods\n# necessary for autodetec"
},
{
"path": "facedancer/descriptor.py",
"chars": 9671,
"preview": "#\n# This file is part of Facedancer.\n#\n\"\"\" Functionality for working with objects with associated USB descriptors. \"\"\"\n\n"
},
{
"path": "facedancer/device.py",
"chars": 38984,
"preview": "#\n# This file is part of Facedancer.\n#\n\"\"\" Functionality for defining USB devices. \"\"\"\n\n# Support annotations on Python "
},
{
"path": "facedancer/devices/__init__.py",
"chars": 1598,
"preview": "#\n# This file is part of Facedancer.\n#\n\nimport sys\nimport pprint\nimport asyncio\nimport inspect\nimport argparse\n\nfrom ..e"
},
{
"path": "facedancer/devices/ftdi.py",
"chars": 10684,
"preview": "# pylint: disable=unused-wildcard-import, wildcard-import\n#\n# This file is part of Facedancer.\n#\n\"\"\" Emulation of an FTD"
},
{
"path": "facedancer/devices/keyboard.py",
"chars": 9274,
"preview": "# pylint: disable=unused-wildcard-import, wildcard-import\n#\n# This file is part of Facedancer.\n#\n\nimport asyncio\n\nfrom t"
},
{
"path": "facedancer/devices/umass/__init__.py",
"chars": 54,
"preview": "from .umass import *\nfrom .disk_image import *\n"
},
{
"path": "facedancer/devices/umass/disk_image.py",
"chars": 18487,
"preview": "from mmap import mmap\n\nimport os\n\n\nclass DiskImage:\n \"\"\"\n Class representing an arbitrary disk image, which ca"
},
{
"path": "facedancer/devices/umass/umass.py",
"chars": 17955,
"preview": "# USBMassStorage.py\n#\n# Contains class definitions to implement a USB mass storage device.\n#\n\"\"\" Emulation of a USB Mass"
},
{
"path": "facedancer/endpoint.py",
"chars": 10396,
"preview": "#\n# This file is part of Facedancer\n#\n\"\"\" Functionality for describing USB endpoints. \"\"\"\n\n# Support annotations on Pyth"
},
{
"path": "facedancer/errors.py",
"chars": 261,
"preview": "#\n# This file is part of Facedancer.\n#\n\nclass DeviceNotFoundError(IOError):\n \"\"\" Error indicating a device was not fo"
},
{
"path": "facedancer/filters/__init__.py",
"chars": 131,
"preview": "from .base import USBProxyFilter\nfrom .logging import USBProxyPrettyPrintFilter\nfrom .standard import USBProxySe"
},
{
"path": "facedancer/filters/base.py",
"chars": 4873,
"preview": "#\n# This file is part of Facedancer.\n#\n\n\nclass USBProxyFilter:\n \"\"\"\n Base class for filters that modify USB data.\n"
},
{
"path": "facedancer/filters/hid.py",
"chars": 4735,
"preview": "#\n# USBProxy HID logging\n#\n\nfrom warnings import filterwarnings\nimport hid_parser\nfrom hid_parser import HIDComplianceWa"
},
{
"path": "facedancer/filters/logging.py",
"chars": 3511,
"preview": "#\n# USBProxy logging filters\n#\n\nimport datetime\n\nfrom ..logging import log\n\nfrom . import USBProxyFilter\n\n\nclass"
},
{
"path": "facedancer/filters/standard.py",
"chars": 4183,
"preview": "#\n# This file is part of Facedancer.\n#\n\"\"\" Standard filters for USBProxy that should (almost) always be used. \"\"\"\n\nfrom "
},
{
"path": "facedancer/interface.py",
"chars": 14442,
"preview": "#\n# This file is part of facedancer.\n#\n\"\"\" Functionality for defining USB interfaces. \"\"\"\n\n# Support annotations on Pyth"
},
{
"path": "facedancer/logging.py",
"chars": 984,
"preview": "import functools\nimport logging\nimport sys\n\n\nLOGLEVEL_TRACE = 5\n\nLOG_FORMAT_COLOR = \"\\u001b[37;1m%(levelname)-8s| \\u001b"
},
{
"path": "facedancer/magic.py",
"chars": 4823,
"preview": "#\n# This file is part of Facedancer.\n#\n\"\"\" Functionally for automatic instantiations / tracking via decorators. \"\"\"\n\nimp"
},
{
"path": "facedancer/proxy.py",
"chars": 16892,
"preview": "#\n# This file is part of Facedancer.\n#\n\"\"\" USB Proxy implementation. \"\"\"\n\nimport atexit\nimport platform\nimport usb1\nimpo"
},
{
"path": "facedancer/request.py",
"chars": 14392,
"preview": "#\n# This file is part of Facedancer.\n#\n\"\"\" Functionality for declaring and working with USB control requests. \"\"\"\n\nimpor"
},
{
"path": "facedancer/types.py",
"chars": 15639,
"preview": "#\n# This file is part of Facedancer.\n#\n\"\"\" USB types -- defines enumerations that describe standard USB types \"\"\"\n\nfrom "
},
{
"path": "pyproject.toml",
"chars": 1461,
"preview": "[build-system]\nrequires = [\"setuptools>=64\", \"wheel\", \"setuptools-git-versioning<2\"]\nbuild-backend = \"setuptools.build_m"
},
{
"path": "test/README.md",
"chars": 239,
"preview": "## Running tests\n\n1. Connect your Facedancer hardware\n\n2. In one terminal, start the test device from the repository roo"
},
{
"path": "test/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "test/base.py",
"chars": 5024,
"preview": "#\n# This file is part of Facedancer.\n#\n\nimport logging\nimport unittest\nimport usb1\n\nfrom facedancer.errors import Device"
},
{
"path": "test/device.py",
"chars": 5401,
"preview": "#!/usr/bin/env python3\n# pylint: disable=unused-wildcard-import, wildcard-import\n#\n# This file is part of Facedancer.\n#\n"
},
{
"path": "test/test_alternate.py",
"chars": 2187,
"preview": "#\n# This file is part of Facedancer.\n#\n\nimport asyncio, logging, random, sys, time\nimport unittest\nimport usb1\n\nfrom .ba"
},
{
"path": "test/test_descriptors.py",
"chars": 14930,
"preview": "from facedancer import *\nimport unittest\n\n# Test case similar to a game pad seen in the wild.\n\ndevice_data = bytes([\n "
},
{
"path": "test/test_stress.py",
"chars": 3001,
"preview": "#\n# This file is part of Facedancer.\n#\n\nimport asyncio, logging, random, sys, time\nimport unittest\nimport usb1\n\nfrom .ba"
},
{
"path": "test/test_transfers.py",
"chars": 3476,
"preview": "#\n# This file is part of Facedancer.\n#\n\nimport asyncio, logging, random, sys, time\nimport unittest\nimport usb1\n\nfrom .ba"
}
]
About this extraction
This page contains the full source code of the greatscottgadgets/Facedancer GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 77 files (498.6 KB), approximately 118.5k tokens, and a symbol index with 763 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.