@@ -4,7 +4,7 @@ from setuptools import setup, find_packages | |||||
setup( | setup( | ||||
# Vitals | # Vitals | ||||
name='usb-protocol', | |||||
name='usb_protocol', | |||||
license='BSD', | license='BSD', | ||||
url='https://github.com/usb-tool/luna', | url='https://github.com/usb-tool/luna', | ||||
author='Katherine J. Temkin', | 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)", | 0xf8ff: "HID (Vendor Defined 3)", | ||||
0xfcff: "HID (Vendor Defined 4)", | 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. """ | """ Type elements for defining USB descriptors. """ | ||||
import unittest | |||||
import construct | import construct | ||||
class DescriptorFormat(construct.Struct): | class DescriptorFormat(construct.Struct): | ||||
@@ -76,6 +77,28 @@ class DescriptorNumber(construct.Const): | |||||
return self.number | 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): | class DescriptorField(construct.Subconstruct): | ||||
""" | """ | ||||
@@ -90,12 +113,12 @@ class DescriptorField(construct.Subconstruct): | |||||
# FIXME: these are really primitive views of these types; | # FIXME: these are really primitive views of these types; | ||||
# we should extend these to get implicit parsing wherever possible | # we should extend these to get implicit parsing wherever possible | ||||
USB_TYPES = { | 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 | @staticmethod | ||||
@@ -128,12 +151,26 @@ class DescriptorField(construct.Subconstruct): | |||||
raise ValueError("field names must be formatted per the USB standard!") | 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.description = description | ||||
self.default = default | |||||
def __rtruediv__(self, field_name): | def __rtruediv__(self, field_name): | ||||
field_type = self._get_type_for_name(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() |