[
  {
    "path": ".editorconfig",
    "content": "# top-most EditorConfig file\nroot = true\n\n# Set our default format parameters.\n[*]\ncharset = utf-8\nindent_size = 4\nend_of_line = lf\ninsert_final_newline = true\ntrim_trailing_whitespace = true\nmax_line_length = 120\n\n# Use python standard indentation for python files.\n[*.py]\nindent_style = space\n\n"
  },
  {
    "path": ".gitignore",
    "content": "# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packaging\n.Python\nenv/\nbuild/\ndevelop-eggs/\ndist/\ndownloads/\neggs/\n.eggs/\nlib/\nlib64/\nparts/\nsdist/\nvar/\nwheels/\n*.egg-info/\n.installed.cfg\n*.egg\n\n# PyInstaller\n#  Usually these files are written by a python script from a template\n#  before PyInstaller builds the exe, so as to inject date/other infos into it.\n*.manifest\n*.spec\n\n# Installer logs\npip-log.txt\npip-delete-this-directory.txt\n\n# Unit test / coverage reports\nhtmlcov/\n.tox/\n.coverage\n.coverage.*\n.cache\nnosetests.xml\ncoverage.xml\n*,cover\n.hypothesis/\n\n# Translations\n*.mo\n*.pot\n\n# Django stuff:\n*.log\nlocal_settings.py\n\n# Flask stuff:\ninstance/\n.webassets-cache\n\n# Scrapy stuff:\n.scrapy\n\n# Sphinx documentation\ndocs/build/\n\n# PyBuilder\ntarget/\n\n# Jupyter Notebook\n.ipynb_checkpoints\n\n# pyenv\n.python-version\n\n# celery beat schedule file\ncelerybeat-schedule\n\n# dotenv\n.env\n\n# virtualenv\n.venv/\nvenv/\nENV/\n\n# Spyder project settings\n.spyderproject\n\n# Rope project settings\n.ropeproject\ntags\n\n# IntelliJ Project\n.idea/\n\n# Release files\nVERSION\nrelease-files\nhost-packages\n"
  },
  {
    "path": ".readthedocs.yaml",
    "content": "# .readthedocs.yaml\n# Read the Docs configuration file\n# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details\n\n# Required\nversion: 2\n\n# Set the OS, Python version and other tools \nbuild:\n  os: ubuntu-22.04\n  tools:\n    python: \"3.12\"\n\n# Build documentation in the \"docs/\" directory with Sphinx\nsphinx:\n   configuration: docs/source/conf.py\n\n# Build PDF for docs\nformats:\n  - pdf\n\npython:\n  install:\n    - requirements: docs/requirements.txt\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Changelog\n\nAll notable changes to this project will be documented in this file.\n\nThe format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),\nand this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).\n\n<!--\n## [Unreleased]\n-->\n\n## [3.1.2] - 2025-12-05\n### Fixed\n* FTDI emulation stopped working with recent Windows releases. (tx @gniezen!)\n### Added\n* Added a usbproxy filter for logging high-level HID requests. (tx @akvadrako!)\n### Security\n* Updated `jinja2` from 3.1.5 to 3.1.6.\n\n## [3.1.1] - 2025-08-01\n### Added\n* Hydradancer: Handle `clear_halt` (tx @kauwua!)\n* Add `parent` field to all descriptors. (tx @kauwua!)\n* Extend mass storage device constructor to be more configurable. (tx @gniezen!)\n### Fixed\n* Mass storage device was unable to use custom descriptors. (tx @gniezen!)\n\n\n## [3.1.0] - 2025-01-08\n> This is a breaking release which may require updates to your usage of Facedancer API's.\n\n### Changed\n* Dropped support for Python 3.8 and 3.9. The minimum supported Python version is now Python 3.10.\n* The descriptor API has been changed and expanded to handle more complex device definitions:\n  - New USBDescriptor property: `include_in_config`, which specifies whether the descriptor should be included in a GET_CONFIGURATION response.\n  - Descriptors attached to endpoints are now instantiated (replaces #139)\n  - The `instantiate_subordinates` function is redesigned to avoid silent dropping of subordinates with duplicate identifiers.\n  - Orderings of declaration/insertion of subordinates are preserved, allowing control of ordering in binary configurations.\n  - Fixes to convert some fields to the right types in `from_binary_descriptor` methods.\n  - A dictionary of known strings and their indexes may be passed to `from_binary_descriptor` methods.\n  - The `number` field of `USBDescriptor` is made optional, as it is not required for descriptors attached in a configuration.\n  - The `type_number` field will now be inferred from the `raw` bytes if not otherwise specified.\n  - Add `@include_in_config` and `@requestable(number=N)` decorators for use on declared descriptor classes.\n  - Add docstrings for all `USBDescriptor` fields.\n  - More information: #126 #141\n\n### Fixed\n* USBProxy errors after changes for alternate interface settings.\n\n### Added\n* 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.\n* New backend method: `validate_configuration` for rejecting USB device configurations that are not supported by a given backend.\n\n\n## [3.0.6] - 2024-11-27\n### Fixed\n* Updated Keyboard device / rubber-ducky to work with new descriptor handling features.\n\n## [3.0.5] - 2024-11-25\n### Added\n* Support switching between alternate interface settings.\n* Improved Facedancer descriptor functionality.\n* Log a warning when Moondancer needs system permissions for the interface.\n* Group Facedancer request handler suggestions by their recipients.\n* Implement the `raw` field in `HIDReportDescriptor`. (tx @jalmeroth!)\n### Fixed\n* Moondancer: Only prime control endpoints on receipt of a setup packet.\n* Moondancer: Use `ep_out_interface_enable` instead of `ep_out_prime_endpoint` where appropriate.\n\n## [3.0.4] - 2024-10-10\n### Added\n* Example: `examples/coroutine.py` demonstrates how to create a custom main function and the use of coroutines.\n* Keyboard shortcut: `Ctrl-C` will now gracefully exit a Facedancer emulation.\n\n## [3.0.3] - 2024-09-19\n### Added\n* Support for specifying string descriptor indices.\n* Allow `supported_languages = None` for device definitions.\n* Provide an error message when device claim/release fails.\n* New backend method: `clear_halt()`\n* New backend method: `send_on_control_endpoint()`\n* [HydraDancer](https://github.com/HydraDancer) backend. (tx @kauwua!)\n### Fixed\n* Correct byteorder for bcdUSB and bcdDevice.\n* Older facedancer backends were not derived from `FacedancerBackend`.\n* Log message in `handle_set_interface_request` was using the incorrect logging method. (tx @kawua!)\n\n\n## [3.0.2] - 2024-08-20\n### Changed\n* Added support for Cynthion on Windows.\n* Update docs to reflect current status of GreatFET support on Windows.\n\n## [3.0.1] - 2024-08-19\n### Changed\n* USBProxy now auto-detaches kernel drivers for the device being proxied.\n* Updated documentation with current status of Facedancer support on Windows.\n\n### Fixed\n* Clarify the explanatory text for endpoint numbers in the app template. (tx @salcho!)\n* Shutting down Facedancer proxy devices could result in a `LIBUSB_ERROR_BUSY` (tx @mipek!)\n* Facedancer devices would be incorrectly identified as `goodfet` when `/dev/ttyUSB0` exists on the host device.\n* Fixed ambiguous documentation terminology to always use one of \"Target Host\", \"Control Host\".\n\n\n## [3.0.0] - 2024-06-18\n### Added\n- Facedancer documentation has been updated and can be found at: [https://facedancer.readthedocs.io](https://facedancer.readthedocs.io)\n- A new backend has been added for the Great Scott Gadgets Cynthion.\n- Emulations can now set USB device speed on supported boards.\n\n### Changed\n- The Facedancer core API has been rewritten. See the Facedancer documentation for details.\n- Some legacy applets have been replaced with new examples based on the modern Facedancer core:\n  - `facedancer-ftdi.py` => `ftdi-echo.py`\n  - `facedancer-keyboard.py` => `rubber-ducky.py`\n  - `facedancer-umass.py`    => `mass-storage.py`\n\n### Fixed\n- 64bit LBA support has been added to the `mass-storage.py` example. (Tx @shutingrz!)\n\n### Removed\n- 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`.\n- All legacy applets not ported to the modern Facedancer core have been removed.\n\n\n## [2.9.0] - 2024-02-09\n\nThis 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.\n\nAny 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.\n\n### Deprecated\n- The current Facedancer core will be supersed by the implementation in `future/` with the `v3.0` release.\n\n\n[Unreleased]: https://github.com/greatscottgadgets/facedancer/compare/3.1.2...HEAD\n[3.1.2]: https://github.com/greatscottgadgets/facedancer/compare/3.1.1...3.1.2\n[3.1.1]: https://github.com/greatscottgadgets/facedancer/compare/3.1.0...3.1.1\n[3.1.0]: https://github.com/greatscottgadgets/facedancer/compare/3.0.6...3.1.0\n[3.0.6]: https://github.com/greatscottgadgets/facedancer/compare/3.0.5...3.0.6\n[3.0.5]: https://github.com/greatscottgadgets/facedancer/compare/3.0.4...3.0.5\n[3.0.4]: https://github.com/greatscottgadgets/facedancer/compare/3.0.3...3.0.4\n[3.0.3]: https://github.com/greatscottgadgets/facedancer/compare/3.0.2...3.0.3\n[3.0.2]: https://github.com/greatscottgadgets/facedancer/compare/3.0.1...3.0.2\n[3.0.1]: https://github.com/greatscottgadgets/facedancer/compare/3.0.0...3.0.1\n[3.0.0]: https://github.com/greatscottgadgets/facedancer/compare/2.9.0...3.0.0\n[2.9.0]: https://github.com/greatscottgadgets/facedancer/releases/tag/2.9.0\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nWe as members, contributors, and leaders pledge to make participation in our\ncommunity a harassment-free experience for everyone, regardless of age, body\nsize, visible or invisible disability, ethnicity, sex characteristics, gender\nidentity and expression, level of experience, education, socio-economic status,\nnationality, personal appearance, race, religion, or sexual identity\nand orientation.\n\nWe pledge to act and interact in ways that contribute to an open, welcoming,\ndiverse, inclusive, and healthy community.\n\n## Our Standards\n\nExamples of behavior that contributes to a positive environment for our\ncommunity include:\n\n* Demonstrating empathy and kindness toward other people\n* Being respectful of differing opinions, viewpoints, and experiences\n* Giving and gracefully accepting constructive feedback\n* Accepting responsibility and apologizing to those affected by our mistakes,\n  and learning from the experience\n* Focusing on what is best not just for us as individuals, but for the\n  overall community\n\nExamples of unacceptable behavior include:\n\n* The use of sexualized language or imagery, and sexual attention or\n  advances of any kind\n* Trolling, insulting or derogatory comments, and personal or political attacks\n* Public or private harassment\n* Publishing others' private information, such as a physical or email\n  address, without their explicit permission\n* Other conduct which could reasonably be considered inappropriate in a\n  professional setting\n\n## Enforcement Responsibilities\n\nCommunity leaders are responsible for clarifying and enforcing our standards of\nacceptable behavior and will take appropriate and fair corrective action in\nresponse to any behavior that they deem inappropriate, threatening, offensive,\nor harmful.\n\nCommunity leaders have the right and responsibility to remove, edit, or reject\ncomments, commits, code, wiki edits, issues, and other contributions that are\nnot aligned to this Code of Conduct, and will communicate reasons for moderation\ndecisions when appropriate.\n\n## Scope\n\nThis Code of Conduct applies within all community spaces, and also applies when\nan individual is officially representing the community in public spaces.\nExamples of representing our community include using an official e-mail address,\nposting via an official social media account, or acting as an appointed\nrepresentative at an online or offline event.\n\n## Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be\nreported to the community leaders responsible for enforcement at\nstraithe@greatscottgadgets.com.\nAll complaints will be reviewed and investigated promptly and fairly.\n\nAll community leaders are obligated to respect the privacy and security of the\nreporter of any incident.\n\n## Enforcement Guidelines\n\nCommunity leaders will follow these Community Impact Guidelines in determining\nthe consequences for any action they deem in violation of this Code of Conduct:\n\n### 1. Correction\n\n**Community Impact**: Use of inappropriate language or other behavior deemed\nunprofessional or unwelcome in the community.\n\n**Consequence**: A private, written warning from community leaders, providing\nclarity around the nature of the violation and an explanation of why the\nbehavior was inappropriate. A public apology may be requested.\n\n### 2. Warning\n\n**Community Impact**: A violation through a single incident or series\nof actions.\n\n**Consequence**: A warning with consequences for continued behavior. No\ninteraction with the people involved, including unsolicited interaction with\nthose enforcing the Code of Conduct, for a specified period of time. This\nincludes avoiding interactions in community spaces as well as external channels\nlike social media. Violating these terms may lead to a temporary or\npermanent ban.\n\n### 3. Temporary Ban\n\n**Community Impact**: A serious violation of community standards, including\nsustained inappropriate behavior.\n\n**Consequence**: A temporary ban from any sort of interaction or public\ncommunication with the community for a specified period of time. No public or\nprivate interaction with the people involved, including unsolicited interaction\nwith those enforcing the Code of Conduct, is allowed during this period.\nViolating these terms may lead to a permanent ban.\n\n### 4. Permanent Ban\n\n**Community Impact**: Demonstrating a pattern of violation of community\nstandards, including sustained inappropriate behavior,  harassment of an\nindividual, or aggression toward or disparagement of classes of individuals.\n\n**Consequence**: A permanent ban from any sort of public interaction within\nthe community.\n\n## Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant][homepage],\nversion 2.0, available at\nhttps://www.contributor-covenant.org/version/2/0/code_of_conduct.html.\n\nCommunity Impact Guidelines were inspired by [Mozilla's code of conduct\nenforcement ladder](https://github.com/mozilla/diversity).\n\n[homepage]: https://www.contributor-covenant.org\n\nFor answers to common questions about this code of conduct, see the FAQ at\nhttps://www.contributor-covenant.org/faq. Translations are available at\nhttps://www.contributor-covenant.org/translations.\n"
  },
  {
    "path": "LICENSE",
    "content": "Copyright (c) 2019 Katherine J. Temkin <k@ktemkin.com>\nCopyright (c) 2018 Dominic Spill <dominicgs@gmail.com>\nCopyright (c) 2018 Travis Goodspeed <travis@radiantmachines.com>\n\nRedistribution and use in source and binary forms, with or without modification,\nare permitted provided that the following conditions are met:\n\n1. Redistributions of source code must retain the above copyright notice, this\nlist of conditions and the following disclaimer.\n\n2. Redistributions in binary form must reproduce the above copyright notice,\nthis list of conditions and the following disclaimer in the documentation and/or\nother materials provided with the distribution.\n\n3. Neither the name of the copyright holder nor the names of its contributors\nmay be used to endorse or promote products derived from this software without\nspecific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\nANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\nWARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR\nANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\nLOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON\nANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\nSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
  },
  {
    "path": "README.md",
    "content": "# Facedancer 3.0\n\nThis repository houses the next generation of Facedancer software. Descended from\nthe original GoodFET-based Facedancer, this repository provides a python module\nthat provides expanded Facedancer support -- including support for multiple boards\nand some pretty significant new features.\n\n## Project Documentation\n\nFacedancer's documentation is captured on [Read the Docs](https://facedancer.readthedocs.io/en/latest/). Raw documentation sources are in the [`docs/`](docs/) folder.\n\n\n## Installation\n\nInstall this package with the following command:\n\n    pip install facedancer\n\nAfter that you can import the facedancer package as usual:\n\n    $ python\n    >>> import facedancer\n\n\n## Where are my scripts?\n\nFacedancer 3.0 is a ground-up rewrite of the original emulation core\nand does not support legacy scripts.\n\nIf you're using scripts or training materials that depend on features\nor APIs deprecated in `v3.0.x` you can install the latest `v2.9.x`\nrelease of Facedancer with:\n\n    pip install \"facedancer<=3\"\n\nLegacy applets and examples can be found in the [`v2.9.x`](https://github.com/greatscottgadgets/facedancer/tree/v2.9.x)\nbranch.\n\n\n## What is a Facedancer?\n\nFacedancer boards are simple hardware devices that act as \"remote-controlled\" USB\ncontrollers. With the proper software, you can use these boards to quickly and\neasily emulate USB devices -- and to fuzz USB host controllers!\n\nThis particular software repository currently allows you to easily create emulations\nof USB devices in Python. Control is fine-grained enough that you can cause all\nkinds of USB misbehaviors. :)\n\nFor more information, see:\n\n * [Travis Goodspeed's blog post on Facedancer](http://travisgoodspeed.blogspot.com/2012/07/emulating-usb-devices-with-python.html)\n * [The Facedancer 21, the original supported board](http://goodfet.sourceforge.net/hardware/facedancer21/)\n\n## USBProxy 'Nouveau' and Protocol Analysis\n\nA 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)\nproject. This opens up a whole new realm of applications -- including protocol analysis\nand live manipulation of USB packets -- and is especially useful when you don't control\nthe software running on the target device (e.g. on embedded systems or games consoles).\n\n```\n                 +-----------------------------------------------------------------------+\n+------------+   |  +--------------------------------+   +---------------------------+   |  +--------------+\n|            |   |  |                                |   |                           |   |  |              |\n|  PROXIED   |   |  |         HOST COMPUTER          |   |    FACEDANCER DEVICE      |   |  |  TARGET USB  |\n|   DEVICE   <------>  running Facedancer software   <--->  acts as USB-Controlled   <------>     HOST     |\n|            |   |  |                                |   |      USB Controller       |   |  |              |\n|            |   |  |                                |   |                           |   |  |              |\n+------------+   |  +--------------------------------+   +---------------------------+   |  +--------------+\n                 |                                                                       |\n                 |                    MITM Setup (HOST + FACEDANCER)                     |\n                 +-----------------------------------------------------------------------+\n```\n\n\nThis feature is complete, but could use more documentation. Pull requests are welcome. :)\n\n\n## How do I use this repository?\n\nFirst, you'll likely want to set the ```BACKEND``` environment variable, which lets\nthe software know which type of Facedancer board you'd like to use. If this variable\nisn't set, the software will try to guess for you based on what's connected. It doesn't\nalways make the best guesses, so you're probably better off setting it yourself.\n\nNext, you'll probably want to check out one of the examples, or one of the pre-made scripts.\nExamples in the new syntax are located under `examples`. The core Facedancer scripts in the\n\"old\" syntax are located in `legacy-applets`.\n\nFor example:\n\n```sh\nexport BACKEND=greatfet\n./examples/rubber-ducky.py\n```\n\n## What boards are currently supported?\n\n * The [Cynthion USB Test Instrument](http://greatscottgadgets.com/cynthion/) (```BACKEND=cynthion```)\n * The [GreatFET One](http://greatscottgadgets.com/greatfet/) (```BACKEND=greatfet```)\n * The NXP LPC4330 Xplorer board. (```BACKEND=greatfet```)\n * The CCCamp 2015 rad1o badge with GreatFET l0adable (```BACKEND=greatfet```)\n * All GoodFET-based Facedancers, including the common Facedancer21 (```BACKEND=goodfet```)\n * RPi + Max3241 Raspdancer boards (```BACKEND=raspdancer```)\n * HydraDancer and HydraUSB3 boards (```BACKEND=hydradancer```)\n\nNote that hardware restrictions prevent the MAX3420/MAX3421 boards from emulating\nmore complex devices -- there's limitation on the number/type of endpoints that can be\nset up. The LPC4330 boards -- such as the GreatFET -- have fewer limitations.\n\nFor a similar reason, the MAX3420/MAX3421 boards (`BACKEND=goodfet` or `BACKEND=raspdancer`)\ncurrently cannot be used as USBProxy-nv MITM devices. All modern boards (`BACKEND=greatfet`, `BACKEND=hydradancer`)\nshould be fully functional.\n\nNote that the HydraDancer and HydraUSB3 boards (`BACKEND=hydradancer`) do not currently support host-mode.\nNote actual FaceDancer 3.0 does not work on Windows(some issues in pyusb...) and only GNU/Linux\n\n## What boards could be supported soon?\n\n * Any Linux computer with gadgetfs support (e.g. the Pi Zero or Beaglebone Black)\n * Anything supporting USB-over-IP.\n\n## What features do you plan on adding?\n\nThe roadmap is under development, but in addition to multi-board support, this repository\nwill eventually be home to some cool new features, including:\n\n * High-speed (\"USB 2.0\") device emulation on devices with USB 2.0 PHYs.\n * On-the-fly generation of USB device controllers in gateware.\n\n## Whose fault _is_ this?\n\nThere are a lot of people to blame for the awesomeness that is this repo,\nincluding:\n\n * Kate Temkin (@ktemkin)\n * Travis Goodspeed (@travisgoodspeed)\n * Sergey Bratus (@sergeybratus)\n * Dominic Spill (@dominicgs)\n * Michael Ossmann (@michaelossmann)\n * Mikaela Szekely (@Qyriad)\n * anyone whose name appears in the git history :)\n\n## Contributions?\n\n... are always welcome. Shoot us a PR!\n"
  },
  {
    "path": "Release.make",
    "content": "#\n# This file is part of Facedancer.\n# Maintainer quick actions for generating releases.\n#\n\n\n# By default, use the system's \"python3\" binary; but note that some distros now \n# correctly have 'python' as python3.\nPYTHON  ?= python3\n\nall: prepare_release\n.PHONY: prepare_release\n\nPROJECT = facedancer\n\nifndef VERSION\n$(error This Makefile is for release maintainers; and requires VERSION to be defined for a release.)\nendif\n\n# Flags for creating build archives.\n# These effectively tell the release tool how to modify git-archive output to create a complete build.\nARCHIVE_FLAGS = \\\n\t--extra=VERSION $(HOST_PACKAGE_FLAGS) --prefix=$(PROJECT)-$(VERSION)/\n\n#\n# Prepares a Facedancer release based on the VERSION arguments. \n# Currently, we don't yet have a RELEASENOTE filel or anything like that.\n#\nprepare_release:\n\t@mkdir -p release-files/\n\n\t@echo Tagging release $(VERSION).\n\t@git tag -a v$(VERSION) $(TAG_FORCE) -m \"release $(VERSION)\"\n\t@echo \"$(VERSION)\" > VERSION\n\n\t@echo --- Creating our host-python distribution.\n\t@rm -rf host-packages\n\t@mkdir -p host-packages\n\n\t@#Build the host libraries.\n\t@$(PYTHON) setup.py sdist bdist_wheel -d host-packages\n\n\t@echo --- Preparing the release archives.\n\t$(eval HOST_PACKAGE_FLAGS := $(addprefix --extra=, $(wildcard host-packages/*)))\n\t@git-archive-all $(ARCHIVE_FLAGS) release-files/$(PROJECT)-$(VERSION).tar.xz\n\t@git-archive-all $(ARCHIVE_FLAGS) release-files/$(PROJECT)-$(VERSION).zip\n\n\t@echo\n\t@echo Archives seem to be ready in ./release-files.\n\t@echo If everything seems okay, you probably should push the relevant tag:\n\t@echo \"    git push origin v$(VERSION)\"\n\t@echo\n\t@echo And push the relevant packages to Pypi:\n\t@echo \"    python3 setup.py dsit bdist_wheel register upload\"\n"
  },
  {
    "path": "docs/Makefile",
    "content": "# Minimal makefile for Sphinx documentation\n#\n\n# You can set these variables from the command line.\nSPHINXOPTS\t  =\nSPHINXBUILD\t  = sphinx-build\nSOURCEDIR\t  = source\nBUILDDIR\t  = build\n\n# Put it first so that \"make\" without argument is like \"make help\".\nhelp:\n\t@$(SPHINXBUILD) -M help \"$(SOURCEDIR)\" \"$(BUILDDIR)\" $(SPHINXOPTS) $(O)\n\n.PHONY: help Makefile\n\n# Catch-all target: route all unknown targets to Sphinx using the new\n# \"make mode\" option.  $(O) is meant as a shortcut for $(SPHINXOPTS).\n%: Makefile\n\t@$(SPHINXBUILD) -M $@ \"$(SOURCEDIR)\" \"$(BUILDDIR)\" $(SPHINXOPTS) $(O)\n"
  },
  {
    "path": "docs/make.bat",
    "content": "@ECHO OFF\n\npushd %~dp0\n\nREM Command file for Sphinx documentation\n\nif \"%SPHINXBUILD%\" == \"\" (\n\tset SPHINXBUILD=sphinx-build\n)\nset SOURCEDIR=source\nset BUILDDIR=build\n\nif \"%1\" == \"\" goto help\n\n%SPHINXBUILD% >NUL 2>NUL\nif errorlevel 9009 (\n\techo.\n\techo.The 'sphinx-build' command was not found. Make sure you have Sphinx\n\techo.installed, then set the SPHINXBUILD environment variable to point\n\techo.to the full path of the 'sphinx-build' executable. Alternatively you\n\techo.may add the Sphinx directory to PATH.\n\techo.\n\techo.If you don't have Sphinx installed, grab it from\n\techo.http://sphinx-doc.org/\n\texit /b 1\n)\n\n%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%\ngoto end\n\n:help\n%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%\n\n:end\npopd\n"
  },
  {
    "path": "docs/requirements.txt",
    "content": "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# needed to build api docs\nfacedancer @ git+https://github.com/greatscottgadgets/facedancer\n"
  },
  {
    "path": "docs/source/conf.py",
    "content": "import os, pkg_resources, sys, time\nsys.path.insert(0, os.path.abspath(\"../../\"))\nsys.path.insert(0, os.path.abspath('../../facedancer'))\n\nimport sphinx_rtd_theme\n\nextensions = [\n    'sphinx_rtd_theme'\n]\n\n# -- Project information -----------------------------------------------------\n\nproject = 'Facedancer'\ncopyright = time.strftime('2018-%Y, Great Scott Gadgets')\nauthor = 'Great Scott Gadget'\n\nversion = pkg_resources.get_distribution('facedancer').version\nrelease = ''\n\n\n# -- General configuration ---------------------------------------------------\n\ntemplates_path = ['_templates']\nexclude_patterns = ['_build']\nsource_suffix = '.rst'\nmaster_doc = 'index'\nlanguage = \"en\"\nexclude_patterns = []\npygments_style = None\n\nextensions = [\n    'sphinx.ext.autodoc',\n    'sphinx.ext.extlinks',\n    'sphinx.ext.napoleon',\n    'sphinx.ext.viewcode',\n    'sphinxcontrib.apidoc',\n]\n\n# configure extension: sphinxcontrib.apidoc\napidoc_module_dir = '../../facedancer'\napidoc_output_dir = 'api_docs'\napidoc_excluded_paths = ['test']\napidoc_separate_modules = True\n\n# configure extension: extlinks\nextlinks = {\n    'repo':    ('https://github.com/greatscottgadgets/facedancer/blob/main/%s',          '%s'),\n    'example': ('https://github.com/greatscottgadgets/facedancer/blob/main/examples/%s', '%s'),\n}\n\n# configure extension: napoleon\nnapoleon_google_docstring = True\nnapoleon_numpy_docstring = False\nnapoleon_include_init_with_doc = True\nnapoleon_use_ivar = True\nnapoleon_include_private_with_doc = False\nnapoleon_include_special_with_doc = True\nnapoleon_use_param = False\n\n\n# -- Options for HTML output -------------------------------------------------\n# run pip install sphinx_rtd_theme if you get sphinx_rtd_theme errors\nhtml_theme = \"sphinx_rtd_theme\"\nhtml_css_files = ['status.css']\n"
  },
  {
    "path": "docs/source/facedancer_examples.rst",
    "content": "===================\nFacedancer Examples\n===================\n\n.. warning::\n\n   Facedancer and GreatFET are not currently supported with Windows as the Control Host.\n\n   Windows is however supported as the Target Host when using Linux or macOS for the Control Host.\n\n   For more information please see the tracking issue: `#170 <https://github.com/greatscottgadgets/cynthion/issues/170>`__\n\n\nThere are a number of :repo:`Facedancer examples<examples/>` available that demonstrate emulation of various USB device functions.\n\n\n:example:`rubber-ducky.py`\n--------------------------\n\nThe 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.\n\n.. list-table:: Target Host Compatibility\n   :widths: 30 30 30\n   :header-rows: 1\n\n   * - Linux\n     - macOS\n     - Windows\n   * - ✅\n     - ✅\n     - ✅\n\n\n\n:example:`ftdi-echo.py`\n-----------------------\n\nAn 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.\n\n.. list-table:: Target Host Compatibility\n   :widths: 30 30 30\n   :header-rows: 1\n\n   * - Linux\n     - macOS\n     - Windows\n   * - ✅\n     - ❌\n     - ✅\n\n\n\n:example:`mass-storage.py`\n--------------------------\n\nAn 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.\n\nYou can create an empty disk image for use with the emulation using:\n\n.. code-block :: sh\n\n    dd if=/dev/zero of=disk.img bs=1M count=100\n    mkfs -t ext4 disk.img\n\nYou can also test or modify the disk image locally by mounting it with:\n\n.. code-block :: sh\n\n    mount -t auto -o loop disk.img /mnt\n\nRemember to unmount it before using it with the device emulation!\n\n\n.. list-table:: Target Host Compatibility\n   :widths: 30 30 30\n   :header-rows: 1\n\n   * - Linux\n     - macOS\n     - Windows\n   * - ✅\n     - ✅\n     - ❌\n"
  },
  {
    "path": "docs/source/getting_started.rst",
    "content": "================================================\nGetting started with Facedancer\n================================================\n\n.. warning::\n\n   Facedancer and USBProxy are not currently supported in a Control Host role on Windows with GreatFET.\n\n   For more information please see the tracking issue: `#170 <https://github.com/greatscottgadgets/cynthion/issues/170>`__\n\nInstall the Facedancer library\n------------------------------\n\nYou 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/>`__.\n\n\nInstall From PyPI\n^^^^^^^^^^^^^^^^^\n\nYou can use the `pip <https://pypi.org/project/pip/>`__ tool to install the Facedancer library from PyPI using the following command:\n\n.. code-block :: sh\n\n    pip install facedancer\n\nFor 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.\n\n\nInstall From Source\n^^^^^^^^^^^^^^^^^^^\n\n.. code-block :: sh\n\n    git clone https://github.com/greatscottgadgets/facedancer.git\n    cd facedancer/\n\nOnce you have the source code downloaded you can install the Facedancer library with:\n\n.. code-block :: sh\n\n    pip install .\n\n\n\nRun a Facedancer example\n------------------------\n\nCreate a new Python file called `rubber-ducky.py` with the following content:\n\n.. code-block :: python\n\n    import asyncio\n    import logging\n\n    from facedancer import main\n    from facedancer.devices.keyboard     import USBKeyboardDevice\n    from facedancer.classes.hid.keyboard import KeyboardModifiers\n\n    device = USBKeyboardDevice()\n\n    async def type_letters():\n        # Wait for device to connect\n        await asyncio.sleep(2)\n\n        # Type a string with the device\n        await device.type_string(\"echo hello, facedancer\\n\")\n\n    main(device, type_letters())\n\n\n\nOpen a terminal and run:\n\n.. code-block :: sh\n\n    python ./rubber-ducky.py\n"
  },
  {
    "path": "docs/source/howto_facedancer_backend.rst",
    "content": "=====================================\nHow to write a new Facedancer Backend\n=====================================\n\nFacedancer board backends can be found in the :repo:`facedancer/backends/<facedancer/backends/>` directory.\n\nTo create a new backend, follow these steps:\n\n\n1. Derive a new backend class\n-----------------------------\n\nAll Facedancer board backends inherit from the ``FacedancerApp`` and ``FacedancerBackend`` classes. Begin by deriving your new backend class from these base classes, as shown below:\n\n.. code-block :: python\n\n    from facedancer.core           import FacedancerApp\n    from facedancer.backends.base  import FacedancerBackend\n\n    class MydancerBackend(FacedancerApp, FacedancerBackend):\n\n        app_name = \"Mydancer\"\n\n\n\n\n2. Implement backend callback methods\n-------------------------------------\n\nYour new backend must implement the required callback methods defined in the ``FacedancerBackend`` class. These methods contain the functionality specific to your Facedancer board:\n\n.. literalinclude:: ../../facedancer/backends/base.py\n   :language: python\n   :emphasize-lines: 0\n   :linenos:\n\n\n\n3. Implement the backend event loop\n-----------------------------------\n\nFacedancer uses a polling approach to service events originating from the Facedancer board.\n\nThe actual events that need to be serviced will be specific to your Facedancer board but will generally include at least the following:\n\n* Receiving a setup packet.\n* Receiving data on an endpoint.\n* Receiving NAK events (e.g. host requested data from an IN endpoint)\n\nFacedancer 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.\n\nThat said, most backend implementations will follow a pattern similiar to the pseudo-code below:\n\n.. code-block :: python\n\n    class MydancerBackend(FacedancerApp, FacedancerBackend):\n\n        ...\n\n        def service_irqs(self):\n            \"\"\"\n            Core routine of the Facedancer execution/event loop. Continuously monitors the\n            Moondancer's execution status, and reacts as events occur.\n            \"\"\"\n\n            # obtain latest events and handle them\n            for event in self.mydancer.get_events():\n                match event:\n                    case USB_RECEIVE_SETUP:\n                        self.usb_device.create_request(event.data)\n                    case USB_RECEIVE_PACKET:\n                        self.usb_device.handle_data_available(event.endpoint_number, event.data)\n                    case USB_EP_IN_NAK:\n                        self.usb_device.handle_nak(event.endpoint_number)\n\nAdditionally, referencing the ``service_irqs`` methods of the other backend implementations can provide valuable insights into handling events specific to your implementation.\n"
  },
  {
    "path": "docs/source/index.rst",
    "content": "========================\nFacedancer Documentation\n========================\n\n.. toctree::\n  :maxdepth: 2\n  :caption: User Documentation\n\n  getting_started\n  library_overview\n  using_facedancer\n  using_usb_proxy\n  facedancer_examples\n\n.. toctree::\n  :maxdepth: 2\n  :caption: Developer Documentation\n\n  howto_facedancer_backend\n\n.. toctree::\n  :maxdepth: 1\n  :caption: API Documentation\n\n  api_docs/modules\n\n:ref:`genindex` | :ref:`modindex`\n"
  },
  {
    "path": "docs/source/library_overview.rst",
    "content": "================================================\nLibrary Overview\n================================================\n\nThe Facedancer library may be somewhat overwhelming at first but the modules can be broken down into a number of clearly delineated categories:\n\n\nCore USB Device Model\n~~~~~~~~~~~~~~~~~~~~~\n\nThese packages contain the functionality used to define devices and their organisation closely mirrors the hierarchical USB device model:\n\n.. code-block:: text\n\n    +--------------------------------+\n    | USB Device                     |\n    |   - Device Descriptor          |\n    |   - Configuration Descriptor   |\n    |      - Interface Descriptor    |     +----------------------------------+\n    |         - Endpoint Descriptor  |     | Host                             |\n    |           - Request Handler    | --> |   - Function                     |\n    |         - Endpoint Descriptor  |     |                                  |\n    |           - Request Handler    | <-- |   - Function                     |\n    |   - Control Interface          |     |                                  |\n    |      - Request Handlers        | <-> |   - Enumeration, Status, Command |\n    +--------------------------------+     +----------------------------------+\n\n    (simplified diagram for didactic purposes, not drawn to scale)\n\n* :mod:`facedancer.device`\n    -- :class:`~facedancer.device.USBDevice` is the device root. It is responsible for managing the device's descriptors and marshalling host requests.\n* :mod:`facedancer.configuration`\n    -- :class:`~facedancer.configuration.USBConfiguration` is responsible for managing the device's configuration descriptor(s).\n* :mod:`facedancer.interface`\n    -- :class:`~facedancer.interface.USBInterface` is responsible for managing the device's interface descriptor(s).\n* :mod:`facedancer.endpoint`\n    -- :class:`~facedancer.endpoint.USBEndpoint` is responsible for managing the device's endpoints.\n* :mod:`facedancer.request`\n    -- :class:`~facedancer.request.USBControlRequest` is responsible for managing USB control transfers.\n\nIn addition to the core device model there are also two modules containing support functionality:\n\n* :mod:`facedancer.descriptor`\n    -- contains functionality for working with USB descriptors.\n* :mod:`facedancer.magic`\n    -- contains functionality for Facedancer's declarative device definition syntax.\n\n\n\nDevice Emulation Support\n~~~~~~~~~~~~~~~~~~~~~~~~\n\nThese modules contain a small selection of example USB device classes and device emulations.\n\n* :mod:`facedancer.classes`\n* :mod:`facedancer.devices`\n\n\n\nUSB Proxy\n~~~~~~~~~\n\nThese modules contain the USB Proxy implementation.\n\n* :mod:`facedancer.proxy`\n    -- contains the :class:`~facedancer.proxy.USBProxyDevice` implementation.\n* :mod:`facedancer.filters`\n    -- contains a selection of filters to intercept, view or modify proxied USB transfers.\n\n\n\nFacedancer Board Backends\n~~~~~~~~~~~~~~~~~~~~~~~~~\n\nContains backend implementations for the various supported Facedancer boards.\n\n* :mod:`facedancer.backends`\n\n\n\nSupporting Functionality\n~~~~~~~~~~~~~~~~~~~~~~~~\n\n* :mod:`facedancer.core`\n    -- the Facedancer scheduler and execution core.\n* :mod:`facedancer.errors`\n    -- an error type, there should probably be more.\n* :mod:`facedancer.types`\n    -- various type definitions and constants.\n* :mod:`facedancer.logging`\n    -- logging boilerplate.\n"
  },
  {
    "path": "docs/source/using_facedancer.rst",
    "content": "================================================\nUsing Facedancer\n================================================\n\nIntroduction\n------------\n\nFacedancer allows you to easily define emulations using a simple declarative DSL that mirrors the hierarchical structure of the abstract USB device model.\n\nLet's look at a simple example that defines a USB device with two endpoints and a control interface:\n\n.. literalinclude:: ../../examples/minimal.py\n   :language: python\n   :lines: 7-\n   :lineno-start: 7\n   :linenos:\n\n\nDevice Descriptor\n-----------------\n\nThe 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.\n\n.. note::\n\n    In some cases you may want to use the :class:`~facedancer.device.USBBaseDevice` class if you'd like to\n    provide your own implementation of the standard request handlers.\n\n    See, for example, :class:`~facedancer.proxy.USBProxyDevice`.\n\n\nStarting with the initial class declaration we can define our device as:\n\n.. code-block:: python\n\n    from facedancer import *\n\n    @use_inner_classes_automatically\n    class MyDevice(USBDevice):\n        product_string      : str = \"Example USB Device\"\n        manufacturer_string : str = \"Facedancer\"\n        vendor_id           : int = 0x1209 # https://pid.codes/1209/\n        product_id          : int = 0x0001\n\nWe start by importing the Facedancer library and declaring a class `MyDevice` derived from :class:`~facedancer.device.USBDevice`.\n\nWe 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`!\n\nFinally, we fill in some basic fields Facedancer will use to populate the device descriptor: ``product_string``, ``manufacturer_string``, ``vendor_id`` and ``product_id``.\n\n.. note:: You can find a full list of supported fields in the :class:`~facedancer.device.USBDevice` API documentation.\n\n\n\nConfiguration Descriptor\n------------------------\n\nOnce 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.\n\nMost 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.\n\nHere 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.\n\n.. code-block:: python\n    :emphasize-lines: 5-\n\n    ...\n    class MyDevice(USBDevice):\n        ...\n\n        class MyConfiguration(USBConfiguration):\n            class MyInterface(USBInterface):\n                class MyInEndpoint(USBEndpoint):\n                    number    : int          = 1\n                    direction : USBDirection = USBDirection.IN\n                class MyOutEndpoint(USBEndpoint):\n                    number    : int          = 1\n                    direction : USBDirection = USBDirection.OUT\n\nWe'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!\n\n\n\nRequest Handlers\n----------------\n\nFor our device to actually do something we also need a way to:\n\n* Respond to a request for data from the host.\n* Receive data sent by the host.\n\n.. 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.\n\nThe 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.)\n\n\nEndpoint Request Handlers\n~~~~~~~~~~~~~~~~~~~~~~~~~\n\nEndpoint request handlers are usually either class-specific or vendor-defined and can be declared inside the device's endpoint declaration.\n\nHere we will define two simple handlers for each endpoint.\n\nFor 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.\n\n.. code-block:: python\n    :emphasize-lines: 11-13, 19-21\n\n    ...\n    class MyDevice(USBDevice):\n        ...\n\n        class MyConfiguration(USBConfiguration):\n            class MyInterface(USBInterface):\n                class MyInEndpoint(USBEndpoint):\n                    number    : int          = 1\n                    direction : USBDirection = USBDirection.IN\n\n                    # called when the host requested data from the device on endpoint 0x81\n                    def handle_data_requested(self: USBEndpoint):\n                        self.send(b\"device sent response on bulk endpoint\", blocking=True)\n\n                class MyOutEndpoint(USBEndpoint):\n                    number    : int          = 1\n                    direction : USBDirection = USBDirection.OUT\n\n                    # called when the host sent data to the device on endpoint 0x01\n                    def handle_data_received(self: USBEndpoint, data):\n                        logging.info(f\"device received '{data}' on bulk endpoint\")\n\nFor more information on supported endpoint operations and fields see the :class:`~facedancer.endpoint.USBEndpoint` documentation.\n\n\nControl Request Handlers\n~~~~~~~~~~~~~~~~~~~~~~~~\n\nControl 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.\n\nTo this end, Facedancer provides two sets of decorators to be used when defining a device's control interface:\n\nThe first set of decorators allows you to specify the type of control request to be handled:\n\n* :func:`@control_request_handler <facedancer.request.control_request_handler>`\n* :func:`@standard_request_handler <facedancer.request.standard_request_handler>`\n* :func:`@vendor_request_handler <facedancer.request.vendor_request_handler>`\n* :func:`@class_request_handler <facedancer.request.class_request_handler>`\n* :func:`@reserved_request_handler <facedancer.request.reserved_request_handler>`\n\nThe second set defines the target for the control request:\n\n* :func:`@to_device <facedancer.request.to_device>`\n* :func:`@to_this_endpoint <facedancer.request.to_this_endpoint>`\n* :func:`@to_any_endpoint <facedancer.request.to_any_endpoint>`\n* :func:`@to_this_interface <facedancer.request.to_this_interface>`\n* :func:`@to_any_interface <facedancer.request.to_any_interface>`\n* :func:`@to_other <facedancer.request.to_other>`\n\nFor instance, to define some vendor request handlers you can do:\n\n.. code-block:: python\n    :emphasize-lines: 7-\n\n    ...\n    class MyDevice(USBDevice):\n        ...\n        class MyConfiguration(USBConfiguration):\n        ...\n\n        @vendor_request_handler(request_number=1, direction=USBDirection.IN)\n        @to_device\n        def my_vendor_request_handler(self: USBDevice, request: USBControlRequest):\n            request.reply(b\"device sent response on control endpoint\")\n\n        @vendor_request_handler(request_number=2, direction=USBDirection.OUT)\n        @to_device\n        def my_other_vendor_request_handler(self: USBDevice, request: USBControlRequest):\n            logging.info(f\"device received '{request.index}' '{request.value}' '{request.data}' on control endpoint\")\n\n            # acknowledge the request\n            request.ack()\n\nMore information on the ``request`` parameter can be found in the :class:`~facedancer.request.USBControlRequest` documentation.\n\n\nTesting The Emulation\n---------------------\n\nWe now have a full USB device emulation that will enumerate and respond to requests from the host.\n\nGive it a try!\n\n.. literalinclude:: ../../examples/test_minimal.py\n   :language: python\n   :linenos:\n\n\nSuggestion Engine\n-----------------\n\nFacedancer provides a suggestion engine that can help when trying to map an undocumented device's control interface.\n\nIt works by monitoring the control requests from the host and tracking any which are not supported by your emulation.\n\nYou can enable it by passing the `--suggest` flag when running an emulation:\n\n.. code-block:: shell\n\n    python ./emulation.py --suggest\n\nWhen 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:\n\n.. code-block:: text\n\n    Automatic Suggestions\n    ---------------------\n    These suggestions are based on simple observed behavior;\n    not all of these suggestions may be useful / desirable.\n\n    Request handler code:\n\n    @vendor_request_handler(number=1, direction=USBDirection.IN)\n    @to_device\n    def handle_control_request_1(self, request):\n        # Most recent request was for 64B of data.\n        # Replace me with your handler.\n        request.stall()\n\n\nAnnotated template\n------------------\n\nThe Facedancer repository contains an :example:`annotated template <template.py>` which provides an excellent reference source when building your own devices:\n\n.. literalinclude:: ../../examples/template.py\n   :language: python\n   :lines: 8-276\n   :lineno-start: 8\n   :linenos:\n"
  },
  {
    "path": "docs/source/using_usb_proxy.rst",
    "content": "================================================\nUsing USB Proxy\n================================================\n\nIntroduction\n------------\n\nA 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).\n\n.. code-block:: text\n\n                     +-----------------------------------------------------------------------+\n    +------------+   |  +--------------------------------+   +---------------------------+   |  +--------------+\n    |            |   |  |                                |   |                           |   |  |              |\n    |  PROXIED   |   |  |         CONTROL HOST           |   |    FACEDANCER DEVICE      |   |  |    TARGET    |\n    |    USB     <------>  running Facedancer software   <--->  acts as USB-Controlled   <------>     HOST     |\n    |  DEVICE    |   |  |                                |   |      USB Controller       |   |  |              |\n    |            |   |  |                                |   |                           |   |  |              |\n    +------------+   |  +--------------------------------+   +---------------------------+   |  +--------------+\n                     |                                                                       |\n                     |                    MITM Setup (HOST + FACEDANCER)                     |\n                     +-----------------------------------------------------------------------+\n\n\n\nThe Simplest USB Proxy\n----------------------\n\n.. note::\n\n   On macOS USBProxy needs to run as root in order to claim the device being proxied from the operating system.\n\nThe 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.\n\n.. literalinclude:: ../../examples/usbproxy.py\n   :language: python\n   :lines: 7-\n   :lineno-start: 7\n   :linenos:\n\nSetting 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.\n\nThe 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.\n\nThe 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.\n\nThe second filter is a :class:`~facedancer.filters.logging.USBProxyPrettyPrintFilter` which will intercept all transactions and then log them to the console.\n\n\nWriting USB Proxy Filters\n-------------------------\n\nTo 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.\n\nFor example, a simple filter to intercept and modify data from a MIDI controller could look like this:\n\n.. code-block:: python\n\n    from facedancer.filters import USBProxyFilter\n\n    class MyFilter(USBProxyFilter):\n\n        # intercept the midi controllers IN endpoint\n        def filter_in(self, ep_num, data):\n\n            # check if the data is from the correct endpoint and a midi message\n            if ep_num == (0x82 & 0x7f) and len(data) == 4:\n\n                # check if it is a midi note-on/off message\n                if data[1] in [0x80, 0x90]:\n                    # transpose the note up by an octave - 7f\n                    data[2] += 12\n\n            # return the endpoint number and modified data\n            return ep_num, data\n\nWhich you can then add to the proxy using :class:`~facedancer.proxy.USBProxyDevice`'s :meth:`~facedancer.proxy.USBProxyDevice.add_filter` method:\n\n.. code-block:: python\n\n    # add my filter to the proxy\n    proxy.add_filter(MyFilter())\n\nYou can find more information about the supported handlers in the :class:`~facedancer.filters.base.USBProxyFilter` documentation.\n"
  },
  {
    "path": "examples/coroutine.py",
    "content": "#!/usr/bin/env python3\n# pylint: disable=unused-wildcard-import, wildcard-import\n#\n# This file is part of Facedancer.\n#\n\nimport asyncio\nimport sys\n\nfrom facedancer         import *\nfrom facedancer.errors  import EndEmulation\nfrom facedancer.logging import configure_default_logging, log\n\nfrom minimal import MyDevice\n\n\nasync def my_exit_handler(bindkey: bytes):\n    \"\"\"A custom exit handler that will gracefully shut down the\n        emulation when the user presses the given key combination.\n    \"\"\"\n    import platform\n    if platform.system() == \"Windows\":\n        import msvcrt\n        def get_key():\n          key = msvcrt.getch()\n          # check for, and propagate Control-C\n          if key == b'\\x03':\n              raise KeyboardInterrupt\n          return key\n    else:\n        import termios, tty\n        def get_key():\n          fd = sys.stdin.fileno()\n          restore = termios.tcgetattr(fd)\n          try:\n              tty.setcbreak(fd)\n              key = sys.stdin.read(1)\n          finally:\n              termios.tcsetattr(fd, termios.TCSADRAIN, restore)\n          return str.encode(key)\n\n    while True:\n        key = get_key()\n        if key == bindkey:\n            raise EndEmulation(\"User quit the emulation.\")\n        await asyncio.sleep(0)\n\n\ndef my_main_function(device, *coroutines):\n    \"\"\"\n    A custom main function for emulating a Facedancer device.\n    \"\"\"\n\n    # Set up our logging output.\n    configure_default_logging(level=20)\n\n    # Add a custom exit handler to our coroutines.\n    coroutines = (*coroutines, my_exit_handler(b'\\x05'))\n\n    # Run the relevant code, along with any added coroutines.\n    log.info(\"Starting emulation, press 'Control-E' to disconnect and exit.\")\n    device.emulate(*coroutines)\n\n\nif __name__ == \"__main__\":\n    my_main_function(MyDevice())\n"
  },
  {
    "path": "examples/ftdi-echo.py",
    "content": "#!/usr/bin/env python3\n#\n# This file is part of Facedancer.\n#\n\n\nimport logging\n\nfrom facedancer import main\nfrom facedancer.devices.ftdi import FTDIDevice\n\ndevice = FTDIDevice()\n\nasync def send_hello():\n    \"\"\" Waits for the host to connect, and then says hello. \"\"\"\n\n    logging.info(\"Waiting for the host to connect.\")\n    await device.wait_for_host()\n    logging.info(\"Host connected!\")\n\n    logging.info(\"Telling the user hello...\")\n    device.transmit(\"Hello! Welcome to the FTDI demo.\\n\")\n    device.transmit(\"Enter any text you'd like, and we'll send it back in UPPERCASE.\\n\")\n\n\ndef uppercasize(data):\n    \"\"\" Convert any received data to uppercase. \"\"\"\n\n    # Convert the data to uppercase...\n    uppercase = data.decode('utf-8').upper()\n\n    # ... convert serial line endings to Python line endings...\n    uppercase = uppercase.replace('\\r', '\\n')\n\n    # ... and transmit our response.\n    device.transmit(uppercase)\n\n\n# Override the serial data handler by adding a singleton method on our object.\n# This is an easy way to create one-off objects. :)\ndevice.handle_serial_data_received = uppercasize\n\n\nmain(device, send_hello())\n"
  },
  {
    "path": "examples/hackrf-info.py",
    "content": "#!/usr/bin/env python3\n# pylint: disable=unused-wildcard-import, wildcard-import\n#\n# This file is part of Facedancer.\n#\n\nfrom facedancer import *\nfrom facedancer import main\n\n\n@use_inner_classes_automatically\nclass HackRF(USBDevice):\n    \"\"\" Device that emulates a HackRF enough to appear in ``hackrf_info``.\n\n    You can try to create this script yourself! It's relatively easy using the\n    --suggest option and the ``template.py`` example.\n    \"\"\"\n\n    # Show up as a HackRF.\n    product_string      : str = \"HackRF One (Emulated)\"\n    manufacturer_string : str = \"Great Scott Gadgets\"\n    vendor_id           : int = 0x1d50\n    product_id          : int = 0x6089\n\n    # Most hosts won't accept a device unless it has a configuration\n    # and an interface. We'll add some default/empty ones. Facedancer\n    # provides sane defaults, so we don't need to do anything else!\n    class DefaultConfiguration(USBConfiguration):\n        class DefaultInterface(USBInterface):\n            pass\n\n    #\n    # Vendor requests.\n    #\n    # These templates were generated using --suggest, and then modified\n    # by the author to get the functionality she wanted.\n    #\n    @vendor_request_handler(number=14, direction=USBDirection.IN)\n    @to_device\n    def handle_control_request_14(self, request):\n\n        # The --suggest command gives us the following info:\n        # Most recent request was for 1B of data.\n\n        # Theoretically, this is the point where you'd experiment\n        # with providing one-byte responses and see what `hackrf_info` does.\n        request.reply([2])\n\n\n    #\n    # From here on out, we'll give these requests more descriptive names,\n    # rather than using the ones from --suggest. When creating this, we'd\n    # theoretically do our reverse engineering, and then rename the request.\n    #\n    # Because the decorator indicates to the backend that this is a vendor\n    # request handler, these names can be whatever we'd like -- and we don't\n    # have to update anything when we change them!\n    #\n    @vendor_request_handler(number=15, direction=USBDirection.IN)\n    @to_device\n    def handle_get_version_request(self, request):\n        # Most recent request was for 255B of data.\n\n        # When hackrf_info gets to this point, we can see that it's\n        # failing with \"hackrf_version_string_read() failed: Pipe error (-1000).\"\n        #\n        # That's a pretty good hint of what it expects.\n        request.reply(b\"Sekret Facedancer Version\")\n\n\n    @vendor_request_handler(number=18, direction=USBDirection.IN)\n    @to_device\n    def handle_get_serial_request(self, request):\n        # Most recent request was for 24B of data.\n        request.reply(b'A' * 24)\n\n\n    #\n    # There's one last thing to do -- we'll need to implement one more\n    # simple request. We'll leave this last one as an exercise to the reader. :)\n    #\n\n\nmain(HackRF)\n"
  },
  {
    "path": "examples/imperative.py",
    "content": "#!/usr/bin/env python3\n# pylint: disable=unused-wildcard-import, wildcard-import\n#\n# This file is part of Facedancer.\n#\n\"\"\" Example for using the imperative API. \"\"\"\n\n#\n# The other Facedancer examples tend to use the declarative API,\n# as it's more succinct, and typically can be created faster.\n#\n# However, the new API still supports an imperative syntax,\n# which may be useful in some circumstances.\n#\n\nimport logging\n\nfrom facedancer import main\nfrom facedancer import *\n\n\nclass ImperativeDevice(USBDevice):\n\n    def __init__(self):\n\n        # We can still implement our types imperatively, like in the old API.\n        super().__init__(\n            vendor_id=0x1234,\n            product_string=\"Imperatively-created Device\"\n        )\n\n        # The constructor arguments to each type accept the same fields as the declarative\n        # API -- and like the declarative API, parameters have sane defaults...\n        configuration = USBConfiguration()\n        self.add_configuration(configuration)\n\n        #  ... which means we don't really need to do much to create the various components.\n        interface = USBInterface()\n        configuration.add_interface(interface)\n\n        # Like the declarative APIs, endpoints require a number and direction.\n        out_endpoint = USBEndpoint(number=3, direction=USBDirection.OUT)\n        interface.add_endpoint(out_endpoint)\n\n\n    #\n    # We'll still use our request decorators to declare request handlers\n    # on the relevant objects...\n    #\n    @vendor_request_handler(number=13)\n    def handle_my_request(self, request):\n        request.acknowledge()\n\n\n    #\n    # ... and callbacks continue to work the same way.\n    #\n    def handle_data_received(self, endpoint, data):\n        logging.info(f\"New data: {data} on {endpoint}.\")\n\n\nmain(ImperativeDevice())\n"
  },
  {
    "path": "examples/mass-storage.py",
    "content": "#!/usr/bin/env python3\n#\n# This file is part of Facedancer.\n#\n\nimport sys\nimport logging\n\nfrom facedancer import main\nfrom facedancer.devices.umass import RawDiskImage\nfrom facedancer.devices.umass import USBMassStorageDevice\n\n\n# usage instructions\nif len(sys.argv)==1:\n    print(\"Usage: mass-storage.py disk.img\")\n    sys.exit(1)\n\n# get disk image filename and clear arguments\nfilename = sys.argv[1]\nsys.argv = [sys.argv[0]]\n\n# open our disk image\ndisk_image = RawDiskImage(filename, 512, verbose=3)\n\n# create the device\ndevice = USBMassStorageDevice(disk_image)\n\n\nasync def hello():\n    \"\"\" Waits for the host to connect, and then says hello. \"\"\"\n\n    logging.info(\"Waiting for the host to connect.\")\n    await device.wait_for_host()\n    logging.info(\"Host connected!\")\n\nmain(device, hello())\n\n\n# Creating a disk image for testing:\n#\n#    dd if=/dev/zero of=disk.img bs=1M count=100\n#    mkfs -t ext4 disk.img\n#    mount -t auto -o loop disk.img /mnt\n"
  },
  {
    "path": "examples/minimal.py",
    "content": "#!/usr/bin/env python3\n# pylint: disable=unused-wildcard-import, wildcard-import\n#\n# This file is part of Facedancer.\n#\n\nimport logging\n\nfrom facedancer import *\nfrom facedancer import main\n\n@use_inner_classes_automatically\nclass MyDevice(USBDevice):\n    product_string      : str = \"Example USB Device\"\n    manufacturer_string : str = \"Facedancer\"\n    vendor_id           : int = 0x1209\n    product_id          : int = 0x0001\n    device_speed        : DeviceSpeed = DeviceSpeed.FULL\n\n    class MyConfiguration(USBConfiguration):\n\n        class MyInterface(USBInterface):\n\n            class MyInEndpoint(USBEndpoint):\n                number          : int          = 1\n                direction       : USBDirection = USBDirection.IN\n                max_packet_size : int          = 64\n\n                def handle_data_requested(self: USBEndpoint):\n                    logging.info(\"handle_data_requested\")\n                    self.send(b\"device on bulk endpoint\")\n\n            class MyOutEndpoint(USBEndpoint):\n                number          : int          = 1\n                direction       : USBDirection = USBDirection.OUT\n                max_packet_size : int          = 64\n\n                def handle_data_received(self: USBEndpoint, data):\n                    logging.info(f\"device received {data} on bulk endpoint\")\n\n    @vendor_request_handler(number=1, direction=USBDirection.IN)\n    @to_device\n    def my_in_vendor_request_handler(self: USBDevice, request: USBControlRequest):\n        logging.info(\"my_in_vendor_request_handler\")\n        request.reply(b\"device on control endpoint\")\n\n    @vendor_request_handler(number=2, direction=USBDirection.OUT)\n    @to_device\n    def my_out_vendor_request_handler(self: USBDevice, request: USBControlRequest):\n        logging.info(f\"device received {request.index} {request.value} {bytes(request.data)} on control endpoint\")\n        request.ack()\n\n\nif __name__ == \"__main__\":\n    main(MyDevice)\n"
  },
  {
    "path": "examples/rubber-ducky.py",
    "content": "#!/usr/bin/env python3\n#\n# This file is part of Facedancer.\n#\n\"\"\" USB 'Rubber Ducky' example; enters some text via the keyboard module. \"\"\"\n\nimport asyncio\nimport logging\n\nfrom facedancer import main\nfrom facedancer.devices.keyboard     import USBKeyboardDevice\nfrom facedancer.classes.hid.keyboard import KeyboardModifiers\n\ndevice = USBKeyboardDevice()\n\nasync def type_letters():\n    logging.info(\"Beginning message typing demo...\")\n\n    await asyncio.sleep(2)\n    await device.type_string(\"echo Hello, Facedancer!\\n\")\n\n    logging.info(\"Typing complete. Idly handling USB requests.\")\n\n\nmain(device, type_letters())\n"
  },
  {
    "path": "examples/template.py",
    "content": "#!/usr/bin/env python3\n# pylint: disable=unused-wildcard-import, wildcard-import\n#\n# This file is part of Facedancer.\n#\n\"\"\" Example template for creating new Facedancer devices. \"\"\"\n\nimport logging\n\nfrom facedancer         import main\nfrom facedancer         import *\nfrom facedancer.classes import USBDeviceClass\n\n@use_inner_classes_automatically\nclass TemplateDevice(USBDevice):\n    \"\"\" This class is meant to act as a template to help you get acquainted with Facedancer.\"\"\"\n\n    #\n    # Core 'dataclass' definitions.\n    # These define the basic way that a Facedancer device advertises itself to the host.\n    #\n    # Every one of these is optional. The defaults are relatively sane, so you can mostly\n    # ignore these unless you want to change them! See the other examples for more minimal\n    # data definitions.\n    #\n\n    # The USB device class, subclass, and protocol for the given device.\n    # Often, we'll leave these all set to 0, which means the actual class is read\n    # from the interface.\n    #\n    # Note that we _need_ the type annotations on these. Without them, Python doesn't\n    # consider them valid dataclass members, and ignores them. (This is a detail of python3.7+\n    # dataclasses.)\n    #\n    device_class             : int  = 0\n    device_subclass          : int  = 0\n    protocol_revision_number : int  = 0\n\n    # The maximum packet size on EP0. For most devices, the default value of 64 is fine.\n    max_packet_size_ep0      : int  = 64\n\n    # The vendor ID and product ID that we want to give our device.\n    vendor_id                : int  = 0x610b\n    product_id               : int  = 0x4653\n\n    # The string descriptors we'll provide for our device.\n    # Note that these should be Python strings, and _not_ bytes.\n    manufacturer_string      : str  = \"Facedancer\"\n    product_string           : str  = \"Generic USB Device\"\n    serial_number_string     : str  = \"S/N 3420E\"\n\n    # This tuple is a list of languages we're choosing to support.\n    # This gives us an opportunity to provide strings in various languages.\n    # We don't typically use this; so we can leave this set to a language of\n    # your choice.\n    supported_languages      : tuple = (LanguageIDs.ENGLISH_US,)\n\n    # The revision of the device hardware. This doesn't matter to the USB specification,\n    # but it's sometimes read by drivers. 0x0001 represents \"0.1\" in BCD.\n    device_revision          : int  = 0x0001\n\n    # The revision of the USB specification that this device adheres to.\n    # Typically, you'll leave this at 0x0200 which represents \"2.0\" in BCD.\n    usb_spec_version         : int  = 0x0200\n\n\n    #\n    # We'll define a single configuration on our device. To be compliant,\n    # every device needs at least a configuration and an interface.\n    #\n    # Note that we don't need to do anything special to have this be used.\n    # As long as we're using the @use_inner_classes_automatically decorator,\n    # this configuration will automatically be instantiated and used.\n    #\n    class TemplateConfiguration(USBConfiguration):\n\n        #\n        # Configuration fields.\n        #\n        # Again, all of these are optional; and the default values\n        # are sane and useful.\n        #\n\n        # Configuration number. Every configuration should have a unique\n        # number, which should count up from one. Note that a configuration\n        # shouldn't have a number of 0, as that's USB for \"unconfigured\".\n        configuration_number : int            = 1\n\n        # A simple, optional descriptive name for the configuration. If provided,\n        # this is referenced in the configuration's descriptor.\n        configuration_string : str            = None\n\n        # This setting is set to true if the device can run without bus power,\n        # or false if it pulls its power from the USB bus.\n        self_powered           : bool         = False\n\n        # This setting is set to true if the device can ask that the host\n        # wake it up from sleep. If set to true, the host may choose to\n        # leave power on to the device when the host is suspended.\n        supports_remote_wakeup : bool         = True\n\n        # The maximum power the device will use in this configuration, in mA.\n        # Typically, most devices will request 500mA, the maximum allowed.\n        max_power              : int            = 500\n\n\n        class TemplateInterface(USBInterface):\n\n            #\n            # Interface fields.\n            # Again, all optional and with useful defaults.\n            #\n\n            # The interface index. Each interface should have a unique index,\n            # starting from 0.\n            number                 : int = 0\n\n            # The information about the USB class implemented by this interface.\n            # This is the place where you'd specify if this is e.g. a HID device.\n            class_number           : int = USBDeviceClass.VENDOR_SPECIFIC\n            subclass_number        : int = 0\n            protocol_number        : int = 0\n\n            # A short description of the interface. Optional and typically only informational.\n            interface_string       : str = None\n\n\n            #\n            # Here's where we define any endpoints we want to add to the device.\n            # These behave essentially the same way as the above.\n            #\n            class TemplateInEndpoint(USBEndpoint):\n\n                #\n                # Endpoints are unique in that they have two _required_\n                # properties -- their number and direction.\n                #\n                # Together, these two fields form the endpoint's address.\n                # Endpoint numbers should be > 0, since endpoint 0 is reserved as the default pipe by the spec.\n                number               : int                    = 1\n                direction            : USBDirection           = USBDirection.IN\n\n                #\n                # The remainder of the fields are optional and have useful defaults.\n                #\n\n                # The transfer type selects how data will be transferred over the endpoints.\n                # The currently supported types are BULK and INTERRUPT.\n                transfer_type        : USBTransferType        = USBTransferType.BULK\n\n                # The maximum packet size determines how large packets are allowed to be.\n                # For a full speed device, a max-size value of 64 is typical.\n                max_packet_size      : int = 64\n\n                # For interrupt endpoints, the interval specifies how often the host should\n                # poll the endpoint, in milliseconds. 10ms is a typical value.\n                interval             : int = 0\n\n\n                #\n                # Let's add an event handler. This one is called whenever the host\n                # wants to read data from the device.\n                #\n                def handle_data_requested(self):\n\n                    # We can reply to this request using the .send() method on this\n                    # endpoint, like so:\n                    self.send(b\"Hello!\")\n\n                    # We can also get our parent interface using .parent;\n                    # or a reference to our device using .get_device().\n\n\n            class TemplateOutEndpoint(USBEndpoint):\n                #\n                # We'll use a more typical set of properties for our OUT endpoint.\n                #\n                number               : int                    = 1\n                direction            : USBDirection           = USBDirection.OUT\n\n\n                #\n                # We'll also demonstrate use of another event handler.\n                # This one is called whenever data is sent to this endpoint.\n                #\n                def handle_data_received(self, data):\n                    logging.info(f\"Received data: {data}\")\n\n\n    #\n    # Any of our components can use callback functions -- not just our endpoints!\n    # The callback names are the same no matter where we use them.\n    #\n    def handle_data_received(self, endpoint, data):\n\n        #\n        # When using a callback on something other than an endpoint, our function's\n        # signature is slightly different -- it takes the relevant endpoint as an\n        # argument, as well.\n        #\n\n        # We'll delegate this back to the core handler, here, so it propagates to our subordinate\n        # endpoints -- but we don't have to! If we wanted to, we could call functions on the\n        # endpoint itself. This is especially useful if we're hooking handle_data_requested(),\n        # where we can use endpoint.send() to provide the relevant data.\n        super().handle_data_received(endpoint, data)\n\n        # Note that non-endpoints have a get_endpoint() method, which you can use to get references\n        # to endpoints by their endpoint numbers / directions. This is useful if you want to\n        # send something on another endpoint in response to data received.\n        #\n        # The device also has a .send() method, which accepts an endpoint number and the data to\n        # be sent. This is equivalent to calling .send() on the relevant endpoint.\n\n\n    #\n    # We can very, very easily add request handlers to our devices.\n    #\n    @vendor_request_handler(number=12)\n    def handle_my_request(self, request):\n\n        #\n        # By decorating this function with \"vendor_request_handler\", we've ensured this\n        # function is called to handle vendor request 12. We can also add other arguments to\n        # the vendor_request_handler function -- it'll accept a keyword argument for every\n        # property on the request. If you provide these, the handler will only be called\n        # if the request matches the relevant constraint.\n        #\n        # For example, @vendor_request_handler(number=14, direction=USBDirection.IN, index_low=3)\n        # means the decorated function is only called to handle vendor request 14 for IN requests\n        # where the low byte of the index is 3.\n        #\n        # Other handler decorators exist -- like \"class_request_handler\" or \"standard_request_handler\"\n        #\n\n        # Replying to an IN request is easy -- you just provide the reply data using request.reply().\n        request.reply(b\"Hello, there!\")\n\n\n    @vendor_request_handler(number=1, direction=USBDirection.OUT)\n    @to_device\n    def handle_another_request(self, request):\n\n        #\n        # Another set of convenience decorators exist to refine requests.\n        # Decorators like `to_device` or `to_any_endpoint` chain with our\n        # request decorators, and are syntax sugar for having an argument like\n        # ``recipient=USBRequestRecipient.DEVICE`` in the handler decorator.\n        #\n\n        # For out requests, in lieu of a response, we typically want to acknowledge\n        # the request. This can be accomplished by calling .acknowledge() or .ack()\n        # on the request.\n        request.ack()\n\n        # Of course, if we want to let the host know we can't handle a request, we\n        # may also choose to stall it. This is as simple as calling request.stall().\n\n\n    #\n    # Note that request handlers can be used on configurations, interfaces, and\n    # endpoints as well. For the latter two cases, the decorators `to_this_interface`\n    # and `to_this_endpoint` are convenient -- they tell a request to run only if\n    # it's directed at that endpoint in particular, as selected by its ``index`` parameter.\n    #\n\n\n# Facedancer ships with a default main() function that you can use to set up and run\n# your device. It ships with some nice features -- including a ``--suggest`` function\n# that can suggest pieces of boilerplate code that might be useful in device emulation.\n#\n# main() will accept either the type of device to emulate, or an device instance.\n# It'll also accept asyncio coroutines, in case you want to run things alongside the\n# relevant device code. See e.g. `examples/rubber-ducky.py` for an example.\n#\nmain(TemplateDevice)\n\n\n#\n# Of course, this template looks verbose as heck.\n# For an example that's much less verbose, check out `examples/hackrf-info.py`.\n#\n"
  },
  {
    "path": "examples/test_minimal.py",
    "content": "import logging\n\ndef main():\n    import asyncio\n    import usb1\n\n    VENDOR_REQUEST    = 0x65\n    MAX_TRANSFER_SIZE = 64\n\n    with usb1.USBContext() as context:\n        #logging.info(\"Host: waiting for device to connect\")\n        #await asyncio.sleep(1)\n\n        device_handle = context.openByVendorIDAndProductID(0x1209, 0x0001)\n        if device_handle is None:\n            raise Exception(\"device not found\")\n        device_handle.claimInterface(0)\n\n        # test IN endpoint\n        logging.info(\"Testing bulk IN endpoint\")\n        response = device_handle.bulkRead(\n            endpoint = 0x81,\n            length   = MAX_TRANSFER_SIZE,\n            timeout  = 1000,\n        )\n        logging.info(f\"[host] received '{response}' from bulk endpoint\")\n        print(\"\")\n\n        # test OUT endpoint\n        logging.info(\"Testing bulk OUT endpoint\")\n        response = device_handle.bulkWrite(\n            endpoint = 0x01,\n            data     = b\"host say oh hai on bulk endpoint\",\n            timeout  = 1000,\n        )\n        print(f\"sent {response} bytes\\n\")\n\n        # test IN vendor request handler\n        logging.info(\"Testing IN control transfer\")\n        response = device_handle.controlRead(\n            request_type = usb1.TYPE_VENDOR | usb1.RECIPIENT_DEVICE,\n            request      = 1,\n            index        = 2,\n            value        = 3,\n            length       = MAX_TRANSFER_SIZE,\n            timeout      = 1000,\n        )\n        logging.info(f\"[host] received '{response}' from control endpoint\")\n        print(\"\")\n\n        # test OUT vendor request handler\n        logging.info(\"Testing OUT control transfer\")\n        response = device_handle.controlWrite(\n            request_type = usb1.TYPE_VENDOR | usb1.RECIPIENT_DEVICE,\n            request      = 2,\n            index        = 3,\n            value        = 4,\n            data         = b\"host say oh hai on control endpoint\",\n            timeout      = 1000,\n        )\n        print(f\"sent {response} bytes\\n\")\n\n\nif __name__ == \"__main__\":\n    logging.getLogger().setLevel(logging.DEBUG)\n    main()\n"
  },
  {
    "path": "examples/usbproxy.py",
    "content": "#!/usr/bin/env python3\n#\n# This file is part of Facedancer.\n#\n\"\"\" USB Proxy example; forwards all USB transactions and logs them to the console. \"\"\"\n\nfrom facedancer          import *\nfrom facedancer          import main\n\nfrom facedancer.proxy    import USBProxyDevice\nfrom facedancer.filters  import USBProxySetupFilters, USBProxyPrettyPrintFilter\n\n# replace with the proxied device's information\nID_VENDOR=0x09e8\nID_PRODUCT=0x0031\n\n\nif __name__ == \"__main__\":\n    # create a USB Proxy Device\n    proxy = USBProxyDevice(idVendor=ID_VENDOR, idProduct=ID_PRODUCT)\n\n    # add a filter to forward control transfers between the target host and\n    # proxied device\n    proxy.add_filter(USBProxySetupFilters(proxy, verbose=0))\n\n    # add a filter to log USB transactions to the console\n    proxy.add_filter(USBProxyPrettyPrintFilter(verbose=5))\n\n    main(proxy)\n"
  },
  {
    "path": "facedancer/__init__.py",
    "content": "# Standard types.\nfrom .device        import USBDevice\nfrom .configuration import USBConfiguration\nfrom .interface     import USBInterface\nfrom .endpoint      import USBEndpoint\nfrom .descriptor    import USBDescriptor, USBClassDescriptor, USBDescriptorTypeNumber, StringRef\n\n# Control request handlers.\nfrom .request       import standard_request_handler, class_request_handler, vendor_request_handler\nfrom .request       import to_device, to_this_endpoint, to_this_interface, to_other\nfrom .request       import to_any_endpoint, to_any_interface\nfrom .request       import USBControlRequest\n\n# Raw types.\nfrom .types         import USBDirection, USBTransferType, USBUsageType, USBSynchronizationType\nfrom .types         import USBRequestType, USBRequestRecipient, USBStandardRequests, LanguageIDs\nfrom .types         import DeviceSpeed\n\n# Decorators.\nfrom .magic         import use_automatically, use_inner_classes_automatically\nfrom .descriptor    import include_in_config, requestable\n\n# Alias objects to make them easier to import.\nfrom .backends import *\nfrom .core     import FacedancerUSBApp, FacedancerUSBHostApp, FacedancerBasicScheduler\nfrom .devices  import default_main as main\n\n# Wildcard import.\n__all__ = [\n    'USBDevice', 'USBConfiguration', 'USBInterface', 'USBEndpoint', 'USBDescriptor',\n    'USBClassDescriptor', 'USBDescriptorTypeNumber', 'standard_request_handler',\n    'class_request_handler', 'vendor_request_handler', 'to_device', 'to_this_endpoint',\n    'to_any_endpoint', 'to_this_interface', 'to_any_interface', 'to_other',\n    'USBDirection', 'USBTransferType', 'USBUsageType', 'USBSynchronizationType',\n    'USBRequestType', 'USBRequestRecipient', 'USBStandardRequests', 'LanguageIDs',\n    'DeviceSpeed', 'use_automatically', 'use_inner_classes_automatically',\n    'USBControlRequest', 'include_in_config', 'requestable', 'StringRef',\n]\n"
  },
  {
    "path": "facedancer/backends/MAXUSBApp.py",
    "content": "# MAXUSBApp.py\n#\n# Contains class definition for MAXUSBApp.\n\nimport time\n\nfrom ..core import FacedancerApp\n\nfrom .base            import FacedancerBackend\n\n\nclass MAXUSBApp(FacedancerApp, FacedancerBackend):\n    app_name = \"MAXUSB\"\n\n    reg_ep0_fifo                    = 0x00\n    reg_ep1_out_fifo                = 0x01\n    reg_ep2_in_fifo                 = 0x02\n    reg_ep3_in_fifo                 = 0x03\n    reg_setup_data_fifo             = 0x04\n    reg_ep0_byte_count              = 0x05\n    reg_ep1_out_byte_count          = 0x06\n    reg_ep2_in_byte_count           = 0x07\n    reg_ep3_in_byte_count           = 0x08\n    reg_ep_stalls                   = 0x09\n    reg_clr_togs                    = 0x0a\n    reg_endpoint_irq                = 0x0b\n    reg_endpoint_interrupt_enable   = 0x0c\n    reg_usb_irq                     = 0x0d\n    reg_usb_interrupt_enable        = 0x0e\n    reg_usb_control                 = 0x0f\n    reg_cpu_control                 = 0x10\n    reg_pin_control                 = 0x11\n    reg_revision                    = 0x12\n    reg_function_address            = 0x13\n    reg_io_pins                     = 0x14\n\n    # bitmask values for reg_endpoint_irq = 0x0b\n    is_setup_data_avail             = 0x20     # SUDAVIRQ\n    is_in3_buffer_avail             = 0x10     # IN3BAVIRQ\n    is_in2_buffer_avail             = 0x08     # IN2BAVIRQ\n    is_out1_data_avail              = 0x04     # OUT1DAVIRQ\n    is_out0_data_avail              = 0x02     # OUT0DAVIRQ\n    is_in0_buffer_avail             = 0x01     # IN0BAVIRQ\n\n    # bitmask values for reg_usb_control = 0x0f\n    usb_control_vbgate              = 0x40\n    usb_control_connect             = 0x08\n\n    # bitmask values for reg_pin_control = 0x11\n    interrupt_level                 = 0x08\n    full_duplex                     = 0x10\n    ep0_in_nak                      = (1 << 5)\n    ep2_in_nak                      = (1 << 6)\n    ep3_in_nak                      = (1 << 7)\n\n    # TODO: Support a generic MaxUSB interface that doesn't\n    # depend on any GoodFET details.\n\n    @staticmethod\n    def bytes_as_hex(b, delim=\" \"):\n        return delim.join([\"%02x\" % x for x in b])\n\n\n    # HACK: but given the limitations of the MAX chips, it seems necessary\n    def send_on_endpoint(self, ep_num, data, blocking=False):\n        if ep_num == 0:\n            fifo_reg = self.reg_ep0_fifo\n            bc_reg = self.reg_ep0_byte_count\n        elif ep_num == 2:\n            fifo_reg = self.reg_ep2_in_fifo\n            bc_reg = self.reg_ep2_in_byte_count\n        elif ep_num == 3:\n            fifo_reg = self.reg_ep3_in_fifo\n            bc_reg = self.reg_ep3_in_byte_count\n        else:\n            raise ValueError('endpoint ' + str(ep_num) + ' not supported')\n\n        # FIFO buffer is only 64 bytes, must loop\n        while len(data) > 64:\n            self.write_bytes(fifo_reg, data[:64])\n            self.write_register(bc_reg, 64, ack=True)\n\n            data = data[64:]\n\n        self.write_bytes(fifo_reg, data)\n        self.write_register(bc_reg, len(data), ack=True)\n\n        if self.verbose > 1:\n            print(self.app_name, \"wrote\", self.bytes_as_hex(data), \"to endpoint\",\n                    ep_num)\n\n    # HACK: but given the limitations of the MAX chips, it seems necessary\n    def read_from_endpoint(self, ep_num):\n        if ep_num != 1:\n            return b''\n\n        byte_count = self.read_register(self.reg_ep1_out_byte_count)\n        if byte_count == 0:\n            return b''\n\n        data = self.read_bytes(self.reg_ep1_out_fifo, byte_count)\n\n        if self.verbose > 1:\n            print(self.app_name, \"read\", self.bytes_as_hex(data), \"from endpoint\",\n                    ep_num)\n\n        return data\n\n\n    def stall_endpoint(self, ep_number, direction=0):\n        \"\"\"\n        Stalls an arbitrary endpoint.\n\n        Args:\n            ep_number : The endpoint number to be stalled\n            direction : 0 for out, 1 for in\n        \"\"\"\n        if self.verbose > 0:\n            print(self.app_name, \"stalling endpoint {}\".format(ep_number))\n\n        # TODO: Verify our behavior, here. The original facedancer code stalls\n        # EP0 both _in_ and out, as well as uses the special STALL SETUP bit.\n        # Is this really what we want?\n        if ep_number == 0:\n            self.write_register(self.reg_ep_stalls, 0x23)\n        elif ep_number < 4:\n            self.write_writer(self.reg_ep_stalls, 1 << (ep_num + 1))\n        else:\n            raise ValueError(\"Invalid endpoint for MAXUSB device!\")\n\n\n    def stall_ep0(self, direction=0):\n        return self.stall_endpoint(0, direction)\n\n\n    def get_version(self):\n        return self.read_register(self.reg_revision)\n\n\n    def connect(self, usb_device, max_packet_size_ep0=64, device_speed=None):\n        if self.read_register(self.reg_usb_control) & self.usb_control_connect:\n            self.write_register(self.reg_usb_control, self.usb_control_vbgate)\n            time.sleep(.1)\n\n        self.write_register(self.reg_usb_control, self.usb_control_vbgate |\n                self.usb_control_connect)\n\n        self.connected_device = usb_device\n\n        if self.verbose > 0:\n            print(self.app_name, \"connected device\", self.connected_device.name)\n\n\n    def disconnect(self):\n        self.write_register(self.reg_usb_control, self.usb_control_vbgate)\n\n        if self.verbose > 0:\n            print(self.app_name, \"disconnected device\", self.connected_device.name)\n        self.connected_device = None\n\n\n    def clear_irq_bit(self, reg, bit):\n        self.write_register(reg, bit)\n\n\n    def service_irqs(self):\n        irq = self.read_register(self.reg_endpoint_irq)\n        in_nak = self.read_register(self.reg_pin_control)\n\n        if self.verbose > 3:\n            print(self.app_name, \"read endpoint irq: 0x%02x\" % irq)\n            print(self.app_name, \"read pin control: 0x%02x\" % in_nak)\n\n        if self.verbose > 2:\n            if irq & ~ (self.is_in0_buffer_avail \\\n                    | self.is_in2_buffer_avail | self.is_in3_buffer_avail):\n                print(self.app_name, \"notable irq: 0x%02x\" % irq)\n\n        if irq & self.is_setup_data_avail:\n            self.clear_irq_bit(self.reg_endpoint_irq, self.is_setup_data_avail)\n\n            b = self.read_bytes(self.reg_setup_data_fifo, 8)\n            if (irq & self.is_out0_data_avail) and (b[0] & 0x80 == 0x00):\n                data_bytes_len = b[6] + (b[7] << 8)\n                b += self.read_bytes(self.reg_ep0_fifo, data_bytes_len)\n            req = self.connected_device.create_request(b)\n            self.connected_device.handle_request(req)\n\n        if irq & self.is_out1_data_avail:\n            data = self.read_from_endpoint(1)\n            if data:\n                self.connected_device.handle_data_available(1, data)\n            self.clear_irq_bit(self.reg_endpoint_irq, self.is_out1_data_avail)\n\n        if irq & self.is_in2_buffer_avail:\n            self.connected_device.handle_buffer_available(2)\n\n        if irq & self.is_in3_buffer_avail:\n            self.connected_device.handle_buffer_available(3)\n\n        # Check to see if we've NAK'd on either of our IN endpoints,\n        # and generate the relevant events.\n\n        if in_nak & self.ep2_in_nak:\n            self.connected_device.handle_nak(2)\n            self.clear_irq_bit(self.reg_pin_control, in_nak | self.ep2_in_nak)\n\n        if in_nak & self.ep3_in_nak:\n            self.connected_device.handle_nak(3)\n            self.clear_irq_bit(self.reg_pin_control, in_nak | self.ep3_in_nak)\n\n\n\n    def set_address(self, address, defer=False):\n        \"\"\"\n        Sets the device address of the Facedancer. Usually only used during\n        initial configuration.\n\n        Args:\n            address : The address that the Facedancer should assume.\n        \"\"\"\n\n        # The MAXUSB chip handles this for us, so we don't need to do anything.\n        pass\n\n\n    def configured(self, configuration):\n        \"\"\"\n        Callback that's issued when a USBDevice is configured, e.g. by the\n        SET_CONFIGURATION request. Allows us to apply the new configuration.\n\n        Args:\n            configuration : The configuration applied by the SET_CONFIG request.\n        \"\"\"\n        self.validate_configuration(configuration)\n\n        # For the MAXUSB case, we don't need to do anything, though it might\n        # be nice to print a message or store the active configuration for\n        # use by the USBDevice, etc. etc.\n"
  },
  {
    "path": "facedancer/backends/__init__.py",
    "content": "__all__ = [\n    \"goodfet\",\n    \"MAXUSBApp\",\n    \"greatdancer\",\n    \"raspdancer\",\n    \"greathost\",\n    \"libusbhost\",\n    \"moondancer\",\n    \"hydradancer\"\n]\n"
  },
  {
    "path": "facedancer/backends/base.py",
    "content": "from typing    import List\nfrom ..        import *\n\n\nclass FacedancerBackend:\n    def __init__(self, device: USBDevice=None, verbose: int=0, quirks: List[str]=[]):\n        \"\"\"\n        Initializes the backend.\n\n        Args:\n            device  :  The device that will act as our Facedancer.   (Optional)\n            verbose : The verbosity level of the given application. (Optional)\n            quirks  :  List of USB platform quirks.                  (Optional)\n        \"\"\"\n        raise NotImplementedError\n\n\n    @classmethod\n    def appropriate_for_environment(cls, backend_name: str) -> bool:\n        \"\"\"\n        Determines if the current environment seems appropriate\n        for using this backend.\n\n        Args:\n            backend_name : Backend name being requested. (Optional)\n        \"\"\"\n        raise NotImplementedError\n\n\n    def get_version(self):\n        \"\"\"\n        Returns information about the active Facedancer version.\n        \"\"\"\n        raise NotImplementedError\n\n\n    def connect(self, usb_device: USBDevice, max_packet_size_ep0: int=64, device_speed: DeviceSpeed=DeviceSpeed.FULL):\n        \"\"\"\n        Prepares backend to connect to the target host and emulate\n        a given device.\n\n        Args:\n            usb_device : The USBDevice object that represents the emulated device.\n            max_packet_size_ep0 : Max packet size for control endpoint.\n            device_speed : Requested usb speed for the Facedancer board.\n        \"\"\"\n        raise NotImplementedError\n\n\n    def disconnect(self):\n        \"\"\" Disconnects Facedancer from the target host. \"\"\"\n        raise NotImplementedError\n\n\n    def reset(self):\n        \"\"\"\n        Triggers the Facedancer to handle its side of a bus reset.\n        \"\"\"\n        raise NotImplementedError\n\n\n    def set_address(self, address: int, defer: bool=False):\n        \"\"\"\n        Sets the device address of the Facedancer. Usually only used during\n        initial configuration.\n\n        Args:\n            address : The address the Facedancer should assume.\n            defer   : True iff the set_address request should wait for an active transaction to\n                      finish.\n        \"\"\"\n        raise NotImplementedError\n\n\n    def configured(self, configuration: USBConfiguration):\n        \"\"\"\n        Callback that's issued when a USBDevice is configured, e.g. by the\n        SET_CONFIGURATION request. Allows us to apply the new configuration.\n\n        Args:\n            configuration : The USBConfiguration object applied by the SET_CONFIG request.\n        \"\"\"\n        raise NotImplementedError\n\n\n    def read_from_endpoint(self, endpoint_number: int) -> bytes:\n        \"\"\"\n        Reads a block of data from the given endpoint.\n\n        Args:\n            endpoint_number : The number of the OUT endpoint on which data is to be rx'd.\n        \"\"\"\n        raise NotImplementedError\n\n\n    def send_on_control_endpoint(self, endpoint_number: int, in_request: USBControlRequest, data: bytes, blocking: bool=True):\n        \"\"\"\n        Sends a collection of USB data in response to a IN control request by the host.\n\n        Args:\n            endpoint_number  : The number of the IN endpoint on which data should be sent.\n            in_request       : The control request being responded to.\n            data             : The data to be sent.\n            blocking         : If true, this function should wait for the transfer to complete.\n        \"\"\"\n        # Truncate data to requested length and forward to `send_on_endpoint()` for backends\n        # that do not need to support this method.\n        return self.send_on_endpoint(endpoint_number, data[:in_request.length], blocking)\n\n\n    def send_on_endpoint(self, endpoint_number: int, data: bytes, blocking: bool=True):\n        \"\"\"\n        Sends a collection of USB data on a given endpoint.\n\n        Args:\n            endpoint_number : The number of the IN endpoint on which data should be sent.\n            data : The data to be sent.\n            blocking : If true, this function should wait for the transfer to complete.\n        \"\"\"\n        raise NotImplementedError\n\n\n    def ack_status_stage(self, direction: USBDirection=USBDirection.OUT, endpoint_number:int =0, blocking: bool=False):\n        \"\"\"\n        Handles the status stage of a correctly completed control request,\n        by priming the appropriate endpoint to handle the status phase.\n\n        Args:\n            direction : Determines if we're ACK'ing an IN or OUT vendor request.\n                       (This should match the direction of the DATA stage.)\n            endpoint_number : The endpoint number on which the control request\n                              occurred.\n            blocking : True if we should wait for the ACK to be fully issued\n                       before returning.\n        \"\"\"\n\n\n    def stall_endpoint(self, endpoint_number:int, direction: USBDirection=USBDirection.OUT):\n        \"\"\"\n        Stalls the provided endpoint, as defined in the USB spec.\n\n        Args:\n            endpoint_number : The number of the endpoint to be stalled.\n        \"\"\"\n        raise NotImplementedError\n\n\n    def clear_halt(self, endpoint_number:int, direction: USBDirection):\n        \"\"\" Clears a halt condition on the provided non-control endpoint.\n\n        Args:\n            endpoint_number : The endpoint number\n            direction       : The endpoint direction; or OUT if not provided.\n        \"\"\"\n        # FIXME do nothing as only the moondancer backend supports this for now\n        # raise NotImplementedError\n        pass\n\n\n    def service_irqs(self):\n        \"\"\"\n        Core routine of the Facedancer execution/event loop. Continuously monitors the\n        Facedancer's execution status, and reacts as events occur.\n        \"\"\"\n        raise NotImplementedError\n\n\n    def validate_configuration(self, configuration: USBConfiguration):\n        \"\"\"\n        Check if this backend is able to support this configuration.\n        Raises an exception if it is not.\n\n        Args:\n            configuration : The configuration to validate.\n        \"\"\"\n        if configuration is None:\n            return\n\n        # Currently, endpoints are only set up in the configured() method, and\n        # cannot be changed on the fly by SET_INTERFACE requests.\n        #\n        # Therefore, no backends are able to support configurations which\n        # re-use endpoint addresses between alternate interface settings.\n        used_addresses = set()\n        for interface in configuration.get_interfaces():\n            for endpoint in interface.get_endpoints():\n                address = endpoint.get_identifier()\n                if address in used_addresses:\n                    raise Exception(\n                        f\"This configuration cannot currently be supported, \"\n                        f\"because it re-uses endpoint address 0x{address:02X} \"\n                        f\"between multiple interface definitions.\")\n                used_addresses.add(address)\n"
  },
  {
    "path": "facedancer/backends/goodfet.py",
    "content": "import os\nimport serial\nimport sys\nimport time\n\nfrom ..core               import FacedancerApp\nfrom ..backends.MAXUSBApp import MAXUSBApp\nfrom ..logging            import log\n\n\nclass GoodfetMaxUSBApp(MAXUSBApp):\n    app_name = \"MAXUSB\"\n    app_num = 0x40\n\n\n    @classmethod\n    def appropriate_for_environment(cls, backend_name):\n        \"\"\"\n        Determines if the current environment seems appropriate\n        for using the GoodFET::MaxUSB backend.\n        \"\"\"\n\n        # Check: Only proceed if the user has specified \"goodfet\" as\n        # the backend name, we don't want a false positive because the\n        # user has another USB serial device connected.\n        if backend_name is None or backend_name != \"goodfet\":\n            return False\n\n        # See if there's a connected GoodFET.\n        try:\n            gf = GoodFETSerialPort()\n            gf.close()\n            return True\n        except ImportError:\n            log.info(\"Skipping GoodFET-based devices, as pyserial isn't installed.\")\n            return False\n        except:\n            return False\n\n\n    def __init__(self, device=None, verbose=0, quirks=None):\n\n        if device is None:\n            serial = GoodFETSerialPort()\n            device = Facedancer(serial, verbose=verbose)\n\n        FacedancerApp.__init__(self, device, verbose)\n\n        self.connected_device = None\n\n        self.enable()\n\n        if verbose > 0:\n            rev = self.read_register(self.reg_revision)\n            print(self.app_name, \"revision\", rev)\n\n        # set duplex and negative INT level (from GoodFEDMAXUSB.py)\n        self.write_register(self.reg_pin_control,\n                self.full_duplex | self.interrupt_level)\n\n    def init_commands(self):\n        self.read_register_cmd  = FacedancerCommand(self.app_num, 0x00, b'')\n        self.write_register_cmd = FacedancerCommand(self.app_num, 0x00, b'')\n        self.enable_app_cmd     = FacedancerCommand(self.app_num, 0x10, b'')\n        self.ack_cmd            = FacedancerCommand(self.app_num, 0x00, b'\\x01')\n\n    def enable(self):\n        for i in range(3):\n            self.device.writecmd(self.enable_app_cmd)\n            self.device.readcmd()\n\n        if self.verbose > 0:\n            print(self.app_name, \"enabled\")\n\n    def ack_status_stage(self, blocking=False):\n        if self.verbose > 5:\n            print(self.app_name, \"sending ack!\")\n\n        self.device.writecmd(self.ack_cmd)\n        self.device.readcmd()\n\n    def read_register(self, reg_num, ack=False):\n        if self.verbose > 1:\n            print(self.app_name, \"reading register 0x%02x\" % reg_num)\n\n        self.read_register_cmd.data = bytearray([ reg_num << 3, 0 ])\n        if ack:\n            self.read_register_cmd.data[0] |= 1\n\n        self.device.writecmd(self.read_register_cmd)\n\n        resp = self.device.readcmd()\n\n        if self.verbose > 2:\n            print(self.app_name, \"read register 0x%02x has value 0x%02x\" %\n                    (reg_num, resp.data[1]))\n\n        return resp.data[1]\n\n    def write_register(self, reg_num, value, ack=False):\n        if self.verbose > 2:\n            print(self.app_name, \"writing register 0x%02x with value 0x%02x\" %\n                    (reg_num, value))\n\n        self.write_register_cmd.data = bytearray([ (reg_num << 3) | 2, value ])\n        if ack:\n            self.write_register_cmd.data[0] |= 1\n\n        self.device.writecmd(self.write_register_cmd)\n        self.device.readcmd()\n\n\n    def read_bytes(self, reg, n):\n        if self.verbose > 2:\n            print(self.app_name, \"reading\", n, \"bytes from register\", reg)\n\n        data = bytes([ (reg << 3) ] + ([0] * n))\n        cmd = FacedancerCommand(self.app_num, 0x00, data)\n\n        self.device.writecmd(cmd)\n        resp = self.device.readcmd()\n\n        if self.verbose > 3:\n            print(self.app_name, \"read\", len(resp.data) - 1, \"bytes from register\", reg)\n\n        return resp.data[1:]\n\n    def write_bytes(self, reg, data):\n        data = bytes([ (reg << 3) | 3 ]) + data\n        cmd = FacedancerCommand(self.app_num, 0x00, data)\n\n        self.device.writecmd(cmd)\n        self.device.readcmd() # null response\n\n        if self.verbose > 3:\n            print(self.app_name, \"wrote\", len(data) - 1, \"bytes to register\", reg)\n\n\n\n\nclass Facedancer:\n    def __init__(self, serialport, verbose=0):\n        self.serialport = serialport\n        self.verbose = verbose\n\n        self.reset()\n        self.monitor_app = GoodFETMonitorApp(self, verbose=self.verbose)\n        self.monitor_app.announce_connected()\n\n    def halt(self):\n        self.serialport.setRTS(1)\n        self.serialport.setDTR(1)\n\n    def reset(self):\n        if self.verbose > 1:\n            print(\"Facedancer resetting...\")\n\n        self.halt()\n        self.serialport.setDTR(0)\n\n        c = self.readcmd()\n\n        if self.verbose > 0:\n            print(\"Facedancer reset\")\n\n    def read(self, n):\n        \"\"\"Read raw bytes.\"\"\"\n\n        b = self.serialport.read(n)\n\n        if self.verbose > 3:\n            print(\"Facedancer received\", len(b), \"bytes;\",\n                    self.serialport.inWaiting(), \"bytes remaining\")\n\n        if self.verbose > 2:\n            print(\"Facedancer Rx:\", MAXUSBApp.bytes_as_hex(b))\n\n        return b\n\n    def readcmd(self):\n        \"\"\"Read a single command.\"\"\"\n\n        b = self.read(4)\n\n        app = b[0]\n        verb = b[1]\n        n = b[2] + (b[3] << 8)\n\n        if n > 0:\n            data = self.read(n)\n        else:\n            data = b''\n\n        if len(data) != n:\n            raise ValueError('Facedancer expected ' + str(n) \\\n                    + ' bytes but received only ' + str(len(data)))\n\n        cmd = FacedancerCommand(app, verb, data)\n\n        if self.verbose > 1:\n            print(\"Facedancer Rx command:\", cmd)\n\n        return cmd\n\n    def write(self, b):\n        \"\"\"Write raw bytes.\"\"\"\n\n        if self.verbose > 2:\n            print(\"Facedancer Tx:\", MAXUSBApp.bytes_as_hex(b))\n\n        self.serialport.write(b)\n\n    def writecmd(self, c):\n        \"\"\"Write a single command.\"\"\"\n        self.write(c.as_bytestring())\n\n        if self.verbose > 1:\n            print(\"Facedancer Tx command:\", c)\n\n\nclass FacedancerCommand:\n    def __init__(self, app=None, verb=None, data=None):\n        self.app = app\n        self.verb = verb\n        self.data = data\n\n    def __str__(self):\n        s = \"app 0x%02x, verb 0x%02x, len %d\" % (self.app, self.verb,\n                len(self.data))\n\n        if len(self.data) > 0:\n            s += \", data \" + MAXUSBApp.bytes_as_hex(self.data)\n\n        return s\n\n    def long_string(self):\n        s = \"app: \" + str(self.app) + \"\\n\" \\\n          + \"verb: \" + str(self.verb) + \"\\n\" \\\n          + \"len: \" + str(len(self.data))\n\n        if len(self.data) > 0:\n            try:\n                s += \"\\n\" + self.data.decode(\"utf-8\")\n            except UnicodeDecodeError:\n                s += \"\\n\" + MAXUSBApp.bytes_as_hex(self.data)\n\n        return s\n\n    def as_bytestring(self):\n        n = len(self.data)\n\n        b = bytearray(n + 4)\n        b[0] = self.app\n        b[1] = self.verb\n        b[2] = n & 0xff\n        b[3] = n >> 8\n        b[4:] = self.data\n\n        return b\n\n\nclass GoodFETMonitorApp(FacedancerApp):\n    app_name = \"GoodFET monitor\"\n    app_num = 0x00\n\n    def read_byte(self, addr):\n        d = [ addr & 0xff, addr >> 8 ]\n        cmd = FacedancerCommand(self.app_num, 2, d)\n\n        self.device.writecmd(cmd)\n        resp = self.device.readcmd()\n\n        return resp.data[0]\n\n    def get_infostring(self):\n        return bytes([ self.read_byte(0xff0), self.read_byte(0xff1) ])\n\n    def get_clocking(self):\n        return bytes([ self.read_byte(0x57), self.read_byte(0x56) ])\n\n    def print_info(self):\n        infostring = self.get_infostring()\n        clocking = self.get_clocking()\n\n        print(\"MCU\", MAXUSBApp.bytes_as_hex(infostring, delim=\"\"))\n        print(\"clocked at\", MAXUSBApp.bytes_as_hex(clocking, delim=\"\"))\n\n    def list_apps(self):\n        cmd = FacedancerCommand(self.app_num, 0x82, b'\\x01')\n        self.device.writecmd(cmd)\n\n        resp = self.device.readcmd()\n        print(\"build date:\", resp.data.decode(\"utf-8\"))\n\n        print(\"firmware apps:\")\n        while True:\n            resp = self.device.readcmd()\n            if len(resp.data) == 0:\n                break\n            print(resp.data.decode(\"utf-8\"))\n\n    def echo(self, s):\n        b = bytes(s, encoding=\"utf-8\")\n\n        cmd = FacedancerCommand(self.app_num, 0x81, b)\n        self.device.writecmd(cmd)\n\n        resp = self.device.readcmd()\n\n        return resp.data == b\n\n    def announce_connected(self):\n        cmd = FacedancerCommand(self.app_num, 0xb1, b'')\n        self.device.writecmd(cmd)\n        resp = self.device.readcmd()\n\n\ndef GoodFETSerialPort(**kwargs):\n    \"Return a Serial port using default values possibly overriden by caller\"\n\n    port = os.environ.get('GOODFET') or \"/dev/ttyUSB0\"\n    args = dict(port=port, baudrate=115200,\n                parity=serial.PARITY_NONE, timeout=2)\n    args.update(kwargs)\n    return serial.Serial(**args)\n"
  },
  {
    "path": "facedancer/backends/greatdancer.py",
    "content": "# GreatDancerApp.py\n\nimport sys\nimport time\nimport codecs\nimport traceback\n\nfrom ..core     import *\nfrom ..types    import *\n\nfrom ..logging  import log\n\nfrom .base            import FacedancerBackend\n\n\nclass GreatDancerApp(FacedancerApp, FacedancerBackend):\n    \"\"\"\n    Backend for using GreatFET devices as Facedancers.\n    \"\"\"\n\n    app_name = \"GreatDancer\"\n    app_num = 0x00 # This doesn't have any meaning for us.\n\n    # Interrupt register (USBSTS) bits masks.\n    USBSTS_D_UI   = (1 <<  0)\n    USBSTS_D_URI  = (1 <<  6)\n    USBSTS_D_NAKI = (1 << 16)\n\n    # Number of supported USB endpoints.\n    # TODO: bump this up when we develop support using USB0 (cables flipped)\n    SUPPORTED_ENDPOINTS = 4\n\n    # USB directions\n    HOST_TO_DEVICE = 0\n    DEVICE_TO_HOST = 1\n\n    # Get status command indexes\n    GET_USBSTS         = 0\n    GET_ENDPTSETUPSTAT = 1\n    GET_ENDPTCOMPLETE  = 2\n    GET_ENDPTSTATUS    = 3\n    GET_ENDPTNAK       = 4\n\n    # Quirk flags\n    QUIRK_MANUAL_SET_ADDRESS = 0x01\n\n    @classmethod\n    def appropriate_for_environment(cls, backend_name):\n        \"\"\"\n        Determines if the current environment seems appropriate\n        for using the GreatDancer backend.\n        \"\"\"\n\n        # Check: if we have a backend name other than greatfet,\n        # the user is trying to use something else. Abort!\n        if backend_name and backend_name != \"greatfet\":\n            return False\n\n        # If we're not explicitly trying to use something else,\n        # see if there's a connected GreatFET.\n        try:\n            import greatfet\n            gf = greatfet.GreatFET()\n            return gf.supports_api('greatdancer')\n        except ImportError:\n            log.info(\"Skipping GreatFET-based devices, as the greatfet python module isn't installed.\")\n            return False\n        except:\n            return False\n\n\n    def __init__(self, device=None, verbose=0, quirks=None):\n        \"\"\"\n        Sets up a new GreatFET-backed Facedancer (GreatDancer) application.\n\n        device: The GreatFET device that will act as our GreatDancer.\n        verbose: The verbosity level of the given application.\n        \"\"\"\n\n        import greatfet\n\n        if device is None:\n            device = greatfet.GreatFET()\n\n        self.device = device\n\n        self.device.comms.get_exclusive_access()\n\n        FacedancerApp.__init__(self, device, verbose)\n        self.connected_device = None\n\n        # Grab the raw API object from the GreatFET object.\n        # This has the low-level RPCs used for raw USB control.\n        self.api = self.device.apis.greatdancer\n\n        # Initialize a dictionary that will store the last setup\n        # whether each endpoint is currently stalled.\n        self.endpoint_stalled = {}\n        for i in range(self.SUPPORTED_ENDPOINTS):\n            self.endpoint_stalled[i] = False\n\n        # Assume a max packet size of 64 until configured otherwise.\n        self.max_packet_size_ep0 = 64\n\n        # Start off by assuming we're not waiting for an OUT control transfer's\n        # data stage.  # See _handle_setup_complete_on_endpoint for details.\n        self.pending_control_request = None\n\n        # Store a reference to the device's active configuration,\n        # which we'll use to know which endpoints we'll need to check\n        # for data transfer readiness.\n        self.configuration = None\n\n        #\n        # Store our list of quirks to handle.\n        #\n        if quirks:\n            self.quirks = quirks\n        else:\n            self.quirks = []\n\n\n    def init_commands(self):\n        \"\"\"\n        API compatibility function; not necessary for GreatDancer.\n        \"\"\"\n        pass\n\n\n    def get_version(self):\n        \"\"\"\n        Returns information about the active GreatDancer version.\n        \"\"\"\n        # TODO: Return the GreatFET software version, or something indicating\n        # the GreatFET API number?\n        raise NotImplementedError()\n\n\n    def ack_status_stage(self, direction=HOST_TO_DEVICE, endpoint_number=0, blocking=False):\n        \"\"\"\n            Handles the status stage of a correctly completed control request,\n            by priming the appropriate endpoint to handle the status phase.\n\n            Args:\n                direction : Determines if we're ACK'ing an IN or OUT vendor request.\n                            (This should match the direction of the DATA stage.)\n                endpoint_number : The endpoint number on which the control request\n                                  occurred.\n                blocking : True if we should wait for the ACK to be fully issued\n                           before returning.\n        \"\"\"\n        if direction == self.HOST_TO_DEVICE:\n            # If this was an OUT request, we'll prime the output buffer to\n            # respond with the ZLP expected during the status stage.\n            self.send_on_endpoint(endpoint_number, data=[], blocking=blocking)\n\n        else:\n            # If this was an IN request, we'll need to set up a transfer descriptor\n            # so the status phase can operate correctly. This effectively reads the\n            # zero length packet from the STATUS phase.\n            self.read_from_endpoint(endpoint_number)\n\n\n    def _generate_endpoint_config_arguments(self, config):\n        \"\"\"\n        Generates the data content for an Endpoint Configuration command that will\n        set up the GreatDancer's endpoints to match the active configuration.\n\n        Args:\n             config : A USBConfiguration object that represents the configuration being\n                      applied to the GreatDancer.\n        \"\"\"\n        arguments = []\n\n        # If our configuration is None, there's nothing to configure; bail out.\n        if config is None:\n            return arguments\n\n        for interface in config.get_interfaces():\n            for endpoint in interface.get_endpoints():\n                log.info(f\"Configuring {endpoint}.\")\n\n                triple = (endpoint.get_address(), endpoint.max_packet_size, endpoint.transfer_type,)\n                arguments.append(triple)\n\n        return arguments\n\n\n    def connect(self, usb_device, max_packet_size_ep0=64, device_speed=DeviceSpeed.FULL):\n        \"\"\"\n        Prepares the GreatDancer to connect to the target host and emulate\n        a given device.\n\n        Args:\n            usb_device : The USBDevice object that represents the device to be\n                         emulated.\n        \"\"\"\n\n        if device_speed != DeviceSpeed.FULL:\n            log.warning(f\"GreatFET only supports USB Full Speed. Ignoring requested speed: {device_speed.name}\")\n\n        self.max_packet_size_ep0 = max_packet_size_ep0\n\n        quirks = 0\n\n        # Compute our quirk flags.\n        if 'manual_set_address' in self.quirks:\n            log.info(\"Handling SET_ADDRESS on the host side!\")\n\n            quirks |= self.QUIRK_MANUAL_SET_ADDRESS\n\n        self.api.connect(self.max_packet_size_ep0, quirks)\n        self.connected_device = usb_device\n\n        log.info(\"Connecting to host.\")\n\n\n    def disconnect(self):\n        \"\"\" Disconnects the GreatDancer from its target host. \"\"\"\n        log.info(\"Disconnecting from host.\")\n        self.device.comms.release_exclusive_access()\n        self.api.disconnect()\n\n\n    def _wait_until_ready_to_send(self, ep_num):\n\n        # If we're already ready, we don't need to do anything. Abort.\n        if self._is_ready_for_priming(ep_num, self.DEVICE_TO_HOST):\n            return\n\n        # Otherwise, wait until we're ready to send...\n        while not self._is_ready_for_priming(ep_num, self.DEVICE_TO_HOST):\n            pass\n\n        # ... and since we've blocked the app from cleaning up any transfer\n        # descriptors automatically by spinning in this thread, we'll clean up\n        # the relevant transfers here.\n        self._clean_up_transfers_for_endpoint(ep_num, self.DEVICE_TO_HOST)\n\n\n    def send_on_endpoint(self, ep_num, data, blocking=True):\n        \"\"\"\n        Sends a collection of USB data on a given endpoint.\n\n        Args:\n            ep_num : The number of the IN endpoint on which data should be sent.\n            data : The data to be sent.\n            blocking : If true, this function will wait for the transfer to complete.\n        \"\"\"\n        log.trace(f\"EP{ep_num}/IN: <- {bytes(data)}\")\n\n        self._wait_until_ready_to_send(ep_num)\n        self.api.send_on_endpoint(ep_num, bytes(data))\n\n        # If we're blocking, wait until the transfer completes.\n        if blocking:\n            while not self._transfer_is_complete(ep_num, self.DEVICE_TO_HOST):\n                pass\n\n        self._clean_up_transfers_for_endpoint(ep_num, self.DEVICE_TO_HOST)\n\n\n    def read_from_endpoint(self, ep_num):\n        \"\"\"\n        Reads a block of data from the given endpoint.\n\n        Args:\n            ep_num : The number of the OUT endpoint on which data is to be rx'd.\n        \"\"\"\n\n        # Start a nonblocking read from the given endpoint...\n        self._prime_out_endpoint(ep_num)\n\n        # ... and wait for the transfer to complete.\n        while not self._transfer_is_complete(ep_num, self.HOST_TO_DEVICE):\n            pass\n\n        # Finally, return the result.\n        return self._finish_primed_read_on_endpoint(ep_num)\n\n\n    @staticmethod\n    def _endpoint_address(ep_num, direction):\n        \"\"\"\n        Returns the endpoint number that corresponds to a given direction\n        and address.\n        \"\"\"\n        if direction:\n            return ep_num | 0x80\n        else:\n            return ep_num\n\n\n    def stall_endpoint(self, ep_num, direction=0):\n        \"\"\"\n        Stalls the provided endpoint, as defined in the USB spec.\n\n        Args:\n            ep_num : The number of the endpoint to be stalled.\n        \"\"\"\n\n        in_vs_out = \"IN\" if direction else \"OUT\"\n        log.trace(\"Stalling EP{} {}\".format(ep_num, in_vs_out))\n\n        self.endpoint_stalled[ep_num] = True\n        self.api.stall_endpoint(self._endpoint_address(ep_num, direction))\n\n\n    def stall_ep0(self, direction=0):\n        \"\"\"\n        Convenience function that stalls the control endpoint zero.\n        \"\"\"\n        self.stall_endpoint(0, direction)\n\n\n    def set_address(self, address, defer=False):\n        \"\"\"\n        Sets the device address of the GreatDancer. Usually only used during\n        initial configuration.\n\n        Args:\n            address : The address that the GreatDancer should assume.\n            defer   : True iff the set_address request should wait for an active\n                      transaction to finish.\n        \"\"\"\n\n        self.api.set_address(address, 1 if defer else 0)\n\n\n    @staticmethod\n    def _decode_usb_register(transfer_result):\n        \"\"\"\n        Decodes a raw 32-bit register value from a form encoded\n        for transit as a USB control request.\n\n        Args:\n            transfer_result : The value returned by the vendor request.\n\n        Returns: The raw integer value of the given register.\n        \"\"\"\n        status_hex = codecs.encode(transfer_result[::-1], 'hex')\n        return int(status_hex, 16)\n\n\n    def _fetch_irq_status(self):\n        \"\"\"\n        Fetch the USB controller's pending-IRQ bitmask, which indicates\n        which interrupts need to be serviced.\n\n        Returns: A raw integer bitmap.\n        \"\"\"\n        return self.api.get_status(self.GET_USBSTS)\n\n\n    def _fetch_setup_status(self):\n        \"\"\"\n        Fetch the USB controller's \"pending setup packet\" bitmask, which\n        indicates which endpoints have setup packets to be read.\n\n        Returns: A raw integer bitmap.\n        \"\"\"\n        return self.api.get_status(self.GET_ENDPTSETUPSTAT)\n\n\n    def _handle_setup_events(self):\n        \"\"\"\n        Handles any outstanding setup events on the USB controller.\n        \"\"\"\n\n        # Determine if we have setup packets on any of our endpoints.\n        status = self._fetch_setup_status()\n\n        # If we don't, abort.\n        if not status:\n            return\n\n        # Otherwise, figure out which endpoints have outstanding setup events,\n        # and handle them.\n        for i in range(self.SUPPORTED_ENDPOINTS):\n            if status & (1 << i):\n                self._handle_setup_event_on_endpoint(i)\n\n\n    def _handle_setup_event_on_endpoint(self, endpoint_number):\n        \"\"\"\n        Handles a known outstanding setup event on a given endpoint.\n\n        Args:\n            endpoint_number : The endpoint number for which a setup event should be serviced.\n        \"\"\"\n\n        # HACK: to maintain API compatibility with the existing facedancer API,\n        # we need to know if a stall happens at any point during our handler.\n        self.endpoint_stalled[endpoint_number] = False\n\n        # Read the data from the SETUP stage...\n        data    = bytearray(self.api.read_setup(endpoint_number))\n        request = self.connected_device.create_request(data)\n\n        # If this is an OUT request, handle the data stage,\n        # and add it to the request.\n        is_out   = request.get_direction() == self.HOST_TO_DEVICE\n        has_data = (request.length > 0)\n\n        # Special case: if this is an OUT request with a data stage, we won't\n        # handle the request until the data stage has been completed. Instead,\n        # we'll stash away the data received in the setup stage, prime the\n        # endpoint for the data stage, and then wait for the data stage to\n        # complete, triggering a corresponding code path in\n        # in _handle_transfer_complete_on_endpoint.\n        if is_out and has_data:\n            self._prime_out_endpoint(endpoint_number)\n            self.pending_control_request = request\n            return\n\n        self.connected_device.handle_request(request)\n\n        if not is_out and not self.endpoint_stalled[endpoint_number]:\n            self.ack_status_stage(direction=self.DEVICE_TO_HOST)\n\n\n    def _fetch_transfer_status(self):\n        \"\"\"\n        Fetch the USB controller's \"completed transfer\" bitmask, which\n        indicates which endpoints have recently completed transactions.\n\n        Returns : A raw integer bitmap.\n        \"\"\"\n        return self.api.get_status(self.GET_ENDPTCOMPLETE)\n\n\n    def _transfer_is_complete(self, endpoint_number, direction):\n        \"\"\"\n        Returns true iff a given endpoint has just completed a transfer.\n        Can be used to check for completion of a non-blocking transfer.\n\n        Args:\n            endpoint_number : The endpoint number to be queried.\n            direction : The direction of the transfer. Should be self.HOST_TO_DEVICE or\n                        self.DEVICE_TO_HOST.\n        \"\"\"\n        status = self._fetch_transfer_status()\n\n        # From the LPC43xx manual: out endpoint completions start at bit zero,\n        # while in endpoint completions start at bit 16.\n        out_is_ready = (status & (1 << endpoint_number))\n        in_is_ready  = (status & (1 << (endpoint_number + 16)))\n\n        if direction == self.HOST_TO_DEVICE:\n            return out_is_ready\n        else:\n            return in_is_ready\n\n\n    def _handle_transfer_events(self):\n        \"\"\"\n        Handles any outstanding setup events on the USB controller.\n        \"\"\"\n\n        # Determine if we have ready packets on any of our endpoints.\n        status = self._fetch_transfer_status()\n\n        # If we don't, abort.\n        if not status:\n            return\n\n        # Figure out which endpoints have recently completed transfers,\n        # and clean up any transactions on those endpoints. It's important\n        # that this be done /before/ the _handle_transfer_complete... section\n        # below, as those can generate further events which will need the freed\n        # transfer descriptors.\n        # [Note that it's safe to clean up the transfer descriptors before reading,\n        #  here-- the GreatFET's USB controller has transparently moved any data\n        #  from OUT transactions into a holding buffer for us. Nice of it!]\n        for i in range(self.SUPPORTED_ENDPOINTS):\n            if status & (1 << i):\n                self._clean_up_transfers_for_endpoint(i, self.HOST_TO_DEVICE)\n\n            if status & (1 << (i + 16)):\n                self._clean_up_transfers_for_endpoint(i, self.DEVICE_TO_HOST)\n\n        # Now that we've cleaned up all relevant transfer descriptors, trigger\n        # any events that should occur due to the completed transaction.\n        for i in range(self.SUPPORTED_ENDPOINTS):\n            if status & (1 << i):\n                self._handle_transfer_complete_on_endpoint(i, self.HOST_TO_DEVICE)\n\n            if status & (1 << (i + 16)):\n                self._handle_transfer_complete_on_endpoint(i, self.DEVICE_TO_HOST)\n\n\n        # Finally, after completing all of the above, we may now have idle\n        # (unprimed) endpoints. For OUT endpoints, we'll need to re-prime them\n        # so we're ready for receipt; for IN endpoints, we'll want to give the\n        # emulated device a chance to provide new data.\n        self._handle_transfer_readiness()\n\n\n    def _finish_primed_read_on_endpoint(self, endpoint_number):\n        \"\"\"\n        Completes a non-blocking (primed) read on an OUT endpoint by reading any data\n        received since the endpoint was primed. See read_from_endpoint for an example\n        of proper use.\n\n        Args:\n            endpoint_number : The endpoint to read from.\n        \"\"\"\n\n        return self.api.finish_nonblocking_read(endpoint_number)\n\n\n    def _clean_up_transfers_for_endpoint(self, endpoint_number, direction):\n        \"\"\"\n        Cleans up any outstanding transfers on the given endpoint. This must be\n        called for each completed transaction so the relevant transfer descriptors\n        can be re-used.\n\n        There's no harm in calling this if a transaction isn't complete, but it _must_\n        be called at least once for each completed transaction.\n\n        Args:\n            endpoint_number : The endpoint number whose transfer descriptors should be cleaned\n                              up.\n            direction : The endpoint direction for which TD's should be cleaned.\n        \"\"\"\n\n        # Ask the device to clean up any transaction descriptors related to the transfer.\n        self.api.clean_up_transfer(self._endpoint_address(endpoint_number, direction))\n\n\n    def _is_control_endpoint(self, endpoint_number):\n        \"\"\"\n        Returns true iff the given endpoint number corresponds to a control endpoint.\n        \"\"\"\n\n        # FIXME: Support control endpoints other than EP0.\n        return endpoint_number == 0\n\n\n    def _handle_transfer_complete_on_endpoint(self, endpoint_number, direction):\n        \"\"\"\n        Handles a known-completed transfer on a given endpoint.\n\n        Args:\n            endpoint_number : The endpoint number for which a setup event should be serviced.\n        \"\"\"\n\n        # If a transfer has just completed on an OUT endpoint, we've just received data\n        # that we need to handle.\n        if direction == self.HOST_TO_DEVICE:\n\n            # Special case: if we've just received data on a control endpoint,\n            # we're completing a control request.\n            if self._is_control_endpoint(endpoint_number):\n\n                # If we received a setup packet to handle, handle it.\n                if self.pending_control_request:\n\n                    # Read the rest of the data from the endpoint, completing\n                    # the control request.\n                    new_data = self._finish_primed_read_on_endpoint(endpoint_number)\n\n                    # Append our new data to the pending control request.\n                    self.pending_control_request.data.extend(new_data)\n\n                    all_data_received = len(self.pending_control_request.data) == self.pending_control_request.length\n                    is_short_packet   = len(new_data) < self.max_packet_size_ep0\n\n                    if all_data_received or is_short_packet:\n\n                        # Handle the completed setup request...\n                        self.connected_device.handle_request(self.pending_control_request)\n\n                        # And clear our pending setup data.\n                        self.pending_control_request = None\n\n                    else:\n                        # Otherwise, re-prime the endpoint to grab the next packet.\n                        self._prime_out_endpoint(endpoint_number)\n\n            # Typical case: this isn't a control endpoint, so we don't have a\n            # defined packet format. Read the data and issue the corresponding\n            # callback.\n            else:\n                data = self._finish_primed_read_on_endpoint(endpoint_number)\n                log.trace(f\"EP{endpoint_number}/OUT -> {data}\")\n                self.connected_device.handle_data_available(endpoint_number, data)\n\n\n    def _fetch_transfer_readiness(self):\n        \"\"\"\n        Queries the GreatFET for a bitmap describing the endpoints that are not\n        currently primed, and thus ready to be primed again.\n        \"\"\"\n        return self.api.get_status(self.GET_ENDPTSTATUS)\n\n\n    def _fetch_endpoint_nak_status(self):\n        \"\"\"\n        Queries the GreatFET for a bitmap describing the endpoints that have issued\n        a NAK since the last time this was checked.\n        \"\"\"\n        return self.api.get_status(self.GET_ENDPTNAK)\n\n\n    def _prime_out_endpoint(self, endpoint_number):\n        \"\"\"\n        Primes an out endpoint, allowing it to receive data the next time the host chooses to send it.\n\n        Args:\n            endpoint_number : The endpoint that should be primed.\n        \"\"\"\n        self.api.start_nonblocking_read(endpoint_number)\n\n\n    def _handle_transfer_readiness(self):\n        \"\"\"\n        Check to see if any non-control IN endpoints are ready to\n        accept data from our device, and handle if they are.\n        \"\"\"\n\n        # If we haven't been configured yet, we can't have any\n        # endpoints other than the control endpoint, and we don't n\n        if not self.configuration:\n            return\n\n        # Fetch the endpoint status.\n        status = self._fetch_transfer_readiness()\n\n        # Check the status of every endpoint /except/ endpoint zero,\n        # which is always a control endpoint and set handled by our\n        # control transfer handler.\n        for interface in self.configuration.get_interfaces():\n            for endpoint in interface.get_endpoints():\n\n                # Check to see if the endpoint is ready to be primed.\n                if self._is_ready_for_priming(endpoint.number, endpoint.direction):\n\n                    # If this is an IN endpoint, we're ready to accept data to be\n                    # presented on the next IN token.\n                    if endpoint.direction == USBDirection.IN:\n                        self.connected_device.handle_buffer_available(endpoint.number)\n\n                    # If this is an OUT endpoint, we'll need to prime the endpoint to\n                    # accept new data. This provides a place for data to go once the\n                    # host sends an OUT token.\n                    else:\n                        self._prime_out_endpoint(endpoint.number)\n\n\n    def _is_ready_for_priming(self, ep_num, direction):\n        \"\"\"\n        Returns true iff the endpoint is ready to be primed.\n\n        Args:\n            ep_num : The endpoint number in question.\n            direction : The endpoint direction in question.\n        \"\"\"\n\n        # Fetch the endpoint status.\n        status = self._fetch_transfer_readiness()\n\n        ready_for_in  = (not status & (1 << (ep_num + 16)))\n        ready_for_out = (not status & (1 << (ep_num)))\n\n        if direction == self.HOST_TO_DEVICE:\n            return ready_for_out\n        else:\n            return ready_for_in\n\n\n    @classmethod\n    def _has_issued_nak(cls, ep_nak, ep_num, direction):\n        \"\"\"\n        Interprets an ENDPTNAK status result to determine\n        whether a given endpoint has NAK'd.\n\n        Args:\n            ep_nak : The status work read from the ENDPTNAK register\n            ep_num : The endpoint number in question.\n            direction : The endpoint direction in question.\n        \"\"\"\n\n        in_nak  = (ep_nak & (1 << (ep_num + 16)))\n        out_nak = (ep_nak & (1 << (ep_num)))\n\n        if direction == cls.HOST_TO_DEVICE:\n            return out_nak\n        else:\n            return in_nak\n\n\n    def _bus_reset(self):\n        \"\"\"\n        Triggers the GreatDancer to perform its side of a bus reset.\n        \"\"\"\n\n        log.debug(\"Host issued bus reset.\")\n\n        if self.connected_device:\n            self.connected_device.handle_bus_reset()\n        else:\n            self.api.bus_reset()\n\n\n    def reset(self):\n        \"\"\"\n        Triggers the GreatFET to handle its side of a bus reset.\n        \"\"\"\n        self.api.bus_reset()\n\n\n\n    def _handle_nak_events(self):\n        \"\"\"\n        Handles an event in which the GreatDancer has NAK'd an IN token.\n        \"\"\"\n\n        # If we haven't been configured yet, we can't have any\n        # endpoints other than the control endpoint, and we don't need to\n        # handle any NAKs.\n        if not self.configuration:\n            return\n\n        # Fetch the endpoint status.\n        status = self._fetch_endpoint_nak_status()\n\n        # Iterate over each usable endpoint.\n        for interface in self.configuration.get_interfaces():\n            for endpoint in interface.get_endpoints():\n\n                # Skip OUT endpoints\n                if endpoint.direction == self.HOST_TO_DEVICE:\n                    continue\n\n                # If the endpoint has NAK'd, issued the relevant callback.\n                if self._has_issued_nak(status, endpoint.number, endpoint.direction):\n                    self.connected_device.handle_nak(endpoint.number)\n\n\n\n    def _configure_endpoints(self, configuration):\n        \"\"\"\n        Configures the GreatDancer's endpoints to match the provided configuration.\n\n        Args:\n            configuration : The USBConfiguration object that describes the endpoints provided.\n        \"\"\"\n        endpoint_triplets = self._generate_endpoint_config_arguments(configuration)\n\n        # If we need to issue a configuration command, issue one.\n        # (If there are no endpoints other than control, this command will be\n        #  empty, and we can skip this.)\n        if endpoint_triplets:\n            self.api.set_up_endpoints(*endpoint_triplets)\n\n\n    def configured(self, configuration):\n        \"\"\"\n        Callback that's issued when a USBDevice is configured, e.g. by the\n        SET_CONFIGURATION request. Allows us to apply the new configuration.\n\n        Args:\n            configuration: The configuration applied by the SET_CONFIG request.\n        \"\"\"\n        self.validate_configuration(configuration)\n        self._configure_endpoints(configuration)\n        self.configuration = configuration\n\n        # If we've just set up endpoints, check to see if any of them\n        # need to be primed, or have NAKs waiting.\n        self._handle_transfer_readiness()\n        self._handle_nak_events()\n\n\n    def service_irqs(self):\n        \"\"\"\n        Core routine of the Facedancer execution/event loop. Continuously monitors the\n        GreatDancer's execution status, and reacts as events occur.\n        \"\"\"\n\n        status = self._fetch_irq_status()\n\n        # Other bits that may be of interest:\n        # D_SRI = start of frame received\n        # D_PCI = port change detect (switched between low, full, high speed state)\n        # D_SLI = device controller suspend\n        # D_UEI = USB error; completion of transaction caused error, see usb1_isr in firmware\n        # D_NAKI = both the tx/rx NAK bit and corresponding endpoint NAK enable are set\n\n        if status & self.USBSTS_D_UI:\n            self._handle_setup_events()\n            self._handle_transfer_events()\n\n        if status & self.USBSTS_D_URI:\n            self._bus_reset()\n\n        if status & self.USBSTS_D_NAKI:\n            self._handle_nak_events()\n"
  },
  {
    "path": "facedancer/backends/greathost.py",
    "content": "#\n# This file is part of Facedancer.\n#\n\"\"\" Host support for GreatFET-base devices. \"\"\"\n\nimport sys\nimport time\nimport codecs\nimport struct\n\nfrom ..core import *\nfrom ..endpoint import USBEndpoint\nfrom ..types import *\n\nfrom ..logging import log\n\n\nclass GreatDancerHostApp(FacedancerUSBHost):\n    \"\"\"\n    Class that represents a GreatFET-based USB host.\n    \"\"\"\n    app_name = \"GreatDancer Host\"\n\n\n    PORT_STATUS_REG  = 0\n    READ_STATUS_REG  = 1\n    WRITE_STATUS_REG = 2\n\n    PORT_STATUS_REGISTER_CONNECTED_MASK = (1 << 0)\n    PORT_STATUS_REGISTER_ENABLED_MASK = (1 << 2)\n    PORT_STATUS_REGISTER_POWERED_MASK = (1 << 12)\n\n    PORT_STATUS_REGISTER_SPEED_SHIFT  = 26\n    PORT_STATUS_REGISTER_SPEED_MASK  = 0b11\n\n    PORT_STATUS_REGISTER_LINE_STATE_SHIFT  = 10\n    PORT_STATUS_REGISTER_LINE_STATE_MASK   = 0b11\n\n    LINE_STATE_NAMES = {\n        0: \"SE0\",\n        1: \"J\",\n        2: \"K\",\n        3: \"No device / SE1\"\n    }\n\n    LINE_STATE_SE0 = 0\n    LINE_STATE_J = 1\n    LINE_STATE_K = 2\n    LINE_STATE_SE1 = 3\n\n    DEVICE_SPEED_LOW = 0\n    DEVICE_SPEED_FULL = 1\n    DEVICE_SPEED_HIGH = 2\n    DEVICE_SPEED_NONE = 3\n\n    STATUS_REG_SPEED_VALUES = {\n        0: DEVICE_SPEED_FULL,\n        1: DEVICE_SPEED_LOW,\n        2: DEVICE_SPEED_HIGH,\n        3: DEVICE_SPEED_NONE\n    }\n    DEVICE_SPEED_NAMES = {\n        DEVICE_SPEED_FULL: \"Full speed\",\n        DEVICE_SPEED_LOW: \"Low speed\",\n        DEVICE_SPEED_HIGH: \"High speed\",\n        DEVICE_SPEED_NONE: \"Disconnected\"\n    }\n\n    SPEED_REQUESTS = {\n        0: 1,\n        1: 0,\n        2: 2,\n        3: 3\n    }\n\n    # Endpoint directions\n    DIRECTION_IN  = 0x00\n    DIRECTION_OUT = 0x80\n\n    # Endpoint types\n    ENDPOINT_TYPE_CONTROL = 0\n\n    # Packet IDs\n    PID_SETUP = 2\n    PID_OUT = 0\n    PID_IN = 1\n\n\n    @classmethod\n    def appropriate_for_environment(cls, backend_name):\n        \"\"\"\n        Determines if the current environment seems appropriate\n        for using the GreatDancer backend.\n        \"\"\"\n\n        # Check: if we have a backend name other than greatfet,\n        # the user is trying to use something else. Abort!\n        if backend_name and backend_name != \"greatfet\":\n            return False\n\n        # If we're not explicitly trying to use something else,\n        # see if there's a connected GreatFET.\n        try:\n            import greatfet\n            greatfet.GreatFET()\n            return True\n        except ImportError:\n            log.info(\"Skipping GreatFET-based devices, as the greatfet python module isn't installed.\")\n            return False\n        except:\n            return False\n\n\n    def __init__(self, verbose=0, quirks=[], autoconnect=True, device=None):\n        \"\"\"\n        Sets up a GreatFET-based host connection.\n        \"\"\"\n\n        import greatfet\n\n        if device is None:\n            device = greatfet.GreatFET()\n\n        # Store our input args.\n        # TODO: pull into base class\n        self.device = device\n        self.verbose = verbose\n\n        # Grab a reference to our protocol definitions.\n        self.vendor_requests = greatfet.protocol.vendor_requests\n\n        if autoconnect:\n            self.connect()\n\n\n    def connect(self, device_speed=None):\n        \"\"\"\n        Sets up our host to talk to the device, including turning on VBUS.\n        \"\"\"\n        self.device.comms._vendor_request_out(self.vendor_requests.USBHOST_CONNECT)\n\n\n    def bus_reset(self, delay=0.500):\n        \"\"\"\n        Issues a \"bus reset\", requesting that the downstream device reset itself.\n\n        Args:\n            delay : The amount of time, in seconds, to wait before or after the\n                reset request. To be compliant, this should be omitted, or set\n                to 0.1s.\n        \"\"\"\n\n        # Note: we need to wait a reset delay before and after the bus reset.\n        # This allows the host to initialize _and_ then allows the device to settle.\n        time.sleep(delay)\n        self.device.comms._vendor_request_out(self.vendor_requests.USBHOST_BUS_RESET)\n        time.sleep(delay)\n\n\n    @staticmethod\n    def _decode_usb_register(transfer_result):\n        \"\"\"\n        Decodes a raw 32-bit register value from a form encoded\n        for transit as a USB control request.\n\n        Args:\n            transfer_result : The value returned by the vendor request.\n            returns : The raw integer value of the given register.\n        \"\"\"\n        status_hex = codecs.encode(transfer_result[::-1], 'hex')\n        return int(status_hex, 16)\n\n\n    def _fetch_status_register(self, register_number):\n        \"\"\"\n        Fetches a status register from the GreatDacner, and returns it\n        as an integer.\n        \"\"\"\n        raw_status = self.device.comms._vendor_request_in(self.vendor_requests.USBHOST_GET_STATUS, index=register_number, length=4)\n        return self._decode_usb_register(raw_status)\n\n\n    def _port_status(self):\n        \"\"\" Returns the raw state of the port status register. \"\"\"\n        return self._fetch_status_register(self.PORT_STATUS_REG)\n\n\n    def _get_read_status(self):\n        \"\"\" Returns the raw state of the read status word. \"\"\"\n        return self._fetch_status_register(self.READ_STATUS_REG)\n\n\n    def _get_write_status(self):\n        \"\"\" Returns the raw state of the read status word. \"\"\"\n        return self._fetch_status_register(self.WRITE_STATUS_REG)\n\n\n    def device_is_connected(self):\n        \"\"\" Returns true iff a given device is connected.  \"\"\"\n        status = self._port_status()\n        return bool(status & self.PORT_STATUS_REGISTER_CONNECTED_MASK)\n\n\n    def port_is_enabled(self):\n        \"\"\" Returns true iff the Facedancer host port's enabled. \"\"\"\n        status = self._port_status()\n        return bool(status & self.PORT_STATUS_REGISTER_ENABLED_MASK)\n\n\n    def port_is_powered(self):\n        \"\"\" Returns true iff the Facedancer host port's enabled. \"\"\"\n        status = self._port_status()\n        return bool(status & self.PORT_STATUS_REGISTER_POWERED_MASK)\n\n\n    def current_device_speed(self, as_string=False):\n        \"\"\" Returns the speed of the connected device\n\n        Args:\n            as_string : If true, returns the speed as a string for printing; otherwise\n                        returns a DEVICE_SPEED_* constant.\n        \"\"\"\n\n\n        port_speed_raw = \\\n            (self._port_status() >> self.PORT_STATUS_REGISTER_SPEED_SHIFT) & \\\n            self.PORT_STATUS_REGISTER_SPEED_MASK\n\n        # Translate from a GreatFET format device speed to a Facedancer one.\n        port_speed = self.STATUS_REG_SPEED_VALUES[port_speed_raw]\n\n        if as_string:\n            port_speed = self.DEVICE_SPEED_NAMES[port_speed]\n\n        return port_speed\n\n\n    def current_line_state(self, as_string=False):\n        \"\"\" Returns the current state of the USB differential pair\n\n        Args:\n            as_string : If true, returns the speed as a string for printing; otherwise\n                        returns a LINE_STATE_* constant.\n        \"\"\"\n\n        line_state = \\\n            (self._port_status() >> self.PORT_STATUS_REGISTER_LINE_STATE_SHIFT) & \\\n            self.PORT_STATUS_REGISTER_LINE_STATE_MASK\n\n        if as_string:\n            line_state = self.LINE_STATE_NAMES[line_state]\n\n        return line_state\n\n\n    def set_up_endpoint(self, endpoint_address_or_object, endpoint_type=None, max_packet_size=None,\n                        device_address=None, endpoint_speed=None, handle_data_toggle=None,\n                        is_control_endpoint=None):\n        \"\"\"\n        Sets up an endpoint for use. Can be used to initialize an endpoint or to update\n        its parameters. Two forms exist:\n\n        Args:\n            endpoint_object : a USBEndpoint object with the parameters to be populated\n\n        or\n\n        Args:\n            endpoint_address    : the address of the endpoint to be setup; including the direction bit\n            endpoint_type       : one of the ENDPOINT_TYPE constants that specifies the transfer mode on\n                                   the endpoint_address\n            max_packet_size     : the maximum packet size to be communicated on the given endpoint\n            device_address      : the address of the device to be communicated with; if not provided, the last\n                                   address will be used.\n            endpoint_speed      : the speed of the packets to be communicated on the endpoint; should be a\n                                   DEVICE_SPEED_* constant; if not provided, the last device's speed will be used.\n            handle_data_toggle  : true iff the hardware should automatically handle selection of data packet PIDs\n            is_control_endpoint : true iff the given packet is a for a control endpoint\n        \"\"\"\n\n        if isinstance(endpoint_address_or_object, USBEndpoint):\n            endpoint = endpoint_address_or_object\n\n            # Figure out the endpoint address from its direction and number.\n            endpoint_address = endpoint.number\n            if endpoint.direction == USBDirection.IN:\n                endpoint_address |= self.DIRECTION_IN\n\n            self.set_up_endpoint(endpoint_address, endpoint.transfer_type, endpoint.max_packet_size)\n            return\n\n        endpoint_address = endpoint_address_or_object\n        endpoint_number = endpoint_address & 0x7f\n\n        if endpoint_number > 15:\n            raise ValueError(\"cannot have an endpoint with a number > 15!\")\n\n        # Figure out defaults for any arguments not provided.\n        if device_address is None:\n            device_address = self.last_device_address\n        if endpoint_speed is None:\n            endpoint_speed = self.last_device_speed\n        if is_control_endpoint is None:\n            is_control_endpoint = (endpoint_number == 0)\n        if handle_data_toggle is None:\n            handle_data_toggle = True if not is_control_endpoint else False\n\n        # Figure out which endpoint schedule to use.\n        # FIXME: support more than the asynchronous schedule\n        endpoint_schedule = 0\n\n        # TODO: do we translate speed requests, here?\n\n        # Issue the configuration packet.\n        packet = struct.pack(\"<BBBBBHB\", endpoint_schedule, device_address, endpoint_address,\n                             endpoint_speed, is_control_endpoint, max_packet_size, handle_data_toggle)\n        self.device.comms._vendor_request_out(self.vendor_requests.USBHOST_SET_UP_ENDPOINT, data=packet)\n\n\n    def initialize_control_endpoint(self, device_address=None, device_speed=None, max_packet_size=None):\n        \"\"\"\n        Set up the device's control endpoint, so we can use it for e.g. enumeration.\n        \"\"\"\n\n        # If not overridden, apply the specification default maximum packet size.\n        # TODO: support high speed devices, here?\n        if max_packet_size is None:\n            max_packet_size = 8 if device_speed == self.DEVICE_SPEED_LOW else 64\n\n        # Set up both directions on the control endpoint.\n        self.set_up_endpoint(0 | self.DIRECTION_OUT, self.ENDPOINT_TYPE_CONTROL, max_packet_size)\n        self.set_up_endpoint(0 | self.DIRECTION_IN, self.ENDPOINT_TYPE_CONTROL, max_packet_size)\n\n\n\n\n    def send_on_endpoint(self, endpoint_number, data, is_setup=False,\n                         blocking=True, data_packet_pid=0):\n        \"\"\"\n        Sends a block of data on the provided endpoints.\n\n        Args:\n            endpoint_number : The endpoint number on which to send.\n            data            : The data to be transmitted.\n            is_setup        : True iff this transfer should begin with a SETUP token.\n            blocking        : True iff this transaction should wait for the transaction to\n                              complete.\n            data_packet_pid : The data packet PID to use (1 or 0). Ignored if the endpoint is\n                              set to automatically alternate data PIDs.\n\n        Raises an IOError on a communications error or stall.\n        \"\"\"\n\n        # Determine the PID token with which to start the request...\n        pid_token = self.PID_SETUP if is_setup else self.PID_OUT\n\n        # Issue the actual send itself.\n        # TODO: validate length\n\n        self.device.comms._vendor_request_out(self.vendor_requests.USBHOST_SEND_ON_ENDPOINT,\n                                       index=endpoint_number, value=(data_packet_pid << 8) | pid_token,\n                                       data=data)\n\n        # ... and if we're blocking, also finish it.\n        if blocking:\n            complete = False\n            stalled  = False\n\n            # Wait until we get a complete flag in the status register.\n            # XXX: This isn't entirely correct-- it'll clear too much status.\n            while not complete:\n                status = self._get_write_status()\n\n                stalled  = (status >> endpoint_number) & 0x1\n                complete = (status >> (endpoint_number + 16)) & 0x1\n\n                if stalled:\n                    raise IOError(\"Stalled!\")\n\n\n\n    def read_from_endpoint(self, endpoint_number, expected_read_size=64, data_packet_pid=0):\n        \"\"\"\n        Sends a block of data on the provided endpoints.\n\n        Args:\n            endpoint_number    : The endpoint number on which to send.\n            expected_read_size : The expected amount of data to be read.\n            data_packet_pid    : The data packet PID to use (1 or 0).\n                Ignored if the endpoint is set to automatically alternate data PIDs.\n\n        Raises an IOError on a communications error or stall.\n        \"\"\"\n\n        # Start the request...\n        self.device.comms._vendor_request_out(self.vendor_requests.USBHOST_START_NONBLOCKING_READ,\n                                       index=(data_packet_pid << 8) | endpoint_number, value=expected_read_size)\n\n        # ... and if we're blocking, also finish it.\n        complete = False\n        stalled  = False\n\n        # Wait until we get a complete flag in the status register.\n        # XXX: This isn't entirely correct-- it'll clear too much status.\n        while not complete:\n            status = self._get_read_status()\n\n            stalled  = (status >> endpoint_number) & 0x1\n            complete = (status >> (endpoint_number + 16)) & 0x1\n\n            if stalled:\n                raise IOError(\"Stalled!\")\n\n        # Figure out how muhc to read.\n        raw_length = self.device.comms._vendor_request_in(self.vendor_requests.USBHOST_GET_NONBLOCKING_LENGTH,\n                                                   index=endpoint_number, length=4)\n        length = self._decode_usb_register(raw_length)\n\n        if self.verbose > 4:\n            print(\"Supposedly, we've got {} bytes of data to read\".format(length))\n\n        # If there's no data available, we don't need to waste time reading anything.\n        if length == 0:\n            return b''\n\n        # Otherwise, read the data from the endpoint and return it.\n        data = self.device.comms._vendor_request_in(self.vendor_requests.USBHOST_FINISH_NONBLOCKING_READ,\n                                             index=endpoint_number, length=length)\n        return data.tobytes()\n"
  },
  {
    "path": "facedancer/backends/hydradancer.py",
    "content": "\"\"\"\nBackend for the Hydradancer boards.\n\nSupports 5 endpoints, with addresses between 0 and 7. Supports low, full and high-speed.\n\"\"\"\n\nimport sys\nimport logging\nimport time\nfrom array import array\nfrom time import time_ns\nfrom dataclasses import dataclass\nfrom typing import List, Dict, Any\n\nimport usb\nfrom usb.util import CTRL_TYPE_VENDOR, CTRL_RECIPIENT_DEVICE, CTRL_IN, CTRL_OUT\n\nfrom ..core           import *\nfrom ..device         import USBDevice, USBConfiguration, USBDirection, USBEndpoint\nfrom ..types          import DeviceSpeed\nfrom ..logging        import log\nfrom .base            import FacedancerBackend\n\n\n@dataclass\nclass HydradancerEvent:\n    # Events\n    EVENT_BUS_RESET = 0x0\n    EVENT_IN_BUFFER_AVAILABLE = 0x1\n    EVENT_OUT_BUFFER_AVAILABLE = 0x2\n    EVENT_NAK = 0x3\n\n    event_type : int = -1\n    value : int = -1\n\n    @staticmethod\n    def from_bytes(data : bytes):\n        return HydradancerEvent(event_type = data[0], value = data[1])\n\n    def __repr__(self):\n        return f\"event_type {self.event_type} value {self.value}\"\n\n\nclass HydradancerHostApp(FacedancerApp, FacedancerBackend):\n    \"\"\"\n    Backend for the HydraUSB3 boards.\n    \"\"\"\n    app_name = \"Hydradancer Host\"\n\n    MANUFACTURER_STRING = \"Quarkslab https://www.quarkslab.com/ & HydraBus https://hydrabus.com/\"\n\n    # USB directions\n    HOST_TO_DEVICE = 0\n    DEVICE_TO_HOST = 1\n\n    USB2_MAX_EP_IN = 16\n\n    current_setup_req = None\n\n    def __init__(self, device: USBDevice=None, verbose: int=0, quirks: List[str]=[]):\n        \"\"\"\n        Initializes the backend.\n\n        Args:\n            device  :  The device that will act as our Facedancer.   (Optional)\n            verbose : The verbosity level of the given application. (Optional)\n            quirks  :  List of USB platform quirks.                  (Optional)\n        \"\"\"\n        super().__init__(self)\n\n        self.configuration = None\n        self.pending_control_out_request = None\n        self.connected_device = None\n        self.max_ep0_packet_size = None\n\n        self.ep_transfer_queue : List[List[Any]] = [[]] * self.USB2_MAX_EP_IN\n\n        self.ep_in : Dict[int, USBEndpoint] = {}\n        self.ep_out : Dict[int, USBEndpoint] = {}\n\n        self.api = HydradancerBoard()\n        self.verbose = verbose\n        self.api.wait_board_ready()\n\n    @classmethod\n    def appropriate_for_environment(cls, backend_name: str) -> bool:\n        \"\"\"\n        Determines if the current environment seems appropriate\n        for using this backend.\n\n        Args:\n            backend_name : Backend name being requested. (Optional)\n        \"\"\"\n\n        logging.info(\"this is hydradancer hi\")\n        # Open a connection to the target device...\n        device = usb.core.find(idVendor=0x16c0, idProduct=0x27d8)\n\n        if device is not None and device.manufacturer == cls.MANUFACTURER_STRING and backend_name == \"hydradancer\":\n            return True\n\n        return False\n\n    def get_version(self):\n        \"\"\"\n        Returns information about the active Facedancer version.\n        \"\"\"\n        raise NotImplementedError\n\n    def connect(self, usb_device: USBDevice, max_packet_size_ep0: int=64, device_speed: DeviceSpeed=DeviceSpeed.FULL):\n        \"\"\"\n        Prepares backend to connect to the target host and emulate\n        a given device.\n\n        Args:\n            usb_device : The USBDevice object that represents the emulated device.\n            max_packet_size_ep0 : Max packet size for control endpoint.\n            device_speed : Requested usb speed for the Facedancer board.\n        \"\"\"\n        self.api.set_endpoint_mapping(0)\n\n        if device_speed not in [DeviceSpeed.LOW, DeviceSpeed.FULL, DeviceSpeed.HIGH]:\n            log.warning(f\"Hydradancer only supports USB Low, Full and High Speed. Ignoring requested speed: {device_speed.name}\")\n\n        self.api.set_usb2_speed(device_speed)\n        logging.info(\"connect ...\")\n\n        self.api.connect()\n\n        self.connected_device = usb_device\n\n        self.max_ep0_packet_size = max_packet_size_ep0\n\n    def disconnect(self):\n        \"\"\" Disconnects Facedancer from the target host. \"\"\"\n        logging.info(\"disconnect\")\n        self.configuration = None\n        self.pending_control_out_request = None\n        self.connected_device = None\n        self.max_ep0_packet_size = 0\n        self.ep_transfer_queue = [[]] * self.USB2_MAX_EP_IN\n        self.api.disconnect()\n\n    def reset(self):\n        \"\"\"\n        Triggers the Facedancer to handle its side of a bus reset.\n        \"\"\"\n        logging.info(\"bus reset\")\n\n    def set_address(self, address: int, defer: bool=False):\n        \"\"\"\n        Sets the device address of the Facedancer. Usually only used during\n        initial configuration.\n\n        Args:\n            address : The address the Facedancer should assume.\n            defer   : True iff the set_address request should wait for an active transaction to\n                      finish.\n        \"\"\"\n        logging.info(\"set address\")\n        self.api.set_address(address, defer)\n\n    def configured(self, configuration: USBConfiguration):\n        \"\"\"\n        Callback that's issued when a USBDevice is configured, e.g. by the\n        SET_CONFIGURATION request. Allows us to apply the new configuration.\n\n        Args:\n            configuration : The USBConfiguration object applied by the SET_CONFIG request.\n        \"\"\"\n        self.validate_configuration(configuration)\n\n        if configuration is None:\n            self.configuration = None\n            self.api.configured = False\n            logging.debug(\"unconfigured\")\n            return\n\n        self.api.reinit(keep_ep0=True)\n        endpoint_numbers = []\n\n        for interface in configuration.get_interfaces():\n            for endpoint in interface.get_endpoints():\n                ep_num = endpoint.number\n                is_ep_in = endpoint.direction == 1\n                if ep_num not in endpoint_numbers:\n                    endpoint_numbers.append(ep_num)\n\n                if is_ep_in:\n                    self.ep_in[ep_num] = endpoint\n                else:\n                    self.ep_out[ep_num] = endpoint\n\n        self.api.configure(endpoint_numbers)\n        self.configuration = configuration\n        logging.debug(\"configured\")\n\n    def read_from_endpoint(self, endpoint_number: int) -> bytes:\n        \"\"\"\n        Reads a block of data from the given endpoint.\n\n        Args:\n            endpoint_number : The number of the OUT endpoint on which data is to be rx'd.\n        \"\"\"\n        return self.api.read(endpoint_number, blocking=True)\n\n    def send_on_endpoint(self, endpoint_number: int, data: bytes, blocking: bool=True):\n        \"\"\"\n        Sends a collection of USB data on a given endpoint.\n\n        Args:\n            endpoint_number : The number of the IN endpoint on which data should be sent.\n            data : The data to be sent.\n            blocking : If true, this function should wait for the transfer to complete.\n        \"\"\"\n        if endpoint_number != 0 and not blocking and not self.api.in_buffer_empty(endpoint_number):\n            logging.debug(f\"Storing {len(data)} on ep {endpoint_number} for later\")\n            self.ep_transfer_queue[endpoint_number].append(data)\n            return\n\n        backup_len = len(data)\n        max_packet_size = self.max_ep0_packet_size if endpoint_number == 0 else self.ep_in[endpoint_number].max_packet_size\n\n        if not data:\n            self.api.send(endpoint_number, data)\n\n        while data:\n            packet = data[0:max_packet_size]\n            data = data[len(packet):]\n            logging.debug(f\"Sending {len(packet)} on ep {endpoint_number}\")\n            self.api.send(endpoint_number, packet)\n\n        # Many things to take into account here ...\n        # 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)\n        # however, if the endpoint is endpoint 0, the host knows the size of the transfer in advance so it might not request the ZLP\n        # 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)\n        # however, this could add too much latency and make enumeration fail\n        if endpoint_number == 0 and (backup_len % max_packet_size) == 0 and backup_len > 0 and backup_len != self.current_setup_req.length:\n            logging.debug(f\"Sending ZLP\")\n            self.api.send(endpoint_number, b\"\") # Sending ZLP\n\n    def ack_status_stage(self, direction: USBDirection=USBDirection.OUT, endpoint_number:int =0, blocking: bool=False):\n        \"\"\"\n        Handles the status stage of a correctly completed control request,\n        by priming the appropriate endpoint to handle the status phase.\n\n        Args:\n            direction : Determines if we're ACK'ing an IN or OUT vendor request.\n                       (This should match the direction of the DATA stage.)\n            endpoint_number : The endpoint number on which the control request\n                              occurred.\n            blocking : True if we should wait for the ACK to be fully issued\n                       before returning.\n        \"\"\"\n        if direction == USBDirection.OUT:\n            # If this was an OUT request, we'll prime the output buffer to\n            # respond with the ZLP expected during the status stage.\n            self.send_on_endpoint(endpoint_number, data=b\"\", blocking=blocking)\n\n        else:\n            # If this was an IN request, we'll need to set up a transfer descriptor\n            # so the status phase can operate correctly. This effectively reads the\n            # zero length packet from the STATUS phase.\n            self.read_from_endpoint(endpoint_number)\n\n    def stall_endpoint(self, endpoint_number:int, direction: USBDirection=USBDirection.OUT):\n        \"\"\"\n        Stalls the provided endpoint, as defined in the USB spec.\n\n        Args:\n            endpoint_number : The number of the endpoint to be stalled.\n        \"\"\"\n        in_vs_out = \"IN\" if direction else \"OUT\"\n        logging.info(f\"Stalling EP {endpoint_number} {in_vs_out}\")\n\n        self.api.stall_endpoint(endpoint_number, direction)         \n\n    def clear_halt(self, endpoint_number:int, direction: USBDirection):\n        \"\"\" Clears a halt condition on the provided non-control endpoint.\n\n        Args:\n            endpoint_number : The endpoint number\n            direction       : The endpoint direction; or OUT if not provided.\n        \"\"\"\n        logging.debug(f\"Clearing halt for endpoint {endpoint_number}\")\n        self.api.clear_halt(endpoint_number, direction)\n\n    def service_irqs(self):\n        \"\"\"\n        Core routine of the Facedancer execution/event loop. Continuously monitors the\n        Facedancer's execution status, and reacts as events occur.\n        \"\"\"\n        events = self.api.fetch_events()\n\n        if events is not None:\n            for event in events:\n                if event is None:\n                    continue\n                if event.event_type == HydradancerEvent.EVENT_BUS_RESET:\n                    self.handle_bus_reset()\n                if event.event_type == HydradancerEvent.EVENT_IN_BUFFER_AVAILABLE and event.value != 0 and (event.value in self.ep_in.keys()):\n                    self.connected_device.handle_buffer_empty(self.ep_in[event.value])\n\n        self.handle_control_request()\n        self.handle_data_endpoints()\n\n    def handle_bus_reset(self):\n        \"\"\"\n        Triggers Hydradancer to perform its side of a bus reset.\n        \"\"\"\n        if self.connected_device:\n            self.connected_device.handle_bus_reset()\n        else:\n            self.reset()\n\n    def handle_data_endpoints(self):\n        \"\"\"\n        Handle IN or OUT requests on non-control endpoints.\n        \"\"\"\n\n        # process ep OUT firsts, transfer is dictated by the host, if there is data available on an ep OUT,\n        # it should be processed before setting new IN data\n        for ep_num in self.ep_out:\n            if self.api.out_buffer_available(ep_num):\n                data = self.api.read(ep_num)\n                if data is not None:\n                    self.connected_device.handle_data_available(\n                        ep_num, data.tobytes())\n\n        for ep_num, ep in self.ep_in.items():\n            if self.api.in_buffer_empty(ep_num) and self.api.nak_on_endpoint(ep_num):\n                if len(self.ep_transfer_queue[ep_num]) != 0:\n                    max_packet_size = ep.max_packet_size\n                    packet = self.ep_transfer_queue[ep_num][0][0:max_packet_size]\n                    self.ep_transfer_queue[ep_num][0] = self.ep_transfer_queue[ep_num][0][len(packet):]\n\n                    self.api.send(ep_num, packet)\n\n                    if len(self.ep_transfer_queue[ep_num][0]) == 0:\n                        self.ep_transfer_queue[ep_num].pop(0)\n                else:\n                    self.connected_device.handle_nak(ep_num)\n\n    def handle_control_request(self):\n        if not self.api.control_buffer_available():\n            return\n\n        data = self.api.read(0)\n        if data is None:\n            return\n\n        logging.debug(\n            f\"CONTROL EP/OUT: -> size {len(data)} {bytes(data)}\")\n\n        #  inspired from moondancer and greatdancer backends\n        if self.pending_control_out_request is not None:\n            self.pending_control_out_request.data.extend(data)\n            all_data_received = len(\n                self.pending_control_out_request.data) == self.pending_control_out_request.length\n            is_short_packet = len(data) < self.max_ep0_packet_size\n\n            if all_data_received or is_short_packet:\n                self.connected_device.handle_request(\n                    self.pending_control_out_request)\n                self.pending_control_out_request = None\n        elif len(data) > 0:\n            request = self.connected_device.create_request(data)\n            is_out = request.get_direction() == self.HOST_TO_DEVICE\n            has_data = (request.length > 0)\n\n            self.current_setup_req = request\n\n            if is_out and has_data:\n                logging.debug(\"queuing Control OUT req, waiting for more data\")\n                self.pending_control_out_request = request\n                return\n\n            self.connected_device.handle_request(request)\n\n        # handle status stage of IN transfer\n        elif len(data) == 0:\n            logging.debug(\"Received ACK for IN Ctrl req\")\n\n\n\n\nclass HydradancerBoardFatalError(Exception):\n    pass\n\n\nclass HydradancerBoard():\n    \"\"\"\n    Handles the communication with the Hydradancer control board and manages the events it sends.\n    \"\"\"\n    MAX_PACKET_SIZE = 1024\n\n    # USB Vendor Requests codes\n    ENABLE_USB_CONNECTION = 50\n    SET_ADDRESS = 51\n    GET_EVENT = 52\n    SET_ENDPOINT_MAPPING = 53\n    DISABLE_USB = 54\n    SET_SPEED = 55\n    SET_EP_RESPONSE = 56\n    CHECK_HYDRADANCER_READY = 57\n    DO_BUS_RESET = 58\n    CONFIGURED = 59\n    CLEAR_HALT = 60\n\n    # Facedancer USB2 speed to Hydradancer USB2 speed\n    facedancer_to_hydradancer_speed = {\n        DeviceSpeed.LOW : 0,\n        DeviceSpeed.FULL : 1,\n        DeviceSpeed.HIGH : 2\n    }\n    \n    # Max number of events that can be sent by the board\n    # This must not be less than what is defined in the firmware\n    EVENT_QUEUE_SIZE = 100\n\n    # Endpoint states on the emulation board\n    ENDP_STATE_ACK = 0x00\n    ENDP_STATE_NAK = 0x02\n    ENDP_STATE_STALL = 0x03\n\n    # USB endpoints direction\n    HOST_TO_DEVICE = 0\n    DEVICE_TO_HOST = 1\n\n    EP_POLL_NUMBER = 1\n    SUPPORTED_EP_NUM = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]\n    INCOMPATIBLE_EP = [[], [9], [10], [11], [8, 12], [13], [14], [15], [4], [1], [2], [3], [4], [5], [6], [7]]\n\n    timeout_ms_poll = 1\n\n    def reinit(self, keep_ep0:bool = False):\n        if keep_ep0 and 0 in self.endpoints_mapping:\n            old_control_ep = self.endpoints_mapping[0]\n            self.endpoints_mapping = {0: self.endpoints_mapping[0]}\n            self.reverse_endpoints_mapping = {old_control_ep:0}\n        else:\n            self.endpoints_mapping = {}  # emulated endpoint -> control board endpoint\n            self.reverse_endpoints_mapping = {}  # control_board_endpoint -> emulated_endpoint\n\n        self.events = array('B', [0] * 2 * self.EVENT_QUEUE_SIZE)\n        # True when SET_CONFIGURATION has been received and the Hydradancer boards are configured\n        self.configured = False\n        # 0x00ff (IN status mask, 1 = emulated ep ready for priming), 0xff00 (OUT mask, data received on emulated ep)\n        self._hydradancer_status_bytes = array('B', [0] * 4)\n        self.hydradancer_status = {}\n        self.hydradancer_status[\"ep_in_status\"] = (1 << 0) & 0xff\n        self.hydradancer_status[\"ep_out_status\"] = 0x00\n        self.hydradancer_status[\"ep_in_nak\"] = 0x00\n        \n\n    def __init__(self):\n        \"\"\"\n        Get handles on the USB control board, and wait for Hydradancer to be ready\n        \"\"\"\n        self.configured = False\n        self.endpoints_mapping : Dict[int,int] = {}\n\n        self.reinit()\n\n        # Open a connection to the target device...\n        self.device = usb.core.find(idVendor=0x16c0, idProduct=0x27d8)\n\n        if self.device is None:\n            raise HydradancerBoardFatalError(\"Hydradancer board not found\")\n\n        if self.device.speed != usb.util.SPEED_SUPER:\n            raise HydradancerBoardFatalError(\n                \"Hydradancer not detected as USB3 Superspeed\")\n\n        cfg = self.device.get_active_configuration()\n        intf = cfg[(0, 0)]\n\n        # Detach the device from any kernel driver\n        for intf in cfg:\n            if self.device.is_kernel_driver_active(intf.bInterfaceNumber):\n                try:\n                    self.device.detach_kernel_driver(intf.bInterfaceNumber)\n                except usb.core.USBError as e:\n                    sys.exit(\"Could not detach kernel driver from interface({0}): {1}\".format(\n                        intf.bInterfaceNumber, str(e)))\n\n        # store the different endpoints handles we need\n        self.ep_in = list(usb.util.find_descriptor(\n            intf,\n            find_all=True,\n            custom_match=lambda e:\n            usb.util.endpoint_direction(e.bEndpointAddress) ==\n            usb.util.ENDPOINT_IN and usb.util.endpoint_address(e.bEndpointAddress) !=\n            self.EP_POLL_NUMBER))\n\n        self.ep_in = {usb.util.endpoint_address(\n            e.bEndpointAddress): e for e in self.ep_in}\n\n        self.ep_out = list(usb.util.find_descriptor(\n            intf,\n            find_all=True,\n            custom_match=lambda e:\n            usb.util.endpoint_direction(e.bEndpointAddress) ==\n            usb.util.ENDPOINT_OUT and usb.util.endpoint_address(e.bEndpointAddress) !=\n            self.EP_POLL_NUMBER))\n\n        self.ep_out = {usb.util.endpoint_address(\n            ep.bEndpointAddress): ep for ep in self.ep_out}\n\n        # the endpoint on which status information is received\n        self.ep_poll = usb.util.find_descriptor(\n            intf,\n            custom_match=lambda e:\n            usb.util.endpoint_direction(e.bEndpointAddress) ==\n            usb.util.ENDPOINT_IN and usb.util.endpoint_address(e.bEndpointAddress) ==\n            self.EP_POLL_NUMBER)\n\n        if len(self.ep_in.keys()) == 0 and len(self.ep_out.keys()) == 0:\n            logging.info(\"Dumping device configuration \\r\\n\" + str(cfg))\n            raise HydradancerBoardFatalError(\n                \"Could not fetch Hydradancer IN and OUT endpoints list\")\n        if len(self.ep_in.keys()) == 0:\n            logging.info(\"Dumping device configuration \\r\\n\" + str(cfg))\n            raise HydradancerBoardFatalError(\n                \"Could not fetch Hydradancer IN endpoints list\")\n        if len(self.ep_out.keys()) == 0:\n            logging.info(\"Dumping device configuration \\r\\n\" + str(cfg))\n            raise HydradancerBoardFatalError(\n                \"Could not fetch Hydradancer OUT endpoints list\")\n        if self.ep_out.keys() != self.ep_in.keys():\n            logging.info(\"Dumping device configuration \\r\\n\" + str(cfg))\n            raise HydradancerBoardFatalError(\n                f\"Hydradancer IN/OUT endpoints pair incomplete \\r\\nep_in {self.ep_in} \\r\\nep_out {self.ep_out}\")\n        if self.ep_poll is None:\n            logging.info(\"Dumping device configuration \\r\\n\" + str(cfg))\n            raise HydradancerBoardFatalError(\n                f\"Could not get handle on Hydradancer events endpoint (EP {self.EP_POLL_NUMBER})\")\n\n        self.endpoints_pool = set(self.ep_in.keys())\n\n        # wait until the board is ready, for instance if a disconnect was previously issued\n        self.wait_board_ready()\n\n    def connect(self):\n        \"\"\"\n        Enable the USB2 connection on the emulation board\n        \"\"\"\n        try:\n            self.device.ctrl_transfer(\n                CTRL_TYPE_VENDOR | CTRL_RECIPIENT_DEVICE | CTRL_OUT, self.ENABLE_USB_CONNECTION)\n        except (usb.core.USBTimeoutError, usb.core.USBError) as exception:\n            logging.error(exception)\n            raise HydradancerBoardFatalError(\"Error, unable to connect\") from exception\n\n    def disconnect(self):\n        \"\"\"\n        Disable the USB2 connection on the emulation board,\n        and reset internal states on both control and emulation boards.\n        \"\"\"\n        try:\n            self.device.ctrl_transfer(\n                CTRL_TYPE_VENDOR | CTRL_RECIPIENT_DEVICE | CTRL_OUT, self.DISABLE_USB)\n            usb.util.dispose_resources(self.device)\n\n        except (usb.core.USBTimeoutError, usb.core.USBError) as exception:\n            logging.error(exception)\n            raise HydradancerBoardFatalError(\"Error, unable to disconnect\") from exception\n\n    def wait_board_ready(self):\n        \"\"\"\n        Wait until the Hydradancer boards are ready, try to disconnect at some point to reset the internal states,\n        hoping it will be ready next time.\n        \"\"\"\n        #  num of checks before trying to disconnect\n        max_num_status_ready_before_disconnect = 100\n        count_status_ready = 0\n        max_disconnect = 2\n        count_disconnect = 2\n        time_after_disconnect_sec = 1\n        time_between_checks_sec = 0.01\n\n        try:\n            # check if the board is ready a first time\n            hydradancer_ready = self.device.ctrl_transfer(\n                CTRL_TYPE_VENDOR | CTRL_RECIPIENT_DEVICE | CTRL_IN, self.CHECK_HYDRADANCER_READY,\n                data_or_wLength=1, timeout=5)\n\n            # repeat max_num_status_ready_before_disconnect times\n            while (hydradancer_ready is None or hydradancer_ready == 0) and count_disconnect < max_disconnect:\n                count_status_ready += 1\n                if count_status_ready % max_num_status_ready_before_disconnect == 0 and \\\n                   count_disconnect < max_disconnect:\n                    logging.info(\n                        \"This is taking too long, disconnecting again ...\")\n                    self.disconnect()\n                    time.sleep(time_after_disconnect_sec)\n                    count_disconnect += 1\n                hydradancer_ready = self.device.ctrl_transfer(\n                    CTRL_TYPE_VENDOR | CTRL_RECIPIENT_DEVICE | CTRL_IN, self.CHECK_HYDRADANCER_READY,\n                    data_or_wLength=1, timeout=5)\n                time.sleep(time_between_checks_sec)\n\n            # if hydradancer is still not ready\n            if hydradancer_ready == 0:\n                raise HydradancerBoardFatalError(\n                    \"Hydradancer is not ready, please reset the board\")\n        except (usb.core.USBTimeoutError, usb.core.USBError) as exception:\n            logging.error(exception)\n            raise HydradancerBoardFatalError(\n                \"USB Error while waiting for Hydradancer go-ahead\") from exception\n\n    def set_endpoint_mapping(self, ep_num):\n        \"\"\"\n        Maps emulated endpoints (endpoints facing the target) to Facedancer's host endpoints (control board endpoints)\n        \"\"\"\n        if ep_num not in self.SUPPORTED_EP_NUM:\n            raise HydradancerBoardFatalError(\n                f\"Endpoint number {ep_num} not supported, supported numbers : {self.SUPPORTED_EP_NUM}\")\n\n        if len(self.endpoints_mapping.values()) >= len(self.endpoints_pool):\n            raise HydradancerBoardFatalError(\n                f\"All {len(self.endpoints_pool)} endpoints are already in use (for EP0 included)\")\n\n        if ep_num not in self.endpoints_mapping:\n            self.endpoints_mapping[ep_num] = list(\n                self.endpoints_pool - set(self.endpoints_mapping.values()))[0]\n            self.reverse_endpoints_mapping[self.endpoints_mapping[ep_num]] = ep_num\n\n        try:\n            self.device.ctrl_transfer(CTRL_TYPE_VENDOR | CTRL_RECIPIENT_DEVICE | CTRL_OUT,\n                                      self.SET_ENDPOINT_MAPPING, wValue=(\n                                          ep_num & 0x00ff) | ((self.endpoints_mapping[ep_num] << 8) & 0xff00))\n        except (usb.core.USBTimeoutError, usb.core.USBError) as exception:\n            logging.error(exception)\n            raise HydradancerBoardFatalError(\n                f\"Could not set mapping for ep {ep_num}\") from exception\n\n    def set_usb2_speed(self, device_speed: DeviceSpeed=DeviceSpeed.FULL):\n        \"\"\"\n        Set the speed of the USB2 device. Speed is physically determined by the host,\n        so the emulation board must be configured.\n        \"\"\"\n        try:\n            self.device.ctrl_transfer(\n                CTRL_TYPE_VENDOR | CTRL_RECIPIENT_DEVICE | CTRL_OUT, self.SET_SPEED, wValue=self.facedancer_to_hydradancer_speed[device_speed] & 0x00ff)\n        except (usb.core.USBTimeoutError, usb.core.USBError) as exception:\n            logging.error(exception)\n            raise HydradancerBoardFatalError(\"Error, unable to set speed\") from exception\n\n    def clear_halt(self, endpoint_number:int, direction: USBDirection):\n        try:\n            self.device.ctrl_transfer(\n                CTRL_TYPE_VENDOR | CTRL_RECIPIENT_DEVICE | CTRL_OUT, self.CLEAR_HALT, wValue=((endpoint_number & 0xff) | ((direction & 0xff) << 8)))\n        except (usb.core.USBTimeoutError, usb.core.USBError) as exception:\n            logging.error(exception)\n            raise HydradancerBoardFatalError(\"Error, unable to clear halt on endpoint {endpoint_number} direction {direction}\") from exception\n      \n    def set_address(self, address, defer=False):\n        \"\"\"\n        Set the USB address on the emulation board\n        \"\"\"\n        try:\n            self.device.ctrl_transfer(\n                CTRL_TYPE_VENDOR | CTRL_RECIPIENT_DEVICE | CTRL_OUT, self.SET_ADDRESS, address)\n        except (usb.core.USBTimeoutError, usb.core.USBError) as exception:\n            logging.error(exception)\n            raise HydradancerBoardFatalError(\n                \"Error, unable to set address on emulated device\") from exception\n\n    def stall_endpoint(self, ep_num, direction=0):\n        \"\"\"\n        Stall the ep_num endpoint on the emulation board.\n        STALL will be cleared automatically after next SETUP packet received.\n        \"\"\"\n        # Stall EP\n\n        try:\n            if ep_num == 0:\n                self.device.ctrl_transfer(\n                    CTRL_TYPE_VENDOR | CTRL_RECIPIENT_DEVICE | CTRL_OUT, self.SET_EP_RESPONSE, wValue=(ep_num | 0 << 7) | (self.ENDP_STATE_STALL << 8) & 0xff00)\n                self.device.ctrl_transfer(\n                    CTRL_TYPE_VENDOR | CTRL_RECIPIENT_DEVICE | CTRL_OUT, self.SET_EP_RESPONSE, wValue=(ep_num | 1 << 7) | (self.ENDP_STATE_STALL << 8) & 0xff00)\n            else:\n                self.device.ctrl_transfer(\n                    CTRL_TYPE_VENDOR | CTRL_RECIPIENT_DEVICE | CTRL_OUT, self.SET_EP_RESPONSE, wValue=(ep_num | direction << 7) | (self.ENDP_STATE_STALL << 8) & 0xff00)\n        except (usb.core.USBTimeoutError, usb.core.USBError) as exception:\n            logging.error(exception)\n            raise HydradancerBoardFatalError(f\"Could not stall ep {ep_num}\") from exception\n\n    def send(self, ep_num, data):\n        \"\"\"\n        Prime target endpoint ep_num.\n        \"\"\"\n        try:\n            while not self.in_buffer_empty(ep_num):\n                events = self.fetch_events()\n            logging.debug(f\"Sending len {len(data)} {data} on ep {ep_num}\")\n            self.ep_out[self.endpoints_mapping[ep_num]].write(\n                data)\n            self.hydradancer_status[\"ep_in_status\"] &= ~(0x01 << ep_num)\n            self.hydradancer_status[\"ep_in_nak\"] &= ~(0x01 << ep_num)\n        except (usb.core.USBTimeoutError, usb.core.USBError):\n            logging.error(f\"could not send data on ep {ep_num}\")\n\n    def read(self, ep_num, blocking=False):\n        \"\"\"\n        Read from target endpoint ep_num. If blocking=True, wait until the endpoint's buffer is full.\n        \"\"\"\n        logging.debug(f\"reading from ep {ep_num}\")\n        try:\n            if blocking:\n                while not self.out_buffer_available(ep_num):\n                    self.fetch_events()\n            if self.out_buffer_available(ep_num):\n                read = self.ep_in[self.endpoints_mapping[ep_num]].read(\n                    self.MAX_PACKET_SIZE)\n                logging.debug(\n                    f\"EP{ep_num}/OUT: <- size {len(read)} {bytes(read)}\")\n                self.hydradancer_status[\"ep_out_status\"] &= ~(0x01 << ep_num)\n                return read\n            return None\n        except (usb.core.USBTimeoutError, usb.core.USBError):\n            logging.error(f\"could not read data from ep {ep_num}\")\n            return None\n\n    def configure(self, endpoint_numbers):\n        if len(endpoint_numbers) > len(self.endpoints_pool):\n            raise HydradancerBoardFatalError(\n                f\"Hydradancer cannot handle {len(endpoint_numbers)} endpoints, only {len(self.endpoints_pool)}\")\n        try:\n            for number in endpoint_numbers:\n                if self.INCOMPATIBLE_EP[number] in endpoint_numbers:\n                   raise HydradancerBoardFatalError(\n                    f\"EP {number} can't be used at the same time as EPs {','.join([endpoint_numbers])}\") from exception             \n                self.set_endpoint_mapping(number)\n            self.device.ctrl_transfer(CTRL_TYPE_VENDOR | CTRL_RECIPIENT_DEVICE | CTRL_OUT,\n                                    self.CONFIGURED)\n            logging.info(f\"Endpoints mapping {self.endpoints_mapping}\")\n            self.configured = True\n        except (usb.core.USBTimeoutError, usb.core.USBError) as exception:\n            logging.error(exception)\n            raise HydradancerBoardFatalError(\n                \"Could not pass configured step on board\") from exception\n\n\n    def fetch_events(self):\n        \"\"\"\n        Poll the status of the endpoints. The state are accumulated (like on the boards),\n        and cleared when sending or reading data (which will trigger a similar clear on the boards).\n        Thus, self.ep_status should always be in sync with the endpoint's status on the boards.\n        \"\"\"\n        try:\n            # Use the endpoint type that best fits the type of request :\n            # -> for control requests, polling using ctrl transfers garanties the fastest status update.\n            #     Latency is key in the enumeration phase\n            # -> for bulk requests, polling using bulk transfers allows for more status updates to be sent,\n            #    thus increasing the speed\n            #  TODO : what about interrupt or isochronous transfers ?\n\n            if not self.configured:\n                read = self.device.ctrl_transfer(\n                    CTRL_TYPE_VENDOR | CTRL_RECIPIENT_DEVICE | CTRL_IN, self.GET_EVENT, data_or_wLength=self.events, timeout=self.timeout_ms_poll)\n            else:\n                read = self.ep_poll.read(\n                    self.events, timeout=self.timeout_ms_poll*10)\n            if read >= 2:\n                events = []\n                for i in range(0, read, 2):\n                    event = HydradancerEvent.from_bytes(self.events[i:i+2])\n                    events.append(event)\n                    logging.debug(event)\n                    if event.event_type == HydradancerEvent.EVENT_IN_BUFFER_AVAILABLE:\n                        self.hydradancer_status[\"ep_in_status\"] |= (0x1 << event.value) & 0xff\n                    elif event.event_type == HydradancerEvent.EVENT_OUT_BUFFER_AVAILABLE:\n                        self.hydradancer_status[\"ep_out_status\"] |= (0x1 << event.value) & 0xff\n                    elif event.event_type == HydradancerEvent.EVENT_NAK:\n                        self.hydradancer_status[\"ep_in_nak\"] |= (0x1 << event.value) & 0xff\n                logging.debug(f\"Hydradancer status {self.hydradancer_status}\")\n                return events\n            return None\n        except usb.core.USBTimeoutError:\n            return None\n        except usb.core.USBError as exception:\n            logging.error(exception)\n            raise HydradancerBoardFatalError(\"USB Error while fetching events\") from exception\n\n    def in_buffer_empty(self, ep_num):\n        \"\"\"\n        Returns True if the IN buffer for target endpoint ep_num is ready for priming\n        \"\"\"\n        return self.hydradancer_status[\"ep_in_status\"] & (0x1 << ep_num)\n\n    def nak_on_endpoint(self, ep_num):\n        \"\"\"\n        Returns True if the IN Endpoint has sent a NAK (meaning a host has sent an IN request)\n        \"\"\"\n        return self.hydradancer_status[\"ep_in_nak\"] & (0x1 << ep_num)\n\n    def out_buffer_available(self, ep_num):\n        \"\"\"\n        Returns True if the OUT buffer for target endpoint ep_num is full\n        \"\"\"\n        return self.hydradancer_status[\"ep_out_status\"] & (0x1 << ep_num)\n\n    def control_buffer_available(self):\n        \"\"\"\n        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.\n        \"\"\"\n        return self.out_buffer_available(0)\n"
  },
  {
    "path": "facedancer/backends/libusbhost.py",
    "content": "#\n# This file is part of Facedancer.\n#\n\"\"\" Host support for accessing libusb with a Facedancer-like syntax. \"\"\"\n\nimport sys\nimport time\nimport codecs\nimport struct\n\nimport usb\n\nfrom ..core import *\n\nclass LibUSBHostApp(FacedancerUSBHost):\n    \"\"\"\n    Class that represents a libusb-based USB host.\n    \"\"\"\n\n    app_name = \"LibUSB Host\"\n\n    @classmethod\n    def appropriate_for_environment(cls, backend_name):\n        \"\"\"\n        Determines if the current environment seems appropriate\n        for using the libusb backend.\n        \"\"\"\n\n        # For this to work, we need to somehow select a single port.\n        # The best way to do this is with BUS and PORT, so allow the user\n        # to select those using the environment.\n        # TODO: accept these via quirks?\n        if os.environ.get('LIBUSB_BUS') and os.environ.get('LIBUSB_PORT'):\n            return True\n\n        # As a stand-in, allow use if the user specifies a device's address.\n        if os.environ.get('LIBUSB_ADDRESS'):\n            return True\n\n        # Never automatically instantiate the libusb backend,\n        # as it's not a full implementation and requires host-OS oddities.\n        return False\n\n\n    def __init__(self, verbose=0, quirks=[], index=0, **kwargs):\n        \"\"\"\n        Creates a new libusb backend for communicating with a target device.\n        \"\"\"\n\n        self.verbose = verbose\n\n        # If we have a specified bus/port, accept them.\n        # TODO: accept these via quirks?\n        desired_bus = os.environ.get('LIBUSB_BUS')\n        desired_port = os.environ.get('LIBUSB_PORT')\n        if desired_bus and desired_port:\n            kwargs['bus'] = int(desired_bus)\n            kwargs['port_number'] = int(desired_port)\n\n        # If the user's searching by address, use that.\n        # TODO: accept these via quirks?\n        desired_address = os.environ.get('LIBUSB_ADDRESS')\n        if desired_address:\n            kwargs['address'] = int(desired_address)\n\n        # Open a connection to the target device...\n        usb_devices = list(usb.core.find(find_all=True, **kwargs))\n        if len(usb_devices) <= index:\n            raise DeviceNotFoundError(\"Could not find a device to connect to via libusb!\")\n        self.device = usb_devices[index]\n\n        # Detach any existing drivers, where possible.\n        try:\n            index = self.device.get_active_configuration().index\n            self.device.detach_kernel_driver(index)\n        except:\n            # FIXME: note this here, with a warning?\n            pass\n\n\n    def connect(self, device_speed=None):\n        \"\"\"\n        Sets up our host to talk to the device, including turning on VBUS.\n        \"\"\"\n        pass\n\n\n    def bus_reset(self, delay=0):\n        \"\"\"\n        Issues a \"bus reset\", requesting that the downstream device reset itself.\n\n        Args:\n            delay : The amount of time, in seconds, to wait before or after the\n                     reset request. To be compliant, this should be omitted, or set\n                     to 0.1s.\n        \"\"\"\n\n        # Note: we need to wait a reset delay before and after the bus reset.\n        # This allows the host to initialize _and_ then allows the device to settle.\n        time.sleep(delay)\n        self.device.reset()\n        time.sleep(delay)\n\n\n    def current_device_speed(self, as_string=False):\n        \"\"\" Returns the speed of the connected device\n\n        Args:\n            as_string : If true, returns the speed as a string for printing; otherwise returns\n                         a DEVICE_SPEED_* constant.\n        \"\"\"\n        return self.device.speed\n\n\n    def current_line_state(self, as_string=False):\n        \"\"\" Returns the current state of the USB differential pair\n\n        as_string : If true, returns the speed as a string for printing; otherwise\n                     returns a LINE_STATE_* constant.\n        \"\"\"\n        return None\n\n\n    def device_is_connected(self):\n        \"\"\" Returns true iff a given device is connected.  \"\"\"\n        return True\n\n\n    def port_is_enabled(self):\n        \"\"\" Returns true iff a given device is connected.  \"\"\"\n        return True\n\n\n    def port_is_powered(self):\n        \"\"\" Returns true iff a given device is connected.  \"\"\"\n        return True\n\n\n    def set_up_endpoint(self, endpoint_address_or_object, endpoint_type=None, max_packet_size=None,\n                        device_address=None, endpoint_speed=None, handle_data_toggle=None,\n                        is_control_endpoint=None):\n        \"\"\"\n        Sets up an endpoint for use. Can be used to initialize an endpoint or to update\n        its parameters. Two forms exist:\n\n        Args:\n            endpoint_object : a USBEndpoint object with the parameters to be populated\n\n        or\n\n        Args:\n            endpoint_address    : the address of the endpoint to be setup; including the direction bit\n            endpoint_type       : one of the ENDPOINT_TYPE constants that specifies the transfer mode on\n                                   the endpoint_address\n            max_packet_size     : the maximum packet size to be communicated on the given endpoint\n            device_address      : the address of the device to be communicated with; if not provided, the\n                                   last address will be used\n            endpoint_speed      : the speed of the packets to be communicated on the endpoint; should be a\n                                   DEVICE_SPEED_* constant; if not provided, the last device's speed will be used.\n            handle_data_toggle  : true iff the hardware should automatically handle selection of data packet PIDs\n            is_control_endpoint : true iff the given packet is a for a control endpoint\n        \"\"\"\n\n        # TODO: eventually support hubs / more than one device?\n\n        pass\n\n\n    def initialize_control_endpoint(self, device_address=None, device_speed=None, max_packet_size=None):\n        \"\"\"\n        Set up the device's control endpoint, so we can use it for e.g. enumeration.\n        \"\"\"\n        pass\n\n\n    def send_on_endpoint(self, endpoint_number, data, is_setup=False,\n                         blocking=True, data_packet_pid=0):\n        \"\"\"\n        Sends a block of data on the provided endpoints.\n\n        Args:\n            endpoint_number : The endpoint number on which to send.\n            data : The data to be transmitted.\n            is_setup : True iff this transfer should begin with a SETUP token.\n            blocking : True iff this transaction should wait for the transaction to complete.\n            data_packet_pid : The data packet PID to use (1 or 0). Ignored if the endpoint is set to automatically\n                    alternate data PIDs.\n\n        raises an IOError on a communications error or stall\n        \"\"\"\n        self.device.write(endpoint_number, data)\n\n\n    def read_from_endpoint(self, endpoint_number, expected_read_size=64, data_packet_pid=0):\n        \"\"\"\n        Sends a block of data on the provided endpoints.\n\n        Args:\n            endpoint_number    : The endpoint number on which to send.\n            expected_read_size : The expected amount of data to be read.\n            data_packet_pid    : The data packet PID to use (1 or 0).\n                                  Ignored if the endpoint is set to automatically alternate data PIDs.\n\n        raises an IOError on a communications error or stall\n        \"\"\"\n        data = self.device.read(endpoint_number, expected_read_size)\n        return data.tobytes()\n\n\n    def control_request_in(self, request_type, recipient, request, value=0, index=0, length=0):\n        \"\"\" Performs an IN control request.\n\n        Args:\n            request_type : Determines if this is a standard, class, or vendor request. Accepts a\n                            REQUEST_TYPE_* constant.\n            recipient    : Determines the context in which this command is interpreted. Accepts a\n                            REQUEST_RECIPIENT_* constant.\n            request      : The request number to be performed.\n            value, index : The standard USB request arguments, to be included in the setup packet.\n                            Their meaning varies depending on the request.\n            length       : The maximum length of data expected in response, or 0 if we don't expect any data back.\n        \"\"\"\n\n        request_type = self._build_request_type(True, request_type, recipient)\n        data = self.device.ctrl_transfer(request_type, request,\n                                         value, index, length)\n        return data.tobytes()\n\n\n    def control_request_out(self, request_type, recipient, request, value=0, index=0, data=[]):\n        \"\"\" Performs an OUT control request.\n\n        Args:\n            request_type : Determines if this is a standard, class, or vendor request. Accepts a\n                            REQUEST_TYPE_* constant.\n            recipient    : Determines the context in which this command is interpreted. Accepts a\n                            REQUEST_RECIPIENT_* constant.\n            request      : The request number to be performed.\n            value, index : The standard USB request arguments, to be included in the setup packet. Their meaning\n                            varies depending on the request.\n            data         : The data to be transmitted with this control request.\n        \"\"\"\n\n        request_type = self._build_request_type(True, request_type, recipient)\n        self.device.ctrl_transfer(request_type, request,\n                                         value, index, data)\n"
  },
  {
    "path": "facedancer/backends/moondancer.py",
    "content": "# MoondancerApp.py\n\nimport sys\nimport time\nimport codecs\nimport enum\nimport traceback\n\nfrom typing           import List, Tuple\n\nfrom ..core           import *\nfrom ..device         import USBDevice\nfrom ..configuration  import USBConfiguration\nfrom ..request        import USBControlRequest\nfrom ..types          import DeviceSpeed, USBDirection\n\nfrom ..logging        import log\n\nfrom .base            import FacedancerBackend\n\n\n# Quirk flags\nclass QuirkFlag(enum.IntFlag):\n    MANUAL_SET_ADDRESS: int = 0x01\n\n\n# Cynthion interrupt events\nclass InterruptEvent:\n    USB_BUS_RESET:       int = 10\n    USB_RECEIVE_CONTROL: int = 11\n    USB_RECEIVE_PACKET:  int = 12\n    USB_SEND_COMPLETE:   int = 13\n\n    def __init__(self, data: Tuple[int, int]):\n        \"\"\"\n        Parses a tuple of two bytes representing an interrupt event into an InterruptEvent.\n\n        Args:\n            data : A tuple of two bytes. The first byte is the interrupt code, the second is the endpoint number.\n        \"\"\"\n        if len(data) != 2:\n            log.error(f\"Invalid length for InterruptEvent: {len(data)}\")\n            raise ValueError(f\"Invalid length for InterruptEvent: {len(data)}\")\n\n        event = data[0]\n        endpoint_number = data[1]\n\n        if event not in [\n            InterruptEvent.USB_BUS_RESET,\n            InterruptEvent.USB_RECEIVE_CONTROL,\n            InterruptEvent.USB_RECEIVE_PACKET,\n            InterruptEvent.USB_SEND_COMPLETE\n        ]: raise ValueError(f\"Unknown InterruptEvent id: {event}\")\n\n        self.event = event\n        self.endpoint_number = endpoint_number\n\n    def __eq__(self, rhs):\n        return self.event == rhs\n\n    def __repr__(self):\n        name = \"UNKNOWN\"\n        if self.event == InterruptEvent.USB_BUS_RESET:\n            name = \"USB_BUS_RESET\"\n        elif self.event == InterruptEvent.USB_RECEIVE_CONTROL:\n            name = \"USB_RECEIVE_CONTROL\"\n        elif self.event == InterruptEvent.USB_RECEIVE_PACKET:\n            name = \"USB_RECEIVE_PACKET\"\n        elif self.event == InterruptEvent.USB_SEND_COMPLETE:\n            name = \"USB_SEND_COMPLETE\"\n        return f\"{name} {self.endpoint_number}\"\n\n\n#\n# Moondancer backend implementation\n#\nclass MoondancerApp(FacedancerApp, FacedancerBackend):\n    \"\"\"\n    Backend for using Cynthion devices as Facedancers.\n    \"\"\"\n\n    app_name = \"Moondancer\"\n\n    # Number of supported USB endpoints.\n    SUPPORTED_ENDPOINTS = 16\n\n    def __init__(self, device: USBDevice=None, verbose: int=0, quirks: List[str]=[]):\n        \"\"\"\n        Sets up a new Cynthion-backed Facedancer (Moondancer) application.\n\n        Args:\n            device  : The Cynthion device that will act as our Moondancer.\n            verbose : The verbosity level of the given application.\n        \"\"\"\n\n        log.info(\"Using the Moondancer backend.\")\n\n        import cynthion\n\n        if device is None:\n            device = cynthion.Cynthion()\n\n        self.device = device\n\n        self.device.comms.get_exclusive_access()\n\n        FacedancerApp.__init__(self, device, verbose)\n        self.connected_device = None\n\n        # Grab the raw API object from the Cynthion object.\n        # This has the low-level RPCs used for raw USB control.\n        self.api = self.device.apis.moondancer\n\n        # Initialize a dictionary that will store the last setup\n        # whether each endpoint is currently stalled.\n        self.endpoint_stalled = {}\n        for i in range(self.SUPPORTED_ENDPOINTS):\n            self.endpoint_stalled[i] = False\n\n        # Assume a max packet size of 64 until configured otherwise.\n        self.max_packet_size_ep0 = 64\n\n        # Start off by assuming we're not waiting for an OUT control transfer's\n        # data stage.  # See handle_setup_complete_on_endpoint for details.\n        self.pending_control_request = None\n\n        # Store a reference to the device's active configuration,\n        # which we'll use to know which endpoints we'll need to check\n        # for data transfer readiness.\n        self.configuration = None\n\n        #\n        # Store our list of quirks to handle.\n        #\n        if quirks:\n            self.quirks = quirks\n        else:\n            self.quirks = []\n\n        # Maintain a list of configured endpoints with form: (address, max_packet_size, USBTransferType)\n        self.configured_endpoints = dict()\n\n\n    # - Facedancer backend methods --------------------------------------------\n\n    @classmethod\n    def appropriate_for_environment(cls, backend_name: str) -> bool:\n        \"\"\"\n        Determines if the current environment seems appropriate\n        for using the Moondancer backend.\n        \"\"\"\n\n        # Check: if we have a backend name other than moondancer,\n        # the user is trying to use something else. Abort!\n        if backend_name and backend_name != \"cynthion\":\n            return False\n\n        # If we're not explicitly trying to use something else,\n        # see if there's a connected Cynthion.\n        try:\n            import cynthion\n            device = cynthion.Cynthion()\n            return device.supports_api('moondancer')\n        except ImportError:\n            log.info(\"Skipping Cynthion-based devices, as the cynthion python module isn't installed.\")\n            return False\n        except IOError:\n            log.warning(\"Found Cynthion-based device, but could not access it. (Check permissions?)\")\n            return False\n        except:\n            return False\n\n\n    def get_version(self):\n        \"\"\"\n        Returns information about the active Moondancer version.\n        \"\"\"\n        # TODO: Return the Cynthion software version, or something indicating\n        # the Cynthion API number?\n        raise NotImplementedError()\n\n\n    def connect(self, usb_device: USBDevice, max_packet_size_ep0: int=64, device_speed: DeviceSpeed=DeviceSpeed.FULL):\n        \"\"\"\n        Prepares Cynthion to connect to the target host and emulate\n        a given device.\n\n        Args:\n            usb_device : The USBDevice object that represents the device to be\n                         emulated.\n        \"\"\"\n\n        if device_speed not in [DeviceSpeed.FULL, DeviceSpeed.HIGH]:\n            log.warning(f\"Moondancer only supports USB Full and High Speed. Ignoring requested speed: {device_speed.name}\")\n\n        log.debug(f\"moondancer.connect(max_packet_size_ep0:{max_packet_size_ep0}, device_speed:{device_speed}, quirks:{self.quirks})\")\n\n        self.max_packet_size_ep0 = max_packet_size_ep0\n\n        # compute our quirk flags\n        quirks = 0\n        if 'manual_set_address' in self.quirks:\n            log.warning(\"Handling SET_ADDRESS on the target host side!\")\n            quirks |= QuirkFlag.MANUAL_SET_ADDRESS\n\n        # connect to target host\n        self.api.connect(self.max_packet_size_ep0, device_speed, quirks)\n        self.connected_device = usb_device\n\n        # get device name\n        device_name = f\"{type(self.connected_device).__module__}.{type(self.connected_device).__qualname__}\"\n\n        log.info(f\"Connected {device_speed.name} speed device '{device_name}' to target host.\")\n\n\n    def disconnect(self):\n        \"\"\" Disconnects Cynthion from the target host. \"\"\"\n\n        log.info(\"Disconnecting from target host.\")\n\n        self.device.comms.release_exclusive_access()\n\n        # disconnect from target host\n        self.api.disconnect()\n        self.connected_device = None\n\n\n    def reset(self):\n        \"\"\"\n        Triggers the Cynthion to handle its side of a bus reset.\n        \"\"\"\n\n        log.debug(f\"moondancer.bus_reset()\")\n\n        self.api.bus_reset()\n\n\n    def set_address(self, address: int, defer: bool=False):\n        \"\"\"\n        Sets the device address of Moondancer. Usually only used during\n        initial configuration.\n\n        Args:\n            address : The address that Moondancer should assume.\n            defer   : True iff the set_address request should wait for an active transaction to finish.\n        \"\"\"\n\n        log.debug(f\"moondancer.set_address({address}, {defer})\")\n\n        self.api.set_address(address, 1 if defer else 0)\n\n\n    def configured(self, configuration: USBConfiguration):\n        \"\"\"\n        Callback that's issued when a USBDevice is configured, e.g. by the\n        SET_CONFIGURATION request. Allows us to apply the new configuration.\n\n        Args:\n            configuration : The USBConfiguration object applied by the SET_CONFIG request.\n        \"\"\"\n        self.validate_configuration(configuration)\n\n        log.debug(f\"moondancer.configured({configuration})\")\n\n        if configuration is None:\n            log.error(\"Target host configuration could not be applied.\")\n            return\n\n        # If we need to issue a configuration command, issue one.\n        # (If there are no endpoints other than control, this command will be\n        #  empty, and we can skip this.)\n        endpoint_triplets = []\n\n        for interface in configuration.get_interfaces():\n            for endpoint in interface.get_endpoints():\n\n                log.debug(f\"Configuring endpoint: {endpoint}.\")\n\n                triple = (endpoint.get_address(), endpoint.max_packet_size, endpoint.transfer_type,)\n                endpoint_triplets.append(triple)\n\n        if len(endpoint_triplets):\n            self.api.configure_endpoints(*endpoint_triplets)\n            for triplet in endpoint_triplets:\n                self.configured_endpoints[triplet[0]] = triplet\n\n        # save configuration\n        self.configuration = configuration\n\n        # If we've just set up endpoints, check to see if any of them\n        # have NAKs waiting.\n        nak_status = self.api.get_nak_status()\n        self.handle_ep_in_nak_status(nak_status)\n\n        log.info(\"Target host configuration complete.\")\n\n\n    def read_from_endpoint(self, endpoint_number: int) -> bytes:\n        \"\"\"\n        Reads a block of data from the given endpoint.\n\n        Args:\n            endpoint_number : The number of the OUT endpoint on which data is to be rx'd.\n        \"\"\"\n\n        log.debug(f\"moondancer.read_from_endpoint({endpoint_number})\")\n\n        # Read from the given endpoint...\n        data = self.api.read_endpoint(endpoint_number)\n\n        # Re-enable OUT interface to receive data again...\n        self.api.ep_out_interface_enable()\n\n        log.trace(f\"  moondancer.api.read_endpoint({endpoint_number}) -> {len(data)} '{data}'\")\n\n        # Finally, return the result.\n        return data\n\n\n    def send_on_control_endpoint(self, endpoint_number: int, in_request: USBControlRequest, data: bytes, blocking: bool=True):\n        \"\"\"\n        Sends a collection of USB data in response to a IN control request by the host.\n\n        Args:\n            endpoint_number  : The number of the IN endpoint on which data should be sent.\n            requested_length : The number of bytes requested by the host.\n            data             : The data to be sent.\n            blocking         : If true, this function should wait for the transfer to complete.\n        \"\"\"\n        requested_length = in_request.length\n        self.api.write_control_endpoint(endpoint_number, requested_length, blocking, bytes(data))\n\n        log.debug(f\"moondancer.send_on_control_endpoint({endpoint_number}, {requested_length}, {len(data)}, {blocking})\")\n        log.trace(f\"  moondancer.api.write_control_endpoint({endpoint_number}, {requested_length}, {blocking}, {len(data)})\")\n\n\n    def send_on_endpoint(self, endpoint_number: int, data: bytes, blocking: bool=True):\n        \"\"\"\n        Sends a collection of USB data on a given endpoint.\n\n        Args:\n            endpoint_number : The number of the IN endpoint on which data should be sent.\n            data     : The data to be sent.\n            blocking : If true, this function will wait for the transfer to complete.\n        \"\"\"\n\n        self.api.write_endpoint(endpoint_number, blocking, bytes(data))\n\n        log.debug(f\"moondancer.send_on_endpoint({endpoint_number}, {len(data)}, {blocking})\")\n        log.trace(f\"  moondancer.api.write_endpoint({endpoint_number}, {blocking}, {len(data)})\")\n\n\n    # TODO this is only used by USBProxy - replace with \"backend.ep_prime_for_receive\" and \"backend.send_zlp\"\n    def ack_status_stage(self, direction: USBDirection=USBDirection.OUT, endpoint_number:int =0, blocking: bool=False):\n        \"\"\"\n            Handles the status stage of a correctly completed control request,\n            by priming the appropriate endpoint to handle the status phase.\n\n            Args:\n                direction : Determines if we're ACK'ing an IN or OUT vendor request.\n                            (This should match the direction of the DATA stage.)\n                endpoint_number : The endpoint number on which the control request\n                                  occurred.\n                blocking : True if we should wait for the ACK to be fully issued\n                           before returning.\n        \"\"\"\n\n        log.debug(f\"moondancer.ack_status_stage({direction.name}, {endpoint_number}, {blocking})\")\n\n        if direction == USBDirection.OUT: # HOST_TO_DEVICE\n            # If this was an OUT request, we'll prime the output buffer to\n            # respond with the ZLP expected during the status stage.\n            self.api.write_endpoint(endpoint_number, blocking, bytes([]))\n\n            log.trace(f\"  moondancer.api.write_endpoint({endpoint_number}, {blocking}, [])\")\n\n        else: # DEVICE_TO_HOST (IN)\n            # If this was an IN request, we'll need to set up a transfer descriptor\n            # so the status phase can operate correctly. This effectively reads the\n            # zero length packet from the STATUS phase.\n            self.api.ep_out_prime_receive(endpoint_number)\n\n            log.trace(f\"  moondancer.api.ep_out_prime_receive({endpoint_number})\")\n\n\n    def stall_endpoint(self, endpoint_number:int, direction: USBDirection=USBDirection.OUT):\n        \"\"\"\n        Stalls the provided endpoint, as defined in the USB spec.\n\n        Args:\n            endpoint_number : The number of the endpoint to be stalled.\n        \"\"\"\n\n        endpoint_address = (endpoint_number | 0x80) if direction else endpoint_number\n        log.debug(f\"Stalling EP{endpoint_number} {USBDirection(direction).name} (0x{endpoint_address:x})\")\n\n        # Mark endpoint number as stalled.\n        self.endpoint_stalled[endpoint_number] = True\n\n        # Stall endpoint address.\n        if direction:\n            self.api.stall_endpoint_in(endpoint_number)\n            log.debug(f\"  moondancer.api.stall_endpoint_in({endpoint_number})\")\n        else:\n            self.api.stall_endpoint_out(endpoint_number)\n            log.debug(f\"  moondancer.api.stall_endpoint_out({endpoint_number})\")\n\n\n    def clear_halt(self, endpoint_number: int, direction: USBDirection):\n        \"\"\" Clears a halt condition on the provided non-control endpoint.\n\n        Args:\n            endpoint_number : The endpoint number\n            direction       : The endpoint direction; or OUT if not provided.\n        \"\"\"\n\n        endpoint_address = (endpoint_number | 0x80) if direction else endpoint_number\n        log.debug(f\"Clearing halt EP{endpoint_number} {USBDirection(direction).name} (0x{endpoint_address:x})\")\n\n        self.api.clear_feature_endpoint_halt(endpoint_number, direction)\n        log.debug(f\"  moondancer.api.clear_feature_endpoint_halt({endpoint_number}, {direction})\")\n\n\n    def service_irqs(self):\n        \"\"\"\n        Core routine of the Facedancer execution/event loop. Continuously monitors the\n        Moondancer's execution status, and reacts as events occur.\n        \"\"\"\n\n        # Get latest interrupt events\n        events: List[Tuple[int, int]] = self.api.get_interrupt_events()\n\n        # Handle interrupt events.\n        if len(events) > 0:\n\n            # gcp doesn't seem to return a nested tuple if it's only one event\n            if isinstance(events[0], int):\n                events = [ events ]\n\n            parsed_events = [InterruptEvent(event) for event in events]\n\n            for event in parsed_events:\n                log.debug(f\"MD IRQ => {event}\")\n                if event == InterruptEvent.USB_BUS_RESET:\n                    self.handle_bus_reset()\n                elif event == InterruptEvent.USB_RECEIVE_CONTROL:\n                    self.handle_receive_control(event.endpoint_number)\n                elif event == InterruptEvent.USB_RECEIVE_PACKET and event.endpoint_number == 0:\n                    # TODO support endpoints other than EP0\n                    self.handle_receive_control_packet(event.endpoint_number)\n                elif event == InterruptEvent.USB_RECEIVE_PACKET:\n                    self.handle_receive_packet(event.endpoint_number)\n                elif event == InterruptEvent.USB_SEND_COMPLETE:\n                    self.handle_send_complete(event.endpoint_number)\n                else:\n                    log.error(f\"Unhandled interrupt event: {event}\")\n\n        # Check EP_IN NAK status for pending data requests\n        else:\n            nak_status = self.api.get_nak_status()\n            if nak_status != 0:\n                self.handle_ep_in_nak_status(nak_status)\n\n    # - Interrupt event handlers ----------------------------------------------\n\n    # USB0_BUS_RESET\n    def handle_bus_reset(self):\n        \"\"\"\n        Triggers Moondancer to perform its side of a bus reset.\n        \"\"\"\n\n        if self.connected_device:\n            self.connected_device.handle_bus_reset()\n        else:\n            self.api.bus_reset()\n\n\n    # USB0_RECEIVE_CONTROL\n    def handle_receive_control(self, endpoint_number: int):\n        \"\"\"\n        Handles a known outstanding control event on a given endpoint.\n\n        endpoint_number: The endpoint number for which a control event should be serviced.\n        \"\"\"\n\n        log.debug(f\"handle_receive_control({endpoint_number})\")\n\n        # HACK: to maintain API compatibility with the existing facedancer API,\n        # we need to know if a stall happens at any point during our handler.\n        self.endpoint_stalled[endpoint_number] = False\n\n        # Read the data from the SETUP stage...\n        data    = bytearray(self.api.read_control())\n        request = self.connected_device.create_request(data)\n\n        log.debug(f\"  moondancer.api.read_control({endpoint_number}) -> {len(data)} '{request}'\")\n\n        is_out   = request.get_direction() == USBDirection.OUT # HOST_TO_DEVICE\n        has_data = (request.length > 0)\n        log.trace(f\"  is_out:{is_out}  has_data:{has_data}\")\n\n        # Special case: if this is an OUT request with a data stage, we won't\n        # handle the request until the data stage has been completed. Instead,\n        # we'll stash away the data received in the setup stage, prime the\n        # endpoint for the data stage, and then wait for the data stage to\n        # complete, triggering a corresponding code path in\n        # in handle_transfer_complete_on_endpoint.\n        if is_out and has_data:\n            log.debug(f\"  setup packet has data - queueing read\")\n            self.pending_control_request = request\n            self.api.ep_out_prime_receive(endpoint_number)\n            return\n\n        # Pass the request to the emulated device for handling.\n        log.trace(f\"  connected_device.handle_request({request})\")\n        self.connected_device.handle_request(request)\n\n        # If it was an IN request with a data stage we now need to\n        # prime the endpoint to receive a ZLP from the host\n        # acknowledging receipt of our response.\n        if has_data and not is_out and not self.endpoint_stalled[endpoint_number]:\n            log.debug(f\"  CONTROL IN -> prime ep to receive zlp\")\n            self.api.ep_out_prime_receive(endpoint_number)\n\n\n    # USB0_RECEIVE_PACKET(0)\n    def handle_receive_control_packet(self, endpoint_number: int):\n        log.debug(f\"moondancer.handle_receive_control_packet({endpoint_number}) pending:{self.pending_control_request}\")\n\n        # Handle packet if we don't have a pending control request\n        if not self.pending_control_request:\n            data = self.api.read_endpoint(endpoint_number)\n            if len(data) == 0:\n                # It's a zlp following an IN control transfer, re-enable interface for reception on other endpoints.\n                self.api.ep_out_interface_enable()\n            else:\n                log.error(f\"Discarding {len(data)} bytes on control endpoint with no pending control request\")\n            return\n\n        # We have a pending control request with a data stage...\n        # Read the rest of the data from the endpoint, completing the control request.\n        new_data = self.api.read_endpoint(endpoint_number)\n\n        log.debug(f\"  handling control data stage: {len(new_data)} bytes\")\n        log.trace(f\"  moondancer.api.read_endpoint({endpoint_number}) -> {len(new_data)}\")\n\n        if len(new_data) == 0:\n            # It's a zlp following a control IN transfer, re-enable interface for reception on other endpoints.\n            self.api.ep_out_interface_enable()\n            log.debug(f\"ZLP ending Control IN transfer on ep: {endpoint_number}\")\n            return\n\n        # Append our new data to the pending control request.\n        self.pending_control_request.data.extend(new_data)\n\n        all_data_received = len(self.pending_control_request.data) == self.pending_control_request.length\n        is_short_packet   = len(new_data) < self.max_packet_size_ep0\n\n        if all_data_received or is_short_packet:\n            # Handle the completed setup request...\n            self.connected_device.handle_request(self.pending_control_request)\n\n            # And clear our pending setup data.\n            self.pending_control_request = None\n\n            # Finally, re-enable interface for reception on other endpoints.\n            self.api.ep_out_interface_enable()\n\n            return\n\n        # Finally, re-prime our control endpoint to receive the rest of the control data.\n        self.api.ep_out_prime_receive(endpoint_number)\n\n\n    # USB0_RECEIVE_PACKET(1...15)\n    def handle_receive_packet(self, endpoint_number: int):\n        \"\"\"\n        Handles a known-completed transfer on a given endpoint.\n\n        Args:\n            endpoint_number : The endpoint number for which the transfer should be serviced.\n        \"\"\"\n\n        log.debug(f\"moondancer.handle_receive_packet({endpoint_number})\")\n\n        # Read the data from the endpoint\n        data = self.api.read_endpoint(endpoint_number)\n\n        log.trace(f\"  moondancer.api.read_endpoint({endpoint_number}) -> {len(data)}\")\n\n        # Ignore it if it's a ZLP ack as Facedancer devices don't handle it.\n        if len(data) == 0:\n            # Finally, Prime endpoint to receive again.\n            self.api.ep_out_interface_enable()\n            log.debug(f\"  ZLP ending Bulk IN transfer on ep: {endpoint_number}\")\n            return\n\n        # Pass it to the device's handler\n        self.connected_device.handle_data_available(endpoint_number, data)\n\n        # Finally, re-enable other OUT endpoints so we can receive on them again.\n        self.api.ep_out_interface_enable()\n\n\n    # USB0_SEND_COMPLETE\n    def handle_send_complete(self, endpoint_number: int):\n        log.debug(f\"handle_send_complete({endpoint_number})\")\n        pass\n\n    # Handle pending data requests on EP_IN\n    def handle_ep_in_nak_status(self, nak_status: int):\n        nakked_endpoints = [epno for epno in range(self.SUPPORTED_ENDPOINTS) if (nak_status >> epno) & 1]\n        for endpoint_number in nakked_endpoints:\n            if endpoint_number != 0:\n                log.trace(f\"Received IN NAK on ep{endpoint_number}\")\n                self.connected_device.handle_nak(endpoint_number)\n"
  },
  {
    "path": "facedancer/backends/raspdancer.py",
    "content": "# pylint: disable=import-error\n\n#\n# Raspdancer\n#\n# Implementation of the Facedancer API that supports direct access to the MAX324x\n# chip via a RasPi's SoC SPI bus. Emulates talking to a Facedancer, but ignores\n# the details of the GreatFET protocol.\n#\n\nimport os\nimport sys\nimport time\n\nfrom ..core               import FacedancerApp\nfrom ..backends.MAXUSBApp import MAXUSBApp\n\nfrom ..logging            import log\n\n\nclass RaspdancerMaxUSBApp(MAXUSBApp):\n    app_name = \"MAXUSB\"\n    app_num = 0x00 # Not meaningful for us. TODO: Remove!\n\n    @classmethod\n    def appropriate_for_environment(cls, backend_name):\n        \"\"\"\n        Determines if the current environment seems appropriate\n        for using the GoodFET::MaxUSB backend.\n        \"\"\"\n\n        # Only ever try Raspdancer backends if the backend is set to raspdancer;\n        # we don't want to start randomly spamming a system's SPI bus.\n        if backend_name is None or backend_name != \"raspdancer\":\n            return False\n\n        # If we're not explicitly trying to use something else,\n        # see if there's a connected Raspdancer.\n        try:\n            rd = Raspdancer()\n            return True\n        except ImportError as e:\n            log.info(\"Skipping Raspdancer devices, as perquisites aren't installed ({}).\".format(e))\n            return False\n        except:\n            return False\n\n\n    def __init__(self, device=None, verbose=0, quirks=None):\n\n        if device is None:\n            device = Raspdancer(verbose=verbose)\n\n        FacedancerApp.__init__(self, device, verbose)\n\n        self.connected_device = None\n        self.enable()\n\n        if verbose > 0:\n            rev = self.read_register(self.reg_revision)\n            print(self.app_name, \"revision\", rev)\n\n        # set duplex and negative INT level (from GoodFEDMAXUSB.py)\n        self.write_register(self.reg_pin_control,\n                self.full_duplex | self.interrupt_level)\n\n\n    def init_commands(self):\n        pass\n\n    def enable(self):\n        for i in range(3):\n            self.device.set_up_comms()\n\n        if self.verbose > 0:\n            print(self.app_name, \"enabled\")\n\n    def ack_status_stage(self, blocking=False):\n        if self.verbose > 5:\n            print(self.app_name, \"sending ack!\")\n\n        self.device.transfer(b'\\x01')\n\n\n    def read_register(self, reg_num, ack=False):\n        if self.verbose > 1:\n            print(self.app_name, \"reading register 0x%02x\" % reg_num)\n\n        data = bytearray([ reg_num << 3, 0 ])\n        if ack:\n            data[0] |= 1\n\n        resp = self.device.transfer(data)\n\n        if self.verbose > 2:\n            print(self.app_name, \"read register 0x%02x has value 0x%02x\" %\n                    (reg_num, resp[1]))\n\n        return resp[1]\n\n    def write_register(self, reg_num, value, ack=False):\n        if self.verbose > 2:\n            print(self.app_name, \"writing register 0x%02x with value 0x%02x\" %\n                    (reg_num, value))\n\n        data = bytearray([ (reg_num << 3) | 2, value ])\n        if ack:\n            data[0] |= 1\n\n        self.device.transfer(data)\n\n\n    def read_bytes(self, reg, n):\n        if self.verbose > 2:\n            print(self.app_name, \"reading\", n, \"bytes from register\", reg)\n\n        data = bytes([ (reg << 3) ] + ([0] * n))\n        resp = self.device.transfer(data)\n\n        if self.verbose > 3:\n            print(self.app_name, \"read\", len(resp) - 1, \"bytes from register\", reg)\n\n        return resp[1:]\n\n    def write_bytes(self, reg, data):\n        data = bytes([ (reg << 3) | 3 ]) + data\n\n        self.device.transfer(data)\n\n        if self.verbose > 3:\n            print(self.app_name, \"wrote\", len(data) - 1, \"bytes to register\", reg)\n\n\nclass Raspdancer(object):\n    \"\"\"\n        Extended version of the Facedancer class that accepts a direct\n        SPI connection to the MAX324x chip, as used by the Raspdancer.\n    \"\"\"\n\n    def __init__(self, verbose=0):\n        \"\"\"\n            Initializes our connection to the MAXUSB device.\n        \"\"\"\n\n        import spi\n        import RPi.GPIO as GPIO\n\n        self.verbose = verbose\n        self.buffered_result = b''\n        self.last_verb = -1\n\n        self.spi = spi\n        self.gpio = GPIO\n\n        self.gpio.setwarnings(False)\n        self.gpio.setmode(self.gpio.BOARD)\n        self.reset()\n\n    def reset(self):\n        \"\"\"\n            Resets the connected MAXUSB chip.\n        \"\"\"\n        self.gpio.setup(15,  self.gpio.OUT)\n        self.gpio.output(15, self.gpio.LOW)\n        self.gpio.output(15, self.gpio.HIGH)\n\n\n    def set_up_comms(self):\n        \"\"\"\n            Sets up the Raspdancer to communicate with the MAX324x.\n        \"\"\"\n        # pin15=GPIO22 is linked to MAX3420 -RST\n        self.gpio.setup(15, self.gpio.OUT)\n        self.gpio.output(15,self.gpio.LOW)\n        self.gpio.output(15,self.gpio.HIGH)\n\n        self.spi.openSPI(speed=26000000)\n\n\n    def transfer(self, data):\n        \"\"\"\n            Emulate the facedancer's write command, which blasts data\n            directly over to the SPI bus.\n        \"\"\"\n        if isinstance(data,str):\n            data = [ord(x) for x in data]\n\n        data = tuple(data)\n        data = self.spi.transfer(data)\n\n        return bytearray(data)\n"
  },
  {
    "path": "facedancer/classes/__init__.py",
    "content": "#\n# This file is part of Facedancer.\n#\n\"\"\" Support code for USB classes. \"\"\"\n\nfrom enum import IntEnum\n\n\nclass USBDeviceClass(IntEnum):\n    \"\"\" Class representing known USB class numbers. \"\"\"\n    COMPOSITE            = 0x00\n    AUDIO                = 0x01\n    COMMUNICATIONS       = 0x02\n    HID                  = 0x03\n    PHYSICAL             = 0x05\n    IMAGE                = 0x06\n    PRINTER              = 0x07\n    MASS_STORAGE         = 0x08\n    HUB                  = 0x09\n    CDC_DATA             = 0x0A\n    SMART_CARD           = 0x0B\n    CONTENT_SECURITY     = 0x0D\n    VIDEO                = 0x0E\n    PERSONAL_HEALTHCARE  = 0x0F\n    AUDIO_VIDEO          = 0x10\n    BILLBOARD            = 0x11\n    TYPE_C_BRIDGE        = 0x12\n    DIAGNOSTIC           = 0xDC\n    WIRELESS_CONTROLLER  = 0xE0\n    MISCELLANEOUS        = 0xEF\n    APPLICATION_SPECIFIC = 0xFE\n    VENDOR_SPECIFIC      = 0xFF\n"
  },
  {
    "path": "facedancer/classes/hid/__init__.py",
    "content": "#\n# This file is part of Facedancer.\n#\n\"\"\" Code for implementing HID classes. \"\"\"\n"
  },
  {
    "path": "facedancer/classes/hid/descriptor.py",
    "content": "#\n# This file is part of Facedancer.\n#\n\"\"\" Code for implementing HID classes. \"\"\"\n\n# Support annotations on Python < 3.9\nfrom __future__  import annotations\n\nfrom enum        import IntEnum\nfrom typing      import Tuple, Iterable\n\nfrom ...descriptor import USBDescriptor, USBDescriptorTypeNumber\n\n\n#\n# Global items.\n#\n\ndef _hid_item_generator(constant) -> Tuple[int]:\n    \"\"\" Generates a HID descriptor global item entry. \"\"\"\n    # See See HID1.1 [6.2.2.1 Items Types and Tags]\n    size_code_map = {\n        0: 0b00,  # No data\n        1: 0b01,  # 1 byte\n        2: 0b10,  # 2 bytes\n        4: 0b11,  # 4 bytes\n    }\n    # Generate a function that creates a item with\n    # the relevant type...\n    def hid_item(*octets):\n        size = len(octets)\n        if size not in size_code_map:\n            raise ValueError(\n                f\"HID short item can only have 0, 1, 2 or 4 data bytes, got {size}\"\n            )\n\n        size_code = size_code_map[size]\n        prefix = constant | size_code\n\n        return (prefix, *octets)\n\n    # ... and return it.\n    return hid_item\n\n\ndef _io_item_generator(type_constant) -> Tuple[int]:\n\n    # Generate a function that creates a item with\n    # the relevant type...\n    def hid_io_item(\n            constant=False,\n            variable=False,\n            relative=False,\n            wrap=False,\n            nonlinear=False,\n            preferred_state=True,\n            nullable=False,\n            buffered_bytes=False\n        ):\n\n        # If we have a buffered bytes byte, include it.\n        item_length = 2 if buffered_bytes else 1\n\n        # Build the relevant item.\n        # See HID1.1 [6.2.2.4]\n        item  = (1 << 0) if constant  else 0\n        item |= (1 << 1) if variable  else 0\n        item |= (1 << 2) if relative  else 0\n        item |= (1 << 3) if wrap      else 0\n        item |= (1 << 4) if nonlinear else 0\n        item |= 0 if preferred_state  else (1 << 5)\n        item |= (1 << 6) if nullable else 0\n\n        # Build the item, and return it.\n        extra = (1,) if buffered_bytes else ()\n        return (type_constant | item_length, item, *extra)\n\n    # ... and return our function.\n    return hid_io_item\n\n\n#\n# Main items.\n#\n\nINPUT              =  _io_item_generator(0b1000_00_00)\nOUTPUT             =  _io_item_generator(0b1001_00_00)\nFEATURE            =  _io_item_generator(0b1011_00_00)\nCOLLECTION         = _hid_item_generator(0b1010_00_00)\nEND_COLLECTION     = lambda : (0b1100_00_00,)\n\n\n# Note: the odd separation of the last two bits here is due to\n# the formatting of the USB specification (and due to the fact)\n# that those bits are overridden, and thus always should be zero.\nUSAGE_PAGE         = _hid_item_generator(0b0000_01_00)\nLOGICAL_MINIMUM    = _hid_item_generator(0b0001_01_00)\nLOGICAL_MAXIMUM    = _hid_item_generator(0b0010_01_00)\nPHYSICAL_MINIMUM   = _hid_item_generator(0b0011_01_00)\nPHYSICAL_MAXIMUM   = _hid_item_generator(0b0100_01_00)\nUNIT_EXPONENT      = _hid_item_generator(0b0101_01_00)\nUNIT               = _hid_item_generator(0b0110_01_00)\nREPORT_SIZE        = _hid_item_generator(0b0111_01_00)\nREPORT_ID          = _hid_item_generator(0b1000_01_00)\nREPORT_COUNT       = _hid_item_generator(0b1001_01_00)\nPUSH               = _hid_item_generator(0b1010_01_00)\nPOP                = _hid_item_generator(0b1011_01_00)\n\n#\n# Local items.\n#\nUSAGE              = _hid_item_generator(0b0000_10_00)\nUSAGE_MINIMUM      = _hid_item_generator(0b0001_10_00)\nUSAGE_MAXIMUM      = _hid_item_generator(0b0010_10_00)\nDESGINATOR_INDEX   = _hid_item_generator(0b0011_10_00)\nDESGINATOR_MINIMUM = _hid_item_generator(0b0100_10_00)\nDESGINATOR_MAXIMUM = _hid_item_generator(0b0101_10_00)\nSTRING_INDEX       = _hid_item_generator(0b0111_10_00)\nSTRING_MINIMUM     = _hid_item_generator(0b1000_10_00)\nSTRING_MAXIMUM     = _hid_item_generator(0b1001_10_00)\nDELIMITER          = _hid_item_generator(0b1010_10_00)\n\n\nclass HIDCollection(IntEnum):\n    \"\"\" HID collections; from HID1.1 [6.2.2.4]. \"\"\"\n    PHYSICAL       = 0x00\n    APPLICATION    = 0x01\n    LOGICAL        = 0x02\n    REPORT         = 0x03\n    NAMED_ARRAY    = 0x04\n    USAGE_SWITCH   = 0x05\n    USAGE_MODIFIER = 0x06\n    VENDOR         = 0xFF\n\n\nclass HIDReportDescriptor(USBDescriptor):\n    \"\"\" Descriptor class representing a HID report descriptor. \"\"\"\n\n    # Parameter where the user defines the descriptor's fields.\n    fields: Iterable[bytes] = ()\n\n    # Mark this as a HID report descriptor.\n    type_number : int = USBDescriptorTypeNumber.REPORT\n    raw         : None | bytes = None\n\n    def __call__(self, index=0):\n        \"\"\" Converts the descriptor object into raw bytes. \"\"\"\n\n        if self.raw is not None:\n            return self.raw\n\n        raw = bytearray()\n\n        # Squish together all of our fields to make a descriptor.\n        for field in self.fields:\n            raw.extend(field)\n\n        return bytes(raw)\n"
  },
  {
    "path": "facedancer/classes/hid/keyboard.py",
    "content": "#\n# This file is part of Facedancer.\n#\n\"\"\" Helpers for HID keyboards. \"\"\"\n\nimport string\nfrom enum import IntEnum, IntFlag\n\n\n# Table mapping ASCII codes to their equivalent HID keycodes.\n# From the Adafruit HID library; https://github.com/adafruit/Adafruit_CircuitPython_HID/.\n# Used under copyright exemption (as these are facts, rather than implementation).\n#\n# Like their table; the most significant bit is used to indicate whether we should press shift.\n#\n_ASCII_TO_KEYCODE = (\n    b'\\x00'    # NUL\n    b'\\x00'    # SOH\n    b'\\x00'    # STX\n    b'\\x00'    # ETX\n    b'\\x00'    # EOT\n    b'\\x00'    # ENQ\n    b'\\x00'    # ACK\n    b'\\x00'    # BEL \\a\n    b'\\x2a'    # BS BACKSPACE \\b (called DELETE in the usb.org document)\n    b'\\x2b'    # TAB \\t\n    b'\\x28'    # LF \\n (called Return or ENTER in the usb.org document)\n    b'\\x00'    # VT \\v\n    b'\\x00'    # FF \\f\n    b'\\x00'    # CR \\r\n    b'\\x00'    # SO\n    b'\\x00'    # SI\n    b'\\x00'    # DLE\n    b'\\x00'    # DC1\n    b'\\x00'    # DC2\n    b'\\x00'    # DC3\n    b'\\x00'    # DC4\n    b'\\x00'    # NAK\n    b'\\x00'    # SYN\n    b'\\x00'    # ETB\n    b'\\x00'    # CAN\n    b'\\x00'    # EM\n    b'\\x00'    # SUB\n    b'\\x29'    # ESC\n    b'\\x00'    # FS\n    b'\\x00'    # GS\n    b'\\x00'    # RS\n    b'\\x00'    # US\n    b'\\x2c'    # SPACE\n    b'\\x9e'    # ! x1e|SHIFT_FLAG (shift 1)\n    b'\\xb4'    # \" x34|SHIFT_FLAG (shift ')\n    b'\\xa0'    # # x20|SHIFT_FLAG (shift 3)\n    b'\\xa1'    # $ x21|SHIFT_FLAG (shift 4)\n    b'\\xa2'    # % x22|SHIFT_FLAG (shift 5)\n    b'\\xa4'    # & x24|SHIFT_FLAG (shift 7)\n    b'\\x34'    # '\n    b'\\xa6'    # ( x26|SHIFT_FLAG (shift 9)\n    b'\\xa7'    # ) x27|SHIFT_FLAG (shift 0)\n    b'\\xa5'    # * x25|SHIFT_FLAG (shift 8)\n    b'\\xae'    # + x2e|SHIFT_FLAG (shift =)\n    b'\\x36'    # ,\n    b'\\x2d'    # -\n    b'\\x37'    # .\n    b'\\x38'    # /\n    b'\\x27'    # 0\n    b'\\x1e'    # 1\n    b'\\x1f'    # 2\n    b'\\x20'    # 3\n    b'\\x21'    # 4\n    b'\\x22'    # 5\n    b'\\x23'    # 6\n    b'\\x24'    # 7\n    b'\\x25'    # 8\n    b'\\x26'    # 9\n    b'\\xb3'    # : x33|SHIFT_FLAG (shift ;)\n    b'\\x33'    # ;\n    b'\\xb6'    # < x36|SHIFT_FLAG (shift ,)\n    b'\\x2e'    # =\n    b'\\xb7'    # > x37|SHIFT_FLAG (shift .)\n    b'\\xb8'    # ? x38|SHIFT_FLAG (shift /)\n    b'\\x9f'    # @ x1f|SHIFT_FLAG (shift 2)\n    b'\\x84'    # A x04|SHIFT_FLAG (shift a)\n    b'\\x85'    # B x05|SHIFT_FLAG (etc.)\n    b'\\x86'    # C x06|SHIFT_FLAG\n    b'\\x87'    # D x07|SHIFT_FLAG\n    b'\\x88'    # E x08|SHIFT_FLAG\n    b'\\x89'    # F x09|SHIFT_FLAG\n    b'\\x8a'    # G x0a|SHIFT_FLAG\n    b'\\x8b'    # H x0b|SHIFT_FLAG\n    b'\\x8c'    # I x0c|SHIFT_FLAG\n    b'\\x8d'    # J x0d|SHIFT_FLAG\n    b'\\x8e'    # K x0e|SHIFT_FLAG\n    b'\\x8f'    # L x0f|SHIFT_FLAG\n    b'\\x90'    # M x10|SHIFT_FLAG\n    b'\\x91'    # N x11|SHIFT_FLAG\n    b'\\x92'    # O x12|SHIFT_FLAG\n    b'\\x93'    # P x13|SHIFT_FLAG\n    b'\\x94'    # Q x14|SHIFT_FLAG\n    b'\\x95'    # R x15|SHIFT_FLAG\n    b'\\x96'    # S x16|SHIFT_FLAG\n    b'\\x97'    # T x17|SHIFT_FLAG\n    b'\\x98'    # U x18|SHIFT_FLAG\n    b'\\x99'    # V x19|SHIFT_FLAG\n    b'\\x9a'    # W x1a|SHIFT_FLAG\n    b'\\x9b'    # X x1b|SHIFT_FLAG\n    b'\\x9c'    # Y x1c|SHIFT_FLAG\n    b'\\x9d'    # Z x1d|SHIFT_FLAG\n    b'\\x2f'    # [\n    b'\\x31'    # \\ backslash\n    b'\\x30'    # ]\n    b'\\xa3'    # ^ x23|SHIFT_FLAG (shift 6)\n    b'\\xad'    # _ x2d|SHIFT_FLAG (shift -)\n    b'\\x35'    # `\n    b'\\x04'    # a\n    b'\\x05'    # b\n    b'\\x06'    # c\n    b'\\x07'    # d\n    b'\\x08'    # e\n    b'\\x09'    # f\n    b'\\x0a'    # g\n    b'\\x0b'    # h\n    b'\\x0c'    # i\n    b'\\x0d'    # j\n    b'\\x0e'    # k\n    b'\\x0f'    # l\n    b'\\x10'    # m\n    b'\\x11'    # n\n    b'\\x12'    # o\n    b'\\x13'    # p\n    b'\\x14'    # q\n    b'\\x15'    # r\n    b'\\x16'    # s\n    b'\\x17'    # t\n    b'\\x18'    # u\n    b'\\x19'    # v\n    b'\\x1a'    # w\n    b'\\x1b'    # x\n    b'\\x1c'    # y\n    b'\\x1d'    # z\n    b'\\xaf'    # { x2f|SHIFT_FLAG (shift [)\n    b'\\xb1'    # | x31|SHIFT_FLAG (shift \\)\n    b'\\xb0'    # } x30|SHIFT_FLAG (shift ])\n    b'\\xb5'    # ~ x35|SHIFT_FLAG (shift `)\n    b'\\x4c'    # DEL DELETE (called Forward Delete in usb.org document)\n)\n\n\n\nclass KeyboardModifiers(IntFlag):\n    MOD_LEFT_CTRL   = 0x01\n    MOD_LEFT_SHIFT  = 0x02\n    MOD_LEFT_ALT    = 0x04\n    MOD_LEFT_META   = 0x08\n    MOD_RIGHT_CTRL  = 0x10\n    MOD_RIGHT_SHIFT = 0x20\n    MOD_RIGHT_ALT   = 0x40\n    MOD_RIGHT_META  = 0x80\n\n\nclass KeyboardKeys(IntEnum):\n    NONE            = 0x00 # No key pressed\n    ERR_OVF         = 0x01 #  Keyboard Error Roll Over - used for all slots if too many keys are pressed (\"Phantom key\")\n    A               = 0x04 # Keyboard a and A\n    B               = 0x05 # Keyboard b and B\n    C               = 0x06 # Keyboard c and C\n    D               = 0x07 # Keyboard d and D\n    E               = 0x08 # Keyboard e and E\n    F               = 0x09 # Keyboard f and F\n    G               = 0x0a # Keyboard g and G\n    H               = 0x0b # Keyboard h and H\n    I               = 0x0c # Keyboard i and I\n    J               = 0x0d # Keyboard j and J\n    K               = 0x0e # Keyboard k and K\n    L               = 0x0f # Keyboard l and L\n    M               = 0x10 # Keyboard m and M\n    N               = 0x11 # Keyboard n and N\n    O               = 0x12 # Keyboard o and O\n    P               = 0x13 # Keyboard p and P\n    Q               = 0x14 # Keyboard q and Q\n    R               = 0x15 # Keyboard r and R\n    S               = 0x16 # Keyboard s and S\n    T               = 0x17 # Keyboard t and T\n    U               = 0x18 # Keyboard u and U\n    V               = 0x19 # Keyboard v and V\n    W               = 0x1a # Keyboard w and W\n    X               = 0x1b # Keyboard x and X\n    Y               = 0x1c # Keyboard y and Y\n    Z               = 0x1d # Keyboard z and Z\n    NUM_1           = 0x1e # Keyboard 1 and !\n    NUM_2           = 0x1f # Keyboard 2 and @\n    NUM_3           = 0x20 # Keyboard 3 and #\n    NUM_4           = 0x21 # Keyboard 4 and $\n    NUM_5           = 0x22 # Keyboard 5 and %\n    NUM_6           = 0x23 # Keyboard 6 and ^\n    NUM_7           = 0x24 # Keyboard 7 and &\n    NUM_8           = 0x25 # Keyboard 8 and *\n    NUM_9           = 0x26 # Keyboard 9 and (\n    NUM_0           = 0x27 # Keyboard 0 and )\n    ENTER           = 0x28 # Keyboard Return (ENTER)\n    ESC             = 0x29 # Keyboard ESCAPE\n    BACKSPACE       = 0x2a # Keyboard DELETE (Backspace)\n    TAB             = 0x2b # Keyboard Tab\n    SPACE           = 0x2c # Keyboard Spacebar\n    MINUS           = 0x2d # Keyboard - and _\n    EQUAL           = 0x2e # Keyboard = and +\n    LEFTBRACE       = 0x2f # Keyboard [ and {\n    RIGHTBRACE      = 0x30 # Keyboard ] and }\n    BACKSLASH       = 0x31 # Keyboard \\ and |\n    HASHTILDE       = 0x32 # Keyboard Non-US # and ~\n    SEMICOLON       = 0x33 # Keyboard ; and :\n    APOSTROPHE      = 0x34 # Keyboard ' and \"\n    GRAVE           = 0x35 # Keyboard ` and ~\n    COMMA              = 0x36 # Keyboard, and <\n    DOT                = 0x37 # Keyboard . and >\n    SLASH              = 0x38 # Keyboard / and ?\n    CAPSLOCK           = 0x39 # Keyboard Caps Lock\n    F1                 = 0x3a # Keyboard F1\n    F2                 = 0x3b # Keyboard F2\n    F3                 = 0x3c # Keyboard F3\n    F4                 = 0x3d # Keyboard F4\n    F5                 = 0x3e # Keyboard F5\n    F6                 = 0x3f # Keyboard F6\n    F7                 = 0x40 # Keyboard F7\n    F8                 = 0x41 # Keyboard F8\n    F9                 = 0x42 # Keyboard F9\n    F10                = 0x43 # Keyboard F10\n    F11                = 0x44 # Keyboard F11\n    F12                = 0x45 # Keyboard F12\n    SYSRQ              = 0x46 # Keyboard Print Screen\n    SCROLLLOCK         = 0x47 # Keyboard Scroll Lock\n    PAUSE              = 0x48 # Keyboard Pause\n    INSERT             = 0x49 # Keyboard Insert\n    HOME               = 0x4a # Keyboard Home\n    PAGEUP             = 0x4b # Keyboard Page Up\n    DELETE             = 0x4c # Keyboard Delete Forward\n    END                = 0x4d # Keyboard End\n    PAGEDOWN           = 0x4e # Keyboard Page Down\n    RIGHT              = 0x4f # Keyboard Right Arrow\n    LEFT               = 0x50 # Keyboard Left Arrow\n    DOWN               = 0x51 # Keyboard Down Arrow\n    UP                 = 0x52 # Keyboard Up Arrow\n    NUMLOCK            = 0x53 # Keyboard Num Lock and Clear\n    KPSLASH            = 0x54 # Keypad /\n    KPASTERISK         = 0x55 # Keypad *\n    KPMINUS            = 0x56 # Keypad -\n    KPPLUS             = 0x57 # Keypad +\n    KPENTER            = 0x58 # Keypad ENTER\n    KP1                = 0x59 # Keypad 1 and End\n    KP2                = 0x5a # Keypad 2 and Down Arrow\n    KP3                = 0x5b # Keypad 3 and PageDn\n    KP4                = 0x5c # Keypad 4 and Left Arrow\n    KP5                = 0x5d # Keypad 5\n    KP6                = 0x5e # Keypad 6 and Right Arrow\n    KP7                = 0x5f # Keypad 7 and Home\n    KP8                = 0x60 # Keypad 8 and Up Arrow\n    KP9                = 0x61 # Keypad 9 and Page Up\n    KP0                = 0x62 # Keypad 0 and Insert\n    KPDOT              = 0x63 # Keypad . and Delete\n    COMPOSE            = 0x65 # Keyboard Application\n    POWER              = 0x66 # Keyboard Power\n    KPEQUAL            = 0x67 # Keypad   =\n    F13                = 0x68 # Keyboard F13\n    F14                = 0x69 # Keyboard F14\n    F15                = 0x6a # Keyboard F15\n    F16                = 0x6b # Keyboard F16\n    F17                = 0x6c # Keyboard F17\n    F18                = 0x6d # Keyboard F18\n    F19                = 0x6e # Keyboard F19\n    F20                = 0x6f # Keyboard F20\n    F21                = 0x70 # Keyboard F21\n    F22                = 0x71 # Keyboard F22\n    F23                = 0x72 # Keyboard F23\n    F24                = 0x73 # Keyboard F24\n    OPEN               = 0x74 # Keyboard Execute\n    HELP               = 0x75 # Keyboard Help\n    PROPS              = 0x76 # Keyboard Menu\n    FRONT              = 0x77 # Keyboard Select\n    STOP               = 0x78 # Keyboard Stop\n    AGAIN              = 0x79 # Keyboard Again\n    UNDO               = 0x7a # Keyboard Undo\n    CUT                = 0x7b # Keyboard Cut\n    COPY               = 0x7c # Keyboard Copy\n    PASTE              = 0x7d # Keyboard Paste\n    FIND               = 0x7e # Keyboard Find\n    MUTE               = 0x7f # Keyboard Mute\n    VOLUMEUP           = 0x80 # Keyboard Volume Up\n    VOLUMEDOWN         = 0x81 # Keyboard Volume Down\n    KPCOMMA            = 0x85 # Keypad Comma\n    RO                 = 0x87 # Keyboard International1\n    KATAKANAHIRAGANA   = 0x88 # Keyboard International2\n    YEN                = 0x89 # Keyboard International3\n    HENKAN             = 0x8a # Keyboard International4\n    MUHENKAN           = 0x8b # Keyboard International5\n    KPJPCOMMA          = 0x8c # Keyboard International6\n    HANGEUL            = 0x90 # Keyboard LANG1\n    HANJA              = 0x91 # Keyboard LANG2\n    KATAKANA           = 0x92 # Keyboard LANG3\n    HIRAGANA           = 0x93 # Keyboard LANG4\n    ZENKAKUHANKAKU     = 0x94 # Keyboard LANG5\n    #SYSRQ              = 0x9a # Keyboard SysReq/Attention\n    KEYPAD_00          = 0xb0 # Keypad 00\n    KEYPAD_000         = 0xb1 #  Keypad 000\n    KPLEFTPAREN        = 0xb6 # Keypad (\n    KPRIGHTPAREN       = 0xb7 # Keypad )\n    LEFTCTRL           = 0xe0 # Keyboard Left Control\n    LEFTSHIFT          = 0xe1 # Keyboard Left Shift\n    LEFTALT            = 0xe2 # Keyboard Left Alt\n    LEFTMETA           = 0xe3 # Keyboard Left GUI\n    RIGHTCTRL          = 0xe4 # Keyboard Right Control\n    RIGHTSHIFT         = 0xe5 # Keyboard Right Shift\n    RIGHTALT           = 0xe6 # Keyboard Right Alt\n    RIGHTMETA          = 0xe7 # Keyboard Right GUI\n    MEDIA_PLAYPAUSE    = 0xe8\n    MEDIA_STOPCD       = 0xe9\n    MEDIA_PREVIOUSSONG = 0xea\n    MEDIA_NEXTSONG     = 0xeb\n    MEDIA_EJECTCD      = 0xec\n    MEDIA_VOLUMEUP     = 0xed\n    MEDIA_VOLUMEDOWN   = 0xee\n    MEDIA_MUTE         = 0xef\n    MEDIA_WWW          = 0xf0\n    MEDIA_BACK         = 0xf1\n    MEDIA_FORWARD      = 0xf2\n    MEDIA_STOP         = 0xf3\n    MEDIA_FIND         = 0xf4\n    MEDIA_SCROLLUP     = 0xf5\n    MEDIA_SCROLLDOWN   = 0xf6\n    MEDIA_EDIT         = 0xf7\n    MEDIA_SLEEP        = 0xf8\n    MEDIA_COFFEE       = 0xf9\n    MEDIA_REFRESH      = 0xfa\n    MEDIA_CALC         = 0xfb\n\n\n    @classmethod\n    def get_scancode_for_ascii(cls, letter_or_code):\n        \"\"\" Returns the (modifiers, scancode) used to type a given ASCII letter. \"\"\"\n\n        # Look up the relevant ASCII code in our table.\n        ascii_code = letter_or_code if isinstance(letter_or_code, int) else ord(letter_or_code)\n        composite = _ASCII_TO_KEYCODE[ascii_code]\n\n        # The Adafruit table uses bits [6:0] to indicate keycode; and bit [7] to indicate\n        # if shift is necessary.\n        modifiers = KeyboardModifiers.MOD_LEFT_SHIFT if (composite & 0x80) else 0\n        scancode  = composite & 0x7F\n\n        return (modifiers, scancode)\n"
  },
  {
    "path": "facedancer/classes/hid/usage.py",
    "content": "#\n# This file is part of Facedancer.\n#\n\"\"\" Code for working with HID usages. \"\"\"\n\n\nfrom enum import IntEnum\n\n\nclass HIDUsagePage(IntEnum):\n    \"\"\" HID Usage Page numbers; from USB HID Usage Tables [Table 1]. \"\"\"\n    GENERIC_DESKTOP      = 0x01\n    SIMULATION           = 0x02\n    VR                   = 0x03\n    SPORT                = 0x04\n    GAME                 = 0x05\n    GENERIC              = 0x06\n    KEYBOARD             = 0x07\n    LEDS                 = 0x08\n    BUTTONS              = 0x09\n    ORDINAL              = 0x0A\n    TELEPHONY            = 0x0B\n    CONSUMER             = 0x0C\n    DIGITIZER            = 0x0D\n    PID                  = 0x0F\n    UNICODE              = 0x10\n    ALPHANUMERIC_DISPLAY = 0x14\n    MEDICAL_INSTRUMENTS  = 0x40\n    BARCODE_SCANNER      = 0x8C\n    SCALE                = 0x8D\n    MAGNETIC_STRIPE      = 0x8E\n    CAMERA_CONTROL       = 0x90\n    ARCADE               = 0x91\n    VENDOR_DEFINED       = 0xFFFF\n\n\nclass HIDGenericDesktopUsage(IntEnum):\n    \"\"\" HID Usages for Generic Desktop Control; from [Table 6]. \"\"\"\n    POINTER                    = 0x01\n    MOUSE                      = 0x02\n    JOYSTICK                   = 0x04\n    GAMEPAD                    = 0x05\n    KEYBOARD                   = 0x06\n    KEYPAD                     = 0x07\n    MULTIAXIS_CONTROLLER       = 0x08\n    TABLET_PC_SYSTEM_CONTROLS  = 0x09\n    X                          = 0x30\n    Y                          = 0x31\n    Z                          = 0x32\n    RX                         = 0x33\n    RY                         = 0x34\n    RZ                         = 0x35\n    SLIDER                     = 0x36\n    DIAL                       = 0x37\n    WHEEL                      = 0x38\n    HAT_SWITCH                 = 0x39\n    COUNTED_BUFFER             = 0x3A\n    BYTE_COUNT                 = 0x3B\n    MOTION_WAKEUP              = 0x3C\n    START                      = 0x3D\n    SELECT                     = 0x3E\n    VX                         = 0x40\n    VY                         = 0x41\n    VZ                         = 0x42\n    VBRX                       = 0x43\n    VBRY                       = 0x44\n    VBRZ                       = 0x45\n    VNO                        = 0x46\n    FEATURE_NOTIFICATION       = 0x47\n    RESOLUTION_MULTIPLIER      = 0x48\n    SYSTEM_CONTROL             = 0x80\n    SYSTEM_POWER_DOWN          = 0x81\n    SYSTEM_SLEEP               = 0x82\n    SYSTEM_WAKE_UP             = 0x83\n    SYSTEM_CONTEXT_MENU        = 0x84\n    SYSTEM_MAIN_MENU           = 0x85\n    SYSTEM_APP_MENU            = 0x86\n    SYSTEM_MENU_HELP           = 0x87\n    SYSTEM_MENU_EXIT           = 0x88\n    SYSTEM_MENU_SELECT         = 0x89\n    SYSTEM_MENU_RIGHT          = 0x8A\n    SYSTEM_MENU_LEFT           = 0x8B\n    SYSTEM_MENU_UP             = 0x8C\n    SYSTEM_MENU_DOWN           = 0x8D\n    SYSTEM_COLD_RESTART        = 0x8E\n    SYSTEM_WARM_UP             = 0x8F\n    DPAD_UP                    = 0x90\n    DPAD_DOWN                  = 0x91\n    DPAD_RIGHT                 = 0x92\n    DPAD_LEFT                  = 0x93\n    SYSTEM_DOCK                = 0xA0\n    SYSTEM_UNDOCK              = 0xA1\n    SYSTEM_SETUP               = 0xA2\n    SYSTEM_BREAK               = 0xA3\n    SYSTEM_DEBUGGER_BREAK      = 0xA4\n    APPLICATION_BREAK          = 0xA5\n    APPLICATION_DEBUGGER_BREAK = 0xA6\n    SYSTEM_SPEAKER_MUTE        = 0xA7\n    SYSTEM_HIBERNATE           = 0xA8\n    SYSTEM_DISPLAY_INVERT      = 0xB0\n    SYSTEM_DISPLAY_INTERNAL    = 0xB1\n    SYSTEM_DISPLAY_EXTERNAL    = 0xB2\n    SYSTEM_DISPLAY_BOTH        = 0xB3\n    SYSTEM_DISPLAY_DUAL        = 0xB4\n    SYSTEM_DISPLAY_TOGGLE      = 0xB5\n    SYSTEM_DISPLAY_SWAP        = 0xB6\n    SYSTEM_DISPLAY_AUTOSCALE   = 0xB7\n\n"
  },
  {
    "path": "facedancer/configuration.py",
    "content": "#\n# This file is part of Facedancer.\n#\n\"\"\" Functionality for describing USB device configurations. \"\"\"\n\nimport struct\nimport textwrap\n\nfrom dataclasses  import field\nfrom typing       import Iterable\n\nfrom .types       import USBDirection\nfrom .magic       import instantiate_subordinates, AutoInstantiable\nfrom .request     import USBRequestHandler\n\nfrom .interface   import USBInterface\nfrom .descriptor  import USBDescribable, USBDescriptor, StringRef\nfrom .endpoint    import USBEndpoint\n\n\nclass USBConfiguration(USBDescribable, AutoInstantiable, USBRequestHandler):\n    \"\"\" Class representing a USBDevice's configuration.\n\n    Fields:\n        number:\n            The configuration's number; one-indexed.\n        configuration_string\n            A string describing the configuration; or None if not provided.\n        max_power:\n            The maximum power expected to be drawn by the device when using this interface, in mA. Typically 500mA, for maximum possible.\n        supports_remote_wakeup:\n            True iff this device should be able to wake the host from suspend.\n    \"\"\"\n\n    DESCRIPTOR_TYPE_NUMBER  = 0x02\n    DESCRIPTOR_SIZE_BYTES   = 9\n\n    number                 : int            = 1\n    configuration_string   : StringRef      = None\n\n    max_power              : int            = 500\n\n    self_powered           : bool           = True\n    supports_remote_wakeup : bool           = True\n\n    parent                 : USBDescribable = None\n    interfaces             : USBInterface   = field(default_factory=dict)\n\n\n    @classmethod\n    def from_binary_descriptor(cls, data, strings={}):\n        \"\"\"\n        Generates a new USBConfiguration object from a configuration descriptor,\n        handling any attached subordinate descriptors.\n\n        Args:\n            data: The raw bytes for the descriptor to be parsed.\n        \"\"\"\n\n        length = data[0]\n\n        # Unpack the main collection of data into the descriptor itself.\n        descriptor_type, total_length, num_interfaces, index, string_index, \\\n            attributes, half_max_power = struct.unpack_from('<xBHBBBBB', data[0:length])\n\n        configuration = cls(\n            number=index,\n            configuration_string=StringRef.lookup(strings, string_index),\n            max_power=half_max_power * 2,\n            self_powered=bool((attributes >> 6) & 1),\n            supports_remote_wakeup=bool((attributes >> 5) & 1),\n        )\n\n        data = data[length:total_length]\n        last_interface = None\n        last_endpoint  = None\n\n        # Continue parsing until we run out of descriptors.\n        while data:\n\n            # Determine the length and type of the next descriptor.\n            length     = data[0]\n            descriptor = USBDescribable.from_binary_descriptor(data[:length], strings=strings)\n\n            # If we have an interface descriptor, add it to our list of interfaces.\n            if isinstance(descriptor, USBInterface):\n                configuration.add_interface(descriptor)\n                last_interface = descriptor\n                last_endpoint = None\n            elif isinstance(descriptor, USBEndpoint):\n                last_interface.add_endpoint(descriptor)\n                last_endpoint = descriptor\n            elif isinstance(descriptor, USBDescriptor):\n                descriptor.include_in_config = True\n                if len(last_interface.endpoints) == 0:\n                    last_interface.add_descriptor(descriptor)\n                else:\n                    last_endpoint.add_descriptor(descriptor)\n\n            # Move on to the next descriptor.\n            data = data[length:]\n\n        return configuration\n\n\n    def __post_init__(self):\n\n        self.configuration_string = StringRef.ensure(self.configuration_string)\n\n        # Gather any interfaces attached to the configuration.\n        for interface in instantiate_subordinates(self, USBInterface):\n            self.add_interface(interface)\n\n\n    @property\n    def attributes(self):\n        \"\"\" Retrives the \"attributes\" composite word. \"\"\"\n\n        # Start off with the required bits set to one...\n        attributes = 0b10000000\n\n        # ... and then add in our attributes.\n        attributes |= (1 << 6) if self.self_powered           else 0\n        attributes |= (1 << 5) if self.supports_remote_wakeup else 0\n\n\n        return attributes\n\n    #\n    # User API.\n    #\n\n    def get_device(self):\n        \"\"\" Returns a reference to the associated device.\"\"\"\n        return self.parent\n\n\n    def add_interface(self, interface: USBInterface):\n        \"\"\" Adds an interface to the configuration. \"\"\"\n        identifier = interface.get_identifier()\n        num, alt = identifier\n\n        if identifier in self.interfaces:\n            other = self.interfaces[identifier]\n            iface_name = type(interface).__name__\n            other_name = type(other).__name__\n            raise Exception(\n                f\"Interface of type {iface_name} cannot be added to this \"\n                f\"configuration because there is already an interface of \"\n                f\"type {other_name} with the same interface number {num} \"\n                f\"and alternate setting {alt}\")\n        else:\n            self.interfaces[identifier] = interface\n            interface.parent = self\n\n\n    def get_endpoint(self, number: int, direction: USBDirection) -> USBEndpoint:\n        \"\"\" Attempts to find an endpoint with the given number + direction.\n\n        Args:\n            number    : The endpoint number to look for.\n            direction : Whether to look for an IN or OUT endpoint.\n        \"\"\"\n\n        # Search each of our interfaces for the relevant endpoint.\n        for interface in self.active_interfaces.values():\n            endpoint = interface.get_endpoint(number, direction)\n            if endpoint is not None:\n                return endpoint\n\n        # If none have one, return None.\n        return None\n\n\n    #\n    # Event handlers.\n    #\n\n\n    def handle_data_received(self, endpoint: USBEndpoint, data: bytes):\n        \"\"\" Handler for receipt of non-control request data.\n\n        Typically, this method will delegate any data received to the\n        appropriate configuration/interface/endpoint. If overridden, the\n        overriding function will receive all data; and can delegate it by\n        calling the `.handle_data_received` method on `self.configuration`.\n\n        Args:\n            endpoint : The endpoint on which the data was received.\n            data     : The raw bytes received on the relevant endpoint.\n        \"\"\"\n\n        for interface in self.active_interfaces.values():\n            if interface.has_endpoint(endpoint.number, direction=USBDirection.OUT):\n                interface.handle_data_received(endpoint, data)\n                return\n\n        # If no interface owned the targeted endpoint, consider the data unexpected.\n        self.get_device().handle_unexpected_data_received(endpoint.number, data)\n\n\n    def handle_data_requested(self, endpoint: USBEndpoint):\n        \"\"\" Handler called when the host requests data on a non-control endpoint.\n\n        Typically, this method will delegate the request to the appropriate\n        interface+endpoint. If overridden, the overriding function will receive\n        all data.\n\n        Args:\n            endpoint : The endpoint on which the host requested data.\n        \"\"\"\n\n        for interface in self.active_interfaces.values():\n            if interface.has_endpoint(endpoint.number, direction=USBDirection.IN):\n                interface.handle_data_requested(endpoint)\n                return\n\n        # If no one interface owned the targeted endpoint, consider the data unexpected.\n        self.get_device().handle_unexpected_data_requested(endpoint.number)\n\n\n    def handle_buffer_empty(self, endpoint: USBEndpoint):\n        \"\"\" Handler called when a given endpoint first has an empty buffer.\n\n        Often, an empty buffer indicates an opportunity to queue data\n        for sending ('prime an endpoint'), but doesn't necessarily mean\n        that the host is planning on reading the data.\n\n        This function is called only once per buffer.\n        \"\"\"\n\n        for interface in self.active_interfaces.values():\n            if interface.has_endpoint(endpoint.number, direction=USBDirection.IN):\n                interface.handle_buffer_empty(endpoint)\n                return\n\n\n\n    #\n    # Backend interface functions.\n    #\n\n    def get_interfaces(self) -> Iterable[USBInterface]:\n        \"\"\" Returns an iterable over all interfaces on the provided device. \"\"\"\n        return self.interfaces.values()\n\n\n    def get_descriptor(self) -> bytes:\n        \"\"\" Returns this configuration's configuration descriptor, including subordinates. \"\"\"\n        interface_descriptors = bytearray()\n\n        # FIXME: use construct\n\n        # All all subordinate descriptors together to create a big subordinate descriptor.\n        for interface in self.interfaces.values():\n            interface_descriptors += interface.get_descriptor()\n\n        total_len      = len(interface_descriptors) + 9\n        string_manager = self.get_device().strings\n\n        # Build the core interface descriptor.\n        d = bytes([\n                9,          # length of descriptor in bytes\n                2,          # descriptor type 2 == configuration\n                total_len & 0xff,\n                (total_len >> 8) & 0xff,\n                len(set(interface.number for interface in self.interfaces.values())),\n                self.number,\n                string_manager.get_index(self.configuration_string),\n                self.attributes,\n                self.max_power // 2\n        ])\n\n        return d + interface_descriptors\n\n\n    #\n    # Interfacing functions for AutoInstantiable.\n    #\n    def get_identifier(self) -> int:\n        return self.number\n\n\n    #\n    # Backend functions for our RequestHandler class.\n    #\n\n    def _request_handlers(self) -> Iterable[callable]:\n        return ()\n\n    def _get_subordinate_handlers(self) -> Iterable[USBInterface]:\n        return self.interfaces.values()\n\n\n    def generate_code(self, name=None, indent=0):\n\n        if name is None:\n            name = f\"Configuration_{self.number}\"\n\n        code = f\"\"\"\nclass {name}(USBConfiguration):\n    number                 = {self.number}\n    configuration_string   = {self.configuration_string.generate_code()}\n    max_power              = {self.max_power}\n    self_powered           = {repr(self.self_powered)}\n    supports_remote_wakeup = {repr(self.supports_remote_wakeup)}\n\"\"\"\n\n        for interface in self.interfaces.values():\n            code += interface.generate_code(indent=4)\n\n        return textwrap.indent(code, indent * ' ')\n"
  },
  {
    "path": "facedancer/core.py",
    "content": "# Facedancer.py\n#\n# Contains the core methods for working with a facedancer, inclduing methods\n# necessary for autodetection.\n# and GoodFETMonitorApp.\n\nimport os\n\nfrom .errors import *\nfrom .logging import log\n\ndef FacedancerUSBApp(verbose=0, quirks=None):\n    \"\"\"\n    Convenience function that automatically creates a FacedancerApp\n    based on the BOARD environment variable and some crude internal\n    automagic.\n\n    Args:\n        verbose : Sets the verbosity level of the relevant app. Increasing\n                  this from zero yields progressively more output.\n    \"\"\"\n    return FacedancerApp.autodetect(verbose, quirks)\n\n\nclass FacedancerApp:\n    app_name = \"override this\"\n    app_num = 0x00\n\n    @classmethod\n    def autodetect(cls, verbose=0, quirks=None):\n        \"\"\"\n        Convenience function that automatically creates the appropriate\n        subclass based on the BOARD environment variable and some crude internal\n        automagic.\n\n        Args:\n            verbose: Sets the verbosity level of the relevant app. Increasing\n                     this from zero yields progressively more output.\n        \"\"\"\n\n        if 'BACKEND' in os.environ:\n            backend_name = os.environ['BACKEND'].lower()\n        else:\n            backend_name = None\n\n        # Iterate over each subclass of FacedancerApp until we find one\n        # that seems appropriate.\n        subclass = cls._find_appropriate_subclass(backend_name)\n\n        if subclass:\n            if verbose > 0:\n                log.info(\"Using {} backend.\".format(subclass.app_name))\n\n            return subclass(verbose=verbose, quirks=quirks)\n        else:\n            log.error(\"FacedancerApp failed to autodetect any Facedancer devices.\")\n            log.error(\"Try specifying a backend with: BACKEND=\\\"<backend name>\\\" <your app>\")\n            raise DeviceNotFoundError(\"FacedancerApp failed to autodetect any Facedancer devices.\")\n\n\n    @classmethod\n    def _find_appropriate_subclass(cls, backend_name):\n\n        # Recursive case: if we have any subnodes, see if they are\n        # feed them to this function.\n        for subclass in cls.__subclasses__():\n\n            # Check to see if the subnode has any appropriate children.\n            appropriate_class = subclass._find_appropriate_subclass(backend_name)\n\n            # If it does, that's our answer!\n            if appropriate_class:\n                return appropriate_class\n\n        # Base case: check the current node.\n        if cls.appropriate_for_environment(backend_name):\n            return cls\n        else:\n            return None\n\n\n    @classmethod\n    def appropriate_for_environment(cls, backend_name=None):\n        \"\"\"\n        Returns true if the current class is likely to be the appropriate\n        class to connect to a facedancer given the board_name and other\n        environmental factors.\n\n        Args:\n            backend_name : The name of the backend, as typically retrieved from the BACKEND\n                           environment variable, or None to try figuring things out based\n                           on other environmental factors.\n        \"\"\"\n        return False\n\n\n    def __init__(self, device, verbose=0):\n        self.device = device\n        self.verbose = verbose\n\n        self.init_commands()\n\n        if self.verbose > 0:\n            log.info(self.app_name, \"initialized\")\n\n    def init_commands(self):\n        pass\n\n    def enable(self):\n        pass\n\n\ndef FacedancerUSBHostApp(verbose=0, quirks=None):\n    \"\"\"\n    Convenience function that automatically creates a FacedancerApp\n    based on the BOARD environment variable and some crude internal\n    automagic.\n\n    verbose: Sets the verbosity level of the relevant app. Increasing\n        this from zero yields progressively more output.\n    \"\"\"\n    return FacedancerUSBHost.autodetect(verbose, quirks)\n\n\nclass FacedancerUSBHost:\n    \"\"\"\n    Base class for Facedancer host connections-- extended to provide actual\n    connections to each host.\n    \"\"\"\n\n    # TODO: remove this redundancy; these should be somewhere common\n    # Endpoint directions\n    ENDPOINT_DIRECTION_OUT  = 0x00\n    ENDPOINT_DIRECTION_IN = 0x80\n\n    # Endpoint types\n    ENDPOINT_TYPE_CONTROL = 0\n\n    # Packet IDs\n    PID_SETUP = 2\n    PID_OUT = 0\n    PID_IN = 1\n\n    # USB Request Types\n    REQUEST_TYPE_STANDARD = 0\n    REQUEST_TYPE_CLASS = 1\n    REQUEST_TYPE_VENDOR = 2\n    REQUEST_TYPE_RESERVED = 3\n\n    # USB Request Recipients\n    REQUEST_RECIPIENT_DEVICE = 0\n    REQUEST_RECIPIENT_INTERFACE = 1\n    REQUEST_RECIPIENT_ENDPOINT = 2\n    REQUEST_RECIPIENT_OTHER = 3\n\n    # USB Standard Requests\n    STANDARD_REQUEST_GET_STATUS = 0\n    STANDARD_REQUEST_SET_ADDRESS = 5\n    STANDARD_REQUEST_GET_DESCRIPTOR = 6\n    STANDARD_REQUEST_SET_CONFIGURATION = 9\n\n\n    @classmethod\n    def autodetect(cls, verbose=0, quirks=None):\n        \"\"\"\n        Convenience function that automatically creates the appropriate\n        subclass based on the BOARD environment variable and some crude internal\n        automagic.\n\n        Args:\n            verbose: Sets the verbosity level of the relevant app. Increasing\n                     this from zero yields progressively more output.\n        \"\"\"\n\n        # TODO: Filter this out into some kind of autodetecting base class...\n\n        if 'BACKEND' in os.environ:\n            backend_name = os.environ['BACKEND'].lower()\n        else:\n            backend_name = None\n\n        # Iterate over each subclass of FacedancerApp until we find one\n        # that seems appropriate.\n        subclass = cls._find_appropriate_subclass(backend_name)\n\n        if subclass:\n            if verbose > 0:\n                log.info(\"Using {} backend.\".format(subclass.app_name))\n\n            return subclass(verbose=verbose, quirks=quirks)\n        else:\n            log.error(\"FacedancerUSBHost failed to autodetect any Facedancer devices.\")\n            log.error(\"Try specifying a backend with: BACKEND=\\\"<backend name>\\\" <your app>\")\n            raise DeviceNotFoundError(\"FacedancerUSBHost failed to autodetect any Facedancer devices.\")\n\n\n    @classmethod\n    def _find_appropriate_subclass(cls, backend_name):\n\n        # TODO: Filter this out into some kind of autodetecting base class...\n\n        # Recursive case: if we have any subnodes, see if they are\n        # feed them to this function.\n        for subclass in cls.__subclasses__():\n\n            # Check to see if the subnode has any appropriate children.\n            appropriate_class = subclass._find_appropriate_subclass(backend_name)\n\n            # If it does, that's our answer!\n            if appropriate_class:\n                return appropriate_class\n\n        # Base case: check the current node.\n        if cls.appropriate_for_environment(backend_name):\n            return cls\n        else:\n            return None\n\n\n    @classmethod\n    def appropriate_for_environment(cls, backend_name=None):\n        \"\"\"\n        Returns true if the current class is likely to be the appropriate\n        class to connect to a facedancer given the board_name and other\n        environmental factors.\n\n        Args:\n            backend_name : The name of the backend, as typically retrieved from the BACKEND\n                           environment variable, or None to try figuring things out based\n                           on other environmental factors.\n        \"\"\"\n        return False\n\n\n    @classmethod\n    def _build_request_type(cls, is_in, req_type, recipient):\n        \"\"\" Builds the request type field for a USB request.\n\n        Args:\n            is_in     : True iff this is a DEVICE-to-HOST request.\n            req_type  : The type of request to be used.\n            recipient : The context in which this request should be interpreted.\n\n        Returns : a request_type byte\n        \"\"\"\n\n        request_type = 0\n\n        if is_in:\n            request_type |= cls.ENDPOINT_DIRECTION_IN\n\n        request_type |= (req_type << 5)\n        request_type |= (recipient)\n\n        return request_type\n\n\n    @classmethod\n    def _build_setup_request(cls, is_in, request_type, recipient, request, value, index, length):\n        \"\"\" Builds a setup request packet from the standard USB request fields. \"\"\"\n\n        # Fields:\n        #       uint8_t request_type;\n        #       uint8_t request;\n        #       uint16_t value;\n        #       uint16_t index;\n        #       uint16_t length;\n\n        def split(value):\n            value_high  = value >> 8\n            value_low = value & 0xFF\n            return [value_low, value_high]\n\n        setup_request = [cls._build_request_type(is_in, request_type, recipient), request]\n        setup_request.extend(split(value))\n        setup_request.extend(split(index))\n        setup_request.extend(split(length))\n        return setup_request\n\n\n    def control_request_in(self, request_type, recipient, request, value=0, index=0, length=0):\n        \"\"\" Performs an IN control request.\n\n        Args:\n            request_type : Determines if this is a standard, class, or vendor request. Accepts\n                           a REQUEST_TYPE_* constant.\n            recipient    : Determines the context in which this command is interpreted. Accepts\n                           a REQUEST_RECIPIENT_* constant.\n            request      : The request number to be performed.\n            value, index : The standard USB request arguments, to be included in the setup\n                           packet. Their meaning varies depending on the request.\n            length       : The maximum length of data expected in response, or 0 if we don't\n                           expect any data back.\n        \"\"\"\n\n        # Create the raw setup request, and send it.\n        setup_request = self._build_setup_request(True, request_type, recipient,\n                                                  request, value, index, length)\n\n        if self.verbose > 4:\n            log.info(\"Issuing setup packet: {}\".format(setup_request))\n\n        self.send_on_endpoint(0, setup_request, True, data_packet_pid=0)\n\n        if self.verbose > 4:\n            log.info(\"Done.\")\n\n        # If we have a data stage, issue it:\n        if length:\n\n            if self.verbose > 4:\n                log.info(\"Reading response... \")\n\n            data = self.read_from_endpoint(0, length, data_packet_pid=1)\n\n            if self.verbose > 4:\n                log.info(\"Got response: {}\".format(data))\n\n            # and give the host an opportunity to ACK by sending a ZLP.\n            self.send_on_endpoint(0, [], data_packet_pid=1)\n            return data\n\n        else:\n            self.read_from_endpoint(0, 0, data_packet_pid=1)\n\n\n    def control_request_out(self, request_type, recipient, request, value=0, index=0, data=[]):\n        \"\"\" Performs an OUT control request.\n\n        Args:\n            request_type : Determines if this is a standard, class, or vendor request. Accepts\n                           a REQUEST_TYPE_* constant.\n            recipient    : Determines the context in which this command is interpreted. Accepts\n                           a REQUEST_RECIPIENT_* constant.\n            request      : The request number to be performed.\n            value, index : The standard USB request arguments, to be included in the setup\n                           packet. Their meaning varies depending on the request.\n            data         : The data to be transmitted with this control request.\n        \"\"\"\n\n        # Create the raw setup request, and send it.\n        setup_request = self._build_setup_request(False, request_type, recipient,\n                                                  request, value, index, len(data))\n        self.send_on_endpoint(0, setup_request, True)\n\n        # If we have a data stage, issue it:\n        if data:\n            self.send_on_endpoint(0, data)\n\n        # And try to read a ZLP from the host for ACK'ing purposes.\n        self.read_from_endpoint(0, 0, data_packet_pid=1)\n\n\n    def initialize_device(self, apply_configuration=0, assign_address=0):\n        \"\"\"\n        Sets up a connection to a directly-attached USB device.\n\n        Args:\n            apply_configuration : If non-zero, the configuration with the given index will\n                                  be applied to the relevant device.\n            assign_address      : If non-zero, the device will be assigned the given address\n                                  as part of the enumeration/initialization process.\n        \"\"\"\n\n        # TODO: support timeouts in waiting for a connection\n\n        # Repeatedly attempt to connect to any connected devices.\n        while not self.device_is_connected():\n            self.bus_reset()\n\n        # Assume the default device addresses, and read the device's speed.\n        self.last_device_address = 0\n        self.last_device_speed = self.current_device_speed()\n\n        # Set up the device to work.\n        if self.verbose > 3:\n            log.info(\"Initializing control endpoint...\")\n        self.initialize_control_endpoint()\n\n        # Try to ask the device for its maximum packet size on EP0.\n        self.last_ep0_max_packet_size = self.read_ep0_max_packet_size()\n\n        # If we've been asked to assign an address,\n        # set the device's address, and reinitialize the control endpoint\n        # with the updated address.\n        if assign_address:\n            self.set_address(assign_address)\n            self.initialize_control_endpoint(max_packet_size=self.last_ep0_max_packet_size)\n\n        # If we're auto-configuring the device, read the full configuration descriptor,\n        # assign the first configuration, and then set up endpoints accordingly\n        if apply_configuration:\n            self.apply_configuration(apply_configuration)\n\n\n\n\n    def get_descriptor(self, descriptor_type, descriptor_index,\n                       language_id, max_length):\n        \"\"\" Reads up to max_length bytes of a device's descriptors. \"\"\"\n\n        return self.control_request_in(\n                self.REQUEST_TYPE_STANDARD, self.REQUEST_RECIPIENT_DEVICE,\n                self.STANDARD_REQUEST_GET_DESCRIPTOR,\n                (descriptor_type << 8) | descriptor_index, language_id, max_length)\n\n\n    def get_device_descriptor(self, max_length=18):\n        \"\"\" Returns the device's device descriptor. \"\"\"\n\n        from .device import USBDevice\n\n        raw_descriptor = self.get_descriptor(USBDevice.DESCRIPTOR_TYPE_NUMBER, 0, 0, max_length)\n        return USBDevice.from_binary_descriptor(raw_descriptor)\n\n\n    def read_ep0_max_packet_size(self):\n        \"\"\" Returns the device's reported maximum packet size on EP0, in a way appropriate for an barely-configured endpoint. \"\"\"\n        device_descriptor = self.get_device_descriptor(max_length=8)\n        return device_descriptor.max_packet_size_ep0\n\n\n    def get_configuration_descriptor(self, index=0, include_subordinates=True):\n        \"\"\" Returns the device's configuration descriptor.\n\n        Args:\n            include_subordinate : if true, subordinate descriptors will also be returned\n        \"\"\"\n\n        from .configuration import USBConfiguration\n\n        # Read just the raw configuration descriptor.\n        raw_descriptor = self.get_descriptor(USBConfiguration.DESCRIPTOR_TYPE_NUMBER, index, 0, USBConfiguration.DESCRIPTOR_SIZE_BYTES)\n\n        # If we want to include the subordinate descriptors, read-read the configuration descriptor with an updated length.\n        if include_subordinates:\n\n            from struct import unpack\n\n            total_descriptor_lengths = unpack('<H', raw_descriptor[2:4])[0]\n            raw_descriptor = self.get_descriptor(USBConfiguration.DESCRIPTOR_TYPE_NUMBER, index, 0, total_descriptor_lengths)\n\n        return USBConfiguration.from_binary_descriptor(raw_descriptor)\n\n\n    def set_address(self, device_address):\n        \"\"\" Sets the device's address.\n\n        Note that all endpoints must be set up again after issuing the new address;\n        the easiest way to do this is to call apply_configuration().\n\n        Args:\n            device_address : the address to apply to the given device\n        \"\"\"\n        self.control_request_out(\n                self.REQUEST_TYPE_STANDARD, self.REQUEST_RECIPIENT_DEVICE,\n                self.STANDARD_REQUEST_SET_ADDRESS, value=device_address)\n        self.last_device_address = device_address\n\n\n    def set_configuration(self, index):\n        \"\"\" Sets the device's active configuration.\n\n        Note that this does not configure the host for the given configuration.\n        Most of the time, you probably want apply_configuration, which does.\n\n        Args:\n            index : the index of the configuration to apply\n        \"\"\"\n        self.control_request_out(\n                self.REQUEST_TYPE_STANDARD, self.REQUEST_RECIPIENT_DEVICE,\n                self.STANDARD_REQUEST_SET_CONFIGURATION, value=index)\n\n\n    def apply_configuration(self, index, set_configuration=True):\n        \"\"\" Applies a device's configuration. Necessary to use endpoints other\n            than the control endpoint.\n\n        Args:\n             index             : The configuration index to apply.\n             set_configuration : If true, also informs the device of the change.\n                 Setting this to false can allow the host to update its view of all\n                 endpoints without communicating with the device -- e.g. to update the\n                 device's address.\n        \"\"\"\n\n        # Read the full set of descriptors for the given configuration...\n        # TODO: don't assume that the indices increment nicely from 1?\n        configuration = self.get_configuration_descriptor(index - 1)\n\n        # If we're informing the device of the change, do so.\n        if set_configuration:\n            self.set_configuration(index)\n\n        # Locally, set up our endpoints to handle device communication.\n        for interface in configuration.interfaces.values():\n            for endpoint in interface.endpoints.values():\n                self.set_up_endpoint(endpoint)\n\n    def handle_events(self):\n        self.service_irqs()\n\n\nclass FacedancerBasicScheduler(object):\n    \"\"\"\n    Most basic scheduler for Facedancer devices-- and the schedule which is\n    created implicitly if no other scheduler is provided. Executes each of its\n    tasks in order, over and over.\n    \"\"\"\n    do_exit = False\n\n    def __init__(self):\n        self.tasks = []\n        self.do_exit = False\n\n\n    def add_task(self, callback):\n        \"\"\"\n        Adds a facedancer task to the scheduler, which will be called\n        repeatedly according to the internal scheduling algorithm\n\n        callback: The callback to be scheduled.\n        \"\"\"\n        self.tasks.append(callback)\n\n\n    def run(self):\n        \"\"\"\n        Run the main scheduler stack.\n        \"\"\"\n\n        self.do_exit = False\n        while not self.do_exit:\n            for task in self.tasks:\n                task()\n\n\n    def stop(self):\n        \"\"\"\n        Stop the scheduler on next loop.\n        \"\"\"\n        self.do_exit = True\n"
  },
  {
    "path": "facedancer/descriptor.py",
    "content": "#\n# This file is part of Facedancer.\n#\n\"\"\" Functionality for working with objects with associated USB descriptors. \"\"\"\n\nfrom .magic import AutoInstantiable, DescribableMeta, adjust_defaults\n\nfrom dataclasses import field\nfrom enum import IntEnum\nfrom typing import Dict\nfrom warnings import warn\n\nimport itertools\nimport textwrap\n\nclass USBDescribable(metaclass=DescribableMeta):\n    \"\"\"\n    Abstract base class for objects that can be created from USB descriptors.\n    \"\"\"\n\n    # Override me!\n    DESCRIPTOR_TYPE_NUMBER = None\n\n    @classmethod\n    def handles_binary_descriptor(cls, data):\n        \"\"\"\n        Returns true iff this class handles the given descriptor. By default,\n        this is based on the class's DESCRIPTOR_TYPE_NUMBER declaration.\n        \"\"\"\n        return data[1] == cls.DESCRIPTOR_TYPE_NUMBER\n\n\n\n    @classmethod\n    def from_binary_descriptor(cls, data, strings={}):\n        \"\"\"\n        Attempts to create a USBDescriptor subclass from the given raw\n        descriptor data.\n        \"\"\"\n\n        for subclass in cls.__subclasses__():\n            # If this subclass handles our binary descriptor, use it to parse the given descriptor.\n            if subclass.handles_binary_descriptor(data):\n                return subclass.from_binary_descriptor(data, strings=strings)\n\n        return USBDescriptor.from_binary_descriptor(data, strings=strings)\n\n\nclass USBDescriptor(USBDescribable, AutoInstantiable):\n    \"\"\" Class for arbitrary USB descriptors; minimal concrete implementation of USBDescribable. \"\"\"\n\n    \"\"\" The raw bytes of the descriptor. \"\"\"\n    raw : bytes\n\n    \"\"\" The bDescriptorType of the descriptor. \"\"\"\n    type_number : int = None\n\n    \"\"\" Number to request this descriptor with a GET_DESCRIPTOR request. \"\"\"\n    number : int = None\n\n    \"\"\" Parent object which this descriptor is associated with. \"\"\"\n    parent : USBDescribable = None\n\n    \"\"\" Whether this descriptor should be included in a GET_CONFIGURATION response. \"\"\"\n    include_in_config : bool = False\n\n    def __post_init__(self):\n        # If type number was not set, get it from the raw bytes.\n        if self.type_number is None and self.raw is not None:\n            self.type_number = self.raw[1]\n\n    def __call__(self, index=0):\n        \"\"\" Converts the descriptor object into raw bytes. \"\"\"\n        return self.raw\n\n    def get_identifier(self):\n        return (self.type_number, self.number)\n\n    @classmethod\n    def from_binary_descriptor(cls, data, strings={}):\n        return USBDescriptor(raw=data, type_number=data[1], number=None)\n\n    def generate_code(self, name=None, indent=0):\n        type_num = f\"0x{self.type_number:02X}\"\n        if name is None:\n            if self.include_in_config:\n                name = f\"Descriptor_{type_num}\"\n            else:\n                name = f\"Descriptor_{type_num}_{self.number}\"\n\n        # use a shim until we get itertools.batched in Python 3.12\n        def batched(iterable, n=1):\n            l = len(iterable)\n            for ndx in range(0, l, n):\n                yield iterable[ndx:min(ndx + n, l)]\n\n        num_bytes = len(self.raw)\n        if num_bytes == 0:\n            raw = \"\"\n        elif num_bytes < 7:\n            raw = str.join(\", \", (f'0x{b:02X}' for b in self.raw))\n        else:\n            if 8 < num_bytes < 20:\n                chunk_size = (num_bytes + 1) // 2\n            else:\n                chunk_size = 10\n            raw = \"\\n        \" + str.join(\",\\n        \", (\n                str.join(\", \", (f'0x{b:02X}' for b in chunk))\n                    for chunk in batched(self.raw, chunk_size)))\n\n        code = \"\\n\"\n        if self.include_in_config:\n            code += f\"@include_in_config\\n\"\n        if self.number is not None:\n            code += f\"@requestable(type_number={type_num}, number={self.number})\\n\"\n\n        code += f\"class {name}(USBDescriptor):\\n\"\n        code += f\"    raw = bytes([{raw}])\\n\"\n\n        return textwrap.indent(code, ' ' * indent)\n\n\nclass USBClassDescriptor(USBDescriptor):\n    \"\"\" Class for arbitrary USB Class descriptors. \"\"\"\n\n    include_in_config : bool = True\n\n    def __init_subclass__(cls, **kwargs):\n        warn(\n            \"The USBClassDescriptor class is deprecated. \"\n            \"Use USBDescriptor with include_in_config=True instead.\",\n            UserWarning, 3)\n        super().__init_subclass__(**kwargs)\n\n\nclass USBStringDescriptor(USBDescriptor):\n    \"\"\" Class representing a USB string descriptor. \"\"\"\n\n    DESCRIPTOR_TYPE_NUMBER = 3\n\n    # Property: the python version of the relevant string.\n    python_string : str = None\n\n    @classmethod\n    def from_string(cls, string, *, index=None):\n\n        # Grab the raw string\n        raw_string = string.encode('utf-16')[2:]\n        raw = bytes([len(raw_string) + 2, cls.DESCRIPTOR_TYPE_NUMBER, *raw_string])\n\n        return cls(raw=raw, number=index, type_number=3, python_string=string)\n\n\nclass StringRef:\n    \"\"\" Class representing a reference to a USB string descriptor. \"\"\"\n\n    def __init__(self, index: int = None, string : str = None):\n        if index is None and str is None:\n            raise TypeError(\"A StringRef must have an index or a string\")\n        self.index = index\n        self.string = string\n\n\n    @classmethod\n    def field(cls, **kwargs):\n        \"\"\" Used to create StringRef fields in dataclasses. \"\"\"\n        return field(default_factory=lambda: StringRef(**kwargs))\n\n\n    @classmethod\n    def lookup(cls, strings: Dict[int, str], index: int):\n        \"\"\" Try to construct a StringRef given an index and a mapping \"\"\"\n        if index == 0:\n            return StringRef(index=0)\n        elif index in strings:\n            return StringRef(index=index, string=strings[index])\n        else:\n            return StringRef(index=index)\n\n\n    @classmethod\n    def ensure(cls, value):\n        \"\"\" Turn a value into a StringRef it is not one already. \"\"\"\n        if isinstance(value, StringRef):\n            return value\n        elif isinstance(value, tuple):\n            index, string = value\n            return StringRef(index=index, string=string)\n        elif isinstance(value, int):\n            return StringRef(index=value)\n        elif isinstance(value, str):\n            return StringRef(string=value)\n        elif value is None:\n            return StringRef(index=0)\n        else:\n            raise TypeError(f\"Cannot construct StringRef from {repr(value)}\")\n\n\n    def generate_code(self):\n        \"\"\" Generate input that will produce this StringRef when passed to ensure() \"\"\"\n        if self.index is not None and self.string is not None:\n            return f\"({self.index}, {repr(self.string)})\"\n        elif self.index == 0:\n            return \"None\"\n        elif self.index is not None:\n            return str(self.index)\n        elif self.string is not None:\n            return repr(self.string)\n\n\nclass StringDescriptorManager:\n    \"\"\" Manager class that collects active string descriptors. \"\"\"\n\n    def __init__(self):\n        self.next_index = 1\n\n        # Maps indexes => string descriptors.\n        self.descriptors = {}\n\n        # Maps python strings => indexes.\n        self.indexes     = {}\n\n\n    def add_string(self, string, index=None):\n        \"\"\"Add a Python string as a new string descriptor, and return an index.\n\n        The specified index is used for the new string descriptor, overwriting\n        any previous descriptor with the same index. If an index is not\n        specified, a new, unique, incrementing index is allocated.\n        \"\"\"\n\n        if isinstance(string, StringRef):\n            index = string.index\n            string = string.string\n\n        if index is None:\n            index = self.next_index\n\n        if index in self.descriptors:\n            old_string = self.descriptors[index].python_string\n            self.indexes.pop(old_string)\n\n        self.descriptors[index] = USBStringDescriptor.from_string(string, index=index)\n        self.indexes[string]    = index\n\n        while self.next_index in self.descriptors:\n            self.next_index += 1\n\n        return index\n\n\n    def get_index(self, string):\n        \"\"\" Returns the index of the given string; creating it if the string isn't already known. \"\"\"\n\n        # If we already have an index, leave it alone...\n        if isinstance(string, StringRef):\n            if string.index is not None:\n                return string.index\n            else:\n                string = string.string\n        elif isinstance(string, int):\n            return string\n\n        # Special case: return 0 for None, allowing null strings to be represented.\n        if string is None:\n            return 0\n\n        if string in self.indexes:\n            return self.indexes[string]\n\n        return self.add_string(string)\n\n\n    def __getitem__(self, index):\n        \"\"\" Gets the relevant string descriptor. \"\"\"\n\n        if isinstance(index, str):\n            index = self.get_index(index)\n\n        return self.descriptors.get(index, None)\n\n\nclass USBDescriptorTypeNumber(IntEnum):\n    DEVICE                    = 1\n    CONFIGURATION             = 2\n    STRING                    = 3\n    INTERFACE                 = 4\n    ENDPOINT                  = 5\n    DEVICE_QUALIFIER          = 6\n    OTHER_SPEED_CONFIGURATION = 7\n    INTERFACE_POWER           = 8\n    HID                       = 33\n    REPORT                    = 34\n\n\ndef include_in_config(cls):\n    \"\"\" Decorator that marks a descriptor to be included in configuration data. \"\"\"\n    return adjust_defaults(cls, include_in_config=True)\n\n\ndef requestable(type_number, number):\n    \"\"\" Decorator that marks a descriptor as requestable. \"\"\"\n    return lambda cls: adjust_defaults(cls, type_number=type_number, number=number)\n"
  },
  {
    "path": "facedancer/device.py",
    "content": "#\n# This file is part of Facedancer.\n#\n\"\"\" Functionality for defining USB devices. \"\"\"\n\n# Support annotations on Python < 3.9\nfrom __future__  import annotations\n\nimport sys\nimport asyncio\nimport struct\nimport warnings\nimport itertools\n\nfrom typing         import Coroutine, Dict, Iterable, Union\nfrom dataclasses    import field\n\nfrom prompt_toolkit import HTML, print_formatted_text\n\nfrom .core          import FacedancerUSBApp\nfrom .errors        import EndEmulation\nfrom .types         import DescriptorTypes, LanguageIDs, USBStandardRequests\nfrom .types         import USBDirection, USBRequestType, USBRequestRecipient\nfrom .types         import DeviceSpeed\n\nfrom .magic         import instantiate_subordinates\n\nfrom .descriptor    import USBDescribable, USBDescriptor, StringDescriptorManager, StringRef\nfrom .configuration import USBConfiguration\nfrom .interface     import USBInterface\nfrom .endpoint      import USBEndpoint\nfrom .request       import USBControlRequest, USBRequestHandler\nfrom .request       import standard_request_handler, to_device, get_request_handler_methods\n\nfrom .logging       import log\n\n\nclass USBBaseDevice(USBDescribable, USBRequestHandler):\n    \"\"\"\n    Base-most class for Facedancer USB devices. This version is very similar to the USBDevice type,\n    except that it does not define _any_ standard handlers. This allows you the freedom to declare\n    whatever standard requests you'd like.\n\n    Fields:\n        vendor_id, product_id :\n            The USB vendor and product ID for this device.\n        manufacturer_string, product_string, serial_number_string :\n            Python strings identifying the device to the USB host.\n        device_class, device_subclass, protocol_revision_number :\n            The USB descriptor fields that select the class, subclass, and protocol.\n        supported_languages :\n            A tuple containing all of the language IDs supported by the device.\n        device_revision :\n            Number indicating the hardware revision of this device. Typically BCD.\n        usb_spec_revision :\n            Number indicating the version of the USB specification we adhere to. Typically 0x0200.\n        device_speed :\n            Specify the device speed for boards that support multiple interface speeds.\n    \"\"\"\n\n    DESCRIPTOR_TYPE_NUMBER    = 0x01\n    DESCRIPTOR_LENGTH         = 0x12\n\n    name                     : str = \"generic device\"\n\n    device_class             : int  = 0\n    device_subclass          : int  = 0\n\n    protocol_revision_number : int  = 0\n    max_packet_size_ep0      : int  = 64\n\n    vendor_id                : int  = 0x610b\n    product_id               : int  = 0x4653\n\n    manufacturer_string      : StringRef = StringRef.field(string=\"Facedancer\")\n    product_string           : StringRef = StringRef.field(string=\"Generic USB Device\")\n    serial_number_string     : StringRef = StringRef.field(string=\"S/N 3420E\")\n\n    # I feel bad for putting this as the default language ID / propagating anglocentrism,\n    # but this appears to be the only language ID supported by some systems, so here it is.\n    supported_languages      : tuple = (LanguageIDs.ENGLISH_US,)\n\n    device_revision          : int  = 0\n    usb_spec_version         : int  = 0x0200\n\n    device_speed             : DeviceSpeed = None\n\n    # Descriptors that can be requested with the GET_DESCRIPTOR request.\n    requestable_descriptors  : Dict[tuple[int, int], Union[bytes, callable]] = field(default_factory=dict)\n    configurations           : Dict[int, USBConfiguration]       = field(default_factory=dict)\n    backend                  : FacedancerUSBApp = None\n\n\n    @classmethod\n    def from_binary_descriptor(cls, data, strings={}):\n        \"\"\"\n        Creates a USBBaseDevice object from its descriptor.\n        \"\"\"\n\n        data = bytes(data)\n\n        # Pad the descriptor out with zeroes to the full length of a configuration descriptor.\n        if len(data) < cls.DESCRIPTOR_LENGTH:\n            padding_necessary = cls.DESCRIPTOR_LENGTH - len(data)\n            data += b\"\\0\" * padding_necessary\n\n        # Parse the core descriptor into its components...\n        spec_version, device_class, device_subclass, device_protocol, \\\n                max_packet_size_ep0, vendor_id, product_id, device_rev, \\\n                manufacturer_string_index, product_string_index, \\\n                serial_number_string_index, num_configurations = struct.unpack_from(\"<xxHBBBBHHHBBBB\", data)\n\n        device = cls(\n            device_class=device_class,\n            device_subclass=device_subclass,\n            protocol_revision_number=device_protocol,\n            max_packet_size_ep0=max_packet_size_ep0,\n            vendor_id=vendor_id,\n            product_id=product_id,\n            manufacturer_string=StringRef.lookup(strings, manufacturer_string_index),\n            product_string=StringRef.lookup(strings, product_string_index),\n            serial_number_string=StringRef.lookup(strings, serial_number_string_index),\n            device_revision=device_rev,\n            usb_spec_version=spec_version,\n        )\n\n        # Populate string descriptors\n        for index, string in strings.items():\n            device.strings.add_string(string, index)\n\n        # FIXME: generate better placeholder configurations\n        for configuration in range(0, num_configurations):\n            device.add_configuration(USBConfiguration())\n\n        return device\n\n\n    def __post_init__(self):\n        \"\"\" Set up our device for execution. \"\"\"\n\n        self.manufacturer_string = StringRef.ensure(self.manufacturer_string)\n        self.product_string = StringRef.ensure(self.product_string)\n        self.serial_number_string = StringRef.ensure(self.serial_number_string)\n\n        self.strings = StringDescriptorManager()\n\n        # Gather any descriptors attached to the class.\n        for descriptor in instantiate_subordinates(self, USBDescriptor):\n            self.add_descriptor(descriptor)\n\n        # Add our basic descriptor handlers.\n        self.requestable_descriptors.update({\n            DescriptorTypes.DEVICE:        lambda _ : self.get_descriptor(),\n            DescriptorTypes.CONFIGURATION: self.get_configuration_descriptor,\n            DescriptorTypes.STRING:        self.get_string_descriptor\n        })\n\n        # Start off un-configured, and with an address of 0.\n        self.address = 0\n        self.configuration = None\n\n        # Populate our control request handlers, and any subordinate classes we'll need to create.\n        self._request_handler_methods = get_request_handler_methods(self)\n        for configuration in instantiate_subordinates(self, USBConfiguration):\n            self.add_configuration(configuration)\n\n        # Create a set of suggested requests. We'll use this to store the vitals\n        # of any unhandled requests, so we can provide user suggestions later.\n        self._suggested_requests = set()\n        self._suggested_request_metadata = {}\n\n\n    #\n    # Control interface.\n    #\n\n    def add_configuration(self, configuration: USBConfiguration):\n        \"\"\" Adds the provided configuration to this device. \"\"\"\n        self.configurations[configuration.number] = configuration\n        configuration.parent = self\n\n\n    def add_descriptor(self, descriptor: USBDescriptor):\n        \"\"\" Adds the provided descriptor to this device. \"\"\"\n        identifier = (descriptor.type_number, descriptor.number)\n        desc_name = type(descriptor).__name__\n\n        if descriptor.number is None:\n            raise Exception(\n                f\"Descriptor of type {desc_name} cannot be added to this \"\n                f\"device because it has no number to identify it.\")\n\n        elif identifier in self.requestable_descriptors:\n            other = self.requestable_descriptors[identifier]\n            other_name = type(other).__name__\n            other_type = f\"0x{other.type_number:02X}\"\n            raise Exception(\n                f\"Descriptor of type {desc_name} cannot be added to this \"\n                f\"device because there is already a descriptor of type \"\n                f\"{other_name} with the same type code {other_type} and \"\n                f\"number {other.number}\")\n\n        else:\n            self.requestable_descriptors[identifier] = descriptor\n            descriptor.parent = self\n\n\n    def connect(self, device_speed: DeviceSpeed=DeviceSpeed.FULL):\n        \"\"\" Connects this device to the host; e.g. turning on our presence-detect pull up. \"\"\"\n        if self.backend is None:\n            self.backend = FacedancerUSBApp()\n\n        # override any provided speed if the device already defines it\n        if self.device_speed != None:\n            device_speed=self.device_speed\n\n        self.backend.connect(self, max_packet_size_ep0=self.max_packet_size_ep0, device_speed=device_speed)\n\n\n    def disconnect(self):\n        \"\"\" Disconnects this device from the host. \"\"\"\n        self.backend.disconnect()\n\n\n    async def run(self):\n        \"\"\" Runs the actual device emulation. \"\"\"\n\n        # Sanity check to avoid common issues.\n        from .proxy import USBProxyDevice\n        if len(self.configurations) == 0 and not isinstance(self, USBProxyDevice):\n            log.error(\"No configurations defined on the emulated device! \"\n                    \"Did you forget @use_inner_classes_automatically?\")\n\n        if self.backend is None:\n            self.connect()\n\n        # Constantly service any events that need to be performed.\n        while True:\n            self.backend.service_irqs()\n            await asyncio.sleep(0)\n\n\n    def run_with(self, *coroutines: Iterable[Coroutine]):\n        \"\"\"\n        Runs the actual device emulation synchronously; running any provided\n        coroutines simultaneously.\n        \"\"\"\n\n        async def inner():\n            await asyncio.gather(self.run(), *coroutines)\n\n        asyncio.run(inner())\n\n\n    def emulate(self, *coroutines: Iterable[Coroutine]):\n        \"\"\" Convenience method that runs a full method in a blocking manner.\n        Performs connect, run, and then disconnect.\n\n        Args:\n            *coroutines : any asyncio coroutines to be executed concurrently\n                           with our emulation\n        \"\"\"\n\n        self.connect()\n\n        try:\n            self.run_with(*coroutines)\n        except EndEmulation as e:\n            log.info(f\"{e}\")\n        finally:\n            self.disconnect()\n\n\n    #\n    # I/O interface.\n    #\n\n    def stall(self, *, endpoint_number: int = 0, direction: USBDirection = USBDirection.OUT):\n        \"\"\" Stalls the provided endpoint.\n\n        For endpoint zero, this indicates that the active (or next)\n        request is not supported. For all other endpoints, this indicates\n        a persistent 'halt' condition.\n\n        Args:\n            endpoint : The endpoint address; or EP0 if not provided.\n        \"\"\"\n        self.backend.stall_endpoint(endpoint_number, direction)\n\n\n    def clear_halt(self, endpoint_number: int, direction: USBDirection):\n        \"\"\" Clears a halt condition on the provided non-control endpoint.\n\n        Args:\n            endpoint_number : The endpoint number\n            direction       : The endpoint direction; or OUT if not provided.\n        \"\"\"\n        self.backend.clear_halt(endpoint_number, direction)\n\n\n    def control_send(self, endpoint_number: int, in_request: USBControlRequest, data: bytes, *, blocking: bool = False):\n        \"\"\" Queues sending data on the provided control endpoint in\n            response to a IN control request.\n\n        Args:\n            endpoint_number : The endpoint number to send data upon.\n            in_request      : The control request being responded to.\n            data            : The data to send.\n            blocking        : If provided and true, this function will block\n                               until the backend indicates the send is complete.\n\n        \"\"\"\n        self.backend.send_on_control_endpoint(endpoint_number, in_request, data, blocking=blocking)\n\n\n    def send(self, endpoint_number: int, data: bytes, *, blocking: bool = False):\n        \"\"\" Queues sending data on the IN endpoint with the provided number.\n\n        Args:\n            endpoint_number : The endpoint number to send data upon.\n            data            : The data to send.\n            blocking        : If provided and true, this function will block\n                               until the backend indicates the send is complete.\n        \"\"\"\n\n        if endpoint_number == 0:\n            # TODO upgrade to an exception with the release of Facedancer 3.1.0\n            log.warning(\"Please use USBDevice::control_send() for control transfers\")\n        elif self.configuration:\n            endpoint = self.configuration.get_endpoint(endpoint_number, USBDirection.IN)\n            endpoint.send(data, blocking=blocking)\n\n\n    def _send_in_packets(self, endpoint_number: int, data: bytes, *,\n            packet_size: int, blocking: bool = False):\n        \"\"\" Queues sending data on the IN endpoint with the provided number.\n\n        Sends the relevant data to the backend in chunks of packet_size.\n\n        Args:\n            endpoint_number : The endpoint number to send data upon.\n            data            : The data to send.\n            packet_size     : The \"chunk\" size to send in.\n            blocking        : If provided and true, this function will block\n                               until the backend indicates the send is complete.\n        \"\"\"\n\n        data = bytearray(data)\n\n        # Special case: if we have a ZLP to begin with, send it, and return.\n        if not data:\n            self.backend.send_on_endpoint(endpoint_number, data, blocking=blocking)\n            return\n\n        # Send the relevant data one packet at a time,\n        # chunking if we're larger than the max packet size.\n        # This matches the behavior of the MAX3420E.\n        while data:\n            packet = data[0:packet_size]\n            del data[0:packet_size]\n\n            self.backend.send_on_endpoint(endpoint_number, packet, blocking=blocking)\n\n\n    def get_endpoint(self, endpoint_number: int, direction: USBDirection) -> USBEndpoint:\n        \"\"\" Attempts to find a subordinate endpoint matching the given number/direction.\n\n        Args:\n            endpoint_number : The endpoint number to search for.\n            direction       : The endpoint direction to be matched.\n\n        Returns:\n            The matching endpoint; or None if no matching endpoint existed.\n        \"\"\"\n\n        if self.configuration:\n            endpoint = self.configuration.get_endpoint(endpoint_number, direction)\n            if endpoint is None:\n                log.error(f\"Requested non-existent endpoint EP{endpoint_number}/{direction.name} for configured device!\")\n            return endpoint\n        else:\n            log.error(f\"Requested endpoint EP{endpoint_number}/{direction.name} for unconfigured device!\")\n            return None\n\n\n    #\n    # Backend interface helpers.\n    #\n    def create_request(self, raw_data: bytes) -> USBControlRequest:\n        return USBControlRequest.from_raw_bytes(raw_data, device=self)\n\n\n    #\n    # Backend / low-level event receivers.\n    #\n\n\n    def handle_nak(self, ep_num: int):\n        \"\"\" Backend data-requested handler; for legacy compatibility.\n\n        Prefer overriding handle_data_requested() and handle_unexpected_data_Requested\n        \"\"\"\n        endpoint = self.get_endpoint(ep_num, USBDirection.IN)\n\n        if endpoint:\n            self.handle_data_requested(endpoint)\n        else:\n            self.handle_unexpected_data_requested(ep_num)\n\n\n    def handle_buffer_available(self, ep_num):\n        \"\"\" Backend data-buffer-empty handler; for legacy compatibility.\n\n        Prefer overriding handle_buffer_available().\n        \"\"\"\n        endpoint = self.get_endpoint(ep_num, USBDirection.IN)\n\n        if endpoint:\n            self.handle_buffer_empty(endpoint)\n\n\n    def handle_data_available(self, ep_num, data):\n        \"\"\" Backend data-available handler; for legacy compatibility.\n\n        Prefer overriding handle_data_received().\n        \"\"\"\n        endpoint = self.get_endpoint(ep_num, USBDirection.OUT)\n\n        if endpoint:\n            self.handle_data_received(endpoint, data)\n        else:\n            self.handle_unexpected_data_received(ep_num, data)\n\n\n    #\n    # Event handlers.\n    #\n\n    def handle_bus_reset(self):\n        \"\"\" Event handler for a bus reset. \"\"\"\n        log.info(\"Host issued a bus reset; resetting our connection.\")\n\n        # Clear our state back to address zero and no configuration.\n        self.configuration = None\n        self.address = 0\n\n        self.backend.reset()\n\n\n    def handle_request(self, request: USBControlRequest):\n        \"\"\" Core control request handler.\n\n        This function can be overridden by a subclass if desired; but the typical way to\n        handle a specific control request is to the the ``@control_request_handler`` decorators.\n\n        Args:\n            request : the USBControlRequest object representing the relevant request\n        \"\"\"\n        log.debug(f\"{self.name} received request: {request}\")\n\n        # Call our base USBRequestHandler method.\n        handled = super().handle_request(request)\n\n        # As the top-most handle_request function, we have an extra responsibility:\n        # we'll need to stall the endpoint if no handler was found.\n        if not handled:\n            log.warning(f\"Stalling unhandled {request}.\")\n            self._add_request_suggestion(request)\n            self.stall(direction=USBDirection.IN)\n\n        return handled\n\n\n    def handle_data_received(self, endpoint: USBEndpoint, data: bytes):\n        \"\"\" Handler for receipt of non-control request data.\n\n        Typically, this method will delegate any data received to the\n        appropriate configuration/interface/endpoint. If overridden, the\n        overriding function will receive all data.\n\n        Args:\n            endpoint_number : The endpoint number on which the data was received.\n            data            : The raw bytes received on the relevant endpoint.\n        \"\"\"\n\n        # If we have a configuration, delegate to it.\n        if self.configuration:\n            self.configuration.handle_data_received(endpoint, data)\n\n        # If we're un-configured, we don't expect to receive\n        # anything other than control data; defer to our \"unexpected data\".\n        else:\n            log.error(f\"Received non-control data when unconfigured!\"\n                    \"This is invalid host behavior.\")\n            self.handle_unexpected_data_received(endpoint.number, data)\n\n\n    def handle_unexpected_data_received(self, endpoint_number: int, data: bytes):\n        \"\"\" Handler for unexpected data.\n\n        Handles any data directed at an unexpected target; e.g. an endpoint that\n        doesn't exist. Note that even if `handle_data_received` is overridden,\n        this method can still be called e.g. by configuration.handle_data_received.\n\n        Args:\n            endpoint_number : The endpoint number on which the data was received.\n            data            : The raw bytes received on the relevant endpoint.\n        \"\"\"\n        log.error(f\"Received {len(data)} bytes of data on invalid EP{endpoint_number}/OUT.\")\n\n\n    def handle_data_requested(self, endpoint: USBEndpoint):\n        \"\"\" Handler called when the host requests data on a non-control endpoint.\n\n        Typically, this method will delegate the request to the appropriate\n        configuration+interface+endpoint. If overridden, the\n        overriding function will receive all events.\n\n        Args:\n            endpoint_number : The endpoint number on which the host requested data.\n        \"\"\"\n\n        # If we have a configuration, delegate to it.\n        if self.configuration:\n            self.configuration.handle_data_requested(endpoint)\n\n        # If we're un-configured, we don't expect to receive\n        # anything other than control data; defer to our \"unexpected data\".\n        else:\n            log.error(f\"Received non-control data when unconfigured!\"\n                      \"This is invalid host behavior.\")\n            self.handle_unexpected_data_requested(endpoint.number)\n\n\n    def handle_unexpected_data_requested(self, endpoint_number: int):\n        \"\"\" Handler for unexpected data requests.\n\n        Handles any requests directed at an unexpected target; e.g. an endpoint that\n        doesn't exist. Note that even if `handle_data_requested` is overridden,\n        this method can still be called e.g. by configuration.handle_data_received.\n\n        Args:\n            endpoint_number : The endpoint number on which the data was received.\n        \"\"\"\n        log.error(f\"Host requested data on invalid EP{endpoint_number}/IN.\")\n\n\n    def handle_buffer_empty(self, endpoint: USBEndpoint):\n        \"\"\" Handler called when a given endpoint first has an empty buffer.\n\n        Often, an empty buffer indicates an opportunity to queue data\n        for sending ('prime an endpoint'), but doesn't necessarily mean\n        that the host is planning on reading the data.\n\n        This function is called only once per buffer.\n        \"\"\"\n\n        # If we have a configuration, delegate to it.\n        if self.configuration:\n            self.configuration.handle_buffer_empty(endpoint)\n\n\n    #\n    # Methods for USBRequestHandler.\n    #\n\n    def _request_handlers(self) -> Iterable[callable]:\n        return self._request_handler_methods\n\n\n    def _get_subordinate_handlers(self) -> Iterable[callable]:\n        # As a device, our subordinates are our configurations.\n        return self.configurations.values()\n\n\n    #\n    # Suggestion engine.\n    #\n\n    def _add_request_suggestion(self, request: USBControlRequest):\n        \"\"\" Adds a 'suggestion' to the list of requests that may need implementing.\n\n        Args:\n            request : The unhandled request on which the suggestion should be based.\n         \"\"\"\n\n        # Build a tuple of the relevant immutable parts of the request,\n        # and store it as a suggestion.\n        if request.recipient in (USBRequestRecipient.INTERFACE, USBRequestRecipient.ENDPOINT):\n            recipient_id = request.index & 0xFF\n        else:\n            recipient_id = None\n        suggestion_summary = (request.direction, request.type, request.recipient, recipient_id, request.number)\n\n        self._suggested_requests.add(suggestion_summary)\n        self._suggested_request_metadata[suggestion_summary] = {\n            'length': request.length,\n            'data':   request.data\n        }\n\n\n    def _print_suggested_requests(self):\n        \"\"\" Prints a collection of suggested additions to the stdout. \"\"\"\n\n        # Create a quick printing shortcut.\n        print_html = lambda data : print_formatted_text(HTML(data))\n\n        # Look-ups for the function's decorators / etc.\n        request_type_decorator = {\n            USBRequestType.STANDARD:    '@standard_request_handler',\n            USBRequestType.VENDOR:      '@vendor_request_handler',\n            USBRequestType.CLASS:       '@class_request_handler',\n            USBRequestRecipient.OTHER:  '@reserved_request_handler'\n        }\n        target_decorator = {\n            USBRequestRecipient.DEVICE:    '@to_device',\n            USBRequestRecipient.INTERFACE: '@to_this_interface',\n            USBRequestRecipient.ENDPOINT:  '@to_this_endpoint',\n            USBRequestRecipient.OTHER:     '@to_other',\n        }\n\n        # Helper function used to group requests by recipients.\n        def grouper(suggestion):\n            direction, request_type, recipient, recipient_id, number = suggestion\n            return (recipient, recipient_id)\n\n        print_html(\"\\n<u>Request handler code:</u>\")\n\n        if not self._suggested_requests:\n            print_html(\"\\t No suggestions.\")\n            return\n\n        # Sort the suggested requests by recipient, then group them.\n        all_suggestions = sorted(self._suggested_requests, key=grouper)\n        groups = itertools.groupby(all_suggestions, key=grouper)\n\n        # Print each suggestion, grouped by recipient.\n        for group, suggestions in groups:\n\n            recipient, recipient_id = group\n            if recipient == USBRequestRecipient.INTERFACE:\n                print_html(f\"\\nOn interface {recipient_id}:\")\n            elif recipient == USBRequestRecipient.ENDPOINT:\n                print_html(f\"\\nOn endpoint {recipient_id}:\")\n            else:\n                print_html(f\"\\nOn the device:\")\n\n            for suggestion in suggestions:\n                direction, request_type, recipient, recipient_id, number = suggestion\n                metadata = self._suggested_request_metadata[suggestion]\n\n                # Find the associated text descriptions for the relevant field.\n                decorator = request_type_decorator[request_type]\n                direction_name = USBDirection(direction).name\n\n                # Generate basic metadata for our function.\n                request_number = f\"<ansiblue>{number}</ansiblue>\"\n                function_name = f\"handle_control_request_{number}\"\n\n                # Figure out if we want to use a cleaner request number.\n                if request_type == USBRequestType.STANDARD:\n                    try:\n                        request_number = f\"USBStandardRequests.{USBStandardRequests(number).name}\"\n                        function_name  = f\"handle_{USBStandardRequests(number).name.lower()}_request\"\n                    except ValueError:\n                        pass\n\n\n                # Figure out if we should include a target decorator.\n                if recipient in target_decorator:\n                    recipient_decorator = target_decorator[recipient]\n                    specific_recipient  = \"\"\n                else:\n                    recipient_decorator = None\n                    specific_recipient = f\"recipient=<ansiblue>{recipient}</ansiblue>, \"\n\n\n                #\n                # Print the code block.\n                #\n                print_html(\"\")\n\n                # Primary request decorator, e.g. \"@standard_request_handler\".\n                print_html(f\"    <ansigreen>{decorator}</ansigreen>(\"\n                        f\"number={request_number}, \"\n                        f\"{specific_recipient}\"\n                        f\"direction=USBDirection.{direction_name}\"\n                        f\")\")\n\n                # Recipient specifier; e.g. \"@to_device\"\n                if recipient_decorator:\n                    print_html(f\"    <ansigreen>{recipient_decorator}</ansigreen>\")\n\n                # Function definition.\n                print_html(f\"    <ansiwhite><b>def</b></ansiwhite> \"\n                        f\"<ansiyellow>{function_name}</ansiyellow>\"\n                        \"(self, request):\"\n                )\n\n                # Note about the requested length, if applicable.\n                if direction == USBDirection.IN:\n                    print_html(f\"        <ansimagenta># Most recent request was for {metadata['length']}B of data.</ansimagenta>\")\n                else:\n                    print_html(f\"        <ansimagenta># Most recent request data: {metadata['data']}.</ansimagenta>\")\n\n                # Default function body.\n                print_html(f\"        <ansimagenta># Replace me with your handler.</ansimagenta>\")\n                print_html(f\"        request.stall()\")\n\n\n    def print_suggested_additions(self):\n        \"\"\" Prints a collection of suggested additions to the stdout. \"\"\"\n\n        sys.stdout.flush()\n        sys.stderr.flush()\n\n        # Create a quick printing shortcut.\n        print_html = lambda data : print_formatted_text(HTML(data))\n\n        # Header.\n        print_html(\"\")\n        print_html(\"<b><u>Automatic Suggestions</u></b>\")\n        print_html(\"These suggestions are based on simple observed behavior;\")\n        print_html(\"not all of these suggestions may be useful / desirable.\")\n        print_html(\"\")\n\n        self._print_suggested_requests()\n        print_html(\"\")\n\n\n    #\n    # Backend helpers.\n    #\n\n    def set_address(self, address: int, defer: bool = False):\n        \"\"\" Updates the device's knowledge of its own address.\n\n        Args:\n            address : The address to apply.\n            defer   : If true, the address change should be deferred\n                      until the next time a control request ends. Should\n                      be set if we're changing the address before we ack\n                      the relevant transaction.\n        \"\"\"\n        self.address = address\n        self.backend.set_address(address, defer)\n\n\n    def get_descriptor(self) -> bytes:\n        \"\"\" Returns a complete descriptor for this device. \"\"\"\n\n        d = bytearray([\n            18,         # length of descriptor in bytes\n            1,          # descriptor type 1 == device\n            self.usb_spec_version & 0xff,\n            (self.usb_spec_version >> 8) & 0xff,\n            self.device_class,\n            self.device_subclass,\n            self.protocol_revision_number,\n            self.max_packet_size_ep0,\n            self.vendor_id & 0xff,\n            (self.vendor_id >> 8) & 0xff,\n            self.product_id & 0xff,\n            (self.product_id >> 8) & 0xff,\n            self.device_revision & 0xff,\n            (self.device_revision >> 8) & 0xff,\n            self.strings.get_index(self.manufacturer_string),\n            self.strings.get_index(self.product_string),\n            self.strings.get_index(self.serial_number_string),\n            len(self.configurations)\n        ])\n        return d\n\n\n    def get_configuration_descriptor(self, index: int) -> bytes:\n        \"\"\" Returns the configuration descriptor with the given configuration number. \"\"\"\n\n        # The index argument is zero-indexed; here, but configuration numbers\n        # are one-indexed (as 0 is unconfigured). Adjust accordingly.\n        return self.configurations[index + 1].get_descriptor()\n\n\n    def handle_get_supported_languages_descriptor(self) -> bytes:\n        \"\"\"Return the special string-descriptor-zero that indicates which languages are supported.\"\"\"\n\n        if self.supported_languages is None:\n            return None\n\n        # Our string descriptor is going to have two header bytes, plus two bytes\n        # for each language.\n        total_length = (len(self.supported_languages) * 2) + 2\n        packet = bytearray([total_length, DescriptorTypes.STRING])\n\n        for language in self.supported_languages:\n            packet.extend(language.to_bytes(2, byteorder='little'))\n\n        return bytes(packet)\n\n\n    def get_string_descriptor(self, index:int) -> bytes:\n        \"\"\" Returns the string descriptor associated with a given index. \"\"\"\n\n        if index == 0:\n            return self.handle_get_supported_languages_descriptor()\n        else:\n            return self.strings[index]\n\n    @staticmethod\n    def handle_generic_get_descriptor_request(\n            self: Union['USBDevice', USBInterface],\n            request: USBControlRequest):\n        \"\"\" Handle GET_DESCRIPTOR requests; per USB2 [9.4.3] \"\"\"\n\n        log.debug(f\"received GET_DESCRIPTOR request {request}\")\n\n        # Extract the core parameters from the request.\n        descriptor_type  = request.value_high\n        descriptor_index = request.value_low\n        identifier = (descriptor_type, descriptor_index)\n\n        # Try to find the specific descriptor for the request.\n        response = self.requestable_descriptors.get(identifier, None)\n        # If that fails, try to find a function covering this type.\n        if response is None:\n            response = self.requestable_descriptors.get(descriptor_type, None)\n\n        # If we have a callable, we need to evaluate it to figure\n        # out what the actual descriptor should be.\n        while callable(response):\n            response = response(descriptor_index)\n\n        # If we wound up with a valid response, reply with it.\n        if response:\n            response_length = min(request.length, len(response))\n            request.reply(response[:response_length])\n\n            log.trace(f\"sending {response_length} bytes in response\")\n        else:\n            log.trace(f\"stalling descriptor request\")\n            request.stall()\n\n\nclass USBDevice(USBBaseDevice):\n    \"\"\" Class representing the behavior of a USB device.\n\n    This default implementation provides standard request handlers\n    in order to facilitate creating a host-compatible USB device.\n\n    These functions can be overloaded to change their behavior. If\n    you want to dramatically change the behavior of these requests,\n    you can opt to use USBBaseDevice, which lacks standard request\n    handling.\n\n    Fields:\n        device_class/device_subclass/protocol_revision_number --\n                The USB descriptor fields that select the class, subclass, and protcol.\n        vendor_id, product_id --\n                The USB vendor and product ID for this device.\n        manufacturer_string, product_string, serial_number_string --\n                Python strings identifying the device to the USB host.\n        supported_languages --\n                A tuple containing all of the language IDs supported by the device.\n        device_revision --\n                Number indicating the hardware revision of this device. Typically BCD.\n        usb_spec_revision --\n                Number indicating the version of the USB specification we adhere to. Typically 0x0200.\n    \"\"\"\n\n\n    @standard_request_handler(number=USBStandardRequests.GET_STATUS)\n    @to_device\n    def handle_get_status_request(self, request):\n        \"\"\" Handles GET_STATUS requests; per USB2 [9.4.5].\"\"\"\n\n        log.debug(\"received GET_STATUS request\")\n\n        # self-powered and remote-wakeup (USB 2.0 Spec section 9.4.5)\n        request.reply(b'\\x03\\x00')\n\n\n    @standard_request_handler(number=USBStandardRequests.CLEAR_FEATURE)\n    @to_device\n    def handle_clear_feature_request(self, request):\n        \"\"\" Handle CLEAR_FEATURE requests; per USB2 [9.4.1] \"\"\"\n        log.debug(f\"Received CLEAR_FEATURE request with type {request.number} and value {request.value}.\")\n        request.acknowledge()\n\n\n    @standard_request_handler(number=USBStandardRequests.SET_FEATURE)\n    @to_device\n    def handle_set_feature_request(self, request):\n        \"\"\" Handle SET_FEATURE requests; per USB2 [9.4.9] \"\"\"\n        log.debug(\"received SET_FEATURE request\")\n        request.stall()\n\n\n    @standard_request_handler(number=USBStandardRequests.SET_ADDRESS)\n    @to_device\n    def handle_set_address_request(self, request):\n        \"\"\" Handle SET_ADDRESS requests; per USB2 [9.4.6] \"\"\"\n        request.acknowledge(blocking=True)\n        self.set_address(request.value)\n\n\n    @standard_request_handler(number=USBStandardRequests.GET_DESCRIPTOR)\n    @to_device\n    def handle_get_descriptor_request(self, request):\n        \"\"\" Handle GET_DESCRIPTOR requests; per USB2 [9.4.3] \"\"\"\n\n        # Defer to our generic get_descriptor handler.\n        self.handle_generic_get_descriptor_request(self, request)\n\n\n\n    @standard_request_handler(number=USBStandardRequests.SET_DESCRIPTOR)\n    @to_device\n    def handle_set_descriptor_request(self, request):\n        \"\"\" Handle SET_DESCRIPTOr requests; per USB2 [9.4.8] \"\"\"\n        log.debug(\"received SET_DESCRIPTOR request\")\n        request.stall()\n\n\n    @standard_request_handler(number=USBStandardRequests.GET_CONFIGURATION)\n    @to_device\n    def handle_get_configuration_request(self, request):\n        \"\"\" Handle GET_CONFIGURATION requests; per USB2 [9.4.2] \"\"\"\n        log.debug(f\"received GET_CONFIGURATION request for configuration {request.value}\")\n\n        # If we haven't yet been configured, send back a zero configuration value.\n        if self.configuration is None:\n            request.reply(b\"\\x00\")\n\n        # Otherwise, return the index for our configuration.\n        else:\n            config_index = self.configuration.number\n            request.reply(config_index.to_bytes(1, byteorder='little'))\n\n\n    @standard_request_handler(number=USBStandardRequests.SET_CONFIGURATION)\n    @to_device\n    def handle_set_configuration_request(self, request):\n        \"\"\" Handle SET_CONFIGURATION requests; per USB2 [9.4.7] \"\"\"\n        log.debug(\"received SET_CONFIGURATION request\")\n\n        # If the host is requesting configuration zero, they're asking\n        # us to drop our configuration.\n        if request.value == 0:\n            self.configuration = None\n            request.acknowledge()\n\n        # Otherwise, we'll find a given configuration and apply it.\n        else:\n            try:\n                self.configuration = self.configurations[request.value]\n\n                # On a configuration change, all interfaces revert\n                # to alternate setting 0.\n                self.configuration.active_interfaces = {\n                    interface.number: interface\n                        for interface in self.configuration.get_interfaces()\n                            if interface.alternate == 0\n                }\n\n                request.acknowledge()\n            except KeyError:\n                request.stall()\n\n        # Notify the backend of the reconfiguration, in case\n        # it needs to e.g. set up endpoints accordingly\n        self.backend.configured(self.configuration)\n\n\n    # USB 2.0 specification, section 9.4.11 (p 288 of pdf)\n    @standard_request_handler(number=USBStandardRequests.SYNCH_FRAME)\n    @to_device\n    def handle_synch_frame_request(self, request):\n        \"\"\" Handle SYNC_FRAME requests; per USB2 [9.4.10] \"\"\"\n        log.debug(f\"f{self.name} received SYNCH_FRAME request\")\n        request.acknowledge()\n\n\n    def generate_code(self, name=\"Device\"):\n\n        languages = [f\"LanguageIDs.{l.name}\" for l in self.supported_languages]\n\n        if len(languages) == 1:\n            languages = f\"({languages[0]},)\"\n        else:\n            languages = f\"({str.join(', '), languages})\"\n\n        if self.device_speed is None:\n            speed = \"None\"\n        else:\n            speed = f\"DeviceSpeed.{self.device_speed.name}\"\n\n        code = f\"\"\"\n@use_inner_classes_automatically\nclass {name}(USBDevice):\n    device_speed             = {speed}\n    device_class             = {self.device_class}\n    device_subclass          = {self.device_subclass}\n    protocol_revision_number = {self.protocol_revision_number}\n    max_packet_size_ep0      = {self.max_packet_size_ep0}\n    vendor_id                = 0x{self.vendor_id:04x}\n    product_id               = 0x{self.product_id:04x}\n    manufacturer_string      = {self.manufacturer_string.generate_code()}\n    product_string           = {self.product_string.generate_code()}\n    serial_number_string     = {self.serial_number_string.generate_code()}\n    supported_languages      = {languages}\n    device_revision          = 0x{self.device_revision:04x}\n    usb_spec_version         = 0x{self.usb_spec_version:04x}\n\"\"\"\n\n        for configuration_id in sorted(self.configurations):\n            code += self.configurations[configuration_id].generate_code(indent=4)\n\n        return code\n"
  },
  {
    "path": "facedancer/devices/__init__.py",
    "content": "#\n# This file is part of Facedancer.\n#\n\nimport sys\nimport pprint\nimport asyncio\nimport inspect\nimport argparse\n\nfrom ..errors  import EndEmulation\nfrom ..logging import configure_default_logging, log\n\ndef default_main(device_or_type, *coroutines):\n    \"\"\" Simple, default main for Facedancer emulation.\n\n    Parameters:\n        device_type -- The USBDevice type to emulate.\n    \"\"\"\n\n    # Instantiate the relevant device, and connect it to our host.\n    parser = argparse.ArgumentParser(description=f\"Emulation frontend for {device_or_type.name}(s).\")\n    parser.add_argument('--print-only', action='store_true', help=\"Prints information about the device without emulating.\")\n    parser.add_argument('--suggest', action='store_true', help=\"Prints suggested code additions after device emualtion is complete.\")\n    parser.add_argument('-v', '--verbose', help=\"Controls verbosity. 0=silent, 3=default, 5=spammy\", default=3)\n    args = parser.parse_args()\n\n    # Set up our logging output.\n    python_loglevel = 50 - (int(args.verbose) * 10)\n    configure_default_logging(level=python_loglevel)\n\n    if inspect.isclass(device_or_type):\n        device = device_or_type()\n    else:\n        device = device_or_type\n\n    if args.print_only:\n        pprint.pprint(device)\n        sys.exit(0)\n\n    # Run the relevant code, along with any added coroutines.\n    log.info(\"Starting emulation, press 'Control-C' to disconnect and exit.\")\n    try:\n        device.emulate(*coroutines)\n    except KeyboardInterrupt:\n        pass\n    finally:\n        if args.suggest:\n            device.print_suggested_additions()\n"
  },
  {
    "path": "facedancer/devices/ftdi.py",
    "content": "# pylint: disable=unused-wildcard-import, wildcard-import\n#\n# This file is part of Facedancer.\n#\n\"\"\" Emulation of an FTDI USB-to-serial converter. \"\"\"\n\nimport asyncio\nimport struct\n\nfrom enum   import IntFlag\nfrom typing import Union\n\nfrom .         import default_main\nfrom ..        import *\nfrom ..classes import USBDeviceClass\n\nfrom ..logging import log\n\nOUT_ENDPOINT = 2\nIN_ENDPOINT  = 1\n\n\nclass FTDIFlowControl(IntFlag):\n    \"\"\" Constants describing how FTDI flow control works. \"\"\"\n    NO_FLOW_CONTROL = 0\n    RTS_CTS         = 1\n    DTR_DSR         = 2\n    XON_XOFF        = 4\n\n\n@use_inner_classes_automatically\nclass FTDIDevice(USBDevice):\n    \"\"\" Class implementing an emulated FTDI device. \"\"\"\n\n    vendor_id           : int = 0x0403\n    product_id          : int = 0x6001\n    device_revision     : int = 0x0600\n    serial_number       : str = \"FT123450\"\n\n    manufacturer_string      : StringRef = StringRef.field(string=\"not-FTDI\")\n    product_string           : StringRef = StringRef.field(string=\"FTDI emulation\")\n    serial_number_string     : StringRef = StringRef.field(string=serial_number)\n\n    eeprom_data = [\n        0x0440, 0x0304, 0x0160, 0x0006, 0x802D, 0x0800, 0x0002, 0x1812,\n        0x2A20, 0x4812, 0x0000, 0x0000, 0x1203, 0x6E00, 0x6F00, 0x7400,\n        0x2D00, 0x4600, 0x5400, 0x4400, 0x4900, 0x1E03, 0x4600, 0x5400,\n        0x4400, 0x4900, 0x2000, 0x6500, 0x6D00, 0x7500, 0x6C00, 0x6100,\n        0x7400, 0x6900, 0x6F00, 0x6E00, 0x1203, 0x4600, 0x5400, 0x3100,\n        0x3200, 0x3300, 0x3400, 0x3500, 0x3000, 0x0000, 0x0000, 0x0000,\n        0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,\n        0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x6B67\n    ]\n\n    class _Configuration(USBConfiguration):\n        configuration_string : str = \"FTDI config\"\n\n        class _Interface(USBInterface):\n\n            # This is a completely vendor-specific device.\n            class_number    : int = USBDeviceClass.VENDOR_SPECIFIC\n            subclass_number : int = USBDeviceClass.VENDOR_SPECIFIC\n            protocol_number : int = USBDeviceClass.VENDOR_SPECIFIC\n\n            class _OutEndpoint(USBEndpoint):\n                number        : int          = OUT_ENDPOINT\n                direction     : USBDirection = USBDirection.OUT\n                transfer_type : USBTransferType = USBTransferType.BULK\n\n            class _InEndpoint(USBEndpoint):\n                number        : int          = IN_ENDPOINT\n                direction     : USBDirection = USBDirection.IN\n                transfer_type : USBTransferType = USBTransferType.BULK\n\n\n    def __post_init__(self):\n        super().__post_init__()\n        # FTDI Windows driver reads serial number descriptor after reading the EEPROM for verification\n        self.strings.add_string(self.serial_number, index=3)\n        self.reset_ftdi()\n\n\n    def reset_ftdi(self):\n        \"\"\" Resets the FTDI driver back to its original state. \"\"\"\n\n        # Create a fake baud rate.\n        self.baud_rate          = 9600\n\n        # Start off with DTR/RTS disabled.\n        self.use_dtr             = False\n        self.use_rts             = False\n\n        # Create synthetic values for our control signals.\n        self.clear_to_send       = True\n        self.data_set_ready      = True\n        self.ring_detect         = False\n        self.line_status_detect  = True\n        self.data_terminal_ready = False\n        self.ready_to_send       = False\n\n        # Start off with no flow control.\n        self.flow_control        = FTDIFlowControl.NO_FLOW_CONTROL\n\n\n    #\n    # Request handlers.\n    #\n\n    @vendor_request_handler(number=0)\n    def handle_reset_request(self, request):\n        log.debug(\"Received FTDI reset; assuming initial settings.\")\n\n        self.reset_ftdi()\n        request.acknowledge()\n\n\n    @vendor_request_handler(number=1)\n    def handle_modem_ctrl_request(self, req):\n        log.debug(\"received modem_ctrl request\")\n\n        dtr          = bool(req.value & 0x0001)\n        rts          = bool(req.value & 0x0002)\n        self.use_dtr = bool(req.value & 0x0100)\n        self.use_rts = bool(req.value & 0x0200)\n\n        if dtr:\n            log.info(\"DTR set -- host appears to have connected via virtual serial.\")\n        else:\n            log.info(\"DTR cleared -- host appears to have disconnected from virtual serial.\")\n\n        if self.use_dtr:\n            self.data_terminal_ready = dtr\n        if self.use_rts:\n            self.ready_to_send = rts\n\n        req.acknowledge()\n\n\n\n    @vendor_request_handler(number=2)\n    def handle_set_flow_ctrl_request(self, request):\n        \"\"\" Control request to set up flow control. \"\"\"\n\n        try:\n            self.flow_control = FTDIFlowControl(request.value)\n\n            if self.flow_control:\n                log.info(f\"Host has set up {self.flow_control.name} flow control.\")\n            else:\n                log.info(f\"Host has disabled flow control.\")\n\n            request.acknowledge()\n        except KeyError:\n            request.stall()\n\n\n\n    @vendor_request_handler(number=3)\n    def handle_set_baud_rate_request(self, request):\n        \"\"\" Control request to set our baud rate. \"\"\"\n\n        if request.value > 9:\n            log.warning(\"Host specified an unknown baud rate value.\")\n            request.acknowledge()\n            return\n\n        # For most values, the FTDI device uses the value to set the baud divisor,\n        # such that 0 = 300, 1 = 600, etc.\n        if request.value < 8:\n            self.baud_rate = 300 * (2 ** request.value)\n\n        # For values of 8/9, it jumps up to hit two more standard bauds.\n        elif request.value == 8:\n            self.baud_rate = 57600\n        elif request.value == 9:\n            self.baud_rate = 115200\n\n        log.info(f\"Host set baud rate to {self.baud_rate}.\")\n        request.acknowledge()\n\n\n    @vendor_request_handler(number=4)\n    def handle_set_data_request(self, request):\n        log.debug(\"received set_data request\")\n        request.acknowledge()\n\n\n    @vendor_request_handler(number=5)\n    def handle_get_modem_status_request(self, request):\n        \"\"\" Handles requests for the FTDI device's modem status. \"\"\"\n\n        # Currently, we're emulating the original FTDI SIO, so we only provide\n        # a single byte of status. Otherwise, we'd have an second byte with line status.\n        response = \\\n            (1 << 4) if self.clear_to_send      else 0 | \\\n            (1 << 5) if self.data_set_ready     else 0 | \\\n            (1 << 6) if self.ring_detect        else 0 | \\\n            (1 << 7) if self.line_status_detect else 0\n        request.reply((response,))\n\n\n    @vendor_request_handler(number=6)\n    def handle_set_event_char_request(self, request):\n        log.debug(\"received set_event_char request\")\n        request.acknowledge()\n\n\n    @vendor_request_handler(number=7)\n    def handle_set_error_char_request(self, request):\n        log.debug(\"received set_error_char request\")\n        request.acknowledge()\n\n\n    @vendor_request_handler(number=9)\n    def handle_set_latency_timer_request(self, request):\n        log.debug(\"received set_latency_timer request\")\n        request.acknowledge()\n\n\n    @vendor_request_handler(number=10)\n    def handle_get_latency_timer_request(self, request):\n        log.debug(\"received get_latency_timer request\")\n\n        # Per Travis Goodspeed, this is a \"bullshit value\".\n        request.reply(b'\\x01')\n\n    @vendor_request_handler(number=144)\n    def handle_read_eeprom_request(self, request):\n        log.info(f\"received read_eeprom request at index {request.index}\")\n\n        if 0 <= request.index < len(self.eeprom_data):\n            data_word = self.eeprom_data[request.index]\n            response_bytes = struct.pack('>H', data_word)\n            request.reply(response_bytes)\n            log.debug(f\"Handled EEPROM read at index {request.index}: sent {response_bytes.hex()}\")\n        elif request.index == 66:\n            request.reply(struct.pack('>H', 0x0000))\n            log.debug(f\"Handled EEPROM read at index {request.index}: sent 0000\")\n        elif request.index < 128:\n            request.reply(struct.pack('>H', 0xFFFF))\n            log.debug(f\"Handled EEPROM read at index {request.index}: sent FFFF\")\n        else:\n            request.stall()\n            log.info(f\"EEPROM read at out-of-bounds index {request.index}: stalled\")\n\n    #\n    # Internal event handlers.\n    #\n\n    def handle_data_received(self, endpoint, data):\n        \"\"\" Called back whenever data is received. \"\"\"\n        log.debug(f\"received {len(data)} bytes on {endpoint}\")\n        self.handle_serial_data_received(data)\n\n\n    #\n    # User I/O interface.\n    #\n    async def wait_for_host(self):\n        \"\"\" Waits until the host connects by waiting for DTR assertion. \"\"\"\n\n        # Wait for the host to assert DTR.\n        while not self.data_terminal_ready:\n            await asyncio.sleep(0.1)\n\n\n    def handle_serial_data_received(self, data):\n        \"\"\" Callback executed when serial data is received.\n\n        Subclasses should override this to capture data from the host.\n        \"\"\"\n        log.debug(f\"Received serial data: {data}\")\n\n\n    def transmit(self, data: Union[str, bytes], *, blocking: bool = False, adjust_endings: bool = True):\n        \"\"\" Transmits a block of data over the provided FTDI link to the host.\n\n        Parameters:\n            data           -- The data to be sent.\n            blocking       -- If true, this method will wait for completion before returning.\n            adjust_endings -- If true, line endings will be adjusted before sending.\n        \"\"\"\n\n        FTDI_PAYLOAD_LENGTH = 62\n\n        # If this isn't a set of raw bytes, encode it into bytes.\n        if hasattr(data, 'encode'):\n            if adjust_endings:\n                data = data.replace(\"\\n\", \"\\r\\n\")\n\n            data = data.encode('utf-8')\n\n        # Packetize and send the relevant data.\n        data = bytearray(data)\n\n        while data:\n            packet = data[0:FTDI_PAYLOAD_LENGTH]\n            del data[0:FTDI_PAYLOAD_LENGTH]\n\n            self._transmit_packet(packet, blocking=blocking)\n\n\n    def _transmit_packet(self, data: bytes, *, blocking: bool = False):\n        \"\"\" Sends a single packet of up to 63 data bytes over our link. \"\"\"\n\n        # Generate an FTDI packet.\n        packet = bytearray()\n\n        # Our first/header byte contains the payload length in bits [7:2], and a packet ID of 01 in [1:0].\n        packet.append((len(data) << 2) | 0b01)\n        packet.append(0)\n\n        # The remainder of the packet is our payload.\n        packet.extend(data)\n        self.send(IN_ENDPOINT, packet, blocking=blocking)\n\n\n\nif __name__ == \"__main__\":\n    default_main(FTDIDevice)\n"
  },
  {
    "path": "facedancer/devices/keyboard.py",
    "content": "# pylint: disable=unused-wildcard-import, wildcard-import\n#\n# This file is part of Facedancer.\n#\n\nimport asyncio\n\nfrom typing                   import Iterable\n\nfrom .                        import default_main\n\nfrom ..                       import *\nfrom ..classes.hid.usage      import *\nfrom ..classes.hid.descriptor import *\nfrom ..classes.hid.keyboard   import *\n\n\n# Specifies how many simultaneously keys we want to support.\nKEY_ROLLOVER = 8\n\n\n@use_inner_classes_automatically\nclass USBKeyboardDevice(USBDevice):\n    \"\"\" Simple USB keyboard device. \"\"\"\n\n    name           : str = \"USB keyboard device\"\n    product_string : str = \"Non-suspicious Keyboard\"\n\n\n    class KeyboardConfiguration(USBConfiguration):\n        \"\"\" Primary USB configuration: act as a keyboard. \"\"\"\n\n\n        class KeyboardInterface(USBInterface):\n            \"\"\" Core HID interface for our keyboard. \"\"\"\n\n            name         : str = \"USB keyboard interface\"\n            class_number : int = 3\n\n\n            class KeyEventEndpoint(USBEndpoint):\n                number        : int             = 3\n                direction     : USBDirection    = USBDirection.IN\n                transfer_type : USBTransferType = USBTransferType.INTERRUPT\n                interval      : int             = 10\n\n\n            #\n            # Raw descriptors -- TODO: build these from their component parts.\n            #\n\n\n            class HIDDescriptor(USBDescriptor):\n                number            : int   = 0\n                type_number       : int   = USBDescriptorTypeNumber.HID\n                raw               : bytes = b'\\x09\\x21\\x10\\x01\\x00\\x01\\x22\\x2b\\x00'\n                include_in_config : bool  = True\n\n\n            class ReportDescriptor(HIDReportDescriptor):\n                number : int   =  0\n                fields : tuple = (\n\n                    # Identify ourselves as a keyboard.\n                    USAGE_PAGE       (HIDUsagePage.GENERIC_DESKTOP),\n                    USAGE            (HIDGenericDesktopUsage.KEYBOARD),\n                    COLLECTION       (HIDCollection.APPLICATION),\n                    USAGE_PAGE       (HIDUsagePage.KEYBOARD),\n\n                    # Modifier keys.\n                    # These span the full range of modifier key codes (left control to right meta),\n                    # and each has two possible values (0 = unpressed, 1 = pressed).\n                    USAGE_MINIMUM    (KeyboardKeys.LEFTCTRL),\n                    USAGE_MAXIMUM    (KeyboardKeys.RIGHTMETA),\n                    LOGICAL_MINIMUM  (0),\n                    LOGICAL_MAXIMUM  (1),\n                    REPORT_SIZE      (1),\n                    REPORT_COUNT     (KeyboardKeys.RIGHTMETA - KeyboardKeys.LEFTCTRL + 1),\n                    INPUT            (variable=True),\n\n                    # One byte of constant zero-padding.\n                    # This is required for compliance; and Windows will ignore this report\n                    # if the zero byte isn't present.\n                    REPORT_SIZE      (8),\n                    REPORT_COUNT     (1),\n                    INPUT            (constant=True),\n\n                    # Capture our actual, pressed keyboard keys.\n                    # Support a standard, 101-key keyboard; which has\n                    # keycodes from 0 (NONE) to 101 (COMPOSE).\n                    #\n                    # We provide the capability to press up to eight keys\n                    # simultaneously. Setting the REPORT_COUNT effectively\n                    # sets the key rollover; so 8 reports means we can have\n                    # up to eight keys pressed at once.\n                    USAGE_MINIMUM    (KeyboardKeys.NONE),\n                    USAGE_MAXIMUM    (KeyboardKeys.COMPOSE),\n                    LOGICAL_MINIMUM  (KeyboardKeys.NONE),\n                    LOGICAL_MAXIMUM  (KeyboardKeys.COMPOSE),\n\n                    REPORT_SIZE      (8),\n                    REPORT_COUNT     (KEY_ROLLOVER),\n                    INPUT            (),\n\n                    # End the report.\n                    END_COLLECTION   (),\n                )\n\n\n            @class_request_handler(number=USBStandardRequests.GET_INTERFACE)\n            @to_this_interface\n            def handle_get_interface_request(self, request):\n                # Silently stall GET_INTERFACE class requests.\n                request.stall()\n\n\n    def __post_init__(self):\n        super().__post_init__()\n\n        # Keep track of any pressed keys, and any pressed modifiers.\n        self.active_keys = set()\n        self.modifiers   = 0\n\n\n    def _generate_hid_report(self) -> bytes:\n        \"\"\" Generates a single HID report for the given keyboard state. \"\"\"\n\n        # If we have active keypresses, compose a set of scancodes from them.\n        scancodes = \\\n             list(self.active_keys)[:KEY_ROLLOVER] + \\\n             [0] * (KEY_ROLLOVER - len(self.active_keys))\n\n        return bytes([self.modifiers, 0, *scancodes])\n\n\n    def handle_data_requested(self, endpoint: USBEndpoint):\n        \"\"\" Provide data once per host request. \"\"\"\n        report = self._generate_hid_report()\n        endpoint.send(report)\n\n\n    #\n    # User-facing API.\n    #\n\n\n    def key_down(self, code: KeyboardKeys):\n        \"\"\" Marks a given key as pressed; should be a scancode from KeyboardKeys. \"\"\"\n        self.active_keys.add(code)\n\n\n    def key_up(self, code: KeyboardKeys):\n        \"\"\" Marks a given key as released; should be a scancode from KeyboardKeys. \"\"\"\n        self.active_keys.remove(code)\n\n\n    def modifier_down(self, code: KeyboardModifiers):\n        \"\"\" Marks a given modifier as pressed; should be a flag from KeyboardModifiers. \"\"\"\n        if code is not None:\n            self.modifiers |= code\n\n\n    def modifier_up(self, code: KeyboardModifiers):\n        \"\"\" Marks a given modifier as released; should be a flag from KeyboardModifiers. \"\"\"\n        if code is not None:\n            self.modifiers &= ~code\n\n\n    async def type_scancode(self, code: KeyboardKeys, duration: float = 0.1, modifiers: KeyboardModifiers = None):\n        \"\"\" Presses, and then releases, a single key.\n\n        Parameters:\n            code      -- The keyboard key to be pressed's scancode.\n            duration  -- How long the given key should be pressed, in seconds.\n            modifiers -- Any modifier keys that should be held while typing.\n        \"\"\"\n\n        self.modifier_down(modifiers)\n        self.key_down(code)\n\n        await asyncio.sleep(duration)\n\n        self.key_up(code)\n        self.modifier_up(modifiers)\n\n        await asyncio.sleep(duration)\n\n\n    async def type_scancodes(self, *codes: Iterable[KeyboardKeys], duration: float = 0.1):\n        \"\"\" Presses, and then releases, a collection of keys, in order.\n\n        Parameters:\n            *code     -- The keyboard keys to be pressed's scancodes.\n            duration  -- How long each key should be pressed, in seconds.\n        \"\"\"\n        for code in codes:\n            await self.type_scancode(code, duration=duration)\n\n\n    async def type_letter(self, letter: str, duration: float = 0.1, modifiers: KeyboardModifiers = None):\n        \"\"\" Attempts to type a single letter, based on its ASCII string representation.\n\n        Parameters:\n            letter    -- A single-character string literal, to be typed.\n            duration  -- How long each key should be pressed, in seconds.\n            modifiers -- Any modifier keys that should be held while typing.\n        \"\"\"\n        shift, code = KeyboardKeys.get_scancode_for_ascii(letter)\n        modifiers   = shift if modifiers is None else modifiers | shift\n\n        await self.type_scancode(code, modifiers=modifiers, duration=duration)\n\n\n    async def type_letters(self, *letters: Iterable[str], duration:float = 0.1):\n        \"\"\" Attempts to type a string of letters, based on ASCII string representations.\n\n        Parameters:\n            *letters  -- A collection of single-character string literal, to be typed in order.\n            duration  -- How long each key should be pressed, in seconds.\n        \"\"\"\n        for letter in letters:\n            await self.type_letter(letter, duration=duration)\n\n\n    async def type_string(self, to_type: str, *, duration:float = 0.1, modifiers: KeyboardModifiers = None):\n        \"\"\" Attempts to type a python string into the remote host.\n\n        Parameters:\n            letter    -- A collection of single-character string literal, to be typed in order.\n            duration  -- How long each key should be pressed, in seconds.\n            modifiers -- Any modifier keys that should be held while typing.\n        \"\"\"\n\n        self.modifier_down(modifiers)\n\n        for letter in to_type:\n            await self.type_letter(letter, duration=duration)\n\n        self.modifier_up(modifiers)\n\n\n    def all_keys_up(self, *, include_modifiers: bool = True):\n        \"\"\" Releases all keys currently pressed.\n\n        Parameters:\n            include_modifiers -- If set to false, modifiers will be left at their current states.\n        \"\"\"\n        self.active_keys.clear()\n\n        if include_modifiers:\n            self.all_modifiers_up()\n\n\n    def all_modifiers_up(self):\n        \"\"\" Releases all modifiers currently held. \"\"\"\n        self.modifiers = 0\n\n\nif __name__ == \"__main__\":\n    default_main(USBKeyboardDevice)\n"
  },
  {
    "path": "facedancer/devices/umass/__init__.py",
    "content": "from .umass       import *\nfrom .disk_image  import *\n"
  },
  {
    "path": "facedancer/devices/umass/disk_image.py",
    "content": "from mmap import mmap\n\nimport os\n\n\nclass DiskImage:\n    \"\"\"\n        Class representing an arbitrary disk image, which can be procedurally generated,\n        or which can be rendered from e.g. a file.\n\n        Currently limited to representing disk with 512-byte sectors.\n    \"\"\"\n\n    def close(self):\n        \"\"\" Closes and cleans up any resources held by the disk image. \"\"\"\n        pass\n\n    def get_sector_size(self):\n        return 512\n\n    def get_sector_count(self):\n        \"\"\" Returns the disk's sector count. \"\"\"\n        raise NotImplementedError()\n\n    def get_data(self, address, length):\n        data_to_read = length\n        sector_size  = self.get_sector_size()\n        data         = bytes()\n\n        while data_to_read > 0:\n            data.extend(self.get_sector_data(address))\n            data_to_read -= sector_size\n\n            address += 1\n\n        return data\n\n\n    def get_sector_data(self, address):\n        \"\"\" Returns the raw binary data for a given sector. \"\"\"\n        raise NotImplementedError()\n\n\n    def put_data(self, address, data):\n        sector_size   = self.get_sector_size()\n\n        while data:\n            sector = data[:sector_size]\n            data   = data[sector_size:]\n\n            self.put_sector_data(address, sector)\n            address += 1\n\n        return data\n\n    def put_sector_data(self, address, data):\n        \"\"\" Sets the raw binary data for a given disk sector. \"\"\"\n        sys.stderr.write(\"WARNING: UMS write ignored; this type of image does not support writing.\\n\")\n\n\nclass FAT32DiskImage(DiskImage):\n    \"\"\"\n    Class for manufacturing synthetic FAT32 disk images.\n    \"\"\"\n\n    CLUSTER_SIZE         = 512\n\n    MBR_SECTOR           = 0\n    BPB_SECTOR           = 2048   # set by our MBR partition entry\n    FSINFO_SECTOR        = 2049   # set by our BPB entry\n    FAT_START            = 2080   # specified by our BPB entry (partition start + reserved sectors)\n    FAT_END              = 6113   # specified by our BPB entry (fat start + fat size)\n    DATA_SECTION_START   = 10146  # specified by our BPB entry (fat start + num_fats * fat_size)\n    ROOT_DIR_ENTRY       = 10146  # specified by our BPB entry (we put the directory at the very start)\n\n    def __init__(self, size = 1024 * 1024 * 256, verbose=0):\n        self.verbose = verbose\n        self.size = size\n\n        # Initialize the commands we'll use to handle sector writes.\n        self._initialize_sector_handlers()\n\n\n    def _register_sector_handler(self, sector_or_lambda, name, handler=None):\n\n        if handler is None:\n            handler = self.handle_unhandled_sector\n\n        descriptor = {\n            \"sector_or_lambda\": sector_or_lambda,\n            \"name\": name,\n            \"handler\": handler\n        }\n\n        self.sector_handlers.append(descriptor)\n\n\n    def _initialize_sector_handlers(self):\n        self.sector_handlers = []\n\n        # Handlers for disk special sectors...\n        self._register_sector_handler(self.MBR_SECTOR, \"MBR/partition table\", self.handle_mbr_read)\n        self._register_sector_handler(self.BPB_SECTOR, \"BIOS Parameter Block\", self.handle_bpb_read)\n        self._register_sector_handler(self.FSINFO_SECTOR, \"FSINFO Block\", self.handle_fsinfo_read)\n        self._register_sector_handler(lambda x : x >= self.FAT_START and x < self.FAT_END, \"File Allocation Table\", self.handle_fat_read)\n        self._register_sector_handler(self.ROOT_DIR_ENTRY, \"Root Directory\", self.handle_root_dir_read)\n\n\n    def handle_mbr_read(self, address):\n        \"\"\"\n        Returns a master boot record directing the target device to our\n        emulated FAT32 partition.\n        \"\"\"\n\n        response  = 440 * b'\\0'                           # bootstrap code + timestamp\n        response += b'\\xDE\\xAD\\xBE\\xEF'                   # disk signature (we're making one up)\n        response += b'\\x00\\x00'                           # 0 = not copy protected\n        response += self._generate_fat_partition_entry()  # partition entry for our FAT32 partition\n        response += (16 * 3) * b'\\0'                      # three empty partition slots\n        response += b'\\x55\\xAA'                           # end of sector signature\n        return response\n\n\n    def handle_bpb_read(self, address):\n        \"\"\"\n            Returns a valid Boot Parameter Block, which tells the device how to\n            interpret our FAT filesystem.\n        \"\"\"\n\n        response  = b'\\xEB\\x00\\x90'     # jump to bootloader (oddly, checked on some non-x86 uCs)\n        response += b'MSWIN4.1'         # OEM name (this one seems broadly compatible)\n\n        # Bytes per disk sector.\n        response += self.get_sector_size().to_bytes(2, byteorder='little')\n\n        # Sectors per cluster.\n        response += self._sectors_per_cluster().to_bytes(1, byteorder='little')\n\n        response += b'\\x20\\x00'          # reserved sectors\n        response += b'\\x02'              # number of FATs (must be 2)\n        response += b'\\x00\\x00'          # root entries (must be 0 for fat32)\n        response += b'\\x00\\x00'          # total 16-bit count of sectors (must be 0 for fat32)\n        response += b'\\xF8'              # media type: hard drive (0xF8)\n        response += b'\\x00\\x00'          # sectors per FAT (must be 0 for fat32)\n        response += b'\\x00\\x00'          # sectors per track (most likely ignored)\n        response += b'\\x00\\x00'          # number of heads (most likely ignored)\n        response += b'\\x00\\x00\\x00\\x00'  # hidden sectors (most likely ignored)\n\n        # The total number of sectors in the volume.\n        response += self.get_partition_sectors().to_bytes(4, byteorder='little')\n\n        response += b'\\xC1\\x0F\\x00\\x00'  # sectors per FAT\n        response += b'\\x00\\x00'          # flags\n        response += b'\\x00\\x00'          # filesystem revision\n        response += b'\\x02\\x00\\x00\\x00'  # cluster for the root directory\n        response += b'\\x01\\x00'          # address of the fsinfo sector\n        response += b'\\x06\\x00'          # address of the backup of the boot sector\n        response += 12 * b'\\x00'         # reserved space\n        response += b'\\x80'              # drive number for PC-BIOS (0x80 = hard disk)\n        response += b'\\x00'              # reserved space\n        response += b'\\x29'              # boot signature (from mkfs.vfat)\n        response += b'0000'              # disk serial number (for volume tracking)\n        response += b'Facedancer '       # volume label (must be 11 bytes; spaces for padding)\n        response += b'FAT32   '          # should be \"FAT32\" for FAT32, padded to eight bytes\n        response += 420 * b'\\x00'        # reserved space\n        response += b'\\x55\\xAA'          # end of sector marker\n\n        return response\n\n\n    def handle_fsinfo_read(self, address):\n        \"\"\"\n            Returns a valid filesystem info block, which is used to cache information\n            about free sectors on the filesystem. We don't actually sport writing,\n            so we return a valid-but-useless block.\n        \"\"\"\n\n        response  = b'\\x52\\x52\\x61\\x41'  # fsinfo block signature (magic number)\n        response += 480 * b'\\x00'        # reserved for future use\n        response += b'\\x72\\x72\\x41\\x61'  # second signature (magic number)\n        response += b'\\xFF\\xFF\\xFF\\xFF'  # free sector count (-1 = \"don't know\")\n        response += b'\\xFF\\xFF\\xFF\\xFF'  # next free sector (-1 = \"don't know\")\n        response += 12 * b'\\x00'         # reserved for future use\n        response += b'\\x00\\x00\\x55\\xAA'  # final signature (magic number)\n\n        return response\n\n    def _generate_directory_entry(self, filename, file_size, cluster_number, flags=b'\\x00'):\n\n        # TODO: automatically convert filename, filesize to bytes\n        # TODO: support long form name entries?\n\n\n        cluster_number_bytes = cluster_number.to_bytes(4, byteorder='little')\n        cluster_number_low   = cluster_number_bytes[:2]\n        cluster_number_high  = cluster_number_bytes[2:]\n\n        entry  = filename             # short name (first 8 are name; last three are extension)\n        entry += flags                # file attributes\n        entry += b'\\x00'              # reserved byte\n        entry += 5 * b'\\x00'          # dir creation date/time\n        entry += 2 * b'\\x00'          # last access date\n        entry += cluster_number_high  # high word of the entry's first cluster number\n        entry += 4 * b'\\x00'          # last write date/time\n        entry += cluster_number_low   # low word of the entry's first cluster number\n        entry += file_size.to_bytes(4, byteorder='little')\n        return entry\n\n\n    def _short_filename_checksum(self, short_filename):\n        \"\"\"\n        Generates a long-form name checksum for a given 8.3 short filename.\n        \"\"\"\n        sum = 0\n\n        for byte in short_filename:\n\n            # I'm sorry. This is copied directly from the bloody spec.\n            # Don't judge me. Judge them.\n            sum = (((sum & 1) << 7) | ((sum & 0xfe) >> 1)) + byte\n\n        return sum & 0xFF\n\n\n    def _is_valid_83_char(self, c):\n        \"\"\"\n        Returns true iff the given character is a valid 8-3 filename character.\n        \"\"\"\n\n        if c in \" !#$%&'()-@^_`{}~'\":\n            return True\n        if c.isupper() or c.isdigit():\n            return True\n\n        return False\n\n\n    def _is_valid_83_name(self, long_filename):\n        \"\"\"\n        Returns true iff the given filename is a valid filename.\n        \"\"\"\n        if len(long_filename) != 11:\n            return False\n\n        return all([self._is_valid_83_char(c) for c in long_filename])\n\n\n    def _short_filename_from_long(self, long_filename):\n        \"\"\"\n        Generates short-form filenames from a long-form name.\n        \"\"\"\n\n        # TODO: Generalize this to behave like Windows\n        if self._is_valid_83_name(long_filename):\n            return long_filename.encode('utf-8')\n        else:\n            # FIXME: This breaks in lots of cases; it's just Good Enough (TM)\n            # for now.\n            prefix = re.sub(r'\\W+', '', long_filename)[:6]\n            extension = long_filename[-3:]\n\n            short_name = '{}~1{}'.format(prefix, extension)\n            return short_name.encode('utf-8')\n\n\n    def _generate_long_directory_entries(self, long_filename, short_filename):\n        \"\"\"\n        Generate long-form directory entries for a long filename.\n        Should be called immediately before calling the short_form directory\n        entry functions.\n        \"\"\"\n        index = 1\n        entries = []\n\n        # Null terminate our long filename, as the filesystem expects.\n        long_filename += \"\\0\"\n\n        while long_filename:\n            entry_file      = long_filename[:13]\n            long_filename   = long_filename[13:]\n\n            # If this is the final entry, set the sixth bit of the index,\n            # indicating that this is the final index present.\n            if not long_filename:\n                index |= 0x40\n\n            # Compute the checksum for the short filename.\n            checksum = self._short_filename_checksum(short_filename)\n\n            # Encode the filename in UTF-8, padded with FFs as necessary.\n            entry_file = bytes(entry_file.encode('utf-16'))[2:]\n            entry_file = entry_file.ljust(26, b'\\xFF')\n\n            # Generate the entry itself.\n            entry  = index.to_bytes(1, byteorder='little')    # index of this entry\n            entry += entry_file[:10]                          # first five characters\n            entry += b'\\x0F'                                  # attribute indicating this is a long filename\n            entry += b'\\x00'                                  # always zeroes for VFAT LFNs\n            entry += checksum.to_bytes(1, byteorder='little') # checksum of the short name\n            entry += entry_file[10:22]                        # next six characters of the filename\n            entry += b'\\x00\\x00'                              # always zeroes\n            entry += entry_file[22:]                          # the final two characters of the filename\n\n            # Move to the next entry...\n            index += 1\n            entries.append(entry)\n\n        # Reverse the order of the entries, and convert them to a byte string.\n        return b''.join(entries[::-1])\n\n\n    def handle_root_dir_read(self, address):\n        \"\"\"\n            Returns a valid entry describing the root directory of our FAT filesystem.\n        \"\"\"\n\n        # Generate the volume label entry.\n        response = self._generate_directory_entry(b'Facedancer ', 0, 0, flags=b'\\x08')\n\n        return response\n\n\n    def _generate_fat_partition_entry(self):\n        \"\"\"\n        Returns a partition entry pointing to our synthetic FAT partition.\n        \"\"\"\n\n        response  = b'\\x00'           # Status: 0x00 = not bootable, 0x80 = bootable\n        response += b'\\x00\\x00\\x00'   # CHS address of the partition's first sector; typically ignored\n        response += b'\\x0B'           # disk type: FAT32 with CHS/LBA addressing\n        response += b'\\x00\\x00\\x00'   # CHS address of the partition's end; typically ignored\n\n        # LBA of our first sector.\n        response += self.BPB_SECTOR.to_bytes(4, byteorder='little')\n\n        # Report the size of the partition, in sectors. We'll use up all \"unallocated\"\n        # space on the drive with our FAT partition.\n        response += self.get_partition_sectors().to_bytes(4, byteorder='little')\n\n        return response\n\n    def _sectors_per_cluster(self):\n        \"\"\"\n        Returns the number of sectors in a cluster.\n        \"\"\"\n        return int(self.CLUSTER_SIZE / self.get_sector_size())\n\n\n\n    def handle_fat_read(self, address):\n        \"\"\"\n        Handles an access to the device's file allocation table.\n        \"\"\"\n\n        # TODO: Create general method for reading from the FAT based on\n        # virtual files, and methods to add those files!\n        raise NotImplementedError()\n\n\n\n\n    def handle_unhandled_sector(self, address):\n        \"\"\"\n        Handles unsupported sector reads.\n        \"\"\"\n\n        if self.verbose > 3:\n            print(\"<-- !!! unhandled sector {}, returning all zeroes\".format(address))\n\n        return bytes(bytearray(self.get_sector_size()))\n\n\n    def get_sector_count(self):\n        \"\"\"\n        Returns the total number of sectors present on the disk.\n        \"\"\"\n        return int(self.size / self.get_sector_size()) - 1\n\n\n    def get_partition_sectors(self):\n        \"\"\"\n        Get the amount of sectors available for use by our main FAT partition.\n        \"\"\"\n\n        # Return everything but the MBR and reserved space.\n        return (self.get_sector_count() - 4096)\n\n\n    def _find_sector_handler(self, address):\n        \"\"\"\n        Locates the function that should handle generation of the given sector.\n        \"\"\"\n\n        # Check each of our sector handlers to see if it is appropriate to handle\n        # the given sector...\n        for handler in self.sector_handlers:\n            sector_or_lambda = handler['sector_or_lambda']\n\n            if(callable(sector_or_lambda)):\n                matches = sector_or_lambda(address)\n            else:\n                matches = (sector_or_lambda == address)\n\n            if matches:\n                return handler\n\n        return None\n\n\n    def get_sector_data(self, address):\n        \"\"\"\n        Fetches the data at the given sector of our emulated disk.\n        \"\"\"\n\n        handler = self._find_sector_handler(address)\n\n        # If we have a handler for this sector, handle it.\n        if handler:\n            name     = handler['name']\n            function = handler['handler']\n\n            if self.verbose > 0:\n                print(\"<-- handling read of {} sector ({})\".format(name, address))\n\n            # Call the main handler.\n            response = function(address)\n\n            # If our response is smaller than our sector size, pad it out with zeroes.\n            if len(response) < self.get_sector_size():\n                needed_bytes = self.get_sector_size() - len(response)\n                response += needed_bytes * b'\\x00'\n\n            if self.verbose > 4:\n                print(\"    response: {} ({})\".format(len(response), response))\n\n            return response\n\n        # Otherwise, run the unknown command handler.\n        else:\n            return self.handle_unhandled_sector(address)\n\n\n\nclass RawDiskImage(DiskImage):\n    \"\"\"\n        Raw disk image backed by a file.\n    \"\"\"\n\n    def __init__(self, filename, block_size, verbose=0):\n        self.filename = filename\n        self.block_size = block_size\n        self.verbose = verbose\n\n        statinfo = os.stat(self.filename)\n        self.size = statinfo.st_size\n\n        self.file = open(self.filename, 'r+b')\n        self.image = mmap(self.file.fileno(), 0)\n\n    def close(self):\n        self.image.flush()\n        self.image.close()\n\n    def get_sector_count(self):\n        return int(self.size / self.block_size) - 1\n\n    def get_sector_data(self, address):\n\n        if self.verbose == 2:\n            print(\"<-- reading sector {}\".format(address))\n\n        block_start = address * self.block_size\n        block_end   = (address + 1) * self.block_size   # slices are NON-inclusive\n        data = self.image[block_start:block_end]\n\n        if self.verbose > 3:\n\n            if not any(data):\n                print(\"<-- reading sector {} [all zeroes]\".format(address))\n            else:\n                print(\"<-- reading sector {} [{}]\".format(address, data))\n\n        return data\n\n    def put_data(self, address, data):\n        if self.verbose > 1:\n            blocks = int(len(data) / self.block_size)\n            print(\"--> writing {} blocks at lba {}\".format(blocks, address))\n\n        super().put_data(address, data)\n\n\n    def put_sector_data(self, address, data):\n\n        if self.verbose == 2:\n            print(\"--> writing sector {}\".format(address))\n\n        if len(data) > self.block_size:\n            print(\"WARNING: got {} bytes of sector data; expected a max of {}\".format(len(data), self.block_size))\n\n        block_start = address * self.block_size\n        block_end   = (address + 1) * self.block_size   # slices are NON-inclusive\n\n        if self.verbose > 3:\n            if not any(data):\n                print(\"--> writing sector {} [all zeroes]\".format(address))\n            else:\n                print(\"--> writing sector {} [{}]\".format(address, data))\n\n        self.image[block_start:block_end] = data[:self.block_size]\n        self.image.flush()\n"
  },
  {
    "path": "facedancer/devices/umass/umass.py",
    "content": "# USBMassStorage.py\n#\n# Contains class definitions to implement a USB mass storage device.\n#\n\"\"\" Emulation of a USB Mass storage device. \"\"\"\n\nimport asyncio\nimport os\nimport re\nimport struct\nimport sys\nimport time\n\nfrom enum   import IntFlag\nfrom typing import Union\n\nfrom ..         import default_main\n\nfrom ...        import *\nfrom ...classes import USBDeviceClass\n\nfrom ...logging import log\n\n\nENDPOINT_OUT = 1\nENDPOINT_IN  = 3\n\n@use_inner_classes_automatically\nclass USBMassStorageDevice(USBDevice):\n    \"\"\" Class implementing an emulated USB Mass Storage device. \"\"\"\n\n    class _Configuration(USBConfiguration):\n        configuration_string : str = \"Mass Storage config\"\n\n        class _Interface(USBInterface):\n\n            # This is a Mass Storage Class device\n            class_number    : int = USBDeviceClass.MASS_STORAGE\n            subclass_number : int = 0x06 # SCSI transparent command set\n            protocol_number : int = 0x50 # bulk-only (BBB) transport\n\n            class _OutEndpoint(USBEndpoint):\n                number          : int             = ENDPOINT_OUT\n                direction       : USBDirection    = USBDirection.OUT\n                transfer_type   : USBTransferType = USBTransferType.BULK\n                max_packet_size : int             = 64\n\n            class _InEndpoint(USBEndpoint):\n                number        : int               = ENDPOINT_IN\n                direction     : USBDirection      = USBDirection.IN\n                transfer_type : USBTransferType   = USBTransferType.BULK\n                max_packet_size : int             = 64\n\n    def __init__(self, disk_image,\n                name=\"USB mass storage interface\",\n                vendor_id=0x8107, # Sandisk\n                product_id=0x5051, # SDCZ2 Cruzer Mini Flash Drive (thin)\n                device_revision=0x0003,\n                manufacturer_string=\"Facedancer\",\n                product_string=\"USB Mass Storage emulation\",\n                max_packet_size_ep0=64,\n                serial_number_string=None,\n                vendor=\"LifeScan\"):\n\n        self.disk_image = disk_image\n        self.vendor = vendor\n\n        # Pass our custom values explicitly to prevent them from being reset\n        super().__init__(\n            name=name,\n            vendor_id=vendor_id,\n            product_id=product_id,\n            device_revision=device_revision,\n            manufacturer_string=manufacturer_string,\n            product_string=product_string,\n            max_packet_size_ep0=max_packet_size_ep0,\n            serial_number_string=serial_number_string\n        )\n\n    #\n    # Device overrides\n    #\n\n    def connect(self):\n        super().connect()\n\n        # instantiate our SCSI command handler\n        self.scsi_command_handler = ScsiCommandHandler(self, self.disk_image, verbose=3, vendor=self.vendor)\n\n\n    def disconnect(self):\n        super().disconnect()\n\n        # close our disk image\n        self.disk_image.close()\n\n\n    def handle_data_received(self, endpoint, data):\n        if endpoint.number == ENDPOINT_OUT:\n            # dispatch received data to our SCSI command handler\n            self.scsi_command_handler.handle_data_received(data)\n        else:\n            log.warning(f\"Received data on unexpected endpoint: {endpoint}\")\n\n\n    #\n    # Class Request handlers.\n    #\n\n    @class_request_handler(number=254, direction=USBDirection.IN)\n    @to_this_interface\n    def handle_get_max_lun_request(self, request):\n        request.reply(b'\\x00')\n\n    @class_request_handler(number=255, direction=USBDirection.IN)\n    @to_this_interface\n    def handle_bulk_only_mass_storage_reset_request(self, request):\n        request.reply(b'')\n\n    # TODO is this an internal event handler maybe?\n    async def wait_for_host(self):\n        \"\"\" Waits until the host connects by TODO. \"\"\"\n\n        while not True:\n            await asyncio.sleep(0.1)\n\n\n\ndef bytes_as_hex(b, delim=\" \"):\n    return delim.join([\"%02x\" % x for x in b])\n\nclass ScsiCommandHandler:\n    name : str = \"SCSI Command Handler\"\n\n    STATUS_OKAY       = 0x00\n    STATUS_FAILURE    = 0x02 # TODO: Should this be 0x01?\n    STATUS_INCOMPLETE = -1   # Special case status that aborts before response.\n\n    def __init__(self, device, disk_image, verbose=0, vendor=\"GoodFET \"):\n        self.device = device\n        self.disk_image = disk_image\n        self.verbose = verbose\n        self.vendor = vendor\n\n        self.is_write_in_progress = False\n        self.write_cbw = None\n        self.write_base_lba = 0\n        self.write_length = 0\n        self.write_data = b''\n\n        self._register_scsi_commands()\n\n\n    def handle_data_received(self, data):\n        if self.is_write_in_progress:\n            cbw = self.write_cbw\n            status, response = self.continue_write(cbw, data)\n        else:\n            cbw = CommandBlockWrapper(data)\n            status, response = self.handle_scsi_command(cbw)\n\n        # If we weren't able to complete the operation, return without\n        # transmitting a response.\n        if status == self.STATUS_INCOMPLETE:\n            return\n\n        # If we have a response payload to transmit, transmit it.\n        if response:\n            if self.verbose > 2:\n                print(\"--> responding with\", len(response),\n                      \"bytes [{}], status={}\".format(bytes_as_hex(response), status))\n\n            self.device.send(ENDPOINT_IN, response, blocking=True)\n\n        # Otherwise, respond with our status.\n        csw = bytes([\n            ord('U'), ord('S'), ord('B'), ord('S'),\n            cbw.tag[0], cbw.tag[1], cbw.tag[2], cbw.tag[3],\n            0x00, 0x00, 0x00, 0x00,\n            status\n        ])\n\n        self.device.send(ENDPOINT_IN, csw, blocking=True)\n\n\n    def handle_scsi_command(self, cbw):\n        \"\"\"\n            Handles an SCSI command.\n        \"\"\"\n\n        opcode = cbw.cb[0]\n        direction = cbw.flags >> 7\n\n        # If we have a handler for this routine, handle it.\n        if opcode in self.commands:\n\n            # Extract the command's data.\n            command = self.commands[opcode]\n            name    = command['name']\n            handler = command['handler']\n            direction_name = 'IN' if direction else 'OUT'\n            direction_arrow = \"<--\" if direction else \"-->\"\n            expected_length = cbw.data_transfer_length\n\n            if self.verbose > 0:\n                print(\"{} handling {} ({}) {}:[{}]\".format(direction_arrow, name.upper(), direction_name, expected_length, bytes_as_hex(cbw.cb[1:])))\n\n            # Delegate to its handler function.\n            return handler(cbw)\n\n        # Otherwise, run the unknown command handler.\n        else:\n            return self.handle_unknown_command(cbw)\n\n\n    def handle_unknown_command(self, cbw):\n        \"\"\"\n            Handles unsupported SCSI commands.\n        \"\"\"\n        print(self.name, \"received unsupported SCSI opcode 0x%x\" % cbw.cb[0])\n\n        # Generate an empty response to the relevant command.\n        if cbw.data_transfer_length > 0:\n            response = bytes([0] * cbw.data_transfer_length)\n        else:\n            response = None\n\n        # Return failure.\n        return self.STATUS_FAILURE, response\n\n\n    def handle_ignored_event(self, cbw):\n        \"\"\"\n            Handles SCSI events that we can safely ignore.\n        \"\"\"\n\n        # Always return success, and no response.\n        return self.STATUS_OKAY, None\n\n\n    def handle_sense(self, cbw):\n        \"\"\"\n            Handles SCSI sense requests.\n        \"\"\"\n        response = b'\\x70\\x00\\xFF\\x00\\x00\\x00\\x00\\x0A\\x00\\x00\\x00\\x00\\xFF\\xFF\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00'\n        return self.STATUS_OKAY, response\n\n\n    def handle_inquiry(self, cbw):\n        opcode, flags, page_code, allocation_length, control = struct.unpack(\">BBBHB\", cbw.cb[0:6])\n\n        # Print out the details of our inquiry.\n        if self.verbose > 1:\n            print(\"-- INQUIRY ({}) flags: {} page_code: {} allocation_length: {} control: {}\". \\\n                  format(opcode, flags, page_code, allocation_length, control))\n\n        response = bytes([\n            0x00,       # 0x00 = device present, and provides direct access to blocks\n            0x00,       # 0x00 = media not removable, 0x80 = media removable\n            0x05,       # 0 = no standards compliance, 3 = SPC compliant, 4 = SPC-2 compliant, 5 = SCSI compliant :)\n            0x02,       # 0x02 = data responses follow the spec\n            0x14,       # Additional length.\n            0x00, 0x00, 0x00\n        ])\n\n        response += self.vendor.encode('utf-8')  # vendor\n        response += b'GoodFET '         # product id\n        response += b'        '         # product revision\n        response += b'0.01'\n\n        # pad up to data_transfer_length bytes\n        diff = cbw.data_transfer_length - len(response)\n        response += bytes([0] * diff)\n\n        return self.STATUS_OKAY, response\n\n\n    def handle_mode_sense_6(self, cbw):\n        page = cbw.cb[2] & 0x3f\n\n        response = b'\\x03\\x00\\x00\\x1c'\n        if page != 0x3f:\n            print(self.name, \"unknown page, returning empty page\")\n            response = b'\\x03\\x00\\x00\\x00'\n\n        return self.STATUS_OKAY, response\n\n\n    def handle_mode_sense_10(self, cbw):\n        page = cbw.cb[2] & 0x3f\n\n        response = b'\\x07\\x00\\x00\\x00\\x00\\x00\\x00\\x1c'\n        if page != 0x3f:\n            print(self.name, \"unknown page, returning empty page\")\n            response = b'\\x07\\x00\\x00\\x00\\x00\\x00\\x00\\x00'\n\n        return self.STATUS_OKAY, response\n\n\n    def handle_service_action_in(self, cbw):\n        opcode = cbw.cb[0]\n\n        if opcode == 0x9e:\n            return self.handle_get_read_capacity_16(cbw)\n        else:\n            # Always return success, and no response.\n            return self.STATUS_OKAY, None\n\n\n    def handle_get_format_capacity(self, cbw):\n        response = bytes([\n            0x00, 0x00, 0x00, 0x08,     # capacity list length\n            0x00, 0x00, 0x10, 0x00,     # number of sectors (0x1000 = 10MB)\n            0x10, 0x00,                 # reserved/descriptor code\n            0x02, 0x00,                 # 512-byte sectors\n        ])\n        return self.STATUS_OKAY, response\n\n\n    def handle_get_read_capacity(self, cbw):\n        lastlba = self.disk_image.get_sector_count()\n        if lastlba > 0xffffffff:\n            lastlba = 0xffffffff\n\n        response = bytes([\n            (lastlba >> 24) & 0xff,\n            (lastlba >> 16) & 0xff,\n            (lastlba >>  8) & 0xff,\n            (lastlba      ) & 0xff,\n            0x00, 0x00, 0x02, 0x00,     # 512-byte blocks\n        ])\n        return self.STATUS_OKAY, response\n\n\n    def handle_get_read_capacity_16(self, cbw):\n        lastlba = self.disk_image.get_sector_count()\n\n        response = bytes([\n            (lastlba >> 56) & 0xff,\n            (lastlba >> 48) & 0xff,\n            (lastlba >> 40) & 0xff,\n            (lastlba >> 32) & 0xff,\n            (lastlba >> 24) & 0xff,\n            (lastlba >> 16) & 0xff,\n            (lastlba >>  8) & 0xff,\n            (lastlba      ) & 0xff,\n            0x00, 0x00, 0x02, 0x00,     # 512-byte blocks\n        ])\n        return self.STATUS_OKAY, response\n\n\n    def handle_read(self, cbw):\n        base_lba = cbw.cb[2] << 24 \\\n                 | cbw.cb[3] << 16 \\\n                 | cbw.cb[4] << 8 \\\n                 | cbw.cb[5]\n\n        num_blocks = cbw.cb[7] << 8 \\\n                   | cbw.cb[8]\n\n        if self.verbose > 0:\n            print(\"<-- performing READ (10), lba\", base_lba, \"+\", num_blocks, \"block(s)\")\n\n        # Note that here we send the data directly rather than putting\n        # something in 'response' and letting the end of the switch send\n        for block_num in range(num_blocks):\n            data = self.disk_image.get_sector_data(base_lba + block_num)\n            self.device.send(ENDPOINT_IN, data, blocking=True)\n\n        if self.verbose > 3:\n            print(\"--> responded with {} bytes\".format(cbw.data_transfer_length))\n\n        return self.STATUS_OKAY, None\n\n\n    def handle_read_16(self, cbw):\n        base_lba = cbw.cb[2] << 56 \\\n                 | cbw.cb[3] << 48 \\\n                 | cbw.cb[4] << 40 \\\n                 | cbw.cb[5] << 32 \\\n                 | cbw.cb[6] << 24 \\\n                 | cbw.cb[7] << 16 \\\n                 | cbw.cb[8] << 8 \\\n                 | cbw.cb[9]\n\n        num_blocks = cbw.cb[10] << 24 \\\n                   | cbw.cb[11] << 16 \\\n                   | cbw.cb[12] << 8  \\\n                   | cbw.cb[13]\n\n        if self.verbose > 0:\n            print(\"<-- performing READ (16), lba\", base_lba, \"+\", num_blocks, \"block(s)\")\n\n        # Note that here we send the data directly rather than putting\n        # something in 'response' and letting the end of the switch send\n        for block_num in range(num_blocks):\n            data = self.disk_image.get_sector_data(base_lba + block_num)\n            self.ep_to_host.send_packet(data, blocking=True)\n\n        if self.verbose > 3:\n            print(\"--> responded with {} bytes\".format(cbw.data_transfer_length))\n\n        return self.STATUS_OKAY, None\n\n\n    def handle_write(self, cbw):\n        base_lba = cbw.cb[2] << 24 \\\n                 | cbw.cb[3] << 16 \\\n                 | cbw.cb[4] <<  8 \\\n                 | cbw.cb[5]\n\n        num_blocks = cbw.cb[7] << 8 \\\n                   | cbw.cb[8]\n\n        if self.verbose > 0:\n            print(\"--> performing WRITE (10), lba\", base_lba, \"+\", num_blocks, \"block(s)\")\n\n        # save for later\n        self.write_cbw = cbw\n        self.write_base_lba = base_lba\n        self.write_length = num_blocks * self.disk_image.get_sector_size()\n        self.is_write_in_progress = True\n\n        # because we need to snarf up the data from wire before we reply\n        # with the CSW\n        return self.STATUS_INCOMPLETE, None\n\n\n    def handle_write_16(self, cbw):\n        base_lba = cbw.cb[2] << 56 \\\n                 | cbw.cb[3] << 48 \\\n                 | cbw.cb[4] << 40 \\\n                 | cbw.cb[5] << 32 \\\n                 | cbw.cb[6] << 24 \\\n                 | cbw.cb[7] << 16 \\\n                 | cbw.cb[8] << 8 \\\n                 | cbw.cb[9]\n\n        num_blocks = cbw.cb[10] << 24 \\\n                   | cbw.cb[11] << 16 \\\n                   | cbw.cb[12] << 8  \\\n                   | cbw.cb[13]\n\n        if self.verbose > 0:\n            print(\"--> performing WRITE (16), lba\", base_lba, \"+\", num_blocks, \"block(s)\")\n\n        # save for later\n        self.write_cbw = cbw\n        self.write_base_lba = base_lba\n        self.write_length = num_blocks * self.disk_image.get_sector_size()\n        self.is_write_in_progress = True\n\n        # because we need to snarf up the data from wire before we reply\n        # with the CSW\n        return self.STATUS_INCOMPLETE, None\n\n\n    def continue_write(self, cbw, data):\n        if self.verbose > 3:\n            print(\"--> continue write with {} more bytes of data\".format(len(data)))\n\n        self.write_data += data\n\n        if len(self.write_data) < self.write_length:\n            # more yet to read, don't send the CSW\n            return self.STATUS_INCOMPLETE, None\n\n        self.disk_image.put_data(self.write_base_lba, self.write_data)\n\n        self.is_write_in_progress = False\n        self.write_data = b''\n\n        return self.STATUS_OKAY, None\n\n\n    def _register_scsi_commands(self):\n        self.commands = {}\n\n        self._register_scsi_command(0x00, \"Test Unit Ready\", self.handle_ignored_event)\n        self._register_scsi_command(0x03, \"Request Sense\", self.handle_sense)\n        self._register_scsi_command(0x12, \"Inquiry\", self.handle_inquiry)\n        self._register_scsi_command(0x1a, \"Mode Sense (6)\", self.handle_mode_sense_6)\n        self._register_scsi_command(0x5a, \"Mode Sense (10)\", self.handle_mode_sense_10)\n        self._register_scsi_command(0x1e, \"Prevent/Allow Removal\", self.handle_ignored_event)\n        self._register_scsi_command(0x23, \"Get Format Capacity\", self.handle_get_format_capacity)\n        self._register_scsi_command(0x25, \"Get Read Capacity\", self.handle_get_read_capacity)\n        self._register_scsi_command(0x28, \"Read\", self.handle_read)\n        self._register_scsi_command(0x88, \"Read (16)\", self.handle_read_16)\n        self._register_scsi_command(0x2a, \"Write (10)\", self.handle_write)\n        self._register_scsi_command(0x8a, \"Write (16)\", self.handle_write_16)\n        self._register_scsi_command(0x36, \"Synchronize Cache\", self.handle_ignored_event)\n        self._register_scsi_command(0x9e, \"Service Action In\", self.handle_service_action_in)\n\n\n    def _register_scsi_command(self, number, name, handler=None):\n        if handler is None:\n            handler = self.handle_unknown_command\n\n        descriptor = {\n            \"number\": number,\n            \"name\": name,\n            \"handler\": handler,\n        }\n        self.commands[number] = descriptor\n\n\nclass CommandBlockWrapper:\n    def __init__(self, bytestring):\n        self.signature              = bytestring[0:4]\n        self.tag                    = bytestring[4:8]\n        self.data_transfer_length   = bytestring[8] \\\n                                    | bytestring[9] << 8 \\\n                                    | bytestring[10] << 16 \\\n                                    | bytestring[11] << 24\n        self.flags                  = int(bytestring[12])\n        self.lun                    = int(bytestring[13] & 0x0f)\n        self.cb_length              = int(bytestring[14] & 0x1f)\n        self.cb                     = bytestring[15:]\n\n    def __str__(self):\n        s  = \"sig: \" + bytes_as_hex(self.signature) + \"\\n\"\n        s += \"tag: \" + bytes_as_hex(self.tag) + \"\\n\"\n        s += \"data transfer len: \" + str(self.data_transfer_length) + \"\\n\"\n        s += \"flags: \" + str(self.flags) + \"\\n\"\n        s += \"lun: \" + str(self.lun) + \"\\n\"\n        s += \"command block len: \" + str(self.cb_length) + \"\\n\"\n        s += \"command block: \" + bytes_as_hex(self.cb) + \"\\n\"\n\n        return s\n\n\nif __name__ == \"__main__\":\n    default_main(USBMassStorageDevice)\n"
  },
  {
    "path": "facedancer/endpoint.py",
    "content": "#\n# This file is part of Facedancer\n#\n\"\"\" Functionality for describing USB endpoints. \"\"\"\n\n# Support annotations on Python < 3.9\nfrom __future__  import annotations\n\nimport struct\nimport textwrap\n\nfrom typing      import Iterable, List, Dict\nfrom dataclasses import field\nfrom collections import defaultdict\n\nfrom .magic      import AutoInstantiable, instantiate_subordinates\nfrom .descriptor import USBDescribable, USBDescriptor\nfrom .request    import USBRequestHandler, get_request_handler_methods\nfrom .request    import to_this_endpoint, standard_request_handler\nfrom .types      import USBDirection, USBTransferType, USBSynchronizationType\nfrom .types      import USBUsageType, USBStandardRequests\n\nfrom .logging    import log\n\n\nclass USBEndpoint(USBDescribable, AutoInstantiable, USBRequestHandler):\n    \"\"\" Class representing a USBEndpoint object.\n\n    Field:\n        number:\n            The endpoint number (without the direction bit) for this endpoint.\n        direction:\n            A USBDirection constant indicating this endpoint's direction.\n        transfer_type:\n            A USBTransferType constant indicating the type of communications used.\n        max_packet_size:\n            The maximum packet size for this endpoint.\n        interval:\n            The polling interval, for an INTERRUPT endpoint.\n    \"\"\"\n    DESCRIPTOR_TYPE_NUMBER      = 0x05\n\n    # Core identifiers.\n\n    number               : int\n    direction            : USBDirection\n\n    # Endpoint attributes.\n    transfer_type        : USBTransferType        = USBTransferType.BULK\n    synchronization_type : USBSynchronizationType = USBSynchronizationType.NONE\n    usage_type           : USBUsageType           = USBUsageType.DATA\n\n    max_packet_size      : int = 64\n    interval             : int = 0\n\n    # Extra bytes that extend the basic endpoint descriptor.\n    extra_bytes          : bytes = b''\n\n    # Descriptors that will be included in a GET_CONFIGURATION response.\n    attached_descriptors : List[USBDescriptor] = field(default_factory=list)\n\n    # Descriptors that can be requested with the GET_DESCRIPTOR request.\n    requestable_descriptors : Dict[tuple[int, int], USBDescriptor] = field(default_factory=dict)\n\n    parent               : USBDescribable = None\n\n\n    @classmethod\n    def from_binary_descriptor(cls, data, strings={}):\n        \"\"\"\n        Creates an endpoint object from a description of that endpoint.\n        \"\"\"\n\n        # Parse the core descriptor into its components...\n        address, attributes, max_packet_size, interval = struct.unpack_from(\"xxBBHB\", data)\n\n        # ... and break down the packed fields.\n        number        = address & 0x7F\n        direction     = address >> 7\n        transfer_type = attributes & 0b11\n        sync_type     = attributes >> 2 & 0b1111\n        usage_type    = attributes >> 4 & 0b11\n\n        return cls(\n            number=number,\n            direction=USBDirection(direction),\n            transfer_type=USBTransferType(transfer_type),\n            synchronization_type=USBSynchronizationType(sync_type),\n            usage_type=USBUsageType(usage_type),\n            max_packet_size=max_packet_size,\n            interval=interval,\n            extra_bytes=data[7:]\n        )\n\n\n    def __post_init__(self):\n\n        # Capture any descriptors declared directly on the class.\n        for descriptor in instantiate_subordinates(self, USBDescriptor):\n            self.add_descriptor(descriptor)\n\n        # Grab our request handlers.\n        self._request_handler_methods = get_request_handler_methods(self)\n\n    #\n    # User interface.\n    #\n\n    @staticmethod\n    def address_for_number(endpoint_number: int, direction: USBDirection) -> int:\n        \"\"\" Computes the endpoint address for a given number + direction. \"\"\"\n        direction_mask = 0x80 if direction == USBDirection.IN else 0x00\n        return endpoint_number | direction_mask\n\n\n    def get_device(self):\n        \"\"\" Returns the device associated with the given descriptor. \"\"\"\n        return self.parent.get_device()\n\n\n    def send(self, data: bytes, *, blocking: bool = False):\n        \"\"\" Sends data on this endpoint. Valid only for IN endpoints.\n\n        Args:\n            data     : The data to be sent.\n            blocking : True if we should block until the backend reports\n                        the transmission to be complete.\n        \"\"\"\n        self.get_device()._send_in_packets(self.number, data,\n            packet_size=self.max_packet_size, blocking=blocking)\n\n\n    #\n    # Event handlers.\n    #\n\n    def handle_data_received(self, data: bytes):\n        \"\"\" Handler for receipt of non-control request data.\n\n        Args:\n            data   : The raw bytes received.\n        \"\"\"\n        log.info(f\"EP{self.number} received {len(data)} bytes of data; \"\n                \"but has no handler.\")\n\n\n    def handle_data_requested(self):\n        \"\"\" Handler called when the host requests data on this endpoint.\"\"\"\n\n\n    def handle_buffer_empty(self):\n        \"\"\" Handler called when this endpoint first has an empty buffer. \"\"\"\n\n\n    @standard_request_handler(number=USBStandardRequests.CLEAR_FEATURE)\n    @to_this_endpoint\n    def handle_clear_feature_request(self, request):\n        log.debug(f\"received CLEAR_FEATURE request for endpoint {self.number} \"\n            f\"with value {request.value}\")\n        request.acknowledge()\n\n\n    #\n    # Properties.\n    #\n\n    @property\n    def address(self):\n        \"\"\" Fetches the address for the given endpoint. \"\"\"\n        return self.address_for_number(self.number, self.direction)\n\n\n    def get_address(self):\n        \"\"\" Method alias for the address property. For backend support. \"\"\"\n        return self.address\n\n\n    @property\n    def attributes(self):\n        \"\"\" Fetches the attributes for the given endpoint, as a single byte. \"\"\"\n        return (self.transfer_type & 0x03)               | \\\n               ((self.synchronization_type & 0x03) << 2) | \\\n               ((self.usage_type & 0x03) << 4)\n\n\n    def add_descriptor(self, descriptor: USBDescriptor):\n        \"\"\" Adds the provided descriptor to the endpoint. \"\"\"\n        identifier = descriptor.get_identifier()\n        desc_name = type(descriptor).__name__\n\n        if descriptor.include_in_config:\n            self.attached_descriptors.append(descriptor)\n            descriptor.parent = self\n\n        elif descriptor.number is None:\n            raise Exception(\n                f\"Descriptor of type {desc_name} cannot be added to this \"\n                f\"endpoint because it is not to be included in the \"\n                f\"configuration descriptor, yet does not have a number \"\n                f\"to request it separately with\")\n\n        elif identifier in self.requestable_descriptors:\n            other = self.requestable_descriptors[identifier]\n            other_name = type(other).__name__\n            other_type = f\"0x{other.type_number:02X}\"\n            raise Exception(\n                f\"Descriptor of type {desc_name} cannot be added to this \"\n                f\"endpoint because there is already a descriptor of type \"\n                f\"{other_name} with the same type code {other_type} and \"\n                f\"number {other.number}\")\n\n        else:\n            self.requestable_descriptors[identifier] = descriptor\n            descriptor.parent = self\n\n\n    def get_descriptor(self) -> bytes:\n        \"\"\" Get a descriptor string for this endpoint. \"\"\"\n        # FIXME: use construct\n\n        d = bytearray([\n                # length of descriptor in bytes\n                7 + len(self.extra_bytes),\n                # descriptor type 5 == endpoint\n                5,\n                self.address,\n                self.attributes,\n                self.max_packet_size & 0xff,\n                (self.max_packet_size >> 8) & 0xff,\n                self.interval\n        ])\n\n        return d + self.extra_bytes\n\n\n    #\n    # Automatic instantiation helpers.\n    #\n\n    def get_identifier(self) -> int:\n        return self.address\n\n    def matches_identifier(self, other:int) -> bool:\n        # Use only the MSB and the lower nibble; per the USB specification.\n        masked_other = other & 0b10001111\n        return self.get_identifier() == masked_other\n\n\n    #\n    # Request handling.\n    #\n\n    def _request_handlers(self) -> Iterable[callable]:\n        return self._request_handler_methods\n\n\n    #\n    # Pretty-printing.\n    #\n    def __str__(self):\n        direction     = USBDirection(self.direction).name\n        transfer_type = USBTransferType(self.transfer_type).name\n        is_interrupt  = (self.transfer_type == USBTransferType.INTERRUPT)\n        additional    = f\" every {self.interval}ms\" if is_interrupt else \"\"\n\n        return f\"endpoint {self.number:02x}/{direction}: {transfer_type} transfers{additional}\"\n\n\n    def generate_code(self, name=None, indent=0):\n\n        if name is None:\n            name = f\"Endpoint_{self.number}_{self.direction.name}\"\n\n        direction = f\"USBDirection.{self.direction.name}\"\n        transfer_type = f\"USBTransferType.{self.transfer_type.name}\"\n        sync_type = f\"USBSynchronizationType.{self.synchronization_type.name}\"\n        usage_type = f\"USBUsageType.{self.usage_type.name}\"\n\n        values = str.join(\", \", map(lambda x: f\"0x{x:02x}\", self.extra_bytes))\n\n        code = f\"\"\"\nclass {name}(USBEndpoint):\n    number               = {self.number}\n    direction            = {direction}\n    transfer_type        = {transfer_type}\n    synchronization_type = {sync_type}\n    usage_type           = {usage_type}\n    max_packet_size      = {self.max_packet_size}\n    interval             = {self.interval}\n    extra_bytes          = bytes([{values}])\n\"\"\"\n\n        # Use alphabetic suffixes to distinguish between multiple attached\n        # descriptors with the same type number.\n        suffixes = defaultdict(lambda: 'A')\n\n        for descriptor in self.attached_descriptors:\n            type_number = descriptor.type_number\n            suffix = suffixes[type_number]\n            suffixes[type_number] = chr(ord(suffix) + 1)\n            name = f\"Descriptor_0x{type_number:02X}_{suffix}\"\n            code += descriptor.generate_code(name=name, indent=4)\n\n        for descriptor_id in sorted(self.requestable_descriptors):\n            descriptor = self.requestable_descriptors[descriptor_id]\n            code += descriptor.generate_code(indent=4)\n\n        return textwrap.indent(code, indent * ' ')\n"
  },
  {
    "path": "facedancer/errors.py",
    "content": "#\n# This file is part of Facedancer.\n#\n\nclass DeviceNotFoundError(IOError):\n    \"\"\" Error indicating a device was not found. \"\"\"\n    pass\n\nclass EndEmulation(Exception):\n    \"\"\" When an EndEmulation exception is thrown the emulation will shutdown and exit. \"\"\"\n"
  },
  {
    "path": "facedancer/filters/__init__.py",
    "content": "from .base     import  USBProxyFilter\nfrom .logging  import  USBProxyPrettyPrintFilter\nfrom .standard import  USBProxySetupFilters\n"
  },
  {
    "path": "facedancer/filters/base.py",
    "content": "#\n# This file is part of Facedancer.\n#\n\n\nclass USBProxyFilter:\n    \"\"\"\n    Base class for filters that modify USB data.\n    \"\"\"\n\n    def filter_control_in_setup(self, request, stalled):\n        \"\"\"\n        Filters a SETUP stage for an IN control request. This allows us to modify\n        the SETUP stage before it's proxied to the real device.\n\n        Args:\n            request : The request to be issued.\n            stalled : True iff the packet has been stalled by a previous filter.\n\n        Returns:\n            Modified versions of the arguments. If stalled is set to true,\n            the packet will be immediately stalled an not proxied. If stalled is\n            false, but request is returned as None, the packet will be NAK'd instead\n            of proxied.\n        \"\"\"\n        return request, stalled\n\n\n    def filter_control_in(self, request, data, stalled):\n        \"\"\"\n        Filters the data response from the proxied device during an IN control\n        request. This allows us to modify the data returned from the proxied\n        device during a setup stage.\n\n        Args:\n            request : The request that was issued to the target host.\n            data    : The data being proxied during the data stage.\n            stalled : True if the proxied device (or a previous filter) stalled the request.\n\n        Returns:\n            Modified versions of the arguments. Note that modifying request\n            will _only_ modify the request as seen by future filters, as the\n            SETUP stage has already passed and the request has already been\n            sent to the device.\n        \"\"\"\n        return request, data, stalled\n\n\n    def filter_control_out(self, request, data):\n        \"\"\"\n        Filters handling of an OUT control request, which contains both a\n        request and (optional) data stage.\n\n        Args:\n            request : The request issued by the target host.\n            data :    The data sent by the target host with the request.\n\n        Returns:\n            Modified versions of the arguments. Returning a request of\n            None will absorb the packet silently and not proxy it to the\n            device.\n        \"\"\"\n        return request, data\n\n\n    def handle_out_request_stall(self, request, data, stalled):\n        \"\"\"\n        Handles an OUT request that was stalled by the proxied device.\n\n        Args:\n            request : The request header for the request that stalled.\n            data    : The data stage for the request that stalled, if appropriate.\n            stalled : True iff the request is still considered stalled. This can\n                      be overridden by previous filters, so it's possible for this to be false.\n        \"\"\"\n        return request, data, stalled\n\n\n    def filter_in_token(self, ep_num):\n        \"\"\"\n        Filters an IN token before it's passed to the proxied device.\n        This allows modification of e.g. the endpoint or absorption of\n        the IN token before it's issued to the real device.\n\n        Args:\n            ep_num : The endpoint number on which the IN token is to be proxied.\n\n        Returns:\n            A modified version of the arguments. If ep_num is set to None,\n            the token will be absorbed and not issued to the target host.\n        \"\"\"\n        return ep_num\n\n\n    def filter_in(self, ep_num, data):\n        \"\"\"\n        Filters the response to an IN token (the data packet received in response\n        to the host issuing an IN token).\n\n        Args:\n            ep_num : The endpoint number associated with the data packet.\n            data   : The data packet received from the proxied device.\n\n        Returns:\n            A modified version of the arguments. If data is set to none,\n            the packet will be absorbed, and a NAK will be issued instead of\n            responding to the IN request with data.\n        \"\"\"\n        return ep_num, data\n\n\n    def filter_out(self, ep_num, data):\n        \"\"\"\n        Filters a packet sent from the host via an OUT token.\n\n        Args:\n            ep_num: The endpoint number associated with the data packet.\n            data: The data packet received from host.\n\n        Returns:\n            A modified version of the arguments. If data is set to none,\n            the packet will be absorbed,\n        \"\"\"\n        return ep_num, data\n\n\n    def handle_out_stall(self, ep_num, data, stalled):\n        \"\"\"\n        Handles an OUT transfer that was stalled by the victim.\n\n        Args:\n            ep_num  : The endpoint number for the data that stalled.\n            data    : The data for the transfer that stalled, if appropriate.\n            stalled : True iff the transfer is still considered stalled. This can\n                      be overridden by previous filters, so it's possible for this to\n                      be false.\n        \"\"\"\n        return ep_num, data, stalled\n"
  },
  {
    "path": "facedancer/filters/hid.py",
    "content": "#\n# USBProxy HID logging\n#\n\nfrom warnings import filterwarnings\nimport hid_parser\nfrom hid_parser import HIDComplianceWarning\nfrom enum import IntEnum\n\nfrom facedancer.descriptor import USBDescriptorTypeNumber\nfrom facedancer.device import USBBaseDevice\nfrom facedancer.filters.base import USBProxyFilter\nfrom facedancer.request import USBControlRequest\nfrom facedancer.types import (\n    USBDirection,\n    USBRequestRecipient,\n    USBRequestType,\n    USBStandardRequests,\n)\n\nfrom ..logging import log\n\nGET_REPORT = 0x01\nSET_REPORT = 0x09\n\nSET_IDLE = 0x0A\n\nfilterwarnings(\"ignore\", r\"Usage.* has no compatible usage types\", HIDComplianceWarning)\nfilterwarnings(\"ignore\", r\"Expecting 60 usages but got 1\", HIDComplianceWarning)\n\n\nclass HIDReportType(IntEnum):\n    HID_TYPE_INPUT = 1\n    HID_TYPE_OUTPUT = 2\n    HID_TYPE_FEATURE = 3\n\n\nclass USBProxyHIDFilter(USBProxyFilter):\n    \"\"\"\n    Print HID packets\n\n    If verbose > 2 - print all fields\n    \"\"\"\n\n    def __init__(self, device: USBBaseDevice, verbose=1):\n        self.device = device\n        self.verbose = verbose\n        self.rdescs = {}\n\n    def filter_control_in(self, req: USBControlRequest | None, data, stalled):\n        if req:\n            if req.type == USBRequestType.STANDARD and USBRequestRecipient.INTERFACE:\n                if req.number == USBStandardRequests.GET_DESCRIPTOR:\n                    self._log_desc(req, data)\n\n            if req.type == USBRequestType.CLASS and USBRequestRecipient.INTERFACE:\n                self._log_in(req, data)\n\n        return req, data, stalled\n\n    def _log_desc(self, req, data):\n        kind = req.value_high\n        iface = req.index\n        # index = req.value_low\n\n        if kind == USBDescriptorTypeNumber.HID:\n            log.info(f\"GET_DESC HID_DEVICE I{iface} {dump(data)}\")\n\n        if kind == USBDescriptorTypeNumber.REPORT:\n            log.info(f\"GET_DESC HID_REPORT I{iface} {dump(data)}\")\n            try:\n                self.rdescs[iface] = rdesc = hid_parser.ReportDescriptor(data)\n            except NotImplementedError as e:\n                log.warning(f\"Failed to parse report: {e}\")\n                self.rdescs[iface] = None\n                return\n\n            if self.verbose > 2:\n                for rid in rdesc.output_report_ids:\n                    log.info(f\"  output {rid} {rdesc.get_output_report_size(rid)}\")\n\n                for rid in rdesc.input_report_ids:\n                    log.info(f\"  input {rid} {rdesc.get_input_report_size(rid)}\")\n\n                for rid in rdesc.feature_report_ids:\n                    log.info(f\"  feature {rid} {rdesc.get_feature_report_size(rid)}\")\n\n    def _log_in(self, req, data):\n        iface = req.index\n\n        if req.number == GET_REPORT:\n            kind = HIDReportType(req.value_high)\n            log.info(f\"GET_REPORT {kind} RID {req.value_low} I{iface} {dump(data)}\")\n\n            self._report(iface, \"parse_input_report\", data)\n\n    def filter_control_out(self, req, data):\n        if req and req.type == USBRequestType.CLASS and USBRequestRecipient.INTERFACE:\n            self._log_out(req, data)\n\n        return req, data\n\n    def _log_out(self, req, data):\n        iface = req.index\n\n        if req.number == SET_REPORT:\n            kind = HIDReportType(req.value_high)\n            log.info(f\"SET_REPORT {kind} RID {req.value_low} I{iface} {dump(data)}\")\n            self._report(iface, \"parse_output_report\", data)\n\n        if req.number == SET_IDLE:\n            dur = req.value_high * 4\n            log.info(f\"SET_IDLE {dur}ms RID {req.value_low} I{iface} {dump(data)}\")\n\n    def filter_in(self, ep_num, data):\n        if interface := self._find_interface(ep_num):\n            self._log_ep_in(interface.number, ep_num, data)\n\n        return ep_num, data\n\n    def _find_interface(self, ep_num):\n        \"\"\"Return the interface that has ep_num.\"\"\"\n        if not self.device.configuration:\n            return\n\n        for interface in self.device.configuration.active_interfaces.values():\n            if interface.has_endpoint(ep_num, USBDirection.IN):\n                return interface\n\n    def _log_ep_in(self, iface, num, data):\n        log.info(f\"EP{num} I{iface} RID {data[0]} {dump(data[1:])}\")\n        self._report(iface, \"parse_input_report\", data)\n\n    def _report(self, iface: int, kind: str, data: bytes):\n        if self.verbose < 3:\n            return\n\n        rdesc = self.rdescs.get(iface)\n        if len(data) > 1 and rdesc:\n            try:\n                # TODO - handle feature\n                for usage, value in getattr(rdesc, kind)(data).items():\n                    log.info(f\"  {usage} {value}\")\n            except Exception as e:\n                log.warning(f\"  {e}\")\n\n\ndef dump(raw: bytes):\n    return raw.hex(\" \", -2)\n"
  },
  {
    "path": "facedancer/filters/logging.py",
    "content": "#\n# USBProxy logging filters\n#\n\nimport datetime\n\nfrom ..logging import log\n\nfrom .         import USBProxyFilter\n\n\nclass USBProxyPrettyPrintFilter(USBProxyFilter):\n    \"\"\"\n    Filter that pretty prints USB transactions according to log levels.\n    \"\"\"\n\n    def __init__(self, verbose=4, decoration=''):\n        \"\"\"\n        Sets up a new USBProxy pretty printing filter.\n        \"\"\"\n        self.verbose = verbose\n        self.decoration = decoration\n\n\n\n    def filter_control_in(self, req, data, stalled):\n        \"\"\"\n        Log IN control requests without modification.\n        \"\"\"\n\n        if self.verbose > 3 and req is None:\n            log.info(\"{} {}< --filtered out-- \".format(self.timestamp(), self.decoration))\n            return req, data, stalled\n\n        if self.verbose > 3:\n            log.info(\"{} {}{}\".format(self.timestamp(), self.decoration, repr(req)))\n\n        if self.verbose > 3 and stalled:\n            log.info(\"{} {}< --STALLED-- \".format(self.timestamp(), self.decoration))\n\n        if self.verbose > 4 and data:\n            is_string = (req.request == 6) and (req.value >> 8 == 3)\n            self._pretty_print_data(data, '<', self.decoration, is_string)\n\n        return req, data, stalled\n\n\n    def filter_control_out(self, req, data):\n        \"\"\"\n        Log OUT control requests without modification.\n        \"\"\"\n\n        # TODO: just call control_in, it's the same:\n\n        if self.verbose > 3 and req is None:\n            log.info(\"{} {}> --filtered out-- \".format(self.timestamp(), self.decoration))\n            return req, data\n\n        if self.verbose > 3:\n            log.info(\"{} {}{}\".format(self.timestamp(), self.decoration, repr(req)))\n\n        if self.verbose > 4 and data:\n            self._pretty_print_data(data, '>', self.decoration)\n\n        return req, data\n\n\n    def handle_out_request_stall(self, req, data, stalled):\n        \"\"\"\n        Handles cases where OUT requests are stalled (and thus we don't get data).\n        \"\"\"\n        if self.verbose > 3 and req is None:\n            if stalled:\n                log.info(\"{} {}> --STALLED-- \".format(self.timestamp(), self.decoration))\n            else:\n                log.info(\"{} {}> --STALLED, but unstalled by filter-- \".format(self.timestamp(), self.decoration))\n\n        return req, data, stalled\n\n\n    def filter_in(self, ep_num, data):\n        \"\"\"\n        Log IN transfers without modification.\n        \"\"\"\n\n        if self.verbose > 4 and data:\n            self._pretty_print_data(data, '<', self.decoration, ep_marker=ep_num)\n\n        return ep_num, data\n\n    def filter_out(self, ep_num, data):\n        \"\"\"\n        Log OUT transfers without modification.\n        \"\"\"\n\n        if self.verbose > 4 and data:\n            self._pretty_print_data(data, '>', self.decoration, ep_marker=ep_num)\n\n        return ep_num, data\n\n\n    def timestamp(self):\n        \"\"\" Generate a quick timestamp for printing. \"\"\"\n        return datetime.datetime.now().strftime(\"[%H:%M:%S]\")\n\n    def _magic_decode(self, data):\n        \"\"\" Simple decode function that attempts to find a nice string representation for the console.\"\"\"\n        try:\n            return bytes(data).decode('utf-16le')\n        except:\n            return bytes(data)\n\n\n    def _pretty_print_data(self, data, direction_marker, decoration='', is_string=False, ep_marker=''):\n        data = self._magic_decode(data) if is_string else bytes(data)\n        log.info(\"{} {}{}{}: {}\".format(self.timestamp(), ep_marker, decoration, direction_marker, data))\n"
  },
  {
    "path": "facedancer/filters/standard.py",
    "content": "#\n# This file is part of Facedancer.\n#\n\"\"\" Standard filters for USBProxy that should (almost) always be used. \"\"\"\n\nfrom ..            import *\nfrom ..descriptor  import USBDescribable\nfrom ..errors      import *\nfrom ..logging     import log\n\nfrom .             import USBProxyFilter\n\n\nclass USBProxySetupFilters(USBProxyFilter):\n    SET_ADDRESS_REQUEST = 5\n    SET_CONFIGURATION_REQUEST = 9\n    SET_INTERFACE_REQUEST = 11\n    GET_DESCRIPTOR_REQUEST = 6\n    RECIPIENT_DEVICE = 0\n    RECIPIENT_INTERFACE = 1\n\n    DESCRIPTOR_DEVICE        = 0x01\n    DESCRIPTOR_CONFIGURATION = 0x02\n\n    MAX_PACKET_SIZE_EP0 = 64\n\n    def __init__(self, device, verbose=0):\n        self.device = device\n        self.configurations = {}\n        self.verbose = verbose\n\n    def filter_control_in(self, req, data, stalled):\n\n        if stalled:\n            return req, data, stalled\n\n        # If this is a read of a valid configuration descriptor (and subordinate\n        # descriptors, parse them and store the results for later).\n        if req.request == self.GET_DESCRIPTOR_REQUEST:\n\n            # Get the descriptor type and index.\n            descriptor_type  = req.value >> 8\n            descriptor_index = req.value & 0xFF\n\n            # If this is a configuration descriptor, store information relevant\n            # to the configuration. We'll need this to set up the endpoint\n            # hardware on the facedancer device.\n            if descriptor_type == self.DESCRIPTOR_CONFIGURATION and req.length >= 32:\n                configuration = USBDescribable.from_binary_descriptor(data)\n                self.configurations[configuration.number] = configuration\n                if self.verbose > 0:\n                    log.info(\"-- Storing configuration {} --\".format(configuration))\n\n\n            if descriptor_type == self.DESCRIPTOR_DEVICE and req.length >= 7:\n                # Patch our data to overwrite the maximum packet size on EP0.\n                # See USBProxy.connect for a rationale on this.\n                device = USBDescribable.from_binary_descriptor(data)\n                device.max_packet_size_ep0 = 64\n                data = bytearray(device.get_descriptor())[:len(data)]\n                if self.verbose > 0:\n                    log.info(\"-- Patched device descriptor. --\")\n\n        return req, data, stalled\n\n\n    def filter_control_out(self, req, data):\n        # Special case: if this is a SET_ADDRESS request,\n        # handle it ourself, and absorb it.\n        if req.get_recipient() == self.RECIPIENT_DEVICE and \\\n           req.request == self.SET_ADDRESS_REQUEST:\n            req.acknowledge(blocking=True)\n            self.device.set_address(req.value)\n            return None, None\n\n        # Special case: if this is a SET_CONFIGURATION_REQUEST,\n        # pass it through, but also set up the Facedancer hardware\n        # in response.\n        if req.get_recipient() == self.RECIPIENT_DEVICE and \\\n           req.request == self.SET_CONFIGURATION_REQUEST:\n            configuration_index = req.value\n\n            # If we have a known configuration for this index, apply it.\n            if configuration_index in self.configurations:\n                configuration = self.configurations[configuration_index]\n\n                if self.verbose > 0:\n                    log.info(\"-- Applying configuration {} --\".format(configuration))\n\n                self.device.configured(configuration)\n\n            # Otherwise, the host has applied a configuration without ever reading\n            # its descriptor. This is mighty strange behavior!\n            else:\n                log.warning(\"-- WARNING: Applying configuration {}, but we've never read that configuration's descriptor! --\".format(configuration_index))\n\n        # Special case: if this is a SET_INTERFACE_REQUEST,\n        # pass it through, but also tell the device so it can update\n        # its current configuration.\n        if req.get_recipient() == self.RECIPIENT_INTERFACE and \\\n           req.request == self.SET_INTERFACE_REQUEST:\n            interface_number = req.index\n            alternate = req.value\n            self.device.interface_changed(interface_number, alternate)\n\n        return req, data\n"
  },
  {
    "path": "facedancer/interface.py",
    "content": "#\n# This file is part of facedancer.\n#\n\"\"\" Functionality for defining USB interfaces. \"\"\"\n\n# Support annotations on Python < 3.9\nfrom __future__  import annotations\n\nimport struct\nimport textwrap\n\nfrom typing       import Dict, List, Iterable\nfrom dataclasses  import field\nfrom collections  import defaultdict\n\nfrom .magic       import instantiate_subordinates, AutoInstantiable\nfrom .types       import USBDirection, USBStandardRequests\n\nfrom .            import device\nfrom .descriptor  import USBDescribable, USBDescriptor, USBClassDescriptor, USBDescriptorTypeNumber, StringRef\nfrom .request     import USBControlRequest, USBRequestHandler, get_request_handler_methods\nfrom .request     import standard_request_handler, to_this_interface\nfrom .endpoint    import USBEndpoint\n\nfrom .logging     import log\n\n\nclass USBInterface(USBDescribable, AutoInstantiable, USBRequestHandler):\n    \"\"\" Class representing a USBDevice interface.\n\n    Fields:\n        number :\n            The interface's index. Zero indexed.\n        class_number, subclass_number, protocol_number :\n            The USB class adhered to on this interface; usually a USBDeviceClass constant.\n        interface_string :\n            A short, descriptive string used to identify the endpoint; or None if not provided.\n    \"\"\"\n    DESCRIPTOR_TYPE_NUMBER = 0x4\n\n    name                   : StringRef = StringRef.field(string=\"generic USB interface\")\n\n    number                 : int = 0\n    alternate              : int = 0\n\n    class_number           : int = 0\n    subclass_number        : int = 0\n    protocol_number        : int = 0\n\n    interface_string       : str = None\n\n    # Descriptors that will be included in a GET_CONFIGURATION response.\n    attached_descriptors     : List[USBDescriptor] = field(default_factory=list)\n\n    # Descriptors that can be requested with the GET_DESCRIPTOR request.\n    requestable_descriptors    : Dict[tuple[int, int], USBDescriptor] = field(default_factory=dict)\n\n    endpoints              : Dict[int, USBEndpoint] = field(default_factory=dict)\n    parent                 : USBDescribable = None\n\n\n    @classmethod\n    def from_binary_descriptor(cls, data, strings={}):\n        \"\"\"\n        Generates an interface object from a descriptor.\n        \"\"\"\n        interface_number, alternate_setting, num_endpoints, interface_class, \\\n                interface_subclass, interface_protocol, string_index \\\n                = struct.unpack_from(\"xxBBBBBBB\", data)\n        return cls(\n            name=None,\n            number=interface_number,\n            alternate=alternate_setting,\n            class_number=interface_class,\n            subclass_number=interface_subclass,\n            protocol_number=interface_protocol,\n            interface_string=StringRef.lookup(strings, string_index)\n        )\n\n\n    def __post_init__(self):\n\n        self.interface_string = StringRef.ensure(self.interface_string)\n\n        # Capture any descriptors/endpoints declared directly on the class.\n        for endpoint in instantiate_subordinates(self, USBEndpoint):\n            self.add_endpoint(endpoint)\n        for descriptor in instantiate_subordinates(self, USBDescriptor):\n            self.add_descriptor(descriptor)\n\n        # Populate our request handlers.\n        self._request_handler_methods = get_request_handler_methods(self)\n\n\n    #\n    # User interface.\n    #\n\n    def get_device(self):\n        \"\"\" Returns the device associated with the given descriptor. \"\"\"\n        return self.parent.get_device()\n\n\n    def add_endpoint(self, endpoint: USBEndpoint):\n        \"\"\" Adds the provided endpoint to the interface. \"\"\"\n        if endpoint.address in self.endpoints:\n            ep_name = type(endpoint).__name__\n            ep_addr = f\"0x{endpoint.address:02X}\"\n            other = self.endpoints[endpoint.address]\n            other_name = type(other).__name__\n            raise Exception(\n                f\"Endpoint of type {ep_name} cannot be added to this \"\n                f\"interface because there is already an endpoint of \"\n                f\"type {other_name} with the same address {ep_addr}\")\n        else:\n            self.endpoints[endpoint.address] = endpoint\n            endpoint.parent = self\n\n\n    def get_endpoint(self, endpoint_number: int, direction: USBDirection) -> USBEndpoint:\n        \"\"\" Attempts to find a subordinate endpoint matching the given number/direction.\n\n        Args:\n            endpoint_number : The endpoint number to search for.\n            direction       : The endpoint direction to be matched.\n\n        Returns:\n            The matching endpoint; or None if no matching endpoint existed.\n        \"\"\"\n        address = USBEndpoint.address_for_number(endpoint_number, direction)\n        return self.endpoints.get(address, None)\n\n\n    def has_endpoint(self, endpoint_number: int, direction: USBDirection) -> USBEndpoint:\n        \"\"\" Returns true iff we have matching subordinate endpoint.\n\n        Args:\n            endpoint_number : The endpoint number to search for.\n            direction       : The endpoint direction to be matched.\n        \"\"\"\n        return (self.get_endpoint(endpoint_number, direction) is not None)\n\n\n    def add_descriptor(self, descriptor: USBDescriptor):\n        \"\"\" Adds the provided descriptor to the interface. \"\"\"\n        identifier = descriptor.get_identifier()\n        desc_name = type(descriptor).__name__\n\n        if descriptor.include_in_config:\n            self.attached_descriptors.append(descriptor)\n            descriptor.parent = self\n\n        elif descriptor.number is None:\n            raise Exception(\n                f\"Descriptor of type {desc_name} cannot be added to this \"\n                f\"interface because it is not to be included in the \"\n                f\"configuration descriptor, yet does not have a number \"\n                f\"to request it separately with\")\n\n        elif identifier in self.requestable_descriptors:\n            other = self.requestable_descriptors[identifier]\n            other_name = type(other).__name__\n            other_type = f\"0x{other.type_number:02X}\"\n            raise Exception(\n                f\"Descriptor of type {desc_name} cannot be added to this \"\n                f\"interface because there is already a descriptor of type \"\n                f\"{other_name} with the same type code {other_type} and \"\n                f\"number {other.number}\")\n\n        else:\n            self.requestable_descriptors[identifier] = descriptor\n            descriptor.parent = self\n\n\n    #\n    # Event handlers.\n    #\n\n    def handle_data_received(self, endpoint: USBEndpoint, data: bytes):\n        \"\"\" Handler for receipt of non-control request data.\n\n        Typically, this method will delegate any data received to the\n        appropriate configuration/interface/endpoint. If overridden, the\n        overriding function will receive all data; and can delegate it by\n        calling the `.handle_data_received` method on `self.configuration`.\n\n        Args:\n            endpoint_number : The endpoint number on which the data was received.\n            data            : The raw bytes received on the relevant endpoint.\n        \"\"\"\n\n        if self.has_endpoint(endpoint.number, endpoint.direction):\n            endpoint.handle_data_received(data)\n        else:\n            self.get_device().handle_unexpected_data_received(endpoint.number, data)\n\n\n    def handle_data_requested(self, endpoint: USBEndpoint):\n        \"\"\" Handler called when the host requests data on a non-control endpoint.\n\n        Typically, this method will delegate the request to the appropriate\n        interface+endpoint. If overridden, the overriding function will receive\n        all data.\n\n        Args:\n            endpoint_number : The endpoint number on which the host requested data.\n        \"\"\"\n\n        if self.has_endpoint(endpoint.number, endpoint.direction):\n            endpoint.handle_data_requested()\n        else:\n            self.get_device().handle_unexpected_data_requested(endpoint.number)\n\n\n    def handle_buffer_empty(self, endpoint: USBEndpoint):\n        \"\"\" Handler called when a given endpoint first has an empty buffer.\n\n        Often, an empty buffer indicates an opportunity to queue data\n        for sending ('prime an endpoint'), but doesn't necessarily mean\n        that the host is planning on reading the data.\n\n        This function is called only once per buffer.\n        \"\"\"\n\n        if self.has_endpoint(endpoint.number, endpoint.direction):\n            endpoint.handle_buffer_empty()\n\n\n    #\n    # Backend helpers.\n    #\n\n    def get_endpoints(self):\n        \"\"\" Returns an iterable over all endpoints in this interface. \"\"\"\n        return self.endpoints.values()\n\n\n    #\n    # Internal interface.\n    #\n\n    @standard_request_handler(number=USBStandardRequests.GET_DESCRIPTOR)\n    @to_this_interface\n    def handle_get_descriptor_request(self, request):\n        \"\"\" Handle GET_DESCRIPTOR requests; per USB2 [9.4.3] \"\"\"\n        log.debug(\"Handling GET_DESCRIPTOR on endpoint.\")\n\n        # This is the same as the USBDevice get descriptor request => avoid duplication.\n        self.get_device().handle_generic_get_descriptor_request(self, request)\n\n\n    # Table 9-12 of USB 2.0 spec (pdf page 296)\n    def get_descriptor(self) -> bytes:\n        \"\"\" Retrieves the given interface's interface descriptor, with subordinates. \"\"\"\n\n        # FIXME: use construct\n\n        string_manager = self.get_device().strings\n\n        d = bytearray([\n                9,          # length of descriptor in bytes\n                4,          # descriptor type 4 == interface\n                self.number,\n                self.alternate,\n                len(self.endpoints),\n                self.class_number,\n                self.subclass_number,\n                self.protocol_number,\n                string_manager.get_index(self.interface_string)\n        ])\n\n        for descriptor in self.attached_descriptors:\n            if callable(descriptor):\n                d += descriptor()\n            else:\n                d += descriptor\n\n        # ... append each endpoint's endpoint descriptor.\n        for e in self.endpoints.values():\n            d += e.get_descriptor()\n            for descriptor in e.attached_descriptors:\n                if callable(descriptor):\n                    d += descriptor()\n                else:\n                    d += descriptor\n\n\n        return d\n\n    #\n    # Alternate interface support.\n    #\n\n    @standard_request_handler(number=USBStandardRequests.SET_INTERFACE)\n    @to_this_interface\n    def handle_set_interface_request(self, request: USBControlRequest):\n        \"\"\" Handle SET_INTERFACE requests; per USB2 [9.4.10] \"\"\"\n        log.debug(f\"f{self.name} received SET_INTERFACE request\")\n\n        configuration = self.parent\n        device = configuration.parent\n        backend = device.backend\n\n        if device.configuration is None:\n            request.stall()\n        else:\n            try:\n                # Find this alternate setting and switch to it.\n                number = request.index_low\n                alternate = request.value\n                identifier = (number, alternate)\n                interface = configuration.interfaces[identifier]\n                configuration.active_interfaces[number] = interface\n                # Reset the data toggles of this interface's endpoints.\n                for endpoint in interface.endpoints.values():\n                    backend.clear_halt(endpoint.number, endpoint.direction)\n                request.acknowledge()\n            except KeyError:\n                request.stall()\n\n    @standard_request_handler(number=USBStandardRequests.GET_INTERFACE)\n    @to_this_interface\n    def handle_get_interface_request(self, request):\n        \"\"\" Handle GET_INTERFACE requests; per USB2 [9.4.4] \"\"\"\n        log.debug(\"received GET_INTERFACE request\")\n\n        configuration = self.parent\n        device = configuration.parent\n\n        if device.configuration is None:\n            request.stall()\n        else:\n            try:\n                number = request.index_low\n                interface = configuration.active_interfaces[number]\n                request.reply(bytes([interface.alternate]))\n            except KeyError:\n                request.stall()\n\n\n    #\n    # Automatic instantiation support.\n    #\n\n    def get_identifier(self) -> (int, int):\n        return (self.number, self.alternate)\n\n\n    # Although we identify interfaces by (number, alternate), this helper\n    # is called from the request handling code, where we only want to\n    # match by interface number. The correct alternate interface should have\n    # been selected earlier in the request handling process.\n    def matches_identifier(self, other: int) -> bool:\n        return (other == self.number)\n\n\n    #\n    # Request handler functions.\n    #\n\n    def _request_handlers(self) -> Iterable[callable]:\n        return self._request_handler_methods\n\n    def _get_subordinate_handlers(self) -> Iterable[callable]:\n        return self.endpoints.values()\n\n\n    def generate_code(self, name=None, indent=0):\n\n        if name is None:\n            if self.alternate == 0:\n                name = f\"Interface_{self.number}\"\n            else:\n                name = f\"Interface_{self.number}_{self.alternate}\"\n\n        code = f\"\"\"\nclass {name}(USBInterface):\n    number           = {self.number}\n    alternate        = {self.alternate}\n    class_number     = {self.class_number}\n    subclass_number  = {self.subclass_number}\n    protocol_number  = {self.protocol_number}\n    interface_string = {self.interface_string.generate_code()}\n\"\"\"\n\n        # Use alphabetic suffixes to distinguish between multiple attached\n        # descriptors with the same type number.\n        suffixes = defaultdict(lambda: 'A')\n\n        for descriptor in self.attached_descriptors:\n            type_number = descriptor.type_number\n            suffix = suffixes[type_number]\n            suffixes[type_number] = chr(ord(suffix) + 1)\n            name = f\"Descriptor_0x{type_number:02X}_{suffix}\"\n            code += descriptor.generate_code(name=name, indent=4)\n\n        for endpoint in self.endpoints.values():\n            code += endpoint.generate_code(indent=4)\n\n        for descriptor_id in sorted(self.requestable_descriptors):\n            descriptor = self.requestable_descriptors[descriptor_id]\n            code += descriptor.generate_code(indent=4)\n\n        return textwrap.indent(code, indent * ' ')\n"
  },
  {
    "path": "facedancer/logging.py",
    "content": "import functools\nimport logging\nimport sys\n\n\nLOGLEVEL_TRACE = 5\n\nLOG_FORMAT_COLOR = \"\\u001b[37;1m%(levelname)-8s| \\u001b[0m\\u001b[1m%(module)-15s|\\u001b[0m %(message)s\"\nLOG_FORMAT_PLAIN = \"%(levelname)-8s| %(module)-15s| %(message)s\"\n\n\ndef configure_default_logging(level=logging.INFO, logger=logging):\n    if sys.stdout.isatty():\n        log_format = LOG_FORMAT_COLOR\n    else:\n        log_format = LOG_FORMAT_PLAIN\n\n    logger.basicConfig(level=level, format=log_format)\n    logging.getLogger(\"facedancer\").level = level\n\n\ndef _initialize_logging():\n    # add a TRACE level to logging\n    logging.TRACE = LOGLEVEL_TRACE\n    logging.addLevelName(logging.TRACE, \"TRACE\")\n    logging.Logger.trace = functools.partialmethod(logging.Logger.log, logging.TRACE)\n    logging.trace = functools.partial(logging.log, logging.TRACE)\n\n    # Configure facedancer logger\n    logger = logging.getLogger(\"facedancer\")\n    logger.level = logging.WARN\n\n    return logger\n\n\nlog = _initialize_logging()\n"
  },
  {
    "path": "facedancer/magic.py",
    "content": "#\n# This file is part of Facedancer.\n#\n\"\"\" Functionally for automatic instantiations / tracking via decorators. \"\"\"\n\nimport inspect\n\nfrom abc         import ABCMeta, abstractmethod\nfrom dataclasses import dataclass, is_dataclass, field, fields\n\n\nclass DescribableMeta(ABCMeta):\n    \"\"\" Metaclass for USBDescribable subclasses. \"\"\"\n    def __new__(cls, name, bases, classdict):\n        annotations = classdict.setdefault('__annotations__', {})\n        for base in bases:\n            if is_dataclass(base):\n                for field in fields(base):\n                    if field.name in classdict:\n                        if field.name not in annotations:\n                            annotations[field.name] = str(field.type)\n        new_cls = ABCMeta.__new__(cls, name, bases, classdict)\n        return dataclass(new_cls, kw_only=True)\n\n\ndef adjust_defaults(cls, **kwargs):\n    \"\"\" Adjusts the defaults of an existing dataclass. \"\"\"\n    assert is_dataclass(cls)\n    for name, value in kwargs.items():\n        cls.__dataclass_fields__[name] = field(default = value)\n        cls.__init__.__kwdefaults__[name] = value\n    return cls\n\n\nclass AutoInstantiable(metaclass=DescribableMeta):\n    \"\"\" Base class for methods that can be decorated with use_automatically. \"\"\"\n\n    @abstractmethod\n    def get_identifier(self) -> int:\n        \"\"\" Returns a unique integer identifier for this object.\n\n        This is usually the index or address of the relevant USB object.\n        \"\"\"\n\n    def matches_identifier(self, other: int) -> bool:\n        return (other == self.get_identifier())\n\n\nclass AutoInstantiator:\n    \"\"\" Simple wrapper class annotated on objects that can be instantiated automatically.\n\n    Used for the @use_automatically decorator; which removes a lot of the Facedancer boilerplate\n    at the cost of being somewhat cryptic.\n    \"\"\"\n\n    def __init__(self, target_type):\n        self._target_type = target_type\n\n    def creates_instance_of(self, expected_type):\n        return issubclass(self._target_type, expected_type)\n\n    def __call__(self, parent):\n        return self._target_type(parent=parent)\n\n\ndef use_automatically(cls):\n    \"\"\" Class decorator used to annotate Facedancer inner classes. Implies @dataclass.\n\n    This decorator can be placed on inner classes that describe \"subordinate\"\n    objects on USB devices. For example, a USBDevice can have several subordinate\n    USBConfigurations; which select the various configurations for that class.\n\n    When placed on a subordinate class, this allows the parent class to automatically\n    instantiate the relevant given class during its creation; automatically populating\n    the subordinate properties of the relevant device.\n\n    For example, assume we have a Facedancer class representing a custom USB device::\n\n        class ExampleDevice(USBDevice):\n            product_string : str = \"My Example Device\"\n\n            @use_automatically\n            class DefaultConfiguration(USBConfiguration):\n                number : int = 1\n\n    In this case, when an ExampleDevice is instantiated, the USBDevice code knows how\n    to instantiate DefaultConfiguration, and will do so automatically.\n\n    Note that this decorator should _only_ be used for subordinate types; and expects that\n    the decorated class has no explicitly-declared __init__ method. The __post_init__ mechanism\n    of python dataclasses can be overridden to perform any needed initialization.\n    \"\"\"\n    return AutoInstantiator(cls)\n\n\ndef _use_inner_classes_automatically(cls):\n    # Iterate over the relevant class...\n    for name, member in cls.__dict__.items():\n\n        # ... and tag each inner class with both use_automatically\n        # -and- use_inner_classes_automatically. The former\n        if inspect.isclass(member) and issubclass(member, AutoInstantiable):\n\n            wrapped_class = _use_inner_classes_automatically(member)\n            wrapped_class = use_automatically(member)\n\n            setattr(cls, name, wrapped_class)\n\n    return cls\n\n\ndef use_inner_classes_automatically(cls):\n    \"\"\" Decorator that acts as if all inner classes were defined with `use_automatically`. \"\"\"\n    return _use_inner_classes_automatically(cls)\n\n\ndef instantiate_subordinates(obj, expected_type):\n    \"\"\" Automatically instantiates any inner classes with a matching type.\n\n    This is used by objects that represent USB hardware behaviors (e.g. USBDevice,\n    USBConfiguration, USBInterface, USBEndpoint) in order to automatically create\n    objects of any inner class decorated with ``@use_automatically``.\n    \"\"\"\n\n    # Search our class for anything decorated with an AutoInstantiator of the relevant type.\n    for member in type(obj).__dict__.values():\n        if isinstance(member, AutoInstantiator) and member.creates_instance_of(expected_type):\n            yield member(object)\n"
  },
  {
    "path": "facedancer/proxy.py",
    "content": "#\n# This file is part of Facedancer.\n#\n\"\"\" USB Proxy implementation. \"\"\"\n\nimport atexit\nimport platform\nimport usb1\nimport sys\n\nfrom usb1        import USBError, USBErrorTimeout\n\nfrom .           import DeviceSpeed, USBConfiguration, USBDirection\nfrom .device     import USBBaseDevice\nfrom .errors     import DeviceNotFoundError\nfrom .logging    import log\nfrom .request    import USBControlRequest\nfrom .types      import USB\n\n\nclass USBProxyDevice(USBBaseDevice):\n    \"\"\" USB Proxy Device \"\"\"\n\n    name = \"USB Proxy Device\"\n\n    filter_list = []\n\n    def __init__(self, index=0, quirks=[], scheduler=None, **kwargs):\n        \"\"\"\n        Sets up a new USBProxy instance.\n        \"\"\"\n\n        # Finally, initialize our base class with a minimal set of\n        # parameters.  We'll do almost nothing, as we'll be proxying\n        # packets by default to the device.\n        super().__init__()\n\n        # We have only one proxy backend in existence at this time.\n        self.proxied_device = LibUSB1Device\n\n        # Find the device to proxy matching the given keyword arguments...\n        usb_devices = list(self.proxied_device.find(find_all=True, **kwargs))\n        if len(usb_devices) <= index:\n            raise DeviceNotFoundError(f\"Could not find device to proxy.\")\n        device = usb_devices[index]\n\n        # Open a connection to the proxied device and attempt to\n        # detach it from any kernel-side driver that may prevent us\n        # from communicating with it...\n        device_handle = self.proxied_device.open(device, detach=True)\n        log.info(f\"Found {self.proxied_device.device_speed().name} speed device to proxy: {device}\")\n\n\n    def add_filter(self, filter_object, head=False):\n        \"\"\"\n        Adds a filter to the USBProxy filter stack.\n        \"\"\"\n        if head:\n            self.filter_list.insert(0, filter_object)\n        else:\n            self.filter_list.append(filter_object)\n\n\n    def connect(self):\n        \"\"\"\n        Initialize this device. We perform a reduced initialization, as we really\n        only want to proxy data.\n        \"\"\"\n\n        # Always use a max_packet_size of 64 on EP0.\n\n        # This works around a Linux spec violation in which Linux assumes it can read 64 bytes of\n        # control descriptor no matter the device speed and actual maximum packet size. If this\n        # doesn't work, Linux tries to reset / power-cycle the device, and then recovers with an\n        # in-spec read; but this causes a huge delay and/or breakage, depending on the proxied\n        # device.\n        #\n        # Since we're working at the transfer levels, the packet sizes will automatically be\n        # translated, anyway.\n        self.max_packet_size_ep0 = 64\n\n        # Get the USB device speed of the device being proxied.\n        device_speed = self.proxied_device.device_speed()\n\n        # Connect device.\n        super().connect(device_speed=device_speed)\n\n        # TODO check if we still need this in facedancer v3\n        # skipping USB.state_attached may not be strictly correct (9.1.1.{1,2})\n        self.state = USB.state_powered\n\n\n    # - event handlers --------------------------------------------------------\n\n    def configured(self, configuration: USBConfiguration):\n        \"\"\"\n        Callback that handles when the target device becomes configured.\n        If you're using the standard filters, this will be called automatically;\n        if not, you'll have to call it once you know the device has been configured.\n\n        Args:\n            configuration: The configuration to be applied.\n        \"\"\"\n\n        # All interfaces on the configuration are set to their default setting.\n        configuration.active_interfaces = {\n            interface.number : interface\n                for interface in configuration.get_interfaces()\n                    if interface.alternate == 0\n        }\n\n        # Pass our configuration on to the core device.\n        self.backend.configured(configuration)\n        configuration.parent = self # FIXME Not great semantics\n        self.configuration = configuration\n\n\n    def interface_changed(self, interface_number: int, alternate: int):\n        \"\"\"\n        Callback that handles when a SET_INTERFACE request is made to the target.\n        If you're using the standard filters, this will be called automatically;\n        if not, you'll have to call it once you know an alternate setting has been\n        applied.\n\n        Args:\n            interface_number: The interface number.\n            alternate: The alternate setting to be applied.\n        \"\"\"\n        identifier = (interface_number, alternate)\n        interface = self.configuration.interfaces[identifier]\n        self.configuration.active_interfaces[interface_number] = interface\n\n\n    def handle_bus_reset(self):\n        super().handle_bus_reset()\n\n\n    def handle_request(self, request: USBControlRequest):\n        \"\"\"\n        Proxies EP0 requests between the victim and the target.\n        \"\"\"\n\n        if request.get_direction() == 1:\n            self._proxy_in_control_request(request)\n        else:\n            self._proxy_out_control_request(request)\n\n\n    def handle_get_configuration_request(self, request):\n        super().handle_get_configuration_request(request)\n\n\n    def handle_get_descriptor_request(self, request):\n        super().handle_get_descriptor_request(request)\n\n\n    def handle_data_available(self, ep_num, data):\n        \"\"\"\n        Handles the case where data is ready from the Facedancer device\n        that needs to be proxied to the target device.\n        \"\"\"\n\n        # Run the data through all of our filters.\n        for f in self.filter_list:\n            ep_num, data = f.filter_out(ep_num, data)\n\n        # If the data wasn't filtered out, communicate it to the target device.\n        if data:\n            try:\n                self.proxied_device.write(ep_num, data)\n            except USBError as e:\n                stalled = True\n\n                for f in self.filter_list:\n                    request, data, stalled = f.handle_out_stall(ep_num, data, stalled)\n\n                if stalled:\n                    self.backend.stall_endpoint(0, USBDirection.OUT)\n\n\n    def handle_nak(self, ep_num):\n        \"\"\"\n        Handles a NAK, which means that the target asked the proxied device\n        to participate in a transfer. We use this as our cue to participate\n        in communications.\n        \"\"\"\n        # Make sure the endpoint exists for the current configuration\n        # before attempting to handle NAK events.\n        # Skip handling OUT endpoints, as we handle those in handle_data_available.\n        endpoint = self.configuration.get_endpoint(ep_num, USBDirection.IN)\n        if endpoint is None:\n            return\n\n        # TODO: Currently, we use this for _all_ non-control transfers, as we\n        # don't e.g. periodically schedule isochronous or interrupt transfers.\n        # We probably should set up those to be independently scheduled and\n        # then limit this to only bulk endpoints.\n        self._proxy_in_transfer(endpoint)\n\n\n    # - helpers ---------------------------------------------------------------\n\n    def _ack_status_stage(self, blocking=False):\n        self.backend.ack_status_stage(blocking=blocking)\n\n    def _proxy_in_control_request(self, request: USBControlRequest):\n        \"\"\"\n        Proxy IN requests, which gather data from the device and\n        forward it to the target host.\n        \"\"\"\n\n        data = []\n        stalled = False\n\n        # Filter the setup stage generated by the target device. We can use this\n        # to e.g. change the setup stage before proxying it to the target device,\n        # or to absorb a packet before it's proxied.\n        for f in self.filter_list:\n            request, stalled = f.filter_control_in_setup(request, stalled)\n\n        # If we stalled immediately, handle the stall and return without proxying.\n        if stalled:\n            self.backend.stall_endpoint(0, USBDirection.IN)\n            return\n\n        # If we filtered out the setup request, NAK.\n        if request is None:\n            return\n\n        # Read any data from the real device...\n        try:\n            data = self.proxied_device.controlRead(\n                request_type=request.request_type,\n                request=request.request,\n                value=request.value,\n                index=request.index,\n                length=request.length,\n            )\n        except USBError as e:\n            stalled = True\n\n        # Run filters here.\n        for f in self.filter_list:\n            request, data, stalled = f.filter_control_in(request, data, stalled)\n\n        #... and proxy it to our victim.\n        if stalled:\n            # TODO: allow stalling of eps other than 0!\n            self.backend.stall_endpoint(0, USBDirection.IN)\n        else:\n            # TODO: support control endpoints other than 0\n            self.control_send(0, request, data)\n\n\n    def _proxy_out_control_request(self, request: USBControlRequest):\n        \"\"\"\n        Proxy OUT requests, which sends a request from the victim to the\n        target device.\n        \"\"\"\n\n        data = request.data\n\n        for f in self.filter_list:\n            request, data = f.filter_control_out(request, data)\n\n        # ... forward the request to the real device.\n        if request:\n            try:\n                self.proxied_device.controlWrite(\n                    request_type=request.request_type,\n                    request=request.request,\n                    value=request.value,\n                    index=request.index,\n                    data=data\n                )\n                self._ack_status_stage()\n\n            # Special case: we've stalled, allow the filters to decide what to do.\n            except USBError as e:\n                stalled = True\n\n                for f in self.filter_list:\n                    request, data, stalled = f.handle_out_request_stall(request, data, stalled)\n\n                if stalled:\n                    self.backend.stall_endpoint(0, USBDirection.OUT)\n\n\n    def _proxy_in_transfer(self, endpoint):\n        \"\"\"\n        Proxy OUT requests, which sends a request from the target device to the\n        victim, at the target's request.\n        \"\"\"\n\n        ep_num = endpoint.number\n\n        # Filter the \"IN token\" generated by the target device. We can use this\n        # to e.g. change the endpoint before proxying to the target device, or\n        # to absorb a packet before it's proxied.\n        for f in self.filter_list:\n            ep_num = f.filter_in_token(ep_num)\n\n        if ep_num is None:\n            return\n\n        try:\n            # Quick hack to improve responsiveness on interrupt endpoints.\n            if endpoint.interval:\n                data = self.proxied_device.read(ep_num, endpoint.max_packet_size, timeout=endpoint.interval)\n            else:\n                data = self.proxied_device.read(ep_num, endpoint.max_packet_size)\n\n        except usb1.USBErrorPipe:\n            self.proxied_device.clear_halt(ep_num, USBDirection.IN)\n            return\n        except USBErrorTimeout:\n            return\n\n        # Run the data through all of our filters.\n        for f in self.filter_list:\n            ep_num, data = f.filter_in(endpoint.number, data)\n\n        # If our data wasn't filtered out, transmit it to the target!\n        if data:\n            endpoint.send(data)\n\n\n\nclass LibUSB1Device:\n    \"\"\" A wrapper around the proxied device based on libusb1. \"\"\"\n\n\n    \"\"\" Class variable that stores our global libusb library context. \"\"\"\n    context = None\n\n    \"\"\" Class variable that stores our device handle. \"\"\"\n    device_handle = None\n\n\n    @classmethod\n    def _get_libusb_context(cls):\n        \"\"\" Retrieves the libusb context we'll use to fetch libusb device instances. \"\"\"\n\n        # If we don't have a libusb context, create one.\n        if cls.context is None:\n            cls.context = usb1.USBContext().__enter__()\n            atexit.register(cls._destroy_libusb_context)\n\n        return cls.context\n\n\n    @classmethod\n    def _destroy_libusb_context(cls):\n        \"\"\" Destroys our libusb context on closing our python instance. \"\"\"\n        if cls.device_handle is not None:\n            device = cls.device_handle.getDevice()\n            number = cls.device_handle.getConfiguration()\n            active_configuration = next(filter(lambda c: c.getConfigurationValue() == number, device), None)\n            if active_configuration:\n                for interface in active_configuration:\n                    number = interface[0].getNumber()\n                    try:\n                        cls.device_handle.releaseInterface(number)\n                    except usb1.USBErrorNotFound as e:\n                        log.warning(f\"Failed to releace interface {0} for {device}\")\n                        pass\n\n            cls.device_handle.close()\n            cls.device_handle = None\n\n        if cls.context is not None:\n            cls.context.close()\n            cls.context = None\n\n\n    @classmethod\n    def open(cls, device, detach=True):\n        cls.device_handle = device.open()\n        try:\n            cls.device_handle.setAutoDetachKernelDriver(detach)\n        except usb1.USBErrorNotSupported:\n            pass\n\n        number = cls.device_handle.getConfiguration()\n        active_configuration = next(filter(lambda c: c.getConfigurationValue() == number, device), None)\n        if active_configuration:\n            for interface in active_configuration:\n                number = interface[0].getNumber()\n                try:\n                    cls.device_handle.claimInterface(number)\n                except usb1.USBErrorAccess:\n                    log.error(f\"Failed to claim interface {number} for {device}\")\n                    if platform.system() == \"Darwin\":\n                        log.error(\"You may need to run your proxy code as root.\\n\")\n                    elif platform.system() == \"Linux\":\n                        log.error(\"Please ensure you have configured an entry for the device in your\")\n                        log.error(\"/etc/udev/rules.d directory.\\n\")\n                    elif platform.system() == \"Windows\":\n                        log.error(\"You may need to experiment with the Zadig driver to access the device.\\n\")\n                    sys.exit(1)\n\n        return cls.device_handle\n\n\n    # TODO adapt logic from pygreat usb1.py\n    @classmethod\n    def find(cls, idVendor, idProduct, find_all=True):\n        \"\"\" Finds a USB device by its identifiers. \"\"\"\n\n        matching_devices = []\n        context = cls._get_libusb_context()\n\n        for device in context.getDeviceList():\n            if device.getVendorID() == idVendor and device.getProductID() == idProduct:\n                matching_devices.append(device)\n\n        if find_all:\n            return matching_devices\n        elif matching_devices:\n            return matching_devices\n        else:\n            return None\n\n\n    @classmethod\n    def device_speed(cls):\n        return DeviceSpeed(cls.device_handle.getDevice().getDeviceSpeed())\n\n\n    @classmethod\n    def controlRead(cls, request_type, request, value, index, length, timeout=1000):\n        return cls.device_handle.controlRead(request_type, request, value, index, length, timeout)\n\n\n    @classmethod\n    def controlWrite(cls, request_type, request, value, index, data, timeout=1000):\n        return cls.device_handle.controlWrite(request_type, request, value, index, data, timeout)\n\n\n    @classmethod\n    def read(cls, endpoint_number, length, timeout=1000):\n        # Avoid accidental uses of endpoint address\n        endpoint_number = endpoint_number & 0x7f\n\n        # TODO support interrupt endpoints\n        return cls.device_handle.bulkRead(endpoint_number, length, timeout)\n\n\n    @classmethod\n    def write(cls, endpoint_number, data, timeout=1000):\n        # TODO support interrupt endpoints\n        return cls.device_handle.bulkWrite(endpoint_number, data, timeout)\n\n\n    @classmethod\n    def clear_halt(cls, endpoint_number, direction):\n        endpoint_address = direction.to_endpoint_address(endpoint_number)\n        return cls.device_handle.clearHalt(endpoint_address)\n\n\nif __name__ == \"__main__\":\n    from .                  import FacedancerUSBApp\n    from .filters.standard  import USBProxySetupFilters\n    from .filters.logging   import USBProxyPrettyPrintFilter\n\n    # akai midimix\n    VENDOR_ID  = 0x09e8\n    PRODUCT_ID = 0x0031\n\n    # xbox controller\n    #VENDOR_ID  = 0x045e\n    #PRODUCT_ID = 0x02d1\n\n    device = USBProxyDevice(idVendor=VENDOR_ID, idProduct=PRODUCT_ID)\n\n    device.add_filter(USBProxySetupFilters(device, verbose=2))\n    device.add_filter(USBProxyPrettyPrintFilter(verbose=5))\n\n    async def configure_logging():\n        import logging\n        logging.getLogger(\"facedancer\").setLevel(logging.INFO)\n\n    from facedancer import main\n    main(device, configure_logging())\n"
  },
  {
    "path": "facedancer/request.py",
    "content": "#\n# This file is part of Facedancer.\n#\n\"\"\" Functionality for declaring and working with USB control requests. \"\"\"\n\nimport inspect\nimport warnings\nimport functools\n\nfrom typing      import List, Iterable\nfrom dataclasses import dataclass\nfrom abc         import ABCMeta, abstractmethod\n\nfrom .descriptor import USBDescribable\nfrom .types      import USBRequestRecipient, USBRequestType, USBDirection, USBStandardRequests\n\n\ndef _wrap_with_field_matcher(func, field_name, field_value, match_index=False):\n    \"\"\" Internal function; generates a request-refinement decorator.\n\n    This generates a decorator that ensures a request-handler is only executed\n    if its one if its field (named `field_name`) matches a given value.\n\n    As an example, if this is called with `field_name`='index' and 'field_value'=3,\n    this modifies `func` so it is only executed for requests with an index of 3.\n\n    Args:\n        func        : The handler function to wrap.\n        field_name  : The name of the field to check.\n        field_value : The value the given field must have for the function to execute.\n\n        match_index : If true, the matcher is further refined in order to only execute\n                       for requests targeting a given e.g. interface or endpoint object.\n\n                       In this case, the handler is only executed if the low byte of the\n                       function's index matches the owning object's identifier, as verified\n                       with `matches_identifier`.\n    \"\"\"\n\n    @functools.wraps(func)\n    def _wrapped(caller, request):\n\n        # Compute our two conditions...\n        field_matches = (getattr(request, field_name) == field_value)\n        index_matches = \\\n            caller.matches_identifier(request.index & 0xff) \\\n            if hasattr(caller, \"matches_identifier\") and match_index else True\n\n        # ... and call the inner function only if they match.\n        if field_matches and index_matches:\n            func(caller, request)\n\n        # Otherwise, raise NotImplemented, which translates to a \"not handled here\".\n        else:\n            raise NotImplementedError()\n\n    return _wrapped\n\n\nclass ControlRequestHandler:\n    \"\"\" Class representing a control request handler.\n\n    Instances of this class are generated automatically each time a control request is\n    defined using decorator syntax; and track the association between the relevant handler\n    function and the condition under which it's executed.\n    \"\"\"\n\n    def __init__(self, handler_function, execution_condition):\n        self._handler   = handler_function\n        self._condition = execution_condition\n\n\n    def __call__(self, caller, request):\n        \"\"\" Primary execution; calls the relevant handler if our conditions are met. \"\"\"\n\n        if self._condition(request):\n            try:\n                self._handler(caller, request)\n                return True\n            except NotImplementedError:\n                return False\n\n\n    def add_condition(self, condition):\n        \"\"\" Refines a control request handler such that it's only called when the added condition is true. \"\"\"\n        base_condition = self._condition\n        self._condition = lambda req : base_condition(req) and condition(req)\n\n\n    def add_field_matcher(self, field_name, field_value):\n        \"\"\" Refines a control request handler such that it's only called when one of its fields matches a given value.\n\n        Args:\n            field_name : The property of the USBControlRequest object to be checked.\n            field_value : The value the relevant property must match to be called.\n        \"\"\"\n        matcher = lambda req : getattr(req, field_name) == field_value\n        self.add_condition(matcher)\n\n\n    def __repr__(self):\n        return f\"<ControlRequestHandler wrapping {self._handler.__qualname__} at 0x{id(self):x}\"\n\n\n\ndef control_request_handler(condition=lambda _ : True, **kwargs):\n    \"\"\" Decorator that declares a control request handler.\n\n    Used while defining a USBDevice, USBInterface, USBEndpoint, or\n    USBOtherRecipient class to declare handlers for that function.\n\n    Args:\n        condition : A function that, when evaluated on a USBControlRequest, evaluates\n                    true if and only if this function is an appropriate handler.\n    \"\"\"\n\n    def decorator(func):\n\n        # Wrap the handler function with a ControlRequestHandler, which will handle\n        # conditional execution of the relevant function.\n        handler = ControlRequestHandler(func, condition)\n\n        # Treat any keyword arguments passed to us beyond our condition as field matchers;\n        # which specify the properties a given request must have to trigger this handler.\n        for field, value in kwargs.items():\n            handler.add_field_matcher(field, value)\n\n        return handler\n\n    return decorator\n\n\ndef standard_request_handler(**kwargs):\n    \"\"\" Decorator; declares a standard request handler. See control_request_handler() for usage. \"\"\"\n    return control_request_handler(type=USBRequestType.STANDARD, **kwargs)\n\n\ndef vendor_request_handler(**kwargs):\n    \"\"\" Decorator; declares a vendor request handler. See control_request_handler() for usage. \"\"\"\n    return control_request_handler(type=USBRequestType.VENDOR, **kwargs)\n\n\ndef class_request_handler(**kwargs):\n    \"\"\" Decorator; declares a class request handler. See control_request_handler() for usage. \"\"\"\n    return control_request_handler(type=USBRequestType.CLASS, **kwargs)\n\n\ndef reserved_request_handler(**kwargs):\n    \"\"\" Decorator; declares a reserved-type request handler. Not typically used. \"\"\"\n    return control_request_handler(type=USBRequestType.RESERVED, **kwargs)\n\n\n#\n# Convenience request-refining decorators.\n#\n\ndef to_device(func):\n    \"\"\" Decorator; refines a handler so it's only called on requests with a device recipient. \"\"\"\n    return _wrap_with_field_matcher(func, 'recipient', USBRequestRecipient.DEVICE)\n\ndef to_this_endpoint(func):\n    \"\"\" Decorator; refines a handler so it's only called on requests targeting this endpoint. \"\"\"\n    return _wrap_with_field_matcher(func, 'recipient', USBRequestRecipient.ENDPOINT, match_index=True)\n\ndef to_any_endpoint(func):\n    \"\"\" Decorator; refines a handler so it's only called on requests with an endpoint recipient. \"\"\"\n    return _wrap_with_field_matcher(func, 'recipient', USBRequestRecipient.ENDPOINT)\n\ndef to_this_interface(func):\n    \"\"\" Decorator; refines a handler so it's only called on requests targeting this interface. \"\"\"\n    return _wrap_with_field_matcher(func, 'recipient', USBRequestRecipient.INTERFACE, match_index=True)\n\ndef to_any_interface(func):\n    \"\"\" Decorator; refines a handler so it's only called on requests with an interface recipient. \"\"\"\n    return _wrap_with_field_matcher(func, 'recipient', USBRequestRecipient.INTERFACE)\n\ndef to_other(func):\n    \"\"\" Decorator; refines a handler so it's only called on requests with an Other (TM) recipient. \"\"\"\n    return _wrap_with_field_matcher(func, 'recipient', USBRequestRecipient.OTHER)\n\n\n#\n# Metaprogramming aides.\n#\n\ndef get_request_handler_methods(cls) -> List[callable]:\n    \"\"\" Returns a list of all handler methods on a given class or object.\n\n    This is used to find all methods of an object decorated with the\n    @*_request_handler decorators.\n    \"\"\"\n\n    members = inspect.getmembers(cls)\n    return [m for _, m in members if isinstance(m, ControlRequestHandler)]\n\n\n#\n# Control request definitions.\n#\n\n@dataclass\nclass USBControlRequest:\n    \"\"\" Class encapsulating a USB control request.\n\n    TODO: document parameters\n    \"\"\"\n\n    direction    : USBDirection\n    type         : USBRequestType\n    recipient    : USBRequestRecipient\n\n    number       : int\n    value        : int\n    index        : int\n    length       : int\n\n    data         : bytes = b\"\"\n    device       : USBDescribable = None\n\n\n    @classmethod\n    def from_raw_bytes(cls, raw_bytes: bytes, *, device = None):\n        \"\"\" Creates a request object from a sequence of raw bytes.\n\n        Args:\n            raw_bytes : The raw bytes to create the object from.\n            device    : The USBDevice to associate with the given request.\n                         Optional, but necessary to use the .reply() / .acknowledge()\n                         methods.\n        \"\"\"\n\n        # FIXME: parse using construct\n        fields = {\n            'direction': (raw_bytes[0] >> 7) & 0b1,\n            'type':      (raw_bytes[0] >> 5) & 0b11,\n            'recipient': (raw_bytes[0] >> 0) & 0b11111,\n\n            'number':    raw_bytes[1],\n            'value':     (raw_bytes[3] << 8) | raw_bytes[2],\n            'index':     (raw_bytes[5] << 8) | raw_bytes[4],\n            'length':    (raw_bytes[7] << 8) | raw_bytes[6],\n            'data':      raw_bytes[8:],\n            'device':    device\n        }\n        return cls(**fields)\n\n\n    #\n    # I/O API.\n    #\n\n    def reply(self, data: bytes):\n        \"\"\" Replies to the given request with a given set of bytes. \"\"\"\n        self.device.control_send(endpoint_number=0, in_request=self, data=data)\n\n\n    def acknowledge(self, *, blocking: bool = False):\n        \"\"\" Acknowledge the given request without replying.\n\n        Args:\n            blocking : If true, the relevant control request will complete before returning.\n        \"\"\"\n        self.device.control_send(endpoint_number=0, in_request=self, data=b\"\", blocking=blocking)\n\n\n    def ack(self, *, blocking: bool = False):\n        \"\"\" Acknowledge the given request without replying.\n\n        Convenience alias for .acknowledge().\n\n        Args:\n            blocking : If true, the relevant control request will complete before returning.\n\n        \"\"\"\n        self.acknowledge(blocking=blocking)\n\n\n    def stall(self):\n        \"\"\" Stalls the associated device's control request.\n\n        Used to indicate that a given request isn't supported;\n        or isn't supported with the provided arguments.\n        \"\"\"\n        # Always stall IN endpoint for control requests\n        self.device.stall(endpoint_number=0, direction=USBDirection.IN)\n\n\n    #\n    # Properties.\n    #\n\n\n    @property\n    def request(self) -> int:\n        warnings.warn('`request` should be replaced with `number`', DeprecationWarning)\n        return self.number\n\n    @property\n    def request_type(self) -> int:\n        \"\"\" Fetches the whole `request_type` byte. \"\"\"\n        return (self.direction << 7) | \\\n               (self.type      << 5) | \\\n               (self.recipient << 0)\n\n    @property\n    def value_low(self) -> int:\n        return self.value & 0xff\n\n    @property\n    def value_high(self) -> int:\n        return self.value >> 8\n\n    @property\n    def index_low(self) -> int:\n        return self.index & 0xff\n\n    @property\n    def index_high(self) -> int:\n        return self.index >> 8\n\n    def get_direction(self) -> USBDirection:\n        return self.direction\n\n    def get_type(self) -> USBRequestType:\n        return self.type\n\n    def get_recipient(self) -> USBRequestRecipient:\n        return self.recipient\n\n\n    def raw(self) -> bytes:\n        \"\"\" Returns the raw bytes that compose the request. \"\"\"\n\n        # FIXME: use construct?\n        b = bytes([ self.request_type, self.number,\n                    self.value  & 0xff, (self.value  >> 8) & 0xff,\n                    self.index  & 0xff, (self.index  >> 8) & 0xff,\n                    self.length & 0xff, (self.length >> 8) & 0xff\n                  ])\n        return b\n\n    #\n    # Pretty printing & log output.\n    #\n    def __str__(self):\n\n        direction = USBDirection(self.direction).name\n        type_name = USBRequestType(self.type).name\n        recipient = USBRequestRecipient.from_integer(self.recipient).name\n        name      = f\"0x{self.number:02x}\"\n\n        # If this is a standard request, try to convert it to a name.\n        if self.type == USBRequestType.STANDARD:\n            try:\n                name = f\"{USBStandardRequests(self.number).name} (0x{self.number:02x})\"\n            except ValueError:\n                pass\n\n        return f\"{direction} {type_name} request {name} to {recipient} \" \\\n                f\"[value=0x{self.value:04x}, index=0x{self.index:04x}, length={self.length}]\"\n\n\n\nclass USBRequestHandler(metaclass=ABCMeta):\n    \"\"\" Base class for any object that handles USB requests. \"\"\"\n\n\n    @abstractmethod\n    def _request_handlers(self) -> Iterable[callable]:\n        \"\"\" Returns an iterable of request handlers provided by the class. \"\"\"\n\n\n    def _get_subordinate_handlers(self) -> Iterable[callable]:\n        \"\"\" Returns an iterable of subordinate handlers who should have an opportunity to handle requests.\n\n        Normally called by _call_subordinate_handlers; may not be valid if that function is overridden.\n        \"\"\"\n        return ()\n\n\n    def _call_subordinate_handlers(self, request: USBControlRequest) -> bool:\n        \"\"\" Calls the ``handle_request`` method of any subordinate handlers.\n\n        This default implementation uses get_subordinates to get an iterable\n        of subordinates we should call handle_request on.\n\n        Returns:\n            true iff the request is handled\n        \"\"\"\n\n        handled = False\n\n        for configuration in self._get_subordinate_handlers():\n            handled = handled or configuration.handle_request(request)\n\n        return handled\n\n\n\n    def handle_request(self, request: USBControlRequest) -> bool:\n        \"\"\" Core control request handler.\n\n        This function can be overridden by a subclass if desired; but the typical way to\n        handle a specific control request is to the the ``@control_request_handler`` decorators.\n\n        Args:\n            request : the USBControlRequest object representing the relevant request\n\n        Returns:\n            true iff the request is handled\n        \"\"\"\n\n        handled = False\n\n        # Our default implementation is simple: we try every handler; allowing any\n        # handler that wants to handle the relevant function a chance to handle it.\n        #\n        # Calling the handler for _every_ matching request (as opposed to e.g. the first one)\n        # allows one to trivially add observers.\n        for handler in self._request_handlers():\n            handled = handler(self, request) or handled\n\n        # Pass our requests down to our subordinates, as well.\n        handled = self._call_subordinate_handlers(request) or handled\n        return handled\n"
  },
  {
    "path": "facedancer/types.py",
    "content": "#\n# This file is part of Facedancer.\n#\n\"\"\" USB types -- defines enumerations that describe standard USB types \"\"\"\n\nfrom enum import Enum, IntFlag, IntEnum\n\nclass USBDirection(IntEnum):\n    \"\"\" Class representing USB directions. \"\"\"\n    OUT = 0\n    IN  = 1\n\n    def is_in(self):\n        return self is self.IN\n\n    def is_out(self):\n        return self is self.OUT\n\n    @classmethod\n    def parse(cls, value):\n        \"\"\" Helper that converts a numeric field into a direction. \"\"\"\n        return cls(value)\n\n    @classmethod\n    def from_request_type(cls, request_type_int):\n        \"\"\" Helper method that extracts the direction from a request_type integer. \"\"\"\n        return cls(request_type_int >> 7)\n\n    @classmethod\n    def from_endpoint_address(cls, address):\n        \"\"\" Helper method that extracts the direction from an endpoint address. \"\"\"\n        return cls(address >> 7)\n\n    def token(self):\n        \"\"\" Generates the token corresponding to the given direction. \"\"\"\n        return USBPacketID.IN if (self is self.IN) else USBPacketID.OUT\n\n    def reverse(self):\n        \"\"\" Returns the reverse of the given direction. \"\"\"\n        return self.OUT if (self is self.IN) else self.IN\n\n\n    def to_endpoint_address(self, endpoint_number):\n        \"\"\" Helper method that converts and endpoint_number to an address, given direction. \"\"\"\n        if self.is_in():\n            return endpoint_number | (1 << 7)\n        else:\n            return endpoint_number\n\n\nclass USBPIDCategory(IntFlag):\n    \"\"\" Category constants for each of the groups that PIDs can fall under. \"\"\"\n\n    SPECIAL   = 0b00\n    TOKEN     = 0b01\n    HANDSHAKE = 0b10\n    DATA      = 0b11\n\n    MASK      = 0b11\n\n\n\nclass USBPacketID(IntFlag):\n    \"\"\" Enumeration specifying all of the valid USB PIDs we can handle. \"\"\"\n\n    # Token group (lsbs = 0b01).\n    OUT   = 0b0001\n    IN    = 0b1001\n    SOF   = 0b0101\n    SETUP = 0b1101\n\n    # Data group (lsbs = 0b11).\n    DATA0 = 0b0011\n    DATA1 = 0b1011\n    DATA2 = 0b0111\n    MDATA = 0b1111\n\n    # Handshake group (lsbs = 0b10)\n    ACK   = 0b0010\n    NAK   = 0b1010\n    STALL = 0b1110\n    NYET  = 0b0110\n\n    # Special group.\n    PRE   = 0b1100\n    ERR   = 0b1100\n    SPLIT = 0b1000\n    PING  = 0b0100\n\n    # Flag representing that the PID seems invalid.\n    PID_INVALID   = 0b10000\n    PID_CORE_MASK = 0b01111\n\n\n    @classmethod\n    def from_byte(cls, byte, skip_checks=False):\n        \"\"\" Creates a PID object from a byte. \"\"\"\n\n        # Convert the raw PID to an integer.\n        pid_as_int = int.from_bytes(byte, byteorder='little')\n        return cls.from_int(pid_as_int, skip_checks=skip_checks)\n\n\n    @classmethod\n    def from_int(cls, value, skip_checks=True):\n        \"\"\" Create a PID object from an integer. \"\"\"\n\n        PID_MASK           = 0b1111\n        INVERTED_PID_SHIFT = 4\n\n        # Pull out the PID and its inverse from the byte.\n        pid          = cls(value & PID_MASK)\n        inverted_pid = value >> INVERTED_PID_SHIFT\n\n        # If we're not skipping checks,\n        if not skip_checks:\n            if (pid ^ inverted_pid) != PID_MASK:\n                pid |= cls.PID_INVALID\n\n        return cls(pid)\n\n\n    @classmethod\n    def from_name(cls, name):\n        \"\"\" Create a PID object from a string representation of its name. \"\"\"\n        return cls[name]\n\n\n    @classmethod\n    def parse(cls, value):\n        \"\"\" Attempt to create a PID object from a number, byte, or string. \"\"\"\n\n        if isinstance(value, bytes):\n            return cls.from_byte(value)\n\n        if isinstance(value, str):\n            return cls.from_name(value)\n\n        if isinstance(value, int):\n            return cls.from_int(value)\n\n        return cls(value)\n\n\n    def category(self):\n        \"\"\" Returns the USBPIDCategory that each given PID belongs to. \"\"\"\n        return USBPIDCategory(self & USBPIDCategory.MASK)\n\n\n    def is_data(self):\n        \"\"\" Returns true iff the given PID represents a DATA packet. \"\"\"\n        return self.category() is USBPIDCategory.DATA\n\n\n    def is_token(self):\n        \"\"\" Returns true iff the given PID represents a token packet. \"\"\"\n        return self.category() is USBPIDCategory.TOKEN\n\n\n    def is_handshake(self):\n        \"\"\" Returns true iff the given PID represents a handshake packet. \"\"\"\n        return self.category() is USBPIDCategory.HANDSHAKE\n\n\n    def is_invalid(self):\n        \"\"\" Returns true if this object is an attempt to encapsulate an invalid PID. \"\"\"\n        return (self & self.PID_INVALID)\n\n    def direction(self):\n        \"\"\" Get a USB direction from a PacketID. \"\"\"\n\n        if self is self.SOF:\n            return None\n\n        if self is self.SETUP or self is self.OUT:\n            return USBDirection.OUT\n\n        if self is self.IN:\n            return USBDirection.IN\n\n        raise ValueError(\"cannot determine the direction of a non-token PID\")\n\n\n\n    def summarize(self):\n        \"\"\" Return a summary of the given packet. \"\"\"\n\n        # By default, get the raw name.\n        core_pid  = self & self.PID_CORE_MASK\n        name = core_pid.name\n\n        if self.is_invalid():\n            return \"{} (check-nibble invalid)\".format(name)\n        else:\n            return name\n\n\nclass USBRequestRecipient(IntEnum):\n    \"\"\" Enumeration that describes each 'recipient' of a USB request field. \"\"\"\n\n    DEVICE    = 0\n    INTERFACE = 1\n    ENDPOINT  = 2\n    OTHER     = 3\n\n    RESERVED  = 4\n\n    @classmethod\n    def from_integer(cls, value):\n        \"\"\" Special factory that correctly handles reserved values. \"\"\"\n\n        # If we have one of the reserved values; indicate so.\n        if 4 <= value < 32:\n            return cls.RESERVED\n\n        # Otherwise, translate the raw value.\n        return cls(value)\n\n\n    @classmethod\n    def from_request_type(cls, request_type_int):\n        \"\"\" Helper method that extracts the type from a request_type integer. \"\"\"\n\n        MASK  = 0b11111\n        return cls(request_type_int & MASK)\n\n\nclass USBRequestType(IntEnum):\n    \"\"\" Enumeration that describes each possible Type field for a USB request. \"\"\"\n\n    STANDARD  = 0\n    CLASS     = 1\n    VENDOR    = 2\n    RESERVED  = 3\n\n\n    @classmethod\n    def from_request_type(cls, request_type_int):\n\n        \"\"\" Helper method that extracts the type from a request_type integer. \"\"\"\n        SHIFT = 5\n        MASK  = 0b11\n\n        return cls((request_type_int >> SHIFT) & MASK)\n\n\n\nclass USBTransferType(IntEnum):\n    CONTROL     = 0\n    ISOCHRONOUS = 1\n    BULK        = 2\n    INTERRUPT   = 3\n\n\ndef endpoint_number_from_address(number):\n    return number & 0x7F\n\nclass LanguageIDs(IntEnum):\n    AFRIKAANS                  = 0X0436\n    ALBANIAN                   = 0X041C\n    ARABIC_SAUDI_ARABIA        = 0X0401\n    ARABIC_IRAQ                = 0X0801\n    ARABIC_EGYPT               = 0X0C01\n    ARABIC_LIBYA               = 0X1001\n    ARABIC_ALGERIA             = 0X1401\n    ARABIC_MOROCCO             = 0X1801\n    ARABIC_TUNISIA             = 0X1C01\n    ARABIC_OMAN                = 0X2001\n    ARABIC_YEMEN               = 0X2401\n    ARABIC_SYRIA               = 0X2801\n    ARABIC_JORDAN              = 0X2C01\n    ARABIC_LEBANON             = 0X3001\n    ARABIC_KUWAIT              = 0X3401\n    ARABIC_UAE                 = 0X3801\n    ARABIC_BAHRAIN             = 0X3C01\n    ARABIC_QATAR               = 0X4001\n    ARMENIAN                   = 0X042B\n    ASSAMESE                   = 0X044D\n    AZERI_LATIN                = 0X042C\n    AZERI_CYRILLIC             = 0X082C\n    BASQUE                     = 0X042D\n    BELARUSSIAN                = 0X0423\n    BENGALI                    = 0X0445\n    BULGARIAN                  = 0X0402\n    BURMESE                    = 0X0455\n    CATALAN                    = 0X0403\n    CHINESE_TAIWAN             = 0X0404\n    CHINESE_PRC                = 0X0804\n    CHINESE_HONG_KONG          = 0X0C04\n    CHINESE_SINGAPORE          = 0X1004\n    CHINESE_MACAU_SAR          = 0X1404\n    CROATIAN                   = 0X041A\n    CZECH                      = 0X0405\n    DANISH                     = 0X0406\n    DUTCH_NETHERLANDS          = 0X0413\n    DUTCH_BELGIUM              = 0X0813\n    ENGLISH_US                 = 0X0409\n    ENGLISH_UNITED_KINGDOM     = 0X0809\n    ENGLISH_AUSTRALIAN         = 0X0C09\n    ENGLISH_CANADIAN           = 0X1009\n    ENGLISH_NEW_ZEALAND        = 0X1409\n    ENGLISH_IRELAND            = 0X1809\n    ENGLISH_SOUTH_AFRICA       = 0X1C09\n    ENGLISH_JAMAICA            = 0X2009\n    ENGLISH_CARIBBEAN          = 0X2409\n    ENGLISH_BELIZE             = 0X2809\n    ENGLISH_TRINIDAD           = 0X2C09\n    ENGLISH_ZIMBABWE           = 0X3009\n    ENGLISH_PHILIPPINES        = 0X3409\n    ESTONIAN                   = 0X0425\n    FAEROESE                   = 0X0438\n    FARSI                      = 0X0429\n    FINNISH                    = 0X040B\n    FRENCH_STANDARD            = 0X040C\n    FRENCH_BELGIAN             = 0X080C\n    FRENCH_CANADIAN            = 0X0C0C\n    FRENCH_SWITZERLAND         = 0X100C\n    FRENCH_LUXEMBOURG          = 0X140C\n    FRENCH_MONACO              = 0X180C\n    GEORGIAN                   = 0X0437\n    GERMAN_STANDARD            = 0X0407\n    GERMAN_SWITZERLAND         = 0X0807\n    GERMAN_AUSTRIA             = 0X0C07\n    GERMAN_LUXEMBOURG          = 0X1007\n    GERMAN_LIECHTENSTEIN       = 0X1407\n    GREEK                      = 0X0408\n    GUJARATI                   = 0X0447\n    HEBREW                     = 0X040D\n    HINDI                      = 0X0439\n    HUNGARIAN                  = 0X040E\n    ICELANDIC                  = 0X040F\n    INDONESIAN                 = 0X0421\n    ITALIAN_STANDARD           = 0X0410\n    ITALIAN_SWITZERLAND        = 0X0810\n    JAPANESE                   = 0X0411\n    KANNADA                    = 0X044B\n    KASHMIRI_INDIA             = 0X0860\n    KAZAKH                     = 0X043F\n    KONKANI                    = 0X0457\n    KOREAN                     = 0X0412\n    KOREAN_JOHAB               = 0X0812\n    LATVIAN                    = 0X0426\n    LITHUANIAN                 = 0X0427\n    LITHUANIAN_CLASSIC         = 0X0827\n    MACEDONIAN                 = 0X042F\n    MALAY_MALAYSIAN            = 0X043E\n    MALAY_BRUNEI_DARUSSALAM    = 0X083E\n    MALAYALAM                  = 0X044C\n    MANIPURI                   = 0X0458\n    MARATHI                    = 0X044E\n    NEPALI_INDIA               = 0X0861\n    NORWEGIAN_BOKMAL           = 0X0414\n    NORWEGIAN_NYNORSK          = 0X0814\n    ORIYA                      = 0X0448\n    POLISH                     = 0X0415\n    PORTUGUESE_BRAZIL          = 0X0416\n    PORTUGUESE_STANDARD        = 0X0816\n    PUNJABI                    = 0X0446\n    ROMANIAN                   = 0X0418\n    RUSSIAN                    = 0X0419\n    SANSKRIT                   = 0X044F\n    SERBIAN_CYRILLIC           = 0X0C1A\n    SERBIAN_LATIN              = 0X081A\n    SINDHI                     = 0X0459\n    SLOVAK                     = 0X041B\n    SLOVENIAN                  = 0X0424\n    SPANISH_TRADITIONAL_SORT   = 0X040A\n    SPANISH_MEXICAN            = 0X080A\n    SPANISH_MODERN_SORT        = 0X0C0A\n    SPANISH_GUATEMALA          = 0X100A\n    SPANISH_COSTA_RICA         = 0X140A\n    SPANISH_PANAMA             = 0X180A\n    SPANISH_DOMINICAN_REPUBLIC = 0X1C0A\n    SPANISH_VENEZUELA          = 0X200A\n    SPANISH_COLOMBIA           = 0X240A\n    SPANISH_PERU               = 0X280A\n    SPANISH_ARGENTINA          = 0X2C0A\n    SPANISH_ECUADOR            = 0X300A\n    SPANISH_CHILE              = 0X340A\n    SPANISH_URUGUAY            = 0X380A\n    SPANISH_PARAGUAY           = 0X3C0A\n    SPANISH_BOLIVIA            = 0X400A\n    SPANISH_EL_SALVADOR        = 0X440A\n    SPANISH_HONDURAS           = 0X480A\n    SPANISH_NICARAGUA          = 0X4C0A\n    SPANISH_PUERTO_RICO        = 0X500A\n    SUTU                       = 0X0430\n    SWAHILI_KENYA              = 0X0441\n    SWEDISH                    = 0X041D\n    SWEDISH_FINLAND            = 0X081D\n    TAMIL                      = 0X0449\n    TATAR_TATARSTAN            = 0X0444\n    TELUGU                     = 0X044A\n    THAI                       = 0X041E\n    TURKISH                    = 0X041F\n    UKRAINIAN                  = 0X0422\n    URDU_PAKISTAN              = 0X0420\n    URDU_INDIA                 = 0X0820\n    UZBEK_LATIN                = 0X0443\n    UZBEK_CYRILLIC             = 0X0843\n    VIETNAMESE                 = 0X042A\n    HID_USAGE_DATA_DESCRIPTOR  = 0X04FF\n    HID_VENDOR_DEFINED_1       = 0XF0FF\n    HID_VENDOR_DEFINED_2       = 0XF4FF\n    HID_VENDOR_DEFINED_3       = 0XF8FF\n    HID_VENDOR_DEFINED_4       = 0XFCFF\n\n\nclass DescriptorTypes(IntEnum):\n    DEVICE                    = 1\n    CONFIGURATION             = 2\n    STRING                    = 3\n    INTERFACE                 = 4\n    ENDPOINT                  = 5\n    DEVICE_QUALIFIER          = 6\n    OTHER_SPEED_CONFIGURATION = 7\n    INTERFACE_POWER           = 8\n    HID                       = 33\n    REPORT                    = 34\n\n\nclass USBSynchronizationType(IntEnum):\n    NONE         = 0x00\n    ASYNC        = 0x01\n    ADAPTIVE     = 0x02\n    SYNCHRONOUS  = 0x03\n\n\nclass USBUsageType(IntEnum):\n    DATA              = 0\n    FEEDBACK          = 1\n    IMPLICIT_FEEDBACK = 2\n\n\nclass USBStandardRequests(IntEnum):\n    GET_STATUS        = 0\n    CLEAR_FEATURE     = 1\n    SET_FEATURE       = 3\n    SET_ADDRESS       = 5\n    GET_DESCRIPTOR    = 6\n    SET_DESCRIPTOR    = 7\n    GET_CONFIGURATION = 8\n    SET_CONFIGURATION = 9\n    GET_INTERFACE     = 10\n    SET_INTERFACE     = 11\n    SYNCH_FRAME       = 12\n\n\n# Based on libusb's LIBUSB_SPEED_* constants.\n#\n# See: https://github.com/libusb/libusb/blob/master/libusb/libusb.h#L1126\nclass DeviceSpeed(IntEnum):\n    UNKNOWN = 0\n    LOW = 1\n    FULL = 2\n    HIGH = 3\n    SUPER = 4\n    SUPER_PLUS = 5\n\n\n# Contains definition of USB class, which is just a container for a bunch of\n# constants/enums associated with the USB protocol.\n#\n# TODO: would be nice if this module could re-export the other USB* classes so\n# one need import only USB to get all the functionality\n#\n# TODO: check if it still makes sense to keep this around in facedancer v3, it's\n# only used by USBProxyDevice\n\nclass USB:\n    state_detached                      = 0\n    state_attached                      = 1\n    state_powered                       = 2\n    state_default                       = 3\n    state_address                       = 4\n    state_configured                    = 5\n    state_suspended                     = 6\n\n    request_direction_host_to_device    = 0\n    request_direction_device_to_host    = 1\n\n    request_type_standard               = 0\n    request_type_class                  = 1\n    request_type_vendor                 = 2\n\n    request_recipient_device            = 0\n    request_recipient_interface         = 1\n    request_recipient_endpoint          = 2\n    request_recipient_other             = 3\n\n    feature_endpoint_halt               = 0\n    feature_device_remote_wakeup        = 1\n    feature_test_mode                   = 2\n\n    desc_type_device                    = 1\n    desc_type_configuration             = 2\n    desc_type_string                    = 3\n    desc_type_interface                 = 4\n    desc_type_endpoint                  = 5\n    desc_type_device_qualifier          = 6\n    desc_type_other_speed_configuration = 7\n    desc_type_interface_power           = 8\n    desc_type_hid                       = 33\n    desc_type_report                    = 34\n\n    # while this holds for HID, it may not be a correct model for the USB\n    # ecosystem at large\n    if_class_to_desc_type = {\n            3 : desc_type_hid\n    }\n\n    def interface_class_to_descriptor_type(interface_class):\n        return USB.if_class_to_desc_type.get(interface_class, None)\n"
  },
  {
    "path": "pyproject.toml",
    "content": "[build-system]\nrequires = [\"setuptools>=64\", \"wheel\", \"setuptools-git-versioning<2\"]\nbuild-backend = \"setuptools.build_meta\"\n\n[project]\nname = \"facedancer\"\ndescription = \"Implement your own USB device in Python, supported by a hardware peripheral such as Cynthion or GreatFET.\"\nlicense = { text = \"BSD\" }\nreadme = \"README.md\"\nrequires-python = \">=3.10\"\nauthors = [\n    {name = \"Great Scott Gadgets\", email = \"dev@greatscottgadgets.com\"},\n]\n\nclassifiers = [\n    \"Development Status :: 5 - Production/Stable\",\n    \"Programming Language :: Python\",\n    \"Programming Language :: Python :: 3\",\n    \"License :: OSI Approved :: BSD License\",\n    \"Operating System :: OS Independent\",\n    \"Natural Language :: English\",\n    \"Environment :: Console\",\n    \"Environment :: Plugins\",\n    \"Intended Audience :: Developers\",\n    \"Intended Audience :: Science/Research\",\n    \"Topic :: Scientific/Engineering\",\n    \"Topic :: Security\",\n    \"Topic :: System :: Hardware :: Universal Serial Bus (USB)\",\n]\n\ndependencies = [\n    \"pyusb\",\n    \"pyserial\",\n    \"prompt-toolkit\",\n    \"libusb1\",\n    \"hid-parser>=0.1.0\",\n]\n\ndynamic = [\"version\"]\n\n[project.urls]\nDocumentation = \"https://facedancer.readthedocs.io\"\nRepository    = \"https://github.com/greatscottgadgets/facedancer\"\nIssues        = \"https://github.com/greatscottgadgets/facedancer/issues\"\n\n[tool.setuptools.package-dir]\nfacedancer = \"facedancer\"\n\n[tool.setuptools-git-versioning]\nenabled = true\nstarting_version = \"3.0.0\"\n"
  },
  {
    "path": "test/README.md",
    "content": "## Running tests\n\n1. Connect your Facedancer hardware\n\n2. In one terminal, start the test device from the repository root:\n\n    python -m test.device\n\n3. In another terminal, run the tests from the repository root:\n\n    python -m unittest\n"
  },
  {
    "path": "test/__init__.py",
    "content": ""
  },
  {
    "path": "test/base.py",
    "content": "#\n# This file is part of Facedancer.\n#\n\nimport logging\nimport unittest\nimport usb1\n\nfrom facedancer.errors import DeviceNotFoundError\nfrom facedancer.types import USBStandardRequests\n\n\nVENDOR_ID  = 0x1209\nPRODUCT_ID = 0x0001\n\nOUT_ENDPOINT = 0x01\nIN_ENDPOINT  = 0x82\n\nOUT_ALT_ENDPOINT = 0x03\nIN_ALT_ENDPOINT  = 0x84\n\n# This is constrained by pygreat::comms_backends::usb1::LIBGREAT_MAX_COMMAND_SIZE\n# and is board dependent.\nMAX_TRANSFER_LENGTH = 768\n\n\nclass FacedancerTestCase(unittest.TestCase):\n\n    # - life-cycle ------------------------------------------------------------\n\n    @classmethod\n    def setUpClass(cls):\n        logging.basicConfig(level=logging.INFO)\n        cls.context = usb1.USBContext().open()\n        cls.device_handle = cls.context.openByVendorIDAndProductID(VENDOR_ID, PRODUCT_ID)\n        if cls.device_handle is None:\n            raise Exception(\"device not found\")\n        cls.device_handle.claimInterface(0)\n\n\n    @classmethod\n    def tearDownClass(cls):\n        cls.context.close()\n\n\n\n    # - transfers -------------------------------------------------------------\n\n    def bulk_out_transfer(self, ep, data):\n        logging.debug(\"Testing bulk OUT endpoint\")\n        response = self.device_handle.bulkWrite(\n            endpoint = ep,\n            data     = data,\n            timeout  = 1000,\n        )\n        logging.debug(f\"sent {response} bytes\\n\")\n        return response\n\n    def bulk_in_transfer(self, ep, length):\n        logging.debug(\"Testing bulk IN endpoint\")\n        response = self.device_handle.bulkRead(\n            endpoint = ep,\n            length   = length,\n            timeout  = 1000,\n        )\n        logging.debug(f\"[host] received '{len(response)}' bytes from bulk endpoint\")\n        return response\n\n    def control_out_transfer(self, data):\n        logging.debug(\"Testing OUT control transfer\")\n        hi, lo = len(data).to_bytes(2, byteorder=\"big\")\n        response = self.device_handle.controlWrite(\n            request_type = usb1.TYPE_VENDOR | usb1.RECIPIENT_DEVICE,\n            request      = 10,\n            index        = hi,\n            value        = lo,\n            data         = data,\n            timeout      = 1000,\n        )\n        logging.debug(f\"sent {response} bytes\\n\")\n        return response\n\n    def control_in_transfer(self, length):\n        logging.debug(\"Testing IN control transfer\")\n        hi, lo = length.to_bytes(2, byteorder=\"big\")\n        response = self.device_handle.controlRead(\n            request_type = usb1.TYPE_VENDOR | usb1.RECIPIENT_DEVICE,\n            request      = 20,\n            index        = hi,\n            value        = lo,\n            length       = length,\n            timeout      = 1000,\n        )\n        logging.debug(f\"[host] received '{len(response)}' bytes from control endpoint\")\n        return response\n\n    # - device control ------------------------------------------------------------\n\n    def set_in_transfer_length(self, length):\n        hi, lo = length.to_bytes(2, byteorder=\"big\")\n        logging.debug(f\"Setting transfer length to {length} bytes\")\n        response = self.device_handle.controlWrite(\n            request_type = usb1.TYPE_VENDOR | usb1.RECIPIENT_DEVICE,\n            request      = 1,\n            index        = hi,\n            value        = lo,\n            data         = [],\n            timeout      = 1000,\n        )\n        return response\n\n    def get_last_out_transfer_data(self):\n        logging.debug(\"Getting last OUT transfer data\")\n        response = self.device_handle.controlRead(\n            request_type = usb1.TYPE_VENDOR | usb1.RECIPIENT_DEVICE,\n            request      = 2,\n            index        = 0,\n            value        = 0,\n            length       = MAX_TRANSFER_LENGTH,\n            timeout      = 1000,\n        )\n        logging.debug(f\"[host] sent '{len(response)}' bytes with last out transfer\")\n        return response\n\n    def reset_device_state(self):\n        logging.debug(f\"Resetting stress test device state\")\n        response = self.device_handle.controlWrite(\n            request_type = usb1.TYPE_VENDOR | usb1.RECIPIENT_DEVICE,\n            request      = 3,\n            index        = 0,\n            value        = 0,\n            data         = [],\n            timeout      = 1000,\n        )\n        return response\n\n    def set_interface(self, interface_number, alternate):\n        logging.debug(\"Setting interface {interface_number} to alternate setting {alternate}\")\n        self.device_handle.setInterfaceAltSetting(interface_number, alternate)\n\n    def get_interface(self, interface_number):\n        logging.debug(\"Getting alternate setting of interface {interface}\")\n        response = self.device_handle.controlRead(\n            request_type = usb1.TYPE_STANDARD | usb1.RECIPIENT_INTERFACE,\n            request      = USBStandardRequests.GET_INTERFACE,\n            index        = interface_number,\n            value        = 0,\n            length       = 1,\n            timeout      = 1000,\n        )\n        return response[0]\n\n"
  },
  {
    "path": "test/device.py",
    "content": "#!/usr/bin/env python3\n# pylint: disable=unused-wildcard-import, wildcard-import\n#\n# This file is part of Facedancer.\n#\n\nimport logging, random, sys\n\nfrom facedancer import *\nfrom facedancer import main\n\nfrom .base import VENDOR_ID, PRODUCT_ID, OUT_ENDPOINT, IN_ENDPOINT, OUT_ALT_ENDPOINT, IN_ALT_ENDPOINT, MAX_TRANSFER_LENGTH\n\n\nDEVICE_SPEED = DeviceSpeed.HIGH\n\n\n@use_inner_classes_automatically\nclass StressTestDevice(USBDevice):\n    product_string      : str = \"Stress Test Device\"\n    manufacturer_string : str = \"Facedancer\"\n    vendor_id           : int = VENDOR_ID\n    product_id          : int = PRODUCT_ID\n    device_speed        : DeviceSpeed = DEVICE_SPEED\n\n    def __post_init__(self):\n        super().__post_init__()\n        self.in_transfer_length = 0\n        self.last_out_transfer_data = bytearray()\n\n    class MyConfiguration(USBConfiguration):\n\n        class MyInterface(USBInterface):\n            number    : int = 0\n            alternate : int = 0\n\n            class MyOutEndpoint(USBEndpoint):\n                number          : int          = OUT_ENDPOINT\n                direction       : USBDirection = USBDirection.OUT\n                max_packet_size : int          = 512 if DEVICE_SPEED == DeviceSpeed.HIGH else 64\n\n                def handle_data_received(self: USBEndpoint, data):\n                    self.get_device().last_out_transfer_data += bytes(data)\n                    logging.debug(f\"test_bulk_out received {len(data)} bytes\")\n\n            class MyInEndpoint(USBEndpoint):\n                number          : int          = IN_ENDPOINT & 0x7f\n                direction       : USBDirection = USBDirection.IN\n                max_packet_size : int          = 512 if DEVICE_SPEED == DeviceSpeed.HIGH else 64\n\n                def handle_data_requested(self: USBEndpoint):\n                    in_transfer_length = self.get_device().in_transfer_length\n                    logging.debug(f\"test_bulk_in sending {in_transfer_length} bytes\")\n                    self.send(generate_data(in_transfer_length), blocking=False)\n\n        class MyAlternateInterface(USBInterface):\n            number    : int = 0\n            alternate : int = 1\n\n            class MyOutEndpoint(USBEndpoint):\n                number          : int          = OUT_ALT_ENDPOINT\n                direction       : USBDirection = USBDirection.OUT\n                max_packet_size : int          = 512 if DEVICE_SPEED == DeviceSpeed.HIGH else 64\n\n                def handle_data_received(self: USBEndpoint, data):\n                    self.get_device().last_out_transfer_data += bytes(data)\n                    logging.debug(f\"test_bulk_out alternate received {len(data)} bytes\")\n\n            class MyInEndpoint(USBEndpoint):\n                number          : int          = IN_ALT_ENDPOINT & 0x7f\n                direction       : USBDirection = USBDirection.IN\n                max_packet_size : int          = 512 if DEVICE_SPEED == DeviceSpeed.HIGH else 64\n\n                def handle_data_requested(self: USBEndpoint):\n                    in_transfer_length = self.get_device().in_transfer_length\n                    logging.debug(f\"test_bulk_in alternate sending {in_transfer_length} bytes\")\n                    self.send(generate_data(in_transfer_length), blocking=False)\n\n    @vendor_request_handler(number=10, direction=USBDirection.OUT)\n    @to_device\n    def out_vendor_request(self: USBDevice, request: USBControlRequest):\n        self.last_out_transfer_data += bytes(request.data)\n        length = int.from_bytes([request.index, request.value], byteorder=\"big\")\n        logging.debug(f\"test_control_out received {len(request.data)}/{length} bytes\")\n        request.ack()\n\n    @vendor_request_handler(number=20, direction=USBDirection.IN)\n    @to_device\n    def in_vendor_request(self: USBDevice, request: USBControlRequest):\n        length = int.from_bytes([request.index, request.value], byteorder=\"big\")\n        logging.debug(f\"test_control_in sending {length} bytes\")\n        request.reply(generate_data(length))\n\n\n    # - device control --------------------------------------------------------\n\n    @vendor_request_handler(number=1, direction=USBDirection.OUT)\n    @to_device\n    def set_in_transfer_length(self: USBDevice, request: USBControlRequest):\n        length = int.from_bytes([request.index, request.value], byteorder=\"big\")\n        self.in_transfer_length = length\n        logging.debug(f\"set_in_transfer_length: {length} bytes\")\n        request.ack()\n\n    @vendor_request_handler(number=2, direction=USBDirection.IN)\n    @to_device\n    def get_last_out_transfer_data(self: USBDevice, request: USBControlRequest):\n        logging.debug(f\"get_last_out_transfer_data: {len(self.last_out_transfer_data)} bytes\")\n        request.reply(self.last_out_transfer_data)\n        self.last_out_transfer_data = bytearray()\n\n    @vendor_request_handler(number=3, direction=USBDirection.OUT)\n    @to_device\n    def reset_device_state(self: USBDevice, request: USBControlRequest):\n        logging.debug(f\"reset_device_state: {len(self.last_out_transfer_data)} bytes\")\n        self.in_transfer_length = 0\n        self.last_out_transfer_data = bytearray()\n        request.ack()\n\n\n# - helpers -------------------------------------------------------------------\n\ndef generate_data(length):\n    return bytes([(byte % 256) for byte in range(length)])\n\n\nif __name__ == \"__main__\":\n    main(StressTestDevice)\n"
  },
  {
    "path": "test/test_alternate.py",
    "content": "#\n# This file is part of Facedancer.\n#\n\nimport asyncio, logging, random, sys, time\nimport unittest\nimport usb1\n\nfrom .base   import FacedancerTestCase\nfrom .base   import VENDOR_ID, PRODUCT_ID, MAX_TRANSFER_LENGTH, OUT_ENDPOINT, IN_ENDPOINT, OUT_ALT_ENDPOINT, IN_ALT_ENDPOINT\nfrom .device import generate_data\n\n\nclass TestAlternate(FacedancerTestCase):\n    \"\"\"Test alternate interface settings\"\"\"\n\n\n    def setUp(self):\n        # reset test device state between tests\n        self.reset_device_state()\n\n\n    def test_alternate_interfaces(self):\n        endpoints = {\n            0: (OUT_ENDPOINT, IN_ENDPOINT),\n            1: (OUT_ALT_ENDPOINT, IN_ALT_ENDPOINT),\n        }\n\n        for alt in (0, 1):\n            self.set_interface(0, alt)\n            assert(self.get_interface(0) == alt)\n\n            out_ep, in_ep = endpoints[alt]\n\n            # generate test data\n            length = 678\n            data = generate_data(length)\n\n            # set desired IN transfer length\n            self.set_in_transfer_length(length)\n\n            # perform Bulk IN transfer\n            received_data = self.bulk_in_transfer(in_ep, length)\n\n            # generate a set of data to compare against\n            compare_data = generate_data(length)\n\n            # did we receive the right amount of data?\n            self.assertEqual(len(received_data), length)\n\n            # does the content of the received data match the content of our comparison data?\n            self.assertEqual(received_data, compare_data)\n\n            # perform Bulk OUT transfer\n            bytes_sent = self.bulk_out_transfer(out_ep, data)\n\n            # request a copy of the received data to compare against\n            received_data = self.get_last_out_transfer_data()\n\n            # did we send the right amount of data?\n            self.assertEqual(bytes_sent, length)\n\n            # does the length of the sent data match the length of the received data?\n            self.assertEqual(len(data), len(received_data))\n\n            # does the content of the sent data match the content of the received data?\n            self.assertEqual(data, received_data)\n\n\nif __name__ == \"__main__\":\n    unittest.main(verbosity=1)\n"
  },
  {
    "path": "test/test_descriptors.py",
    "content": "from facedancer import *\nimport unittest\n\n# Test case similar to a game pad seen in the wild.\n\ndevice_data = bytes([\n    0x12, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x40, 0x09,\n    0x12, 0x05, 0x00, 0x00, 0x01, 0x01, 0x02, 0x00, 0x01])\n\nstrings = {\n    1: \"Facedancer Test\",\n    2: \"Gamepad With Audio\",\n    4: \"Interface 1\",\n    6: \"Interface 3\",\n}\n\nconfig_data = bytes([\n    0x09, 0x02, 0xE3, 0x00, 0x04, 0x01, 0x03, 0xC0, 0xFA,\n    0x09, 0x04, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x04,\n    0x0A, 0x24, 0x01, 0x00, 0x01, 0x49, 0x00, 0x02, 0x01, 0x02,\n    0x0C, 0x24, 0x02, 0x01, 0x01, 0x01, 0x06, 0x04, 0x33, 0x00, 0x00, 0x00,\n    0x0C, 0x24, 0x06, 0x02, 0x01, 0x01, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x09, 0x24, 0x03, 0x03, 0x01, 0x03, 0x04, 0x02, 0x00,\n    0x0C, 0x24, 0x02, 0x04, 0x02, 0x04, 0x03, 0x02, 0x03, 0x00, 0x00, 0x00,\n    0x09, 0x24, 0x06, 0x05, 0x04, 0x01, 0x03, 0x00, 0x00,\n    0x09, 0x24, 0x03, 0x06, 0x01, 0x01, 0x01, 0x05, 0x00,\n    0x09, 0x04, 0x01, 0x00, 0x00, 0x01, 0x02, 0x00, 0x00,\n    0x09, 0x04, 0x01, 0x01, 0x01, 0x01, 0x02, 0x00, 0x00,\n    0x07, 0x24, 0x01, 0x01, 0x01, 0x01, 0x00,\n    0x0B, 0x24, 0x02, 0x01, 0x04, 0x02, 0x10, 0x01, 0x80, 0xBB, 0x00,\n    0x09, 0x05, 0x01, 0x09, 0x88, 0x01, 0x04, 0x00, 0x00,\n    0x07, 0x25, 0x01, 0x00, 0x00, 0x00, 0x00,\n    0x09, 0x04, 0x02, 0x00, 0x00, 0x01, 0x02, 0x00, 0x00,\n    0x09, 0x04, 0x02, 0x01, 0x01, 0x01, 0x02, 0x00, 0x00,\n    0x07, 0x24, 0x01, 0x06, 0x01, 0x01, 0x00,\n    0x0B, 0x24, 0x02, 0x01, 0x02, 0x02, 0x10, 0x01, 0x80, 0xBB, 0x00,\n    0x09, 0x05, 0x82, 0x05, 0xC4, 0x00, 0x04, 0x00, 0x00,\n    0x07, 0x25, 0x01, 0x00, 0x00, 0x00, 0x00,\n    0x09, 0x04, 0x03, 0x00, 0x02, 0x03, 0x00, 0x00, 0x06,\n    0x09, 0x21, 0x11, 0x01, 0x00, 0x01, 0x22, 0x11, 0x01,\n    0x07, 0x05, 0x84, 0x03, 0x40, 0x00, 0x06,\n    0x07, 0x05, 0x03, 0x03, 0x40, 0x00, 0x06,\n])\n\nreport_data = bytes([\n    0x05, 0x01, 0x09, 0x05, 0xA1, 0x01, 0x85, 0x01, 0x09, 0x30, 0x09, 0x31,\n    0x09, 0x32, 0x09, 0x35, 0x09, 0x33, 0x09, 0x34, 0x15, 0x00, 0x26, 0xFF,\n    0x00, 0x75, 0x08, 0x95, 0x06, 0x81, 0x02, 0x06, 0x00, 0xFF, 0x09, 0x20,\n    0x95, 0x01, 0x81, 0x02, 0x05, 0x01, 0x09, 0x39, 0x15, 0x00, 0x25, 0x07,\n    0x35, 0x00, 0x46, 0x3B, 0x01, 0x65, 0x14, 0x75, 0x04, 0x95, 0x01, 0x81,\n    0x42, 0x65, 0x00, 0x05, 0x09, 0x19, 0x01, 0x29, 0x0F, 0x15, 0x00, 0x25,\n    0x01, 0x75, 0x01, 0x95, 0x0F, 0x81, 0x02, 0x06, 0x00, 0xFF, 0x09, 0x21,\n    0x95, 0x0D, 0x81, 0x02, 0x06, 0x00, 0xFF, 0x09, 0x22, 0x15, 0x00, 0x26,\n    0xFF, 0x00, 0x75, 0x08, 0x95, 0x34, 0x81, 0x02, 0x85, 0x02, 0x09, 0x23,\n    0x95, 0x2F, 0x91, 0x02, 0x85, 0x05, 0x09, 0x33, 0x95, 0x28, 0xB1, 0x02,\n    0x85, 0x08, 0x09, 0x34, 0x95, 0x2F, 0xB1, 0x02, 0x85, 0x09, 0x09, 0x24,\n    0x95, 0x13, 0xB1, 0x02, 0x85, 0x0A, 0x09, 0x25, 0x95, 0x1A, 0xB1, 0x02,\n    0x85, 0x20, 0x09, 0x26, 0x95, 0x3F, 0xB1, 0x02, 0x85, 0x21, 0x09, 0x27,\n    0x95, 0x04, 0xB1, 0x02, 0x85, 0x22, 0x09, 0x40, 0x95, 0x3F, 0xB1, 0x02,\n    0x85, 0x80, 0x09, 0x28, 0x95, 0x3F, 0xB1, 0x02, 0x85, 0x81, 0x09, 0x29,\n    0x95, 0x3F, 0xB1, 0x02, 0x85, 0x82, 0x09, 0x2A, 0x95, 0x09, 0xB1, 0x02,\n    0x85, 0x83, 0x09, 0x2B, 0x95, 0x3F, 0xB1, 0x02, 0x85, 0x84, 0x09, 0x2C,\n    0x95, 0x3F, 0xB1, 0x02, 0x85, 0x85, 0x09, 0x2D, 0x95, 0x02, 0xB1, 0x02,\n    0x85, 0xA0, 0x09, 0x2E, 0x95, 0x01, 0xB1, 0x02, 0x85, 0xE0, 0x09, 0x2F,\n    0x95, 0x3F, 0xB1, 0x02, 0x85, 0xF0, 0x09, 0x30, 0x95, 0x3F, 0xB1, 0x02,\n    0x85, 0xF1, 0x09, 0x31, 0x95, 0x3F, 0xB1, 0x02, 0x85, 0xF2, 0x09, 0x32,\n    0x95, 0x0F, 0xB1, 0x02, 0x85, 0xF4, 0x09, 0x35, 0x95, 0x3F, 0xB1, 0x02,\n    0x85, 0xF5, 0x09, 0x36, 0x95, 0x03, 0xB1, 0x02, 0xC0\n])\n\nclass TestDescriptors(unittest.TestCase):\n\n    def test_device_descriptor_reconstruction(self):\n        device = USBDevice.from_binary_descriptor(device_data)\n\n        assert(device.get_descriptor() == device_data)\n\n    def test_config_descriptor_reconstruction(self):\n        configuration = USBConfiguration.from_binary_descriptor(config_data)\n\n        device = USBDevice()\n        device.add_configuration(configuration)\n\n        assert(configuration.get_descriptor() == config_data)\n\n    def test_code_generation(self):\n        # Construct a device from binary descriptors.\n        device = USBDevice.from_binary_descriptor(device_data, strings)\n        configuration = USBConfiguration.from_binary_descriptor(config_data, strings)\n        device.add_configuration(configuration)\n        report_desc = USBDescriptor(type_number=0x22, number=0, raw=report_data)\n        hid_interface = configuration.interfaces[(3, 0)]\n        hid_interface.add_descriptor(report_desc)\n\n        # Generate code and check it matches expected output.\n        code = device.generate_code()\n        self.maxDiff = None\n        self.assertEqual(expected_code, code)\n\n        # Run that code and check that it generates the matching descriptors.\n        exec(code, globals())\n        new_device = Device()\n        self.assertEqual(new_device.get_descriptor(), device_data)\n        new_config = new_device.configurations[1]\n        self.assertEqual(new_config.get_descriptor(), config_data)\n        new_hid_interface = new_config.interfaces[(3, 0)]\n        new_report_desc = new_hid_interface.requestable_descriptors[(0x22, 0)]\n        self.assertEqual(new_report_desc.raw, report_data)\n\n        # Check that it also produces the same code again.\n        new_code = new_device.generate_code()\n        self.assertEqual(expected_code, new_code)\n\nexpected_code = \"\"\"\n@use_inner_classes_automatically\nclass Device(USBDevice):\n    device_speed             = None\n    device_class             = 0\n    device_subclass          = 0\n    protocol_revision_number = 0\n    max_packet_size_ep0      = 64\n    vendor_id                = 0x1209\n    product_id               = 0x0005\n    manufacturer_string      = (1, 'Facedancer Test')\n    product_string           = (2, 'Gamepad With Audio')\n    serial_number_string     = None\n    supported_languages      = (LanguageIDs.ENGLISH_US,)\n    device_revision          = 0x0100\n    usb_spec_version         = 0x0200\n\n    class Configuration_1(USBConfiguration):\n        number                 = 1\n        configuration_string   = 3\n        max_power              = 500\n        self_powered           = True\n        supports_remote_wakeup = False\n\n        class Interface_0(USBInterface):\n            number           = 0\n            alternate        = 0\n            class_number     = 1\n            subclass_number  = 1\n            protocol_number  = 0\n            interface_string = (4, 'Interface 1')\n\n            @include_in_config\n            class Descriptor_0x24_A(USBDescriptor):\n                raw = bytes([\n                    0x0A, 0x24, 0x01, 0x00, 0x01,\n                    0x49, 0x00, 0x02, 0x01, 0x02])\n\n            @include_in_config\n            class Descriptor_0x24_B(USBDescriptor):\n                raw = bytes([\n                    0x0C, 0x24, 0x02, 0x01, 0x01, 0x01,\n                    0x06, 0x04, 0x33, 0x00, 0x00, 0x00])\n\n            @include_in_config\n            class Descriptor_0x24_C(USBDescriptor):\n                raw = bytes([\n                    0x0C, 0x24, 0x06, 0x02, 0x01, 0x01,\n                    0x03, 0x00, 0x00, 0x00, 0x00, 0x00])\n\n            @include_in_config\n            class Descriptor_0x24_D(USBDescriptor):\n                raw = bytes([\n                    0x09, 0x24, 0x03, 0x03, 0x01,\n                    0x03, 0x04, 0x02, 0x00])\n\n            @include_in_config\n            class Descriptor_0x24_E(USBDescriptor):\n                raw = bytes([\n                    0x0C, 0x24, 0x02, 0x04, 0x02, 0x04,\n                    0x03, 0x02, 0x03, 0x00, 0x00, 0x00])\n\n            @include_in_config\n            class Descriptor_0x24_F(USBDescriptor):\n                raw = bytes([\n                    0x09, 0x24, 0x06, 0x05, 0x04,\n                    0x01, 0x03, 0x00, 0x00])\n\n            @include_in_config\n            class Descriptor_0x24_G(USBDescriptor):\n                raw = bytes([\n                    0x09, 0x24, 0x03, 0x06, 0x01,\n                    0x01, 0x01, 0x05, 0x00])\n\n        class Interface_1(USBInterface):\n            number           = 1\n            alternate        = 0\n            class_number     = 1\n            subclass_number  = 2\n            protocol_number  = 0\n            interface_string = None\n\n        class Interface_1_1(USBInterface):\n            number           = 1\n            alternate        = 1\n            class_number     = 1\n            subclass_number  = 2\n            protocol_number  = 0\n            interface_string = None\n\n            @include_in_config\n            class Descriptor_0x24_A(USBDescriptor):\n                raw = bytes([\n                    0x07, 0x24, 0x01, 0x01, 0x01, 0x01, 0x00])\n\n            @include_in_config\n            class Descriptor_0x24_B(USBDescriptor):\n                raw = bytes([\n                    0x0B, 0x24, 0x02, 0x01, 0x04, 0x02,\n                    0x10, 0x01, 0x80, 0xBB, 0x00])\n\n            class Endpoint_1_OUT(USBEndpoint):\n                number               = 1\n                direction            = USBDirection.OUT\n                transfer_type        = USBTransferType.ISOCHRONOUS\n                synchronization_type = USBSynchronizationType.ADAPTIVE\n                usage_type           = USBUsageType.DATA\n                max_packet_size      = 392\n                interval             = 4\n                extra_bytes          = bytes([0x00, 0x00])\n\n                @include_in_config\n                class Descriptor_0x25_A(USBDescriptor):\n                    raw = bytes([\n                        0x07, 0x25, 0x01, 0x00, 0x00, 0x00, 0x00])\n\n        class Interface_2(USBInterface):\n            number           = 2\n            alternate        = 0\n            class_number     = 1\n            subclass_number  = 2\n            protocol_number  = 0\n            interface_string = None\n\n        class Interface_2_1(USBInterface):\n            number           = 2\n            alternate        = 1\n            class_number     = 1\n            subclass_number  = 2\n            protocol_number  = 0\n            interface_string = None\n\n            @include_in_config\n            class Descriptor_0x24_A(USBDescriptor):\n                raw = bytes([\n                    0x07, 0x24, 0x01, 0x06, 0x01, 0x01, 0x00])\n\n            @include_in_config\n            class Descriptor_0x24_B(USBDescriptor):\n                raw = bytes([\n                    0x0B, 0x24, 0x02, 0x01, 0x02, 0x02,\n                    0x10, 0x01, 0x80, 0xBB, 0x00])\n\n            class Endpoint_2_IN(USBEndpoint):\n                number               = 2\n                direction            = USBDirection.IN\n                transfer_type        = USBTransferType.ISOCHRONOUS\n                synchronization_type = USBSynchronizationType.ASYNC\n                usage_type           = USBUsageType.DATA\n                max_packet_size      = 196\n                interval             = 4\n                extra_bytes          = bytes([0x00, 0x00])\n\n                @include_in_config\n                class Descriptor_0x25_A(USBDescriptor):\n                    raw = bytes([\n                        0x07, 0x25, 0x01, 0x00, 0x00, 0x00, 0x00])\n\n        class Interface_3(USBInterface):\n            number           = 3\n            alternate        = 0\n            class_number     = 3\n            subclass_number  = 0\n            protocol_number  = 0\n            interface_string = (6, 'Interface 3')\n\n            @include_in_config\n            class Descriptor_0x21_A(USBDescriptor):\n                raw = bytes([\n                    0x09, 0x21, 0x11, 0x01, 0x00,\n                    0x01, 0x22, 0x11, 0x01])\n\n            class Endpoint_4_IN(USBEndpoint):\n                number               = 4\n                direction            = USBDirection.IN\n                transfer_type        = USBTransferType.INTERRUPT\n                synchronization_type = USBSynchronizationType.NONE\n                usage_type           = USBUsageType.DATA\n                max_packet_size      = 64\n                interval             = 6\n                extra_bytes          = bytes([])\n\n            class Endpoint_3_OUT(USBEndpoint):\n                number               = 3\n                direction            = USBDirection.OUT\n                transfer_type        = USBTransferType.INTERRUPT\n                synchronization_type = USBSynchronizationType.NONE\n                usage_type           = USBUsageType.DATA\n                max_packet_size      = 64\n                interval             = 6\n                extra_bytes          = bytes([])\n\n            @requestable(type_number=0x22, number=0)\n            class Descriptor_0x22_0(USBDescriptor):\n                raw = bytes([\n                    0x05, 0x01, 0x09, 0x05, 0xA1, 0x01, 0x85, 0x01, 0x09, 0x30,\n                    0x09, 0x31, 0x09, 0x32, 0x09, 0x35, 0x09, 0x33, 0x09, 0x34,\n                    0x15, 0x00, 0x26, 0xFF, 0x00, 0x75, 0x08, 0x95, 0x06, 0x81,\n                    0x02, 0x06, 0x00, 0xFF, 0x09, 0x20, 0x95, 0x01, 0x81, 0x02,\n                    0x05, 0x01, 0x09, 0x39, 0x15, 0x00, 0x25, 0x07, 0x35, 0x00,\n                    0x46, 0x3B, 0x01, 0x65, 0x14, 0x75, 0x04, 0x95, 0x01, 0x81,\n                    0x42, 0x65, 0x00, 0x05, 0x09, 0x19, 0x01, 0x29, 0x0F, 0x15,\n                    0x00, 0x25, 0x01, 0x75, 0x01, 0x95, 0x0F, 0x81, 0x02, 0x06,\n                    0x00, 0xFF, 0x09, 0x21, 0x95, 0x0D, 0x81, 0x02, 0x06, 0x00,\n                    0xFF, 0x09, 0x22, 0x15, 0x00, 0x26, 0xFF, 0x00, 0x75, 0x08,\n                    0x95, 0x34, 0x81, 0x02, 0x85, 0x02, 0x09, 0x23, 0x95, 0x2F,\n                    0x91, 0x02, 0x85, 0x05, 0x09, 0x33, 0x95, 0x28, 0xB1, 0x02,\n                    0x85, 0x08, 0x09, 0x34, 0x95, 0x2F, 0xB1, 0x02, 0x85, 0x09,\n                    0x09, 0x24, 0x95, 0x13, 0xB1, 0x02, 0x85, 0x0A, 0x09, 0x25,\n                    0x95, 0x1A, 0xB1, 0x02, 0x85, 0x20, 0x09, 0x26, 0x95, 0x3F,\n                    0xB1, 0x02, 0x85, 0x21, 0x09, 0x27, 0x95, 0x04, 0xB1, 0x02,\n                    0x85, 0x22, 0x09, 0x40, 0x95, 0x3F, 0xB1, 0x02, 0x85, 0x80,\n                    0x09, 0x28, 0x95, 0x3F, 0xB1, 0x02, 0x85, 0x81, 0x09, 0x29,\n                    0x95, 0x3F, 0xB1, 0x02, 0x85, 0x82, 0x09, 0x2A, 0x95, 0x09,\n                    0xB1, 0x02, 0x85, 0x83, 0x09, 0x2B, 0x95, 0x3F, 0xB1, 0x02,\n                    0x85, 0x84, 0x09, 0x2C, 0x95, 0x3F, 0xB1, 0x02, 0x85, 0x85,\n                    0x09, 0x2D, 0x95, 0x02, 0xB1, 0x02, 0x85, 0xA0, 0x09, 0x2E,\n                    0x95, 0x01, 0xB1, 0x02, 0x85, 0xE0, 0x09, 0x2F, 0x95, 0x3F,\n                    0xB1, 0x02, 0x85, 0xF0, 0x09, 0x30, 0x95, 0x3F, 0xB1, 0x02,\n                    0x85, 0xF1, 0x09, 0x31, 0x95, 0x3F, 0xB1, 0x02, 0x85, 0xF2,\n                    0x09, 0x32, 0x95, 0x0F, 0xB1, 0x02, 0x85, 0xF4, 0x09, 0x35,\n                    0x95, 0x3F, 0xB1, 0x02, 0x85, 0xF5, 0x09, 0x36, 0x95, 0x03,\n                    0xB1, 0x02, 0xC0])\n\"\"\"\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "test/test_stress.py",
    "content": "#\n# This file is part of Facedancer.\n#\n\nimport asyncio, logging, random, sys, time\nimport unittest\nimport usb1\n\nfrom .base   import FacedancerTestCase\nfrom .base   import VENDOR_ID, PRODUCT_ID, MAX_TRANSFER_LENGTH, OUT_ENDPOINT, IN_ENDPOINT\nfrom .device import generate_data\n\n# How many iterations to run for stress test\nITERATIONS = 100\n\n# Transfer length for tests\ndef test_transfer_length():\n    return random.randrange(1, MAX_TRANSFER_LENGTH)\n\n\nclass TestStress(FacedancerTestCase):\n    \"\"\"Stress tests for test device\"\"\"\n\n    # - life-cycle ------------------------------------------------------------\n\n    def setUp(self):\n        # select first interface\n        self.set_interface(0, 0)\n\n        # reset test device state between tests\n        self.reset_device_state()\n\n    def test_stress_test(self):\n        def bulk_out_transfer(self, length):\n            bytes_sent = self.bulk_out_transfer(OUT_ENDPOINT, generate_data(length))\n            self.assertEqual(bytes_sent, length)\n        def bulk_in_transfer(self, length):\n            received = self.bulk_in_transfer(IN_ENDPOINT, length)\n            self.assertEqual(len(received), length)\n        def control_out_transfer(self, length):\n            bytes_sent = self.control_out_transfer(generate_data(length))\n            self.assertEqual(bytes_sent, length)\n        def control_in_transfer(self, length):\n            received = self.control_in_transfer(length)\n            self.assertEqual(len(received), length)\n\n        available_tests = [\n            bulk_out_transfer,\n            bulk_in_transfer,\n            control_out_transfer,\n            control_in_transfer,\n        ]\n        tests = [random.choice(available_tests) for _ in range(ITERATIONS)]\n\n        # pick a random length for transfers\n        transfer_length = test_transfer_length()\n        self.set_in_transfer_length(transfer_length)\n\n        logging.debug(f\"Running stress test with a transfer length of {transfer_length} bytes\")\n        failures = 0\n        for index, test in enumerate(tests):\n            logging.debug(f\"#{index}: {test.__name__}\")\n            try:\n                test(self, transfer_length)\n            except Exception as e:\n                failures += 1\n                logging.error(f\"Failed #{index}: {test.__name__} {e}\")\n\n        if failures > 0:\n            logging.error(f\"Failed {failures} tests.\")\n            raise RuntimeError(f\"Failed {failures} tests.\")\n\n\ndef highly_stressed_edition():\n    from .test_transfers import TestTransfers\n\n    available_tests = [\n        \"test_bulk_out_transfer\",\n        \"test_bulk_in_transfer\",\n        \"test_control_out_transfer\",\n        \"test_control_in_transfer\",\n    ]\n    tests = [random.choice(available_tests) for _ in range(ITERATIONS)]\n\n    suite = unittest.TestSuite()\n    for test in tests:\n        suite.addTest(TestTransfers(test))\n\n    runner = unittest.TextTestRunner()\n    runner.run(suite)\n\n\nif __name__ == \"__main__\":\n    #highly_stressed_edition()\n    unittest.main(verbosity=1)\n"
  },
  {
    "path": "test/test_transfers.py",
    "content": "#\n# This file is part of Facedancer.\n#\n\nimport asyncio, logging, random, sys, time\nimport unittest\nimport usb1\n\nfrom .base   import FacedancerTestCase\nfrom .base   import VENDOR_ID, PRODUCT_ID, MAX_TRANSFER_LENGTH, OUT_ENDPOINT, IN_ENDPOINT\nfrom .device import generate_data\n\n\n# Transfer length for tests\ndef test_transfer_length():\n    return random.randrange(1, MAX_TRANSFER_LENGTH)\n\n\n# Run tests in random order\n#\n# Note: if you can't reproduce a failed run check the order of the\n#       tests in the failed run!\nunittest.TestLoader.sortTestMethodsUsing = lambda self, a, b: random.choice([1, 0, -1])\n\n\nclass TestTransfers(FacedancerTestCase):\n    \"\"\"Transfer tests for test device\"\"\"\n\n    # - life-cycle ------------------------------------------------------------\n\n    def setUp(self):\n        # select first interface\n        self.set_interface(0, 0)\n\n        # reset test device state between tests\n        self.reset_device_state()\n\n\n    # - transfer checks -------------------------------------------------------\n\n    def check_out_transfer(self, length, sent_data, bytes_sent):\n        # request a copy of the received data to compare against\n        received_data = self.get_last_out_transfer_data()\n\n        # did we send the right amount of data?\n        self.assertEqual(bytes_sent, length)\n\n        # does the length of the sent data match the length of the received data?\n        self.assertEqual(len(sent_data), len(received_data))\n\n        # does the content of the sent data match the content of the received data?\n        self.assertEqual(sent_data, received_data)\n\n\n    def check_in_transfer(self, length, received_data):\n        # generate a set of data to compare against\n        compare_data = generate_data(length)\n\n        # did we receive the right amount of data?\n        self.assertEqual(len(received_data), length)\n\n        # does the content of the received data match the content of our comparison data?\n        self.assertEqual(received_data, compare_data)\n\n\n    # - tests -----------------------------------------------------------------\n\n    def test_bulk_out_transfer(self):\n        # generate test data\n        length = test_transfer_length()\n        data = generate_data(length)\n\n        # perform Bulk OUT transfer\n        bytes_sent = self.bulk_out_transfer(OUT_ENDPOINT, data)\n\n        # check transfer\n        self.check_out_transfer(length, data, bytes_sent)\n\n\n    def test_bulk_in_transfer(self):\n        # set desired IN transfer length\n        length = test_transfer_length()\n        self.set_in_transfer_length(length)\n\n        # perform Bulk IN transfer\n        received_data = self.bulk_in_transfer(IN_ENDPOINT, length)\n\n        # check transfer\n        self.check_in_transfer(length, received_data)\n\n\n    def test_control_out_transfer(self):\n        # generate test data\n        length = test_transfer_length()\n        data = generate_data(length)\n\n        # perform Control OUT transfer\n        bytes_sent = self.control_out_transfer(data)\n\n        # check transfer\n        self.check_out_transfer(length, data, bytes_sent)\n\n\n    def test_control_in_transfer(self):\n        # set desired IN transfer length\n        length = test_transfer_length()\n        self.set_in_transfer_length(length)\n\n        # perform Bulk IN transfer\n        received_data = self.control_in_transfer(length)\n\n        # check transfer\n        self.check_in_transfer(length, received_data)\n\n\nif __name__ == \"__main__\":\n    unittest.main(verbosity=1)\n"
  }
]