Chromium Code Reviews| Index: tools/usb_gadget/descriptor.py |
| diff --git a/tools/usb_gadget/descriptor.py b/tools/usb_gadget/descriptor.py |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..a53a86cd5765b8cc4efd1962e22d4fcf95079c72 |
| --- /dev/null |
| +++ b/tools/usb_gadget/descriptor.py |
| @@ -0,0 +1,359 @@ |
| +"""USB descriptor generation utilities. |
|
not at google - send to devlin
2014/07/23 00:05:44
copyright
Reilly Grant (use Gerrit)
2014/07/23 00:43:54
Done.
|
| + |
| +Classes to represent and generate USB descriptors. |
| +""" |
|
Ken Rockot(use gerrit already)
2014/07/23 00:03:48
Maybe this module should be usb_descriptors?
Reilly Grant (use Gerrit)
2014/07/23 00:43:54
Done.
|
| + |
| +import struct |
| + |
| +import usb_const |
|
Ken Rockot(use gerrit already)
2014/07/23 00:03:47
How about "usb_constants" for the module name?
Reilly Grant (use Gerrit)
2014/07/23 00:43:54
Done.
|
| + |
| + |
| +class Field(object): |
| + |
|
Ken Rockot(use gerrit already)
2014/07/23 00:03:47
A bit of documentation?
Reilly Grant (use Gerrit)
2014/07/23 00:43:54
Done.
|
| + def __init__(self, name, str_fmt, struct_fmt, required): |
| + self.name = name |
| + self.str_fmt = str_fmt |
| + self.struct_fmt = struct_fmt |
| + self.required = required |
| + |
| + def Format(self, value): |
| + return self.str_fmt.format(value) |
| + |
| + |
| +class Descriptor(object): |
| + """Base class for USB descriptor types. |
| + |
| + This class provides general functionality for creating object types that |
| + represent USB descriptors. The AddField and related methods are used to |
| + define the fields of each structure. Fields can then be set using keyword |
| + arguments to the object constructor or by accessing properties on the object. |
| + """ |
| + |
| + _fields = None |
| + |
| + @classmethod |
| + def AddField(cls, name, struct_fmt, str_fmt='{}', default=None): |
| + """Adds a user-specified field to this descriptor. |
| + |
| + Adds a field to the binary structure representing this descriptor. The field |
| + can be set by passing a keyword argument name=... to the object constructor |
| + will be accessible as foo.name on any instance. |
| + |
| + If no default value is provided then the constructor will through an |
| + exception if this field is not one of the provided keyword arguments. |
| + |
| + Args: |
| + name: String name of the field. |
| + struct_fmt: Python 'struct' module format string for this field. |
| + str_fmt: Python 'string' module format string for this field. |
| + default: Default value. |
| + """ |
| + if cls._fields is None: |
| + cls._fields = [] |
| + cls._fields.append(Field(name, str_fmt, struct_fmt, default is None)) |
| + |
| + member_name = '_{}'.format(name) |
| + def Setter(self, value): |
| + setattr(self, member_name, value) |
| + def Getter(self): |
| + try: |
| + return getattr(self, member_name) |
| + except AttributeError: |
| + assert default is not None |
| + return default |
| + |
| + setattr(cls, name, property(Getter, Setter)) |
| + |
| + @classmethod |
| + def AddFixedField(cls, name, struct_fmt, value, str_fmt='{}'): |
| + """Adds a constant field to this descriptor. |
| + |
| + Adds a constant field to the binary structure representing this descriptor. |
| + The field will be accessible as foo.name on any instance. |
| + |
| + The value of this field may not be given as a constructor parameter or |
| + set on an existing instance. |
| + |
| + Args: |
| + name: String name of the field. |
| + struct_fmt: Python 'struct' module format string for this field. |
| + value: Field value. |
| + str_fmt: Python 'string' module format string for this field. |
| + """ |
| + if cls._fields is None: |
| + cls._fields = [] |
| + cls._fields.append(Field(name, str_fmt, struct_fmt, False)) |
| + |
| + def Setter(unused_self, unused_value): |
| + raise RuntimeError('{} is a fixed field.'.format(name)) |
| + def Getter(unused_self): |
| + return value |
| + |
| + setattr(cls, name, property(Getter, Setter)) |
| + |
| + @classmethod |
| + def AddComputedField(cls, name, struct_fmt, property_name, str_fmt='{}'): |
| + """Adds a constant field to this descriptor. |
| + |
| + Adds a field to the binary structure representing this descriptor whos value |
| + is equal to an object property. The field will be accessible as foo.name on |
| + any instance. |
| + |
| + The value of this field may not be given as a constructor parameter or |
| + set on an existing instance. |
| + |
| + Args: |
| + name: String name of the field. |
| + struct_fmt: Python 'struct' module format string for this field. |
| + property_name: Property to read. |
| + str_fmt: Python 'string' module format string for this field. |
| + """ |
| + if cls._fields is None: |
| + cls._fields = [] |
| + cls._fields.append(Field(name, str_fmt, struct_fmt, False)) |
| + |
| + def Setter(unused_self, unused_value): |
| + raise RuntimeError('{} is a computed field.'.format(name)) |
| + def Getter(self): |
| + return getattr(self, property_name) |
| + |
| + setattr(cls, name, property(Getter, Setter)) |
| + |
| + def __init__(self, **kwargs): |
| + """Constructs a new instance of this descriptor. |
| + |
| + All fields which do not have a default value and are not fixed or computed |
| + from a property must be specified as keyword arguments. |
| + |
| + Args: |
| + **kwargs: Field values. |
| + |
| + Raises: |
| + TypeError: A required field was missing or an unexpected field was given. |
| + """ |
| + fields = {field.name for field in self._fields} |
| + required_fields = {field.name for field in self._fields if field.required} |
| + |
| + for arg, value in kwargs.iteritems(): |
| + if arg not in fields: |
| + raise TypeError('Unexpected field: {}'.format(arg)) |
| + |
| + setattr(self, arg, value) |
| + required_fields.discard(arg) |
| + |
| + if required_fields: |
| + raise TypeError('Missing fields: {}'.format(', '.join(required_fields))) |
| + |
| + @property |
| + def fmt(self): |
| + """Returns the Python 'struct' module format string for this descriptor.""" |
| + return '<{}'.format(''.join([field.struct_fmt for field in self._fields])) |
| + |
| + @property |
| + def struct_size(self): |
| + """Returns the size of the struct defined by fmt.""" |
| + return struct.calcsize(self.fmt) |
| + |
| + @property |
| + def total_size(self): |
| + """Returns the total size of this descriptor.""" |
| + return self.struct_size |
| + |
| + def Encode(self): |
| + """Returns the binary representation of this descriptor.""" |
| + values = [getattr(self, field.name) for field in self._fields] |
| + return struct.pack(self.fmt, *values) |
| + |
| + def __str__(self): |
| + max_length = max(len(field.name) for field in self._fields) |
| + |
| + return '{}:\n {}'.format( |
| + self.__class__.__name__, |
| + '\n '.join('{} {}'.format( |
| + '{}:'.format(field.name).ljust(max_length+1), |
| + field.Format(getattr(self, field.name)) |
| + ) for field in self._fields) |
| + ) |
| + |
| + |
| +class DeviceDescriptor(Descriptor): |
| + """Represents a USB device descriptor.""" |
| + |
| + pass |
| + |
| +DeviceDescriptor.AddComputedField('bLength', 'B', 'struct_size') |
|
Ken Rockot(use gerrit already)
2014/07/23 00:03:47
Might as well link to / reference any relevant sec
Reilly Grant (use Gerrit)
2014/07/23 00:43:54
Done.
|
| +DeviceDescriptor.AddFixedField('bDescriptorType', 'B', |
| + usb_const.DescriptorType.DEVICE) |
| +DeviceDescriptor.AddField('bcdUSB', 'H', default=0x0200, str_fmt='0x{:04X}') |
| +DeviceDescriptor.AddField('bDeviceClass', 'B', |
| + default=usb_const.DeviceClass.PER_INTERFACE) |
| +DeviceDescriptor.AddField('bDeviceSubClass', 'B', |
| + default=usb_const.DeviceSubClass.PER_INTERFACE) |
| +DeviceDescriptor.AddField('bDeviceProtocol', 'B', |
| + default=usb_const.DeviceProtocol.PER_INTERFACE) |
| +DeviceDescriptor.AddField('bMaxPacketSize0', 'B', default=64) |
| +DeviceDescriptor.AddField('idVendor', 'H', str_fmt='0x{:04X}') |
| +DeviceDescriptor.AddField('idProduct', 'H', str_fmt='0x{:04X}') |
| +DeviceDescriptor.AddField('bcdDevice', 'H', str_fmt='0x{:04X}') |
| +DeviceDescriptor.AddField('iManufacturer', 'B', default=0) |
| +DeviceDescriptor.AddField('iProduct', 'B', default=0) |
| +DeviceDescriptor.AddField('iSerialNumber', 'B', default=0) |
| +DeviceDescriptor.AddField('bNumConfigurations', 'B', default=1) |
| + |
| + |
| +class DescriptorContainer(Descriptor): |
|
Ken Rockot(use gerrit already)
2014/07/23 00:03:48
If this is established nomenclature in USB land, I
Reilly Grant (use Gerrit)
2014/07/23 00:43:54
"CompositeDescriptor" may confusing because "USB C
|
| + """Super-class for descriptors which contain more descriptors. |
| + |
| + This class adds the ability for a descriptor to have an array of additional |
| + descriptors which follow it. |
| + """ |
| + |
| + def __init__(self, **kwargs): |
| + super(DescriptorContainer, self).__init__(**kwargs) |
| + self._descriptors = [] |
| + |
| + @property |
| + def total_size(self): |
| + return self.struct_size + sum([descriptor.total_size |
| + for descriptor in self._descriptors]) |
| + |
| + def Add(self, descriptor): |
| + self._descriptors.append(descriptor) |
| + |
| + def Encode(self): |
| + bufs = [super(DescriptorContainer, self).Encode()] |
| + bufs.extend(descriptor.Encode() for descriptor in self._descriptors) |
| + return ''.join(bufs) |
| + |
| + def __str__(self): |
| + return '{}\n{}'.format(super(DescriptorContainer, self).__str__(), |
| + '\n'.join(str(descriptor) |
| + for descriptor in self._descriptors)) |
| + |
| + |
| +class ConfigurationDescriptor(DescriptorContainer): |
| + """Represents a USB device configuration descriptor. |
| + |
| + Configuration descriptors may have a number of interface descriptors. |
| + """ |
| + |
| + def __init__(self, **kwargs): |
| + super(ConfigurationDescriptor, self).__init__(**kwargs) |
| + self._interfaces = {} |
| + |
| + @property |
| + def num_interfaces(self): |
| + interface_numbers = {key[0] for key in self._interfaces.iterkeys()} |
| + return len(interface_numbers) |
| + |
| + def AddInterface(self, interface): |
| + key = (interface.bInterfaceNumber, interface.bAlternateSetting) |
| + if key in self._interfaces: |
| + raise RuntimeError('Interface {} (alternate {}) already defined.' |
| + .format(key[0], key[1])) |
| + self._interfaces[key] = interface |
| + self.Add(interface) |
| + |
| + def GetInterfaces(self): |
| + return self._interfaces.values() |
| + |
| +ConfigurationDescriptor.AddComputedField('bLength', 'B', 'struct_size') |
| +ConfigurationDescriptor.AddFixedField('bDescriptorType', 'B', |
| + usb_const.DescriptorType.CONFIGURATION) |
| +ConfigurationDescriptor.AddComputedField('wTotalLength', 'H', 'total_size') |
| +ConfigurationDescriptor.AddComputedField('bNumInterfaces', 'B', |
| + 'num_interfaces') |
| +ConfigurationDescriptor.AddField('bConfigurationValue', 'B', default=1) |
| +ConfigurationDescriptor.AddField('iConfiguration', 'B', default=0) |
| +ConfigurationDescriptor.AddField('bmAttributes', 'B', str_fmt='0x{:02X}') |
| +ConfigurationDescriptor.AddField('MaxPower', 'B') |
| + |
| + |
| +class InterfaceDescriptor(DescriptorContainer): |
| + """Represents a USB interface descriptor. |
| + |
| + Interface descriptors may have a number of endpoint descriptors. |
| + """ |
| + |
| + def __init__(self, **kwargs): |
| + super(InterfaceDescriptor, self).__init__(**kwargs) |
| + self._endpoints = {} |
| + |
| + @property |
| + def num_endpoints(self): |
| + return len(self._endpoints) |
| + |
| + def AddEndpoint(self, endpoint): |
| + if endpoint.bEndpointAddress in self._endpoints: |
| + raise RuntimeError('Endpoint 0x{:02X} already defined on this interface.' |
| + .format(endpoint.bEndpointAddress)) |
| + self._endpoints[endpoint.bEndpointAddress] = endpoint |
| + self.Add(endpoint) |
| + |
| + def GetEndpoints(self): |
| + return self._endpoints.values() |
| + |
| +InterfaceDescriptor.AddComputedField('bLength', 'B', 'struct_size') |
| +InterfaceDescriptor.AddFixedField('bDescriptorType', 'B', |
| + usb_const.DescriptorType.INTERFACE) |
| +InterfaceDescriptor.AddField('bInterfaceNumber', 'B') |
| +InterfaceDescriptor.AddField('bAlternateSetting', 'B', default=0) |
| +InterfaceDescriptor.AddComputedField('bNumEndpoints', 'B', 'num_endpoints') |
| +InterfaceDescriptor.AddField('bInterfaceClass', 'B', |
| + default=usb_const.InterfaceClass.VENDOR) |
| +InterfaceDescriptor.AddField('bInterfaceSubClass', 'B', |
| + default=usb_const.InterfaceSubClass.VENDOR) |
| +InterfaceDescriptor.AddField('bInterfaceProtocol', 'B', |
| + default=usb_const.InterfaceProtocol.VENDOR) |
| +InterfaceDescriptor.AddField('iInterface', 'B', default=0) |
| + |
| + |
| +class EndpointDescriptor(Descriptor): |
| + """Represents a USB endpoint descriptor.""" |
| + |
| + pass |
| + |
| +EndpointDescriptor.AddComputedField('bLength', 'B', 'struct_size') |
| +EndpointDescriptor.AddFixedField('bDescriptorType', 'B', |
| + usb_const.DescriptorType.ENDPOINT) |
| +EndpointDescriptor.AddField('bEndpointAddress', 'B', str_fmt='0x{:02X}') |
| +EndpointDescriptor.AddField('bmAttributes', 'B', str_fmt='0x{:02X}') |
| +EndpointDescriptor.AddField('wMaxPacketSize', 'H') |
| +EndpointDescriptor.AddField('bInterval', 'B') |
| + |
| + |
| +class HidDescriptor(Descriptor): |
| + """Represents a USB HID descriptor.""" |
| + |
| + def __init__(self, **kwargs): |
| + super(HidDescriptor, self).__init__(**kwargs) |
| + self._descriptors = [] |
| + |
| + def AddDescriptor(self, typ, length): |
| + self._descriptors.append((typ, length)) |
| + |
| + @property |
| + def struct_size(self): |
| + return super(HidDescriptor, self).struct_size + self.num_descriptors * 3 |
| + |
| + @property |
| + def num_descriptors(self): |
| + return len(self._descriptors) |
| + |
| + def Encode(self): |
| + bufs = [super(HidDescriptor, self).Encode()] |
| + bufs.extend(struct.pack('<BH', typ, length) |
| + for typ, length in self._descriptors) |
| + return ''.join(bufs) |
| + |
| + def __str__(self): |
| + return '{}\n{}'.format( |
| + super(HidDescriptor, self).__str__(), |
| + '\n'.join(' bDescriptorType: 0x{:02X}\n wDescriptorLength: {}' |
| + .format(typ, length) for typ, length in self._descriptors)) |
| + |
| +HidDescriptor.AddComputedField('bLength', 'B', 'struct_size') |
| +HidDescriptor.AddFixedField('bDescriptorType', 'B', 33) |
| +HidDescriptor.AddField('bcdHID', 'H', default=0x0111, str_fmt='0x{:04X}') |
| +HidDescriptor.AddField('bCountryCode', 'B', default=0) |
| +HidDescriptor.AddComputedField('bNumDescriptors', 'B', 'num_descriptors') |