| @@ -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() | |||