• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
5import struct
6from collections import namedtuple
7
8from 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.
13MBIM_ONLY_COMMUNICATION_INTERFACE = {'bAlternateSetting': 0,
14                                     'bNumEndpoints': 1,
15                                     'bInterfaceClass': 0x02,
16                                     'bInterfaceSubClass': 0x0E,
17                                     'bInterfaceProtocol': 0x00}
18
19MBIM_ONLY_DATA_INTERFACE_NO_DATA = {'bAlternateSetting': 0,
20                                    'bNumEndpoints': 0,
21                                    'bInterfaceClass': 0x0A,
22                                    'bInterfaceSubClass': 0x00,
23                                    'bInterfaceProtocol': 0x02}
24
25MBIM_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.
34NCM_MBIM_COMMUNICATION_INTERFACE_NCM = {'bAlternateSetting': 0,
35                                        'bNumEndpoints': 1,
36                                        'bInterfaceClass': 0x02,
37                                        'bInterfaceSubClass': 0x0D}
38
39NCM_MBIM_COMMUNICATION_INTERFACE_MBIM = {'bAlternateSetting': 1,
40                                         'bNumEndpoints': 1,
41                                         'bInterfaceClass': 0x02,
42                                         'bInterfaceSubClass': 0x0E,
43                                         'bInterfaceProtocol': 0x00}
44
45NCM_MBIM_DATA_INTERFACE_NO_DATA = {'bAlternateSetting': 0,
46                                   'bNumEndpoints': 0,
47                                   'bInterfaceClass': 0x0A,
48                                   'bInterfaceSubClass': 0x00,
49                                   'bInterfaceProtocol': 0x01}
50
51NCM_MBIM_DATA_INTERFACE_NCM = {'bAlternateSetting': 1,
52                               'bNumEndpoints': 2,
53                               'bInterfaceClass': 0x0A,
54                               'bInterfaceSubClass': 0x00,
55                               'bInterfaceProtocol': 0x01}
56
57NCM_MBIM_DATA_INTERFACE_MBIM = {'bAlternateSetting': 2,
58                                'bNumEndpoints': 2,
59                                'bInterfaceClass': 0x0A,
60                                'bInterfaceSubClass': 0x00,
61                                'bInterfaceProtocol': 0x02}
62
63
64class 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
171class 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
181class 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
192class 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
211class 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
224class 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
238class 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
249class 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
262class FunctionalDescriptor(Descriptor):
263    """ Functional Descriptor. """
264    DESCRIPTOR_TYPE = 0x24
265    _FIELDS = (('B', 'bLength'),
266               ('B', 'bDescriptorType'),
267               ('B', 'bDescriptorSubtype'))
268
269
270class 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
279class 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
289class 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
303class 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
314class 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
324class 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
386def 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
402def 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
414def 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
433def 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
466def 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