Home | History | Annotate | Download | only in mbim_compliance
      1 # Copyright 2015 The Chromium OS Authors. All rights reserved.
      2 # Use of this source code is governed by a BSD-style license that can be
      3 # found in the LICENSE file.
      4 
      5 import struct
      6 from collections import namedtuple
      7 
      8 from autotest_lib.client.cros.cellular.mbim_compliance import mbim_errors
      9 
     10 # All the MBIM_ONLY_* maps are filters for MBIM only function. These maps
     11 # specify the values of the fields which should be matched in the target
     12 # interface.
     13 MBIM_ONLY_COMMUNICATION_INTERFACE = {'bAlternateSetting': 0,
     14                                      'bNumEndpoints': 1,
     15                                      'bInterfaceClass': 0x02,
     16                                      'bInterfaceSubClass': 0x0E,
     17                                      'bInterfaceProtocol': 0x00}
     18 
     19 MBIM_ONLY_DATA_INTERFACE_NO_DATA = {'bAlternateSetting': 0,
     20                                     'bNumEndpoints': 0,
     21                                     'bInterfaceClass': 0x0A,
     22                                     'bInterfaceSubClass': 0x00,
     23                                     'bInterfaceProtocol': 0x02}
     24 
     25 MBIM_ONLY_DATA_INTERFACE_MBIM = {'bAlternateSetting': 1,
     26                                  'bNumEndpoints': 2,
     27                                  'bInterfaceClass': 0x0A,
     28                                  'bInterfaceSubClass': 0x00,
     29                                  'bInterfaceProtocol': 0x02}
     30 
     31 # All the NCM_MBIM_* maps are filters for NCM/MBIM function. These maps
     32 # specify the values of the fields which should be matched in the target
     33 # interface.
     34 NCM_MBIM_COMMUNICATION_INTERFACE_NCM = {'bAlternateSetting': 0,
     35                                         'bNumEndpoints': 1,
     36                                         'bInterfaceClass': 0x02,
     37                                         'bInterfaceSubClass': 0x0D}
     38 
     39 NCM_MBIM_COMMUNICATION_INTERFACE_MBIM = {'bAlternateSetting': 1,
     40                                          'bNumEndpoints': 1,
     41                                          'bInterfaceClass': 0x02,
     42                                          'bInterfaceSubClass': 0x0E,
     43                                          'bInterfaceProtocol': 0x00}
     44 
     45 NCM_MBIM_DATA_INTERFACE_NO_DATA = {'bAlternateSetting': 0,
     46                                    'bNumEndpoints': 0,
     47                                    'bInterfaceClass': 0x0A,
     48                                    'bInterfaceSubClass': 0x00,
     49                                    'bInterfaceProtocol': 0x01}
     50 
     51 NCM_MBIM_DATA_INTERFACE_NCM = {'bAlternateSetting': 1,
     52                                'bNumEndpoints': 2,
     53                                'bInterfaceClass': 0x0A,
     54                                'bInterfaceSubClass': 0x00,
     55                                'bInterfaceProtocol': 0x01}
     56 
     57 NCM_MBIM_DATA_INTERFACE_MBIM = {'bAlternateSetting': 2,
     58                                 'bNumEndpoints': 2,
     59                                 'bInterfaceClass': 0x0A,
     60                                 'bInterfaceSubClass': 0x00,
     61                                 'bInterfaceProtocol': 0x02}
     62 
     63 
     64 class DescriptorMeta(type):
     65     """
     66     Metaclass for creating a USB descriptor class.
     67 
     68     A derived descriptor class takes raw descriptor data as an array of unsigned
     69     bytes via its constructor and parses the data into individual fields stored
     70     as instance attributes. A derived class of |Descriptor| should specify the
     71     following class attributes as part of the class definition:
     72 
     73         DESCRIPTOR_TYPE: An unsigned 8-bit number specifying the descriptor
     74         type. Except for |UnknownDescriptor|, all derived classes should specify
     75         this attribute. This attribute can be inherited from a parent class.
     76 
     77         DESCRIPTOR_SUBTYPE: An unsigned 8-bit number specifying the descriptor
     78         subtype. Only descriptors have a bDescriptorSubtype field should specify
     79         this attribute.
     80 
     81         _FIELDS: A list of field definitions specified as a nested tuple. The
     82         field definitions are ordered in the same way as the fields are present
     83         in the USB descriptor. Each inner tuple is a field definition and
     84         contains two elements. The first element specifies the format
     85         character(s), which instructs |struct.unpack_from| how to extract the
     86         field from the raw descriptor data. The second element specifies the
     87         field name, which is also the attribute name used by an instance of the
     88         derived descriptor class for storing the field. Each derived descriptor
     89         class must define its own _FIELDS attribute, which must have
     90         ('B', 'bLength'), ('B', 'bDescriptorType') as the first two entries.
     91 
     92     """
     93     descriptor_classes = []
     94 
     95     def __new__(mcs, name, bases, attrs):
     96         # The Descriptor base class, which inherits from 'object', is merely
     97         # used to establish the class hierarchy and is never constructed from
     98         # raw descriptor data.
     99         if object in bases:
    100             return super(DescriptorMeta, mcs).__new__(mcs, name, bases, attrs)
    101 
    102         if '_FIELDS' not in attrs:
    103             raise mbim_errors.MBIMComplianceFrameworkError(
    104                     '%s must define a _FIELDS attribute' % name)
    105 
    106         field_formats, field_names = zip(*attrs['_FIELDS'])
    107         # USB descriptor data are in the little-endian format.
    108         data_format = '<' + ''.join(field_formats)
    109         unpack_length = struct.calcsize(data_format)
    110 
    111         def descriptor_class_new(cls, data):
    112             """
    113             Creates a descriptor instance with the given descriptor data.
    114 
    115             @param cls: The descriptor class of the instance to be created.
    116             @param data: The raw descriptor data as an array of unsigned bytes.
    117             @returns The descriptor instance.
    118 
    119             """
    120             data_length = len(data)
    121 
    122             if unpack_length > data_length:
    123                 raise mbim_errors.MBIMComplianceFrameworkError(
    124                         'Expected %d or more bytes of descriptor data, got %d' %
    125                         (unpack_length, data_length))
    126 
    127             obj = super(cls, cls).__new__(cls, *struct.unpack_from(data_format,
    128                                                                    data))
    129             setattr(obj, 'data', data)
    130 
    131             descriptor_type = attrs.get('DESCRIPTOR_TYPE')
    132             if (descriptor_type is not None and
    133                 descriptor_type != obj.bDescriptorType):
    134                 raise mbim_errors.MBIMComplianceFrameworkError(
    135                         'Expected descriptor type 0x%02X, got 0x%02X' %
    136                         (descriptor_type, obj.bDescriptorType))
    137 
    138             descriptor_subtype = attrs.get('DESCRIPTOR_SUBTYPE')
    139             if (descriptor_subtype is not None and
    140                 descriptor_subtype != obj.bDescriptorSubtype):
    141                 raise mbim_errors.MBIMComplianceFrameworkError(
    142                         'Expected descriptor subtype 0x%02X, got 0x%02X' %
    143                         (descriptor_subtype, obj.bDescriptorSubtype))
    144 
    145             if data_length != obj.bLength:
    146                 raise mbim_errors.MBIMComplianceFrameworkError(
    147                         'Expected descriptor length %d, got %d' %
    148                         (data_length, obj.bLength))
    149 
    150             # TODO(benchan): We don't currently handle the case where
    151             # |data_length| > |unpack_length|, which happens if the descriptor
    152             # contains a variable length field (e.g. StringDescriptor).
    153 
    154             return obj
    155 
    156         attrs['__new__'] = descriptor_class_new
    157         descriptor_class = namedtuple(name, field_names)
    158         # Prepend the class created via namedtuple to |bases| in order to
    159         # correctly resolve the __new__ method while preserving the class
    160         # hierarchy.
    161         cls = super(DescriptorMeta, mcs).__new__(mcs, name,
    162                                                  (descriptor_class,) + bases,
    163                                                  attrs)
    164         # As Descriptor.__subclasses__() only reports its direct subclasses,
    165         # we keep track of all subclasses of Descriptor using the
    166         # |DescriptorMeta.descriptor_classes| attribute.
    167         mcs.descriptor_classes.append(cls)
    168         return cls
    169 
    170 
    171 class Descriptor(object):
    172     """
    173     USB Descriptor base class.
    174 
    175     This class should not be instantiated or used directly.
    176 
    177     """
    178     __metaclass__ = DescriptorMeta
    179 
    180 
    181 class UnknownDescriptor(Descriptor):
    182     """
    183     Unknown USB Descriptor.
    184 
    185     This class is a catch-all descriptor for unsupported or unknown descriptor
    186     types.
    187     """
    188     _FIELDS = (('B', 'bLength'),
    189                ('B', 'bDescriptorType'))
    190 
    191 
    192 class DeviceDescriptor(Descriptor):
    193     """ Device Descriptor. """
    194     DESCRIPTOR_TYPE = 0x01
    195     _FIELDS = (('B', 'bLength'),
    196                ('B', 'bDescriptorType'),
    197                ('H', 'bcdUSB'),
    198                ('B', 'bDeviceClass'),
    199                ('B', 'bDeviceSubClass'),
    200                ('B', 'bDeviceProtocol'),
    201                ('B', 'bMaxPacketSize0'),
    202                ('H', 'idVendor'),
    203                ('H', 'idProduct'),
    204                ('H', 'bcdDevice'),
    205                ('B', 'iManufacturer'),
    206                ('B', 'iProduct'),
    207                ('B', 'iSerialNumber'),
    208                ('B', 'bNumConfigurations'))
    209 
    210 
    211 class ConfigurationDescriptor(Descriptor):
    212     """ Configuration Descriptor. """
    213     DESCRIPTOR_TYPE = 0x02
    214     _FIELDS = (('B', 'bLength'),
    215                ('B', 'bDescriptorType'),
    216                ('H', 'wTotalLength'),
    217                ('B', 'bNumInterfaces'),
    218                ('B', 'bConfigurationValue'),
    219                ('B', 'iConfiguration'),
    220                ('B', 'bmAttributes'),
    221                ('B', 'bMaxPower'))
    222 
    223 
    224 class InterfaceDescriptor(Descriptor):
    225     """ Interface Descriptor. """
    226     DESCRIPTOR_TYPE = 0x04
    227     _FIELDS = (('B', 'bLength'),
    228                ('B', 'bDescriptorType'),
    229                ('B', 'bInterfaceNumber'),
    230                ('B', 'bAlternateSetting'),
    231                ('B', 'bNumEndpoints'),
    232                ('B', 'bInterfaceClass'),
    233                ('B', 'bInterfaceSubClass'),
    234                ('B', 'bInterfaceProtocol'),
    235                ('B', 'iInterface'))
    236 
    237 
    238 class EndpointDescriptor(Descriptor):
    239     """ Endpoint Descriptor. """
    240     DESCRIPTOR_TYPE = 0x05
    241     _FIELDS = (('B', 'bLength'),
    242                ('B', 'bDescriptorType'),
    243                ('B', 'bEndpointAddress'),
    244                ('B', 'bmAttributes'),
    245                ('H', 'wMaxPacketSize'),
    246                ('B', 'bInterval'))
    247 
    248 
    249 class InterfaceAssociationDescriptor(Descriptor):
    250     """ Interface Asscociation Descriptor. """
    251     DESCRIPTOR_TYPE = 0x0B
    252     _FIELDS = (('B', 'bLength'),
    253                ('B', 'bDescriptorType'),
    254                ('B', 'bFirstInterface'),
    255                ('B', 'bInterfaceCount'),
    256                ('B', 'bFunctionClass'),
    257                ('B', 'bFunctionSubClass'),
    258                ('B', 'bFunctionProtocol'),
    259                ('B', 'iFunction'))
    260 
    261 
    262 class FunctionalDescriptor(Descriptor):
    263     """ Functional Descriptor. """
    264     DESCRIPTOR_TYPE = 0x24
    265     _FIELDS = (('B', 'bLength'),
    266                ('B', 'bDescriptorType'),
    267                ('B', 'bDescriptorSubtype'))
    268 
    269 
    270 class HeaderFunctionalDescriptor(FunctionalDescriptor):
    271     """ Header Functional Descriptor. """
    272     DESCRIPTOR_SUBTYPE = 0x00
    273     _FIELDS = (('B', 'bLength'),
    274                ('B', 'bDescriptorType'),
    275                ('B', 'bDescriptorSubtype'),
    276                ('H', 'bcdCDC'))
    277 
    278 
    279 class UnionFunctionalDescriptor(FunctionalDescriptor):
    280     """ Union Functional Descriptor. """
    281     DESCRIPTOR_SUBTYPE = 0x06
    282     _FIELDS = (('B', 'bLength'),
    283                ('B', 'bDescriptorType'),
    284                ('B', 'bDescriptorSubtype'),
    285                ('B', 'bControlInterface'),
    286                ('B', 'bSubordinateInterface0'))
    287 
    288 
    289 class MBIMFunctionalDescriptor(FunctionalDescriptor):
    290     """ MBIM Functional Descriptor. """
    291     DESCRIPTOR_SUBTYPE = 0x1B
    292     _FIELDS = (('B', 'bLength'),
    293                ('B', 'bDescriptorType'),
    294                ('B', 'bDescriptorSubtype'),
    295                ('H', 'bcdMBIMVersion'),
    296                ('H', 'wMaxControlMessage'),
    297                ('B', 'bNumberFilters'),
    298                ('B', 'bMaxFilterSize'),
    299                ('H', 'wMaxSegmentSize'),
    300                ('B', 'bmNetworkCapabilities'))
    301 
    302 
    303 class MBIMExtendedFunctionalDescriptor(FunctionalDescriptor):
    304     """ MBIM Extended Functional Descriptor. """
    305     DESCRIPTOR_SUBTYPE = 0x1C
    306     _FIELDS = (('B', 'bLength'),
    307                ('B', 'bDescriptorType'),
    308                ('B', 'bDescriptorSubtype'),
    309                ('H', 'bcdMBIMExtendedVersion'),
    310                ('B', 'bMaxOutstandingCommandMessages'),
    311                ('H', 'wMTU'))
    312 
    313 
    314 class SuperSpeedEndpointCompanionDescriptor(Descriptor):
    315     """ SuperSpeed Endpoint Companion Descriptor. """
    316     DESCRIPTOR_TYPE = 0x30
    317     _FIELDS = (('B', 'bLength'),
    318                ('B', 'bDescriptorType'),
    319                ('B', 'bMaxBurst'),
    320                ('B', 'bmAttributes'),
    321                ('H', 'wBytesPerInterval'))
    322 
    323 
    324 class DescriptorParser(object):
    325     """
    326     A class for extracting USB descriptors from raw descriptor data.
    327 
    328     This class takes raw descriptor data as an array of unsigned bytes via its
    329     constructor and provides an iterator interface to return individual USB
    330     descriptors via instances derived from a subclass of Descriptor.
    331 
    332     """
    333     _DESCRIPTOR_CLASS_MAP = {
    334             (cls.DESCRIPTOR_TYPE, getattr(cls, 'DESCRIPTOR_SUBTYPE', None)): cls
    335             for cls in DescriptorMeta.descriptor_classes
    336             if hasattr(cls, 'DESCRIPTOR_TYPE')
    337     }
    338 
    339     def __init__(self, data):
    340         self._data = data
    341         self._data_length = len(data)
    342         self._index = 0
    343         # The position of each descriptor in the list.
    344         self._descriptor_index = 0
    345 
    346     def __iter__(self):
    347         return self
    348 
    349     def next(self):
    350         """
    351         Returns the next descriptor found in the descriptor data.
    352 
    353         @returns An instance of a subclass of Descriptor.
    354         @raises StopIteration if no more descriptor is found,
    355 
    356         """
    357         if self._index >= self._data_length:
    358             raise StopIteration
    359 
    360         # Identify the descriptor class based on bDescriptorType, and if
    361         # available, bDescriptorSubtype. The descriptor data has a standard
    362         # layout as follows:
    363         #   self._data[self._index]: bLength
    364         #   self._data[self._index + 1]: bDescriptorType
    365         #   self._data[self._index + 2]: bDescriptorSubtype for some descriptors
    366         descriptor_type, descriptor_subtype = None, None
    367         if self._index + 1 < self._data_length:
    368             descriptor_type = self._data[self._index + 1]
    369             if self._index + 2 < self._data_length:
    370                 descriptor_subtype = self._data[self._index + 2]
    371 
    372         descriptor_class = self._DESCRIPTOR_CLASS_MAP.get(
    373                 (descriptor_type, descriptor_subtype), None)
    374         if descriptor_class is None:
    375             descriptor_class = self._DESCRIPTOR_CLASS_MAP.get(
    376                     (descriptor_type, None), UnknownDescriptor)
    377 
    378         next_index = self._index + self._data[self._index]
    379         descriptor = descriptor_class(self._data[self._index:next_index])
    380         self._index = next_index
    381         descriptor.index = self._descriptor_index
    382         self._descriptor_index += 1
    383         return descriptor
    384 
    385 
    386 def filter_descriptors(descriptor_type, descriptors):
    387     """
    388     Filter a list of descriptors based on the target |descriptor_type|.
    389 
    390     @param descriptor_type: The target descriptor type.
    391     @param descriptors: The list of functional descriptors.
    392                         Type: Array of |Descriptor| objects.
    393     @returns A list of target descriptors.
    394 
    395     """
    396     if not descriptors:
    397         return []
    398     return filter(lambda descriptor: isinstance(descriptor, descriptor_type),
    399                   descriptors)
    400 
    401 
    402 def has_distinct_descriptors(descriptors):
    403     """
    404     Check if there are distinct descriptors in the given list.
    405 
    406     @param descriptors: The list of descriptors.
    407                         Type: Array of |Descriptor| objects.
    408     @returns True if distinct descriptor are found, False otherwise.
    409 
    410     """
    411     return not all(descriptor == descriptors[0] for descriptor in descriptors)
    412 
    413 
    414 def get_descriptor_bundle(descriptors, descriptor):
    415     """
    416     Get the bundle for the |descriptor|. For example, if |descriptor| is of
    417     inferface type, this bundle should include functional descriptors and
    418     endpoint descriptors.
    419 
    420     @param descriptors: A list of all descriptors.
    421                         Type: Array of |Descriptor| objects.
    422     @param descriptor: The starting point of the bundle.
    423     @returns The bundle for |descriptor|.
    424 
    425     """
    426     index = descriptor.index + 1
    427     while (index < len(descriptors) and
    428            type(descriptor) != type(descriptors[index])):
    429         index += 1
    430     return descriptors[descriptor.index: index]
    431 
    432 
    433 def filter_interface_descriptors(descriptors, interface_type):
    434     """
    435     Filter interface descriptors based on the values in fields.
    436 
    437     @param descriptors: A list of interface descriptors.
    438                         Type: Array of |Descriptor| objects.
    439     @param interface_type: A dictionary composed of pairs(field: value) to
    440                            match the target interface.
    441     @returns A list of target interfaces.
    442 
    443     """
    444     def _match_all_fields(interface):
    445         """
    446         Match fields for a given interface descriptor.
    447 
    448         The descriptor is matched based on the fields provided in
    449         |interface_type|.
    450 
    451         @param interface: An interface descriptor.
    452                           Type: |Descriptor| object.
    453         @returns True if all fields match, False otherwise.
    454 
    455         """
    456         for key, value in interface_type.iteritems():
    457             if (not hasattr(interface, key) or
    458                 getattr(interface, key) != value):
    459                 return False
    460         return True
    461 
    462     return filter(lambda descriptor: _match_all_fields(descriptor),
    463                   descriptors)
    464 
    465 
    466 def has_bulk_in_and_bulk_out(endpoints):
    467     """
    468     Check if there are one bulk-in endpoint and one bulk-out endpoint.
    469 
    470     @param endpoints: A list of endpoint descriptors.
    471                       Type: Array of |Descriptor| objects.
    472     @returns True if there are one bulk-in and one bulk-out endpoint, False
    473              otherwise.
    474     """
    475     bulk_in, bulk_out = False, False
    476     for endpoint in endpoints:
    477         if (endpoint.bLength == 7 and
    478             endpoint.bEndpointAddress < 0x80 and
    479             endpoint.bmAttributes == 0x02):
    480             bulk_out = True
    481         elif (endpoint.bLength == 7 and
    482               endpoint.bEndpointAddress >= 0x80 and
    483               endpoint.bmAttributes == 0x02):
    484             bulk_in = True
    485     return bulk_in and bulk_out
    486