| # Copyright 2015 The Chromium OS Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| import struct |
| from collections import namedtuple |
| |
| from autotest_lib.client.cros.cellular.mbim_compliance import mbim_errors |
| |
| # All the MBIM_ONLY_* maps are filters for MBIM only function. These maps |
| # specify the values of the fields which should be matched in the target |
| # interface. |
| MBIM_ONLY_COMMUNICATION_INTERFACE = {'bAlternateSetting': 0, |
| 'bNumEndpoints': 1, |
| 'bInterfaceClass': 0x02, |
| 'bInterfaceSubClass': 0x0E, |
| 'bInterfaceProtocol': 0x00} |
| |
| MBIM_ONLY_DATA_INTERFACE_NO_DATA = {'bAlternateSetting': 0, |
| 'bNumEndpoints': 0, |
| 'bInterfaceClass': 0x0A, |
| 'bInterfaceSubClass': 0x00, |
| 'bInterfaceProtocol': 0x02} |
| |
| MBIM_ONLY_DATA_INTERFACE_MBIM = {'bAlternateSetting': 1, |
| 'bNumEndpoints': 2, |
| 'bInterfaceClass': 0x0A, |
| 'bInterfaceSubClass': 0x00, |
| 'bInterfaceProtocol': 0x02} |
| |
| # All the NCM_MBIM_* maps are filters for NCM/MBIM function. These maps |
| # specify the values of the fields which should be matched in the target |
| # interface. |
| NCM_MBIM_COMMUNICATION_INTERFACE_NCM = {'bAlternateSetting': 0, |
| 'bNumEndpoints': 1, |
| 'bInterfaceClass': 0x02, |
| 'bInterfaceSubClass': 0x0D} |
| |
| NCM_MBIM_COMMUNICATION_INTERFACE_MBIM = {'bAlternateSetting': 1, |
| 'bNumEndpoints': 1, |
| 'bInterfaceClass': 0x02, |
| 'bInterfaceSubClass': 0x0E, |
| 'bInterfaceProtocol': 0x00} |
| |
| NCM_MBIM_DATA_INTERFACE_NO_DATA = {'bAlternateSetting': 0, |
| 'bNumEndpoints': 0, |
| 'bInterfaceClass': 0x0A, |
| 'bInterfaceSubClass': 0x00, |
| 'bInterfaceProtocol': 0x01} |
| |
| NCM_MBIM_DATA_INTERFACE_NCM = {'bAlternateSetting': 1, |
| 'bNumEndpoints': 2, |
| 'bInterfaceClass': 0x0A, |
| 'bInterfaceSubClass': 0x00, |
| 'bInterfaceProtocol': 0x01} |
| |
| NCM_MBIM_DATA_INTERFACE_MBIM = {'bAlternateSetting': 2, |
| 'bNumEndpoints': 2, |
| 'bInterfaceClass': 0x0A, |
| 'bInterfaceSubClass': 0x00, |
| 'bInterfaceProtocol': 0x02} |
| |
| |
| class DescriptorMeta(type): |
| """ |
| Metaclass for creating a USB descriptor class. |
| |
| A derived descriptor class takes raw descriptor data as an array of unsigned |
| bytes via its constructor and parses the data into individual fields stored |
| as instance attributes. A derived class of |Descriptor| should specify the |
| following class attributes as part of the class definition: |
| |
| DESCRIPTOR_TYPE: An unsigned 8-bit number specifying the descriptor |
| type. Except for |UnknownDescriptor|, all derived classes should specify |
| this attribute. This attribute can be inherited from a parent class. |
| |
| DESCRIPTOR_SUBTYPE: An unsigned 8-bit number specifying the descriptor |
| subtype. Only descriptors have a bDescriptorSubtype field should specify |
| this attribute. |
| |
| _FIELDS: A list of field definitions specified as a nested tuple. The |
| field definitions are ordered in the same way as the fields are present |
| in the USB descriptor. Each inner tuple is a field definition and |
| contains two elements. The first element specifies the format |
| character(s), which instructs |struct.unpack_from| how to extract the |
| field from the raw descriptor data. The second element specifies the |
| field name, which is also the attribute name used by an instance of the |
| derived descriptor class for storing the field. Each derived descriptor |
| class must define its own _FIELDS attribute, which must have |
| ('B', 'bLength'), ('B', 'bDescriptorType') as the first two entries. |
| |
| """ |
| descriptor_classes = [] |
| |
| def __new__(mcs, name, bases, attrs): |
| # The Descriptor base class, which inherits from 'object', is merely |
| # used to establish the class hierarchy and is never constructed from |
| # raw descriptor data. |
| if object in bases: |
| return super(DescriptorMeta, mcs).__new__(mcs, name, bases, attrs) |
| |
| if '_FIELDS' not in attrs: |
| raise mbim_errors.MBIMComplianceFrameworkError( |
| '%s must define a _FIELDS attribute' % name) |
| |
| field_formats, field_names = zip(*attrs['_FIELDS']) |
| # USB descriptor data are in the little-endian format. |
| data_format = '<' + ''.join(field_formats) |
| unpack_length = struct.calcsize(data_format) |
| |
| def descriptor_class_new(cls, data): |
| """ |
| Creates a descriptor instance with the given descriptor data. |
| |
| @param cls: The descriptor class of the instance to be created. |
| @param data: The raw descriptor data as an array of unsigned bytes. |
| @returns The descriptor instance. |
| |
| """ |
| data_length = len(data) |
| |
| if unpack_length > data_length: |
| raise mbim_errors.MBIMComplianceFrameworkError( |
| 'Expected %d or more bytes of descriptor data, got %d' % |
| (unpack_length, data_length)) |
| |
| obj = super(cls, cls).__new__(cls, *struct.unpack_from(data_format, |
| data)) |
| setattr(obj, 'data', data) |
| |
| descriptor_type = attrs.get('DESCRIPTOR_TYPE') |
| if (descriptor_type is not None and |
| descriptor_type != obj.bDescriptorType): |
| raise mbim_errors.MBIMComplianceFrameworkError( |
| 'Expected descriptor type 0x%02X, got 0x%02X' % |
| (descriptor_type, obj.bDescriptorType)) |
| |
| descriptor_subtype = attrs.get('DESCRIPTOR_SUBTYPE') |
| if (descriptor_subtype is not None and |
| descriptor_subtype != obj.bDescriptorSubtype): |
| raise mbim_errors.MBIMComplianceFrameworkError( |
| 'Expected descriptor subtype 0x%02X, got 0x%02X' % |
| (descriptor_subtype, obj.bDescriptorSubtype)) |
| |
| if data_length != obj.bLength: |
| raise mbim_errors.MBIMComplianceFrameworkError( |
| 'Expected descriptor length %d, got %d' % |
| (data_length, obj.bLength)) |
| |
| # TODO(benchan): We don't currently handle the case where |
| # |data_length| > |unpack_length|, which happens if the descriptor |
| # contains a variable length field (e.g. StringDescriptor). |
| |
| return obj |
| |
| attrs['__new__'] = descriptor_class_new |
| descriptor_class = namedtuple(name, field_names) |
| # Prepend the class created via namedtuple to |bases| in order to |
| # correctly resolve the __new__ method while preserving the class |
| # hierarchy. |
| cls = super(DescriptorMeta, mcs).__new__(mcs, name, |
| (descriptor_class,) + bases, |
| attrs) |
| # As Descriptor.__subclasses__() only reports its direct subclasses, |
| # we keep track of all subclasses of Descriptor using the |
| # |DescriptorMeta.descriptor_classes| attribute. |
| mcs.descriptor_classes.append(cls) |
| return cls |
| |
| |
| class Descriptor(object): |
| """ |
| USB Descriptor base class. |
| |
| This class should not be instantiated or used directly. |
| |
| """ |
| __metaclass__ = DescriptorMeta |
| |
| |
| class UnknownDescriptor(Descriptor): |
| """ |
| Unknown USB Descriptor. |
| |
| This class is a catch-all descriptor for unsupported or unknown descriptor |
| types. |
| """ |
| _FIELDS = (('B', 'bLength'), |
| ('B', 'bDescriptorType')) |
| |
| |
| class DeviceDescriptor(Descriptor): |
| """ Device Descriptor. """ |
| DESCRIPTOR_TYPE = 0x01 |
| _FIELDS = (('B', 'bLength'), |
| ('B', 'bDescriptorType'), |
| ('H', 'bcdUSB'), |
| ('B', 'bDeviceClass'), |
| ('B', 'bDeviceSubClass'), |
| ('B', 'bDeviceProtocol'), |
| ('B', 'bMaxPacketSize0'), |
| ('H', 'idVendor'), |
| ('H', 'idProduct'), |
| ('H', 'bcdDevice'), |
| ('B', 'iManufacturer'), |
| ('B', 'iProduct'), |
| ('B', 'iSerialNumber'), |
| ('B', 'bNumConfigurations')) |
| |
| |
| class ConfigurationDescriptor(Descriptor): |
| """ Configuration Descriptor. """ |
| DESCRIPTOR_TYPE = 0x02 |
| _FIELDS = (('B', 'bLength'), |
| ('B', 'bDescriptorType'), |
| ('H', 'wTotalLength'), |
| ('B', 'bNumInterfaces'), |
| ('B', 'bConfigurationValue'), |
| ('B', 'iConfiguration'), |
| ('B', 'bmAttributes'), |
| ('B', 'bMaxPower')) |
| |
| |
| class InterfaceDescriptor(Descriptor): |
| """ Interface Descriptor. """ |
| DESCRIPTOR_TYPE = 0x04 |
| _FIELDS = (('B', 'bLength'), |
| ('B', 'bDescriptorType'), |
| ('B', 'bInterfaceNumber'), |
| ('B', 'bAlternateSetting'), |
| ('B', 'bNumEndpoints'), |
| ('B', 'bInterfaceClass'), |
| ('B', 'bInterfaceSubClass'), |
| ('B', 'bInterfaceProtocol'), |
| ('B', 'iInterface')) |
| |
| |
| class EndpointDescriptor(Descriptor): |
| """ Endpoint Descriptor. """ |
| DESCRIPTOR_TYPE = 0x05 |
| _FIELDS = (('B', 'bLength'), |
| ('B', 'bDescriptorType'), |
| ('B', 'bEndpointAddress'), |
| ('B', 'bmAttributes'), |
| ('H', 'wMaxPacketSize'), |
| ('B', 'bInterval')) |
| |
| |
| class InterfaceAssociationDescriptor(Descriptor): |
| """ Interface Asscociation Descriptor. """ |
| DESCRIPTOR_TYPE = 0x0B |
| _FIELDS = (('B', 'bLength'), |
| ('B', 'bDescriptorType'), |
| ('B', 'bFirstInterface'), |
| ('B', 'bInterfaceCount'), |
| ('B', 'bFunctionClass'), |
| ('B', 'bFunctionSubClass'), |
| ('B', 'bFunctionProtocol'), |
| ('B', 'iFunction')) |
| |
| |
| class FunctionalDescriptor(Descriptor): |
| """ Functional Descriptor. """ |
| DESCRIPTOR_TYPE = 0x24 |
| _FIELDS = (('B', 'bLength'), |
| ('B', 'bDescriptorType'), |
| ('B', 'bDescriptorSubtype')) |
| |
| |
| class HeaderFunctionalDescriptor(FunctionalDescriptor): |
| """ Header Functional Descriptor. """ |
| DESCRIPTOR_SUBTYPE = 0x00 |
| _FIELDS = (('B', 'bLength'), |
| ('B', 'bDescriptorType'), |
| ('B', 'bDescriptorSubtype'), |
| ('H', 'bcdCDC')) |
| |
| |
| class UnionFunctionalDescriptor(FunctionalDescriptor): |
| """ Union Functional Descriptor. """ |
| DESCRIPTOR_SUBTYPE = 0x06 |
| _FIELDS = (('B', 'bLength'), |
| ('B', 'bDescriptorType'), |
| ('B', 'bDescriptorSubtype'), |
| ('B', 'bControlInterface'), |
| ('B', 'bSubordinateInterface0')) |
| |
| |
| class MBIMFunctionalDescriptor(FunctionalDescriptor): |
| """ MBIM Functional Descriptor. """ |
| DESCRIPTOR_SUBTYPE = 0x1B |
| _FIELDS = (('B', 'bLength'), |
| ('B', 'bDescriptorType'), |
| ('B', 'bDescriptorSubtype'), |
| ('H', 'bcdMBIMVersion'), |
| ('H', 'wMaxControlMessage'), |
| ('B', 'bNumberFilters'), |
| ('B', 'bMaxFilterSize'), |
| ('H', 'wMaxSegmentSize'), |
| ('B', 'bmNetworkCapabilities')) |
| |
| |
| class MBIMExtendedFunctionalDescriptor(FunctionalDescriptor): |
| """ MBIM Extended Functional Descriptor. """ |
| DESCRIPTOR_SUBTYPE = 0x1C |
| _FIELDS = (('B', 'bLength'), |
| ('B', 'bDescriptorType'), |
| ('B', 'bDescriptorSubtype'), |
| ('H', 'bcdMBIMExtendedVersion'), |
| ('B', 'bMaxOutstandingCommandMessages'), |
| ('H', 'wMTU')) |
| |
| |
| class SuperSpeedEndpointCompanionDescriptor(Descriptor): |
| """ SuperSpeed Endpoint Companion Descriptor. """ |
| DESCRIPTOR_TYPE = 0x30 |
| _FIELDS = (('B', 'bLength'), |
| ('B', 'bDescriptorType'), |
| ('B', 'bMaxBurst'), |
| ('B', 'bmAttributes'), |
| ('H', 'wBytesPerInterval')) |
| |
| |
| class DescriptorParser(object): |
| """ |
| A class for extracting USB descriptors from raw descriptor data. |
| |
| This class takes raw descriptor data as an array of unsigned bytes via its |
| constructor and provides an iterator interface to return individual USB |
| descriptors via instances derived from a subclass of Descriptor. |
| |
| """ |
| _DESCRIPTOR_CLASS_MAP = { |
| (cls.DESCRIPTOR_TYPE, getattr(cls, 'DESCRIPTOR_SUBTYPE', None)): cls |
| for cls in DescriptorMeta.descriptor_classes |
| if hasattr(cls, 'DESCRIPTOR_TYPE') |
| } |
| |
| def __init__(self, data): |
| self._data = data |
| self._data_length = len(data) |
| self._index = 0 |
| # The position of each descriptor in the list. |
| self._descriptor_index = 0 |
| |
| def __iter__(self): |
| return self |
| |
| def next(self): |
| """ |
| Returns the next descriptor found in the descriptor data. |
| |
| @returns An instance of a subclass of Descriptor. |
| @raises StopIteration if no more descriptor is found, |
| |
| """ |
| if self._index >= self._data_length: |
| raise StopIteration |
| |
| # Identify the descriptor class based on bDescriptorType, and if |
| # available, bDescriptorSubtype. The descriptor data has a standard |
| # layout as follows: |
| # self._data[self._index]: bLength |
| # self._data[self._index + 1]: bDescriptorType |
| # self._data[self._index + 2]: bDescriptorSubtype for some descriptors |
| descriptor_type, descriptor_subtype = None, None |
| if self._index + 1 < self._data_length: |
| descriptor_type = self._data[self._index + 1] |
| if self._index + 2 < self._data_length: |
| descriptor_subtype = self._data[self._index + 2] |
| |
| descriptor_class = self._DESCRIPTOR_CLASS_MAP.get( |
| (descriptor_type, descriptor_subtype), None) |
| if descriptor_class is None: |
| descriptor_class = self._DESCRIPTOR_CLASS_MAP.get( |
| (descriptor_type, None), UnknownDescriptor) |
| |
| next_index = self._index + self._data[self._index] |
| descriptor = descriptor_class(self._data[self._index:next_index]) |
| self._index = next_index |
| descriptor.index = self._descriptor_index |
| self._descriptor_index += 1 |
| return descriptor |
| |
| |
| def filter_descriptors(descriptor_type, descriptors): |
| """ |
| Filter a list of descriptors based on the target |descriptor_type|. |
| |
| @param descriptor_type: The target descriptor type. |
| @param descriptors: The list of functional descriptors. |
| Type: Array of |Descriptor| objects. |
| @returns A list of target descriptors. |
| |
| """ |
| if not descriptors: |
| return [] |
| return filter(lambda descriptor: isinstance(descriptor, descriptor_type), |
| descriptors) |
| |
| |
| def has_distinct_descriptors(descriptors): |
| """ |
| Check if there are distinct descriptors in the given list. |
| |
| @param descriptors: The list of descriptors. |
| Type: Array of |Descriptor| objects. |
| @returns True if distinct descriptor are found, False otherwise. |
| |
| """ |
| return not all(descriptor == descriptors[0] for descriptor in descriptors) |
| |
| |
| def get_descriptor_bundle(descriptors, descriptor): |
| """ |
| Get the bundle for the |descriptor|. For example, if |descriptor| is of |
| inferface type, this bundle should include functional descriptors and |
| endpoint descriptors. |
| |
| @param descriptors: A list of all descriptors. |
| Type: Array of |Descriptor| objects. |
| @param descriptor: The starting point of the bundle. |
| @returns The bundle for |descriptor|. |
| |
| """ |
| index = descriptor.index + 1 |
| while (index < len(descriptors) and |
| type(descriptor) != type(descriptors[index])): |
| index += 1 |
| return descriptors[descriptor.index: index] |
| |
| |
| def filter_interface_descriptors(descriptors, interface_type): |
| """ |
| Filter interface descriptors based on the values in fields. |
| |
| @param descriptors: A list of interface descriptors. |
| Type: Array of |Descriptor| objects. |
| @param interface_type: A dictionary composed of pairs(field: value) to |
| match the target interface. |
| @returns A list of target interfaces. |
| |
| """ |
| def _match_all_fields(interface): |
| """ |
| Match fields for a given interface descriptor. |
| |
| The descriptor is matched based on the fields provided in |
| |interface_type|. |
| |
| @param interface: An interface descriptor. |
| Type: |Descriptor| object. |
| @returns True if all fields match, False otherwise. |
| |
| """ |
| for key, value in interface_type.iteritems(): |
| if (not hasattr(interface, key) or |
| getattr(interface, key) != value): |
| return False |
| return True |
| |
| return filter(lambda descriptor: _match_all_fields(descriptor), |
| descriptors) |
| |
| |
| def has_bulk_in_and_bulk_out(endpoints): |
| """ |
| Check if there are one bulk-in endpoint and one bulk-out endpoint. |
| |
| @param endpoints: A list of endpoint descriptors. |
| Type: Array of |Descriptor| objects. |
| @returns True if there are one bulk-in and one bulk-out endpoint, False |
| otherwise. |
| """ |
| bulk_in, bulk_out = False, False |
| for endpoint in endpoints: |
| if (endpoint.bLength == 7 and |
| endpoint.bEndpointAddress < 0x80 and |
| endpoint.bmAttributes == 0x02): |
| bulk_out = True |
| elif (endpoint.bLength == 7 and |
| endpoint.bEndpointAddress >= 0x80 and |
| endpoint.bmAttributes == 0x02): |
| bulk_in = True |
| return bulk_in and bulk_out |