| @@ -1,102 +1,7 @@ | |||
| # | |||
| # This file is part of usb-protocol. | |||
| # | |||
| """ Helpers for creating easy emitters. """ | |||
| """ USB-related 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}") | |||
| def __getattr__(self, name): | |||
| """ Retrieves an emitter field, if possible. """ | |||
| if name in self.fields: | |||
| return self.fields[name] | |||
| else: | |||
| raise AttributeError(f"descriptor emitter has no property {name}") | |||
| 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() | |||
| from .construct import emitter_for_format, ConstructEmitter | |||
| from .descriptors.standard import DeviceDescriptorCollection | |||
| @@ -0,0 +1,103 @@ | |||
| # | |||
| # This file is part of usb-protocol. | |||
| # | |||
| """ Helpers for creating construct-related 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}") | |||
| def __getattr__(self, name): | |||
| """ Retrieves an emitter field, if possible. """ | |||
| if name in self.fields: | |||
| return self.fields[name] | |||
| else: | |||
| raise AttributeError(f"descriptor emitter has no property {name}") | |||
| 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() | |||
| @@ -230,11 +230,22 @@ class DeviceDescriptorCollection: | |||
| self.add_descriptor(descriptor) | |||
| def get_descriptor_bytes(self, type_number: int, index: int = 0): | |||
| """ Returns the raw, binary descriptor for a given descriptor type/index. | |||
| Parmeters: | |||
| type_number -- The descriptor type number. | |||
| index -- The index of the relevant descriptor, if relevant. | |||
| """ | |||
| return self._descriptors[(type_number, index)] | |||
| def __iter__(self): | |||
| """ Allow iterating over each of our descriptors; yields (index, value, descriptor). """ | |||
| return ((number, index, desc) for ((number, index), desc) in self._descriptors.items()) | |||
| class EmitterTests(unittest.TestCase): | |||
| def test_string_emitter(self): | |||
| @@ -29,7 +29,7 @@ class StandardDescriptorNumbers(IntEnum): | |||
| DeviceDescriptor = DescriptorFormat( | |||
| "bLength" / DescriptorLength, | |||
| "bLength" / construct.Const(0x12, construct.Int8ul), | |||
| "bDescriptorType" / DescriptorNumber(StandardDescriptorNumbers.DEVICE), | |||
| "bcdUSB" / DescriptorField("USB Version", default=2.0), | |||
| "bDeviceClass" / DescriptorField("Class", default=0), | |||
| @@ -47,7 +47,7 @@ DeviceDescriptor = DescriptorFormat( | |||
| ConfigurationDescriptor = DescriptorFormat( | |||
| "bLength" / DescriptorLength, | |||
| "bLength" / construct.Const(9, construct.Int8ul), | |||
| "bDescriptorType" / DescriptorNumber(StandardDescriptorNumbers.CONFIGURATION), | |||
| "wTotalLength" / DescriptorField("Length including subordinates"), | |||
| "bNumInterfaces" / DescriptorField("Interface count"), | |||
| @@ -101,7 +101,7 @@ EndpointDescriptor = DescriptorFormat( | |||
| DeviceQualifierDescriptor = DescriptorFormat( | |||
| "bLength" / DescriptorLength, | |||
| "bLength" / construct.Const(9, construct.Int8ul), | |||
| "bDescriptorType" / DescriptorNumber(StandardDescriptorNumbers.DEVICE_QUALIFIER), | |||
| "bcdUSB" / DescriptorField("USB Version"), | |||
| "bDeviceClass" / DescriptorField("Class"), | |||