usb_descriptors.py revision a5c8c671d35fd3c47da64f3452d721fa5c0685a8
1# Copyright (c) 2014 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
11class DescriptorMeta(type):
12    """
13    Metaclass for creating a USB descriptor class.
14
15    A derived descriptor class takes raw descriptor data as an array of unsigned
16    bytes via its constructor and parses the data into individual fields stored
17    as instance attributes. A derived class of |Descriptor| should specify the
18    following class attributes as part of the class definition:
19
20        DESCRIPTOR_TYPE: An unsigned 8-bit number specifying the descriptor
21        type. Except for |UnknownDescriptor|, all derived classes should specify
22        this attribute. This attribute can be inherited from a parent class.
23
24        DESCRIPTOR_SUBTYPE: An unsigned 8-bit number specifying the descriptor
25        subtype. Only descriptors have a bDescriptorSubtype field should specify
26        this attribute.
27
28        _FIELDS: A list of field definitions specified as a nested tuple. The
29        field definitions are ordered in the same way as the fields are present
30        in the USB descriptor. Each inner tuple is a field definition and
31        contains two elements. The first element specifies the format
32        character(s), which instructs |struct.unpack_from| how to extract the
33        field from the raw descriptor data. The second element specifies the
34        field name, which is also the attribute name used by an instance of the
35        derived descriptor class for storing the field. Each derived descriptor
36        class must define its own _FIELDS attribute, which must have
37        ('B', 'bLength'), ('B', 'bDescriptorType') as the first two entries.
38
39    """
40    descriptor_classes = []
41
42    def __new__(mcs, name, bases, attrs):
43        # The Descriptor base class, which inherits from 'object', is merely
44        # used to establish the class hierarchy and is never constructed from
45        # raw descriptor data.
46        if object in bases:
47            return super(DescriptorMeta, mcs).__new__(mcs, name, bases, attrs)
48
49        if '_FIELDS' not in attrs:
50            raise mbim_errors.MBIMComplianceFrameworkError(
51                    '%s must define a _FIELDS attribute' % name)
52
53        field_formats, field_names = zip(*attrs['_FIELDS'])
54        # USB descriptor data are in the little-endian format.
55        data_format = '<' + ''.join(field_formats)
56        unpack_length = struct.calcsize(data_format)
57
58        def descriptor_class_new(cls, data):
59            """
60            Creates a descriptor instance with the given descriptor data.
61
62            @param cls: The descriptor class of the instance to be created.
63            @param data: The raw descriptor data as an array of unsigned bytes.
64            @return The descriptor instance.
65
66            """
67            data_length = len(data)
68
69            if unpack_length > data_length:
70                raise mbim_errors.MBIMComplianceFrameworkError(
71                        'Expected %d or more bytes of descriptor data, got %d' %
72                        (unpack_length, data_length))
73
74            obj = super(cls, cls).__new__(cls, *struct.unpack_from(data_format,
75                                                                   data))
76            setattr(obj, 'data', data)
77
78            descriptor_type = attrs.get('DESCRIPTOR_TYPE')
79            if (descriptor_type is not None and
80                descriptor_type != obj.bDescriptorType):
81                raise mbim_errors.MBIMComplianceFrameworkError(
82                        'Expected descriptor type 0x%02X, got 0x%02X' %
83                        (descriptor_type, obj.bDescriptorType))
84
85            descriptor_subtype = attrs.get('DESCRIPTOR_SUBTYPE')
86            if (descriptor_subtype is not None and
87                descriptor_subtype != obj.bDescriptorSubtype):
88                raise mbim_errors.MBIMComplianceFrameworkError(
89                        'Expected descriptor subtype 0x%02X, got 0x%02X' %
90                        (descriptor_subtype, obj.bDescriptorSubtype))
91
92            if data_length != obj.bLength:
93                raise mbim_errors.MBIMComplianceFrameworkError(
94                        'Expected descriptor length %d, got %d' %
95                        (data_length, obj.bLength))
96
97            # TODO(benchan): We don't currently handle the case where
98            # |data_length| > |unpack_length|, which happens if the descriptor
99            # contains a variable length field (e.g. StringDescriptor).
100
101            return obj
102
103        attrs['__new__'] = descriptor_class_new
104        descriptor_class = namedtuple(name, field_names)
105        # Prepend the class created via namedtuple to |bases| in order to
106        # correctly resolve the __new__ method while preserving the class
107        # hierarchy.
108        cls = super(DescriptorMeta, mcs).__new__(mcs, name,
109                                                 (descriptor_class,) + bases,
110                                                 attrs)
111        # As Descriptor.__subclasses__() only reports its direct subclasses,
112        # we keep track of all subclasses of Descriptor using the
113        # |DescriptorMeta.descriptor_classes| attribute.
114        mcs.descriptor_classes.append(cls)
115        return cls
116
117
118class Descriptor(object):
119    """
120    USB Descriptor base class.
121
122    This class should not be instantiated or used directly.
123
124    """
125    __metaclass__ = DescriptorMeta
126
127
128class UnknownDescriptor(Descriptor):
129    """
130    Unknown USB Descriptor.
131
132    This class is a catch-all descriptor for unsupported or unknown descriptor
133    types.
134    """
135    _FIELDS = (('B', 'bLength'),
136               ('B', 'bDescriptorType'))
137
138
139class DeviceDescriptor(Descriptor):
140    """ Device Descriptor. """
141    DESCRIPTOR_TYPE = 0x01
142    _FIELDS = (('B', 'bLength'),
143               ('B', 'bDescriptorType'),
144               ('H', 'bcdUSB'),
145               ('B', 'bDeviceClass'),
146               ('B', 'bDeviceSubClass'),
147               ('B', 'bDeviceProtocol'),
148               ('B', 'bMaxPacketSize0'),
149               ('H', 'idVendor'),
150               ('H', 'idProduct'),
151               ('H', 'bcdDevice'),
152               ('B', 'iManufacturer'),
153               ('B', 'iProduct'),
154               ('B', 'iSerialNumber'),
155               ('B', 'bNumConfigurations'))
156
157
158class ConfigurationDescriptor(Descriptor):
159    """ Configuration Descriptor. """
160    DESCRIPTOR_TYPE = 0x02
161    _FIELDS = (('B', 'bLength'),
162               ('B', 'bDescriptorType'),
163               ('H', 'wTotalLength'),
164               ('B', 'bNumInterfaces'),
165               ('B', 'bConfigurationValue'),
166               ('B', 'iConfiguration'),
167               ('B', 'bmAttributes'),
168               ('B', 'bMaxPower'))
169
170
171class InterfaceDescriptor(Descriptor):
172    """ Interface Descriptor. """
173    DESCRIPTOR_TYPE = 0x04
174    _FIELDS = (('B', 'bLength'),
175               ('B', 'bDescriptorType'),
176               ('B', 'bInterfaceNumber'),
177               ('B', 'bAlternateSetting'),
178               ('B', 'bNumEndpoints'),
179               ('B', 'bInterfaceClass'),
180               ('B', 'bInterfaceSubClass'),
181               ('B', 'bInterfaceProtocol'),
182               ('B', 'iInterface'))
183
184
185class EndpointDescriptor(Descriptor):
186    """ Endpoint Descriptor. """
187    DESCRIPTOR_TYPE = 0x05
188    _FIELDS = (('B', 'bLength'),
189               ('B', 'bDescriptorType'),
190               ('B', 'bEndpointAddress'),
191               ('B', 'bmAttributes'),
192               ('H', 'wMaxPacketSize'),
193               ('B', 'bInterval'))
194
195
196class InterfaceAssociationDescriptor(Descriptor):
197    """ Interface Asscociation Descriptor. """
198    DESCRIPTOR_TYPE = 0x0B
199    _FIELDS = (('B', 'bLength'),
200               ('B', 'bDescriptorType'),
201               ('B', 'bFirstInterface'),
202               ('B', 'bInterfaceCount'),
203               ('B', 'bFunctionClass'),
204               ('B', 'bFunctionSubClass'),
205               ('B', 'bFunctionProtocol'),
206               ('B', 'iFunction'))
207
208
209class FunctionalDescriptor(Descriptor):
210    """ Functional Descriptor. """
211    DESCRIPTOR_TYPE = 0x24
212    _FIELDS = (('B', 'bLength'),
213               ('B', 'bDescriptorType'),
214               ('B', 'bDescriptorSubtype'))
215
216
217class HeaderFunctionalDescriptor(FunctionalDescriptor):
218    """ Header Functional Descriptor. """
219    DESCRIPTOR_SUBTYPE = 0x00
220    _FIELDS = (('B', 'bLength'),
221               ('B', 'bDescriptorType'),
222               ('B', 'bDescriptorSubtype'),
223               ('H', 'bcdCDC'))
224
225
226class UnionFunctionalDescriptor(FunctionalDescriptor):
227    """ Union Functional Descriptor. """
228    DESCRIPTOR_SUBTYPE = 0x06
229    _FIELDS = (('B', 'bLength'),
230               ('B', 'bDescriptorType'),
231               ('B', 'bDescriptorSubtype'),
232               ('B', 'bControlInterface'),
233               ('B', 'bSubordinateInterface0'))
234
235
236class MBIMFunctionalDescriptor(FunctionalDescriptor):
237    """ MBIM Functional Descriptor. """
238    DESCRIPTOR_SUBTYPE = 0x1B
239    _FIELDS = (('B', 'bLength'),
240               ('B', 'bDescriptorType'),
241               ('B', 'bDescriptorSubtype'),
242               ('H', 'bcdMBIMVersion'),
243               ('H', 'wMaxControlMessage'),
244               ('B', 'bNumberFilters'),
245               ('B', 'bMaxFilterSize'),
246               ('H', 'wMaxSegmentSize'),
247               ('B', 'bmNetworkCapabilities'))
248
249
250class MBIMExtendedFunctionalDescriptor(FunctionalDescriptor):
251    """ MBIM Extended Functional Descriptor. """
252    DESCRIPTOR_SUBTYPE = 0x1C
253    _FIELDS = (('B', 'bLength'),
254               ('B', 'bDescriptorType'),
255               ('B', 'bDescriptorSubtype'),
256               ('H', 'bcdMBIMExtendedVersion'),
257               ('B', 'bMaxOutstandingCommandMessages'),
258               ('H', 'wMTU'))
259
260
261class SuperSpeedEndpointCompanionDescriptor(Descriptor):
262    """ SuperSpeed Endpoint Companion Descriptor. """
263    DESCRIPTOR_TYPE = 0x30
264    _FIELDS = (('B', 'bLength'),
265               ('B', 'bDescriptorType'),
266               ('B', 'bMaxBurst'),
267               ('B', 'bmAttributes'),
268               ('H', 'wBytesPerInterval'))
269
270
271class DescriptorParser(object):
272    """
273    A class for extracting USB descriptors from raw descriptor data.
274
275    This class takes raw descriptor data as an array of unsigned bytes via its
276    constructor and provides an iterator interface to return individual USB
277    descriptors via instances derived from a subclass of Descriptor.
278
279    """
280    _DESCRIPTOR_CLASS_MAP = {
281            (cls.DESCRIPTOR_TYPE, getattr(cls, 'DESCRIPTOR_SUBTYPE', None)): cls
282            for cls in DescriptorMeta.descriptor_classes
283            if hasattr(cls, 'DESCRIPTOR_TYPE')
284    }
285
286    def __init__(self, data):
287        self._data = data
288        self._data_length = len(data)
289        self._index = 0
290
291    def __iter__(self):
292        return self
293
294    def next(self):
295        """
296        Returns the next descriptor found in the descriptor data.
297
298        @return An instance of a subclass of Descriptor.
299        @raise StopIteration if no more descriptor is found,
300
301        """
302        if self._index >= self._data_length:
303            raise StopIteration
304
305        # Identify the descriptor class based on bDescriptorType, and if
306        # available, bDescriptorSubtype. The descriptor data has a standard
307        # layout as follows:
308        #   self._data[self._index]: bLength
309        #   self._data[self._index + 1]: bDescriptorType
310        #   self._data[self._index + 2]: bDescriptorSubtype for some descriptors
311        descriptor_type, descriptor_subtype = None, None
312        if self._index + 1 < self._data_length:
313            descriptor_type = self._data[self._index + 1]
314            if self._index + 2 < self._data_length:
315                descriptor_subtype = self._data[self._index + 2]
316
317        descriptor_class = self._DESCRIPTOR_CLASS_MAP.get(
318                (descriptor_type, descriptor_subtype), None)
319        if descriptor_class is None:
320            descriptor_class = self._DESCRIPTOR_CLASS_MAP.get(
321                    (descriptor_type, None), UnknownDescriptor)
322
323        next_index = self._index + self._data[self._index]
324        descriptor = descriptor_class(self._data[self._index:next_index])
325        self._index = next_index
326        return descriptor
327