Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 """USB descriptor generation utilities. | |
|
not at google - send to devlin
2014/07/23 00:05:44
copyright
Reilly Grant (use Gerrit)
2014/07/23 00:43:54
Done.
| |
| 2 | |
| 3 Classes to represent and generate USB descriptors. | |
| 4 """ | |
|
Ken Rockot(use gerrit already)
2014/07/23 00:03:48
Maybe this module should be usb_descriptors?
Reilly Grant (use Gerrit)
2014/07/23 00:43:54
Done.
| |
| 5 | |
| 6 import struct | |
| 7 | |
| 8 import usb_const | |
|
Ken Rockot(use gerrit already)
2014/07/23 00:03:47
How about "usb_constants" for the module name?
Reilly Grant (use Gerrit)
2014/07/23 00:43:54
Done.
| |
| 9 | |
| 10 | |
| 11 class Field(object): | |
| 12 | |
|
Ken Rockot(use gerrit already)
2014/07/23 00:03:47
A bit of documentation?
Reilly Grant (use Gerrit)
2014/07/23 00:43:54
Done.
| |
| 13 def __init__(self, name, str_fmt, struct_fmt, required): | |
| 14 self.name = name | |
| 15 self.str_fmt = str_fmt | |
| 16 self.struct_fmt = struct_fmt | |
| 17 self.required = required | |
| 18 | |
| 19 def Format(self, value): | |
| 20 return self.str_fmt.format(value) | |
| 21 | |
| 22 | |
| 23 class Descriptor(object): | |
| 24 """Base class for USB descriptor types. | |
| 25 | |
| 26 This class provides general functionality for creating object types that | |
| 27 represent USB descriptors. The AddField and related methods are used to | |
| 28 define the fields of each structure. Fields can then be set using keyword | |
| 29 arguments to the object constructor or by accessing properties on the object. | |
| 30 """ | |
| 31 | |
| 32 _fields = None | |
| 33 | |
| 34 @classmethod | |
| 35 def AddField(cls, name, struct_fmt, str_fmt='{}', default=None): | |
| 36 """Adds a user-specified field to this descriptor. | |
| 37 | |
| 38 Adds a field to the binary structure representing this descriptor. The field | |
| 39 can be set by passing a keyword argument name=... to the object constructor | |
| 40 will be accessible as foo.name on any instance. | |
| 41 | |
| 42 If no default value is provided then the constructor will through an | |
| 43 exception if this field is not one of the provided keyword arguments. | |
| 44 | |
| 45 Args: | |
| 46 name: String name of the field. | |
| 47 struct_fmt: Python 'struct' module format string for this field. | |
| 48 str_fmt: Python 'string' module format string for this field. | |
| 49 default: Default value. | |
| 50 """ | |
| 51 if cls._fields is None: | |
| 52 cls._fields = [] | |
| 53 cls._fields.append(Field(name, str_fmt, struct_fmt, default is None)) | |
| 54 | |
| 55 member_name = '_{}'.format(name) | |
| 56 def Setter(self, value): | |
| 57 setattr(self, member_name, value) | |
| 58 def Getter(self): | |
| 59 try: | |
| 60 return getattr(self, member_name) | |
| 61 except AttributeError: | |
| 62 assert default is not None | |
| 63 return default | |
| 64 | |
| 65 setattr(cls, name, property(Getter, Setter)) | |
| 66 | |
| 67 @classmethod | |
| 68 def AddFixedField(cls, name, struct_fmt, value, str_fmt='{}'): | |
| 69 """Adds a constant field to this descriptor. | |
| 70 | |
| 71 Adds a constant field to the binary structure representing this descriptor. | |
| 72 The field will be accessible as foo.name on any instance. | |
| 73 | |
| 74 The value of this field may not be given as a constructor parameter or | |
| 75 set on an existing instance. | |
| 76 | |
| 77 Args: | |
| 78 name: String name of the field. | |
| 79 struct_fmt: Python 'struct' module format string for this field. | |
| 80 value: Field value. | |
| 81 str_fmt: Python 'string' module format string for this field. | |
| 82 """ | |
| 83 if cls._fields is None: | |
| 84 cls._fields = [] | |
| 85 cls._fields.append(Field(name, str_fmt, struct_fmt, False)) | |
| 86 | |
| 87 def Setter(unused_self, unused_value): | |
| 88 raise RuntimeError('{} is a fixed field.'.format(name)) | |
| 89 def Getter(unused_self): | |
| 90 return value | |
| 91 | |
| 92 setattr(cls, name, property(Getter, Setter)) | |
| 93 | |
| 94 @classmethod | |
| 95 def AddComputedField(cls, name, struct_fmt, property_name, str_fmt='{}'): | |
| 96 """Adds a constant field to this descriptor. | |
| 97 | |
| 98 Adds a field to the binary structure representing this descriptor whos value | |
| 99 is equal to an object property. The field will be accessible as foo.name on | |
| 100 any instance. | |
| 101 | |
| 102 The value of this field may not be given as a constructor parameter or | |
| 103 set on an existing instance. | |
| 104 | |
| 105 Args: | |
| 106 name: String name of the field. | |
| 107 struct_fmt: Python 'struct' module format string for this field. | |
| 108 property_name: Property to read. | |
| 109 str_fmt: Python 'string' module format string for this field. | |
| 110 """ | |
| 111 if cls._fields is None: | |
| 112 cls._fields = [] | |
| 113 cls._fields.append(Field(name, str_fmt, struct_fmt, False)) | |
| 114 | |
| 115 def Setter(unused_self, unused_value): | |
| 116 raise RuntimeError('{} is a computed field.'.format(name)) | |
| 117 def Getter(self): | |
| 118 return getattr(self, property_name) | |
| 119 | |
| 120 setattr(cls, name, property(Getter, Setter)) | |
| 121 | |
| 122 def __init__(self, **kwargs): | |
| 123 """Constructs a new instance of this descriptor. | |
| 124 | |
| 125 All fields which do not have a default value and are not fixed or computed | |
| 126 from a property must be specified as keyword arguments. | |
| 127 | |
| 128 Args: | |
| 129 **kwargs: Field values. | |
| 130 | |
| 131 Raises: | |
| 132 TypeError: A required field was missing or an unexpected field was given. | |
| 133 """ | |
| 134 fields = {field.name for field in self._fields} | |
| 135 required_fields = {field.name for field in self._fields if field.required} | |
| 136 | |
| 137 for arg, value in kwargs.iteritems(): | |
| 138 if arg not in fields: | |
| 139 raise TypeError('Unexpected field: {}'.format(arg)) | |
| 140 | |
| 141 setattr(self, arg, value) | |
| 142 required_fields.discard(arg) | |
| 143 | |
| 144 if required_fields: | |
| 145 raise TypeError('Missing fields: {}'.format(', '.join(required_fields))) | |
| 146 | |
| 147 @property | |
| 148 def fmt(self): | |
| 149 """Returns the Python 'struct' module format string for this descriptor.""" | |
| 150 return '<{}'.format(''.join([field.struct_fmt for field in self._fields])) | |
| 151 | |
| 152 @property | |
| 153 def struct_size(self): | |
| 154 """Returns the size of the struct defined by fmt.""" | |
| 155 return struct.calcsize(self.fmt) | |
| 156 | |
| 157 @property | |
| 158 def total_size(self): | |
| 159 """Returns the total size of this descriptor.""" | |
| 160 return self.struct_size | |
| 161 | |
| 162 def Encode(self): | |
| 163 """Returns the binary representation of this descriptor.""" | |
| 164 values = [getattr(self, field.name) for field in self._fields] | |
| 165 return struct.pack(self.fmt, *values) | |
| 166 | |
| 167 def __str__(self): | |
| 168 max_length = max(len(field.name) for field in self._fields) | |
| 169 | |
| 170 return '{}:\n {}'.format( | |
| 171 self.__class__.__name__, | |
| 172 '\n '.join('{} {}'.format( | |
| 173 '{}:'.format(field.name).ljust(max_length+1), | |
| 174 field.Format(getattr(self, field.name)) | |
| 175 ) for field in self._fields) | |
| 176 ) | |
| 177 | |
| 178 | |
| 179 class DeviceDescriptor(Descriptor): | |
| 180 """Represents a USB device descriptor.""" | |
| 181 | |
| 182 pass | |
| 183 | |
| 184 DeviceDescriptor.AddComputedField('bLength', 'B', 'struct_size') | |
|
Ken Rockot(use gerrit already)
2014/07/23 00:03:47
Might as well link to / reference any relevant sec
Reilly Grant (use Gerrit)
2014/07/23 00:43:54
Done.
| |
| 185 DeviceDescriptor.AddFixedField('bDescriptorType', 'B', | |
| 186 usb_const.DescriptorType.DEVICE) | |
| 187 DeviceDescriptor.AddField('bcdUSB', 'H', default=0x0200, str_fmt='0x{:04X}') | |
| 188 DeviceDescriptor.AddField('bDeviceClass', 'B', | |
| 189 default=usb_const.DeviceClass.PER_INTERFACE) | |
| 190 DeviceDescriptor.AddField('bDeviceSubClass', 'B', | |
| 191 default=usb_const.DeviceSubClass.PER_INTERFACE) | |
| 192 DeviceDescriptor.AddField('bDeviceProtocol', 'B', | |
| 193 default=usb_const.DeviceProtocol.PER_INTERFACE) | |
| 194 DeviceDescriptor.AddField('bMaxPacketSize0', 'B', default=64) | |
| 195 DeviceDescriptor.AddField('idVendor', 'H', str_fmt='0x{:04X}') | |
| 196 DeviceDescriptor.AddField('idProduct', 'H', str_fmt='0x{:04X}') | |
| 197 DeviceDescriptor.AddField('bcdDevice', 'H', str_fmt='0x{:04X}') | |
| 198 DeviceDescriptor.AddField('iManufacturer', 'B', default=0) | |
| 199 DeviceDescriptor.AddField('iProduct', 'B', default=0) | |
| 200 DeviceDescriptor.AddField('iSerialNumber', 'B', default=0) | |
| 201 DeviceDescriptor.AddField('bNumConfigurations', 'B', default=1) | |
| 202 | |
| 203 | |
| 204 class DescriptorContainer(Descriptor): | |
|
Ken Rockot(use gerrit already)
2014/07/23 00:03:48
If this is established nomenclature in USB land, I
Reilly Grant (use Gerrit)
2014/07/23 00:43:54
"CompositeDescriptor" may confusing because "USB C
| |
| 205 """Super-class for descriptors which contain more descriptors. | |
| 206 | |
| 207 This class adds the ability for a descriptor to have an array of additional | |
| 208 descriptors which follow it. | |
| 209 """ | |
| 210 | |
| 211 def __init__(self, **kwargs): | |
| 212 super(DescriptorContainer, self).__init__(**kwargs) | |
| 213 self._descriptors = [] | |
| 214 | |
| 215 @property | |
| 216 def total_size(self): | |
| 217 return self.struct_size + sum([descriptor.total_size | |
| 218 for descriptor in self._descriptors]) | |
| 219 | |
| 220 def Add(self, descriptor): | |
| 221 self._descriptors.append(descriptor) | |
| 222 | |
| 223 def Encode(self): | |
| 224 bufs = [super(DescriptorContainer, self).Encode()] | |
| 225 bufs.extend(descriptor.Encode() for descriptor in self._descriptors) | |
| 226 return ''.join(bufs) | |
| 227 | |
| 228 def __str__(self): | |
| 229 return '{}\n{}'.format(super(DescriptorContainer, self).__str__(), | |
| 230 '\n'.join(str(descriptor) | |
| 231 for descriptor in self._descriptors)) | |
| 232 | |
| 233 | |
| 234 class ConfigurationDescriptor(DescriptorContainer): | |
| 235 """Represents a USB device configuration descriptor. | |
| 236 | |
| 237 Configuration descriptors may have a number of interface descriptors. | |
| 238 """ | |
| 239 | |
| 240 def __init__(self, **kwargs): | |
| 241 super(ConfigurationDescriptor, self).__init__(**kwargs) | |
| 242 self._interfaces = {} | |
| 243 | |
| 244 @property | |
| 245 def num_interfaces(self): | |
| 246 interface_numbers = {key[0] for key in self._interfaces.iterkeys()} | |
| 247 return len(interface_numbers) | |
| 248 | |
| 249 def AddInterface(self, interface): | |
| 250 key = (interface.bInterfaceNumber, interface.bAlternateSetting) | |
| 251 if key in self._interfaces: | |
| 252 raise RuntimeError('Interface {} (alternate {}) already defined.' | |
| 253 .format(key[0], key[1])) | |
| 254 self._interfaces[key] = interface | |
| 255 self.Add(interface) | |
| 256 | |
| 257 def GetInterfaces(self): | |
| 258 return self._interfaces.values() | |
| 259 | |
| 260 ConfigurationDescriptor.AddComputedField('bLength', 'B', 'struct_size') | |
| 261 ConfigurationDescriptor.AddFixedField('bDescriptorType', 'B', | |
| 262 usb_const.DescriptorType.CONFIGURATION) | |
| 263 ConfigurationDescriptor.AddComputedField('wTotalLength', 'H', 'total_size') | |
| 264 ConfigurationDescriptor.AddComputedField('bNumInterfaces', 'B', | |
| 265 'num_interfaces') | |
| 266 ConfigurationDescriptor.AddField('bConfigurationValue', 'B', default=1) | |
| 267 ConfigurationDescriptor.AddField('iConfiguration', 'B', default=0) | |
| 268 ConfigurationDescriptor.AddField('bmAttributes', 'B', str_fmt='0x{:02X}') | |
| 269 ConfigurationDescriptor.AddField('MaxPower', 'B') | |
| 270 | |
| 271 | |
| 272 class InterfaceDescriptor(DescriptorContainer): | |
| 273 """Represents a USB interface descriptor. | |
| 274 | |
| 275 Interface descriptors may have a number of endpoint descriptors. | |
| 276 """ | |
| 277 | |
| 278 def __init__(self, **kwargs): | |
| 279 super(InterfaceDescriptor, self).__init__(**kwargs) | |
| 280 self._endpoints = {} | |
| 281 | |
| 282 @property | |
| 283 def num_endpoints(self): | |
| 284 return len(self._endpoints) | |
| 285 | |
| 286 def AddEndpoint(self, endpoint): | |
| 287 if endpoint.bEndpointAddress in self._endpoints: | |
| 288 raise RuntimeError('Endpoint 0x{:02X} already defined on this interface.' | |
| 289 .format(endpoint.bEndpointAddress)) | |
| 290 self._endpoints[endpoint.bEndpointAddress] = endpoint | |
| 291 self.Add(endpoint) | |
| 292 | |
| 293 def GetEndpoints(self): | |
| 294 return self._endpoints.values() | |
| 295 | |
| 296 InterfaceDescriptor.AddComputedField('bLength', 'B', 'struct_size') | |
| 297 InterfaceDescriptor.AddFixedField('bDescriptorType', 'B', | |
| 298 usb_const.DescriptorType.INTERFACE) | |
| 299 InterfaceDescriptor.AddField('bInterfaceNumber', 'B') | |
| 300 InterfaceDescriptor.AddField('bAlternateSetting', 'B', default=0) | |
| 301 InterfaceDescriptor.AddComputedField('bNumEndpoints', 'B', 'num_endpoints') | |
| 302 InterfaceDescriptor.AddField('bInterfaceClass', 'B', | |
| 303 default=usb_const.InterfaceClass.VENDOR) | |
| 304 InterfaceDescriptor.AddField('bInterfaceSubClass', 'B', | |
| 305 default=usb_const.InterfaceSubClass.VENDOR) | |
| 306 InterfaceDescriptor.AddField('bInterfaceProtocol', 'B', | |
| 307 default=usb_const.InterfaceProtocol.VENDOR) | |
| 308 InterfaceDescriptor.AddField('iInterface', 'B', default=0) | |
| 309 | |
| 310 | |
| 311 class EndpointDescriptor(Descriptor): | |
| 312 """Represents a USB endpoint descriptor.""" | |
| 313 | |
| 314 pass | |
| 315 | |
| 316 EndpointDescriptor.AddComputedField('bLength', 'B', 'struct_size') | |
| 317 EndpointDescriptor.AddFixedField('bDescriptorType', 'B', | |
| 318 usb_const.DescriptorType.ENDPOINT) | |
| 319 EndpointDescriptor.AddField('bEndpointAddress', 'B', str_fmt='0x{:02X}') | |
| 320 EndpointDescriptor.AddField('bmAttributes', 'B', str_fmt='0x{:02X}') | |
| 321 EndpointDescriptor.AddField('wMaxPacketSize', 'H') | |
| 322 EndpointDescriptor.AddField('bInterval', 'B') | |
| 323 | |
| 324 | |
| 325 class HidDescriptor(Descriptor): | |
| 326 """Represents a USB HID descriptor.""" | |
| 327 | |
| 328 def __init__(self, **kwargs): | |
| 329 super(HidDescriptor, self).__init__(**kwargs) | |
| 330 self._descriptors = [] | |
| 331 | |
| 332 def AddDescriptor(self, typ, length): | |
| 333 self._descriptors.append((typ, length)) | |
| 334 | |
| 335 @property | |
| 336 def struct_size(self): | |
| 337 return super(HidDescriptor, self).struct_size + self.num_descriptors * 3 | |
| 338 | |
| 339 @property | |
| 340 def num_descriptors(self): | |
| 341 return len(self._descriptors) | |
| 342 | |
| 343 def Encode(self): | |
| 344 bufs = [super(HidDescriptor, self).Encode()] | |
| 345 bufs.extend(struct.pack('<BH', typ, length) | |
| 346 for typ, length in self._descriptors) | |
| 347 return ''.join(bufs) | |
| 348 | |
| 349 def __str__(self): | |
| 350 return '{}\n{}'.format( | |
| 351 super(HidDescriptor, self).__str__(), | |
| 352 '\n'.join(' bDescriptorType: 0x{:02X}\n wDescriptorLength: {}' | |
| 353 .format(typ, length) for typ, length in self._descriptors)) | |
| 354 | |
| 355 HidDescriptor.AddComputedField('bLength', 'B', 'struct_size') | |
| 356 HidDescriptor.AddFixedField('bDescriptorType', 'B', 33) | |
| 357 HidDescriptor.AddField('bcdHID', 'H', default=0x0111, str_fmt='0x{:04X}') | |
| 358 HidDescriptor.AddField('bCountryCode', 'B', default=0) | |
| 359 HidDescriptor.AddComputedField('bNumDescriptors', 'B', 'num_descriptors') | |
| OLD | NEW |