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 |