@@ -4,7 +4,7 @@ from setuptools import setup, find_packages | |||
setup( | |||
# Vitals | |||
name='usb-protocol', | |||
name='usb_protocol', | |||
license='BSD', | |||
url='https://github.com/usb-tool/luna', | |||
author='Katherine J. Temkin', | |||
@@ -1,163 +0,0 @@ | |||
# | |||
# This file is part of usb-protocol. | |||
# | |||
""" Structures describing standard USB descriptors. """ | |||
import unittest | |||
import construct | |||
from construct import this | |||
from ..descriptor import DescriptorField, DescriptorNumber, DescriptorFormat | |||
DeviceDescriptor = DescriptorFormat( | |||
"bLength" / DescriptorField("Length"), | |||
"bDescriptorType" / DescriptorNumber(1), | |||
"bcdUSB" / DescriptorField("USB Version"), | |||
"bDeviceClass" / DescriptorField("Class"), | |||
"bDeviceSubclass" / DescriptorField("Subclass"), | |||
"bDeviceProtocol" / DescriptorField("Protocol"), | |||
"bMaxPacketSize0" / DescriptorField("EP0 Max Pkt Size"), | |||
"idVendor" / DescriptorField("Vendor ID"), | |||
"idProduct" / DescriptorField("Product ID"), | |||
"bcdDevice" / DescriptorField("Device Version"), | |||
"iManufacturer" / DescriptorField("Manufacturer Str"), | |||
"iProduct" / DescriptorField("Product Str"), | |||
"iSerialNumber" / DescriptorField("Serial Number"), | |||
"bNumConfigurations" / DescriptorField("Configuration Count"), | |||
) | |||
ConfigurationDescriptor = DescriptorFormat( | |||
"bLength" / DescriptorField("Length"), | |||
"bDescriptorType" / DescriptorNumber(2), | |||
"wTotalLength" / DescriptorField("Length including subordinates"), | |||
"bNumInterfaces" / DescriptorField("Interface count"), | |||
"bConfigurationValue" / DescriptorField("Configuration number"), | |||
"iConfiguration" / DescriptorField("Description string"), | |||
"bmAttributes" / DescriptorField("Attributes"), | |||
"bMaxPower" / DescriptorField("Max power consumption"), | |||
) | |||
StringDescriptor = DescriptorFormat( | |||
"bLength" / DescriptorField("Length"), | |||
"bDescriptorType" / DescriptorNumber(3), | |||
"bString" / construct.PaddedString(this.bLength - 2, "utf_16_le") | |||
) | |||
InterfaceDescriptor = DescriptorFormat( | |||
"bLength" / DescriptorField("Length"), | |||
"bDescriptorType" / DescriptorNumber(4), | |||
"bInterfaceNumber" / DescriptorField("Interface number"), | |||
"bAlternateSetting" / DescriptorField("Alternate setting"), | |||
"bNumEndpoints" / DescriptorField("Endpoints included"), | |||
"bInterfaceClass" / DescriptorField("Class"), | |||
"bInterfaceSubclass" / DescriptorField("Subclass"), | |||
"bInterfaceProtocol" / DescriptorField("Protocol"), | |||
"iInterface" / DescriptorField("String index"), | |||
) | |||
EndpointDescriptor = DescriptorFormat( | |||
"bLength" / DescriptorField("Length"), | |||
"bDescriptorType" / DescriptorNumber(5), | |||
"bEndpointAddress" / DescriptorField("Endpoint Address"), | |||
"bmAttributes" / DescriptorField("Attributes"), | |||
"wMaxPacketSize" / DescriptorField("Maximum Packet Size"), | |||
"bInterval" / DescriptorField("Polling interval"), | |||
) | |||
DeviceQualifierDescriptor = DescriptorFormat( | |||
"bLength" / DescriptorField("Length"), | |||
"bDescriptorType" / DescriptorNumber(6), | |||
"bcdUSB" / DescriptorField("USB Version"), | |||
"bDeviceClass" / DescriptorField("Class"), | |||
"bDeviceSubclass" / DescriptorField("Subclass"), | |||
"bDeviceProtocol" / DescriptorField("Protocol"), | |||
"bMaxPacketSize0" / DescriptorField("EP0 Max Pkt Size"), | |||
"bNumConfigurations" / DescriptorField("Configuration Count"), | |||
"_bReserved" / construct.Optional(construct.Const(b"\0")) | |||
) | |||
class DescriptorParserCases(unittest.TestCase): | |||
def test_string_descriptor(self): | |||
string_descriptor = bytes([ | |||
40, # Length | |||
3, # Type | |||
ord('G'), 0x00, | |||
ord('r'), 0x00, | |||
ord('e'), 0x00, | |||
ord('a'), 0x00, | |||
ord('t'), 0x00, | |||
ord(' '), 0x00, | |||
ord('S'), 0x00, | |||
ord('c'), 0x00, | |||
ord('o'), 0x00, | |||
ord('t'), 0x00, | |||
ord('t'), 0x00, | |||
ord(' '), 0x00, | |||
ord('G'), 0x00, | |||
ord('a'), 0x00, | |||
ord('d'), 0x00, | |||
ord('g'), 0x00, | |||
ord('e'), 0x00, | |||
ord('t'), 0x00, | |||
ord('s'), 0x00, | |||
]) | |||
# Parse the relevant string... | |||
parsed = StringDescriptor.parse(string_descriptor) | |||
# ... and check the desriptor's fields. | |||
self.assertEqual(parsed.bLength, 40) | |||
self.assertEqual(parsed.bDescriptorType, 3) | |||
self.assertEqual(parsed.bString, "Great Scott Gadgets") | |||
def test_device_descriptor(self): | |||
device_descriptor = [ | |||
0x12, # Length | |||
0x01, # Type | |||
0x00, 0x02, # USB version | |||
0xFF, # class | |||
0xFF, # subclass | |||
0xFF, # protocol | |||
64, # ep0 max packet size | |||
0xd0, 0x16, # VID | |||
0x3b, 0x0f, # PID | |||
0x00, 0x00, # device rev | |||
0x01, # manufacturer string | |||
0x02, # product string | |||
0x03, # serial number | |||
0x01 # number of configurations | |||
] | |||
# Parse the relevant string... | |||
parsed = DeviceDescriptor.parse(device_descriptor) | |||
# ... and check the desriptor's fields. | |||
self.assertEqual(parsed.bLength, 18) | |||
self.assertEqual(parsed.bDescriptorType, 1) | |||
self.assertEqual(parsed.bcdUSB, 0x0200) | |||
self.assertEqual(parsed.bDeviceClass, 0xFF) | |||
self.assertEqual(parsed.bDeviceSubclass, 0xFF) | |||
self.assertEqual(parsed.bDeviceProtocol, 0xFF) | |||
self.assertEqual(parsed.bMaxPacketSize0, 64) | |||
self.assertEqual(parsed.idVendor, 0x16d0) | |||
self.assertEqual(parsed.idProduct, 0x0f3b) | |||
self.assertEqual(parsed.bcdDevice, 0x0000) | |||
self.assertEqual(parsed.iManufacturer, 1) | |||
self.assertEqual(parsed.iProduct, 2) | |||
self.assertEqual(parsed.iSerialNumber, 3) | |||
self.assertEqual(parsed.bNumConfigurations, 1) | |||
if __name__ == "__main__": | |||
unittest.main() |
@@ -0,0 +1,94 @@ | |||
# | |||
# This file is part of usb-protocol. | |||
# | |||
""" Helpers for creating easy emitters. """ | |||
import unittest | |||
import construct | |||
class ConstructEmitter: | |||
""" Class that creates a simple emitter based on a construct struct. | |||
For example, if we have a construct format that looks like the following: | |||
MyStruct = struct( | |||
"a" / Int8 | |||
"b" / Int8 | |||
) | |||
We could create emit an object like follows: | |||
emitter = ConstructEmitter(MyStruct) | |||
emitter.a = 0xab | |||
emitter.b = 0xcd | |||
my_bytes = emitter.emit() # "\xab\xcd" | |||
""" | |||
def __init__(self, struct): | |||
""" | |||
Parmeters: | |||
construct_format -- The format for which to create an emitter. | |||
""" | |||
self.__dict__['format'] = struct | |||
self.__dict__['fields'] = {} | |||
def _format_contains_field(self, field_name): | |||
""" Returns True iff the given format has a field with the provided name. | |||
Parameters: | |||
format_object -- The Construct format to work with. This includes e.g. most descriptor types. | |||
field_name -- The field name to query. | |||
""" | |||
return any(f.name == field_name for f in self.format.subcons) | |||
def __setattr__(self, name, value): | |||
""" Hook that we used to set our fields. """ | |||
# If the field starts with a '_', don't handle it, as it's an internal field. | |||
if name.startswith('_'): | |||
super().__setattr__(name, value) | |||
return | |||
if not self._format_contains_field(name): | |||
raise AttributeError(f"emitter specification contains no field {name}") | |||
self.fields[name] = value | |||
def emit(self): | |||
""" Emits the stream of bytes associated with this object. """ | |||
try: | |||
return self.format.build(self.fields) | |||
except KeyError as e: | |||
raise KeyError(f"missing necessary field: {e}") | |||
class ConstructEmitterTest(unittest.TestCase): | |||
def test_simple_emitter(self): | |||
test_struct = construct.Struct( | |||
"a" / construct.Int8ul, | |||
"b" / construct.Int8ul | |||
) | |||
emitter = ConstructEmitter(test_struct) | |||
emitter.a = 0xab | |||
emitter.b = 0xcd | |||
self.assertEqual(emitter.emit(), b"\xab\xcd") | |||
def emitter_for_format(construct_format): | |||
""" Creates a factory method for the relevant construct format. """ | |||
def _factory(): | |||
return ConstructEmitter(construct_format) | |||
return _factory | |||
if __name__ == "__main__": | |||
unittest.main() |
@@ -0,0 +1,68 @@ | |||
# | |||
# This file is part of usb-protocol. | |||
# | |||
from . import ConstructEmitter | |||
from collections import defaultdict | |||
class ComplexDescriptorEmitter(ConstructEmitter): | |||
""" Base class for emitting complex descriptors, which contain nested subordinates. """ | |||
# Base classes should override this. | |||
DESCRIPTOR_FORMAT = None | |||
def __init__(self): | |||
# Always create a basic ConstructEmitter from the given format. | |||
super().__init__(self.DESCRIPTOR_FORMAT) | |||
# Store a list of subordinate descriptors, and a count of | |||
# subordinate descriptor types. | |||
self._subordinates = [] | |||
self._type_counts = {} | |||
def add_subordinate_descriptor(self, subordinate): | |||
""" Adds a subordinate descriptor to the relevant descriptor. | |||
Parameter: | |||
subordinate -- The subordinate descriptor to add; can be an emitter, | |||
or a bytes-like object. | |||
""" | |||
if hasattr(subordinate, 'emit'): | |||
subordinate = subordinate.emit() | |||
else: | |||
subordinate = bytes(subordinate) | |||
# The second byte of a given descriptor is always its type number. | |||
# Count this descriptor type... | |||
subordinate_type = subordinate[1] | |||
try: | |||
self._type_counts[subordinate_type] += 1 | |||
except KeyError: | |||
self._type_counts[subordinate_type] = 1 | |||
# ... and add the relevant bytes to our list of subordinates. | |||
self._subordinates.append(subordinate) | |||
def emit(self, include_subordinates=True): | |||
""" Emit our descriptor. | |||
Parameters: | |||
include_subordinates -- If true or not provided, any subordinate descriptors will be included. | |||
""" | |||
result = bytearray() | |||
# Add our basic descriptor... | |||
result.extend(super().emit()) | |||
# ... and if descired, add our subordinates... | |||
for sub in self._subordinates: | |||
result.extend(sub) | |||
return bytes(result) |
@@ -0,0 +1,171 @@ | |||
# | |||
# This file is part of usb_protocol. | |||
# | |||
""" Convenience emitters for simple, standard descriptors. """ | |||
import unittest | |||
import functools | |||
from contextlib import contextmanager | |||
from .. import emitter_for_format | |||
from ..descriptor import ComplexDescriptorEmitter | |||
from ...types.descriptors.standard import \ | |||
DeviceDescriptor, StringDescriptor, EndpointDescriptor, DeviceQualifierDescriptor, \ | |||
ConfigurationDescriptor, InterfaceDescriptor, StandardDescriptorNumbers | |||
# Create our basic emitters... | |||
DeviceDescriptorEmitter = emitter_for_format(DeviceDescriptor) | |||
StringDescriptorEmitter = emitter_for_format(StringDescriptor) | |||
EndpointDescriptorEmitter = emitter_for_format(EndpointDescriptor) | |||
DeviceQualifierDescriptor = emitter_for_format(DeviceQualifierDescriptor) | |||
# ... convenience functions ... | |||
def get_string_descriptor(string): | |||
""" Generates a string descriptor for the relevant string. """ | |||
emitter = StringDescriptorEmitter() | |||
emitter.bString = string | |||
return emitter.emit() | |||
# ... and complex emitters. | |||
class InterfaceDescriptorEmitter(ComplexDescriptorEmitter): | |||
""" Emitter that creates an InterfaceDescriptor. """ | |||
DESCRIPTOR_FORMAT = InterfaceDescriptor | |||
@contextmanager | |||
def EndpointDescriptor(self): | |||
""" Context manager that allows addition of a subordinate endpoint descriptor. | |||
It can be used with a `with` statement; and yields an EndpointDesriptorEmitter | |||
that can be populated: | |||
with interface.EndpointDescriptor() as d: | |||
d.bEndpointAddress = 0x01 | |||
d.bmAttributes = 0x80 | |||
d.wMaxPacketSize = 64 | |||
d.bInterval = 0 | |||
This adds the relevant descriptor, automatically. | |||
""" | |||
descriptor = EndpointDescriptorEmitter() | |||
yield descriptor | |||
self.add_subordinate_descriptor(descriptor) | |||
def emit(self, include_subordinates=True): | |||
# Count our endpoints, and then call our parent emitter. | |||
self.bNumEndpoints = self._type_counts[StandardDescriptorNumbers.ENDPOINT] | |||
return super().emit(include_subordinates=include_subordinates) | |||
class ConfigurationDescriptorEmitter(ComplexDescriptorEmitter): | |||
""" Emitter that creates a configuration descriptor. """ | |||
DESCRIPTOR_FORMAT = ConfigurationDescriptor | |||
@contextmanager | |||
def InterfaceDescriptor(self): | |||
""" Context manager that allows addition of a subordinate interface descriptor. | |||
It can be used with a `with` statement; and yields an InterfaceDescriptorEmitter | |||
that can be populated: | |||
with interface.InterfaceDescriptor() as d: | |||
d.bInterfaceNumber = 0x01 | |||
[snip] | |||
This adds the relevant descriptor, automatically. Note that populating derived | |||
fields such as bNumEndpoints aren't necessary; they'll be populated automatically. | |||
""" | |||
descriptor = InterfaceDescriptorEmitter() | |||
yield descriptor | |||
self.add_subordinate_descriptor(descriptor) | |||
def emit(self, include_subordinates=True): | |||
# Count our interfaces... | |||
self.bNumInterfaces = self._type_counts[StandardDescriptorNumbers.INTERFACE] | |||
# ... and figure out our total length. | |||
subordinate_length = sum(len(sub) for sub in self._subordinates) | |||
self.wTotalLength = subordinate_length + self.DESCRIPTOR_FORMAT.sizeof() | |||
# Finally, emit our whole descriptor. | |||
return super().emit(include_subordinates=include_subordinates) | |||
class EmitterTests(unittest.TestCase): | |||
def test_string_emitter(self): | |||
emitter = StringDescriptorEmitter() | |||
emitter.bString = "Hello" | |||
self.assertEqual(emitter.emit(), b"\x0C\x03H\0e\0l\0l\0o\0") | |||
def test_string_emitter_function(self): | |||
self.assertEqual(get_string_descriptor("Hello"), b"\x0C\x03H\0e\0l\0l\0o\0") | |||
def test_configuration_emitter(self): | |||
descriptor = bytes([ | |||
# config descriptor | |||
12, # length | |||
2, # type | |||
25, 00, # total length | |||
1, # num interfaces | |||
1, # configuration number | |||
0, # config string | |||
0x80, # attributes | |||
250, # max power | |||
# interface descriptor | |||
9, # length | |||
4, # type | |||
0, # number | |||
0, # alternate | |||
1, # num endpoints | |||
0xff, # class | |||
0xff, # subclass | |||
0xff, # protocol | |||
0, # string | |||
# endpoint descriptor | |||
7, # length | |||
5, # type | |||
0x01, # address | |||
2, # attributes | |||
64, 0, # max packet size | |||
255, # interval | |||
]) | |||
# Create a trivial configuration descriptor... | |||
emitter = ConfigurationDescriptorEmitter() | |||
with emitter.InterfaceDescriptor() as interface: | |||
interface.bInterfaceNumber = 0 | |||
with interface.EndpointDescriptor() as endpoint: | |||
endpoint.bEndpointAddress = 1 | |||
# ... and validate that it maches our reference descriptor. | |||
binary = emitter.emit() | |||
self.assertEqual(len(binary), len(descriptor)) | |||
if __name__ == "__main__": | |||
unittest.main() |
@@ -414,3 +414,228 @@ LANGUAGE_NAMES = { | |||
0xf8ff: "HID (Vendor Defined 3)", | |||
0xfcff: "HID (Vendor Defined 4)", | |||
} | |||
class LanguageIDs(IntEnum): | |||
AFRIKAANS = 0X0436 | |||
ALBANIAN = 0X041C | |||
ARABIC_SAUDI_ARABIA = 0X0401 | |||
ARABIC_IRAQ = 0X0801 | |||
ARABIC_EGYPT = 0X0C01 | |||
ARABIC_LIBYA = 0X1001 | |||
ARABIC_ALGERIA = 0X1401 | |||
ARABIC_MOROCCO = 0X1801 | |||
ARABIC_TUNISIA = 0X1C01 | |||
ARABIC_OMAN = 0X2001 | |||
ARABIC_YEMEN = 0X2401 | |||
ARABIC_SYRIA = 0X2801 | |||
ARABIC_JORDAN = 0X2C01 | |||
ARABIC_LEBANON = 0X3001 | |||
ARABIC_KUWAIT = 0X3401 | |||
ARABIC_UAE = 0X3801 | |||
ARABIC_BAHRAIN = 0X3C01 | |||
ARABIC_QATAR = 0X4001 | |||
ARMENIAN = 0X042B | |||
ASSAMESE = 0X044D | |||
AZERI_LATIN = 0X042C | |||
AZERI_CYRILLIC = 0X082C | |||
BASQUE = 0X042D | |||
BELARUSSIAN = 0X0423 | |||
BENGALI = 0X0445 | |||
BULGARIAN = 0X0402 | |||
BURMESE = 0X0455 | |||
CATALAN = 0X0403 | |||
CHINESE_TAIWAN = 0X0404 | |||
CHINESE_PRC = 0X0804 | |||
CHINESE_HONG_KONG = 0X0C04 | |||
CHINESE_SINGAPORE = 0X1004 | |||
CHINESE_MACAU_SAR = 0X1404 | |||
CROATIAN = 0X041A | |||
CZECH = 0X0405 | |||
DANISH = 0X0406 | |||
DUTCH_NETHERLANDS = 0X0413 | |||
DUTCH_BELGIUM = 0X0813 | |||
ENGLISH_US = 0X0409 | |||
ENGLISH_UNITED_KINGDOM = 0X0809 | |||
ENGLISH_AUSTRALIAN = 0X0C09 | |||
ENGLISH_CANADIAN = 0X1009 | |||
ENGLISH_NEW_ZEALAND = 0X1409 | |||
ENGLISH_IRELAND = 0X1809 | |||
ENGLISH_SOUTH_AFRICA = 0X1C09 | |||
ENGLISH_JAMAICA = 0X2009 | |||
ENGLISH_CARIBBEAN = 0X2409 | |||
ENGLISH_BELIZE = 0X2809 | |||
ENGLISH_TRINIDAD = 0X2C09 | |||
ENGLISH_ZIMBABWE = 0X3009 | |||
ENGLISH_PHILIPPINES = 0X3409 | |||
ESTONIAN = 0X0425 | |||
FAEROESE = 0X0438 | |||
FARSI = 0X0429 | |||
FINNISH = 0X040B | |||
FRENCH_STANDARD = 0X040C | |||
FRENCH_BELGIAN = 0X080C | |||
FRENCH_CANADIAN = 0X0C0C | |||
FRENCH_SWITZERLAND = 0X100C | |||
FRENCH_LUXEMBOURG = 0X140C | |||
FRENCH_MONACO = 0X180C | |||
GEORGIAN = 0X0437 | |||
GERMAN_STANDARD = 0X0407 | |||
GERMAN_SWITZERLAND = 0X0807 | |||
GERMAN_AUSTRIA = 0X0C07 | |||
GERMAN_LUXEMBOURG = 0X1007 | |||
GERMAN_LIECHTENSTEIN = 0X1407 | |||
GREEK = 0X0408 | |||
GUJARATI = 0X0447 | |||
HEBREW = 0X040D | |||
HINDI = 0X0439 | |||
HUNGARIAN = 0X040E | |||
ICELANDIC = 0X040F | |||
INDONESIAN = 0X0421 | |||
ITALIAN_STANDARD = 0X0410 | |||
ITALIAN_SWITZERLAND = 0X0810 | |||
JAPANESE = 0X0411 | |||
KANNADA = 0X044B | |||
KASHMIRI_INDIA = 0X0860 | |||
KAZAKH = 0X043F | |||
KONKANI = 0X0457 | |||
KOREAN = 0X0412 | |||
KOREAN_JOHAB = 0X0812 | |||
LATVIAN = 0X0426 | |||
LITHUANIAN = 0X0427 | |||
LITHUANIAN_CLASSIC = 0X0827 | |||
MACEDONIAN = 0X042F | |||
MALAY_MALAYSIAN = 0X043E | |||
MALAY_BRUNEI_DARUSSALAM = 0X083E | |||
MALAYALAM = 0X044C | |||
MANIPURI = 0X0458 | |||
MARATHI = 0X044E | |||
NEPALI_INDIA = 0X0861 | |||
NORWEGIAN_BOKMAL = 0X0414 | |||
NORWEGIAN_NYNORSK = 0X0814 | |||
ORIYA = 0X0448 | |||
POLISH = 0X0415 | |||
PORTUGUESE_BRAZIL = 0X0416 | |||
PORTUGUESE_STANDARD = 0X0816 | |||
PUNJABI = 0X0446 | |||
ROMANIAN = 0X0418 | |||
RUSSIAN = 0X0419 | |||
SANSKRIT = 0X044F | |||
SERBIAN_CYRILLIC = 0X0C1A | |||
SERBIAN_LATIN = 0X081A | |||
SINDHI = 0X0459 | |||
SLOVAK = 0X041B | |||
SLOVENIAN = 0X0424 | |||
SPANISH_TRADITIONAL_SORT = 0X040A | |||
SPANISH_MEXICAN = 0X080A | |||
SPANISH_MODERN_SORT = 0X0C0A | |||
SPANISH_GUATEMALA = 0X100A | |||
SPANISH_COSTA_RICA = 0X140A | |||
SPANISH_PANAMA = 0X180A | |||
SPANISH_DOMINICAN_REPUBLIC = 0X1C0A | |||
SPANISH_VENEZUELA = 0X200A | |||
SPANISH_COLOMBIA = 0X240A | |||
SPANISH_PERU = 0X280A | |||
SPANISH_ARGENTINA = 0X2C0A | |||
SPANISH_ECUADOR = 0X300A | |||
SPANISH_CHILE = 0X340A | |||
SPANISH_URUGUAY = 0X380A | |||
SPANISH_PARAGUAY = 0X3C0A | |||
SPANISH_BOLIVIA = 0X400A | |||
SPANISH_EL_SALVADOR = 0X440A | |||
SPANISH_HONDURAS = 0X480A | |||
SPANISH_NICARAGUA = 0X4C0A | |||
SPANISH_PUERTO_RICO = 0X500A | |||
SUTU = 0X0430 | |||
SWAHILI_KENYA = 0X0441 | |||
SWEDISH = 0X041D | |||
SWEDISH_FINLAND = 0X081D | |||
TAMIL = 0X0449 | |||
TATAR_TATARSTAN = 0X0444 | |||
TELUGU = 0X044A | |||
THAI = 0X041E | |||
TURKISH = 0X041F | |||
UKRAINIAN = 0X0422 | |||
URDU_PAKISTAN = 0X0420 | |||
URDU_INDIA = 0X0820 | |||
UZBEK_LATIN = 0X0443 | |||
UZBEK_CYRILLIC = 0X0843 | |||
VIETNAMESE = 0X042A | |||
HID_USAGE_DATA_DESCRIPTOR = 0X04FF | |||
HID_VENDOR_DEFINED_1 = 0XF0FF | |||
HID_VENDOR_DEFINED_2 = 0XF4FF | |||
HID_VENDOR_DEFINED_3 = 0XF8FF | |||
HID_VENDOR_DEFINED_4 = 0XFCFF | |||
class DescriptorTypes(IntEnum): | |||
DEVICE = 1 | |||
CONFIGURATION = 2 | |||
STRING = 3 | |||
INTERFACE = 4 | |||
ENDPOINT = 5 | |||
DEVICE_QUALIFIER = 6 | |||
OTHER_SPEED_CONFIGURATION = 7 | |||
INTERFACE_POWER = 8 | |||
HID = 33 | |||
REPORT = 34 | |||
class USBSynchronizationType(IntEnum): | |||
NONE = 0x00 | |||
ASYNC = 0x01 | |||
ADAPTIVE = 0x02 | |||
SYNCHRONOUS = 0x03 | |||
class USBUsageType(IntEnum): | |||
DATA = 0 | |||
FEEDBACK = 1 | |||
IMPLICIT_FEEDBACK = 2 | |||
class USBStandardRequests(IntEnum): | |||
GET_STATUS = 0 | |||
CLEAR_FEATURE = 1 | |||
SET_FEATURE = 3 | |||
SET_ADDRESS = 5 | |||
GET_DESCRIPTOR = 6 | |||
SET_DESCRIPTOR = 7 | |||
GET_CONFIGURATION = 8 | |||
SET_CONFIGURATION = 9 | |||
GET_INTERFACE = 10 | |||
SET_INTERFACE = 11 | |||
SYNCH_FRAME = 12 | |||
class USBTransferType(IntEnum): | |||
CONTROL = 0 | |||
ISOCHRONOUS = 1 | |||
BULK = 2 | |||
INTERRUPT = 3 | |||
class USBSynchronizationType(IntEnum): | |||
NONE = 0x00 | |||
ASYNC = 0x01 | |||
ADAPTIVE = 0x02 | |||
SYNCHRONOUS = 0x03 | |||
class USBUsageType(IntEnum): | |||
DATA = 0 | |||
FEEDBACK = 1 | |||
IMPLICIT_FEEDBACK = 2 | |||
class USBStandardRequests(IntEnum): | |||
GET_STATUS = 0 | |||
CLEAR_FEATURE = 1 | |||
SET_FEATURE = 3 | |||
SET_ADDRESS = 5 | |||
GET_DESCRIPTOR = 6 | |||
SET_DESCRIPTOR = 7 | |||
GET_CONFIGURATION = 8 | |||
SET_CONFIGURATION = 9 | |||
GET_INTERFACE = 10 | |||
SET_INTERFACE = 11 | |||
SYNCH_FRAME = 12 |
@@ -3,6 +3,7 @@ | |||
# | |||
""" Type elements for defining USB descriptors. """ | |||
import unittest | |||
import construct | |||
class DescriptorFormat(construct.Struct): | |||
@@ -76,6 +77,28 @@ class DescriptorNumber(construct.Const): | |||
return self.number | |||
class BCDFieldAdapter(construct.Adapter): | |||
""" Construct adapter that dynamically parses BCD fields. """ | |||
def _decode(self, obj, context, path): | |||
hex_string = f"{obj:04x}" | |||
return float(f"{hex_string[0:2]}.{hex_string[2:]}") | |||
def _encode(self, obj, context, path): | |||
# Ensure the data is parseable. | |||
if (obj * 100) % 1: | |||
raise AssertionError("BCD fields must be in the format XX.YY") | |||
# Break the object down into its component parts... | |||
integer = int(obj) | |||
percent = int((obj * 100) % 100) | |||
# ... and squish them into an integer. | |||
return int(f"{integer:02}{percent:02}", 16) | |||
class DescriptorField(construct.Subconstruct): | |||
""" | |||
@@ -90,12 +113,12 @@ class DescriptorField(construct.Subconstruct): | |||
# FIXME: these are really primitive views of these types; | |||
# we should extend these to get implicit parsing wherever possible | |||
USB_TYPES = { | |||
'b' : construct.Optional(construct.Int8ul), | |||
'bcd' : construct.Optional(construct.Int16ul), # TODO: Create a BCD parser for this | |||
'i' : construct.Optional(construct.Int8ul), | |||
'id' : construct.Optional(construct.Int16ul), | |||
'bm' : construct.Optional(construct.Int8ul), | |||
'w' : construct.Optional(construct.Int16ul), | |||
'b' : construct.Int8ul, | |||
'bcd' : BCDFieldAdapter(construct.Int16ul), # TODO: Create a BCD parser for this | |||
'i' : construct.Int8ul, | |||
'id' : construct.Int16ul, | |||
'bm' : construct.Int8ul, | |||
'w' : construct.Int16ul, | |||
} | |||
@staticmethod | |||
@@ -128,12 +151,26 @@ class DescriptorField(construct.Subconstruct): | |||
raise ValueError("field names must be formatted per the USB standard!") | |||
def __init__(self, description=""): | |||
def __init__(self, description="", default=None): | |||
self.description = description | |||
self.default = default | |||
def __rtruediv__(self, field_name): | |||
field_type = self._get_type_for_name(field_name) | |||
# wew does construct make this look weird | |||
return (field_name / field_type) * self.description | |||
# Build our subconstruct. Construct makes this look super weird, | |||
# but this is actually "we have a field with <field_name> of type <field_type>". | |||
# In long form, we'll call it "description". | |||
subconstruct = (field_name / field_type) * self.description | |||
if self.default is not None: | |||
return construct.Default(subconstruct, self.default) | |||
else: | |||
return subconstruct | |||
# Convenience type that gets a descriptor's own length. | |||
DescriptorLength = \ | |||
construct.Rebuild(construct.Int8ul, construct.len_(construct.this)) \ | |||
* "Descriptor Length" |
@@ -0,0 +1,218 @@ | |||
# | |||
# This file is part of usb-protocol. | |||
# | |||
""" Structures describing standard USB descriptors. """ | |||
import unittest | |||
from enum import IntEnum | |||
import construct | |||
from construct import this, Default | |||
from .. import LanguageIDs | |||
from ..descriptor import \ | |||
DescriptorField, DescriptorNumber, DescriptorFormat, \ | |||
BCDFieldAdapter, DescriptorLength | |||
class StandardDescriptorNumbers(IntEnum): | |||
""" Numbers of our standard descriptors. """ | |||
DEVICE = 1 | |||
CONFIGURATION = 2 | |||
STRING = 3 | |||
INTERFACE = 4 | |||
ENDPOINT = 5 | |||
DEVICE_QUALIFIER = 6 | |||
OTHER_SPEED_DESCRIPTOR = 7 | |||
INTERFACE_POWER = 8 | |||
DeviceDescriptor = DescriptorFormat( | |||
"bLength" / DescriptorLength, | |||
"bDescriptorType" / DescriptorNumber(StandardDescriptorNumbers.DEVICE), | |||
"bcdUSB" / DescriptorField("USB Version", default=2.0), | |||
"bDeviceClass" / DescriptorField("Class", default=0), | |||
"bDeviceSubclass" / DescriptorField("Subclass", default=0), | |||
"bDeviceProtocol" / DescriptorField("Protocol", default=0), | |||
"bMaxPacketSize0" / DescriptorField("EP0 Max Pkt Size", default=64), | |||
"idVendor" / DescriptorField("Vendor ID"), | |||
"idProduct" / DescriptorField("Product ID"), | |||
"bcdDevice" / DescriptorField("Device Version", default=0), | |||
"iManufacturer" / DescriptorField("Manufacturer Str", default=0), | |||
"iProduct" / DescriptorField("Product Str", default=0), | |||
"iSerialNumber" / DescriptorField("Serial Number", default=0), | |||
"bNumConfigurations" / DescriptorField("Configuration Count"), | |||
) | |||
ConfigurationDescriptor = DescriptorFormat( | |||
"bLength" / DescriptorLength, | |||
"bDescriptorType" / DescriptorNumber(StandardDescriptorNumbers.CONFIGURATION), | |||
"wTotalLength" / DescriptorField("Length including subordinates"), | |||
"bNumInterfaces" / DescriptorField("Interface count"), | |||
"bConfigurationValue" / DescriptorField("Configuration number", default=1), | |||
"iConfiguration" / DescriptorField("Description string", default=0), | |||
"bmAttributes" / DescriptorField("Attributes", default=0x80), | |||
"bMaxPower" / DescriptorField("Max power consumption", default=250), | |||
) | |||
# Field that automatically reflects a string descriptor's length. | |||
StringDescriptorLength = construct.Rebuild(construct.Int8ul, construct.len_(this.bString) * 2 + 2) | |||
StringDescriptor = DescriptorFormat( | |||
"bLength" / StringDescriptorLength, | |||
"bDescriptorType" / DescriptorNumber(StandardDescriptorNumbers.STRING), | |||
"bString" / construct.GreedyString("utf_16_le") | |||
) | |||
StringLanguageDescriptorLength = \ | |||
construct.Rebuild(construct.Int8ul, construct.len_(this.wLANGID) * 2 + 2) | |||
StringLanguageDescriptor = DescriptorFormat( | |||
"bLength" / StringLanguageDescriptorLength, | |||
"bDescriptorType" / DescriptorNumber(StandardDescriptorNumbers.STRING), | |||
"wLANGID" / construct.GreedyRange(construct.Int16ul) | |||
) | |||
InterfaceDescriptor = DescriptorFormat( | |||
"bLength" / construct.Const(9, construct.Int8ul), | |||
"bDescriptorType" / DescriptorNumber(StandardDescriptorNumbers.INTERFACE), | |||
"bInterfaceNumber" / DescriptorField("Interface number"), | |||
"bAlternateSetting" / DescriptorField("Alternate setting", default=0), | |||
"bNumEndpoints" / DescriptorField("Endpoints included"), | |||
"bInterfaceClass" / DescriptorField("Class", default=0xff), | |||
"bInterfaceSubclass" / DescriptorField("Subclass", default=0xff), | |||
"bInterfaceProtocol" / DescriptorField("Protocol", default=0xff), | |||
"iInterface" / DescriptorField("String index", default=0), | |||
) | |||
EndpointDescriptor = DescriptorFormat( | |||
"bLength" / construct.Const(7, construct.Int8ul), | |||
"bDescriptorType" / DescriptorNumber(StandardDescriptorNumbers.ENDPOINT), | |||
"bEndpointAddress" / DescriptorField("Endpoint Address"), | |||
"bmAttributes" / DescriptorField("Attributes", default=2), | |||
"wMaxPacketSize" / DescriptorField("Maximum Packet Size", default=64), | |||
"bInterval" / DescriptorField("Polling interval", default=255), | |||
) | |||
DeviceQualifierDescriptor = DescriptorFormat( | |||
"bLength" / DescriptorLength, | |||
"bDescriptorType" / DescriptorNumber(StandardDescriptorNumbers.DEVICE_QUALIFIER), | |||
"bcdUSB" / DescriptorField("USB Version"), | |||
"bDeviceClass" / DescriptorField("Class"), | |||
"bDeviceSubclass" / DescriptorField("Subclass"), | |||
"bDeviceProtocol" / DescriptorField("Protocol"), | |||
"bMaxPacketSize0" / DescriptorField("EP0 Max Pkt Size"), | |||
"bNumConfigurations" / DescriptorField("Configuration Count"), | |||
"_bReserved" / construct.Optional(construct.Const(b"\0")) | |||
) | |||
class DescriptorParserCases(unittest.TestCase): | |||
STRING_DESCRIPTOR = bytes([ | |||
40, # Length | |||
3, # Type | |||
ord('G'), 0x00, | |||
ord('r'), 0x00, | |||
ord('e'), 0x00, | |||
ord('a'), 0x00, | |||
ord('t'), 0x00, | |||
ord(' '), 0x00, | |||
ord('S'), 0x00, | |||
ord('c'), 0x00, | |||
ord('o'), 0x00, | |||
ord('t'), 0x00, | |||
ord('t'), 0x00, | |||
ord(' '), 0x00, | |||
ord('G'), 0x00, | |||
ord('a'), 0x00, | |||
ord('d'), 0x00, | |||
ord('g'), 0x00, | |||
ord('e'), 0x00, | |||
ord('t'), 0x00, | |||
ord('s'), 0x00, | |||
]) | |||
def test_string_descriptor_parse(self): | |||
# Parse the relevant string... | |||
parsed = StringDescriptor.parse(self.STRING_DESCRIPTOR) | |||
# ... and check the desriptor's fields. | |||
self.assertEqual(parsed.bLength, 40) | |||
self.assertEqual(parsed.bDescriptorType, 3) | |||
self.assertEqual(parsed.bString, "Great Scott Gadgets") | |||
def test_string_descriptor_build(self): | |||
data = StringDescriptor.build({ | |||
'bString': "Great Scott Gadgets" | |||
}) | |||
self.assertEqual(data, self.STRING_DESCRIPTOR) | |||
def test_string_language_descriptor_build(self): | |||
data = StringLanguageDescriptor.build({ | |||
'wLANGID': (LanguageIDs.ENGLISH_US,) | |||
}) | |||
self.assertEqual(data, b"\x04\x03\x09\x04") | |||
def test_device_descriptor(self): | |||
device_descriptor = [ | |||
0x12, # Length | |||
0x01, # Type | |||
0x00, 0x02, # USB version | |||
0xFF, # class | |||
0xFF, # subclass | |||
0xFF, # protocol | |||
64, # ep0 max packet size | |||
0xd0, 0x16, # VID | |||
0x3b, 0x0f, # PID | |||
0x00, 0x00, # device rev | |||
0x01, # manufacturer string | |||
0x02, # product string | |||
0x03, # serial number | |||
0x01 # number of configurations | |||
] | |||
# Parse the relevant string... | |||
parsed = DeviceDescriptor.parse(device_descriptor) | |||
# ... and check the desriptor's fields. | |||
self.assertEqual(parsed.bLength, 18) | |||
self.assertEqual(parsed.bDescriptorType, 1) | |||
self.assertEqual(parsed.bcdUSB, 2.0) | |||
self.assertEqual(parsed.bDeviceClass, 0xFF) | |||
self.assertEqual(parsed.bDeviceSubclass, 0xFF) | |||
self.assertEqual(parsed.bDeviceProtocol, 0xFF) | |||
self.assertEqual(parsed.bMaxPacketSize0, 64) | |||
self.assertEqual(parsed.idVendor, 0x16d0) | |||
self.assertEqual(parsed.idProduct, 0x0f3b) | |||
self.assertEqual(parsed.bcdDevice, 0) | |||
self.assertEqual(parsed.iManufacturer, 1) | |||
self.assertEqual(parsed.iProduct, 2) | |||
self.assertEqual(parsed.iSerialNumber, 3) | |||
self.assertEqual(parsed.bNumConfigurations, 1) | |||
def test_bcd_constructor(self): | |||
emitter = BCDFieldAdapter(construct.Int16ul) | |||
result = emitter.build(1.4) | |||
self.assertEqual(result, b"\x40\x01") | |||
if __name__ == "__main__": | |||
unittest.main() |