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
275 """ Query the free space available """
276 NUMBER = 0x53
284
285 @apply
287 doc =\
288 """ The length in bytes of the path to follow. C{unsigned int} stored at byte 16. """
289 def fget(self):
290 return self.unpack(start=16, fmt=DWORD)[0]
291
292 def fset(self, val):
293 self.pack(val, start=16, fmt=DWORD)
294
295 return property(**locals())
296
297 @apply
299 doc =\
300 """ The path. Stored as a string at byte 20. """
301
302 def fget(self):
303 return self.unpack(start=20, fmt="<"+str(self.path_length)+"s")[0]
304
305 def fset(self, val):
306 self.pack(val, start=20, fmt="<"+str(self.path_length)+"s")
307
308 return property(**locals())
309
310
323
324 @apply
326 doc =\
327 """ The length in bytes of the path to follow. C{unsigned int} stored at byte 16. """
328 def fget(self):
329 return self.unpack(start=16, fmt=DWORD)[0]
330
331 def fset(self, val):
332 self.pack(val, start=16, fmt=DWORD)
333
334 return property(**locals())
335
336 @apply
338 doc =\
339 """ The path. Stored as a string at byte 20. """
340
341 def fget(self):
342 return self.unpack(start=20, fmt="<"+str(self.path_length)+"s")[0]
343
344 def fset(self, val):
345 self.pack(val, start=20, fmt="<"+str(self.path_length)+"s")
346
347 return property(**locals())
348
350 """ The command that asks the device to send the next item in the list """
351 NUMBER = 0x35
355
357 """ Close a previously opened directory """
358 NUMBER = 0x34
362
363
365
366 """ A L{Command} whoose data section is 16 bytes long """
367
368 SIZE = 32
369
370 - def __init__(self, number=0x00, type=0x00, command=0x00):
381
382 @apply
384 doc =\
385 """
386 The command. Not sure why it is needed in addition to L{Command.number}.
387 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.
388 """
389 def fget(self):
390 return self.unpack(start=16, fmt="<"+str(self.length/4)+"I")
391
392 def fset(self, val):
393 if "__len__" not in dir(val): val = (val,)
394 start = 16
395 for command in val:
396 self.pack(command, start=start, fmt=DWORD)
397 start += struct.calcsize(DWORD)
398
399 return property(**locals())
400
401
403
404 """ Must be sent to device after a bulk read """
405
407 """ bulk_read_id is an integer, the id of the bulk read we are acknowledging. See L{Answer.id} """
408 LongCommand.__init__(self, number=0x1000, type=0x00, command=bulk_read_id)
409
411 """ The command used to ask for device information """
412 NUMBER=0x0101
417
419 """ File close command """
420 NUMBER = 0x11
423
425 """ File open command """
426 NUMBER = 0x10
427 READ = 0x00
428 WRITE = 0x01
437
438 @apply
440 doc =\
441 """ The file open mode. Is either L{FileOpen.READ} or L{FileOpen.WRITE}. C{unsigned int} stored at byte 16. """
442 def fget(self):
443 return self.unpack(start=16, fmt=DWORD)[0]
444
445 def fset(self, val):
446 self.pack(val, start=16, fmt=DWORD)
447
448 return property(**locals())
449
450 @apply
452 doc =\
453 """ The length in bytes of the path to follow. C{unsigned int} stored at byte 20. """
454 def fget(self):
455 return self.unpack(start=20, fmt=DWORD)[0]
456
457 def fset(self, val):
458 self.pack(val, start=20, fmt=DWORD)
459
460 return property(**locals())
461
462 @apply
464 doc =\
465 """ The path. Stored as a string at byte 24. """
466
467 def fget(self):
468 return self.unpack(start=24, fmt="<"+str(self.path_length)+"s")[0]
469
470 def fset(self, val):
471 self.pack(val, start=24, fmt="<"+str(self.path_length)+"s")
472
473 return property(**locals())
474
476 """ Command to read from an open file """
477 NUMBER = 0x16
479 """
480 @param id: File identifier returned by a L{FileOpen} command
481 @type id: C{unsigned int}
482 @param offset: Position in file at which to read
483 @type offset: C{unsigned long long}
484 @param size: number of bytes to read
485 @type size: C{unsigned int}
486 """
487 Command.__init__(self, 32)
488 self.number=FileRead.NUMBER
489 self.type = 0x01
490 self.length = 32
491 self.id = id
492 self.offset = offset
493 self.size = size
494
495 @apply
497 doc =\
498 """ The file ID returned by a FileOpen command. C{unsigned int} stored in 4 bytes at byte 16. """
499 def fget(self):
500 return self.unpack(start=16, fmt=DWORD)[0]
501
502 def fset(self, val):
503 self.pack(val, start=16, fmt=DWORD)
504
505 return property(**locals())
506
507 @apply
509 doc =\
510 """ offset in the file at which to read. C{unsigned long long} stored in 8 bytes at byte 20. """
511 def fget(self):
512 return self.unpack(start=20, fmt=DDWORD)[0]
513
514 def fset(self, val):
515 self.pack(val, start=20, fmt=DDWORD)
516
517 return property(**locals())
518
519 @apply
521 doc =\
522 """ The number of bytes to read. C{unsigned int} stored in 4 bytes at byte 28. """
523 def fget(self):
524 return self.unpack(start=28, fmt=DWORD)[0]
525
526 def fset(self, val):
527 self.pack(val, start=28, fmt=DWORD)
528
529 return property(**locals())
530
531
532
533
535
536 """
537 Defines structure of command that requests information about a path
538
539 >>> print prstypes.PathQuery("/test/path/", number=prstypes.PathQuery.PROPERTIES)
540 1800 0000 0100 0000 0000 0000 0f00 0000 ................
541 0b00 0000 2f74 6573 742f 7061 7468 2f ..../test/path/
542 """
543 NUMBER = 0x18
544
546 Command.__init__(self, 20 + len(path))
547 self.number=PathQuery.NUMBER
548 self.type = 0x01
549 self.length = 4 + len(path)
550 self.path_length = len(path)
551 self.path = path
552
553 @apply
555 doc =\
556 """ The length in bytes of the path to follow. C{unsigned int} stored at byte 16. """
557 def fget(self):
558 return self.unpack(start=16, fmt=DWORD)[0]
559
560 def fset(self, val):
561 self.pack(val, start=16, fmt=DWORD)
562
563 return property(**locals())
564
565 @apply
567 doc =\
568 """ The path. Stored as a string at byte 20. """
569
570 def fget(self):
571 return self.unpack(start=20, fmt="<"+str(self.path_length)+"s")[0]
572
573 def fset(self, val):
574 self.pack(val, start=20, fmt="<"+str(self.path_length)+"s")
575
576 return property(**locals())
577
578
580 """
581 Defines the structure of response packets received from the device.
582
583 C{Response} inherits from C{Command} as the first 16 bytes have the same structure.
584 """
585
586 SIZE = 32
587
589 """ C{len(packet) == Response.SIZE} """
590 if len(packet) != Response.SIZE:
591 raise PacketError(str(self.__class__)[7:-2] + " packets must have exactly " + str(Response.SIZE) + " bytes not " + str(len(packet)))
592 Command.__init__(self, packet)
593 if self.number != 0x00001000:
594 raise PacketError("Response packets must have their number set to " + hex(0x00001000))
595
596 @apply
598 doc =\
599 """
600 The response number. C{unsigned int} stored in 4 bytes at byte 16.
601
602 It will be the command number from a command that was sent to the device sometime before this response.
603 """
604 def fget(self):
605 return self.unpack(start=16, fmt=DWORD)[0]
606
607 def fset(self, val):
608 self.pack(val, start=16, fmt=DWORD)
609
610 return property(**locals())
611
612 @apply
614 doc =\
615 """ The last 3 DWORDs (12 bytes) of data in this response packet. Returned as a list of unsigned integers. """
616 def fget(self):
617 return self.unpack(start=20, fmt="<III")
618
619 def fset(self, val):
620 self.pack(val, start=20, fmt="<III")
621
622 return property(**locals())
623
625
626 """ Defines the structure of response packets received during list (ll) queries. See L{PathQuery}. """
627
628 IS_FILE = 0xffffffd2
629 IS_INVALID = 0xfffffff9
630 IS_UNMOUNTED = 0xffffffc8
631 IS_EOL = 0xfffffffa
632 PATH_NOT_FOUND = 0xffffffd7
633
634 @apply
636 doc =\
637 """ The response code. Used to indicate conditions like EOL/Error/IsFile etc. C{unsigned int} stored in 4 bytes at byte 20. """
638 def fget(self):
639 return self.unpack(start=20, fmt=DDWORD)[0]
640
641 def fset(self, val):
642 self.pack(val, start=20, fmt=DDWORD)
643
644 return property(**locals())
645
646 @apply
651 return property(**locals())
652
653 @apply
658 return property(**locals())
659
660 @apply
665 return property(**locals())
666
667 @apply
669 """ True iff queried path is unmounted (i.e. removed storage card) """
670 def fget(self):
671 return self.code == ListResponse.IS_UNMOUNTED
672 return property(**locals())
673
674 @apply
676 """ True iff there are no more items in the list """
677 def fget(self):
678 return self.code == ListResponse.IS_EOL
679 return property(**locals())
680
682 """ Defines the structure of packets sent to host via a bulk transfer (i.e., bulk reads) """
683
685 """ @param packet: C{len(packet)} S{>=} C{16} """
686 if len(packet) < 16 : raise PacketError(str(self.__class__)[7:-2] + " packets must have a length of atleast 16 bytes")
687 TransferBuffer.__init__(self, packet)
688
689 @apply
691 doc =\
692 """ The id of this bulk transfer packet. C{unsigned int} stored in 4 bytes at byte 0. """
693
694 def fget(self):
695 return self.unpack(start=0, fmt=DWORD)[0]
696
697 def fset(self, val):
698 self.pack(val, start=0, fmt=DWORD)
699
700 return property(**locals())
701
703
704 """ Defines the structure of packets that contain size, date and permissions information about files/directories. """
705
706 @apply
708 doc =\
709 """ The file size. C{unsigned long long} stored in 8 bytes at byte 16. """
710
711 def fget(self):
712 return self.unpack(start=16, fmt=DDWORD)[0]
713
714 def fset(self, val):
715 self.pack(val, start=16, fmt=DDWORD)
716
717 return property(**locals())
718
719 @apply
721 doc =\
722 """
723 True if path points to a directory, False if it points to a file. C{unsigned int} stored in 4 bytes at byte 24.
724
725 Value of 1 == file and 2 == dir
726 """
727
728 def fget(self):
729 return (self.unpack(start=24, fmt=DWORD)[0] == 2)
730
731 def fset(self, val):
732 if val: val = 2
733 else: val = 1
734 self.pack(val, start=24, fmt=DWORD)
735
736 return property(**locals())
737
738 @apply
740 doc =\
741 """ The creation time of this file/dir as an epoch (seconds since Jan 1970). C{unsigned int} stored in 4 bytes at byte 28. """
742
743 def fget(self):
744 return self.unpack(start=28, fmt=DWORD)[0]
745
746 def fset(self, val):
747 self.pack(val, start=28, fmt=DWORD)
748
749 return property(**locals())
750
751 @apply
753 doc =\
754 """ The modification time of this file/dir as an epoch (seconds since Jan 1970). C{unsigned int} stored in 4 bytes at byte 32"""
755
756 def fget(self):
757 return self.unpack(start=32, fmt=DWORD)[0]
758
759 def fset(self, val):
760 self.pack(val, start=32, fmt=DWORD)
761
762 return property(**locals())
763
764 @apply
766 doc =\
767 """
768 Whether this file is readonly. C{unsigned int} stored in 4 bytes at byte 36.
769
770 A value of 0 corresponds to read/write and 4 corresponds to read-only. The device doesn't send full permissions information.
771 """
772
773 def fget(self):
774 return self.unpack(start=36, fmt=DWORD)[0] != 0
775
776 def fset(self, val):
777 if val: val = 4
778 else: val = 0
779 self.pack(val, start=36, fmt=DWORD)
780
781 return property(**locals())
782
784
785 """ Defines the structure of packets that contain identifiers for queries. """
786
787 @apply
789 doc =\
790 """ 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. """
791
792 def fget(self):
793 return self.unpack(start=16, fmt=DWORD)[0]
794
795 def fset(self, val):
796 self.pack(val, start=16, fmt=DWORD)
797
798 return property(**locals())
799
801 """ Defines the structure of the packet containing information about the device """
802
803 @apply
805 """ The name of the device. Stored as a string in 32 bytes starting at byte 16. """
806 def fget(self):
807 src = self.unpack(start=16, fmt="<32s")[0]
808 return src[0:src.find('\x00')]
809 return property(**locals())
810
811 @apply
813 """ The device version. Stored as a string in 32 bytes starting at byte 48. """
814 def fget(self):
815 src = self.unpack(start=48, fmt="<32s")[0]
816 return src[0:src.find('\x00')]
817 return property(**locals())
818
819 @apply
821 """ Version of the software on the device. Stored as a string in 26 bytes starting at byte 80. """
822 def fget(self):
823 src = self.unpack(start=80, fmt="<26s")[0]
824 return src[0:src.find('\x00')]
825 return property(**locals())
826
827 @apply
829 """ Mime type served by tinyhttp?. Stored as a string in 32 bytes starting at byte 104. """
830 def fget(self):
831 src = self.unpack(start=104, fmt="<32s")[0]
832 return src[0:src.find('\x00')]
833 return property(**locals())
834
836 @apply
838 doc =\
839 """ The total space in bytes. C{unsigned long long} stored in 8 bytes at byte 24 """
840 def fget(self):
841 return self.unpack(start=24, fmt=DDWORD)[0]
842
843 def fset(self, val):
844 self.pack(val, start=24, fmt=DDWORD)
845
846 return property(**locals())
847
848 @apply
850 doc =\
851 """ The free space in bytes. C{unsigned long long} stored in 8 bytes at byte 32 """
852 def fget(self):
853 return self.unpack(start=32, fmt=DDWORD)[0]
854
855 def fset(self, val):
856 self.pack(val, start=32, fmt=DDWORD)
857
858 return property(**locals())
859
861
862 """ Defines the structure of packets that contain items in a list. """
863
864 @apply
866 doc =\
867 """ 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. """
868
869 def fget(self):
870 return (self.unpack(start=16, fmt=DWORD)[0] == 2)
871
872 def fset(self, val):
873 if val: val = 2
874 else: val = 1
875 self.pack(val, start=16, fmt=DWORD)
876
877 return property(**locals())
878
879 @apply
881 doc =\
882 """ The length in bytes of the list item to follow. C{unsigned int} stored in 4 bytes at byte 20 """
883 def fget(self):
884 return self.unpack(start=20, fmt=DWORD)[0]
885
886 def fset(self, val):
887 self.pack(val, start=20, fmt=DWORD)
888
889 return property(**locals())
890
891 @apply
893 doc =\
894 """ The name of the list item. Stored as an (ascii?) string at byte 24. """
895
896 def fget(self):
897 return self.unpack(start=24, fmt="<"+str(self.name_length)+"s")[0]
898
899 def fset(self, val):
900 self.pack(val, start=24, fmt="<"+str(self.name_length)+"s")
901
902 return property(**locals())
903