1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 """
17 Defines the structure of packets that are sent to/received from the device.
18
19 Packet structure is defined using classes and inheritance. Each class is a view that imposes
20 structure on the underlying data buffer. The data buffer is encoded in little-endian format, but you don't
21 have to worry about that if you are using the classes. The classes have instance variables with getter/setter functions defined
22 to take care of the encoding/decoding. The classes are intended to mimic C structs.
23
24 There are three kinds of packets. L{Commands<Command>}, L{Responses<Response>}, and L{Answers<Answer>}.
25 C{Commands} are sent to the device on the control bus, C{Responses} are received from the device,
26 also on the control bus. C{Answers} and their sub-classes represent data packets sent to/received from
27 the device via bulk transfers.
28
29 Commands are organized as follows: G{classtree Command}
30
31 You will typically only use sub-classes of Command.
32
33 Responses are organized as follows: G{classtree Response}
34
35 Responses inherit Command as they share header structure.
36
37 Answers are organized as follows: G{classtree Answer}
38 """
39
40 import struct
41 from errors import PacketError
42
43 DWORD = "<I"
44 DDWORD = "<Q"
45
46
53
54
56
57 """
58 Represents raw (unstructured) data packets sent over the usb bus.
59
60 C{TransferBuffer} is a wrapper around the tuples used by L{PyUSB<usb>} for communication.
61 It has convenience methods to read and write data from the underlying buffer. See
62 L{TransferBuffer.pack} and L{TransferBuffer.unpack}.
63 """
64
66 """
67 Create a L{TransferBuffer} from C{packet} or an empty buffer.
68
69 @type packet: integer or listable object
70 @param packet: If packet is a list, it is copied into the C{TransferBuffer} and then normalized (see L{TransferBuffer._normalize}).
71 If it is an integer, a zero buffer of that length is created.
72 """
73 if "__len__" in dir(packet):
74 list.__init__(self, list(packet))
75 self._normalize()
76 else: list.__init__(self, [0 for i in range(packet)])
77
81
85
87 """
88 Return a string representation of this buffer.
89
90 Packets are represented as hex strings, in 2-byte pairs, S{<=} 16 bytes to a line. An ASCII representation is included. For example::
91 0700 0100 0000 0000 0000 0000 0c00 0000 ................
92 0200 0000 0400 0000 4461 7461 ........Data
93 """
94 ans, ascii = ": ".rjust(10,"0"), ""
95 for i in range(0, len(self), 2):
96 for b in range(2):
97 try:
98 ans += TransferBuffer.phex(self[i+b])
99 ascii += chr(self[i+b]) if self[i+b] > 31 and self[i+b] < 127 else "."
100 except IndexError: break
101 ans = ans + " "
102 if (i+2)%16 == 0:
103 if i+2 < len(self):
104 ans += " " + ascii + "\n" + (TransferBuffer.phex(i+2)+": ").rjust(10, "0")
105 ascii = ""
106 last_line = ans[ans.rfind("\n")+1:]
107 padding = 50 - len(last_line)
108 ans += "".ljust(padding) + " " + ascii
109 return ans.strip()
110
112 """
113 Return decoded data from buffer.
114
115 @param fmt: See U{struct<http://docs.python.org/lib/module-struct.html>}
116 @param start: Position in buffer from which to decode
117 """
118 end = start + struct.calcsize(fmt)
119 return struct.unpack(fmt, "".join([ chr(i) for i in list.__getslice__(self, start, end) ]))
120
122 """
123 Encode C{val} and write it to buffer.
124
125 @param fmt: See U{struct<http://docs.python.org/lib/module-struct.html>}
126 @param start: Position in buffer at which to write encoded data
127 """
128 self[start:start+struct.calcsize(fmt)] = [ ord(i) for i in struct.pack(fmt, val) ]
129
131 """ Replace negative bytes in C{self} by 256 + byte """
132 for i in range(len(self)):
133 if self[i] < 0:
134 self[i] = 256 + self[i]
135
136 @classmethod
137 - def phex(cls, num):
138 """
139 Return the hex representation of num without the 0x prefix.
140
141 If the hex representation is only 1 digit it is padded to the left with a zero. Used in L{TransferBuffer.__str__}.
142 """
143 index, sign = 2, ""
144 if num < 0:
145 index, sign = 3, "-"
146 h=hex(num)[index:]
147 if len(h) < 2:
148 h = "0"+h
149 return sign + h
150
151
153 """ A U{Descriptor<http://www.cafepy.com/article/python_attributes_and_methods/python_attributes_and_methods.html>}, that implements access
154 to protocol packets in a human readable way.
155 """
157 """
158 @param start: The byte at which this field is stored in the buffer
159 @param fmt: The packing format for this field. See U{struct<http://docs.python.org/lib/module-struct.html>}.
160 """
161 self._fmt, self._start = fmt, start
162
164 return obj.unpack(start=self._start, fmt=self._fmt)[0]
165
167 obj.pack(val, start=self._start, fmt=self._fmt)
168
170 if self._fmt == DWORD: typ = "unsigned int"
171 if self._fmt == DDWORD: typ = "unsigned long long"
172 return "An " + typ + " stored in " + str(struct.calcsize(self._fmt)) + " bytes starting at byte " + str(self._start)
173
175 """ A field storing a variable length string. """
176 - def __init__(self, length_field, start=16):
177 """
178 @param length_field: A U{Descriptor<http://www.cafepy.com/article/python_attributes_and_methods/python_attributes_and_methods.html>}
179 that returns the length of the string.
180 @param start: The byte at which this field is stored in the buffer
181 """
182 self._length_field = length_field
183 self._start = start
184
188
190 obj.pack(val, start=self._start, fmt="<"+str(len(val))+"s")
191
193 return "A string starting at byte " + str(self._start)
194
196
197 """ Defines the structure of command packets sent to the device. """
198
199 number = field(start=0, fmt=DWORD)
200 """
201 Command number. C{unsigned int} stored in 4 bytes at byte 0.
202
203 Command numbers are:
204 0 GetUsbProtocolVersion
205 1 ReqUsbConnect
206
207 10 FskFileOpen
208 11 FskFileClose
209 12 FskGetSize
210 13 FskSetSize
211 14 FskFileSetPosition
212 15 FskGetPosition
213 16 FskFileRead
214 17 FskFileWrite
215 18 FskFileGetFileInfo
216 19 FskFileSetFileInfo
217 1A FskFileCreate
218 1B FskFileDelete
219 1C FskFileRename
220
221 30 FskFileCreateDirectory
222 31 FskFileDeleteDirectory
223 32 FskFileRenameDirectory
224 33 FskDirectoryIteratorNew
225 34 FskDirectoryIteratorDispose
226 35 FskDirectoryIteratorGetNext
227
228 52 FskVolumeGetInfo
229 53 FskVolumeGetInfoFromPath
230
231 80 FskFileTerminate
232
233 100 ConnectDevice
234 101 GetProperty
235 102 GetMediaInfo
236 103 GetFreeSpace
237 104 SetTime
238 105 DeviceBeginEnd
239 106 UnlockDevice
240 107 SetBulkSize
241
242 110 GetHttpRequest
243 111 SetHttpRespponse
244 112 Needregistration
245 114 GetMarlinState
246
247 200 ReqDiwStart
248 201 SetDiwPersonalkey
249 202 GetDiwPersonalkey
250 203 SetDiwDhkey
251 204 GetDiwDhkey
252 205 SetDiwChallengeserver
253 206 GetDiwChallengeserver
254 207 GetDiwChallengeclient
255 208 SetDiwChallengeclient
256 209 GetDiwVersion
257 20A SetDiwWriteid
258 20B GetDiwWriteid
259 20C SetDiwSerial
260 20D GetDiwModel
261 20C SetDiwSerial
262 20E GetDiwDeviceid
263 20F GetDiwSerial
264 210 ReqDiwCheckservicedata
265 211 ReqDiwCheckiddata
266 212 ReqDiwCheckserialdata
267 213 ReqDiwFactoryinitialize
268 214 GetDiwMacaddress
269 215 ReqDiwTest
270 216 ReqDiwDeletekey
271
272 300 UpdateChangemode
273 301 UpdateDeletePartition
274 302 UpdateCreatePartition
275 303 UpdateCreatePartitionWithImage
276 304 UpdateGetPartitionSize
277 """
278
279 type = field(start=4, fmt=DDWORD)
280
281 length = field(start=12, fmt=DWORD)
282
283 @apply
285 doc =\
286 """
287 The data part of this command. Returned/set as/by a TransferBuffer. Stored at byte 16.
288
289 Setting it by default changes self.length to the length of the new buffer. You may have to reset it to
290 the significant part of the buffer. You would normally use the C{command} property of L{ShortCommand} or L{LongCommand} instead.
291 """
292 def fget(self):
293 return self[16:]
294
295 def fset(self, buffer):
296 self[16:] = buffer
297 self.length = len(buffer)
298
299 return property(**locals())
300
302 """
303 @param packet: len(packet) > 15 or packet > 15
304 """
305 if ("__len__" in dir(packet) and len(packet) < 16) or ("__len__" not in dir(packet) and packet < 16):
306 raise PacketError(str(self.__class__)[7:-2] + " packets must have length atleast 16")
307 TransferBuffer.__init__(self, packet)
308
309
310
312
313 """ A L{Command} whoose data section is 4 bytes long """
314
315 SIZE = 20
316 command = field(start=16, fmt=DWORD)
317
318 - def __init__(self, number=0x00, type=0x00, command=0x00):
329
331 """ The command that asks the device to send the next item in the list """
332 NUMBER = 0x35
336
338 """ Close a previously opened directory """
339 NUMBER = 0x34
343
345 """ Ask device to change status to 'USB connected' i.e., tell the device that the present sequence of commands is complete """
346 NUMBER=0x1
349
351 """ Get USB Protocol version used by device """
352 NUMBER=0x0
355
360
365
367
368 """ A L{Command} whoose data section is 16 bytes long """
369
370 SIZE = 32
371
372 - def __init__(self, number=0x00, type=0x00, command=0x00):
383
384 @apply
386 doc =\
387 """
388 Usually carries extra information needed for the command
389 It is a list of C{unsigned integers} of length between 1 and 4. 4 C{unsigned int} stored in 16 bytes at byte 16.
390 """
391 def fget(self):
392 return self.unpack(start=16, fmt="<"+str(self.length/4)+"I")
393
394 def fset(self, val):
395 if "__len__" not in dir(val): val = (val,)
396 start = 16
397 for command in val:
398 self.pack(command, start=start, fmt=DWORD)
399 start += struct.calcsize(DWORD)
400
401 return property(**locals())
402
415
417 """ Query the free space available """
418 NUMBER = 0x53
421
423 """ Create a directory """
424 NUMBER = 0x30
427
429 """ Open a directory for reading its contents """
430 NUMBER = 0x33
432 PathCommand.__init__(self, path, DirOpen.NUMBER)
433
434
436 """ Must be sent to device after a bulk read """
438 """ bulk_read_id is an integer, the id of the bulk read we are acknowledging. See L{Answer.id} """
439 LongCommand.__init__(self, number=0x1000, type=0x00, command=bulk_read_id)
440
442 """ The command used to ask for device information """
443 NUMBER=0x0101
448
450 """ File close command """
451 NUMBER = 0x11
454
456 """ Create a file """
457 NUMBER=0x1a
460
462 """ Delete a file """
463 NUMBER=0x1B
466
468 """ Delete a directory """
469 NUMBER=0x31
472
474 """ File open command """
475 NUMBER = 0x10
476 READ = 0x00
477 WRITE = 0x01
478 path_length = field(start=20, fmt=DWORD)
479 path = stringfield(path_length, start=24)
480
484
485 @apply
487 doc =\
488 """ The file open mode. Is either L{FileOpen.READ} or L{FileOpen.WRITE}. C{unsigned int} stored at byte 16. """
489 def fget(self):
490 return self.unpack(start=16, fmt=DWORD)[0]
491
492 def fset(self, val):
493 self.pack(val, start=16, fmt=DWORD)
494
495 return property(**locals())
496
497
499 """ Command to read/write from an open file """
500 RNUMBER = 0x16
501 WNUMBER = 0x17
502 id = field(start=16, fmt=DWORD)
503 offset = field(start=20, fmt=DDWORD)
504 size = field(start=28, fmt=DWORD)
505 - def __init__(self, id, offset, size, mode=0x16):
506 """
507 @param id: File identifier returned by a L{FileOpen} command
508 @type id: C{unsigned int}
509 @param offset: Position in file at which to read
510 @type offset: C{unsigned long long}
511 @param size: number of bytes to read
512 @type size: C{unsigned int}
513 @param mode: Either L{FileIO.RNUMBER} or L{File.WNUMBER}
514 """
515 Command.__init__(self, 32)
516 self.number=mode
517 self.type = 0x01
518 self.length = 16
519 self.id = id
520 self.offset = offset
521 self.size = size
522
523
525 """ Defines structure of command that requests information about a path """
526 NUMBER = 0x18
528 PathCommand.__init__(self, path, PathQuery.NUMBER)
529
531 """ Set File information """
532 NUMBER = 0x19
535
537 """
538 Defines the structure of response packets received from the device.
539
540 C{Response} inherits from C{Command} as the first 16 bytes have the same structure.
541 """
542
543 SIZE = 32
544 rnumber = field(start=16, fmt=DWORD)
545 code = field(start=20, fmt=DWORD)
546 data_size = field(start=28, fmt=DWORD)
547
549 """ C{len(packet) == Response.SIZE} """
550 if len(packet) != Response.SIZE:
551 raise PacketError(str(self.__class__)[7:-2] + " packets must have exactly " + str(Response.SIZE) + " bytes not " + str(len(packet)))
552 Command.__init__(self, packet)
553 if self.number != 0x00001000:
554 raise PacketError("Response packets must have their number set to " + hex(0x00001000))
555
556 @apply
558 doc =\
559 """ The last 3 DWORDs (12 bytes) of data in this response packet. Returned as a list of unsigned integers. """
560 def fget(self):
561 return self.unpack(start=20, fmt="<III")
562
563 def fset(self, val):
564 self.pack(val, start=20, fmt="<III")
565
566 return property(**locals())
567
569
570 """ Defines the structure of response packets received during list (ll) queries. See L{PathQuery}. """
571
572 IS_FILE = 0xffffffd2
573 IS_INVALID = 0xfffffff9
574 IS_UNMOUNTED = 0xffffffc8
575 IS_EOL = 0xfffffffa
576 PATH_NOT_FOUND = 0xffffffd7
577
578 @apply
583 return property(**locals())
584
585 @apply
590 return property(**locals())
591
592 @apply
597 return property(**locals())
598
599 @apply
601 """ True iff queried path is unmounted (i.e. removed storage card) """
602 def fget(self):
603 return self.code == ListResponse.IS_UNMOUNTED
604 return property(**locals())
605
606 @apply
608 """ True iff there are no more items in the list """
609 def fget(self):
610 return self.code == ListResponse.IS_EOL
611 return property(**locals())
612
614 """ Defines the structure of packets sent to host via a bulk transfer (i.e., bulk reads) """
615
616 number = field(start=0, fmt=DWORD)
617 length = field(start=12, fmt=DWORD)
618
620 """ @param packet: C{len(packet)} S{>=} C{16} """
621 if "__len__" in dir(packet):
622 if len(packet) < 16 :
623 raise PacketError(str(self.__class__)[7:-2] + " packets must have a length of atleast 16 bytes")
624 elif packet < 16:
625 raise PacketError(str(self.__class__)[7:-2] + " packets must have a length of atleast 16 bytes")
626 TransferBuffer.__init__(self, packet)
627
628
630
631 """ Defines the structure of packets that contain size, date and permissions information about files/directories. """
632
633 file_size = field(start=16, fmt=DDWORD)
634 file_type = field(start=24, fmt=DWORD)
635 ctime = field(start=28, fmt=DWORD)
636 wtime = field(start=32, fmt=DWORD)
637 permissions = field(start=36, fmt=DWORD)
638
639 @apply
641 doc = """True if path points to a directory, False if it points to a file."""
642
643 def fget(self):
644 return (self.file_type == 2)
645
646 def fset(self, val):
647 if val: val = 2
648 else: val = 1
649 self.file_type = val
650
651 return property(**locals())
652
653
654 @apply
656 doc = """ Whether this file is readonly."""
657
658 def fget(self):
659 return self.unpack(start=36, fmt=DWORD)[0] != 0
660
661 def fset(self, val):
662 if val: val = 4
663 else: val = 0
664 self.pack(val, start=36, fmt=DWORD)
665
666 return property(**locals())
667
668
671
673
674 """ Defines the structure of packets that contain identifiers for queries. """
675
676 @apply
678 doc =\
679 """ The identifier. C{unsigned int} stored in 4 bytes at byte 16. Should be sent in commands asking for the next item in the list. """
680
681 def fget(self):
682 return self.unpack(start=16, fmt=DWORD)[0]
683
684 def fset(self, val):
685 self.pack(val, start=16, fmt=DWORD)
686
687 return property(**locals())
688
695
696
700
701
703 """ Defines the structure of packets that contain items in a list. """
704 name_length = field(start=20, fmt=DWORD)
705 name = stringfield(name_length, start=24)
706
707 @apply
709 doc =\
710 """ True if list item points to a directory, False if it points to a file. C{unsigned int} stored in 4 bytes at byte 16. """
711
712 def fget(self):
713 return (self.unpack(start=16, fmt=DWORD)[0] == 2)
714
715 def fset(self, val):
716 if val: val = 2
717 else: val = 1
718 self.pack(val, start=16, fmt=DWORD)
719
720 return property(**locals())
721