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 BYTE = "<B"
44 WORD = "<H"
45 DWORD = "<I"
46 DDWORD = "<Q"
47
48
50
51 """
52 Represents raw (unstructured) data packets sent over the usb bus.
53
54 C{TransferBuffer} is a wrapper around the tuples used by L{PyUSB<usb>} for communication.
55 It has convenience methods to read and write data from the underlying buffer. See
56 L{TransferBuffer.pack} and L{TransferBuffer.unpack}.
57 """
58
60 """
61 Create a L{TransferBuffer} from C{packet} or an empty buffer.
62
63 @type packet: integer or listable object
64 @param packet: If packet is a list, it is copied into the C{TransferBuffer} and then normalized (see L{TransferBuffer._normalize}).
65 If it is an integer, a zero buffer of that length is created.
66 """
67 if "__len__" in dir(packet):
68 list.__init__(self, list(packet))
69 self._normalize()
70 else: list.__init__(self, [0 for i in range(packet)])
71
75
79
81 """
82 Return a string representation of this buffer.
83
84 Packets are represented as hex strings, in 2-byte pairs, S{<=} 16 bytes to a line. An ASCII representation is included. For example::
85 0700 0100 0000 0000 0000 0000 0c00 0000 ................
86 0200 0000 0400 0000 4461 7461 ........Data
87 """
88 ans, ascii = ": ".rjust(10,"0"), ""
89 for i in range(0, len(self), 2):
90 for b in range(2):
91 try:
92 ans += TransferBuffer.phex(self[i+b])
93 ascii += chr(self[i+b]) if self[i+b] > 31 and self[i+b] < 127 else "."
94 except IndexError: break
95 ans = ans + " "
96 if (i+2)%16 == 0:
97 if i+2 < len(self):
98 ans += " " + ascii + "\n" + (TransferBuffer.phex(i+2)+": ").rjust(10, "0")
99 ascii = ""
100 last_line = ans[ans.rfind("\n")+1:]
101 padding = 50 - len(last_line)
102 ans += "".ljust(padding) + " " + ascii
103 return ans.strip()
104
106 """
107 Return decoded data from buffer.
108
109 @param fmt: See U{struct<http://docs.python.org/lib/module-struct.html>}
110 @param start: Position in buffer from which to decode
111 """
112 end = start + struct.calcsize(fmt)
113 return struct.unpack(fmt, "".join([ chr(i) for i in list.__getslice__(self, start, end) ]))
114
116 """
117 Encode C{val} and write it to buffer.
118
119 @param fmt: See U{struct<http://docs.python.org/lib/module-struct.html>}
120 @param start: Position in buffer at which to write encoded data
121 """
122 self[start:start+struct.calcsize(fmt)] = [ ord(i) for i in struct.pack(fmt, val) ]
123
125 """ Replace negative bytes in C{self} by 256 + byte """
126 for i in range(len(self)):
127 if self[i] < 0:
128 self[i] = 256 + self[i]
129
130 @classmethod
131 - def phex(cls, num):
132 """
133 Return the hex representation of num without the 0x prefix.
134
135 If the hex representation is only 1 digit it is padded to the left with a zero. Used in L{TransferBuffer.__str__}.
136 """
137 index, sign = 2, ""
138 if num < 0:
139 index, sign = 3, "-"
140 h=hex(num)[index:]
141 if len(h) < 2:
142 h = "0"+h
143 return sign + h
144
145
146
148
149 """ Defines the structure of command packets sent to the device. """
150
152 """
153 @param packet: len(packet) > 15 or packet > 15
154 """
155 if ("__len__" in dir(packet) and len(packet) < 16) or ("__len__" not in dir(packet) and packet < 16):
156 raise PacketError(str(self.__class__)[7:-2] + " packets must have length atleast 16")
157 TransferBuffer.__init__(self, packet)
158
159 @apply
161 doc =\
162 """
163 Command number. C{unsigned int} stored in 4 bytes at byte 0.
164
165 Observed command numbers are:
166 1. 0x00
167 Test bulk read
168 2. 0x01
169 End session
170 3. 0x0101
171 Ask for device information
172 4. 0x1000
173 Acknowledge
174 5. 0x107
175 Purpose unknown, occurs in the beginning of sessions duing command testing. Best guess is some sort of OK packet
176 6. 0x106
177 Purpose unknown, occurs in the beginning of sessions duing command testing. Best guess is some sort of OK packet
178 7. 0x18
179 Ask for information about a file
180 8. 0x33
181 Open directory for reading
182 9. 0x34
183 Close directory
184 10. 0x35
185 Ask for next item in the directory
186 11. 0x10
187 File open command
188 12. 0x11
189 File close command
190 13. 0x16
191 File read command
192 """
193 def fget(self):
194 return self.unpack(start=0, fmt=DWORD)[0]
195
196 def fset(self, val):
197 self.pack(val, start=0, fmt=DWORD)
198
199 return property(**locals())
200
201 @apply
203 doc =\
204 """ Command type. C{unsigned long long} stored in 8 bytes at byte 4. Known types 0x00, 0x01. Not sure what the type means. """
205 def fget(self):
206 return self.unpack(start=4, fmt=DDWORD)[0]
207
208 def fset(self, val):
209 self.pack(val, start=4, fmt=DDWORD)
210
211 return property(**locals())
212
213 @apply
215 doc =\
216 """ Length in bytes of the data part of the query. C{unsigned int} stored in 4 bytes at byte 12. """
217 def fget(self):
218 return self.unpack(start=12, fmt=DWORD)[0]
219
220 def fset(self, val):
221 self.pack(val, start=12, fmt=DWORD)
222
223 return property(**locals())
224
225 @apply
227 doc =\
228 """
229 The data part of this command. Returned/set as/by a TransferBuffer. Stored at byte 16.
230
231 Setting it by default changes self.length to the length of the new buffer. You may have to reset it to
232 the significant part of the buffer. You would normally use the C{command} property of L{ShortCommand} or L{LongCommand} instead.
233 """
234 def fget(self):
235 return self[16:]
236
237 def fset(self, buffer):
238 self[16:] = buffer
239 self.length = len(buffer)
240
241 return property(**locals())
242
243
245
246 """ A L{Command} whoose data section is 4 bytes long """
247
248 SIZE = 20
249
250 - def __init__(self, number=0x00, type=0x00, command=0x00):
261
262 @apply
264 doc =\
265 """ The command. Not sure why this is needed in addition to Command.number. C{unsigned int} 4 bytes long at byte 16. """
266 def fget(self):
267 return self.unpack(start=16, fmt=DWORD)[0]
268
269 def fset(self, val):
270 self.pack(val, start=16, fmt=DWORD)
271
272 return property(**locals())
273
286
287 @apply
289 doc =\
290 """ The length in bytes of the path to follow. C{unsigned int} stored at byte 16. """
291 def fget(self):
292 return self.unpack(start=16, fmt=DWORD)[0]
293
294 def fset(self, val):
295 self.pack(val, start=16, fmt=DWORD)
296
297 return property(**locals())
298
299 @apply
301 doc =\
302 """ The path. Stored as a string at byte 20. """
303
304 def fget(self):
305 return self.unpack(start=20, fmt="<"+str(self.path_length)+"s")[0]
306
307 def fset(self, val):
308 self.pack(val, start=20, fmt="<"+str(self.path_length)+"s")
309
310 return property(**locals())
311
313 """ The command that asks the device to send the next item in the list """
314 NUMBER = 0x35
318
320 """ Close a previously opened directory """
321 NUMBER = 0x34
325
326
328
329 """ A L{Command} whoose data section is 16 bytes long """
330
331 SIZE = 32
332
333 - def __init__(self, number=0x00, type=0x00, command=0x00):
344
345 @apply
347 doc =\
348 """
349 The command. Not sure why it is needed in addition to L{Command.number}.
350 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.
351 """
352 def fget(self):
353 return self.unpack(start=16, fmt="<"+str(self.length/4)+"I")
354
355 def fset(self, val):
356 if "__len__" not in dir(val): val = (val,)
357 start = 16
358 for command in val:
359 self.pack(command, start=start, fmt=DWORD)
360 start += struct.calcsize(DWORD)
361
362 return property(**locals())
363
364
366
367 """ Must be sent to device after a bulk read """
368
370 """ bulk_read_id is an integer, the id of the bulk read we are acknowledging. See L{Answer.id} """
371 LongCommand.__init__(self, number=0x1000, type=0x00, command=bulk_read_id)
372
374 """ The command used to ask for device information """
375 NUMBER=0x0101
380
382 """ File close command """
383 NUMBER = 0x11
386
388 """ File open command """
389 NUMBER = 0x10
390 READ = 0x00
391 WRITE = 0x01
400
401 @apply
403 doc =\
404 """ The file open mode. Is either L{FileOpen.READ} or L{FileOpen.WRITE}. C{unsigned int} stored at byte 16. """
405 def fget(self):
406 return self.unpack(start=16, fmt=DWORD)[0]
407
408 def fset(self, val):
409 self.pack(val, start=16, fmt=DWORD)
410
411 return property(**locals())
412
413 @apply
415 doc =\
416 """ The length in bytes of the path to follow. C{unsigned int} stored at byte 20. """
417 def fget(self):
418 return self.unpack(start=20, fmt=DWORD)[0]
419
420 def fset(self, val):
421 self.pack(val, start=20, fmt=DWORD)
422
423 return property(**locals())
424
425 @apply
427 doc =\
428 """ The path. Stored as a string at byte 24. """
429
430 def fget(self):
431 return self.unpack(start=24, fmt="<"+str(self.path_length)+"s")[0]
432
433 def fset(self, val):
434 self.pack(val, start=24, fmt="<"+str(self.path_length)+"s")
435
436 return property(**locals())
437
439 """ Command to read from an open file """
440 NUMBER = 0x16
442 """
443 @param id: File identifier returned by a L{FileOpen} command
444 @type id: C{unsigned int}
445 @param offset: Position in file at which to read
446 @type offset: C{unsigned long long}
447 @param size: number of bytes to read
448 @type size: C{unsigned int}
449 """
450 Command.__init__(self, 32)
451 self.number=FileRead.NUMBER
452 self.type = 0x01
453 self.length = 32
454 self.id = id
455 self.offset = offset
456 self.size = size
457
458 @apply
460 doc =\
461 """ The file ID returned by a FileOpen command. C{unsigned int} stored in 4 bytes at byte 16. """
462 def fget(self):
463 return self.unpack(start=16, fmt=DWORD)[0]
464
465 def fset(self, val):
466 self.pack(val, start=16, fmt=DWORD)
467
468 return property(**locals())
469
470 @apply
472 doc =\
473 """ offset in the file at which to read. C{unsigned long long} stored in 8 bytes at byte 20. """
474 def fget(self):
475 return self.unpack(start=20, fmt=DDWORD)[0]
476
477 def fset(self, val):
478 self.pack(val, start=20, fmt=DDWORD)
479
480 return property(**locals())
481
482 @apply
484 doc =\
485 """ The number of bytes to read. C{unsigned int} stored in 4 bytes at byte 28. """
486 def fget(self):
487 return self.unpack(start=28, fmt=DWORD)[0]
488
489 def fset(self, val):
490 self.pack(val, start=28, fmt=DWORD)
491
492 return property(**locals())
493
494
495
496
498
499 """
500 Defines structure of command that requests information about a path
501
502 >>> print prstypes.PathQuery("/test/path/", number=prstypes.PathQuery.PROPERTIES)
503 1800 0000 0100 0000 0000 0000 0f00 0000 ................
504 0b00 0000 2f74 6573 742f 7061 7468 2f ..../test/path/
505 """
506 NUMBER = 0x18
507
509 Command.__init__(self, 20 + len(path))
510 self.number=PathQuery.NUMBER
511 self.type = 0x01
512 self.length = 4 + len(path)
513 self.path_length = len(path)
514 self.path = path
515
516 @apply
518 doc =\
519 """ The length in bytes of the path to follow. C{unsigned int} stored at byte 16. """
520 def fget(self):
521 return self.unpack(start=16, fmt=DWORD)[0]
522
523 def fset(self, val):
524 self.pack(val, start=16, fmt=DWORD)
525
526 return property(**locals())
527
528 @apply
530 doc =\
531 """ The path. Stored as a string at byte 20. """
532
533 def fget(self):
534 return self.unpack(start=20, fmt="<"+str(self.path_length)+"s")[0]
535
536 def fset(self, val):
537 self.pack(val, start=20, fmt="<"+str(self.path_length)+"s")
538
539 return property(**locals())
540
541
543 """
544 Defines the structure of response packets received from the device.
545
546 C{Response} inherits from C{Command} as the first 16 bytes have the same structure.
547 """
548
549 SIZE = 32
550
552 """ C{len(packet) == Response.SIZE} """
553 if len(packet) != Response.SIZE:
554 raise PacketError(str(self.__class__)[7:-2] + " packets must have exactly " + str(Response.SIZE) + " bytes not " + str(len(packet)))
555 Command.__init__(self, packet)
556 if self.number != 0x00001000:
557 raise PacketError("Response packets must have their number set to " + hex(0x00001000))
558
559 @apply
561 doc =\
562 """
563 The response number. C{unsigned int} stored in 4 bytes at byte 16.
564
565 It will be the command number from a command that was sent to the device sometime before this response.
566 """
567 def fget(self):
568 return self.unpack(start=16, fmt=DWORD)[0]
569
570 def fset(self, val):
571 self.pack(val, start=16, fmt=DWORD)
572
573 return property(**locals())
574
575 @apply
577 doc =\
578 """ The last 3 DWORDs (12 bytes) of data in this response packet. Returned as a list of unsigned integers. """
579 def fget(self):
580 return self.unpack(start=20, fmt="<III")
581
582 def fset(self, val):
583 self.pack(val, start=20, fmt="<III")
584
585 return property(**locals())
586
588
589 """ Defines the structure of response packets received during list (ll) queries. See L{PathQuery}. """
590
591 IS_FILE = 0xffffffd2
592 IS_INVALID = 0xfffffff9
593 IS_UNMOUNTED = 0xffffffc8
594 IS_EOL = 0xfffffffa
595 PATH_NOT_FOUND = 0xffffffd7
596
597 @apply
599 doc =\
600 """ The response code. Used to indicate conditions like EOL/Error/IsFile etc. C{unsigned int} stored in 4 bytes at byte 20. """
601 def fget(self):
602 return self.unpack(start=20, fmt=DDWORD)[0]
603
604 def fset(self, val):
605 self.pack(val, start=20, fmt=DDWORD)
606
607 return property(**locals())
608
609 @apply
614 return property(**locals())
615
616 @apply
621 return property(**locals())
622
623 @apply
628 return property(**locals())
629
630 @apply
632 """ True iff queried path is unmounted (i.e. removed storage card) """
633 def fget(self):
634 return self.code == ListResponse.IS_UNMOUNTED
635 return property(**locals())
636
637 @apply
639 """ True iff there are no more items in the list """
640 def fget(self):
641 return self.code == ListResponse.IS_EOL
642 return property(**locals())
643
645 """ Defines the structure of packets sent to host via a bulk transfer (i.e., bulk reads) """
646
648 """ @param packet: C{len(packet)} S{>=} C{16} """
649 if len(packet) < 16 : raise PacketError(str(self.__class__)[7:-2] + " packets must have a length of atleast 16 bytes")
650 TransferBuffer.__init__(self, packet)
651
652 @apply
654 doc =\
655 """ The id of this bulk transfer packet. C{unsigned int} stored in 4 bytes at byte 0. """
656
657 def fget(self):
658 return self.unpack(start=0, fmt=DWORD)[0]
659
660 def fset(self, val):
661 self.pack(val, start=0, fmt=DWORD)
662
663 return property(**locals())
664
666
667 """ Defines the structure of packets that contain size, date and permissions information about files/directories. """
668
669 @apply
671 doc =\
672 """ The file size. C{unsigned long long} stored in 8 bytes at byte 16. """
673
674 def fget(self):
675 return self.unpack(start=16, fmt=DDWORD)[0]
676
677 def fset(self, val):
678 self.pack(val, start=16, fmt=DDWORD)
679
680 return property(**locals())
681
682 @apply
684 doc =\
685 """
686 True if path points to a directory, False if it points to a file. C{unsigned int} stored in 4 bytes at byte 24.
687
688 Value of 1 == file and 2 == dir
689 """
690
691 def fget(self):
692 return (self.unpack(start=24, fmt=DWORD)[0] == 2)
693
694 def fset(self, val):
695 if val: val = 2
696 else: val = 1
697 self.pack(val, start=24, fmt=DWORD)
698
699 return property(**locals())
700
701 @apply
703 doc =\
704 """ The creation time of this file/dir as an epoch (seconds since Jan 1970). C{unsigned int} stored in 4 bytes at byte 28. """
705
706 def fget(self):
707 return self.unpack(start=28, fmt=DWORD)[0]
708
709 def fset(self, val):
710 self.pack(val, start=28, fmt=DWORD)
711
712 return property(**locals())
713
714 @apply
716 doc =\
717 """ The modification time of this file/dir as an epoch (seconds since Jan 1970). C{unsigned int} stored in 4 bytes at byte 32"""
718
719 def fget(self):
720 return self.unpack(start=32, fmt=DWORD)[0]
721
722 def fset(self, val):
723 self.pack(val, start=32, fmt=DWORD)
724
725 return property(**locals())
726
727 @apply
729 doc =\
730 """
731 Whether this file is readonly. C{unsigned int} stored in 4 bytes at byte 36.
732
733 A value of 0 corresponds to read/write and 4 corresponds to read-only. The device doesn't send full permissions information.
734 """
735
736 def fget(self):
737 return self.unpack(start=36, fmt=DWORD)[0] != 0
738
739 def fset(self, val):
740 if val: val = 4
741 else: val = 0
742 self.pack(val, start=36, fmt=DWORD)
743
744 return property(**locals())
745
747
748 """ Defines the structure of packets that contain identifiers for queries. """
749
750 @apply
752 doc =\
753 """ 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. """
754
755 def fget(self):
756 return self.unpack(start=16, fmt=DWORD)[0]
757
758 def fset(self, val):
759 self.pack(val, start=16, fmt=DWORD)
760
761 return property(**locals())
762
764 """ Defines the structure of the packet containing information about the device """
765
766 @apply
768 """ The name of the device. Stored as a string in 32 bytes starting at byte 16. """
769 def fget(self):
770 src = self.unpack(start=16, fmt="<32s")[0]
771 return src[0:src.find('\x00')]
772 return property(**locals())
773
774 @apply
776 """ The device version. Stored as a string in 32 bytes starting at byte 48. """
777 def fget(self):
778 src = self.unpack(start=48, fmt="<32s")[0]
779 return src[0:src.find('\x00')]
780 return property(**locals())
781
782 @apply
784 """ Version of the software on the device. Stored as a string in 26 bytes starting at byte 80. """
785 def fget(self):
786 src = self.unpack(start=80, fmt="<26s")[0]
787 return src[0:src.find('\x00')]
788 return property(**locals())
789
790 @apply
792 """ Mime type served by tinyhttp?. Stored as a string in 32 bytes starting at byte 104. """
793 def fget(self):
794 src = self.unpack(start=104, fmt="<32s")[0]
795 return src[0:src.find('\x00')]
796 return property(**locals())
797
799
800 """ Defines the structure of packets that contain items in a list. """
801
802 @apply
804 doc =\
805 """ 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. """
806
807 def fget(self):
808 return (self.unpack(start=16, fmt=DWORD)[0] == 2)
809
810 def fset(self, val):
811 if val: val = 2
812 else: val = 1
813 self.pack(val, start=16, fmt=DWORD)
814
815 return property(**locals())
816
817 @apply
819 doc =\
820 """ The length in bytes of the list item to follow. C{unsigned int} stored in 4 bytes at byte 20 """
821 def fget(self):
822 return self.unpack(start=20, fmt=DWORD)[0]
823
824 def fset(self, val):
825 self.pack(val, start=20, fmt=DWORD)
826
827 return property(**locals())
828
829 @apply
831 doc =\
832 """ The name of the list item. Stored as an (ascii?) string at byte 24. """
833
834 def fget(self):
835 return self.unpack(start=24, fmt="<"+str(self.name_length)+"s")[0]
836
837 def fset(self, val):
838 self.pack(val, start=24, fmt="<"+str(self.name_length)+"s")
839
840 return property(**locals())
841