| # Copyright 2014 The Chromium Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """USB descriptor generation utilities. |
| |
| Classes to represent and generate USB descriptors. |
| """ |
| |
| import struct |
| |
| import hid_constants |
| import usb_constants |
| |
| |
| class Field(object): |
| """USB descriptor field information.""" |
| |
| def __init__(self, name, str_fmt, struct_fmt, required): |
| """Define a new USB descriptor field. |
| |
| Args: |
| name: Name of the field. |
| str_fmt: Python 'string' module format string for this field. |
| struct_fmt: Python 'struct' module format string for this field. |
| required: Is this a required field? |
| """ |
| 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): |
| """Standard Device Descriptor. |
| |
| See Universal Serial Bus Specification Revision 2.0 Table 9-8. |
| """ |
| pass |
| |
| DeviceDescriptor.AddComputedField('bLength', 'B', 'struct_size') |
| DeviceDescriptor.AddFixedField('bDescriptorType', 'B', |
| usb_constants.DescriptorType.DEVICE) |
| DeviceDescriptor.AddField('bcdUSB', 'H', default=0x0200, str_fmt='0x{:04X}') |
| DeviceDescriptor.AddField('bDeviceClass', 'B', |
| default=usb_constants.DeviceClass.PER_INTERFACE) |
| DeviceDescriptor.AddField('bDeviceSubClass', 'B', |
| default=usb_constants.DeviceSubClass.PER_INTERFACE) |
| DeviceDescriptor.AddField('bDeviceProtocol', 'B', |
| default=usb_constants.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): |
| """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 StringDescriptor(Descriptor): |
| """Standard String Descriptor. |
| |
| See Universal Serial Bus Specification Revision 2.0 Table 9-16. |
| """ |
| |
| def __init__(self, **kwargs): |
| self.bString = kwargs.pop('bString', '') |
| super(StringDescriptor, self).__init__(**kwargs) |
| |
| @property |
| def total_size(self): |
| return self.struct_size + len(self.bString.encode('UTF-16LE')) |
| |
| def Encode(self): |
| return ( |
| super(StringDescriptor, self).Encode() + |
| self.bString.encode('UTF-16LE')) |
| |
| def __str__(self): |
| return '{}\n bString: "{}"'.format( |
| super(StringDescriptor, self).__str__(), self.bString) |
| |
| StringDescriptor.AddComputedField('bLength', 'B', 'total_size') |
| StringDescriptor.AddFixedField( |
| 'bDescriptorType', 'B', usb_constants.DescriptorType.STRING) |
| |
| |
| class ConfigurationDescriptor(DescriptorContainer): |
| """Standard Configuration Descriptor. |
| |
| See Universal Serial Bus Specification Revision 2.0 Table 9-10. |
| """ |
| |
| 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_constants.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): |
| """Standard Interface Descriptor. |
| |
| See Universal Serial Bus Specification Revision 2.0 Table 9-12. |
| """ |
| |
| 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_constants.DescriptorType.INTERFACE) |
| InterfaceDescriptor.AddField('bInterfaceNumber', 'B') |
| InterfaceDescriptor.AddField('bAlternateSetting', 'B', default=0) |
| InterfaceDescriptor.AddComputedField('bNumEndpoints', 'B', 'num_endpoints') |
| InterfaceDescriptor.AddField('bInterfaceClass', 'B', |
| default=usb_constants.InterfaceClass.VENDOR) |
| InterfaceDescriptor.AddField('bInterfaceSubClass', 'B', |
| default=usb_constants.InterfaceSubClass.VENDOR) |
| InterfaceDescriptor.AddField('bInterfaceProtocol', 'B', |
| default=usb_constants.InterfaceProtocol.VENDOR) |
| InterfaceDescriptor.AddField('iInterface', 'B', default=0) |
| |
| |
| class EndpointDescriptor(Descriptor): |
| """Standard Endpoint Descriptor. |
| |
| See Universal Serial Bus Specification Revision 2.0 Table 9-13. |
| """ |
| pass |
| |
| EndpointDescriptor.AddComputedField('bLength', 'B', 'struct_size') |
| EndpointDescriptor.AddFixedField('bDescriptorType', 'B', |
| usb_constants.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): |
| """HID Descriptor. |
| |
| See Device Class Definition for Human Interface Devices (HID) Version 1.11 |
| section 6.2.1. |
| """ |
| |
| 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', |
| hid_constants.DescriptorType.HID) |
| HidDescriptor.AddField('bcdHID', 'H', default=0x0111, str_fmt='0x{:04X}') |
| HidDescriptor.AddField('bCountryCode', 'B', default=0) |
| HidDescriptor.AddComputedField('bNumDescriptors', 'B', 'num_descriptors') |
| |
| |
| class BosDescriptor(DescriptorContainer): |
| """Binary Device Object Store descriptor. |
| |
| See Universal Serial Bus 3.1 Specification, Revision 1.0 Table 9-12. |
| """ |
| |
| def __init__(self, **kwargs): |
| super(BosDescriptor, self).__init__(**kwargs) |
| self._device_caps = [] |
| |
| @property |
| def num_device_caps(self): |
| return len(self._device_caps) |
| |
| def AddDeviceCapability(self, device_capability): |
| self._device_caps.append(device_capability) |
| self.Add(device_capability) |
| |
| def GetDeviceCapabilities(self): |
| return self._device_caps |
| |
| BosDescriptor.AddComputedField('bLength', 'B', 'struct_size') |
| BosDescriptor.AddFixedField('bDescriptorType', 'B', |
| usb_constants.DescriptorType.BOS) |
| BosDescriptor.AddComputedField('wTotalLength', 'H', 'total_size') |
| BosDescriptor.AddComputedField('bNumDeviceCaps', 'B', 'num_device_caps') |
| |
| |
| class ContainerIdDescriptor(Descriptor): |
| """Container ID descriptor. |
| |
| See Universal Serial Bus 3.1 Specification, Revision 1.0 Table 9-17. |
| """ |
| pass |
| |
| ContainerIdDescriptor.AddComputedField('bLength', 'B', 'struct_size') |
| ContainerIdDescriptor.AddFixedField( |
| 'bDescriptorType', 'B', usb_constants.DescriptorType.DEVICE_CAPABILITY) |
| ContainerIdDescriptor.AddFixedField( |
| 'bDevCapabilityType', 'B', usb_constants.CapabilityType.CONTAINER_ID) |
| ContainerIdDescriptor.AddFixedField('bReserved', 'B', 0) |
| ContainerIdDescriptor.AddField('ContainerID', '16s') |