├── usb_protocol ├── __init__.py ├── emitters │ ├── descriptors │ │ ├── __init__.py │ │ ├── uac1.py │ │ ├── cdc.py │ │ ├── uac3.py │ │ ├── uac2.py │ │ ├── midi1.py │ │ ├── microsoft10.py │ │ └── standard.py │ ├── __init__.py │ ├── descriptor.py │ └── construct_interop.py └── types │ ├── descriptors │ ├── __init__.py │ ├── partial │ │ ├── __init__.py │ │ └── standard.py │ ├── microsoft10.py │ ├── cdc.py │ ├── midi2.py │ ├── midi1.py │ ├── uac1.py │ ├── standard.py │ ├── uac3.py │ └── test_uac2.py │ ├── superspeed │ └── __init__.py │ ├── descriptor.py │ └── __init__.py ├── docs ├── source │ ├── index.rst │ └── conf.py ├── requirements.txt ├── Makefile └── make.bat ├── README.md ├── .editorconfig ├── .readthedocs.yaml ├── .github └── workflows │ └── workflow.yml ├── CHANGELOG.md ├── examples ├── simple_descriptors.py └── descriptor_builder.py ├── LICENSE.txt ├── pyproject.toml ├── .gitignore └── CODE_OF_CONDUCT.md /usb_protocol/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /usb_protocol/emitters/descriptors/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | from .standard import * 3 | -------------------------------------------------------------------------------- /usb_protocol/types/descriptors/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | from .standard import * 3 | -------------------------------------------------------------------------------- /usb_protocol/types/descriptors/partial/__init__.py: -------------------------------------------------------------------------------- 1 | from .standard import * 2 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | ========================== 2 | usb_protocol documentation 3 | ========================== 4 | 5 | .. toctree:: 6 | :maxdepth: 4 7 | :caption: API Documentation 8 | 9 | api_docs/modules 10 | 11 | * :ref:`genindex` 12 | * :ref:`modindex` 13 | -------------------------------------------------------------------------------- /usb_protocol/emitters/__init__.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of usb-protocol. 3 | # 4 | """ USB-related emitters. """ 5 | 6 | from .construct_interop import emitter_for_format, ConstructEmitter 7 | from .descriptors.standard import DeviceDescriptorCollection, SuperSpeedDeviceDescriptorCollection 8 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | sphinx==7.2.6 2 | sphinx_rtd_theme==2.0.0 3 | sphinxcontrib-apidoc 4 | readthedocs-sphinx-search==0.3.2 5 | recommonmark==0.7.1 6 | jinja2==3.1.6 7 | 8 | # needed for sphinxcontrib-apidoc to do its thing 9 | usb_protocol @ git+https://github.com/greatscottgadgets/python-usb-protocol.git 10 | -------------------------------------------------------------------------------- /usb_protocol/emitters/descriptors/uac1.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of usb_protocol. 3 | # 4 | """ Convenience emitters for USB Audio Class 1 descriptors. """ 5 | 6 | from contextlib import contextmanager 7 | 8 | from .. import emitter_for_format 9 | from ...types.descriptors.uac1 import * 10 | 11 | AudioControlInterruptEndpointDescriptorEmitter = emitter_for_format(AudioControlInterruptEndpointDescriptor) 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # USB Protocol Library for Python 3 | 4 | `usb-protocol` is a library that collects common data-processing code for USB tasks; 5 | and is meant to support a variety of projects, including USB stacks, analyzers, and 6 | other tools that work with USB data. A primary intention is to unify common code from 7 | LUNA, FaceDancer, and ViewSB. 8 | 9 | The library is currently an early work-in-progress; this documentation will be updated 10 | when the project is more mature. 11 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # top-most EditorConfig file 2 | root = true 3 | 4 | # Set our default format parameters. 5 | [*] 6 | charset = utf-8 7 | indent_size = 4 8 | end_of_line = lf 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | max_line_length = 120 12 | 13 | # Use python standard indentation for python files. 14 | [*.py] 15 | indent_style = space 16 | 17 | # Use tabs for our C files, to match coding conventions of our submodues. 18 | [*.{c,h}] 19 | indent_style = tab 20 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | # .readthedocs.yaml 2 | # Read the Docs configuration file 3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 4 | 5 | # Required 6 | version: 2 7 | 8 | # Set the OS, Python version and other tools 9 | build: 10 | os: ubuntu-22.04 11 | tools: 12 | python: "3.12" 13 | 14 | # Build documentation in the "docs/" directory with Sphinx 15 | sphinx: 16 | configuration: docs/source/conf.py 17 | 18 | # Build PDF for docs 19 | formats: 20 | - pdf 21 | 22 | python: 23 | install: 24 | - requirements: docs/requirements.txt 25 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | SOURCEDIR = source 8 | BUILDDIR = build 9 | 10 | # Put it first so that "make" without argument is like "make help". 11 | help: 12 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 13 | 14 | .PHONY: help Makefile 15 | 16 | # Catch-all target: route all unknown targets to Sphinx using the new 17 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 18 | %: Makefile 19 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 20 | -------------------------------------------------------------------------------- /usb_protocol/emitters/descriptors/cdc.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of usb_protocol. 3 | # 4 | """ Convenience emitters for CDC descriptors. """ 5 | 6 | from .. import emitter_for_format 7 | from ...types.descriptors.cdc import \ 8 | HeaderDescriptor, UnionFunctionalDescriptor, ACMFunctionalDescriptor, \ 9 | CallManagementFunctionalDescriptor 10 | 11 | # Create our emitters. 12 | HeaderDescriptorEmitter = emitter_for_format(HeaderDescriptor) 13 | UnionFunctionalDescriptorEmitter = emitter_for_format(UnionFunctionalDescriptor) 14 | ACMFunctionalDescriptorEmitter = emitter_for_format(ACMFunctionalDescriptor) 15 | CallManagementFunctionalDescriptorEmitter = emitter_for_format(CallManagementFunctionalDescriptor) 16 | -------------------------------------------------------------------------------- /usb_protocol/types/descriptors/partial/standard.py: -------------------------------------------------------------------------------- 1 | """ Convenience aliases for versions of descriptor structs that support parsing incomplete binary data. """ 2 | 3 | from .. import standard 4 | 5 | DeviceDescriptor = standard.DeviceDescriptor.Partial 6 | ConfigurationDescriptor = standard.ConfigurationDescriptor.Partial 7 | StringDescriptor = standard.StringDescriptor.Partial 8 | StringLanguageDescriptor = standard.StringLanguageDescriptor.Partial 9 | InterfaceDescriptor = standard.InterfaceDescriptor.Partial 10 | EndpointDescriptor = standard.EndpointDescriptor.Partial 11 | DeviceQualifierDescriptor = standard.DeviceQualifierDescriptor.Partial 12 | InterfaceAssociationDescriptor = standard.InterfaceAssociationDescriptor.Partial 13 | -------------------------------------------------------------------------------- /.github/workflows/workflow.yml: -------------------------------------------------------------------------------- 1 | name: unit_tests 2 | 3 | on: 4 | push: 5 | pull_request: 6 | workflow_dispatch: 7 | 8 | # Run automatically every monday 9 | schedule: 10 | - cron: 1 12 * * 1 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | strategy: 16 | max-parallel: 5 17 | matrix: 18 | python-version: 19 | - '3.9' 20 | - '3.10' 21 | - '3.11' 22 | - '3.12' 23 | - '3.13' 24 | 25 | name: test (${{ matrix.python-version }}) 26 | steps: 27 | - uses: actions/checkout@v1 28 | - name: Set up PDM 29 | uses: pdm-project/setup-pdm@v4 30 | with: 31 | python-version: ${{ matrix.python-version }} 32 | - name: Install dependencies 33 | run: | 34 | pdm install 35 | - name: Run tests 36 | run: pdm run test 37 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=source 11 | set BUILDDIR=build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /usb_protocol/emitters/descriptors/uac3.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of usb_protocol. 3 | # 4 | """ Convenience emitters for USB Audio Class 3 descriptors. """ 5 | 6 | from .. import emitter_for_format 7 | from ...types.descriptors.uac3 import * 8 | 9 | InputTerminalDescriptorEmitter = emitter_for_format(InputTerminalDescriptor) 10 | OutputTerminalDescriptorEmitter = emitter_for_format(OutputTerminalDescriptor) 11 | AudioStreamingInterfaceDescriptorEmitter = emitter_for_format(AudioStreamingInterfaceDescriptor) 12 | ClassSpecificAudioStreamingInterfaceDescriptorEmitter = emitter_for_format(ClassSpecificAudioStreamingInterfaceDescriptor) 13 | AudioControlInterruptEndpointDescriptorEmitter = emitter_for_format(AudioControlInterruptEndpointDescriptor) 14 | AudioStreamingIsochronousEndpointDescriptorEmitter = emitter_for_format(AudioStreamingIsochronousEndpointDescriptor) 15 | AudioStreamingIsochronousFeedbackEndpointDescriptorEmitter = emitter_for_format(AudioStreamingIsochronousFeedbackEndpointDescriptor) -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | 11 | 12 | ## [0.9.2] - 2025-08-20 13 | ### Added 14 | - Emitter for Interface Association Descriptors. 15 | - Missing partial helper for `InterfaceAssociationDescriptor`. 16 | ### Changed 17 | - Interface Association Descriptor's function string is now optional. 18 | - Use pid.codes VID/PID in unit tests. 19 | ### Fixed 20 | - String escaping for Microsoft OS descriptors. 21 | ### Removed 22 | - Dropped support for Python 3.8 23 | ### Security 24 | - Bumped jinja2 to 3.1.6 25 | 26 | 27 | ## [0.9.1] - 2024-06-21 28 | ### Added 29 | - USB Standard Feature Selectors per [USB 2.0: Table 9-6] 30 | 31 | 32 | ## [0.9.0] - 2024-06-04 33 | ### Added 34 | - Initial release 35 | 36 | [Unreleased]: https://github.com/greatscottgadgets/python-usb-protocol/compare/0.9.0...HEAD 37 | [0.9.1]: https://github.com/greatscottgadgets/python-usb-protocol/compare/0.9.0...0.9.1 38 | [0.9.0]: https://github.com/greatscottgadgets/python-usb-protocol/releases/tag/0.9.0 39 | -------------------------------------------------------------------------------- /examples/simple_descriptors.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # This file is part of usb-protocol. 4 | # 5 | """ Examples for using the simple descriptor data structures. """ 6 | 7 | from usb_protocol.types.descriptors import StringDescriptor 8 | from usb_protocol.emitters.descriptors import DeviceDescriptorEmitter 9 | 10 | string_descriptor = bytes([ 11 | 40, # Length 12 | 3, # Type 13 | ord('G'), 0x00, 14 | ord('r'), 0x00, 15 | ord('e'), 0x00, 16 | ord('a'), 0x00, 17 | ord('t'), 0x00, 18 | ord(' '), 0x00, 19 | ord('S'), 0x00, 20 | ord('c'), 0x00, 21 | ord('o'), 0x00, 22 | ord('t'), 0x00, 23 | ord('t'), 0x00, 24 | ord(' '), 0x00, 25 | ord('G'), 0x00, 26 | ord('a'), 0x00, 27 | ord('d'), 0x00, 28 | ord('g'), 0x00, 29 | ord('e'), 0x00, 30 | ord('t'), 0x00, 31 | ord('s'), 0x00, 32 | ]) 33 | 34 | # Use our simple StringDescriptor object to parse a binary blob string descriptor. 35 | print(f"Parsing: {string_descriptor}") 36 | parsed = StringDescriptor.parse(string_descriptor) 37 | print(parsed) 38 | 39 | # Create a simple Device Descriptor via an emitter object. 40 | # Our object has sane defaults, so we can skip some fields if we want. 41 | builder = DeviceDescriptorEmitter() 42 | builder.idVendor = 0x1234 43 | builder.idProduct = 0xabcd 44 | builder.bNumConfigurations = 3 45 | print(f"Generated device descriptor: {builder.emit()}") 46 | -------------------------------------------------------------------------------- /examples/descriptor_builder.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # This file is part of usb-protocol. 4 | # 5 | """ Example that builds a device-worth of descriptors using a collection object. """ 6 | 7 | from usb_protocol.emitters.descriptors import DeviceDescriptorCollection 8 | 9 | collection = DeviceDescriptorCollection() 10 | 11 | # Create our device descriptor, and populate it with some fields. 12 | # Many fields have sane/common defaults; and thus can be omitted. 13 | with collection.DeviceDescriptor() as d: 14 | d.idVendor = 0xc001 15 | d.idProduct = 0xc0de 16 | d.bNumConfigurations = 1 17 | 18 | d.iManufacturer = "usb-tools" 19 | d.iProduct = "Illegitimate USB Device" 20 | d.iSerialNumber = "123456" 21 | 22 | 23 | # Create our configuration descriptor, and its subordinates. 24 | with collection.ConfigurationDescriptor() as c: 25 | # Here, this is our first configuration, and is automatically assigned number '1'. 26 | 27 | # We'll create a simple interface with a couple of endpoints. 28 | with c.InterfaceDescriptor() as i: 29 | i.bInterfaceNumber = 0 30 | 31 | # Our endpoints default to bulk; with mostly-sane defaults. 32 | with i.EndpointDescriptor() as e: 33 | e.bEndpointAddress = 0x01 34 | e.wMaxPacketSize = 512 35 | 36 | with i.EndpointDescriptor() as e: 37 | e.bEndpointAddress = 0x81 38 | e.wMaxPacketSize = 512 39 | 40 | 41 | print("This device's descriptors would look like:") 42 | 43 | # Iterate over all of our descriptors. 44 | for value, index, raw in collection: 45 | print(f" type {value} (index {index}) = {raw}") 46 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2020-2024, Great Scott Gadgets 4 | Copyright (c) 2020, Katherine J. Temkin 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools>=64", "setuptools-git-versioning<2"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [project] 6 | name = "usb-protocol" 7 | description = "Python library providing utilities, data structures, constants, parsers, and tools for working with the USB protocol." 8 | license = { text = "BSD" } 9 | readme = "README.md" 10 | requires-python = ">=3.9" 11 | authors = [ 12 | {name = "Great Scott Gadgets", email = "dev@greatscottgadgets.com"}, 13 | ] 14 | 15 | classifiers = [ 16 | "Development Status :: 4 - Beta", 17 | "Programming Language :: Python :: 3", 18 | "Programming Language :: Python :: 3.9", 19 | "Programming Language :: Python :: 3.10", 20 | "Programming Language :: Python :: 3.11", 21 | "Programming Language :: Python :: 3.12", 22 | "Programming Language :: Python :: 3.13", 23 | "License :: OSI Approved :: BSD License", 24 | "Operating System :: OS Independent", 25 | "Natural Language :: English", 26 | "Environment :: Console", 27 | "Environment :: Plugins", 28 | "Intended Audience :: Developers", 29 | "Intended Audience :: Science/Research", 30 | "Topic :: Scientific/Engineering :: Electronic Design Automation (EDA)", 31 | "Topic :: Security", 32 | "Topic :: System :: Hardware :: Universal Serial Bus (USB)", 33 | ] 34 | 35 | dependencies = [ 36 | "construct~=2.10" 37 | ] 38 | 39 | dynamic = ["version"] 40 | 41 | [project.urls] 42 | Documentation = "https://python-usb-protocol.readthedocs.io" 43 | Repository = "https://github.com/greatscottgadgets/python-usb-protocol" 44 | Issues = "https://github.com/greatscottgadgets/python-usb-protocol/issues" 45 | 46 | [tool.setuptools.package-dir] 47 | usb_protocol = "usb_protocol" 48 | 49 | [tool.setuptools-git-versioning] 50 | enabled = true 51 | starting_version = "0.9.0" 52 | 53 | [tool.pdm.scripts] 54 | test.cmd = "python -m unittest discover -t . -v" 55 | -------------------------------------------------------------------------------- /usb_protocol/types/superspeed/__init__.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of usb-protocol. 3 | # 4 | """ SuperSpeed USB types -- defines enumerations that describe standard USB3 types. """ 5 | 6 | from enum import IntEnum 7 | 8 | class LinkCommand(IntEnum): 9 | """ Constant values (including both class and type) for link commands. """ 10 | 11 | LGOOD = 0 # Header Packet ACK 12 | LCRD = 1 # Header Credit 13 | LRTY = 2 # Header Packet Retry Sequence 14 | LBAD = 3 # Header Packet NAK 15 | LGO_U = 4 # Request Switching to Power State Ux 16 | LAU = 5 # Power State Acceptance 17 | LXU = 6 # Power State Rejection 18 | LPMA = 7 # Power State Acknowledgement 19 | LUP = 8 # Downstream-facing Keep-alive 20 | LDN = 11 # Upstream-facing Keep-alive 21 | 22 | def get_class(self): 23 | return int(self) >> 2 24 | 25 | def get_type(self): 26 | return int(self) & 0b11 27 | 28 | 29 | class HeaderPacketType(IntEnum): 30 | """ Constants representing the Header Packet archetypes. """ 31 | TRANSACTION = 0b00100 32 | DATA = 0b01000 33 | ISOCHRONOUS_TIMESTAMP = 0b01100 34 | LINK_MANAGEMENT = 0b00000 35 | 36 | 37 | class TransactionPacketSubtype(IntEnum): 38 | """ Constants representing the subtypes of Transition Header Packet. """ 39 | 40 | ACK = 1 41 | NRDY = 2 42 | ERDY = 3 43 | STATUS = 4 44 | STALL = 5 45 | NOTIFICATION = 6 46 | PING = 7 47 | PING_RESPONSE = 8 48 | 49 | 50 | class LinkManagementPacketSubtype(IntEnum): 51 | """ Constants represneting the various types of Link Management Packet. """ 52 | 53 | SET_LINK_FUNCTION = 1 54 | U2_INACTIVITY_TIMEOUT = 2 55 | VENDOR_DEVICE_TEST = 3 56 | PORT_CAPABILITY = 4 57 | PORT_CONFIGURATION = 5 58 | PORT_CONFIGURATION_RESPONSE = 6 59 | PRECISION_TIME_MANAGEMENT = 7 60 | -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | import os, pkg_resources, sys, time 2 | sys.path.insert(0, os.path.abspath('../../')) 3 | sys.path.insert(0, os.path.abspath('../../usb_protocol')) 4 | 5 | import sphinx_rtd_theme 6 | 7 | extensions = [ 8 | 'sphinx_rtd_theme' 9 | ] 10 | 11 | # -- Project information ----------------------------------------------------- 12 | 13 | project = 'usb_protocol' 14 | copyright = time.strftime('2021-%Y, Great Scott Gadgets') 15 | author = 'Great Scott Gadgets' 16 | 17 | version = pkg_resources.get_distribution('usb_protocol').version 18 | release = '' 19 | 20 | 21 | # -- General configuration --------------------------------------------------- 22 | 23 | templates_path = ['_templates'] 24 | exclude_patterns = ['build'] 25 | source_suffix = '.rst' 26 | master_doc = 'index' 27 | language = 'en' 28 | exclude_patterns = [] 29 | pygments_style = None 30 | 31 | extensions = [ 32 | 'recommonmark', 33 | 'sphinx.ext.autodoc', 34 | 'sphinx.ext.coverage', 35 | 'sphinx.ext.doctest', 36 | 'sphinx.ext.extlinks', 37 | 'sphinx.ext.napoleon', 38 | 'sphinx.ext.todo', 39 | 'sphinx.ext.viewcode', 40 | 'sphinxcontrib.apidoc', 41 | ] 42 | 43 | # configure extension: sphinxcontrib.apidoc 44 | apidoc_module_dir = '../../usb_protocol' 45 | apidoc_output_dir = 'api_docs' 46 | apidoc_excluded_paths = ['test'] 47 | apidoc_separate_modules = True 48 | 49 | # configure extension: extlinks 50 | extlinks = { 51 | 'repo': ('https://github.com/greatscottgadgets/facedancer/blob/main/%s', '%s'), 52 | 'example': ('https://github.com/greatscottgadgets/facedancer/blob/main/examples/%s', '%s'), 53 | } 54 | 55 | # configure extension: napoleon 56 | napoleon_google_docstring = True 57 | napoleon_numpy_docstring = False 58 | napoleon_include_init_with_doc = True 59 | napoleon_use_ivar = True 60 | napoleon_include_private_with_doc = False 61 | napoleon_include_special_with_doc = True 62 | napoleon_use_param = False 63 | 64 | 65 | # -- Options for HTML output ------------------------------------------------- 66 | # run pip install sphinx_rtd_theme if you get sphinx_rtd_theme errors 67 | html_theme = 'sphinx_rtd_theme' 68 | html_css_files = ['status.css'] 69 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | # Byte-compiled / optimized / DLL files 3 | __pycache__/ 4 | *.py[cod] 5 | *$py.class 6 | 7 | # C extensions 8 | *.so 9 | 10 | # Distribution / packaging 11 | .Python 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | pip-wheel-metadata/ 25 | share/python-wheels/ 26 | *.egg-info/ 27 | .installed.cfg 28 | *.egg 29 | MANIFEST 30 | 31 | # PyInstaller 32 | # Usually these files are written by a python script from a template 33 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 34 | *.manifest 35 | *.spec 36 | 37 | # Installer logs 38 | pip-log.txt 39 | pip-delete-this-directory.txt 40 | 41 | # Unit test / coverage reports 42 | htmlcov/ 43 | .tox/ 44 | .nox/ 45 | .coverage 46 | .coverage.* 47 | .cache 48 | nosetests.xml 49 | coverage.xml 50 | *.cover 51 | *.py,cover 52 | .hypothesis/ 53 | .pytest_cache/ 54 | 55 | # Translations 56 | *.mo 57 | *.pot 58 | 59 | # Django stuff: 60 | *.log 61 | local_settings.py 62 | db.sqlite3 63 | db.sqlite3-journal 64 | 65 | # Flask stuff: 66 | instance/ 67 | .webassets-cache 68 | 69 | # Scrapy stuff: 70 | .scrapy 71 | 72 | # Sphinx documentation 73 | docs/_build/ 74 | 75 | # PyBuilder 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | .python-version 87 | 88 | # pipenv 89 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 90 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 91 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 92 | # install all needed dependencies. 93 | #Pipfile.lock 94 | 95 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 96 | __pypackages__/ 97 | 98 | # Celery stuff 99 | celerybeat-schedule 100 | celerybeat.pid 101 | 102 | # SageMath parsed files 103 | *.sage.py 104 | 105 | # Environments 106 | .env 107 | .venv 108 | env/ 109 | venv/ 110 | ENV/ 111 | env.bak/ 112 | venv.bak/ 113 | 114 | # Spyder project settings 115 | .spyderproject 116 | .spyproject 117 | 118 | # Rope project settings 119 | .ropeproject 120 | 121 | # mkdocs documentation 122 | /site 123 | 124 | # mypy 125 | .mypy_cache/ 126 | .dmypy.json 127 | dmypy.json 128 | 129 | # Pyre type checker 130 | .pyre/ 131 | 132 | # Editor / IDE files 133 | .vscode 134 | -------------------------------------------------------------------------------- /usb_protocol/types/descriptors/microsoft10.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of usb-protocol. 3 | # 4 | ''' 5 | Structures describing Microsoft OS 1.0 Descriptors. 6 | ''' 7 | 8 | from enum import IntEnum 9 | 10 | import construct 11 | from construct import len_, this 12 | 13 | from ..descriptor import DescriptorField, DescriptorFormat 14 | 15 | 16 | class RegistryTypes(IntEnum): 17 | """ Standard Windows registry data types, used in the extended properties descriptor """ 18 | REG_SZ = 1 19 | REG_EXPAND_SZ = 2 20 | REG_BINARY = 3 21 | REG_DWORD_LITTLE_ENDIAN = 4 22 | REG_DWORD_BIG_ENDIAN = 5 23 | REG_LINK = 6 24 | REG_MULTI_SZ = 7 25 | 26 | 27 | # Extended Compat ID descriptor 28 | ExtendedCompatIDDescriptor = DescriptorFormat( 29 | 'dwLength' / construct.Rebuild(construct.Int32ul, 16 + this.bCount * 24), 30 | 'bcdVersion' / construct.Const(0x0100, construct.Int16ul), 31 | 'wIndex' / construct.Const(0x0004, construct.Int16ul), 32 | 'bCount' / DescriptorField("Number of function sections"), 33 | 'reserved' / construct.Padding(7), 34 | ) 35 | 36 | ExtendedCompatIDDescriptorFunction = DescriptorFormat( 37 | 'bFirstInterfaceNumber' / DescriptorField("Interface number"), 38 | 'bReserved' / construct.Const(0x01, construct.Int8ul), 39 | 'compatibleID' / construct.Default(construct.PaddedString(8, 'ascii'), ""), 40 | 'subCompatibleID' / construct.Default(construct.PaddedString(8, 'ascii'), ""), 41 | 'reserved' / construct.Padding(6), 42 | ) 43 | 44 | 45 | # Extended properties descriptor 46 | ExtendedPropertiesDescriptor = DescriptorFormat( 47 | 'dwLength' / DescriptorField("Length of the complete extended properties descriptor", length=4), 48 | 'bcdVersion' / construct.Const(0x0100, construct.Int16ul), 49 | 'wIndex' / construct.Const(0x0005, construct.Int16ul), 50 | 'wCount' / DescriptorField("Number of custom property sections"), 51 | ) 52 | 53 | ExtendedPropertiesDescriptorSection = DescriptorFormat( 54 | 'dwSize' / construct.Rebuild(construct.Int32ul, 14 + 2 * len_(this.PropertyName) + 2 + len_(this.PropertyData)), 55 | 'dwPropertyDataType' / DescriptorField('Data type of the registry property', length=4), 56 | 'wPropertyNameLength' / construct.Rebuild(construct.Int16ul, 2 * len_(this.PropertyName) + 2), 57 | 'PropertyName' / construct.CString('utf_16_le'), 58 | 'dwPropertyDataLength' / construct.Rebuild(construct.Int32ul, len_(this.PropertyData)), 59 | 'PropertyData' / construct.GreedyBytes, 60 | ) 61 | -------------------------------------------------------------------------------- /usb_protocol/emitters/descriptor.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of usb-protocol. 3 | # 4 | 5 | 6 | from collections import defaultdict 7 | 8 | from . import ConstructEmitter 9 | 10 | from ..types.descriptor import DescriptorFormat 11 | 12 | class ComplexDescriptorEmitter(ConstructEmitter): 13 | """ Base class for emitting complex descriptors, which contain nested subordinates. """ 14 | 15 | # Base classes must override this. 16 | DESCRIPTOR_FORMAT: DescriptorFormat 17 | 18 | def __init__(self, collection=None): 19 | """ 20 | Parameters: 21 | collection -- If this descriptor belongs to a collection, it should be 22 | provided here. Using a collection object allows e.g. automatic 23 | assignment of string descriptor indices. 24 | """ 25 | 26 | self._collection = collection 27 | 28 | # Always create a basic ConstructEmitter from the given format. 29 | super().__init__(self.DESCRIPTOR_FORMAT) 30 | 31 | # Store a list of subordinate descriptors, and a count of 32 | # subordinate descriptor types. 33 | self._subordinates = [] 34 | self._type_counts = defaultdict(int) 35 | 36 | 37 | def add_subordinate_descriptor(self, subordinate): 38 | """ Adds a subordinate descriptor to the relevant descriptor. 39 | 40 | Parameter: 41 | subordinate -- The subordinate descriptor to add; can be an emitter, 42 | or a bytes-like object. 43 | """ 44 | 45 | if hasattr(subordinate, 'emit'): 46 | subordinate = subordinate.emit() 47 | else: 48 | subordinate = bytes(subordinate) 49 | 50 | # The second byte of a given descriptor is always its type number. 51 | # Count this descriptor type... 52 | subordinate_type = subordinate[1] 53 | 54 | self._type_counts[subordinate_type] += 1 55 | 56 | # ... and add the relevant bytes to our list of subordinates. 57 | self._subordinates.append(subordinate) 58 | 59 | 60 | def _pre_emit(self): 61 | """ Performs any manipulations needed on this object before emission. """ 62 | pass 63 | 64 | 65 | def emit(self, include_subordinates=True): 66 | """ Emit our descriptor. 67 | 68 | Parameters: 69 | include_subordinates -- If true or not provided, any subordinate descriptors will be included. 70 | """ 71 | 72 | # Run any pre-emit hook code before we perform our emission... 73 | self._pre_emit() 74 | 75 | # Start with our core descriptor... 76 | result = bytearray() 77 | result.extend(super().emit()) 78 | 79 | # ... and if descired, add our subordinates... 80 | for sub in self._subordinates: 81 | result.extend(sub) 82 | 83 | return bytes(result) 84 | 85 | 86 | -------------------------------------------------------------------------------- /usb_protocol/emitters/descriptors/uac2.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of usb_protocol. 3 | # 4 | """ Convenience emitters for USB Audio Class 2 descriptors. """ 5 | 6 | from contextlib import contextmanager 7 | 8 | from .. import emitter_for_format 9 | from ...types.descriptors.uac2 import * 10 | from ...emitters.descriptor import ComplexDescriptorEmitter 11 | 12 | # Create our emitters. 13 | InterfaceAssociationDescriptorEmitter = emitter_for_format(InterfaceAssociationDescriptor) 14 | StandardAudioControlInterfaceDescriptorEmitter = emitter_for_format(StandardAudioControlInterfaceDescriptor) 15 | 16 | class ClassSpecificAudioControlInterfaceDescriptorEmitter(ComplexDescriptorEmitter): 17 | DESCRIPTOR_FORMAT = ClassSpecificAudioControlInterfaceDescriptor 18 | 19 | def _pre_emit(self): 20 | # Figure out the total length of our descriptor, including subordinates. 21 | subordinate_length = sum(len(sub) for sub in self._subordinates) 22 | self.wTotalLength = subordinate_length + self.DESCRIPTOR_FORMAT.sizeof() 23 | 24 | ClockSourceDescriptorEmitter = emitter_for_format(ClockSourceDescriptor) 25 | InputTerminalDescriptorEmitter = emitter_for_format(InputTerminalDescriptor) 26 | OutputTerminalDescriptorEmitter = emitter_for_format(OutputTerminalDescriptor) 27 | FeatureUnitDescriptorEmitter = emitter_for_format(FeatureUnitDescriptor) 28 | AudioStreamingInterfaceDescriptorEmitter = emitter_for_format(AudioStreamingInterfaceDescriptor) 29 | ClassSpecificAudioStreamingInterfaceDescriptorEmitter = emitter_for_format(ClassSpecificAudioStreamingInterfaceDescriptor) 30 | TypeIFormatTypeDescriptorEmitter = emitter_for_format(TypeIFormatTypeDescriptor) 31 | ExtendedTypeIFormatTypeDescriptorEmitter = emitter_for_format(ExtendedTypeIFormatTypeDescriptor) 32 | TypeIIFormatTypeDescriptorEmitter = emitter_for_format(TypeIIFormatTypeDescriptor) 33 | ExtendedTypeIIFormatTypeDescriptorEmitter = emitter_for_format(ExtendedTypeIIFormatTypeDescriptor) 34 | TypeIIIFormatTypeDescriptorEmitter = emitter_for_format(TypeIIIFormatTypeDescriptor) 35 | ExtendedTypeIIIFormatTypeDescriptorEmitter = emitter_for_format(ExtendedTypeIIIFormatTypeDescriptor) 36 | ClassSpecificAudioStreamingIsochronousAudioDataEndpointDescriptorEmitter = emitter_for_format(ClassSpecificAudioStreamingIsochronousAudioDataEndpointDescriptor) 37 | AudioControlInterruptEndpointDescriptorEmitter = emitter_for_format(AudioControlInterruptEndpointDescriptor) 38 | AudioStreamingIsochronousEndpointDescriptorEmitter = emitter_for_format(AudioStreamingIsochronousEndpointDescriptor) 39 | AudioStreamingIsochronousFeedbackEndpointDescriptorEmitter = emitter_for_format(AudioStreamingIsochronousFeedbackEndpointDescriptor) -------------------------------------------------------------------------------- /usb_protocol/emitters/construct_interop.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of usb-protocol. 3 | # 4 | """ Helpers for creating construct-related emitters. """ 5 | 6 | import unittest 7 | import construct 8 | 9 | 10 | class ConstructEmitter: 11 | """ Class that creates a simple emitter based on a construct struct. 12 | 13 | For example, if we have a construct format that looks like the following: 14 | 15 | .. code-block:: python 16 | 17 | MyStruct = struct( 18 | "a" / Int8 19 | "b" / Int8 20 | ) 21 | 22 | 23 | We could create and emit an object like follows: 24 | 25 | .. code-block:: python 26 | 27 | emitter = ConstructEmitter(MyStruct) 28 | emitter.a = 0xab 29 | emitter.b = 0xcd 30 | my_bytes = emitter.emit() # "\xab\xcd" 31 | 32 | """ 33 | 34 | def __init__(self, struct): 35 | """ 36 | Parameters: 37 | construct_format -- The format for which to create an emitter. 38 | """ 39 | self.__dict__['format'] = struct 40 | self.__dict__['fields'] = {} 41 | 42 | 43 | def _format_contains_field(self, field_name): 44 | """ Returns True iff the given format has a field with the provided name. 45 | 46 | Parameters: 47 | format_object -- The Construct format to work with. This includes e.g. most descriptor types. 48 | field_name -- The field name to query. 49 | """ 50 | return any(f.name == field_name for f in self.format.subcons) 51 | 52 | 53 | def __setattr__(self, name, value): 54 | """ Hook that we used to set our fields. """ 55 | 56 | # If the field starts with a '_', don't handle it, as it's an internal field. 57 | if name.startswith('_'): 58 | super().__setattr__(name, value) 59 | return 60 | 61 | if not self._format_contains_field(name): 62 | raise AttributeError(f"emitter specification contains no field {name}") 63 | 64 | self.fields[name] = value 65 | 66 | 67 | def emit(self): 68 | """ Emits the stream of bytes associated with this object. """ 69 | 70 | try: 71 | return self.format.build(self.fields) 72 | except KeyError as e: 73 | raise KeyError(f"missing necessary field: {e}") 74 | 75 | 76 | def __getattr__(self, name): 77 | """ Retrieves an emitter field, if possible. """ 78 | 79 | if name in self.fields: 80 | return self.fields[name] 81 | else: 82 | raise AttributeError(f"descriptor emitter has no property {name}") 83 | 84 | 85 | class ConstructEmitterTest(unittest.TestCase): 86 | 87 | def test_simple_emitter(self): 88 | 89 | test_struct = construct.Struct( 90 | "a" / construct.Int8ul, 91 | "b" / construct.Int8ul 92 | ) 93 | 94 | emitter = ConstructEmitter(test_struct) 95 | emitter.a = 0xab 96 | emitter.b = 0xcd 97 | 98 | self.assertEqual(emitter.emit(), b"\xab\xcd") 99 | 100 | 101 | def emitter_for_format(construct_format): 102 | """ Creates a factory method for the relevant construct format. """ 103 | 104 | def _factory(): 105 | return ConstructEmitter(construct_format) 106 | 107 | return _factory 108 | 109 | 110 | if __name__ == "__main__": 111 | unittest.main() 112 | -------------------------------------------------------------------------------- /usb_protocol/types/descriptors/cdc.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of usb-protocol. 3 | # 4 | """ Structures describing Communications Device Class descriptors. """ 5 | 6 | import unittest 7 | from enum import IntEnum 8 | 9 | import construct 10 | from construct import this, Default 11 | 12 | from .. import LanguageIDs 13 | from ..descriptor import \ 14 | DescriptorField, DescriptorNumber, DescriptorFormat, \ 15 | BCDFieldAdapter, DescriptorLength 16 | 17 | 18 | class CDCDescriptorNumbers(IntEnum): 19 | CS_INTERFACE = 0x24 20 | CS_ENDPOINT = 0x25 21 | 22 | 23 | class CDCDescriptorSubtypes(IntEnum): 24 | """ Numbers of the Communications Class descriptor subtypes. """ 25 | 26 | HEADER = 0x00 27 | CALL_MANAGEMENT_FUNCTIONAL = 0x01 28 | ABSTRACT_CONTROL_MANAGEMENT_FUNCTIONAL = 0x02 29 | DIRECT_LINE_MANAGEMENT_FUNCTIONAL = 0x03 30 | TELEPHONE_RINGER_FUNCTIONAL = 0x04 31 | TELEPHONE_CALL_AND_LINE_STATE_FUNCTIONAL = 0x05 32 | UNION_FUNCTIONAL_DESCRIPTOR = 0x06 33 | COUNTRY_SELECTION_FUNCTIONAL = 0x07 34 | TELEPHONE_OPERATIONAL_MODES_FUNCTIONAL = 0x08 35 | USB_TERMINAL_FUNCTIONAL = 0x09 36 | NETWORK_CHANNEL_TERMINAL = 0x0a 37 | PROTOCOL_UNIT_FUNCTIONAL = 0x0b 38 | EXTENSION_UNIT_FUNCTIONAL = 0x0c 39 | MULTI_CHANNEL_MANAGEMENT_FUNCTIONAL = 0x0d 40 | CAPI_CONTROL_MANAGEMENT_FUNCTIONAL = 0x0e 41 | ETHERNET_NETWORKING_FUNCTIONAL = 0x0f 42 | ATM_NETWORKING_FUNCTIONAL = 0x10 43 | WIRELESS_HANDSET_CONTROL_MODEL_FUNCTIONAL = 0x11 44 | MOBILE_DIRECT_LINE_MODEL_FUNCTIONAL = 0x12 45 | MDLM_DETAIL_FUNCTIONAL = 0x13 46 | DEVICE_MANAGEMENT_MODEL_FUNCTIONAL = 0x14 47 | OBEX_FUNCTIONAL = 0x15 48 | COMMAND_SET_FUCNTIONAL = 0x16 49 | COMMAND_SET_DETAIL_FUNCTIONAL = 0x17 50 | TELEPHONE_CONTROL_MODEL_FUNCTIONAL = 0x18 51 | OBEX_SERVICE_IDENTIFIER_FUNCTIONAL = 0x19 52 | NCM_FUNCTIONAL = 0x1A 53 | 54 | 55 | HeaderDescriptor = DescriptorFormat( 56 | "bLength" / construct.Const(5, construct.Int8ul), 57 | "bDescriptorType" / DescriptorNumber(CDCDescriptorNumbers.CS_INTERFACE), 58 | "bDescriptorSubtype" / DescriptorNumber(CDCDescriptorSubtypes.HEADER), 59 | "bcdCDC" / DescriptorField(description="CDC Version", default=1.1) 60 | ) 61 | 62 | ACMFunctionalDescriptor = DescriptorFormat( 63 | "bLength" / construct.Const(4, construct.Int8ul), 64 | "bDescriptorType" / DescriptorNumber(CDCDescriptorNumbers.CS_INTERFACE), 65 | "bDescriptorSubtype" / DescriptorNumber(CDCDescriptorSubtypes.ABSTRACT_CONTROL_MANAGEMENT_FUNCTIONAL), 66 | "bmCapabilities" / DescriptorField(description="ACM Capabilities", default=0b0010) 67 | ) 68 | 69 | UnionFunctionalDescriptor = DescriptorFormat( 70 | "bLength" / construct.Const(5, construct.Int8ul), 71 | "bDescriptorType" / DescriptorNumber(CDCDescriptorNumbers.CS_INTERFACE), 72 | "bDescriptorSubtype" / DescriptorNumber(CDCDescriptorSubtypes.UNION_FUNCTIONAL_DESCRIPTOR), 73 | "bControlInterface" / DescriptorField(description="Control Interface Number"), 74 | "bSubordinateInterface0" / DescriptorField(description="Subordinate Interface Number") 75 | ) 76 | 77 | CallManagementFunctionalDescriptor = DescriptorFormat( 78 | "bLength" / construct.Const(5, construct.Int8ul), 79 | "bDescriptorType" / DescriptorNumber(CDCDescriptorNumbers.CS_INTERFACE), 80 | "bDescriptorSubtype" / DescriptorNumber(CDCDescriptorSubtypes.CALL_MANAGEMENT_FUNCTIONAL), 81 | "bmCapabilities" / DescriptorField(description="Call Management capabilities", default=0), 82 | "bDataInterface" / DescriptorField(description="Data Interface Number") 83 | ) 84 | -------------------------------------------------------------------------------- /usb_protocol/emitters/descriptors/midi1.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of usb_protocol. 3 | # 4 | """ Convenience emitters for USB MIDI Class 1 descriptors. """ 5 | 6 | from .. import emitter_for_format 7 | from ...emitters.descriptor import ComplexDescriptorEmitter 8 | from ...types.descriptors.midi1 import * 9 | 10 | class ClassSpecificMidiStreamingInterfaceDescriptorEmitter(ComplexDescriptorEmitter): 11 | DESCRIPTOR_FORMAT = ClassSpecificMidiStreamingInterfaceHeaderDescriptor 12 | 13 | def _pre_emit(self): 14 | # Figure out the total length of our descriptor, including subordinates. 15 | subordinate_length = sum(len(sub) for sub in self._subordinates) 16 | self.wTotalLength = subordinate_length + self.DESCRIPTOR_FORMAT.sizeof() 17 | 18 | class MidiOutJackDescriptorEmitter(ComplexDescriptorEmitter): 19 | DESCRIPTOR_FORMAT = MidiOutJackDescriptorHead 20 | 21 | def add_subordinate_descriptor(self, subordinate): 22 | subordinate = subordinate.emit() 23 | self._subordinates.append(subordinate) 24 | 25 | def add_source(self, sourceId, sourcePin=1): 26 | sourceDescriptor = MidiOutJackDescriptorElementEmitter() 27 | sourceDescriptor.baSourceID = sourceId 28 | sourceDescriptor.BaSourcePin = sourcePin 29 | self.add_subordinate_descriptor(sourceDescriptor) 30 | 31 | def __setattr__(self, name, value): 32 | if name == "iJack": 33 | self._iJack = value 34 | else: 35 | return super().__setattr__(name, value) 36 | 37 | def _pre_emit(self): 38 | foot = MidiOutJackDescriptorFootEmitter() 39 | if hasattr(self, "_iJack"): 40 | foot.iJack = self._iJack 41 | self.add_subordinate_descriptor(foot) 42 | # Figure out the total length of our descriptor, including subordinates. 43 | subordinate_length = sum(len(sub) for sub in self._subordinates) 44 | self.bLength = subordinate_length + self.DESCRIPTOR_FORMAT.sizeof() 45 | 46 | class ClassSpecificMidiStreamingBulkDataEndpointDescriptorEmitter(ComplexDescriptorEmitter): 47 | DESCRIPTOR_FORMAT = ClassSpecificMidiStreamingBulkDataEndpointDescriptorHead 48 | 49 | def add_subordinate_descriptor(self, subordinate): 50 | subordinate = subordinate.emit() 51 | self._subordinates.append(subordinate) 52 | 53 | def add_associated_jack(self, jackID): 54 | jackDescriptor = ClassSpecificMidiStreamingBulkDataEndpointDescriptorElementEmitter() 55 | jackDescriptor.baAssocJackID = jackID 56 | self.add_subordinate_descriptor(jackDescriptor) 57 | 58 | def _pre_emit(self): 59 | # Figure out the total length of our descriptor, including subordinates. 60 | subordinate_length = sum(len(sub) for sub in self._subordinates) 61 | self.bLength = subordinate_length + self.DESCRIPTOR_FORMAT.sizeof() 62 | 63 | StandardMidiStreamingInterfaceDescriptorEmitter = emitter_for_format(StandardMidiStreamingInterfaceDescriptor) 64 | ClassSpecificMidiStreamingInterfaceHeaderDescriptorEmitter = emitter_for_format(ClassSpecificMidiStreamingInterfaceHeaderDescriptor) 65 | MidiInJackDescriptorEmitter = emitter_for_format(MidiInJackDescriptor) 66 | MidiOutJackDescriptorHeadEmitter = emitter_for_format(MidiOutJackDescriptorHead) 67 | MidiOutJackDescriptorElementEmitter = emitter_for_format(MidiOutJackDescriptorElement) 68 | MidiOutJackDescriptorFootEmitter = emitter_for_format(MidiOutJackDescriptorFoot) 69 | StandardMidiStreamingBulkDataEndpointDescriptorEmitter = emitter_for_format(StandardMidiStreamingBulkDataEndpointDescriptor) 70 | ClassSpecificMidiStreamingBulkDataEndpointDescriptorHeadEmitter = emitter_for_format(ClassSpecificMidiStreamingBulkDataEndpointDescriptorHead) 71 | ClassSpecificMidiStreamingBulkDataEndpointDescriptorElementEmitter = emitter_for_format(ClassSpecificMidiStreamingBulkDataEndpointDescriptorElement) 72 | -------------------------------------------------------------------------------- /usb_protocol/types/descriptors/midi2.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of usb-protocol. 3 | # 4 | """ 5 | Descriptors for USB MIDI Class Devices 6 | 7 | [Midi20] refers to "Universal Serial Bus Device Class Definition for MIDI Devices", Release 2.0, May 5, 2020 8 | """ 9 | 10 | import construct 11 | 12 | from enum import IntEnum 13 | 14 | from .. import DescriptorTypes, USBTransferType 15 | 16 | from .standard import StandardDescriptorNumbers 17 | 18 | from ..descriptor import ( 19 | DescriptorField, 20 | DescriptorNumber, 21 | DescriptorFormat, 22 | ) 23 | 24 | from ..descriptors.uac1 import ( 25 | AudioInterfaceClassCodes, 26 | AudioInterfaceSubclassCodes, 27 | AudioClassSpecificDescriptorTypes, 28 | AudioClassSpecificACInterfaceDescriptorSubtypes, 29 | ) 30 | 31 | 32 | class MidiStreamingInterfaceDescriptorTypes(IntEnum): 33 | # As defined in [Midi20], A.1 34 | CS_UNDEFINED = 0x20 35 | CS_DEVICE = 0x21 36 | CS_CONFIGURATION = 0x22 37 | CS_STRING = 0x23 38 | CS_INTERFACE = 0x24 39 | CS_ENDPOINT = 0x25 40 | CS_GR_TRM_BLOCK = 0x26 41 | 42 | 43 | class MidiStreamingInterfaceDescriptorSubtypes(IntEnum): 44 | # As defined in [Midi20], A.1 45 | MS_DESCRIPTOR_UNDEFINED = 0x00 46 | MS_HEADER = 0x01 47 | MIDI_IN_JACK = 0x02 48 | MIDI_OUT_JACK = 0x03 49 | ELEMENT = 0x04 50 | 51 | 52 | class MidiStreamingEndpointDescriptorSubtypes(IntEnum): 53 | # As defined in [Midi20], A.2 54 | DESCRIPTOR_UNDEFINED = 0x00 55 | MS_GENERAL = 0x01 56 | MS_GENERAL_2_0 = 0x02 57 | 58 | 59 | class MidiStreamingInterfaceHeaderClassRevision(IntEnum): 60 | # As defined in [Midi20], A.4 61 | MS_MIDI_1_0 = 0x0100 62 | MS_MIDI_2_0 = 0x0200 63 | 64 | 65 | class MidiStreamingJackTypes(IntEnum): 66 | # As defined in [Midi20], A.5 67 | JACK_TYPE_UNDEFINED = 0x00 68 | EMBEDDED = 0x01 69 | EXTERNAL = 0x02 70 | 71 | 72 | # As defined in [Midi20], Table B-5 73 | StandardMidiStreamingInterfaceDescriptor = DescriptorFormat( 74 | "bLength" / construct.Const(9, construct.Int8ul), 75 | "bDescriptorType" / DescriptorNumber(DescriptorTypes.INTERFACE), 76 | "bInterfaceNumber" / DescriptorField(description="ID of the streaming interface"), 77 | "bAlternateSetting" / DescriptorField(description="alternate setting number for the interface", default=0), 78 | "bNumEndpoints" / DescriptorField(description="Number of data endpoints used (excluding endpoint 0). Can be: 0 (no data endpoint); 1 (data endpoint); 2 (data + explicit feedback endpoint)", default=0), 79 | "bInterfaceClass" / DescriptorNumber(AudioInterfaceClassCodes.AUDIO), 80 | "bInterfaceSubClass" / DescriptorNumber(AudioInterfaceSubclassCodes.MIDI_STREAMING), 81 | "bInterfaceProtocol" / DescriptorNumber(0), 82 | "iInterface" / DescriptorField(description="index of a string descriptor describing this interface (0 = unused)", default=0) 83 | ) 84 | 85 | # As defined in [Midi20], Table B-6 86 | ClassSpecificMidiStreamingInterfaceHeaderDescriptor = DescriptorFormat( 87 | "bLength" / construct.Const(7, construct.Int8ul), 88 | "bDescriptorType" / DescriptorNumber(AudioClassSpecificDescriptorTypes.CS_INTERFACE), 89 | "bDescriptorSubtype" / DescriptorNumber(AudioClassSpecificACInterfaceDescriptorSubtypes.HEADER), 90 | "bcdADC" / DescriptorField(description="Midi Streaming Class specification release version", default=1.0), 91 | "wTotalLength" / DescriptorField(description="Total number of bytes of the class-specific MIDIStreaming interface descriptor. Includes the combined length of this descriptor header and all Jack and Element descriptors."), 92 | ) 93 | 94 | # As defined in [Midi20], Table 5-3 95 | StandardMidiStreamingDataEndpointDescriptor = DescriptorFormat( 96 | "bLength" / construct.Const(7, construct.Int8ul), 97 | "bDescriptorType" / DescriptorNumber(AudioClassSpecificDescriptorTypes.CS_ENDPOINT), 98 | "bEndpointAddress" / DescriptorField(description="endpoint address, use USBDirection.*.from_endpoint_address()"), 99 | "bmAttributes" / DescriptorField(description="endpoint type, see USBTransferType (only NONE, BULK or INTERRUPT allowed)", default=USBTransferType.BULK), 100 | "wMaxPacketSize" / DescriptorField(description="Maximum packet size this endpoint is capable of sending or receiving"), 101 | "bInterval" / DescriptorField(description="Interval for polling endpoint for Interrupt data transfers. For bulk endpoints this field is ignored and must be reset to 0", default=0) 102 | ) 103 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | straithe@greatscottgadgets.com. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /usb_protocol/types/descriptors/midi1.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of usb-protocol. 3 | # 4 | """ 5 | Descriptors for USB MIDI Class Devices 6 | 7 | [Midi10] refers to "Universal Serial Bus Device Class Definition for MIDI Devices", Release 1.0, November 1, 1999 8 | """ 9 | 10 | import construct 11 | 12 | from enum import IntEnum 13 | 14 | from .. import ( 15 | DescriptorTypes, 16 | USBSynchronizationType, 17 | USBTransferType, 18 | USBUsageType, 19 | ) 20 | 21 | from .standard import StandardDescriptorNumbers 22 | 23 | from ..descriptor import ( 24 | DescriptorField, 25 | DescriptorNumber, 26 | DescriptorFormat, 27 | ) 28 | 29 | from ..descriptors.uac1 import ( 30 | AudioInterfaceClassCodes, 31 | AudioInterfaceSubclassCodes, 32 | AudioClassSpecificDescriptorTypes, 33 | AudioClassSpecificACInterfaceDescriptorSubtypes, 34 | ) 35 | 36 | 37 | class MidiStreamingInterfaceDescriptorSubtypes(IntEnum): 38 | # As defined in [Midi10], A.1 39 | MS_DESCRIPTOR_UNDEFINED = 0x00 40 | MS_HEADER = 0x01 41 | MIDI_IN_JACK = 0x02 42 | MIDI_OUT_JACK = 0x03 43 | ELEMENT = 0x04 44 | 45 | 46 | class MidiStreamingEndpointDescriptorSubtypes(IntEnum): 47 | # As defined in [Midi10], A.2 48 | DESCRIPTOR_UNDEFINED = 0x00 49 | MS_GENERAL = 0x01 50 | 51 | 52 | class MidiStreamingJackTypes(IntEnum): 53 | # As defined in [Midi10], A.3 54 | JACK_TYPE_UNDEFINED = 0x00 55 | EMBEDDED = 0x01 56 | EXTERNAL = 0x02 57 | 58 | 59 | # As defined in [Midi10], Table 6-1 60 | StandardMidiStreamingInterfaceDescriptor = DescriptorFormat( 61 | "bLength" / construct.Const(9, construct.Int8ul), 62 | "bDescriptorType" / DescriptorNumber(DescriptorTypes.INTERFACE), 63 | "bInterfaceNumber" / DescriptorField(description="ID of the streaming interface"), 64 | "bAlternateSetting" / DescriptorField(description="alternate setting number for the interface", default=0), 65 | "bNumEndpoints" / DescriptorField(description="Number of data endpoints used (excluding endpoint 0). Can be: 0 (no data endpoint); 1 (data endpoint); 2 (data + explicit feedback endpoint)", default=0), 66 | "bInterfaceClass" / DescriptorNumber(AudioInterfaceClassCodes.AUDIO), 67 | "bInterfaceSubClass" / DescriptorNumber(AudioInterfaceSubclassCodes.MIDI_STREAMING), 68 | "bInterfaceProtocol" / DescriptorNumber(0), 69 | "iInterface" / DescriptorField(description="index of a string descriptor describing this interface (0 = unused)", default=0) 70 | ) 71 | 72 | # As defined in [Midi10], Table 6-2 73 | ClassSpecificMidiStreamingInterfaceHeaderDescriptor = DescriptorFormat( 74 | "bLength" / construct.Const(7, construct.Int8ul), 75 | "bDescriptorType" / DescriptorNumber(AudioClassSpecificDescriptorTypes.CS_INTERFACE), 76 | "bDescriptorSubtype" / DescriptorNumber(AudioClassSpecificACInterfaceDescriptorSubtypes.HEADER), 77 | "bcdADC" / DescriptorField(description="Midi Streaming Class specification release version", default=1.0), 78 | "wTotalLength" / DescriptorField(description="Total number of bytes of the class-specific MIDIStreaming interface descriptor. Includes the combined length of this descriptor header and all Jack and Element descriptors."), 79 | ) 80 | 81 | # As defined in [Midi10], Table 6-3 82 | MidiInJackDescriptor = DescriptorFormat( 83 | "bLength" / construct.Const(6, construct.Int8ul), 84 | "bDescriptorType" / DescriptorNumber(AudioClassSpecificDescriptorTypes.CS_INTERFACE), 85 | "bDescriptorSubtype" / DescriptorNumber(MidiStreamingInterfaceDescriptorSubtypes.MIDI_IN_JACK), 86 | "bJackType" / DescriptorField(description="see MidiStreamingJackTypes"), 87 | "bJackID" / DescriptorField(description="Constant uniquely identifying the MIDI IN Jack within the USB-MIDI function"), 88 | "iJack" / DescriptorField(description="index of a string descriptor describing this jack (0 = unused)", default=0) 89 | ) 90 | 91 | # As defined in [Midi10], Table 6-4 92 | MidiOutJackDescriptorHead = DescriptorFormat( 93 | "bLength" / DescriptorField(description="Size of this descriptor, in bytes: 6+2*p"), 94 | "bDescriptorType" / DescriptorNumber(AudioClassSpecificDescriptorTypes.CS_INTERFACE), 95 | "bDescriptorSubtype" / DescriptorNumber(MidiStreamingInterfaceDescriptorSubtypes.MIDI_OUT_JACK), 96 | "bJackType" / DescriptorField(description="see MidiStreamingJackTypes"), 97 | "bJackID" / DescriptorField(description="Constant uniquely identifying the MIDI IN Jack within the USB-MIDI function"), 98 | "bNrInputPins" / DescriptorField(description="Number of Input Pins of this MIDI OUT Jack: p", default=1) 99 | ) 100 | 101 | MidiOutJackDescriptorElement = DescriptorFormat( 102 | "baSourceID" / construct.Int8ul, # ID of the Entity to which the first Input Pin of this MIDI OUT Jack is connected 103 | "BaSourcePin" / construct.Int8ul, #Output Pin number of the Entity to which the first Input Pin of this MIDI OUT Jack is connected 104 | ) 105 | 106 | MidiOutJackDescriptorFoot = DescriptorFormat( 107 | "iJack" / DescriptorField(description="index of a string descriptor describing this jack (0 = unused)", default=0) 108 | ) 109 | 110 | # As defined in [Midi10], Table 6-6 111 | StandardMidiStreamingBulkDataEndpointDescriptor = DescriptorFormat( 112 | "bLength" / construct.Const(9, construct.Int8ul), 113 | "bDescriptorType" / DescriptorNumber(DescriptorTypes.ENDPOINT), 114 | "bEndpointAddress" / DescriptorField(description="The address of the endpoint, use USBDirection.*.from_endpoint_address()"), 115 | "bmAttributes" / DescriptorField(description="D1..0: transfer type (10=bulk), D3..2: synchronization type (00=no sync);", default=USBTransferType.BULK | USBSynchronizationType.NONE | USBUsageType.DATA), 116 | "wMaxPacketSize" / DescriptorField(description="Maximum packet size this endpoint is capable of", default=512), 117 | "bInterval" / DescriptorField(description="Interval for polling endpoint for data transfers expressed in milliseconds. This field is ignored for bulk endpoints. Must be set to 0", default=0), 118 | "bRefresh" / DescriptorField(description="must be set to 0", default=0), 119 | "bSynchAddress" / DescriptorField(description="The address of the endpoint used to communicate synchronization information if required by this endpoint. Must be set to 0", default=0) 120 | ) 121 | 122 | # As defined in [Midi10], Table 6-7 123 | ClassSpecificMidiStreamingBulkDataEndpointDescriptorHead = DescriptorFormat( 124 | "bLength" / DescriptorField(description="Size of this descriptor, in bytes: 4+n"), 125 | "bDescriptorType" / DescriptorNumber(AudioClassSpecificDescriptorTypes.CS_ENDPOINT), 126 | "bDescriptorSubtype" / DescriptorField(description="see MidiStreamingEndpointDescriptorSubtypes", default=MidiStreamingEndpointDescriptorSubtypes.MS_GENERAL), 127 | "bNumEmbMIDIJack" / DescriptorField(description="Number of Embedded MIDI Jacks: n", default=1) 128 | ) 129 | 130 | ClassSpecificMidiStreamingBulkDataEndpointDescriptorElement = DescriptorFormat( 131 | "baAssocJackID" / construct.Int8ul # ID of the embedded eack that is associated with this endpoint 132 | ) 133 | -------------------------------------------------------------------------------- /usb_protocol/types/descriptor.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of usb-protocol. 3 | # 4 | """ Type elements for defining USB descriptors. """ 5 | 6 | import unittest 7 | import construct 8 | 9 | class DescriptorFormat(construct.Struct): 10 | """ 11 | Creates a Construct structure for a USB descriptor, and a corresponding version that 12 | supports parsing incomplete binary as `DescriptorType.Partial`, e.g. `DeviceDescriptor.Partial`. 13 | """ 14 | 15 | def __init__(self, *subcons, _create_partial=True, **subconskw): 16 | 17 | if _create_partial: 18 | self.Partial = self._create_partial(*subcons, **subconskw) # pylint: disable=invalid-name 19 | 20 | super().__init__(*subcons, **subconskw) 21 | 22 | 23 | @classmethod 24 | def _get_subcon_field_type(cls, subcon): 25 | """ Gets the actual field type for a Subconstruct behind arbitrary levels of `Renamed`s.""" 26 | 27 | # DescriptorFields are usually `>>`. 28 | # The type behind the `Renamed`s is the one we're interested in, so let's recursively examine 29 | # the child Subconstruct until we get to it. 30 | 31 | if not isinstance(subcon, construct.Renamed): 32 | return subcon 33 | else: 34 | return cls._get_subcon_field_type(subcon.subcon) 35 | 36 | 37 | @classmethod 38 | def _create_partial(cls, *subcons, **subconskw): 39 | """ Creates a version of the descriptor format for parsing incomplete binary data as a descriptor. 40 | 41 | This essentially wraps every field after bLength and bDescriptorType in a `construct.Optional`. 42 | """ 43 | 44 | def _apply_optional(subcon): 45 | 46 | subcon_type = cls._get_subcon_field_type(subcon) 47 | 48 | # 49 | # If it's already Optional then we don't need to apply it again. 50 | # 51 | if isinstance(subcon_type, construct.Select): 52 | # construct uses a weird singleton to define Pass. `construct.core.Pass` would normally be 53 | # the type's name, but then they create a singleton of that same name, replacing that name and 54 | # making the type technically unnamable and only accessable via `type()`. 55 | if isinstance(subcon_type.subcons[1], type(construct.Pass)): 56 | return subcon 57 | 58 | return (subcon.name / construct.Optional(subcon_type)) * subcon.docs 59 | 60 | # First store the Subconstructs we don't want to modify: bLength and bDescriptorType, 61 | # as these are never optional. 62 | new_subcons = list(subcons[0:2]) 63 | 64 | # Then apply Optional to all of the rest of the Subconstructs. 65 | new_subcons.extend([_apply_optional(subcon) for subcon in subcons[2:]]) 66 | 67 | return DescriptorFormat(*new_subcons, _create_partial=False, **subconskw) 68 | 69 | 70 | @staticmethod 71 | def _to_detail_dictionary(descriptor, use_pretty_names=True): 72 | result = {} 73 | 74 | # Loop over every entry in our descriptor context, and try to get a 75 | # fancy name for it. 76 | for key, value in descriptor.items(): 77 | 78 | # Don't include any underscore-prefixed private members. 79 | if key.startswith('_'): 80 | continue 81 | 82 | # If there's no definition for the given key in our format, # skip it. 83 | if not hasattr(descriptor._format, key): 84 | continue 85 | 86 | # Try to apply any documentation on the given field rather than it's internal name. 87 | format_element = getattr(descriptor._format, key) 88 | detail_key = format_element.docs if (format_element.docs and use_pretty_names) else key 89 | 90 | # Finally, add the entry to our dict. 91 | result[detail_key] = value 92 | 93 | return result 94 | 95 | 96 | def parse(self, data, **context_keywords): 97 | """ Hook on the parent parse() method which attaches a few methods. """ 98 | 99 | # Use construct to run the parse itself... 100 | result = super().parse(bytes(data), **context_keywords) 101 | 102 | # ... and then bind our static to_detail_dictionary to it. 103 | result._format = self 104 | result._to_detail_dictionary = self._to_detail_dictionary.__get__(result, type(result)) 105 | 106 | return result 107 | 108 | 109 | class DescriptorNumber(construct.Const): 110 | """ Trivial wrapper class that denotes a particular Const as the descriptor number. """ 111 | 112 | def __init__(self, const): 113 | 114 | # If our descriptor number is an integer, instead of "raw", 115 | # convert it to a byte, first. 116 | if not isinstance(const, bytes): 117 | const = const.to_bytes(1, byteorder='little') 118 | 119 | # Grab the inner descriptor number represented by the constant. 120 | self.number = int.from_bytes(const, byteorder='little') 121 | 122 | # And pass this to the core constant class. 123 | super().__init__(const) 124 | 125 | # Finally, add a documentation string for the type. 126 | self.docs = "Descriptor type" 127 | 128 | 129 | def _parse(self, stream, context, path): 130 | const_bytes = super()._parse(stream, context, path) 131 | return const_bytes[0] 132 | 133 | 134 | def get_descriptor_number(self): 135 | """ Returns this constant's associated descriptor number.""" 136 | return self.number 137 | 138 | 139 | class BCDFieldAdapter(construct.Adapter): 140 | """ Construct adapter that dynamically parses BCD fields. """ 141 | 142 | def _decode(self, obj, context, path): 143 | hex_string = f"{obj:04x}" 144 | return float(f"{hex_string[0:2]}.{hex_string[2:]}") 145 | 146 | 147 | def _encode(self, obj, context, path): 148 | 149 | # Break the object down into its component parts... 150 | integer = int(obj) % 100 151 | percent = int(round(obj * 100)) % 100 152 | 153 | # ... make sure nothing is lost during conversion... 154 | if float(f"{integer:02}.{percent:02}") != obj: 155 | raise AssertionError("BCD fields must be in the format XX.YY") 156 | 157 | # ... and squish them into an integer. 158 | return int(f"{integer:02}{percent:02}", 16) 159 | 160 | 161 | 162 | class DescriptorField(construct.Subconstruct): 163 | """ 164 | Construct field definition that automatically adds fields of the proper 165 | size to Descriptor definitions. 166 | """ 167 | 168 | # 169 | # The C++-wonk operator overloading is Construct, not me, I swear. 170 | # 171 | 172 | # FIXME: these are really primitive views of these types; 173 | # we should extend these to get implicit parsing wherever possible 174 | USB_TYPES = { 175 | 'b' : construct.Int8ul, 176 | 'bcd' : BCDFieldAdapter(construct.Int16ul), # TODO: Create a BCD parser for this 177 | 'i' : construct.Int8ul, 178 | 'id' : construct.Int16ul, 179 | 'bm' : construct.Int8ul, 180 | 'w' : construct.Int16ul, 181 | } 182 | 183 | 184 | LENGTH_TYPES = { 185 | 1: construct.Int8ul, 186 | 2: construct.Int16ul, 187 | 3: construct.Int24ul, 188 | 4: construct.Int32ul, 189 | 8: construct.Int64ul 190 | } 191 | 192 | 193 | @staticmethod 194 | def _get_prefix(name): 195 | """ Returns the lower-case prefix on a USB descriptor name. """ 196 | prefix = [] 197 | 198 | # Silly loop that continues until we find an uppercase letter. 199 | # You'd be aghast at how the 'pythonic' answers look. 200 | for c in name: 201 | 202 | # Ignore leading underscores. 203 | if c == '_': 204 | continue 205 | 206 | if c.isupper(): 207 | break 208 | prefix.append(c) 209 | 210 | return ''.join(prefix) 211 | 212 | 213 | @classmethod 214 | def _get_type_for_name(cls, name): 215 | """ Returns the type that's appropriate for a given descriptor field name. """ 216 | 217 | try: 218 | return cls.USB_TYPES[cls._get_prefix(name)] 219 | except KeyError: 220 | raise ValueError("field names must be formatted per the USB standard!") 221 | 222 | 223 | def __init__(self, description="", default=None, *, length=None): 224 | self.description = description 225 | self.default = default 226 | self.length = length 227 | 228 | 229 | def __rtruediv__(self, field_name): 230 | # If we have a length, use it to figure out the type. 231 | # Otherwise, extract the type from the prefix. (Using a length 232 | # is useful for e.g. USB3 bitfields; which can span several bytes.) 233 | if self.length is not None: 234 | field_type = self.LENGTH_TYPES[self.length] 235 | else: 236 | field_type = self._get_type_for_name(field_name) 237 | 238 | if self.default is not None: 239 | field_type = construct.Default(field_type, self.default) 240 | 241 | # Build our subconstruct. Construct makes this look super weird, 242 | # but this is actually "we have a field with of type ". 243 | # In long form, we'll call it "description". 244 | return (field_name / field_type) * self.description 245 | 246 | 247 | # Convenience type that gets a descriptor's own length. 248 | DescriptorLength = \ 249 | construct.Rebuild(construct.Int8ul, construct.len_(construct.this)) \ 250 | * "Descriptor Length" 251 | -------------------------------------------------------------------------------- /usb_protocol/types/descriptors/uac1.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of usb-protocol. 3 | # 4 | """ 5 | Descriptors for USB Audio Class Devices (UAC), Release 1 6 | 7 | [Audio10] refers to "Universal Serial Bus Device Class Definition for Audio Devices", Release 1.0, March 18, 1998 8 | [Frmts10] refers to "Universal Serial Bus Device Class Definition for Audio Data Formats", Release 1.0, March 18, 1998 9 | [TermT10] refers to "Universal Serial Bus Device Class Definition for Terminal Types", Release 1.0, March 18, 1998 10 | """ 11 | 12 | import construct 13 | 14 | from enum import IntEnum 15 | 16 | from .standard import StandardDescriptorNumbers 17 | 18 | from ..descriptor import ( 19 | DescriptorField, 20 | DescriptorNumber, 21 | DescriptorFormat, 22 | ) 23 | 24 | 25 | class AudioInterfaceClassCodes(IntEnum): 26 | # As defined in [Audio10], Table A-1 27 | AUDIO = 0x01 28 | 29 | 30 | class AudioInterfaceSubclassCodes(IntEnum): 31 | # As defined in [Audio10], Table A-2 32 | INTERFACE_SUBCLASS_UNDEFINED = 0x00 33 | AUDIO_CONTROL = 0x01 34 | AUDIO_STREAMING = 0x02 35 | MIDI_STREAMING = 0x03 36 | 37 | 38 | class AudioInterfaceProtocolCodes(IntEnum): 39 | # As defined in [Audio10], Table A-3 40 | PR_PROTOCOL_UNDEFINED = 0x00 41 | 42 | 43 | class AudioClassSpecificDescriptorTypes(IntEnum): 44 | # As defined in [Audio10], Table A-4 45 | CS_UNDEFINED = 0x20 46 | CS_DEVICE = 0x21 47 | CS_CONFIGURATION = 0x22 48 | CS_STRING = 0x23 49 | CS_INTERFACE = 0x24 50 | CS_ENDPOINT = 0x25 51 | 52 | 53 | class AudioClassSpecificACInterfaceDescriptorSubtypes(IntEnum): 54 | # As defined in [Audio10], Table A-5 55 | AC_DESCRIPTOR_UNDEFINED = 0x00 56 | HEADER = 0x01 57 | INPUT_TERMINAL = 0x02 58 | OUTPUT_TERMINAL = 0x03 59 | MIXER_UNIT = 0x04 60 | SELECTOR_UNIT = 0x05 61 | FEATURE_UNIT = 0x06 62 | PROCESSING_UNIT = 0x07 63 | EXTENSION_UNIT = 0x08 64 | 65 | 66 | class AudioClassSpecificASInterfaceDescriptorSubtypes(IntEnum): 67 | # As defined in [Audio10], Table A-6 68 | AS_DESCRIPTOR_UNDEFINED = 0x00 69 | AS_GENERAL = 0x01 70 | FORMAT_TYPE = 0x02 71 | FORMAT_SPECIFIC = 0x03 72 | 73 | 74 | class ProcessingUnitProcessTypes(IntEnum): 75 | # As defined in [Audio10], Table A-7 76 | PROCESS_UNDEFINED = 0x00 77 | UP_DOWNMIX_PROCESS = 0x01 78 | DOLBY_PROLOGIC_PROCESS = 0x02 79 | _3D_STEREO_EXTENDER_PROCESS = 0x03 80 | REVERBERATION_PROCESS = 0x04 81 | CHORUS_PROCESS = 0x05 82 | DYN_RANGE_COMP_PROCESS = 0x06 83 | 84 | 85 | class AudioClassSpecificEndpointDescriptorSubtypes(IntEnum): 86 | # As defined in [Audio10], Table A-8 87 | DESCRIPTOR_UNDEFINED = 0x00 88 | EP_GENERAL = 0x01 89 | 90 | 91 | class AudioClassSpecificRequestCodes(IntEnum): 92 | # As defined in [Audio10], Table A-9 93 | REQUEST_CODE_UNDEFINED = 0x00 94 | SET_CUR = 0x01 95 | GET_CUR = 0x81 96 | SET_MIN = 0x02 97 | GET_MIN = 0x82 98 | SET_MAX = 0x03 99 | GET_MAX = 0x83 100 | SET_RES = 0x04 101 | GET_RES = 0x84 102 | SET_MEM = 0x05 103 | GET_MEM = 0x85 104 | GET_STAT = 0xFF 105 | 106 | 107 | class TerminalControlSelectors(IntEnum): 108 | # As defined in [Audio10], Table A-10 109 | TE_CONTROL_UNDEFINED = 0x00 110 | COPY_PROTECT_CONTROL = 0x01 111 | 112 | 113 | class FeatureUnitControlSelectors(IntEnum): 114 | # As defined in [Audio10], Table A-11 115 | FU_CONTROL_UNDEFINED = 0x00 116 | MUTE_CONTROL = 0x01 117 | VOLUME_CONTROL = 0x02 118 | BASS_CONTROL = 0x03 119 | MID_CONTROL = 0x04 120 | TREBLE_CONTROL = 0x05 121 | GRAPHIC_EQUALIZER_CONTROL = 0x06 122 | AUTOMATIC_GAIN_CONTROL = 0x07 123 | DELAY_CONTROL = 0x08 124 | BASS_BOOST_CONTROL = 0x09 125 | LOUDNESS_CONTROL = 0x0A 126 | 127 | 128 | class UpDownMixProcessingUnitControlSelectors(IntEnum): 129 | # As defined in [Audio10], Table A-12 130 | UD_CONTROL_UNDEFINED = 0x00 131 | UD_ENABLE_CONTROL = 0x01 132 | UD_MODE_SELECT_CONTROL = 0x02 133 | 134 | 135 | class DolbyProLogicProcessingUnitControlSelectors(IntEnum): 136 | # As defined in [Audio10], Table A-13 137 | DP_CONTROL_UNDEFINED = 0x00 138 | DP_ENABLE_CONTROL = 0x01 139 | DP_MODE_SELECT_CONTROL = 0x02 140 | 141 | 142 | class _3DStereoExtenderProcessingUnitControlSelectors(IntEnum): 143 | # As defined in [Audio10], Table A-14 144 | _3D_CONTROL_UNDEFINED = 0x00 145 | _3D_ENABLE_CONTROL = 0x01 146 | SPACIOUSNESS_CONTROL = 0x02 147 | 148 | 149 | class ReverberationProcessingUnitControlSelectors(IntEnum): 150 | # As defined in [Audio10], Table A-15 151 | RV_CONTROL_UNDEFINED = 0x00 152 | RV_ENABLE_CONTROL = 0x01 153 | REVERB_LEVEL_CONTROL = 0x02 154 | REVERB_TIME_CONTROL = 0x03 155 | REVERB_FEEDBACK_CONTROL = 0x04 156 | 157 | 158 | class ChorusProcessingUnitControlSelectors(IntEnum): 159 | # As defined in [Audio10], Table A-16 160 | CH_CONTROL_UNDEFINED = 0x00 161 | CH_ENABLE_CONTROL = 0x01 162 | CHORUS_LEVEL_CONTROL = 0x02 163 | CHORUS_RATE_CONTROL = 0x03 164 | CHORUS_DEPTH_CONTROL = 0x04 165 | 166 | 167 | class DynamicRangeCompressorProcessingUnitControlSelectors(IntEnum): 168 | # As defined in [Audio10], Table A-17 169 | DR_CONTROL_UNDEFINED = 0x00 170 | DR_ENABLE_CONTROL = 0x01 171 | COMPRESSION_RATE_CONTROL = 0x02 172 | MAXAMPL_CONTROL = 0x03 173 | THRESHOLD_CONTROL = 0x04 174 | ATTACK_TIME = 0x05 175 | RELEASE_TIME = 0x06 176 | 177 | 178 | class ExtensionUnitControlSelectors(IntEnum): 179 | # As defined in [Audio10], Table A-18 180 | XU_CONTROL_UNDEFINED = 0x00 181 | XU_ENABLE_CONTROL = 0x01 182 | 183 | 184 | class EndpointsControlSelectors(IntEnum): 185 | # As defined in [Audio10], Table A-19 186 | EP_CONTROL_UNDEFINED = 0x00 187 | SAMPLING_FREQ_CONTROL = 0x01 188 | PITCH_CONTROL = 0x02 189 | 190 | 191 | class USBTerminalTypes(IntEnum): 192 | # As defined in [TermT10], Table 2-1 193 | USB_UNDEFINED = 0x0100 194 | USB_STREAMING = 0x0101 195 | USB_VENDOR_SPECIFIC = 0x01FF 196 | 197 | 198 | class InputTerminalTypes(IntEnum): 199 | # As defined in [TermT10], Table 2-2 200 | INPUT_UNDEFINED = 0x0200 201 | MICROPHONE = 0x0201 202 | DESKTOP_MICROPHONE = 0x0202 203 | PERSONAL_MICROPHONE = 0x0203 204 | OMNI_DIRECTIONAL_MICROPHONE = 0x0204 205 | MICROPHONE_ARRAY = 0x0205 206 | PROCESSING_MICROPHONE_ARRAY = 0x0206 207 | 208 | 209 | class OutputTerminalTypes(IntEnum): 210 | # As defined in [TermT10], Table 2-3 211 | OUTPUT_UNDEFINED = 0x0300 212 | SPEAKER = 0x0301 213 | HEADPHONES = 0x0302 214 | DESKTOP_SPEAKER = 0x0304 215 | ROOM_SPEAKER = 0x0305 216 | COMMUNICATION_SPEAKER = 0x0306 217 | LOW_FREQUENCY_EFFECTS_SPEAKER = 0x0307 218 | 219 | 220 | class BidirectionalTerminalTypes(IntEnum): 221 | # As defined in [TermT10], Table 2-4 222 | BIDIRECTIONAL_UNDEFINED = 0x0400 223 | HANDSET = 0x0401 224 | HEADSET = 0x0402 225 | ECHO_SUPPRESSING_SPEAKERPHONE = 0x0404 226 | ECHO_CANCELING_SPEAKERPHONE = 0x0405 227 | 228 | 229 | class TelephonyTerminalTypes(IntEnum): 230 | # As defined in [TermT10], Table 2-5 231 | TELEPHONY_UNDEFINED = 0x0500 232 | PHONE_LINE = 0x0501 233 | TELEPHONE = 0x0502 234 | DOWN_LINE_PHONE = 0x0503 235 | 236 | 237 | class ExternalTerminalTypes(IntEnum): 238 | # As defined in [TermT10], Table 2-6 239 | EXTERNAL_UNDEFINED = 0x0600 240 | ANALOG_CONNECTOR = 0x0601 241 | DIGITAL_AUDIO_INTERFACE = 0x0602 242 | LINE_CONNECTOR = 0x0603 243 | SPDIF_INTERFACE = 0x0605 244 | IEEE_1394_DA_STREAM = 0x0606 245 | IEEE_1394_DV_STREAM_SOUNDTRACK = 0x0607 246 | 247 | 248 | class EmbeddedFunctionTerminalTypes(IntEnum): 249 | # As defined in [TermT10], Table 2-7 250 | EMBEDDED_UNDEFINED = 0x0700 251 | EQUALIZATION_NOISE = 0x0702 252 | CD_PLAYER = 0x0703 253 | DAT = 0x0704 254 | DCC = 0x0705 255 | ANALOG_TAPE = 0x0707 256 | PHONOGRAPH = 0x0708 257 | VCR_AUDIO = 0x0709 258 | VIDEO_DISC_AUDIO = 0x070A 259 | DVD_AUDIO = 0x070B 260 | TV_TUNER_AUDIO = 0x070C 261 | SATELLITE_RECEIVER_AUDIO = 0x070D 262 | CABLE_TUNER_AUDIO = 0x070E 263 | DSS_AUDIO = 0x070F 264 | RADIO_RECEIVER = 0x0710 265 | RADIO_TRANSMITTER = 0x0711 266 | MULTI_TRACK_RECORDER = 0x0712 267 | SYNTHESIZER = 0x0713 268 | 269 | 270 | # As defined in [Audio10], Table 4-17 271 | AudioControlInterruptEndpointDescriptor = DescriptorFormat( 272 | "bLength" / construct.Const(9, construct.Int8ul), 273 | "bDescriptorType" / DescriptorNumber(AudioClassSpecificDescriptorTypes.CS_ENDPOINT), 274 | "bEndpointAddress" / DescriptorField(description="The address of the endpoint, use USBDirection.*.from_endpoint_address()"), 275 | "bmAttributes" / DescriptorField(description="D1..0: Transfer type (0b11 = Interrupt)", default=0b11), 276 | "wMaxPacketSize" / DescriptorField(description="Maximum packet size this endpoint is capable of. Used here to pass 6-byte interrupt information.", default=6), 277 | "bInterval" / DescriptorField(description="Interval for polling the Interrupt endpoint"), 278 | "bRefresh" / DescriptorField(description="Reset to 0"), 279 | "bSynchAddress" / DescriptorField(description="Reset to 0"), 280 | ) 281 | 282 | -------------------------------------------------------------------------------- /usb_protocol/emitters/descriptors/microsoft10.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of usb_protocol. 3 | # 4 | """ Convenience emitters Microsoft OS 1.0 Descriptors. """ 5 | 6 | import unittest 7 | 8 | from contextlib import contextmanager 9 | 10 | from .. import emitter_for_format 11 | from ..descriptor import ComplexDescriptorEmitter 12 | 13 | from ...types.descriptors.microsoft10 import ( 14 | ExtendedCompatIDDescriptor, 15 | ExtendedCompatIDDescriptorFunction, 16 | ExtendedPropertiesDescriptor, 17 | ExtendedPropertiesDescriptorSection, 18 | RegistryTypes 19 | ) 20 | 21 | 22 | # Create our basic emitters... 23 | ExtendedCompatIDDescriptorFunctionEmitter = emitter_for_format(ExtendedCompatIDDescriptorFunction) 24 | 25 | # ... and complex emitters. 26 | class ExtendedCompatIDDescriptorEmitter(ComplexDescriptorEmitter): 27 | """ Emitter that creates a ExtendedCompatIDDescriptor """ 28 | DESCRIPTOR_FORMAT = ExtendedCompatIDDescriptor 29 | 30 | @contextmanager 31 | def Function(self): 32 | """ Context manager that allows addition of a function section to the descriptor. 33 | 34 | It can be used with a `with` statement; and yields an ExtendedCompatIDDescriptorFunctionEmitter 35 | that can be populated: 36 | 37 | with d.Function() as f: 38 | f.bFirstInterfaceNumber = 0 39 | f.compatibleID = 'WINUSB' 40 | 41 | This adds the relevant descriptor, automatically. 42 | """ 43 | descriptor = ExtendedCompatIDDescriptorFunctionEmitter() 44 | yield descriptor 45 | 46 | self.add_subordinate_descriptor(descriptor) 47 | 48 | def _pre_emit(self): 49 | self.bCount = len(self._subordinates) 50 | 51 | 52 | class ExtendedPropertiesDescriptorEmitter(ComplexDescriptorEmitter): 53 | """ Emitter that creates a ExtendedPropertiesDescriptor """ 54 | 55 | DESCRIPTOR_FORMAT = ExtendedPropertiesDescriptor 56 | 57 | @contextmanager 58 | def Property(self): 59 | """ Context manager that allows addition of a property section to the descriptor. 60 | 61 | It can be used with a `with` statement; and yields an ExtendedPropertiesDescriptorSectionEmitter 62 | that can be populated: 63 | 64 | with d.Property() as p: 65 | p.dwPropertyDataType = RegistryTypes.REG_EXPAND_SZ 66 | p.PropertyName = "Icons" 67 | p.PropertyData = "%SystemRoot%\\system32\\shell32.dll,-233" 68 | 69 | This adds the relevant descriptor, automatically. 70 | """ 71 | descriptor = ExtendedPropertiesDescriptorSectionEmitter() 72 | yield descriptor 73 | 74 | self.add_subordinate_descriptor(descriptor) 75 | 76 | def _pre_emit(self): 77 | self.wCount = len(self._subordinates) 78 | self.dwLength = 10 + sum(len(s) for s in self._subordinates) 79 | 80 | 81 | class ExtendedPropertiesDescriptorSectionEmitter(ComplexDescriptorEmitter): 82 | """ Emitter that creates a ExtendedPropertiesDescriptorSection """ 83 | 84 | DESCRIPTOR_FORMAT = ExtendedPropertiesDescriptorSection 85 | 86 | def _pre_emit(self): 87 | if self.dwPropertyDataType in (RegistryTypes.REG_SZ, RegistryTypes.REG_EXPAND_SZ, RegistryTypes.REG_LINK): 88 | self.PropertyData = self.PropertyData.encode('utf_16_le') + b'\0\0' 89 | elif self.dwPropertyDataType == RegistryTypes.REG_DWORD_LITTLE_ENDIAN: 90 | self.PropertyData = self.PropertyData.to_bytes(4, 'little') 91 | elif self.dwPropertyDataType == RegistryTypes.REG_DWORD_BIG_ENDIAN: 92 | self.PropertyData = self.PropertyData.to_bytes(4, 'big') 93 | elif self.dwPropertyDataType == RegistryTypes.REG_MULTI_SZ: 94 | strings = b'' 95 | for string in self.PropertyData: 96 | strings += string.encode('utf_16_le') + b'\0\0' 97 | self.PropertyData = strings 98 | 99 | 100 | class MicrosoftOS10DescriptorCollection: 101 | """ Object that builds a full collection of Microsoft OS 1.0 descriptors. """ 102 | 103 | def __init__(self): 104 | self._descriptors = {} 105 | 106 | def add_descriptor(self, descriptor, index=None): 107 | """ Adds a descriptor to our collection. 108 | 109 | Parameters: 110 | descriptor -- The descriptor to be added. 111 | index -- The index of the relevant descriptor. Defaults to None. 112 | """ 113 | 114 | # If this is an emitter rather than a descriptor itself, convert it. 115 | if hasattr(descriptor, 'emit'): 116 | descriptor = descriptor.emit() 117 | 118 | # Figure out the index for this descriptor... 119 | if index is None: 120 | index = (descriptor[7] << 8) | descriptor[6] 121 | 122 | # ... and store it. 123 | self._descriptors[index] = descriptor 124 | 125 | @contextmanager 126 | def ExtendedCompatIDDescriptor(self): 127 | descriptor = ExtendedCompatIDDescriptorEmitter() 128 | yield descriptor 129 | self.add_descriptor(descriptor) 130 | 131 | @contextmanager 132 | def ExtendedPropertiesDescriptor(self): 133 | descriptor = ExtendedPropertiesDescriptorEmitter() 134 | yield descriptor 135 | self.add_descriptor(descriptor) 136 | 137 | def __iter__(self): 138 | return ((index, descriptor) for index, descriptor in self._descriptors.items()) 139 | 140 | 141 | 142 | class MicrosoftOS10EmitterTests(unittest.TestCase): 143 | 144 | def test_compat_id_descriptor(self): 145 | 146 | # From Extended Compat ID OS Feature Descriptor Specification, Annex 1 147 | descriptor = bytes([ 148 | 0x58, 0x00, 0x00, 0x00, # Descriptor length 149 | 0x00, 0x01, # Version 1.00 150 | 0x04, 0x00, # Extended compat ID descriptor 151 | 0x03, # Number of function sections 152 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, # Reserved / padding 153 | 154 | # Function Section 1 155 | 0x00, # First interface number 156 | 0x01, # Reserved 157 | 0x52, 0x4e, 0x44, 0x49, 0x53, 0x00, 0x00, 0x00, # compatibleID 158 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, # subCompatibleID 159 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, # Reserved / padding 160 | 161 | # Function Section 2 162 | 0x02, # First interface number 163 | 0x01, # Reserved 164 | 0x4d, 0x54, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, # compatibleID 165 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, # subCompatibleID 166 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, # Reserved / padding 167 | 168 | # Function section 3 169 | 0x03, # First interface number 170 | 0x01, # Reserved 171 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, # compatibleID 172 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, # subCompatibleID 173 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 # Reserved / padding 174 | ]) 175 | 176 | # Create a trivial configuration descriptor... 177 | collection = MicrosoftOS10DescriptorCollection() 178 | 179 | with collection.ExtendedCompatIDDescriptor() as d: 180 | with d.Function() as i: 181 | i.bFirstInterfaceNumber = 0 182 | i.compatibleID = 'RNDIS' 183 | with d.Function() as i: 184 | i.bFirstInterfaceNumber = 2 185 | i.compatibleID = 'MTP' 186 | with d.Function() as i: 187 | i.bFirstInterfaceNumber = 3 188 | 189 | # ... and validate that it maches our reference descriptor. 190 | results = list(collection) 191 | self.assertEqual(results[0], (4, descriptor)) 192 | 193 | 194 | def test_extended_properties_descriptor(self): 195 | 196 | # Based on the Appendix from Extended Properties OS Feature Descriptor Specification 197 | descriptor = bytes([ 198 | # Header 199 | 0xa2, 0x00, 0x00, 0x00, # total descriptor length 200 | 0x00, 0x01, # bcdVersion 201 | 0x05, 0x00, # wIndex 202 | 0x02, 0x00, # Number of custom properties 203 | 204 | # Custom property 1 205 | 0x68, 0x00, 0x00, 0x00, # Size of this custom property section 206 | 0x02, 0x00, 0x00, 0x00, # Property data format 207 | 0x0c, 0x00, # Property name length 208 | 0x49, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x73, 0x00, 0x00, 0x00, # Property name 209 | 0x4e, 0x00, 0x00, 0x00, # Length of the property data 210 | 0x25, 0x00, 0x53, 0x00, 0x79, 0x00, 0x73, 0x00, 0x74, 0x00, 0x65, 0x00, # Property data 211 | 0x6d, 0x00, 0x52, 0x00, 0x6f, 0x00, 0x6f, 0x00, 0x74, 0x00, 0x25, 0x00, 212 | 0x5c, 0x00, 0x73, 0x00, 0x79, 0x00, 0x73, 0x00, 0x74, 0x00, 0x65, 0x00, 213 | 0x6d, 0x00, 0x33, 0x00, 0x32, 0x00, 0x5c, 0x00, 0x73, 0x00, 0x68, 0x00, 214 | 0x65, 0x00, 0x6c, 0x00, 0x6c, 0x00, 0x33, 0x00, 0x32, 0x00, 0x2e, 0x00, 215 | 0x64, 0x00, 0x6c, 0x00, 0x6c, 0x00, 0x2c, 0x00, 0x2d, 0x00, 0x32, 0x00, 216 | 0x33, 0x00, 0x33, 0x00, 0x00, 0x00, 217 | 218 | # Custom property 2 219 | 0x30, 0x00, 0x00, 0x00, # Size of this custom property section 220 | 0x01, 0x00, 0x00, 0x00, # Property data format 221 | 0x0c, 0x00, # Property name length 222 | 0x4c, 0x00, 0x61, 0x00, 0x62, 0x00, 0x65, 0x00, 0x6c, 0x00, 0x00, 0x00, # Property name 223 | 0x16, 0x00, 0x00, 0x00, # Length of the property data 224 | 0x58, 0x00, 0x59, 0x00, 0x5a, 0x00, 0x20, 0x00, 0x44, 0x00, 0x65, 0x00, # Property data 225 | 0x76, 0x00, 0x69, 0x00, 0x63, 0x00, 0x65, 0x00, 0x00, 0x00, 226 | ]) 227 | 228 | # Create a trivial configuration descriptor... 229 | collection = MicrosoftOS10DescriptorCollection() 230 | 231 | with collection.ExtendedPropertiesDescriptor() as d: 232 | with d.Property() as p: 233 | p.dwPropertyDataType = RegistryTypes.REG_EXPAND_SZ 234 | p.PropertyName = "Icons" 235 | p.PropertyData = "%SystemRoot%\\system32\\shell32.dll,-233" 236 | with d.Property() as p: 237 | p.dwPropertyDataType = RegistryTypes.REG_SZ 238 | p.PropertyName = "Label" 239 | p.PropertyData = "XYZ Device" 240 | 241 | # ... and validate that it maches our reference descriptor. 242 | results = list(collection) 243 | self.assertEqual(results[0], (5, descriptor)) 244 | 245 | 246 | if __name__ == "__main__": 247 | unittest.main() 248 | -------------------------------------------------------------------------------- /usb_protocol/types/descriptors/standard.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of usb-protocol. 3 | # 4 | """ 5 | Structures describing standard USB descriptors. Versions that support parsing incomplete binary data 6 | are available as `DescriptorType`.Partial, e.g. `DeviceDescriptor.Partial`, and are collectively available 7 | in the `usb_protocol.types.descriptors.partial.standard` module (which, like the structs in this module, 8 | can also be imported without `.standard`). 9 | """ 10 | 11 | import unittest 12 | from enum import IntEnum 13 | 14 | import construct 15 | from construct import this, Default 16 | 17 | from .. import LanguageIDs 18 | from ..descriptor import ( 19 | DescriptorField, 20 | DescriptorNumber, 21 | DescriptorFormat, 22 | BCDFieldAdapter, 23 | DescriptorLength 24 | ) 25 | 26 | 27 | class StandardDescriptorNumbers(IntEnum): 28 | """ Numbers of our standard descriptors. """ 29 | 30 | DEVICE = 1 31 | CONFIGURATION = 2 32 | STRING = 3 33 | INTERFACE = 4 34 | ENDPOINT = 5 35 | DEVICE_QUALIFIER = 6 36 | OTHER_SPEED_DESCRIPTOR = 7 37 | OTHER_SPEED = 7 38 | INTERFACE_POWER = 8 39 | OTG = 9 40 | DEBUG = 10 41 | INTERFACE_ASSOCIATION = 11 42 | 43 | # SuperSpeed only 44 | BOS = 15 45 | DEVICE_CAPABILITY = 16 46 | SUPERSPEED_USB_ENDPOINT_COMPANION = 48 47 | SUPERSPEEDPLUS_ISOCHRONOUS_ENDPOINT_COMPANION = 49 48 | 49 | 50 | class DeviceCapabilityTypes(IntEnum): 51 | """ Numbers for the SuperSpeed standard Device Capabilities. """ 52 | 53 | WIRELESS_USB = 1 54 | USB_2_EXTENSION = 2 55 | SUPERSPEED_USB = 3 56 | CONTAINER_ID = 4 57 | PLATFORM = 5 58 | POWER_DELIVERY_CAPABILITY = 6 59 | BATTERY_INFO_CAPABILITY = 7 60 | PD_CONSUMER_PORT_CAPABILITY = 8 61 | PD_PROVIDER_PORT_CAPABILITY = 9 62 | SUPERSPEED_PLUS = 10 63 | PRECISION_TIME_MEASUREMENT = 11 64 | WIRELESS_USB_EXTENSION = 12 65 | BILLBOARD = 13 66 | AUTHENTICATION = 14 67 | BILLBOARD_EXTENSION = 15 68 | CONFIGURATION_SUMMARY = 16 69 | 70 | 71 | 72 | DeviceDescriptor = DescriptorFormat( 73 | "bLength" / construct.Const(0x12, construct.Int8ul), 74 | "bDescriptorType" / DescriptorNumber(StandardDescriptorNumbers.DEVICE), 75 | "bcdUSB" / DescriptorField("USB Version", default=2.0), 76 | "bDeviceClass" / DescriptorField("Class", default=0), 77 | "bDeviceSubclass" / DescriptorField("Subclass", default=0), 78 | "bDeviceProtocol" / DescriptorField("Protocol", default=0), 79 | "bMaxPacketSize0" / DescriptorField("EP0 Max Pkt Size", default=64), 80 | "idVendor" / DescriptorField("Vendor ID"), 81 | "idProduct" / DescriptorField("Product ID"), 82 | "bcdDevice" / DescriptorField("Device Version", default=0), 83 | "iManufacturer" / DescriptorField("Manufacturer Str", default=0), 84 | "iProduct" / DescriptorField("Product Str", default=0), 85 | "iSerialNumber" / DescriptorField("Serial Number", default=0), 86 | "bNumConfigurations" / DescriptorField("Configuration Count"), 87 | ) 88 | 89 | 90 | 91 | ConfigurationDescriptor = DescriptorFormat( 92 | "bLength" / construct.Const(9, construct.Int8ul), 93 | "bDescriptorType" / DescriptorNumber(StandardDescriptorNumbers.CONFIGURATION), 94 | "wTotalLength" / DescriptorField("Length including subordinates"), 95 | "bNumInterfaces" / DescriptorField("Interface count"), 96 | "bConfigurationValue" / DescriptorField("Configuration number", default=1), 97 | "iConfiguration" / DescriptorField("Description string", default=0), 98 | "bmAttributes" / DescriptorField("Attributes", default=0x80), 99 | "bMaxPower" / DescriptorField("Max power consumption", default=250), 100 | ) 101 | 102 | # Field that automatically reflects a string descriptor's length. 103 | StringDescriptorLength = construct.Rebuild(construct.Int8ul, construct.len_(this.bString) * 2 + 2) 104 | 105 | StringDescriptor = DescriptorFormat( 106 | "bLength" / StringDescriptorLength, 107 | "bDescriptorType" / DescriptorNumber(StandardDescriptorNumbers.STRING), 108 | "bString" / construct.GreedyString("utf_16_le") 109 | ) 110 | 111 | 112 | StringLanguageDescriptorLength = \ 113 | construct.Rebuild(construct.Int8ul, construct.len_(this.wLANGID) * 2 + 2) 114 | 115 | StringLanguageDescriptor = DescriptorFormat( 116 | "bLength" / StringLanguageDescriptorLength, 117 | "bDescriptorType" / DescriptorNumber(StandardDescriptorNumbers.STRING), 118 | "wLANGID" / construct.GreedyRange(construct.Int16ul) 119 | ) 120 | 121 | 122 | InterfaceDescriptor = DescriptorFormat( 123 | "bLength" / construct.Const(9, construct.Int8ul), 124 | "bDescriptorType" / DescriptorNumber(StandardDescriptorNumbers.INTERFACE), 125 | "bInterfaceNumber" / DescriptorField("Interface number"), 126 | "bAlternateSetting" / DescriptorField("Alternate setting", default=0), 127 | "bNumEndpoints" / DescriptorField("Endpoints included"), 128 | "bInterfaceClass" / DescriptorField("Class", default=0xff), 129 | "bInterfaceSubclass" / DescriptorField("Subclass", default=0xff), 130 | "bInterfaceProtocol" / DescriptorField("Protocol", default=0xff), 131 | "iInterface" / DescriptorField("String index", default=0), 132 | ) 133 | 134 | 135 | EndpointDescriptor = DescriptorFormat( 136 | 137 | # [USB2.0: 9.6; USB Audio Device Class Definition 1.0: 4.6.1.1, 4.6.2.1] 138 | # Interfaces of the Audio 1.0 class extend their subordinate endpoint descriptors with 139 | # 2 additional bytes (extending it from 7 to 9 bytes). Thankfully, this is the only extension that 140 | # changes the length of a standard descriptor type, but we do have to handle this case in Construct. 141 | "bLength" / construct.Default(construct.OneOf(construct.Int8ul, [7, 9]), 7), 142 | "bDescriptorType" / DescriptorNumber(StandardDescriptorNumbers.ENDPOINT), 143 | "bEndpointAddress" / DescriptorField("Endpoint Address"), 144 | "bmAttributes" / DescriptorField("Attributes", default=2), 145 | "wMaxPacketSize" / DescriptorField("Maximum Packet Size", default=64), 146 | "bInterval" / DescriptorField("Polling interval", default=255), 147 | 148 | # 2 bytes that are only present on endpoint descriptors for Audio 1.0 class interfaces. 149 | ("bRefresh" / construct.Optional(construct.Int8ul)) * "Refresh Rate", 150 | ("bSynchAddress" / construct.Optional(construct.Int8ul)) * "Synch Endpoint Address", 151 | ) 152 | 153 | 154 | DeviceQualifierDescriptor = DescriptorFormat( 155 | "bLength" / construct.Const(9, construct.Int8ul), 156 | "bDescriptorType" / DescriptorNumber(StandardDescriptorNumbers.DEVICE_QUALIFIER), 157 | "bcdUSB" / DescriptorField("USB Version"), 158 | "bDeviceClass" / DescriptorField("Class"), 159 | "bDeviceSubclass" / DescriptorField("Subclass"), 160 | "bDeviceProtocol" / DescriptorField("Protocol"), 161 | "bMaxPacketSize0" / DescriptorField("EP0 Max Pkt Size"), 162 | "bNumConfigurations" / DescriptorField("Configuration Count"), 163 | "_bReserved" / construct.Optional(construct.Const(b"\0")) 164 | ) 165 | 166 | InterfaceAssociationDescriptor = DescriptorFormat( 167 | "bLength" / construct.Const(8, construct.Int8ul), 168 | "bDescriptorType" / DescriptorNumber(StandardDescriptorNumbers.INTERFACE_ASSOCIATION), 169 | "bFirstInterface" / DescriptorField("Interface number of the first interface that is associated with this function."), 170 | "bInterfaceCount" / DescriptorField("Number of contiguous interfaces that are associated with this function."), 171 | "bFunctionClass" / DescriptorField("Class code"), 172 | "bFunctionSubClass" / DescriptorField("Subclass code"), 173 | "bFunctionProtocol" / DescriptorField("Protocol code"), 174 | "iFunction" / DescriptorField("Index of string descriptor describing this function.", default=0), 175 | ) 176 | 177 | # 178 | # SuperSpeed descriptors 179 | # 180 | BinaryObjectStoreDescriptor = DescriptorFormat( 181 | "bLength" / construct.Const(0x5, construct.Int8ul), 182 | "bDescriptorType" / DescriptorNumber(StandardDescriptorNumbers.BOS), 183 | "wTotalLength" / DescriptorField("Total Length", default=5), 184 | "bNumDeviceCaps" / DescriptorField("Device Capability Descriptors", default=0), 185 | ) 186 | 187 | USB2ExtensionDescriptor = DescriptorFormat( 188 | "bLength" / construct.Const(0x7, construct.Int8ul), 189 | "bDescriptorType" / DescriptorNumber(StandardDescriptorNumbers.DEVICE_CAPABILITY), 190 | "bDevCapabilityType" / construct.Const(DeviceCapabilityTypes.USB_2_EXTENSION, construct.Int8ul), 191 | "bmAttributes" / DescriptorField("Attributes", default=0b10, length=4) 192 | ) 193 | 194 | SuperSpeedUSBDeviceCapabilityDescriptor = DescriptorFormat( 195 | "bLength" / construct.Const(0xA, construct.Int8ul), 196 | "bDescriptorType" / DescriptorNumber(StandardDescriptorNumbers.DEVICE_CAPABILITY), 197 | "bDevCapabilityType" / construct.Const(DeviceCapabilityTypes.SUPERSPEED_USB, construct.Int8ul), 198 | "bmAttributes" / DescriptorField("Attributes", default=0), 199 | "wSpeedsSupported" / DescriptorField("USB3 Speeds Supported", default=0b1000), 200 | "bFunctionalitySupport" / DescriptorField("Lowest Speed with Full Support", default=3), 201 | "bU1DevExitLat" / DescriptorField("U1 Exit Latency", default=0), 202 | "wU2DevExitLat" / DescriptorField("U2 Exit Latency", default=0) 203 | ) 204 | 205 | 206 | SuperSpeedEndpointCompanionDescriptor = DescriptorFormat( 207 | "bLength" / construct.Const(0x6, construct.Int8ul), 208 | "bDescriptorType" / DescriptorNumber(StandardDescriptorNumbers.SUPERSPEED_USB_ENDPOINT_COMPANION), 209 | "bMaxBurst" / DescriptorField("Maximum Burst Length", default=0), 210 | "bmAttributes" / DescriptorField("Extended Attributes", default=0), 211 | "wBytesPerInterval" / DescriptorField("Bytes Per Service Interval", default=0), 212 | ) 213 | 214 | 215 | 216 | class DescriptorParserCases(unittest.TestCase): 217 | 218 | STRING_DESCRIPTOR = bytes([ 219 | 40, # Length 220 | 3, # Type 221 | ord('G'), 0x00, 222 | ord('r'), 0x00, 223 | ord('e'), 0x00, 224 | ord('a'), 0x00, 225 | ord('t'), 0x00, 226 | ord(' '), 0x00, 227 | ord('S'), 0x00, 228 | ord('c'), 0x00, 229 | ord('o'), 0x00, 230 | ord('t'), 0x00, 231 | ord('t'), 0x00, 232 | ord(' '), 0x00, 233 | ord('G'), 0x00, 234 | ord('a'), 0x00, 235 | ord('d'), 0x00, 236 | ord('g'), 0x00, 237 | ord('e'), 0x00, 238 | ord('t'), 0x00, 239 | ord('s'), 0x00, 240 | ]) 241 | 242 | 243 | def test_string_descriptor_parse(self): 244 | 245 | # Parse the relevant string... 246 | parsed = StringDescriptor.parse(self.STRING_DESCRIPTOR) 247 | 248 | # ... and check the desriptor's fields. 249 | self.assertEqual(parsed.bLength, 40) 250 | self.assertEqual(parsed.bDescriptorType, 3) 251 | self.assertEqual(parsed.bString, "Great Scott Gadgets") 252 | 253 | 254 | def test_string_descriptor_build(self): 255 | data = StringDescriptor.build({ 256 | 'bString': "Great Scott Gadgets" 257 | }) 258 | 259 | self.assertEqual(data, self.STRING_DESCRIPTOR) 260 | 261 | 262 | def test_string_language_descriptor_build(self): 263 | data = StringLanguageDescriptor.build({ 264 | 'wLANGID': (LanguageIDs.ENGLISH_US,) 265 | }) 266 | 267 | self.assertEqual(data, b"\x04\x03\x09\x04") 268 | 269 | 270 | def test_device_descriptor(self): 271 | 272 | device_descriptor = [ 273 | 0x12, # Length 274 | 0x01, # Type 275 | 0x00, 0x02, # USB version 276 | 0xFF, # class 277 | 0xFF, # subclass 278 | 0xFF, # protocol 279 | 64, # ep0 max packet size 280 | 0x09, 0x12, # VID 281 | 0x01, 0x00, # PID 282 | 0x00, 0x00, # device rev 283 | 0x01, # manufacturer string 284 | 0x02, # product string 285 | 0x03, # serial number 286 | 0x01 # number of configurations 287 | ] 288 | 289 | # Parse the relevant string... 290 | parsed = DeviceDescriptor.parse(device_descriptor) 291 | 292 | # ... and check the desriptor's fields. 293 | self.assertEqual(parsed.bLength, 18) 294 | self.assertEqual(parsed.bDescriptorType, 1) 295 | self.assertEqual(parsed.bcdUSB, 2.0) 296 | self.assertEqual(parsed.bDeviceClass, 0xFF) 297 | self.assertEqual(parsed.bDeviceSubclass, 0xFF) 298 | self.assertEqual(parsed.bDeviceProtocol, 0xFF) 299 | self.assertEqual(parsed.bMaxPacketSize0, 64) 300 | self.assertEqual(parsed.idVendor, 0x1209) 301 | self.assertEqual(parsed.idProduct, 0x0001) 302 | self.assertEqual(parsed.bcdDevice, 0) 303 | self.assertEqual(parsed.iManufacturer, 1) 304 | self.assertEqual(parsed.iProduct, 2) 305 | self.assertEqual(parsed.iSerialNumber, 3) 306 | self.assertEqual(parsed.bNumConfigurations, 1) 307 | 308 | 309 | def test_bcd_constructor(self): 310 | 311 | emitter = BCDFieldAdapter(construct.Int16ul) 312 | result = emitter.build(1.4) 313 | 314 | self.assertEqual(result, b"\x40\x01") 315 | 316 | 317 | if __name__ == "__main__": 318 | unittest.main() 319 | -------------------------------------------------------------------------------- /usb_protocol/types/__init__.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of usb-protocol. 3 | # 4 | """ USB types -- defines enumerations that describe standard USB types """ 5 | 6 | from enum import Enum, IntFlag, IntEnum 7 | 8 | class USBDirection(IntEnum): 9 | """ Class representing USB directions. """ 10 | OUT = 0 11 | IN = 1 12 | 13 | def is_in(self): 14 | return self is self.IN 15 | 16 | def is_out(self): 17 | return self is self.OUT 18 | 19 | @classmethod 20 | def parse(cls, value): 21 | """ Helper that converts a numeric field into a direction. """ 22 | return cls(value) 23 | 24 | @classmethod 25 | def from_request_type(cls, request_type_int): 26 | """ Helper method that extracts the direction from a request_type integer. """ 27 | return cls(request_type_int >> 7) 28 | 29 | @classmethod 30 | def from_endpoint_address(cls, address): 31 | """ Helper method that extracts the direction from an endpoint address. """ 32 | return cls(address >> 7) 33 | 34 | def token(self): 35 | """ Generates the token corresponding to the given direction. """ 36 | return USBPacketID.IN if (self is self.IN) else USBPacketID.OUT 37 | 38 | def reverse(self): 39 | """ Returns the reverse of the given direction. """ 40 | return self.OUT if (self is self.IN) else self.IN 41 | 42 | 43 | def to_endpoint_address(self, endpoint_number): 44 | """ Helper method that converts and endpoint_number to an address, given direction. """ 45 | if self.is_in(): 46 | return endpoint_number | (1 << 7) 47 | else: 48 | return endpoint_number 49 | 50 | 51 | def endpoint_number_from_address(number): 52 | """ Helper function that converts an endpoint address to an endpoint number, discarding direction. """ 53 | return number & 0x7F 54 | 55 | 56 | class USBPIDCategory(IntFlag): 57 | """ Category constants for each of the groups that PIDs can fall under. """ 58 | 59 | SPECIAL = 0b00 60 | TOKEN = 0b01 61 | HANDSHAKE = 0b10 62 | DATA = 0b11 63 | 64 | MASK = 0b11 65 | 66 | 67 | 68 | class USBPacketID(IntFlag): 69 | """ Enumeration specifying all of the valid USB PIDs we can handle. """ 70 | 71 | # Token group (lsbs = 0b01). 72 | OUT = 0b0001 73 | IN = 0b1001 74 | SOF = 0b0101 75 | SETUP = 0b1101 76 | 77 | # Data group (lsbs = 0b11). 78 | DATA0 = 0b0011 79 | DATA1 = 0b1011 80 | DATA2 = 0b0111 81 | MDATA = 0b1111 82 | 83 | # Handshake group (lsbs = 0b10) 84 | ACK = 0b0010 85 | NAK = 0b1010 86 | STALL = 0b1110 87 | NYET = 0b0110 88 | 89 | # Special group. 90 | PRE = 0b1100 91 | ERR = 0b1100 92 | SPLIT = 0b1000 93 | PING = 0b0100 94 | 95 | # Flag representing that the PID seems invalid. 96 | PID_INVALID = 0b10000 97 | PID_CORE_MASK = 0b01111 98 | 99 | 100 | @classmethod 101 | def from_byte(cls, byte, skip_checks=False): 102 | """ Creates a PID object from a byte. """ 103 | 104 | # Convert the raw PID to an integer. 105 | pid_as_int = int.from_bytes(byte, byteorder='little') 106 | return cls.from_int(pid_as_int, skip_checks=skip_checks) 107 | 108 | 109 | @classmethod 110 | def from_int(cls, value, skip_checks=True): 111 | """ Create a PID object from an integer. """ 112 | 113 | PID_MASK = 0b1111 114 | INVERTED_PID_SHIFT = 4 115 | 116 | # Pull out the PID and its inverse from the byte. 117 | pid = cls(value & PID_MASK) 118 | inverted_pid = value >> INVERTED_PID_SHIFT 119 | 120 | # If we're not skipping checks, 121 | if not skip_checks: 122 | if (pid ^ inverted_pid) != PID_MASK: 123 | pid |= cls.PID_INVALID 124 | 125 | return cls(pid) 126 | 127 | 128 | @classmethod 129 | def from_name(cls, name): 130 | """ Create a PID object from a string representation of its name. """ 131 | return cls[name] 132 | 133 | 134 | @classmethod 135 | def parse(cls, value): 136 | """ Attempt to create a PID object from a number, byte, or string. """ 137 | 138 | if isinstance(value, bytes): 139 | return cls.from_byte(value) 140 | 141 | if isinstance(value, str): 142 | return cls.from_name(value) 143 | 144 | if isinstance(value, int): 145 | return cls.from_int(value) 146 | 147 | return cls(value) 148 | 149 | 150 | def category(self): 151 | """ Returns the USBPIDCategory that each given PID belongs to. """ 152 | return USBPIDCategory(self & USBPIDCategory.MASK) 153 | 154 | 155 | def is_data(self): 156 | """ Returns true iff the given PID represents a DATA packet. """ 157 | return self.category() is USBPIDCategory.DATA 158 | 159 | 160 | def is_token(self): 161 | """ Returns true iff the given PID represents a token packet. """ 162 | return self.category() is USBPIDCategory.TOKEN 163 | 164 | 165 | def is_handshake(self): 166 | """ Returns true iff the given PID represents a handshake packet. """ 167 | return self.category() is USBPIDCategory.HANDSHAKE 168 | 169 | 170 | def is_invalid(self): 171 | """ Returns true if this object is an attempt to encapsulate an invalid PID. """ 172 | return (self & self.PID_INVALID) 173 | 174 | def direction(self): 175 | """ Get a USB direction from a PacketID. """ 176 | 177 | if self is self.SOF: 178 | return None 179 | 180 | if self is self.SETUP or self is self.OUT: 181 | return USBDirection.OUT 182 | 183 | if self is self.IN: 184 | return USBDirection.IN 185 | 186 | raise ValueError("cannot determine the direction of a non-token PID") 187 | 188 | 189 | def summarize(self): 190 | """ Return a summary of the given packet. """ 191 | 192 | # By default, get the raw name. 193 | core_pid = self & self.PID_CORE_MASK 194 | name = core_pid.name 195 | 196 | if self.is_invalid(): 197 | return "{} (check-nibble invalid)".format(name) 198 | else: 199 | return name 200 | 201 | 202 | def byte(self): 203 | """ Return the PID's value with its upper nibble. """ 204 | 205 | inverted_pid = int(self) ^ 0b1111 206 | full_pid = (inverted_pid << 4) | int(self) 207 | 208 | return full_pid 209 | 210 | 211 | class USBRequestRecipient(IntEnum): 212 | """ Enumeration that describes each 'recipient' of a USB request field. """ 213 | 214 | DEVICE = 0 215 | INTERFACE = 1 216 | ENDPOINT = 2 217 | OTHER = 3 218 | 219 | RESERVED = 4 220 | 221 | @classmethod 222 | def from_integer(cls, value): 223 | """ Special factory that correctly handles reserved values. """ 224 | 225 | # If we have one of the reserved values; indicate so. 226 | if 4 <= value < 16: 227 | return cls.RESERVED 228 | 229 | # Otherwise, translate the raw value. 230 | return cls(value) 231 | 232 | 233 | @classmethod 234 | def from_request_type(cls, request_type_int): 235 | """ Helper method that extracts the type from a request_type integer. """ 236 | 237 | MASK = 0b11111 238 | return cls(request_type_int & MASK) 239 | 240 | 241 | class USBRequestType(IntEnum): 242 | """ Enumeration that describes each possible Type field for a USB request. """ 243 | 244 | STANDARD = 0 245 | CLASS = 1 246 | VENDOR = 2 247 | RESERVED = 3 248 | 249 | 250 | @classmethod 251 | def from_request_type(cls, request_type_int): 252 | 253 | """ Helper method that extracts the type from a request_type integer. """ 254 | SHIFT = 5 255 | MASK = 0b11 256 | 257 | return cls((request_type_int >> SHIFT) & MASK) 258 | 259 | 260 | 261 | class USBTransferType(IntEnum): 262 | CONTROL = 0 263 | ISOCHRONOUS = 1 264 | BULK = 2 265 | INTERRUPT = 3 266 | 267 | 268 | LANGUAGE_NAMES = { 269 | 0x0436: "Afrikaans", 270 | 0x041c: "Albanian", 271 | 0x0401: "Arabic (Saudi Arabia)", 272 | 0x0801: "Arabic (Iraq)", 273 | 0x0c01: "Arabic (Egypt)", 274 | 0x1001: "Arabic (Libya)", 275 | 0x1401: "Arabic (Algeria)", 276 | 0x1801: "Arabic (Morocco)", 277 | 0x1c01: "Arabic (Tunisia)", 278 | 0x2001: "Arabic (Oman)", 279 | 0x2401: "Arabic (Yemen)", 280 | 0x2801: "Arabic (Syria)", 281 | 0x2c01: "Arabic (Jordan)", 282 | 0x3001: "Arabic (Lebanon)", 283 | 0x3401: "Arabic (Kuwait)", 284 | 0x3801: "Arabic (U.A.E.)", 285 | 0x3c01: "Arabic (Bahrain)", 286 | 0x4001: "Arabic (Qatar)", 287 | 0x042b: "Armenian", 288 | 0x044d: "Assamese", 289 | 0x042c: "Azeri (Latin)", 290 | 0x082c: "Azeri (Cyrillic)", 291 | 0x042d: "Basque", 292 | 0x0423: "Belarussian", 293 | 0x0445: "Bengali", 294 | 0x0402: "Bulgarian", 295 | 0x0455: "Burmese", 296 | 0x0403: "Catalan", 297 | 0x0404: "Chinese (Taiwan)", 298 | 0x0804: "Chinese (PRC)", 299 | 0x0c04: "Chinese (Hong Kong SAR, PRC)", 300 | 0x1004: "Chinese (Singapore)", 301 | 0x1404: "Chinese (Macau SAR)", 302 | 0x041a: "Croatian", 303 | 0x0405: "Czech", 304 | 0x0406: "Danish", 305 | 0x0413: "Dutch (Netherlands)", 306 | 0x0813: "Dutch (Belgium)", 307 | 0x0409: "English (US)", 308 | 0x0809: "English (United Kingdom)", 309 | 0x0c09: "English (Australian)", 310 | 0x1009: "English (Canadian)", 311 | 0x1409: "English (New Zealand)", 312 | 0x1809: "English (Ireland)", 313 | 0x1c09: "English (South Africa)", 314 | 0x2009: "English (Jamaica)", 315 | 0x2409: "English (Caribbean)", 316 | 0x2809: "English (Belize)", 317 | 0x2c09: "English (Trinidad)", 318 | 0x3009: "English (Zimbabwe)", 319 | 0x3409: "English (Philippines)", 320 | 0x0425: "Estonian", 321 | 0x0438: "Faeroese", 322 | 0x0429: "Farsi", 323 | 0x040b: "Finnish", 324 | 0x040c: "French (Standard)", 325 | 0x080c: "French (Belgian)", 326 | 0x0c0c: "French (Canadian)", 327 | 0x100c: "French (Switzerland)", 328 | 0x140c: "French (Luxembourg)", 329 | 0x180c: "French (Monaco)", 330 | 0x0437: "Georgian", 331 | 0x0407: "German (Standard)", 332 | 0x0807: "German (Switzerland)", 333 | 0x0c07: "German (Austria)", 334 | 0x1007: "German (Luxembourg)", 335 | 0x1407: "German (Liechtenstein)", 336 | 0x0408: "Greek", 337 | 0x0447: "Gujarati", 338 | 0x040d: "Hebrew", 339 | 0x0439: "Hindi", 340 | 0x040e: "Hungarian", 341 | 0x040f: "Icelandic", 342 | 0x0421: "Indonesian", 343 | 0x0410: "Italian (Standard)", 344 | 0x0810: "Italian (Switzerland)", 345 | 0x0411: "Japanese", 346 | 0x044b: "Kannada", 347 | 0x0860: "Kashmiri (India)", 348 | 0x043f: "Kazakh", 349 | 0x0457: "Konkani", 350 | 0x0412: "Korean", 351 | 0x0812: "Korean (Johab)", 352 | 0x0426: "Latvian", 353 | 0x0427: "Lithuanian", 354 | 0x0827: "Lithuanian (Classic)", 355 | 0x042f: "Macedonian", 356 | 0x043e: "Malay (Malaysian)", 357 | 0x083e: "Malay (Brunei Darussalam)", 358 | 0x044c: "Malayalam", 359 | 0x0458: "Manipuri", 360 | 0x044e: "Marathi", 361 | 0x0861: "Nepali (India)", 362 | 0x0414: "Norwegian (Bokmal)", 363 | 0x0814: "Norwegian (Nynorsk)", 364 | 0x0448: "Oriya", 365 | 0x0415: "Polish", 366 | 0x0416: "Portuguese (Brazil)", 367 | 0x0816: "Portuguese (Standard)", 368 | 0x0446: "Punjabi", 369 | 0x0418: "Romanian", 370 | 0x0419: "Russian", 371 | 0x044f: "Sanskrit", 372 | 0x0c1a: "Serbian (Cyrillic)", 373 | 0x081a: "Serbian (Latin)", 374 | 0x0459: "Sindhi", 375 | 0x041b: "Slovak", 376 | 0x0424: "Slovenian", 377 | 0x040a: "Spanish (Traditional Sort)", 378 | 0x080a: "Spanish (Mexican)", 379 | 0x0c0a: "Spanish (Modern Sort)", 380 | 0x100a: "Spanish (Guatemala)", 381 | 0x140a: "Spanish (Costa Rica)", 382 | 0x180a: "Spanish (Panama)", 383 | 0x1c0a: "Spanish (Dominican Republic)", 384 | 0x200a: "Spanish (Venezuela)", 385 | 0x240a: "Spanish (Colombia)", 386 | 0x280a: "Spanish (Peru)", 387 | 0x2c0a: "Spanish (Argentina)", 388 | 0x300a: "Spanish (Ecuador)", 389 | 0x340a: "Spanish (Chile)", 390 | 0x380a: "Spanish (Uruguay)", 391 | 0x3c0a: "Spanish (Paraguay)", 392 | 0x400a: "Spanish (Bolivia)", 393 | 0x440a: "Spanish (El Salvador)", 394 | 0x480a: "Spanish (Honduras)", 395 | 0x4c0a: "Spanish (Nicaragua)", 396 | 0x500a: "Spanish (Puerto Rico)", 397 | 0x0430: "Sutu", 398 | 0x0441: "Swahili (Kenya)", 399 | 0x041d: "Swedish", 400 | 0x081d: "Swedish (Finland)", 401 | 0x0449: "Tamil", 402 | 0x0444: "Tatar (Tatarstan)", 403 | 0x044a: "Telugu", 404 | 0x041e: "Thai", 405 | 0x041f: "Turkish", 406 | 0x0422: "Ukrainian", 407 | 0x0420: "Urdu (Pakistan)", 408 | 0x0820: "Urdu (India)", 409 | 0x0443: "Uzbek (Latin)", 410 | 0x0843: "Uzbek (Cyrillic)", 411 | 0x042a: "Vietnamese", 412 | 0x04ff: "HID (Usage Data Descriptor)", 413 | 0xf0ff: "HID (Vendor Defined 1)", 414 | 0xf4ff: "HID (Vendor Defined 2)", 415 | 0xf8ff: "HID (Vendor Defined 3)", 416 | 0xfcff: "HID (Vendor Defined 4)", 417 | } 418 | 419 | 420 | class LanguageIDs(IntEnum): 421 | AFRIKAANS = 0X0436 422 | ALBANIAN = 0X041C 423 | ARABIC_SAUDI_ARABIA = 0X0401 424 | ARABIC_IRAQ = 0X0801 425 | ARABIC_EGYPT = 0X0C01 426 | ARABIC_LIBYA = 0X1001 427 | ARABIC_ALGERIA = 0X1401 428 | ARABIC_MOROCCO = 0X1801 429 | ARABIC_TUNISIA = 0X1C01 430 | ARABIC_OMAN = 0X2001 431 | ARABIC_YEMEN = 0X2401 432 | ARABIC_SYRIA = 0X2801 433 | ARABIC_JORDAN = 0X2C01 434 | ARABIC_LEBANON = 0X3001 435 | ARABIC_KUWAIT = 0X3401 436 | ARABIC_UAE = 0X3801 437 | ARABIC_BAHRAIN = 0X3C01 438 | ARABIC_QATAR = 0X4001 439 | ARMENIAN = 0X042B 440 | ASSAMESE = 0X044D 441 | AZERI_LATIN = 0X042C 442 | AZERI_CYRILLIC = 0X082C 443 | BASQUE = 0X042D 444 | BELARUSSIAN = 0X0423 445 | BENGALI = 0X0445 446 | BULGARIAN = 0X0402 447 | BURMESE = 0X0455 448 | CATALAN = 0X0403 449 | CHINESE_TAIWAN = 0X0404 450 | CHINESE_PRC = 0X0804 451 | CHINESE_HONG_KONG = 0X0C04 452 | CHINESE_SINGAPORE = 0X1004 453 | CHINESE_MACAU_SAR = 0X1404 454 | CROATIAN = 0X041A 455 | CZECH = 0X0405 456 | DANISH = 0X0406 457 | DUTCH_NETHERLANDS = 0X0413 458 | DUTCH_BELGIUM = 0X0813 459 | ENGLISH_US = 0X0409 460 | ENGLISH_UNITED_KINGDOM = 0X0809 461 | ENGLISH_AUSTRALIAN = 0X0C09 462 | ENGLISH_CANADIAN = 0X1009 463 | ENGLISH_NEW_ZEALAND = 0X1409 464 | ENGLISH_IRELAND = 0X1809 465 | ENGLISH_SOUTH_AFRICA = 0X1C09 466 | ENGLISH_JAMAICA = 0X2009 467 | ENGLISH_CARIBBEAN = 0X2409 468 | ENGLISH_BELIZE = 0X2809 469 | ENGLISH_TRINIDAD = 0X2C09 470 | ENGLISH_ZIMBABWE = 0X3009 471 | ENGLISH_PHILIPPINES = 0X3409 472 | ESTONIAN = 0X0425 473 | FAEROESE = 0X0438 474 | FARSI = 0X0429 475 | FINNISH = 0X040B 476 | FRENCH_STANDARD = 0X040C 477 | FRENCH_BELGIAN = 0X080C 478 | FRENCH_CANADIAN = 0X0C0C 479 | FRENCH_SWITZERLAND = 0X100C 480 | FRENCH_LUXEMBOURG = 0X140C 481 | FRENCH_MONACO = 0X180C 482 | GEORGIAN = 0X0437 483 | GERMAN_STANDARD = 0X0407 484 | GERMAN_SWITZERLAND = 0X0807 485 | GERMAN_AUSTRIA = 0X0C07 486 | GERMAN_LUXEMBOURG = 0X1007 487 | GERMAN_LIECHTENSTEIN = 0X1407 488 | GREEK = 0X0408 489 | GUJARATI = 0X0447 490 | HEBREW = 0X040D 491 | HINDI = 0X0439 492 | HUNGARIAN = 0X040E 493 | ICELANDIC = 0X040F 494 | INDONESIAN = 0X0421 495 | ITALIAN_STANDARD = 0X0410 496 | ITALIAN_SWITZERLAND = 0X0810 497 | JAPANESE = 0X0411 498 | KANNADA = 0X044B 499 | KASHMIRI_INDIA = 0X0860 500 | KAZAKH = 0X043F 501 | KONKANI = 0X0457 502 | KOREAN = 0X0412 503 | KOREAN_JOHAB = 0X0812 504 | LATVIAN = 0X0426 505 | LITHUANIAN = 0X0427 506 | LITHUANIAN_CLASSIC = 0X0827 507 | MACEDONIAN = 0X042F 508 | MALAY_MALAYSIAN = 0X043E 509 | MALAY_BRUNEI_DARUSSALAM = 0X083E 510 | MALAYALAM = 0X044C 511 | MANIPURI = 0X0458 512 | MARATHI = 0X044E 513 | NEPALI_INDIA = 0X0861 514 | NORWEGIAN_BOKMAL = 0X0414 515 | NORWEGIAN_NYNORSK = 0X0814 516 | ORIYA = 0X0448 517 | POLISH = 0X0415 518 | PORTUGUESE_BRAZIL = 0X0416 519 | PORTUGUESE_STANDARD = 0X0816 520 | PUNJABI = 0X0446 521 | ROMANIAN = 0X0418 522 | RUSSIAN = 0X0419 523 | SANSKRIT = 0X044F 524 | SERBIAN_CYRILLIC = 0X0C1A 525 | SERBIAN_LATIN = 0X081A 526 | SINDHI = 0X0459 527 | SLOVAK = 0X041B 528 | SLOVENIAN = 0X0424 529 | SPANISH_TRADITIONAL_SORT = 0X040A 530 | SPANISH_MEXICAN = 0X080A 531 | SPANISH_MODERN_SORT = 0X0C0A 532 | SPANISH_GUATEMALA = 0X100A 533 | SPANISH_COSTA_RICA = 0X140A 534 | SPANISH_PANAMA = 0X180A 535 | SPANISH_DOMINICAN_REPUBLIC = 0X1C0A 536 | SPANISH_VENEZUELA = 0X200A 537 | SPANISH_COLOMBIA = 0X240A 538 | SPANISH_PERU = 0X280A 539 | SPANISH_ARGENTINA = 0X2C0A 540 | SPANISH_ECUADOR = 0X300A 541 | SPANISH_CHILE = 0X340A 542 | SPANISH_URUGUAY = 0X380A 543 | SPANISH_PARAGUAY = 0X3C0A 544 | SPANISH_BOLIVIA = 0X400A 545 | SPANISH_EL_SALVADOR = 0X440A 546 | SPANISH_HONDURAS = 0X480A 547 | SPANISH_NICARAGUA = 0X4C0A 548 | SPANISH_PUERTO_RICO = 0X500A 549 | SUTU = 0X0430 550 | SWAHILI_KENYA = 0X0441 551 | SWEDISH = 0X041D 552 | SWEDISH_FINLAND = 0X081D 553 | TAMIL = 0X0449 554 | TATAR_TATARSTAN = 0X0444 555 | TELUGU = 0X044A 556 | THAI = 0X041E 557 | TURKISH = 0X041F 558 | UKRAINIAN = 0X0422 559 | URDU_PAKISTAN = 0X0420 560 | URDU_INDIA = 0X0820 561 | UZBEK_LATIN = 0X0443 562 | UZBEK_CYRILLIC = 0X0843 563 | VIETNAMESE = 0X042A 564 | HID_USAGE_DATA_DESCRIPTOR = 0X04FF 565 | HID_VENDOR_DEFINED_1 = 0XF0FF 566 | HID_VENDOR_DEFINED_2 = 0XF4FF 567 | HID_VENDOR_DEFINED_3 = 0XF8FF 568 | HID_VENDOR_DEFINED_4 = 0XFCFF 569 | 570 | 571 | class DescriptorTypes(IntEnum): 572 | DEVICE = 1 573 | CONFIGURATION = 2 574 | STRING = 3 575 | INTERFACE = 4 576 | ENDPOINT = 5 577 | DEVICE_QUALIFIER = 6 578 | OTHER_SPEED_CONFIGURATION = 7 579 | INTERFACE_POWER = 8 580 | HID = 33 581 | REPORT = 34 582 | 583 | 584 | class USBSynchronizationType(IntEnum): 585 | NONE = 0x00 586 | ASYNC = 0x01 587 | ADAPTIVE = 0x02 588 | SYNCHRONOUS = 0x03 589 | 590 | 591 | class USBUsageType(IntEnum): 592 | DATA = 0 593 | FEEDBACK = 1 594 | IMPLICIT_FEEDBACK = 2 595 | 596 | 597 | class USBStandardRequests(IntEnum): 598 | GET_STATUS = 0 599 | CLEAR_FEATURE = 1 600 | SET_FEATURE = 3 601 | SET_ADDRESS = 5 602 | GET_DESCRIPTOR = 6 603 | SET_DESCRIPTOR = 7 604 | GET_CONFIGURATION = 8 605 | SET_CONFIGURATION = 9 606 | GET_INTERFACE = 10 607 | SET_INTERFACE = 11 608 | SYNCH_FRAME = 12 609 | 610 | # USB3 only. 611 | SET_ENCRYPTION = 13 612 | GET_ENCRYPTION = 14 613 | SET_HANDSHAKE = 15 614 | GET_HANDSHAKE = 16 615 | SET_CONNECTION = 17 616 | SET_SECURITY_DATA = 18 617 | GET_SECURITY_DATA = 19 618 | SET_WUSB_DATA = 20 619 | LOOPBACK_DATA_WRITE = 21 620 | LOOPBACK_DATA_READ = 22 621 | SET_INTERFACE_DS = 23 622 | SET_SEL = 48 623 | SET_ISOCH_DELAY = 49 624 | 625 | class USBStandardFeatures(IntEnum): 626 | ENDPOINT_HALT = 0 627 | DEVICE_REMOTE_WAKEUP = 1 628 | TEST_MODE = 2 629 | -------------------------------------------------------------------------------- /usb_protocol/emitters/descriptors/standard.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of usb_protocol. 3 | # 4 | """ Convenience emitters for simple, standard descriptors. """ 5 | 6 | import unittest 7 | 8 | from contextlib import contextmanager 9 | 10 | from .. import emitter_for_format 11 | from ..descriptor import ComplexDescriptorEmitter 12 | 13 | from ...types import LanguageIDs 14 | from ...types.descriptors.standard import * 15 | 16 | 17 | # Create our basic emitters... 18 | DeviceDescriptorEmitter = emitter_for_format(DeviceDescriptor) 19 | StringDescriptorEmitter = emitter_for_format(StringDescriptor) 20 | StringLanguageDescriptorEmitter = emitter_for_format(StringLanguageDescriptor) 21 | DeviceQualifierDescriptor = emitter_for_format(DeviceQualifierDescriptor) 22 | 23 | # ... our basic superspeed emitters ... 24 | USB2ExtensionDescriptorEmitter = emitter_for_format(USB2ExtensionDescriptor) 25 | SuperSpeedUSBDeviceCapabilityDescriptorEmitter = emitter_for_format(SuperSpeedUSBDeviceCapabilityDescriptor) 26 | SuperSpeedEndpointCompanionDescriptorEmitter = emitter_for_format(SuperSpeedEndpointCompanionDescriptor) 27 | 28 | # ... convenience functions ... 29 | def get_string_descriptor(string): 30 | """ Generates a string descriptor for the relevant string. """ 31 | 32 | emitter = StringDescriptorEmitter() 33 | emitter.bString = string 34 | return emitter.emit() 35 | 36 | # ... and complex emitters. 37 | 38 | class InterfaceAssociationDescriptorEmitter(ComplexDescriptorEmitter): 39 | """ Emitter that creates an interface association descriptor. """ 40 | 41 | DESCRIPTOR_FORMAT = InterfaceAssociationDescriptor 42 | 43 | def _pre_emit(self): 44 | # Ensure that our function string is an index, if we can. 45 | if self._collection and hasattr(self, 'iFunction'): 46 | self.iFunction = self._collection.ensure_string_field_is_index(self.iFunction) 47 | 48 | 49 | class EndpointDescriptorEmitter(ComplexDescriptorEmitter): 50 | """ Emitter that creates an InterfaceDescriptor. """ 51 | 52 | DESCRIPTOR_FORMAT = EndpointDescriptor 53 | 54 | @contextmanager 55 | def SuperSpeedCompanion(self): 56 | """ Context manager that allows addition of a SuperSpeed Companion to this endpoint descriptor. 57 | 58 | It can be used with a `with` statement; and yields an SuperSpeedEndpointCompanionDescriptorEmitter 59 | that can be populated: 60 | 61 | with endpoint.SuperSpeedEndpointCompanion() as d: 62 | d.bMaxBurst = 1 63 | 64 | This adds the relevant descriptor, automatically. 65 | """ 66 | 67 | descriptor = SuperSpeedEndpointCompanionDescriptorEmitter() 68 | yield descriptor 69 | 70 | self.add_subordinate_descriptor(descriptor) 71 | 72 | 73 | class InterfaceDescriptorEmitter(ComplexDescriptorEmitter): 74 | """ Emitter that creates an InterfaceDescriptor. """ 75 | 76 | DESCRIPTOR_FORMAT = InterfaceDescriptor 77 | 78 | @contextmanager 79 | def EndpointDescriptor(self, *, add_default_superspeed=False): 80 | """ Context manager that allows addition of a subordinate endpoint descriptor. 81 | 82 | It can be used with a `with` statement; and yields an EndpointDesriptorEmitter 83 | that can be populated: 84 | 85 | with interface.EndpointDescriptor() as d: 86 | d.bEndpointAddress = 0x01 87 | d.bmAttributes = 0x80 88 | d.wMaxPacketSize = 64 89 | d.bInterval = 0 90 | 91 | This adds the relevant descriptor, automatically. 92 | """ 93 | 94 | descriptor = EndpointDescriptorEmitter() 95 | yield descriptor 96 | 97 | # If we're adding a default SuperSpeed extension, do so. 98 | if add_default_superspeed: 99 | with descriptor.SuperSpeedCompanion(): 100 | pass 101 | 102 | self.add_subordinate_descriptor(descriptor) 103 | 104 | 105 | def _pre_emit(self): 106 | 107 | # Count our endpoints, and update our internal count. 108 | self.bNumEndpoints = self._type_counts[StandardDescriptorNumbers.ENDPOINT] 109 | 110 | # Ensure that our interface string is an index, if we can. 111 | if self._collection and hasattr(self, 'iInterface'): 112 | self.iInterface = self._collection.ensure_string_field_is_index(self.iInterface) 113 | 114 | 115 | 116 | class ConfigurationDescriptorEmitter(ComplexDescriptorEmitter): 117 | """ Emitter that creates a configuration descriptor. """ 118 | 119 | DESCRIPTOR_FORMAT = ConfigurationDescriptor 120 | 121 | @contextmanager 122 | def InterfaceAssociationDescriptor(self): 123 | """ Context manager that allows addition of a subordinate interface association descriptor. 124 | 125 | It can be used with a `with` statement; and yields an InterfaceAssociationDescriptorEmitter 126 | that can be populated: 127 | 128 | with configuration.InterfaceAssociationDescriptor() as d: 129 | d.bFirstInterface = 0 130 | d.bInterfaceCount = 2 131 | [snip] 132 | 133 | This adds the relevant descriptor, automatically. 134 | """ 135 | descriptor = InterfaceAssociationDescriptorEmitter(collection=self._collection) 136 | yield descriptor 137 | 138 | self.add_subordinate_descriptor(descriptor) 139 | 140 | 141 | @contextmanager 142 | def InterfaceDescriptor(self): 143 | """ Context manager that allows addition of a subordinate interface descriptor. 144 | 145 | It can be used with a `with` statement; and yields an InterfaceDescriptorEmitter 146 | that can be populated: 147 | 148 | with interface.InterfaceDescriptor() as d: 149 | d.bInterfaceNumber = 0x01 150 | [snip] 151 | 152 | This adds the relevant descriptor, automatically. Note that populating derived 153 | fields such as bNumEndpoints aren't necessary; they'll be populated automatically. 154 | """ 155 | descriptor = InterfaceDescriptorEmitter(collection=self._collection) 156 | yield descriptor 157 | 158 | self.add_subordinate_descriptor(descriptor) 159 | 160 | 161 | def _pre_emit(self): 162 | 163 | # Count our interfaces. Alternate settings of the same interface do not count multiple times. 164 | self.bNumInterfaces = len(set([subordinate[2] for subordinate in self._subordinates if (subordinate[1] == StandardDescriptorNumbers.INTERFACE)])) 165 | 166 | # Figure out our total length. 167 | subordinate_length = sum(len(sub) for sub in self._subordinates) 168 | self.wTotalLength = subordinate_length + self.DESCRIPTOR_FORMAT.sizeof() 169 | 170 | # Ensure that our configuration string is an index, if we can. 171 | if self._collection and hasattr(self, 'iConfiguration'): 172 | self.iConfiguration = self._collection.ensure_string_field_is_index(self.iConfiguration) 173 | 174 | 175 | 176 | class DeviceDescriptorCollection: 177 | """ Object that builds a full collection of descriptors related to a given USB device. """ 178 | 179 | # Most systems seem happiest with en_US (ugh), so default to that. 180 | DEFAULT_SUPPORTED_LANGUAGES = [LanguageIDs.ENGLISH_US] 181 | 182 | 183 | def __init__(self, automatic_language_descriptor=True): 184 | """ 185 | Parameters: 186 | automatic_language_descriptor -- If set or not provided, a language descriptor will automatically 187 | be added if none exists. 188 | """ 189 | 190 | 191 | self._automatic_language_descriptor = automatic_language_descriptor 192 | 193 | # Create our internal descriptor tracker. 194 | # Keys are a tuple of (type, index). 195 | self._descriptors = {} 196 | 197 | # Track string descriptors as they're created. 198 | self._next_string_index = 1 199 | self._index_for_string = {} 200 | 201 | 202 | def ensure_string_field_is_index(self, field_value): 203 | """ Processes the given field value; if it's not an string index, converts it to one. 204 | 205 | Non-index-fields are converted to indices using `get_index_for_string`, which automatically 206 | adds the relevant fields to our string descriptor collection. 207 | """ 208 | 209 | if isinstance(field_value, int): 210 | return field_value 211 | else: 212 | return self.get_index_for_string(field_value) 213 | 214 | 215 | def get_index_for_string(self, string): 216 | """ Returns an string descriptor index for the given string. 217 | 218 | If a string descriptor already exists for the given string, its index is 219 | returned. Otherwise, a string descriptor is created. 220 | """ 221 | 222 | # If we already have a descriptor for this string, return it. 223 | if string in self._index_for_string: 224 | return self._index_for_string[string] 225 | 226 | 227 | # Otherwise, create one: 228 | 229 | # Allocate an index... 230 | index = self._next_string_index 231 | self._index_for_string[string] = index 232 | self._next_string_index += 1 233 | 234 | # ... store our string descriptor with it ... 235 | identifier = StandardDescriptorNumbers.STRING, index 236 | if isinstance(string, str): 237 | descriptor = get_string_descriptor(string) 238 | else: 239 | # Allow custom descriptors 240 | descriptor = string 241 | self._descriptors[identifier] = descriptor 242 | 243 | # ... and return our index. 244 | return index 245 | 246 | 247 | def add_descriptor(self, descriptor, index=0, descriptor_type=None): 248 | """ Adds a descriptor to our collection. 249 | 250 | Parameters: 251 | descriptor -- The descriptor to be added. 252 | index -- The index of the relevant descriptor. Defaults to 0. 253 | descriptor_type -- The type of the descriptor to be added. If `None`, this is automatically derived from the descriptor contents. 254 | """ 255 | 256 | # If this is an emitter rather than a descriptor itself, convert it. 257 | if hasattr(descriptor, 'emit'): 258 | descriptor = descriptor.emit() 259 | 260 | # Figure out the identifier (type + index) for this descriptor... 261 | if (descriptor_type is None): 262 | descriptor_type = descriptor[1] 263 | 264 | # Try to convert descriptor_type to StandardDescriptorNumbers ... 265 | if (type(descriptor_type) == int): 266 | try: 267 | descriptor_type = StandardDescriptorNumbers(descriptor_type) 268 | except ValueError: 269 | # If not possible, keep int 270 | pass 271 | 272 | identifier = descriptor_type, index 273 | 274 | # ... and store it. 275 | self._descriptors[identifier] = descriptor 276 | 277 | 278 | def add_language_descriptor(self, supported_languages=None): 279 | """ Adds a language descriptor to the list of device descriptors. 280 | 281 | Parameters: 282 | supported_languages -- A list of languages supported by the device. 283 | """ 284 | 285 | if supported_languages is None: 286 | supported_languages = self.DEFAULT_SUPPORTED_LANGUAGES 287 | 288 | descriptor = StringLanguageDescriptorEmitter() 289 | descriptor.wLANGID = supported_languages 290 | self.add_descriptor(descriptor) 291 | 292 | 293 | @contextmanager 294 | def DeviceDescriptor(self): 295 | """ Context manager that allows addition of a device descriptor. 296 | 297 | It can be used with a `with` statement; and yields an DeviceDescriptorEmitter 298 | that can be populated: 299 | 300 | with collection.DeviceDescriptor() as d: 301 | d.idVendor = 0xabcd 302 | d.idProduct = 0x1234 303 | [snip] 304 | 305 | This adds the relevant descriptor, automatically. 306 | """ 307 | descriptor = DeviceDescriptorEmitter() 308 | yield descriptor 309 | 310 | # If we have any string fields, ensure that they're indices before continuing. 311 | for field in ('iManufacturer', 'iProduct', 'iSerialNumber'): 312 | if hasattr(descriptor, field): 313 | value = getattr(descriptor, field) 314 | index = self.ensure_string_field_is_index(value) 315 | setattr(descriptor, field, index) 316 | 317 | self.add_descriptor(descriptor) 318 | 319 | 320 | @contextmanager 321 | def ConfigurationDescriptor(self): 322 | """ Context manager that allows addition of a configuration descriptor. 323 | 324 | It can be used with a `with` statement; and yields an ConfigurationDescriptorEmitter 325 | that can be populated: 326 | 327 | with collection.ConfigurationDescriptor() as d: 328 | d.bConfigurationValue = 1 329 | [snip] 330 | 331 | This adds the relevant descriptor, automatically. Note that populating derived 332 | fields such as bNumInterfaces aren't necessary; they'll be populated automatically. 333 | """ 334 | descriptor = ConfigurationDescriptorEmitter(collection=self) 335 | yield descriptor 336 | 337 | self.add_descriptor(descriptor) 338 | 339 | 340 | def _ensure_has_language_descriptor(self): 341 | """ ensures that we have a language descriptor; adding one if necessary.""" 342 | 343 | # if we're not automatically adding a language descriptor, we shouldn't do anything, 344 | # and we'll just ignore this. 345 | if not self._automatic_language_descriptor: 346 | return 347 | 348 | # if we don't have a language descriptor, add our default one. 349 | if (StandardDescriptorNumbers.STRING, 0) not in self._descriptors: 350 | self.add_language_descriptor() 351 | 352 | 353 | 354 | def get_descriptor_bytes(self, type_number: int, index: int = 0): 355 | """ Returns the raw, binary descriptor for a given descriptor type/index. 356 | 357 | Parmeters: 358 | type_number -- The descriptor type number. 359 | index -- The index of the relevant descriptor, if relevant. 360 | """ 361 | 362 | # If this is a request for a language descriptor, return one. 363 | if (type_number, index) == (StandardDescriptorNumbers.STRING, 0): 364 | self._ensure_has_language_descriptor() 365 | 366 | return self._descriptors[(type_number, index)] 367 | 368 | 369 | def __iter__(self): 370 | """ Allow iterating over each of our descriptors; yields (index, value, descriptor). """ 371 | self._ensure_has_language_descriptor() 372 | return ((number, index, desc) for ((number, index), desc) in self._descriptors.items()) 373 | 374 | 375 | 376 | 377 | class BinaryObjectStoreDescriptorEmitter(ComplexDescriptorEmitter): 378 | """ Emitter that creates a BinaryObjectStore descriptor. """ 379 | 380 | DESCRIPTOR_FORMAT = BinaryObjectStoreDescriptor 381 | 382 | @contextmanager 383 | def USB2Extension(self): 384 | """ Context manager that allows addition of a USB 2.0 Extension to this Binary Object Store. 385 | 386 | It can be used with a `with` statement; and yields an USB2ExtensionDescriptorEmitter 387 | that can be populated: 388 | 389 | with bos.USB2Extension() as e: 390 | e.bmAttributes = 1 391 | 392 | This adds the relevant descriptor, automatically. 393 | """ 394 | 395 | descriptor = USB2ExtensionDescriptorEmitter() 396 | yield descriptor 397 | 398 | self.add_subordinate_descriptor(descriptor) 399 | 400 | 401 | @contextmanager 402 | def SuperSpeedUSBDeviceCapability(self): 403 | """ Context manager that allows addition of a SS Device Capability to this Binary Object Store. 404 | 405 | It can be used with a `with` statement; and yields an SuperSpeedUSBDeviceCapabilityDescriptorEmitter 406 | that can be populated: 407 | 408 | with bos.SuperSpeedUSBDeviceCapability() as e: 409 | e.wSpeedSupported = 0b1110 410 | e.bFunctionalitySupport = 1 411 | 412 | This adds the relevant descriptor, automatically. 413 | """ 414 | 415 | descriptor = SuperSpeedUSBDeviceCapabilityDescriptorEmitter() 416 | yield descriptor 417 | 418 | self.add_subordinate_descriptor(descriptor) 419 | 420 | 421 | def _pre_emit(self): 422 | 423 | # Figure out the total length of our descriptor, including subordinates. 424 | subordinate_length = sum(len(sub) for sub in self._subordinates) 425 | self.wTotalLength = subordinate_length + self.DESCRIPTOR_FORMAT.sizeof() 426 | 427 | # Count our subordinate descriptors, and update our internal count. 428 | self.bNumDeviceCaps = len(self._subordinates) 429 | 430 | 431 | 432 | class SuperSpeedDeviceDescriptorCollection(DeviceDescriptorCollection): 433 | """ Object that builds a full collection of descriptors related to a given USB3 device. """ 434 | 435 | def __init__(self, automatic_descriptors=True): 436 | """ 437 | Parameters: 438 | automatic_descriptors -- If set or not provided, certian required descriptors will be 439 | be added if none exists. 440 | """ 441 | self._automatic_descriptors = automatic_descriptors 442 | super().__init__(automatic_language_descriptor=automatic_descriptors) 443 | 444 | 445 | @contextmanager 446 | def BOSDescriptor(self): 447 | """ Context manager that allows addition of a Binary Object Store descriptor. 448 | 449 | It can be used with a `with` statement; and yields an BinaryObjectStoreDescriptorEmitter 450 | that can be populated: 451 | 452 | with collection.BOSDescriptor() as d: 453 | [snip] 454 | 455 | This adds the relevant descriptor, automatically. Note that populating derived 456 | fields such as bNumDeviceCaps aren't necessary; they'll be populated automatically. 457 | """ 458 | descriptor = BinaryObjectStoreDescriptorEmitter() 459 | yield descriptor 460 | 461 | self.add_descriptor(descriptor) 462 | 463 | 464 | def add_default_bos_descriptor(self): 465 | """ Adds a default, empty BOS descriptor. """ 466 | 467 | # Create an empty BOS descriptor... 468 | descriptor = BinaryObjectStoreDescriptorEmitter() 469 | 470 | # ... populate our default required descriptors... 471 | descriptor.add_subordinate_descriptor(USB2ExtensionDescriptorEmitter()) 472 | descriptor.add_subordinate_descriptor(SuperSpeedUSBDeviceCapabilityDescriptorEmitter()) 473 | 474 | # ... and add it to our overall BOS descriptor. 475 | self.add_descriptor(descriptor) 476 | 477 | 478 | def _ensure_has_bos_descriptor(self): 479 | """ Ensures that we have a BOS descriptor; adding one if necessary.""" 480 | 481 | # If we're not automatically adding a language descriptor, we shouldn't do anything, 482 | # and we'll just ignore this. 483 | if not self._automatic_descriptors: 484 | return 485 | 486 | # If we don't have a language descriptor, add our default one. 487 | if (StandardDescriptorNumbers.BOS, 0) not in self._descriptors: 488 | self.add_default_bos_descriptor() 489 | 490 | 491 | def __iter__(self): 492 | """ Allow iterating over each of our descriptors; yields (index, value, descriptor). """ 493 | self._ensure_has_bos_descriptor() 494 | return super().__iter__() 495 | 496 | 497 | class EmitterTests(unittest.TestCase): 498 | 499 | def test_string_emitter(self): 500 | emitter = StringDescriptorEmitter() 501 | emitter.bString = "Hello" 502 | 503 | self.assertEqual(emitter.emit(), b"\x0C\x03H\0e\0l\0l\0o\0") 504 | 505 | 506 | def test_string_emitter_function(self): 507 | self.assertEqual(get_string_descriptor("Hello"), b"\x0C\x03H\0e\0l\0l\0o\0") 508 | 509 | 510 | def test_configuration_emitter(self): 511 | descriptor = bytes([ 512 | 513 | # config descriptor 514 | 12, # length 515 | 2, # type 516 | 25, 00, # total length 517 | 1, # num interfaces 518 | 1, # configuration number 519 | 0, # config string 520 | 0x80, # attributes 521 | 250, # max power 522 | 523 | # interface descriptor 524 | 9, # length 525 | 4, # type 526 | 0, # number 527 | 0, # alternate 528 | 1, # num endpoints 529 | 0xff, # class 530 | 0xff, # subclass 531 | 0xff, # protocol 532 | 0, # string 533 | 534 | # endpoint descriptor 535 | 7, # length 536 | 5, # type 537 | 0x01, # address 538 | 2, # attributes 539 | 64, 0, # max packet size 540 | 255, # interval 541 | ]) 542 | 543 | 544 | # Create a trivial configuration descriptor... 545 | emitter = ConfigurationDescriptorEmitter() 546 | 547 | with emitter.InterfaceDescriptor() as interface: 548 | interface.bInterfaceNumber = 0 549 | 550 | with interface.EndpointDescriptor() as endpoint: 551 | endpoint.bEndpointAddress = 1 552 | 553 | 554 | # ... and validate that it maches our reference descriptor. 555 | binary = emitter.emit() 556 | self.assertEqual(len(binary), len(descriptor)) 557 | 558 | 559 | def test_descriptor_collection(self): 560 | collection = DeviceDescriptorCollection() 561 | 562 | with collection.DeviceDescriptor() as d: 563 | d.idVendor = 0xdead 564 | d.idProduct = 0xbeef 565 | d.bNumConfigurations = 1 566 | 567 | d.iManufacturer = "Test Company" 568 | d.iProduct = "Test Product" 569 | 570 | 571 | with collection.ConfigurationDescriptor() as c: 572 | c.bConfigurationValue = 1 573 | 574 | with c.InterfaceAssociationDescriptor() as ia: 575 | ia.bFirstInterface = 1 576 | ia.bInterfaceCount = 1 577 | ia.bFunctionClass = 0xa 578 | ia.bFunctionSubClass = 0xb 579 | ia.bFunctionProtocol = 0xc 580 | ia.iFunction = "Test IAD" 581 | 582 | 583 | with c.InterfaceDescriptor() as i: 584 | i.bInterfaceNumber = 1 585 | 586 | with i.EndpointDescriptor() as e: 587 | e.bEndpointAddress = 0x81 588 | 589 | with i.EndpointDescriptor() as e: 590 | e.bEndpointAddress = 0x01 591 | 592 | 593 | results = list(collection) 594 | 595 | # We should wind up with six descriptor entries, as our endpoint/interface descriptors are 596 | # included in our configuration descriptor. 597 | self.assertEqual(len(results), 6) 598 | 599 | # Supported languages string. 600 | self.assertIn((3, 0, b'\x04\x03\x09\x04'), results) 601 | 602 | # Manufacturer / product string. 603 | self.assertIn((3, 1, b'\x1a\x03T\x00e\x00s\x00t\x00 \x00C\x00o\x00m\x00p\x00a\x00n\x00y\x00'), results) 604 | self.assertIn((3, 2, b'\x1a\x03T\x00e\x00s\x00t\x00 \x00P\x00r\x00o\x00d\x00u\x00c\x00t\x00'), results) 605 | 606 | # IAD function string 607 | self.assertIn((3, 3, b'\x12\x03T\x00e\x00s\x00t\x00 \x00I\x00A\x00D\x00'), results) 608 | 609 | # Device descriptor. 610 | self.assertIn((1, 0, b'\x12\x01\x00\x02\x00\x00\x00@\xad\xde\xef\xbe\x00\x00\x01\x02\x00\x01'), results) 611 | 612 | # Configuration descriptor, with subordinates. 613 | self.assertIn((2, 0, b'\t\x02\x28\x00\x01\x01\x00\x80\xfa\x08\x0b\x01\x01\x0a\x0b\x0c\x03\t\x04\x01\x00\x02\xff\xff\xff\x00\x07\x05\x81\x02@\x00\xff\x07\x05\x01\x02@\x00\xff'), results) 614 | 615 | 616 | def test_empty_descriptor_collection(self): 617 | collection = DeviceDescriptorCollection(automatic_language_descriptor=False) 618 | results = list(collection) 619 | self.assertEqual(len(results), 0) 620 | 621 | def test_automatic_language_descriptor(self): 622 | collection = DeviceDescriptorCollection(automatic_language_descriptor=True) 623 | results = list(collection) 624 | self.assertEqual(len(results), 1) 625 | 626 | if __name__ == "__main__": 627 | unittest.main() 628 | -------------------------------------------------------------------------------- /usb_protocol/types/descriptors/uac3.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of usb-protocol. 3 | # 4 | """ 5 | Descriptors for USB Audio Class Devices (UAC), Release 3 6 | 7 | [Audio30] refers to "Universal Serial Bus Device Class Definition for Audio Devices", Release 3.0, September 22, 2016 8 | [Frmts30] refers to "Universal Serial Bus Device Class Definition for Audio Data Formats", Release 3.0, September 22, 2016 9 | [TermT30] refers to "Universal Serial Bus Device Class Definition for Terminal Types", Release 3.0, May 31, 2006 10 | 11 | NOTE: This is not complete yet and will be extended as needed 12 | """ 13 | 14 | from usb_protocol.emitters import descriptor 15 | import unittest 16 | from enum import IntEnum 17 | 18 | import construct 19 | from construct import this, Default 20 | 21 | from .. import LanguageIDs 22 | from .standard import StandardDescriptorNumbers 23 | 24 | from ..descriptor import ( 25 | DescriptorField, 26 | DescriptorNumber, 27 | DescriptorFormat, 28 | BCDFieldAdapter, 29 | DescriptorLength 30 | ) 31 | 32 | 33 | class AudioInterfaceClassCodes(IntEnum): 34 | # As defined in [Audio30], Table A-4 35 | AUDIO = 0x01 36 | 37 | 38 | class AudioFunctionClassCodes(IntEnum): 39 | # As defined in [Audio30], Table A-1 40 | AUDIO_FUNCTION = AudioInterfaceClassCodes.AUDIO 41 | 42 | 43 | class AudioFunctionSubclassCodes(IntEnum): 44 | # As defined in [Audio30], Table A-2 45 | FUNCTION_SUBCLASS_UNDEFINED = 0x00 46 | FULL_ADC_3_0 = 0x01 47 | GENERIC_IO = 0x20 48 | HEADPHONE = 0x21 49 | SPEAKER = 0x22 50 | MICROPHONE = 0x23 51 | HEADSET = 0x24 52 | HEADSET_ADAPTER = 0x25 53 | SPEAKERPHONE = 0x26 54 | 55 | 56 | class AudioInterfaceSubclassCodes(IntEnum): 57 | # As defined in [Audio30], Table A-5 58 | INTERFACE_SUBCLASS_UNDEFINED = 0x00 59 | AUDIO_CONTROL = 0x01 60 | AUDIO_STREAMING = 0x02 61 | MIDI_STREAMING = 0x03 62 | 63 | 64 | class AudioInterfaceProtocolCodes(IntEnum): 65 | # As defined in [Audio30], Table A-6 66 | IP_VERSION_01_00 = 0x00 67 | IP_VERSION_02_00 = 0x20 68 | IP_VERSION_03_00 = 0x30 69 | 70 | 71 | class AudioFunctionProtocolCodes(IntEnum): 72 | # As defined in [Audio30], Table A-3 73 | FUNCTION_PROTOCOL_UNDEFINED = 0x00 74 | AF_VERSION_01_00 = AudioInterfaceProtocolCodes.IP_VERSION_01_00 75 | AF_VERSION_02_00 = AudioInterfaceProtocolCodes.IP_VERSION_02_00 76 | AF_VERSION_03_00 = AudioInterfaceProtocolCodes.IP_VERSION_03_00 77 | 78 | 79 | class AudioFunctionCategoryCodes(IntEnum): 80 | # As defined in [Audio30], Table A-7 81 | FUNCTION_SUBCLASS_UNDEFINED = 0x00 82 | DESKTOP_SPEAKER = 0x01 83 | HOME_THEATER = 0x02 84 | MICROPHONE = 0x03 85 | HEADSET = 0x04 86 | TELEPHONE = 0x05 87 | CONVERTER = 0x06 88 | VOICE_SOUND_RECORDER = 0x07 89 | IO_BOX = 0x08 90 | MUSICAL_INSTRUMENT = 0x09 91 | PRO_AUDIO = 0x0A 92 | AUDIO_VIDEO = 0x0B 93 | CONTROL_PANEL = 0x0C 94 | HEADPHONE = 0x0D 95 | GENERIC_SPEAKER = 0x0E 96 | HEADSET_ADAPTER = 0x0F 97 | SPEAKERPHONE = 0x10 98 | OTHER = 0xFF 99 | 100 | 101 | class AudioClassSpecificStandardDescriptorTypes(IntEnum): 102 | # As defined in [Audio30], Table A-8 103 | CS_UNDEFINED = 0x20 104 | CS_DEVICE = 0x21 105 | CS_CONFIGURATION = 0x22 106 | CS_STRING = 0x23 107 | CS_INTERFACE = 0x24 108 | CS_ENDPOINT = 0x25 109 | CS_CLUSTER = 0x26 110 | 111 | 112 | class ClusterDescriptorSubtypes(IntEnum): 113 | # As defined in [Audio30], Table A-9 114 | SUBTYPE_UNDEFINED = 0x00 115 | 116 | 117 | class ClusterDescriptorSegmentTypes(IntEnum): 118 | # As defined in [Audio30], Table A-10 119 | SEGMENT_UNDEFINED = 0x00 120 | CLUSTER_DESCRIPTION = 0x01 121 | CLUSTER_VENDOR_DEFINED = 0x1F 122 | CHANNEL_INFORMATION = 0x20 123 | CHANNEL_AMBISONIC = 0x21 124 | CHANNEL_DESCRIPTION = 0x22 125 | CHANNEL_VENDOR_DEFINED = 0xFE 126 | END_SEGMENT = 0xFF 127 | 128 | 129 | class ChannelPurposeDefinitions(IntEnum): 130 | # As defined in [Audio30], Table A-11 131 | PURPOSE_UNDEFINED = 0x00 132 | GENERIC_AUDIO = 0x01 133 | VOICE = 0x02 134 | SPEECH = 0x03 135 | AMBIENT = 0x04 136 | REFERENCE = 0x05 137 | ULTRASONIC = 0x06 138 | VIBROKINETIC = 0x07 139 | NON_AUDIO = 0xFF 140 | 141 | 142 | class AmbisonicComponentOrderingConventionTypes(IntEnum): 143 | # As defined in [Audio30], Table A-13 144 | ORD_TYPE_UNDEFINED = 0x00 145 | AMBISONIC_CHANNEL_NUMBER_ACN = 0x01 146 | FURSE_MALHAM = 0x02 147 | SINGLE_INDEX_DESIGNATION_SID = 0x03 148 | 149 | 150 | class AmbisonicNormalizationTypes(IntEnum): 151 | # As defined in [Audio30], Table A-14 152 | NORM_TYPE_UNDEFINED = 0x00 153 | MAX_N = 0x01 154 | SN3D = 0x02 155 | N3D = 0x03 156 | SN2D = 0x04 157 | N2D = 0x05 158 | 159 | 160 | class AudioClassSpecificACInterfaceDescriptorSubtypes(IntEnum): 161 | # As defined in [Audio30], Table A-15 162 | AC_DESCRIPTOR_UNDEFINED = 0x00 163 | HEADER = 0x01 164 | INPUT_TERMINAL = 0x02 165 | OUTPUT_TERMINAL = 0x03 166 | EXTENDED_TERMINAL = 0x04 167 | MIXER_UNIT = 0x05 168 | SELECTOR_UNIT = 0x06 169 | FEATURE_UNIT = 0x07 170 | EFFECT_UNIT = 0x08 171 | PROCESSING_UNIT = 0x09 172 | EXTENSION_UNIT = 0x0A 173 | CLOCK_SOURCE = 0x0B 174 | CLOCK_SELECTOR = 0x0C 175 | CLOCK_MULTIPLIER = 0x0D 176 | SAMPLE_RATE_CONVERTER = 0x0E 177 | CONNECTORS = 0x0F 178 | POWER_DOMAIN = 0x10 179 | 180 | 181 | class AudioClassSpecificASInterfaceDescriptorSubtypes(IntEnum): 182 | # As defined in [Audio30], Table A-16 183 | AS_DESCRIPTOR_UNDEFINED = 0x00 184 | AS_GENERAL = 0x01 185 | AS_VALID_FREQ_RANGE = 0x02 186 | 187 | 188 | class AudioClassSpecificStringDescriptorSubtypes(IntEnum): 189 | # As defined in [Audio30], Table A-17 190 | SUBTYPE_UNDEFINED = 0x00 191 | 192 | 193 | class ExtendedTerminalSegmentTypes(IntEnum): 194 | # As defined in [Audio30], Table A-18 195 | SEGMENT_UNDEFINED = 0x00 196 | TERMINAL_VENDOR_DEFINED = 0x1F 197 | CHANNEL_BANDWIDTH = 0x20 198 | CHANNEL_MAGNITUDE_RESPONSE = 0x21 199 | CHANNEL_MAGNITUDE_PHASE_RESPONSE = 0x22 200 | CHANNEL_POSITION_XYZ = 0x23 201 | CHANNEL_POSITION_R_THETA_PHI = 0x24 202 | CHANNEL_VENDOR_DEFINED = 0xFE 203 | END_SEGMENT = 0xFF 204 | 205 | 206 | class EffectUnitEffectTypes(IntEnum): 207 | # As defined in [Audio30], Table A-19 208 | EFFECT_UNDEFINED = 0x00 209 | PARAM_EQ_SECTION_EFFECT = 0x01 210 | REVERBERATION_EFFECT = 0x02 211 | MOD_DELAY_EFFECT = 0x03 212 | DYN_RANGE_COMP_EFFECT = 0x04 213 | 214 | 215 | class ProcessingUnitProcessTypes(IntEnum): 216 | # As defined in [Audio30], Table A20 217 | PROCESS_UNDEFINED = 0x0000 218 | UP_DOWNMIX_PROCESS = 0x0001 219 | STEREO_EXTENDER_PROCESS = 0x0002 220 | MULTI_FUNCTION_PROCESS = 0x0003 221 | 222 | 223 | class AudioClassSpecificEndpointDescriptorSubtypes(IntEnum): 224 | # As defined in [Audio30], Table A-21 225 | DESCRIPTOR_UNDEFINED = 0x00 226 | EP_GENERAL = 0x01 227 | 228 | 229 | class AudioClassSpecificRequestCodes(IntEnum): 230 | # As defined in [Audio30], Table A-22 231 | REQUEST_CODE_UNDEFINED = 0x00 232 | CUR = 0x01 233 | RANGE = 0x02 234 | MEM = 0x03 235 | INTEN = 0x04 236 | STRING = 0x05 237 | HIGH_CAPABILITY_DESCRIPTOR = 0x06 238 | 239 | 240 | class AudioControlInterfaceControlSelectors(IntEnum): 241 | # As defined in [Audio30], Table A-23 242 | AC_CONTROL_UNDEFINED = 0x00 243 | AC_ACTIVE_INTERFACE_CONTROL = 0x01 244 | AC_POWER_DOMAIN_CONTROL = 0x02 245 | 246 | 247 | class ClockSourceControlSelectors(IntEnum): 248 | # As defined in [Audio30], Table A-24 249 | CS_CONTROL_UNDEFINED = 0x00 250 | CS_SAM_FREQ_CONTROL = 0x01 251 | CS_CLOCK_VALID_CONTROL = 0x02 252 | 253 | 254 | class ClockSelectorControlSelectors(IntEnum): 255 | # As defined in [Audio30], Table A-24 256 | CX_CONTROL_UNDEFINED = 0x00 257 | CX_CLOCK_SELECTOR_CONTROL = 0x01 258 | 259 | 260 | class ClockMultiplierControlSelectors(IntEnum): 261 | # As defined in [Audio30], Table A-26 262 | CM_CONTROL_UNDEFINED = 0x00 263 | CM_NUMERATOR_CONTROL = 0x01 264 | CM_DENOMINATOR_CONTROL = 0x02 265 | 266 | 267 | class TerminalControlSelectors(IntEnum): 268 | # As defined in [Audio30], Table A-27 269 | TE_CONTROL_UNDEFINED = 0x00 270 | TE_INSERTION_CONTROL = 0x01 271 | TE_OVERLOAD_CONTROL = 0x02 272 | TE_UNDERFLOW_CONTROL = 0x03 273 | TE_OVERFLOW_CONTROL = 0x04 274 | TE_LATENCY_CONTROL = 0x05 275 | 276 | 277 | class MixerControlSelectors(IntEnum): 278 | # As defined in [Audio30], Table A-28 279 | MU_CONTROL_UNDEFINED = 0x00 280 | MU_MIXER_CONTROL = 0x01 281 | MU_UNDERFLOW_CONTROL = 0x02 282 | MU_OVERFLOW_CONTROL = 0x03 283 | MU_LATENCY_CONTROL = 0x04 284 | 285 | 286 | class SelectorControlSelectors(IntEnum): 287 | # As defined in [Audio30], Table A-29 288 | SU_CONTROL_UNDEFINED = 0x00 289 | SU_SELECTOR_CONTROL = 0x01 290 | SU_LATENCY_CONTROL = 0x02 291 | 292 | 293 | class FeatureUnitControlSelectors(IntEnum): 294 | # As defined in [Audio30], Table A-30 295 | FU_CONTROL_UNDEFINED = 0x00 296 | FU_MUTE_CONTROL = 0x01 297 | FU_VOLUME_CONTROL = 0x02 298 | FU_BASS_CONTROL = 0x03 299 | FU_MID_CONTROL = 0x04 300 | FU_TREBLE_CONTROL = 0x05 301 | FU_GRAPHIC_EQUALIZER_CONTROL = 0x06 302 | FU_AUTOMATIC_GAIN_CONTROL = 0x07 303 | FU_DELAY_CONTROL = 0x08 304 | FU_BASS_BOOST_CONTROL = 0x09 305 | FU_LOUDNESS_CONTROL = 0x0A 306 | FU_INPUT_GAIN_CONTROL = 0x0B 307 | FU_INPUT_GAIN_PAD_CONTROL = 0x0C 308 | FU_PHASE_INVERTER_CONTROL = 0x0D 309 | FU_UNDERFLOW_CONTROL = 0x0E 310 | FU_OVERFLOW_CONTROL = 0x0F 311 | FU_LATENCY_CONTROL = 0x10 312 | 313 | 314 | class ParametricEqualizerSectionEffectUnitControlSelectors(IntEnum): 315 | # As defined in [Audio30], Table A-31 316 | PE_CONTROL_UNDEFINED = 0x00 317 | PE_ENABLE_CONTROL = 0x01 318 | PE_CENTERFREQ_CONTROL = 0x02 319 | PE_QFACTOR_CONTROL = 0x03 320 | PE_GAIN_CONTROL = 0x04 321 | PE_UNDERFLOW_CONTROL = 0x05 322 | PE_OVERFLOW_CONTROL = 0x06 323 | PE_LATENCY_CONTROL = 0x07 324 | 325 | 326 | class ReverberationEffectUnitControlSelectors(IntEnum): 327 | # As defined in [Audio30], Table A-32 328 | RV_CONTROL_UNDEFINED = 0x00 329 | RV_ENABLE_CONTROL = 0x01 330 | RV_TYPE_CONTROL = 0x02 331 | RV_LEVEL_CONTROL = 0x03 332 | RV_TIME_CONTROL = 0x04 333 | RV_FEEDBACK_CONTROL = 0x05 334 | RV_PREDELAY_CONTROL = 0x06 335 | RV_DENSITY_CONTROL = 0x07 336 | RV_HIFREQ_ROLLOFF_CONTROL = 0x08 337 | RV_UNDERFLOW_CONTROL = 0x09 338 | RV_OVERFLOW_CONTROL = 0x0A 339 | RV_LATENCY_CONTROL = 0x0B 340 | 341 | 342 | class ModulationDelayEffectUnitControlSelectors(IntEnum): 343 | # As defined in [Audio30], Table A-33 344 | MD_CONTROL_UNDEFINED = 0x00 345 | MD_ENABLE_CONTROL = 0x01 346 | MD_BALANCE_CONTROL = 0x02 347 | MD_RATE_CONTROL = 0x03 348 | MD_DEPTH_CONTROL = 0x04 349 | MD_TIME_CONTROL = 0x05 350 | MD_FEEDBACK_CONTROL = 0x06 351 | MD_UNDERFLOW_CONTROL = 0x07 352 | MD_OVERFLOW_CONTROL = 0x08 353 | MD_LATENCY_CONTROL = 0x09 354 | 355 | 356 | class DynamicRangeCompressorEffectUnitControlSelectors(IntEnum): 357 | # As defined in [Audio30], Table A-34 358 | DR_CONTROL_UNDEFINED = 0x00 359 | DR_ENABLE_CONTROL = 0x01 360 | DR_COMPRESSION_RATE_CONTROL = 0x02 361 | DR_MAXAMPL_CONTROL = 0x03 362 | DR_THRESHOLD_CONTROL = 0x04 363 | DR_ATTACK_TIME_CONTROL = 0x05 364 | DR_RELEASE_TIME_CONTROL = 0x06 365 | DR_UNDERFLOW_CONTROL = 0x07 366 | DR_OVERFLOW_CONTROL = 0x08 367 | DR_LATENCY_CONTROL = 0x09 368 | 369 | 370 | class UpDownMixProcessingUnitControlSelectors(IntEnum): 371 | # As defined in [Audio30], Table A-35 372 | UD_CONTROL_UNDEFINED = 0x00 373 | UD_MODE_SELECT_CONTROL = 0x01 374 | UD_UNDERFLOW_CONTROL = 0x02 375 | UD_OVERFLOW_CONTROL = 0x03 376 | UD_LATENCY_CONTROL = 0x04 377 | 378 | 379 | class StereoExtenderProcessingUnitControlSelectors(IntEnum): 380 | # As defined in [Audio30], Table A-36 381 | ST_EXT_CONTROL_UNDEFINED = 0x00 382 | ST_EXT_WIDTH_CONTROL = 0x01 383 | ST_EXT_UNDERFLOW_CONTROL = 0x02 384 | ST_EXT_OVERFLOW_CONTROL = 0x03 385 | ST_EXT_LATENCY_CONTROL = 0x04 386 | 387 | 388 | class ExtensionUnitControlSelectors(IntEnum): 389 | # As defined in [Audio30], Table A-37 390 | XU_CONTROL_UNDEFINED = 0x00 391 | XU_UNDERFLOW_CONTROL = 0x01 392 | XU_OVERFLOW_CONTROL = 0x02 393 | XU_LATENCY_CONTROL = 0x03 394 | 395 | 396 | class AudioStreamingInterfaceControlSelectors(IntEnum): 397 | # As defined in [Audio30], Table A-38 398 | AS_CONTROL_UNDEFINED = 0x00 399 | AS_ACT_ALT_SETTING_CONTROL = 0x01 400 | AS_VAL_ALT_SETTINGS_CONTROL = 0x02 401 | AS_AUDIO_DATA_FORMAT_CONTROL = 0x03 402 | 403 | 404 | class EndpointControlSelectors(IntEnum): 405 | # As defined in [Audio30], Table A-39 406 | EP_CONTROL_UNDEFINED = 0x00 407 | EP_PITCH_CONTROL = 0x01 408 | EP_DATA_OVERRUN_CONTROL = 0x02 409 | EP_DATA_UNDERRUN_CONTROL = 0x03 410 | 411 | 412 | class USBTerminalTypes(IntEnum): 413 | # As defined in [TermT30], Table 2-1 414 | USB_UNDEFINED = 0x0100 415 | USB_STREAMING = 0x0101 416 | USB_VENDOR_SPECIFIC = 0x01FF 417 | 418 | 419 | class InputTerminalTypes(IntEnum): 420 | # As defined in [TermT30], Table 2-2 421 | INPUT_UNDEFINED = 0x0200 422 | MICROPHONE = 0x0201 423 | DESKTOP_MICROPHONE = 0x0202 424 | PERSONAL_MICROPHONE = 0x0203 425 | OMNI_DIRECTIONAL_MICROPHONE = 0x0204 426 | MICROPHONE_ARRAY = 0x0205 427 | PROCESSING_MICROPHONE_ARRAY = 0x0206 428 | 429 | 430 | class OutputTerminalTypes(IntEnum): 431 | # As defined in [TermT30], Table 2-3 432 | OUTPUT_UNDEFINED = 0x0300 433 | SPEAKER = 0x0301 434 | HEADPHONES = 0x0302 435 | DESKTOP_SPEAKER = 0x0304 436 | ROOM_SPEAKER = 0x0305 437 | COMMUNICATION_SPEAKER = 0x0306 438 | LOW_FREQUENCY_EFFECTS_SPEAKER = 0x0307 439 | 440 | 441 | class BidirectionalTerminalTypes(IntEnum): 442 | # As defined in [TermT30], Table 2-4 443 | BIDIRECTIONAL_UNDEFINED = 0x0400 444 | HANDSET = 0x0401 445 | HEADSET = 0x0402 446 | ECHO_SUPPRESSING_SPEAKERPHONE = 0x0404 447 | ECHO_CANCELING_SPEAKERPHONE = 0x0405 448 | 449 | 450 | class TelephonyTerminalTypes(IntEnum): 451 | # As defined in [TermT30], Table 2-5 452 | TELEPHONY_UNDEFINED = 0x0500 453 | PHONE_LINE = 0x0501 454 | TELEPHONE = 0x0502 455 | DOWN_LINE_PHONE = 0x0503 456 | 457 | 458 | class ExternalTerminalTypes(IntEnum): 459 | # As defined in [TermT30], Table 2-6 460 | EXTERNAL_UNDEFINED = 0x0600 461 | ANALOG_CONNECTOR = 0x0601 462 | DIGITAL_AUDIO_INTERFACE = 0x0602 463 | LINE_CONNECTOR = 0x0603 464 | SPDIF_INTERFACE = 0x0605 465 | IEEE_1394_DA_STREAM = 0x0606 466 | IEEE_1394_DV_STREAM_SOUNDTRACK = 0x0607 467 | ADAT_LIGHTPIPE = 0x0608 468 | TDIF = 0x0609 469 | MADI = 0x060A 470 | 471 | 472 | class EmbeddedFunctionTerminalTypes(IntEnum): 473 | # As defined in [TermT30], Table 2-7 474 | EMBEDDED_UNDEFINED = 0x0700 475 | EQUALIZATION_NOISE = 0x0702 476 | CD_PLAYER = 0x0703 477 | DAT = 0x0704 478 | DCC = 0x0705 479 | ANALOG_TAPE = 0x0707 480 | PHONOGRAPH = 0x0708 481 | VCR_AUDIO = 0x0709 482 | VIDEO_DISC_AUDIO = 0x070A 483 | DVD_AUDIO = 0x070B 484 | TV_TUNER_AUDIO = 0x070C 485 | SATELLITE_RECEIVER_AUDIO = 0x070D 486 | CABLE_TUNER_AUDIO = 0x070E 487 | DSS_AUDIO = 0x070F 488 | RADIO_RECEIVER = 0x0710 489 | RADIO_TRANSMITTER = 0x0711 490 | MULTI_TRACK_RECORDER = 0x0712 491 | SYNTHESIZER = 0x0713 492 | PIANO = 0x0714 493 | GUITAR = 0x0715 494 | DRUMS_RHYTHM = 0x0716 495 | OTHER_MUSICAL_INSTRUMENT = 0x0717 496 | 497 | 498 | class ConnectorTypes(IntEnum): 499 | UNDEFINED = 0x00 500 | PHONE_CONNECTOR_2_5_MM = 0x01 501 | PHONE_CONNECTOR_3_5_MM = 0x02 502 | PHONE_CONNECTOR_6_35_MM = 0x03 503 | XLR_6_35MM_COMBO_CONNECTOR = 0x04 504 | XLR = 0x05 505 | OPTICAL_3_5MM_COMBO_CONNECTOR = 0x06 506 | RCA = 0x07 507 | BNC = 0x08 508 | BANANA = 0x09 509 | BINDING_POST = 0x0A 510 | SPEAKON = 0x0B 511 | SPRING_CLIP = 0x0C 512 | SCREW_TYPE = 0x0D 513 | DIN = 0x0E 514 | MINI_DIN = 0x0F 515 | EUROBLOCK = 0x10 516 | USB_TYPE_C = 0x11 517 | RJ_11 = 0x12 518 | RJ_45 = 0x13 519 | TOSLINK = 0x14 520 | HDMI = 0x15 521 | Mini_HDMI = 0x16 522 | Micro_HDMI = 0x17 523 | DP = 0x18 524 | MINI_DP = 0x19 525 | D_SUB = 0x1A 526 | THUNDERBOLT = 0x1B 527 | LIGHTNING = 0x1C 528 | WIRELESS = 0x1D 529 | USB_STANDARD_A = 0x1E 530 | USB_STANDARD_B = 0x1F 531 | USB_MINI_B = 0x20 532 | USB_MICRO_B = 0x21 533 | USB_MICRO_AB = 0x22 534 | USB_3_0_MICRO_B = 0x23 535 | 536 | class AudioDataFormats(IntEnum): 537 | PCM = (1 << 0) 538 | PCM8 = (1 << 1) 539 | IEEE_FLOAT = (1 << 2) 540 | ALAW = (1 << 3) 541 | MULAW = (1 << 4) 542 | DSD = (1 << 5) 543 | RAW_DATA = (1 << 6) 544 | PCM_IEC60958 = (1 << 7) 545 | AC_3 = (1 << 8) 546 | MPEG_1_Layer1 = (1 << 9) 547 | MPEG_1_Layer2_3 = (1 << 10) # These share the same bit 548 | MPEG_2_NOEXT = (1 << 10) # These share the same bit 549 | MPEG_2_EXT = (1 << 11) 550 | MPEG_2_AAC_ADTS = (1 << 12) 551 | MPEG_2_Layer1_LS = (1 << 13) 552 | MPEG_2_Layer2_3_LS = (1 << 14) 553 | DTS_I = (1 << 15) 554 | DTS_II = (1 << 16) 555 | DTS_III = (1 << 17) 556 | ATRAC = (1 << 18) 557 | ATRAC2_3 = (1 << 19) 558 | WMA = (1 << 20) 559 | E_AC_3 = (1 << 21) 560 | MAT = (1 << 22) 561 | DTS_IV = (1 << 23) 562 | MPEG_4_HE_AAC = (1 << 24) 563 | MPEG_4_HE_AAC_V2 = (1 << 25) 564 | MPEG_4_AAC_LC = (1 << 26) 565 | DRA = (1 << 27) 566 | MPEG_4_HE_AAC_SURROUND = (1 << 28) 567 | MPEG_4_AAC_LC_SURROUND = (1 << 29) 568 | MPEG_H_3D_AUDIO = (1 << 30) 569 | AC4 = (1 << 31) 570 | MPEG_4_AAC_ELD = (1 << 32) 571 | 572 | 573 | # As defined in [Audio30], Table 4-47 574 | AudioControlInterruptEndpointDescriptor = DescriptorFormat( 575 | "bLength" / construct.Const(7, construct.Int8ul), 576 | "bDescriptorType" / DescriptorNumber(AudioClassSpecificStandardDescriptorTypes.CS_ENDPOINT), 577 | "bEndpointAddress" / DescriptorField(description="The address of the endpoint, use USBDirection.*.from_endpoint_address()"), 578 | "bmAttributes" / DescriptorField(description="D1..0: Transfer type (0b11 = Interrupt)", default=0b11), 579 | "wMaxPacketSize" / DescriptorField(description="Maximum packet size this endpoint is capable of. Used here to pass 6-byte interrupt information.", default=6), 580 | "bInterval" / DescriptorField(description="Interval for polling the Interrupt endpoint") 581 | ) 582 | 583 | # As defined in [Audio30], Table 4-51 584 | AudioStreamingIsochronousEndpointDescriptor = DescriptorFormat( 585 | "bLength" / construct.Const(7, construct.Int8ul), 586 | "bDescriptorType" / DescriptorNumber(StandardDescriptorNumbers.ENDPOINT), 587 | "bEndpointAddress" / DescriptorField(description="The address of the endpoint, use USBDirection.*.from_endpoint_address()"), 588 | "bmAttributes" / DescriptorField(description="D1..0: transfer type (01=isochronous); D3..2: synchronization type (01=asynchronous/10=adaptive/11=synchronous); D5..4: usage (00=data/10=feedback)", default=0b000101), 589 | "wMaxPacketSize" / DescriptorField(description="Maximum packet size this endpoint is capable of. Used here to pass 6-byte interrupt information.", default=6), 590 | "bInterval" / DescriptorField(description="Interval for polling the Interrupt endpoint") 591 | ) 592 | 593 | # As defined in [Audio30], Table 4-53 594 | AudioStreamingIsochronousFeedbackEndpointDescriptor = DescriptorFormat( 595 | "bLength" / construct.Const(7, construct.Int8ul), 596 | "bDescriptorType" / DescriptorNumber(StandardDescriptorNumbers.ENDPOINT), 597 | "bEndpointAddress" / DescriptorField(description="The address of the endpoint, use USBDirection.*.from_endpoint_address()"), 598 | "bmAttributes" / DescriptorField(description="D1..0: transfer type (01=isochronous); D3..2: synchronization type (00=no sync); D5..4: usage (10=feedback)", default=0b00100001), 599 | "wMaxPacketSize" / DescriptorField(description="Maximum packet size this endpoint is capable of. Used here to pass 6-byte interrupt information.", default=6), 600 | "bInterval" / DescriptorField(description="Interval for polling the Interrupt endpoint") 601 | ) 602 | 603 | HeaderDescriptor = DescriptorFormat( 604 | "bLength" / construct.Const(10, construct.Int8ul), 605 | "bDescriptorType" / DescriptorNumber(AudioClassSpecificStandardDescriptorTypes.CS_INTERFACE), 606 | "bDescriptorSubtype" / DescriptorNumber(AudioClassSpecificACInterfaceDescriptorSubtypes.HEADER), 607 | "bCategory" / DescriptorField(description="Audio Function Category, see AudioFunctionCategoryCodes"), 608 | "wTotalLength" / DescriptorField("Length including subordinates"), 609 | "bmControls" / DescriptorField("D1..0: Latency Control; D31..2: Reserved.", length=4, default=0) 610 | ) 611 | 612 | AudioStreamingInterfaceDescriptor = DescriptorFormat( 613 | "bLength" / construct.Const(9, construct.Int8ul), 614 | "bDescriptorType" / DescriptorNumber(StandardDescriptorNumbers.INTERFACE), 615 | "bInterfaceNumber" / DescriptorField(description="ID of the streaming interface"), 616 | "bAlternateSetting" / DescriptorField(description="alternate setting number for the interface", default=0), 617 | "bNumEndpoints" / DescriptorField(description="Number of data endpoints used (excluding endpoint 0). Can be: 0 (no data endpoint); 1 (data endpoint); 2 (data + explicit feedback endpoint)", default=0), 618 | "bInterfaceClass" / DescriptorNumber(AudioInterfaceClassCodes.AUDIO), 619 | "bInterfaceSubClass" / DescriptorNumber(AudioInterfaceSubclassCodes.AUDIO_STREAMING), 620 | "bInterfaceProtocol" / DescriptorNumber(AudioInterfaceProtocolCodes.IP_VERSION_03_00), 621 | "iInterface" / DescriptorField(description="index of a string descriptor describing this interface (0 = unused)") 622 | ) 623 | 624 | ClassSpecificAudioStreamingInterfaceDescriptor = DescriptorFormat( 625 | "bLength" / construct.Const(23, construct.Int8ul), 626 | "bDescriptorType" / DescriptorNumber(AudioClassSpecificStandardDescriptorTypes.CS_INTERFACE), 627 | "bDescriptorSubtype" / DescriptorNumber(AudioClassSpecificASInterfaceDescriptorSubtypes.AS_GENERAL), 628 | "bTerminalLink" / DescriptorField(description="the ID of the terminal to which this interface is connected"), 629 | "bmControls" / DescriptorField(description="D1..0: active alternate setting control; D3..2: valid alternate settings control; D5..4: audio data format control; D31..6: reserved"), 630 | "wClusterDescrID" / DescriptorField(description="ID of the cluster descriptor of the audio streamin interface"), 631 | "bmFormats" / DescriptorField(description="audio data formats which can be used with this interface", length=8, default=AudioDataFormats.PCM), 632 | "bSubslotSize" / DescriptorField(description="number of bytes occupied by one audio subslot"), 633 | "bBitResolution" / DescriptorField(description="number of effectively used bits in the audio subslot"), 634 | "bmAuxProtocols" / DescriptorField(description="which auxiliary protocols are required", default=0), 635 | "bControlSize" / DescriptorField(description="size of the control channel words in bytes") 636 | ) 637 | 638 | InputTerminalDescriptor = DescriptorFormat( 639 | "bLength" / construct.Const(20, construct.Int8ul), 640 | "bDescriptorType" / DescriptorNumber(AudioClassSpecificStandardDescriptorTypes.CS_INTERFACE), 641 | "bDescriptorSubtype" / DescriptorNumber(AudioClassSpecificACInterfaceDescriptorSubtypes.INPUT_TERMINAL), 642 | "bTerminalID" / DescriptorField(description="unique identifier for the terminal within the audio function."), 643 | "wTerminalType" / DescriptorField(description="a value of one of the terminal types Enums (eg InputTerminaTypes, ExternalTerminalTypes)"), 644 | "bAssocTerminal" / DescriptorField(description="ID of the associated output terminal"), 645 | "bCSourceID" / DescriptorField(description="ID of the clock which is connected to this terminal"), 646 | "bmControls" / DescriptorField(description="D1..0: Insertion Control; D3..2: Overload Control; D5..4: Underflow Control; D7..6: Overflow Control; D31..8: Reserved"), 647 | "wClusterDescrID" / DescriptorField(description="ID of the cluster descriptor for this input terminal."), 648 | "wExTerminalDescrID" / DescriptorField(description="ID of the extended terminal descriptor for this input terminal. Zero if no extended terminal descriptor is present."), 649 | "wConnectorsDescrID" / DescriptorField(description="ID of the Connectors descriptor for this Input Terminal. Zero if no connectors descriptor is present."), 650 | "wTerminalDescrStr" / DescriptorField(description="ID of a class-specific string descriptor, describing the input terminal.") 651 | ) 652 | 653 | OutputTerminalDescriptor = DescriptorFormat( 654 | "bLength" / construct.Const(19, construct.Int8ul), 655 | "bDescriptorType" / DescriptorNumber(AudioClassSpecificStandardDescriptorTypes.CS_INTERFACE), 656 | "bDescriptorSubtype" / DescriptorNumber(AudioClassSpecificACInterfaceDescriptorSubtypes.OUTPUT_TERMINAL), 657 | "bTerminalID" / DescriptorField(description="unique identifier for the terminal within the audio function."), 658 | "wTerminalType" / DescriptorField(description="a value of one of the terminal types Enums (eg OutputTerminaTypes, ExternalTerminalTypes)"), 659 | "bAssocTerminal" / DescriptorField(description="ID of the associated input terminal"), 660 | "bSourceID" / DescriptorField(description="ID of the unit or terminal which is connected to this terminal"), 661 | "bCSourceID" / DescriptorField(description="ID of the clock which is connected to this terminal"), 662 | "bmControls" / DescriptorField(description="D1..0: Insertion Control; D3..2: Overload Control; D5..4: Underflow Control; D7..6: Overflow Control; D31..8: Reserved"), 663 | "wClusterDescrID" / DescriptorField(description="ID of the cluster descriptor for this input terminal."), 664 | "wExTerminalDescrID" / DescriptorField(description="ID of the extended terminal descriptor for this output terminal. Zero if no extended terminal descriptor is present.", default=0), 665 | "wConnectorsDescrID" / DescriptorField(description="ID of the connectors descriptor for this input terminal. Zero if no connectors descriptor is present.", default=0), 666 | "wTerminalDescrStr" / DescriptorField(description="ID of a class-specific string descriptor, describing the output terminal.") 667 | ) 668 | -------------------------------------------------------------------------------- /usb_protocol/types/descriptors/test_uac2.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of usb-protocol. 3 | # 4 | """ 5 | Unit tests for USB Audio Class Devices (UAC), Release 2 6 | """ 7 | 8 | import unittest 9 | 10 | from .uac2 import * 11 | 12 | 13 | class UAC2Cases(unittest.TestCase): 14 | 15 | def test_parse_interface_association_descriptor(self): 16 | # Parse the relevant descriptor ... 17 | parsed = InterfaceAssociationDescriptor.parse([ 18 | 0x08, # Length 19 | 0x0B, # Type 20 | 0x01, # First interface 21 | 0x02, # Interface count 22 | 0x01, # Function class 23 | 0x00, # Function subclass 24 | 0x20, # Function protocol 25 | 0x42 # Function name 26 | ]) 27 | 28 | # ... and check the descriptor's fields. 29 | self.assertEqual(parsed.bLength, 8) 30 | self.assertEqual(parsed.bDescriptorType, StandardDescriptorNumbers.INTERFACE_ASSOCIATION) 31 | self.assertEqual(parsed.bFirstInterface, 1) 32 | self.assertEqual(parsed.bInterfaceCount, 2) 33 | self.assertEqual(parsed.bFunctionClass, AudioFunctionClassCodes.AUDIO_FUNCTION) 34 | self.assertEqual(parsed.bFunctionSubClass, AudioFunctionCategoryCodes.FUNCTION_SUBCLASS_UNDEFINED) 35 | self.assertEqual(parsed.bFunctionProtocol, AudioFunctionProtocolCodes.AF_VERSION_02_00) 36 | self.assertEqual(parsed.iFunction, 0x42) 37 | 38 | def test_build_interface_association_descriptor(self): 39 | # Build the relevant descriptor 40 | data = InterfaceAssociationDescriptor.build({ 41 | 'bFirstInterface': 1, 42 | 'bInterfaceCount': 2, 43 | 'iFunction': 0x42, 44 | }) 45 | 46 | # ... and check the binary output 47 | self.assertEqual(data, bytes([ 48 | 0x08, # Length 49 | 0x0B, # Type 50 | 0x01, # First interface 51 | 0x02, # Interface count 52 | 0x01, # Function class 53 | 0x00, # Function subclass 54 | 0x20, # Function protocol 55 | 0x42 # Function name 56 | ])) 57 | 58 | def test_parse_standard_audio_control_interface_descriptor(self): 59 | # Parse the relevant descriptor ... 60 | parsed = StandardAudioControlInterfaceDescriptor.parse([ 61 | 0x09, # Length 62 | 0x04, # Type 63 | 0x01, # Interface number 64 | 0x02, # Alternate settings 65 | 0x00, # Number of endpoints 66 | 0x01, # Interface class 67 | 0x01, # Interface subclass 68 | 0x20, # Interface protocol 69 | 0x42 # Interface name 70 | ]) 71 | 72 | # ... and check the descriptor's fields. 73 | self.assertEqual(parsed.bLength, 9) 74 | self.assertEqual(parsed.bDescriptorType, StandardDescriptorNumbers.INTERFACE) 75 | self.assertEqual(parsed.bInterfaceNumber, 1) 76 | self.assertEqual(parsed.bAlternateSetting, 2) 77 | self.assertEqual(parsed.bNumEndpoints, 0) 78 | self.assertEqual(parsed.bInterfaceClass, AudioInterfaceClassCodes.AUDIO) 79 | self.assertEqual(parsed.bInterfaceSubClass, AudioInterfaceSubclassCodes.AUDIO_CONTROL) 80 | self.assertEqual(parsed.bInterfaceProtocol, AudioInterfaceProtocolCodes.IP_VERSION_02_00) 81 | self.assertEqual(parsed.iInterface, 0x42) 82 | 83 | def test_build_standard_audio_control_interface_descriptor(self): 84 | # Build the relevant descriptor 85 | data = StandardAudioControlInterfaceDescriptor.build({ 86 | 'bInterfaceNumber': 1, 87 | 'bAlternateSetting': 2, 88 | 'bNumEndpoints': 0, 89 | 'iInterface': 0x42, 90 | }) 91 | 92 | # ... and check the binary output 93 | self.assertEqual(data, bytes([ 94 | 0x09, # Length 95 | 0x04, # Type 96 | 0x01, # Interface number 97 | 0x02, # Alternate settings 98 | 0x00, # Number of endpoints 99 | 0x01, # Interface class 100 | 0x01, # Interface subclass 101 | 0x20, # Interface protocol 102 | 0x42 # Interface Name 103 | ])) 104 | 105 | def test_parse_clock_source_descriptor(self): 106 | # Parse the relevant descriptor ... 107 | parsed = ClockSourceDescriptor.parse([ 108 | 0x08, # Length 109 | 0x24, # Type 110 | 0x0A, # Subtype 111 | 0x01, # Clock ID 112 | 0x01, # Attributes 113 | 0x01, # Controls 114 | 0x01, # Associate terminal 115 | 0x42 # Clock source name 116 | ]) 117 | 118 | # ... and check the descriptor's fields. 119 | self.assertEqual(parsed.bLength, 8) 120 | self.assertEqual(parsed.bDescriptorType, AudioClassSpecificStandardDescriptorNumbers.CS_INTERFACE) 121 | self.assertEqual(parsed.bDescriptorSubtype, AudioClassSpecificACInterfaceDescriptorSubtypes.CLOCK_SOURCE) 122 | self.assertEqual(parsed.bClockID, 0x01) 123 | self.assertEqual(parsed.bmAttributes, ClockAttributes.INTERNAL_FIXED_CLOCK) 124 | self.assertEqual(parsed.bmControls, ClockFrequencyControl.HOST_READ_ONLY) 125 | self.assertEqual(parsed.bAssocTerminal, 0x01) 126 | self.assertEqual(parsed.iClockSource, 0x42) 127 | 128 | def test_build_clock_source_descriptor(self): 129 | # Build the relevant descriptor 130 | data = ClockSourceDescriptor.build({ 131 | 'bClockID': 1, 132 | 'bmAttributes': ClockAttributes.INTERNAL_FIXED_CLOCK, 133 | 'bmControls': ClockFrequencyControl.HOST_READ_ONLY, 134 | 'bAssocTerminal': 0x01, 135 | 'iClockSource': 0x42, 136 | }) 137 | 138 | # ... and check the binary output 139 | self.assertEqual(data, bytes([ 140 | 0x08, # Length 141 | 0x24, # Type 142 | 0x0A, # Subtype 143 | 0x01, # Clock ID 144 | 0x01, # Attributes 145 | 0x01, # Controls 146 | 0x01, # Associate terminal 147 | 0x42 # Clock source name 148 | ])) 149 | 150 | def test_parse_input_terminal_descriptor(self): 151 | # Parse the relevant descriptor ... 152 | parsed = InputTerminalDescriptor.parse([ 153 | 0x11, # Length 154 | 0x24, # Type 155 | 0x02, # Subtype 156 | 0x01, # Terminal ID 157 | 0x01, 0x01, # Terminal type 158 | 0x00, # Associated terminal 159 | 0x01, # Clock ID 160 | 0x02, # Number of channels 161 | 0x03, 0x00, 0x00, 0x00, # Channel configuration 162 | 0x23, # First channel name 163 | 0x05, 0x00, # Controls 164 | 0x42 # Terminal name 165 | ]) 166 | 167 | # ... and check the descriptor's fields. 168 | self.assertEqual(parsed.bLength, 17) 169 | self.assertEqual(parsed.bDescriptorType, AudioClassSpecificStandardDescriptorNumbers.CS_INTERFACE) 170 | self.assertEqual(parsed.bDescriptorSubtype, AudioClassSpecificACInterfaceDescriptorSubtypes.INPUT_TERMINAL) 171 | self.assertEqual(parsed.bTerminalID, 0x01) 172 | self.assertEqual(parsed.wTerminalType, USBTerminalTypes.USB_STREAMING) 173 | self.assertEqual(parsed.bAssocTerminal, 0x00) 174 | self.assertEqual(parsed.bCSourceID, 0x01) 175 | self.assertEqual(parsed.bNrChannels, 0x02) 176 | self.assertEqual(parsed.bmChannelConfig, 0x0003) 177 | self.assertEqual(parsed.iChannelNames, 0x23) 178 | self.assertEqual(parsed.bmControls, 5) 179 | self.assertEqual(parsed.iTerminal, 0x42) 180 | 181 | def test_build_input_terminal_descriptor(self): 182 | # Build the relevant descriptor 183 | data = InputTerminalDescriptor.build({ 184 | 'bTerminalID': 1, 185 | 'wTerminalType': USBTerminalTypes.USB_STREAMING, 186 | 'bCSourceID': 1, 187 | 'bNrChannels': 2, 188 | 'bmChannelConfig': 3, 189 | 'iChannelNames': 0x23, 190 | 'bmControls': 5, 191 | 'iTerminal': 0x42, 192 | }) 193 | 194 | # ... and check the binary output 195 | self.assertEqual(data, bytes([ 196 | 0x11, # Length 197 | 0x24, # Type 198 | 0x02, # Subtype 199 | 0x01, # Terminal ID 200 | 0x01, 0x01, # Terminal type 201 | 0x00, # Associated terminal 202 | 0x01, # Clock ID 203 | 0x02, # Number of channels 204 | 0x03, 0x00, 0x00, 0x00, # Channel configuration 205 | 0x23, # First channel name 206 | 0x05, 0x00, # Controls 207 | 0x42 # Terminal name 208 | ])) 209 | 210 | def test_parse_output_terminal_descriptor(self): 211 | # Parse the relevant descriptor ... 212 | parsed = OutputTerminalDescriptor.parse([ 213 | 0x0C, # Length 214 | 0x24, # Type 215 | 0x03, # Subtype 216 | 0x06, # Terminal ID 217 | 0x01, 0x03, # Terminal type 218 | 0x00, # Associated terminal 219 | 0x09, # Source ID 220 | 0x01, # Clock ID 221 | 0x00, 0x00, # Controls 222 | 0x42 # Terminal name 223 | ]) 224 | 225 | # ... and check the descriptor's fields. 226 | self.assertEqual(parsed.bLength, 12) 227 | self.assertEqual(parsed.bDescriptorType, AudioClassSpecificStandardDescriptorNumbers.CS_INTERFACE) 228 | self.assertEqual(parsed.bDescriptorSubtype, AudioClassSpecificACInterfaceDescriptorSubtypes.OUTPUT_TERMINAL) 229 | self.assertEqual(parsed.bTerminalID, 0x06) 230 | self.assertEqual(parsed.wTerminalType, OutputTerminalTypes.SPEAKER) 231 | self.assertEqual(parsed.bAssocTerminal, 0x00) 232 | self.assertEqual(parsed.bSourceID, 0x09) 233 | self.assertEqual(parsed.bCSourceID, 0x01) 234 | self.assertEqual(parsed.bmControls, 0x0000) 235 | self.assertEqual(parsed.iTerminal, 0x42) 236 | 237 | def test_build_output_terminal_descriptor(self): 238 | # Build the relevant descriptor 239 | data = OutputTerminalDescriptor.build({ 240 | 'bTerminalID': 6, 241 | 'wTerminalType': OutputTerminalTypes.SPEAKER, 242 | 'bSourceID': 9, 243 | 'bCSourceID': 1, 244 | 'iTerminal': 0x42, 245 | }) 246 | 247 | # ... and check the binary output 248 | self.assertEqual(data, bytes([ 249 | 0x0C, # Length 250 | 0x24, # Type 251 | 0x03, # Subtype 252 | 0x06, # Terminal ID 253 | 0x01, 0x03, # Terminal type 254 | 0x00, # Associated terminal 255 | 0x09, # Source ID 256 | 0x01, # Clock ID 257 | 0x00, 0x00, # Controls 258 | 0x42 # Terminal name 259 | ])) 260 | 261 | def test_parse_feature_unit_descriptor(self): 262 | # Parse the relevant descriptor ... 263 | parsed = FeatureUnitDescriptor.parse([ 264 | 0x12, # Length 265 | 0x24, # Type 266 | 0x06, # Subtype 267 | 0x06, # Unit ID 268 | 0x09, # Source ID 269 | 0x01, 0x00, 0x00, 0x00, # Controls 0 270 | 0x02, 0x00, 0x00, 0x00, # Controls 1 271 | 0x03, 0x00, 0x00, 0x00, # Controls 2 272 | 0x42 # Unit name 273 | ]) 274 | 275 | # ... and check the descriptor's fields. 276 | self.assertEqual(parsed.bLength, 18) 277 | self.assertEqual(parsed.bDescriptorType, AudioClassSpecificStandardDescriptorNumbers.CS_INTERFACE) 278 | self.assertEqual(parsed.bDescriptorSubtype, AudioClassSpecificACInterfaceDescriptorSubtypes.FEATURE_UNIT) 279 | self.assertEqual(parsed.bUnitID, 0x06) 280 | self.assertEqual(parsed.bSourceID, 0x09) 281 | self.assertEqual(parsed.bmaControls, [0x0001, 0x0002, 0x0003]) 282 | self.assertEqual(parsed.iFeature, 0x42) 283 | 284 | def test_build_feature_unit_descriptor(self): 285 | # Build the relevant descriptor 286 | data = FeatureUnitDescriptor.build({ 287 | 'bUnitID': 6, 288 | 'bSourceID': 9, 289 | 'bmaControls': [1, 2, 3], 290 | 'iFeature': 0x42, 291 | }) 292 | 293 | # ... and check the binary output 294 | self.assertEqual(data, bytes([ 295 | 0x12, # Length 296 | 0x24, # Type 297 | 0x06, # Subtype 298 | 0x06, # Unit ID 299 | 0x09, # Source ID 300 | 0x01, 0x00, 0x00, 0x00, # Controls 0 301 | 0x02, 0x00, 0x00, 0x00, # Controls 1 302 | 0x03, 0x00, 0x00, 0x00, # Controls 2 303 | 0x42 # Unit name 304 | ])) 305 | 306 | def test_parse_audio_streaming_interface_descriptor(self): 307 | # Parse the relevant descriptor ... 308 | parsed = AudioStreamingInterfaceDescriptor.parse([ 309 | 0x09, # Length 310 | 0x04, # Type 311 | 0x02, # Interface number 312 | 0x03, # Alternate setting 313 | 0x01, # Number of endpoints 314 | 0x01, # Interface class 315 | 0x02, # Interface subclass 316 | 0x20, # Interface protocol 317 | 0x42 # Interface name 318 | ]) 319 | 320 | # ... and check the descriptor's fields. 321 | self.assertEqual(parsed.bLength, 9) 322 | self.assertEqual(parsed.bDescriptorType, StandardDescriptorNumbers.INTERFACE) 323 | self.assertEqual(parsed.bInterfaceNumber, 2) 324 | self.assertEqual(parsed.bAlternateSetting, 3) 325 | self.assertEqual(parsed.bNumEndpoints, 1) 326 | self.assertEqual(parsed.bInterfaceClass, AudioInterfaceClassCodes.AUDIO) 327 | self.assertEqual(parsed.bInterfaceSubClass, AudioInterfaceSubclassCodes.AUDIO_STREAMING) 328 | self.assertEqual(parsed.bInterfaceProtocol, AudioInterfaceProtocolCodes.IP_VERSION_02_00) 329 | self.assertEqual(parsed.iInterface, 0x42) 330 | 331 | def test_build_audio_streaming_interface_descriptor(self): 332 | # Build the relevant descriptor 333 | data = AudioStreamingInterfaceDescriptor.build({ 334 | 'bInterfaceNumber': 2, 335 | 'bAlternateSetting': 3, 336 | 'bNumEndpoints': 1, 337 | 'iInterface': 0x42, 338 | }) 339 | 340 | # ... and check the binary output 341 | self.assertEqual(data, bytes([ 342 | 0x09, # Length 343 | 0x04, # Type 344 | 0x02, # Interface number 345 | 0x03, # Alternate setting 346 | 0x01, # Number of endpoints 347 | 0x01, # Interface class 348 | 0x02, # Interface subclass 349 | 0x20, # Interface protocol 350 | 0x42 # Interface name 351 | ])) 352 | 353 | def test_parse_class_specific_audio_streaming_interface_descriptor(self): 354 | # Parse the relevant descriptor ... 355 | parsed = ClassSpecificAudioStreamingInterfaceDescriptor.parse([ 356 | 0x10, # Length 357 | 0x24, # Type 358 | 0x01, # Subtype 359 | 0x03, # Terminal ID 360 | 0x00, # Controls 361 | 0x01, # Format type 362 | 0x01, 0x00, 0x00, 0x00, # Formats 363 | 0x02, # Number of channels 364 | 0x00, 0x00, 0x00, 0x00, # Channel config 365 | 0x42 # First channel name 366 | ]) 367 | 368 | # ... and check the descriptor's fields. 369 | self.assertEqual(parsed.bLength, 16) 370 | self.assertEqual(parsed.bDescriptorType, AudioClassSpecificStandardDescriptorNumbers.CS_INTERFACE) 371 | self.assertEqual(parsed.bDescriptorSubtype, AudioClassSpecificASInterfaceDescriptorSubtypes.AS_GENERAL) 372 | self.assertEqual(parsed.bTerminalLink, 3) 373 | self.assertEqual(parsed.bmControls, 0) 374 | self.assertEqual(parsed.bFormatType, FormatTypes.FORMAT_TYPE_I) 375 | self.assertEqual(parsed.bmFormats, TypeIFormats.PCM) 376 | self.assertEqual(parsed.bNrChannels, 2) 377 | self.assertEqual(parsed.bmChannelConfig, 0x0) 378 | self.assertEqual(parsed.iChannelNames, 0x42) 379 | 380 | def test_build_class_specific_audio_streaming_interface_descriptor(self): 381 | # Build the relevant descriptor 382 | data = ClassSpecificAudioStreamingInterfaceDescriptor.build({ 383 | 'bTerminalLink': 3, 384 | 'bmControls': 0, 385 | 'bFormatType': FormatTypes.FORMAT_TYPE_I, 386 | 'bmFormats': TypeIFormats.PCM, 387 | 'bNrChannels': 2, 388 | 'bmChannelConfig': 0, 389 | 'iChannelNames': 0x42, 390 | }) 391 | 392 | # ... and check the binary output 393 | self.assertEqual(data, bytes([ 394 | 0x10, # Length 395 | 0x24, # Type 396 | 0x01, # Subtype 397 | 0x03, # Terminal ID 398 | 0x00, # Controls 399 | 0x01, # Format type 400 | 0x01, 0x00, 0x00, 0x00, # Formats 401 | 0x02, # Number of channels 402 | 0x00, 0x00, 0x00, 0x00, # Channel config 403 | 0x42 # First channel name 404 | ])) 405 | 406 | def test_parse_type_i_format_type_descriptor(self): 407 | # Parse the relevant descriptor ... 408 | parsed = TypeIFormatTypeDescriptor.parse([ 409 | 0x06, # Length 410 | 0x24, # Type 411 | 0x02, # Subtype 412 | 0x01, # Format type 413 | 0x02, # Subslot size 414 | 0x10, # Bit resolution 415 | ]) 416 | 417 | # ... and check the descriptor's fields. 418 | self.assertEqual(parsed.bLength, 6) 419 | self.assertEqual(parsed.bDescriptorType, AudioClassSpecificStandardDescriptorNumbers.CS_INTERFACE) 420 | self.assertEqual(parsed.bDescriptorSubtype, AudioClassSpecificASInterfaceDescriptorSubtypes.FORMAT_TYPE) 421 | self.assertEqual(parsed.bFormatType, FormatTypes.FORMAT_TYPE_I) 422 | self.assertEqual(parsed.bSubslotSize, 2) 423 | self.assertEqual(parsed.bBitResolution, 16) 424 | 425 | def test_build_type_i_format_type_descriptor(self): 426 | # Build the relevant descriptor 427 | data = TypeIFormatTypeDescriptor.build({ 428 | 'bSubslotSize': 2, 429 | 'bBitResolution': 16, 430 | }) 431 | 432 | # ... and check the binary output 433 | self.assertEqual(data, bytes([ 434 | 0x06, # Length 435 | 0x24, # Type 436 | 0x02, # Subtype 437 | 0x01, # Format type 438 | 0x02, # Subslot size 439 | 0x10, # Bit resolution 440 | ])) 441 | 442 | def test_parse_extended_type_i_format_type_descriptor(self): 443 | # Parse the relevant descriptor ... 444 | parsed = ExtendedTypeIFormatTypeDescriptor.parse([ 445 | 0x09, # Length 446 | 0x24, # Type 447 | 0x02, # Subtype 448 | 0x81, # Format type 449 | 0x02, # Subslot size 450 | 0x10, # Bit resolution 451 | 0x0A, # Header length 452 | 0x04, # Control size 453 | 0x00, # Side band protocol 454 | ]) 455 | 456 | # ... and check the descriptor's fields. 457 | self.assertEqual(parsed.bLength, 9) 458 | self.assertEqual(parsed.bDescriptorType, AudioClassSpecificStandardDescriptorNumbers.CS_INTERFACE) 459 | self.assertEqual(parsed.bDescriptorSubtype, AudioClassSpecificASInterfaceDescriptorSubtypes.FORMAT_TYPE) 460 | self.assertEqual(parsed.bFormatType, FormatTypes.EXT_FORMAT_TYPE_I) 461 | self.assertEqual(parsed.bSubslotSize, 2) 462 | self.assertEqual(parsed.bBitResolution, 16) 463 | self.assertEqual(parsed.bHeaderLength, 10) 464 | self.assertEqual(parsed.bControlSize, 4) 465 | self.assertEqual(parsed.bSideBandProtocol, 0) 466 | 467 | def test_build_extended_type_i_format_type_descriptor(self): 468 | # Build the relevant descriptor 469 | data = ExtendedTypeIFormatTypeDescriptor.build({ 470 | 'bSubslotSize': 2, 471 | 'bBitResolution': 16, 472 | 'bHeaderLength': 10, 473 | 'bControlSize': 4, 474 | 'bSideBandProtocol': 0, 475 | }) 476 | 477 | # ... and check the binary output 478 | self.assertEqual(data, bytes([ 479 | 0x09, # Length 480 | 0x24, # Type 481 | 0x02, # Subtype 482 | 0x81, # Format type 483 | 0x02, # Subslot size 484 | 0x10, # Bit resolution 485 | 0x0A, # Header length 486 | 0x04, # Control size 487 | 0x00, # Side band protocol 488 | ])) 489 | 490 | def test_parse_type_ii_format_type_descriptor(self): 491 | # Parse the relevant descriptor ... 492 | parsed = TypeIIFormatTypeDescriptor.parse([ 493 | 0x08, # Length 494 | 0x24, # Type 495 | 0x02, # Subtype 496 | 0x02, # Format type 497 | 0x40, 0x00, # Maximum bit rate 498 | 0x20, 0x00, # Slots per frame 499 | ]) 500 | 501 | # ... and check the descriptor's fields. 502 | self.assertEqual(parsed.bLength, 8) 503 | self.assertEqual(parsed.bDescriptorType, AudioClassSpecificStandardDescriptorNumbers.CS_INTERFACE) 504 | self.assertEqual(parsed.bDescriptorSubtype, AudioClassSpecificASInterfaceDescriptorSubtypes.FORMAT_TYPE) 505 | self.assertEqual(parsed.bFormatType, FormatTypes.FORMAT_TYPE_II) 506 | self.assertEqual(parsed.wMaxBitRate, 64) 507 | self.assertEqual(parsed.wSlotsPerFrame, 32) 508 | 509 | def test_build_type_ii_format_type_descriptor(self): 510 | # Build the relevant descriptor 511 | data = TypeIIFormatTypeDescriptor.build({ 512 | 'wMaxBitRate': 64, 513 | 'wSlotsPerFrame': 32, 514 | }) 515 | 516 | # ... and check the binary output 517 | self.assertEqual(data, bytes([ 518 | 0x08, # Length 519 | 0x24, # Type 520 | 0x02, # Subtype 521 | 0x02, # Format type 522 | 0x40, 0x00, # Maximum bit rate 523 | 0x20, 0x00, # Slots per frame 524 | ])) 525 | 526 | def test_parse_extended_type_ii_format_type_descriptor(self): 527 | # Parse the relevant descriptor ... 528 | parsed = ExtendedTypeIIFormatTypeDescriptor.parse([ 529 | 0x0A, # Length 530 | 0x24, # Type 531 | 0x02, # Subtype 532 | 0x82, # Format type 533 | 0x40, 0x00, # Maximum bit rate 534 | 0x20, 0x00, # Samples per frame 535 | 0x0A, # Header length 536 | 0x00, # Side band protocol 537 | ]) 538 | 539 | # ... and check the descriptor's fields. 540 | self.assertEqual(parsed.bLength, 10) 541 | self.assertEqual(parsed.bDescriptorType, AudioClassSpecificStandardDescriptorNumbers.CS_INTERFACE) 542 | self.assertEqual(parsed.bDescriptorSubtype, AudioClassSpecificASInterfaceDescriptorSubtypes.FORMAT_TYPE) 543 | self.assertEqual(parsed.bFormatType, FormatTypes.EXT_FORMAT_TYPE_II) 544 | self.assertEqual(parsed.wMaxBitRate, 64) 545 | self.assertEqual(parsed.wSamplesPerFrame, 32) 546 | self.assertEqual(parsed.bHeaderLength, 10) 547 | self.assertEqual(parsed.bSideBandProtocol, 0) 548 | 549 | def test_build_extended_type_ii_format_type_descriptor(self): 550 | # Build the relevant descriptor 551 | data = ExtendedTypeIIFormatTypeDescriptor.build({ 552 | 'wMaxBitRate': 64, 553 | 'wSamplesPerFrame': 32, 554 | 'bHeaderLength': 10, 555 | 'bSideBandProtocol': 0, 556 | }) 557 | 558 | # ... and check the binary output 559 | self.assertEqual(data, bytes([ 560 | 0x0A, # Length 561 | 0x24, # Type 562 | 0x02, # Subtype 563 | 0x82, # Format type 564 | 0x40, 0x00, # Maximum bit rate 565 | 0x20, 0x00, # Samples per frame 566 | 0x0A, # Header length 567 | 0x00, # Side band protocol 568 | ])) 569 | 570 | def test_parse_type_iii_format_type_descriptor(self): 571 | # Parse the relevant descriptor ... 572 | parsed = TypeIIIFormatTypeDescriptor.parse([ 573 | 0x06, # Length 574 | 0x24, # Type 575 | 0x02, # Subtype 576 | 0x03, # Format type 577 | 0x02, # Subslot size 578 | 0x10, # Bit resolution 579 | ]) 580 | 581 | # ... and check the descriptor's fields. 582 | self.assertEqual(parsed.bLength, 6) 583 | self.assertEqual(parsed.bDescriptorType, AudioClassSpecificStandardDescriptorNumbers.CS_INTERFACE) 584 | self.assertEqual(parsed.bDescriptorSubtype, AudioClassSpecificASInterfaceDescriptorSubtypes.FORMAT_TYPE) 585 | self.assertEqual(parsed.bFormatType, FormatTypes.FORMAT_TYPE_III) 586 | self.assertEqual(parsed.bSubslotSize, 2) 587 | self.assertEqual(parsed.bBitResolution, 16) 588 | 589 | def test_build_type_iii_format_type_descriptor(self): 590 | # Build the relevant descriptor 591 | data = TypeIIIFormatTypeDescriptor.build({ 592 | 'bBitResolution': 16, 593 | }) 594 | 595 | # ... and check the binary output 596 | self.assertEqual(data, bytes([ 597 | 0x06, # Length 598 | 0x24, # Type 599 | 0x02, # Subtype 600 | 0x03, # Format type 601 | 0x02, # Subslot size 602 | 0x10, # Bit resolution 603 | ])) 604 | 605 | def test_parse_extended_type_iii_format_type_descriptor(self): 606 | # Parse the relevant descriptor ... 607 | parsed = ExtendedTypeIIIFormatTypeDescriptor.parse([ 608 | 0x08, # Length 609 | 0x24, # Type 610 | 0x02, # Subtype 611 | 0x83, # Format type 612 | 0x02, # Subslot size 613 | 0x10, # Bit resolution 614 | 0x0A, # Header length 615 | 0x00, # Side band protocol 616 | ]) 617 | 618 | # ... and check the descriptor's fields. 619 | self.assertEqual(parsed.bLength, 8) 620 | self.assertEqual(parsed.bDescriptorType, AudioClassSpecificStandardDescriptorNumbers.CS_INTERFACE) 621 | self.assertEqual(parsed.bDescriptorSubtype, AudioClassSpecificASInterfaceDescriptorSubtypes.FORMAT_TYPE) 622 | self.assertEqual(parsed.bFormatType, FormatTypes.EXT_FORMAT_TYPE_III) 623 | self.assertEqual(parsed.bSubslotSize, 2) 624 | self.assertEqual(parsed.bBitResolution, 16) 625 | self.assertEqual(parsed.bHeaderLength, 10) 626 | self.assertEqual(parsed.bSideBandProtocol, 0) 627 | 628 | def test_build_extended_type_iii_format_type_descriptor(self): 629 | # Build the relevant descriptor 630 | data = ExtendedTypeIIIFormatTypeDescriptor.build({ 631 | 'bBitResolution': 16, 632 | 'bHeaderLength': 10, 633 | 'bSideBandProtocol': 0, 634 | }) 635 | 636 | # ... and check the binary output 637 | self.assertEqual(data, bytes([ 638 | 0x08, # Length 639 | 0x24, # Type 640 | 0x02, # Subtype 641 | 0x83, # Format type 642 | 0x02, # Subslot size 643 | 0x10, # Bit resolution 644 | 0x0A, # Header length 645 | 0x00, # Side band protocol 646 | ])) 647 | 648 | def test_parse_class_specific_audio_streaming_isochronous_audio_data_endpoint_descriptor(self): 649 | # Parse the relevant descriptor ... 650 | parsed = ClassSpecificAudioStreamingIsochronousAudioDataEndpointDescriptor.parse([ 651 | 0x08, # Length 652 | 0x25, # Type 653 | 0x01, # Subtype 654 | 0x00, # Attributes 655 | 0x00, # Controls 656 | 0x01, # Lock Delay Units 657 | 0x00, 0x00 # Lock delay 658 | ]) 659 | 660 | # ... and check the descriptor's fields. 661 | self.assertEqual(parsed.bLength, 8) 662 | self.assertEqual(parsed.bDescriptorType, AudioClassSpecificStandardDescriptorNumbers.CS_ENDPOINT) 663 | self.assertEqual(parsed.bDescriptorSubtype, AudioClassSpecificEndpointDescriptorSubtypes.EP_GENERAL) 664 | self.assertEqual(parsed.bmAttributes, 0) 665 | self.assertEqual(parsed.bmControls, 0) 666 | self.assertEqual(parsed.bLockDelayUnits, 1) 667 | self.assertEqual(parsed.wLockDelay, 0) 668 | 669 | def test_build_class_specific_audio_streaming_isochronous_audio_data_endpoint_descriptor(self): 670 | # Build the relevant descriptor 671 | data = ClassSpecificAudioStreamingIsochronousAudioDataEndpointDescriptor.build({ 672 | 'bmAttributes': 0, 673 | 'bmControls': 0, 674 | 'bLockDelayUnits': 1, 675 | 'wLockDelay': 0, 676 | }) 677 | 678 | # ... and check the binary output 679 | self.assertEqual(data, bytes([ 680 | 0x08, # Length 681 | 0x25, # Type 682 | 0x01, # Subtype 683 | 0x00, # Attributes 684 | 0x00, # Controls 685 | 0x01, # Lock Delay Units 686 | 0x00, 0x00 # Lock delay 687 | ])) 688 | --------------------------------------------------------------------------------