mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-11-01 19:17:02 -04:00
747 lines
25 KiB
Python
Executable File
747 lines
25 KiB
Python
Executable File
## Copyright (C) 2006 Kovid Goyal kovid@kovidgoyal.net
|
|
## This program is free software; you can redistribute it and/or modify
|
|
## it under the terms of the GNU General Public License as published by
|
|
## the Free Software Foundation; either version 2 of the License, or
|
|
## (at your option) any later version.
|
|
##
|
|
## This program is distributed in the hope that it will be useful,
|
|
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
## GNU General Public License for more details.
|
|
##
|
|
## You should have received a copy of the GNU General Public License along
|
|
## with this program; if not, write to the Free Software Foundation, Inc.,
|
|
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
|
|
"""
|
|
Defines the structure of packets that are sent to/received from the device.
|
|
|
|
Packet structure is defined using classes and inheritance. Each class is a view that imposes
|
|
structure on the underlying data buffer. The data buffer is encoded in little-endian format, but you don't
|
|
have to worry about that if you are using the classes. The classes have instance variables with getter/setter functions defined
|
|
to take care of the encoding/decoding. The classes are intended to mimic C structs.
|
|
|
|
There are three kinds of packets. L{Commands<Command>}, L{Responses<Response>}, and L{Answers<Answer>}.
|
|
C{Commands} are sent to the device on the control bus, C{Responses} are received from the device,
|
|
also on the control bus. C{Answers} and their sub-classes represent data packets sent to/received from
|
|
the device via bulk transfers.
|
|
|
|
Commands are organized as follows: G{classtree Command}
|
|
|
|
You will typically only use sub-classes of Command.
|
|
|
|
Responses are organized as follows: G{classtree Response}
|
|
|
|
Responses inherit Command as they share header structure.
|
|
|
|
Answers are organized as follows: G{classtree Answer}
|
|
"""
|
|
|
|
import struct, time
|
|
from errors import PacketError
|
|
|
|
DWORD = "<I" #: Unsigned integer little endian encoded in 4 bytes
|
|
DDWORD = "<Q" #: Unsigned long long little endian encoded in 8 bytes
|
|
|
|
|
|
class PathResponseCodes(object):
|
|
""" Known response commands to path related commands """
|
|
NOT_FOUND = 0xffffffd7
|
|
INVALID = 0xfffffff9
|
|
IS_FILE = 0xffffffd2
|
|
HAS_CHILDREN = 0xffffffcc
|
|
|
|
|
|
class TransferBuffer(list):
|
|
|
|
"""
|
|
Represents raw (unstructured) data packets sent over the usb bus.
|
|
|
|
C{TransferBuffer} is a wrapper around the tuples used by L{PyUSB<usb>} for communication.
|
|
It has convenience methods to read and write data from the underlying buffer. See
|
|
L{TransferBuffer.pack} and L{TransferBuffer.unpack}.
|
|
"""
|
|
|
|
def __init__(self, packet):
|
|
"""
|
|
Create a L{TransferBuffer} from C{packet} or an empty buffer.
|
|
|
|
@type packet: integer or listable object
|
|
@param packet: If packet is a list, it is copied into the C{TransferBuffer} and then normalized (see L{TransferBuffer._normalize}).
|
|
If it is an integer, a zero buffer of that length is created.
|
|
"""
|
|
if "__len__" in dir(packet):
|
|
list.__init__(self, list(packet))
|
|
self._normalize()
|
|
else: list.__init__(self, [0 for i in range(packet)])
|
|
|
|
def __add__(self, tb):
|
|
""" Return a TransferBuffer rather than a list as the sum """
|
|
return TransferBuffer(list.__add__(self, tb))
|
|
|
|
def __getslice__(self, start, end):
|
|
""" Return a TransferBuffer rather than a list as the slice """
|
|
return TransferBuffer(list.__getslice__(self, start, end))
|
|
|
|
def __str__(self):
|
|
"""
|
|
Return a string representation of this buffer.
|
|
|
|
Packets are represented as hex strings, in 2-byte pairs, S{<=} 16 bytes to a line. An ASCII representation is included. For example::
|
|
0700 0100 0000 0000 0000 0000 0c00 0000 ................
|
|
0200 0000 0400 0000 4461 7461 ........Data
|
|
"""
|
|
ans, ascii = ": ".rjust(10,"0"), ""
|
|
for i in range(0, len(self), 2):
|
|
for b in range(2):
|
|
try:
|
|
ans += TransferBuffer.phex(self[i+b])
|
|
ascii += chr(self[i+b]) if self[i+b] > 31 and self[i+b] < 127 else "."
|
|
except IndexError: break
|
|
ans = ans + " "
|
|
if (i+2)%16 == 0:
|
|
if i+2 < len(self):
|
|
ans += " " + ascii + "\n" + (TransferBuffer.phex(i+2)+": ").rjust(10, "0")
|
|
ascii = ""
|
|
last_line = ans[ans.rfind("\n")+1:]
|
|
padding = 50 - len(last_line)
|
|
ans += "".ljust(padding) + " " + ascii
|
|
return ans.strip()
|
|
|
|
def unpack(self, fmt=DWORD, start=0):
|
|
"""
|
|
Return decoded data from buffer.
|
|
|
|
@param fmt: See U{struct<http://docs.python.org/lib/module-struct.html>}
|
|
@param start: Position in buffer from which to decode
|
|
"""
|
|
end = start + struct.calcsize(fmt)
|
|
return struct.unpack(fmt, "".join([ chr(i) for i in list.__getslice__(self, start, end) ]))
|
|
|
|
def pack(self, val, fmt=DWORD, start=0):
|
|
"""
|
|
Encode C{val} and write it to buffer.
|
|
|
|
@param fmt: See U{struct<http://docs.python.org/lib/module-struct.html>}
|
|
@param start: Position in buffer at which to write encoded data
|
|
"""
|
|
self[start:start+struct.calcsize(fmt)] = [ ord(i) for i in struct.pack(fmt, val) ]
|
|
|
|
def _normalize(self):
|
|
""" Replace negative bytes in C{self} by 256 + byte """
|
|
for i in range(len(self)):
|
|
if self[i] < 0:
|
|
self[i] = 256 + self[i]
|
|
|
|
@classmethod
|
|
def phex(cls, num):
|
|
"""
|
|
Return the hex representation of num without the 0x prefix.
|
|
|
|
If the hex representation is only 1 digit it is padded to the left with a zero. Used in L{TransferBuffer.__str__}.
|
|
"""
|
|
index, sign = 2, ""
|
|
if num < 0:
|
|
index, sign = 3, "-"
|
|
h=hex(num)[index:]
|
|
if len(h) < 2:
|
|
h = "0"+h
|
|
return sign + h
|
|
|
|
|
|
class field(object):
|
|
""" A U{Descriptor<http://www.cafepy.com/article/python_attributes_and_methods/python_attributes_and_methods.html>}, that implements access
|
|
to protocol packets in a human readable way.
|
|
"""
|
|
def __init__(self, start=16, fmt=DWORD):
|
|
"""
|
|
@param start: The byte at which this field is stored in the buffer
|
|
@param fmt: The packing format for this field. See U{struct<http://docs.python.org/lib/module-struct.html>}.
|
|
"""
|
|
self._fmt, self._start = fmt, start
|
|
|
|
def __get__(self, obj, typ=None):
|
|
return obj.unpack(start=self._start, fmt=self._fmt)[0]
|
|
|
|
def __set__(self, obj, val):
|
|
obj.pack(val, start=self._start, fmt=self._fmt)
|
|
|
|
def __repr__(self):
|
|
typ = ""
|
|
if self._fmt == DWORD: typ = "unsigned int"
|
|
if self._fmt == DDWORD: typ = "unsigned long long"
|
|
return "An " + typ + " stored in " + str(struct.calcsize(self._fmt)) + " bytes starting at byte " + str(self._start)
|
|
|
|
class stringfield(object):
|
|
""" A field storing a variable length string. """
|
|
def __init__(self, length_field, start=16):
|
|
"""
|
|
@param length_field: A U{Descriptor<http://www.cafepy.com/article/python_attributes_and_methods/python_attributes_and_methods.html>}
|
|
that returns the length of the string.
|
|
@param start: The byte at which this field is stored in the buffer
|
|
"""
|
|
self._length_field = length_field
|
|
self._start = start
|
|
|
|
def __get__(self, obj, typ=None):
|
|
length = str(self._length_field.__get__(obj))
|
|
return obj.unpack(start=self._start, fmt="<"+length+"s")[0]
|
|
|
|
def __set__(self, obj, val):
|
|
if val.__class__.__name__ != 'str': val = str(val)
|
|
obj.pack(val, start=self._start, fmt="<"+str(len(val))+"s")
|
|
|
|
def __repr__(self):
|
|
return "A string starting at byte " + str(self._start)
|
|
|
|
class Command(TransferBuffer):
|
|
|
|
""" Defines the structure of command packets sent to the device. """
|
|
|
|
number = field(start=0, fmt=DWORD)
|
|
"""
|
|
Command number. C{unsigned int} stored in 4 bytes at byte 0.
|
|
|
|
Command numbers are:
|
|
0 GetUsbProtocolVersion
|
|
1 ReqEndSession
|
|
10 FskFileOpen
|
|
11 FskFileClose
|
|
12 FskGetSize
|
|
13 FskSetSize
|
|
14 FskFileSetPosition
|
|
15 FskGetPosition
|
|
16 FskFileRead
|
|
17 FskFileWrite
|
|
18 FskFileGetFileInfo
|
|
19 FskFileSetFileInfo
|
|
1A FskFileCreate
|
|
1B FskFileDelete
|
|
1C FskFileRename
|
|
30 FskFileCreateDirectory
|
|
31 FskFileDeleteDirectory
|
|
32 FskFileRenameDirectory
|
|
33 FskDirectoryIteratorNew
|
|
34 FskDirectoryIteratorDispose
|
|
35 FskDirectoryIteratorGetNext
|
|
52 FskVolumeGetInfo
|
|
53 FskVolumeGetInfoFromPath
|
|
80 FskFileTerminate
|
|
100 ConnectDevice
|
|
101 GetProperty
|
|
102 GetMediaInfo
|
|
103 GetFreeSpace
|
|
104 SetTime
|
|
105 DeviceBeginEnd
|
|
106 UnlockDevice
|
|
107 SetBulkSize
|
|
110 GetHttpRequest
|
|
111 SetHttpRespponse
|
|
112 Needregistration
|
|
114 GetMarlinState
|
|
200 ReqDiwStart
|
|
201 SetDiwPersonalkey
|
|
202 GetDiwPersonalkey
|
|
203 SetDiwDhkey
|
|
204 GetDiwDhkey
|
|
205 SetDiwChallengeserver
|
|
206 GetDiwChallengeserver
|
|
207 GetDiwChallengeclient
|
|
208 SetDiwChallengeclient
|
|
209 GetDiwVersion
|
|
20A SetDiwWriteid
|
|
20B GetDiwWriteid
|
|
20C SetDiwSerial
|
|
20D GetDiwModel
|
|
20C SetDiwSerial
|
|
20E GetDiwDeviceid
|
|
20F GetDiwSerial
|
|
210 ReqDiwCheckservicedata
|
|
211 ReqDiwCheckiddata
|
|
212 ReqDiwCheckserialdata
|
|
213 ReqDiwFactoryinitialize
|
|
214 GetDiwMacaddress
|
|
215 ReqDiwTest
|
|
216 ReqDiwDeletekey
|
|
300 UpdateChangemode
|
|
301 UpdateDeletePartition
|
|
302 UpdateCreatePartition
|
|
303 UpdateCreatePartitionWithImage
|
|
304 UpdateGetPartitionSize
|
|
"""
|
|
|
|
type = field(start=4, fmt=DDWORD) #: Known types are 0x00 and 0x01. Acknowledge commands are always type 0x00
|
|
|
|
length = field(start=12, fmt=DWORD) #: Length of the data part of this packet
|
|
|
|
@apply
|
|
def data():
|
|
doc =\
|
|
"""
|
|
The data part of this command. Returned/set as/by a TransferBuffer. Stored at byte 16.
|
|
|
|
Setting it by default changes self.length to the length of the new buffer. You may have to reset it to
|
|
the significant part of the buffer. You would normally use the C{command} property of L{ShortCommand} or L{LongCommand} instead.
|
|
"""
|
|
def fget(self):
|
|
return self[16:]
|
|
|
|
def fset(self, buffer):
|
|
self[16:] = buffer
|
|
self.length = len(buffer)
|
|
|
|
return property(**locals())
|
|
|
|
def __init__(self, packet):
|
|
"""
|
|
@param packet: len(packet) > 15 or packet > 15
|
|
"""
|
|
if ("__len__" in dir(packet) and len(packet) < 16) or ("__len__" not in dir(packet) and packet < 16):
|
|
raise PacketError(str(self.__class__)[7:-2] + " packets must have length atleast 16")
|
|
TransferBuffer.__init__(self, packet)
|
|
|
|
|
|
class SetTime(Command):
|
|
"""
|
|
Set time on device. All fields refer to time in the GMT time zone.
|
|
@todo: figure out what the 4 bytes starting at byte 16 are for
|
|
"""
|
|
NUMBER = 0x104
|
|
|
|
unknown = field(start=0x10, fmt=DWORD) #: Haven't figured out what this is for. Seems to always be set to 4294966816L
|
|
year = field(start=0x14, fmt=DWORD) #: year e.g. 2006
|
|
month = field(start=0x18, fmt=DWORD) #: month 1-12
|
|
day = field(start=0x1c, fmt=DWORD) #: day 1-31
|
|
hour = field(start=0x20, fmt=DWORD) #: hour 0-23
|
|
minute = field(start=0x24, fmt=DWORD) #: minute 0-59
|
|
second = field(start=0x28, fmt=DWORD) #: second 0-59
|
|
|
|
def __init__(self, t=None):
|
|
""" @param t: time as an epoch """
|
|
self.number = SetTime.NUMBER
|
|
self.type = 0x01
|
|
self.length = 0x1c
|
|
self.unknown = 4294966816L
|
|
if not t: t = time.time()
|
|
t = time.gmtime(t)
|
|
self.year = t[0]
|
|
self.month = t[1]
|
|
self.day = t[2]
|
|
self.hour = t[3]
|
|
self.minute = t[4]
|
|
self.second = t[5] if t[5] < 60 else 59 # Hack you should actually update the entire time tree is second is > 59
|
|
|
|
|
|
class ShortCommand(Command):
|
|
|
|
""" A L{Command} whoose data section is 4 bytes long """
|
|
|
|
SIZE = 20 #: Packet size in bytes
|
|
command = field(start=16, fmt=DWORD) #: Usually carries additional information
|
|
|
|
def __init__(self, number=0x00, type=0x00, command=0x00):
|
|
"""
|
|
@param number: L{Command.number}
|
|
@param type: L{Command.type}
|
|
@param command: L{ShortCommand.command}
|
|
"""
|
|
Command.__init__(self, ShortCommand.SIZE)
|
|
self.number = number
|
|
self.type = type
|
|
self.length = 4
|
|
self.command = command
|
|
|
|
class DirRead(ShortCommand):
|
|
""" The command that asks the device to send the next item in the list """
|
|
NUMBER = 0x35 #: Command number
|
|
def __init__(self, id):
|
|
""" @param id: The identifier returned as a result of a L{DirOpen} command """
|
|
ShortCommand.__init__(self, number=DirRead.NUMBER, type=0x01, command=id)
|
|
|
|
class DirClose(ShortCommand):
|
|
""" Close a previously opened directory """
|
|
NUMBER = 0x34 #: Command number
|
|
def __init__(self, id):
|
|
""" @param id: The identifier returned as a result of a L{DirOpen} command """
|
|
ShortCommand.__init__(self, number=DirClose.NUMBER, type=0x01, command=id)
|
|
|
|
class EndSession(ShortCommand):
|
|
""" Ask device to change status to 'USB connected' i.e., tell the device that the present sequence of commands is complete """
|
|
NUMBER=0x1 #: Command number
|
|
def __init__(self):
|
|
ShortCommand.__init__(self, number=EndSession.NUMBER, type=0x01, command=0x00)
|
|
|
|
class GetUSBProtocolVersion(ShortCommand):
|
|
""" Get USB Protocol version used by device """
|
|
NUMBER=0x0 #: Command number
|
|
def __init__(self):
|
|
ShortCommand.__init__(self, number=GetUSBProtocolVersion.NUMBER, type=0x01, command=0x00)
|
|
|
|
class SetBulkSize(ShortCommand):
|
|
NUMBER = 0x107 #: Command number
|
|
def __init__(self, size=0x028000):
|
|
ShortCommand.__init__(self, number=SetBulkSize.NUMBER, type=0x01, command=size)
|
|
|
|
class UnlockDevice(ShortCommand):
|
|
NUMBER = 0x106 #: Command number
|
|
def __init__(self, key=0x312d):
|
|
ShortCommand.__init__(self, number=UnlockDevice.NUMBER, type=0x01, command=key)
|
|
|
|
class LongCommand(Command):
|
|
|
|
""" A L{Command} whoose data section is 16 bytes long """
|
|
|
|
SIZE = 32 #: Size in bytes of C{LongCommand} packets
|
|
|
|
def __init__(self, number=0x00, type=0x00, command=0x00):
|
|
"""
|
|
@param number: L{Command.number}
|
|
@param type: L{Command.type}
|
|
@param command: L{LongCommand.command}
|
|
"""
|
|
Command.__init__(self, LongCommand.SIZE)
|
|
self.number = number
|
|
self.type = type
|
|
self.length = 16
|
|
self.command = command
|
|
|
|
@apply
|
|
def command():
|
|
doc =\
|
|
"""
|
|
Usually carries extra information needed for the command
|
|
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.
|
|
"""
|
|
def fget(self):
|
|
return self.unpack(start=16, fmt="<"+str(self.length/4)+"I")
|
|
|
|
def fset(self, val):
|
|
if "__len__" not in dir(val): val = (val,)
|
|
start = 16
|
|
for command in val:
|
|
self.pack(command, start=start, fmt=DWORD)
|
|
start += struct.calcsize(DWORD)
|
|
|
|
return property(**locals())
|
|
|
|
class PathCommand(Command):
|
|
""" Abstract class that defines structure common to all path related commands. """
|
|
|
|
path_length = field(start=16, fmt=DWORD) #: Length of the path to follow
|
|
path = stringfield(path_length, start=20) #: The path this query is about
|
|
def __init__(self, path, number, path_len_at_byte=16):
|
|
Command.__init__(self, path_len_at_byte+4+len(path))
|
|
self.path_length = len(path)
|
|
self.path = path
|
|
self.type = 0x01
|
|
self.length = len(self)-16
|
|
self.number = number
|
|
|
|
class FreeSpaceQuery(PathCommand):
|
|
""" Query the free space available """
|
|
NUMBER = 0x53 #; Command number
|
|
def __init__(self, path):
|
|
PathCommand.__init__(self, path, FreeSpaceQuery.NUMBER)
|
|
|
|
class DirCreate(PathCommand):
|
|
""" Create a directory """
|
|
NUMBER = 0x30
|
|
def __init__(self, path):
|
|
PathCommand.__init__(self, path, DirCreate.NUMBER)
|
|
|
|
class DirOpen(PathCommand):
|
|
""" Open a directory for reading its contents """
|
|
NUMBER = 0x33 #: Command number
|
|
def __init__(self, path):
|
|
PathCommand.__init__(self, path, DirOpen.NUMBER)
|
|
|
|
|
|
class AcknowledgeBulkRead(LongCommand):
|
|
""" Must be sent to device after a bulk read """
|
|
def __init__(self, bulk_read_id):
|
|
""" bulk_read_id is an integer, the id of the bulk read we are acknowledging. See L{Answer.id} """
|
|
LongCommand.__init__(self, number=0x1000, type=0x00, command=bulk_read_id)
|
|
|
|
class DeviceInfoQuery(Command):
|
|
""" The command used to ask for device information """
|
|
NUMBER=0x0101 #: Command number
|
|
def __init__(self):
|
|
Command.__init__(self, 16)
|
|
self.number=DeviceInfoQuery.NUMBER
|
|
self.type=0x01
|
|
|
|
class FileClose(ShortCommand):
|
|
""" File close command """
|
|
NUMBER = 0x11 #: Command number
|
|
def __init__(self, id):
|
|
ShortCommand.__init__(self, number=FileClose.NUMBER, type=0x01, command=id)
|
|
|
|
class FileCreate(PathCommand):
|
|
""" Create a file """
|
|
NUMBER=0x1a #: Command number
|
|
def __init__(self, path):
|
|
PathCommand.__init__(self, path, FileCreate.NUMBER)
|
|
|
|
class FileDelete(PathCommand):
|
|
""" Delete a file """
|
|
NUMBER=0x1B
|
|
def __init__(self, path):
|
|
PathCommand.__init__(self, path, FileDelete.NUMBER)
|
|
|
|
class DirDelete(PathCommand):
|
|
""" Delete a directory """
|
|
NUMBER=0x31
|
|
def __init__(self, path):
|
|
PathCommand.__init__(self, path, DirDelete.NUMBER)
|
|
|
|
class FileOpen(PathCommand):
|
|
""" File open command """
|
|
NUMBER = 0x10 #: Command number
|
|
READ = 0x00 #: Open file in read mode
|
|
WRITE = 0x01 #: Open file in write mode
|
|
path_length = field(start=20, fmt=DWORD)
|
|
path = stringfield(path_length, start=24)
|
|
|
|
def __init__(self, path, mode=0x00):
|
|
PathCommand.__init__(self, path, FileOpen.NUMBER, path_len_at_byte=20)
|
|
self.mode = mode
|
|
|
|
@apply
|
|
def mode():
|
|
doc =\
|
|
""" The file open mode. Is either L{FileOpen.READ} or L{FileOpen.WRITE}. C{unsigned int} stored at byte 16. """
|
|
def fget(self):
|
|
return self.unpack(start=16, fmt=DWORD)[0]
|
|
|
|
def fset(self, val):
|
|
self.pack(val, start=16, fmt=DWORD)
|
|
|
|
return property(**locals())
|
|
|
|
|
|
class FileIO(Command):
|
|
""" Command to read/write from an open file """
|
|
RNUMBER = 0x16 #: Command number to read from a file
|
|
WNUMBER = 0x17 #: Command number to write to a file
|
|
id = field(start=16, fmt=DWORD) #: The file ID returned by a FileOpen command
|
|
offset = field(start=20, fmt=DDWORD) #: offset in the file at which to read
|
|
size = field(start=28, fmt=DWORD) #: The number of bytes to reead from file.
|
|
def __init__(self, id, offset, size, mode=0x16):
|
|
"""
|
|
@param id: File identifier returned by a L{FileOpen} command
|
|
@type id: C{unsigned int}
|
|
@param offset: Position in file at which to read
|
|
@type offset: C{unsigned long long}
|
|
@param size: number of bytes to read
|
|
@type size: C{unsigned int}
|
|
@param mode: Either L{FileIO.RNUMBER} or L{File.WNUMBER}
|
|
"""
|
|
Command.__init__(self, 32)
|
|
self.number=mode
|
|
self.type = 0x01
|
|
self.length = 16
|
|
self.id = id
|
|
self.offset = offset
|
|
self.size = size
|
|
|
|
|
|
class PathQuery(PathCommand):
|
|
""" Defines structure of command that requests information about a path """
|
|
NUMBER = 0x18 #: Command number
|
|
def __init__(self, path):
|
|
PathCommand.__init__(self, path, PathQuery.NUMBER)
|
|
|
|
class SetFileInfo(PathCommand):
|
|
""" Set File information """
|
|
NUMBER = 0x19 #: Command number
|
|
def __init__(self, path):
|
|
PathCommand.__init__(self, path, SetFileInfo.NUMBER)
|
|
|
|
class Response(Command):
|
|
"""
|
|
Defines the structure of response packets received from the device.
|
|
|
|
C{Response} inherits from C{Command} as the first 16 bytes have the same structure.
|
|
"""
|
|
|
|
SIZE = 32 #: Size of response packets in the SONY protocol
|
|
rnumber = field(start=16, fmt=DWORD) #: Response number, the command number of a command packet sent sometime before this packet was received
|
|
code = field(start=20, fmt=DWORD) #: Used to indicate error conditions. A value of 0 means there was no error
|
|
data_size = field(start=28, fmt=DWORD) #: Used to indicate the size of the next bulk read
|
|
|
|
def __init__(self, packet):
|
|
""" C{len(packet) == Response.SIZE} """
|
|
if len(packet) != Response.SIZE:
|
|
raise PacketError(str(self.__class__)[7:-2] + " packets must have exactly " + str(Response.SIZE) + " bytes not " + str(len(packet)))
|
|
Command.__init__(self, packet)
|
|
if self.number != 0x00001000:
|
|
raise PacketError("Response packets must have their number set to " + hex(0x00001000))
|
|
|
|
@apply
|
|
def data():
|
|
doc =\
|
|
""" The last 3 DWORDs (12 bytes) of data in this response packet. Returned as a list of unsigned integers. """
|
|
def fget(self):
|
|
return self.unpack(start=20, fmt="<III")
|
|
|
|
def fset(self, val):
|
|
self.pack(val, start=20, fmt="<III")
|
|
|
|
return property(**locals())
|
|
|
|
class ListResponse(Response):
|
|
|
|
""" Defines the structure of response packets received during list (ll) queries. See L{PathQuery}. """
|
|
|
|
IS_FILE = 0xffffffd2 #: Queried path is a file
|
|
IS_INVALID = 0xfffffff9 #: Queried path is malformed/invalid
|
|
IS_UNMOUNTED = 0xffffffc8 #: Queried path is not mounted (i.e. a removed storage card/stick)
|
|
IS_EOL = 0xfffffffa #: There are no more entries in the list
|
|
PATH_NOT_FOUND = 0xffffffd7 #: Queried path is not found
|
|
|
|
@apply
|
|
def is_file():
|
|
""" True iff queried path is a file """
|
|
def fget(self):
|
|
return self.code == ListResponse.IS_FILE
|
|
return property(**locals())
|
|
|
|
@apply
|
|
def is_invalid():
|
|
""" True iff queried path is invalid """
|
|
def fget(self):
|
|
return self.code == ListResponse.IS_INVALID
|
|
return property(**locals())
|
|
|
|
@apply
|
|
def path_not_found():
|
|
""" True iff queried path is not found """
|
|
def fget(self):
|
|
return self.code == ListResponse.PATH_NOT_FOUND
|
|
return property(**locals())
|
|
|
|
@apply
|
|
def is_unmounted():
|
|
""" True iff queried path is unmounted (i.e. removed storage card) """
|
|
def fget(self):
|
|
return self.code == ListResponse.IS_UNMOUNTED
|
|
return property(**locals())
|
|
|
|
@apply
|
|
def is_eol():
|
|
""" True iff there are no more items in the list """
|
|
def fget(self):
|
|
return self.code == ListResponse.IS_EOL
|
|
return property(**locals())
|
|
|
|
class Answer(TransferBuffer):
|
|
""" Defines the structure of packets sent to host via a bulk transfer (i.e., bulk reads) """
|
|
|
|
number = field(start=0, fmt=DWORD) #: Answer identifier
|
|
length = field(start=12, fmt=DWORD) #: Length of data to follow
|
|
|
|
def __init__(self, packet):
|
|
""" @param packet: C{len(packet)} S{>=} C{16} """
|
|
if "__len__" in dir(packet):
|
|
if len(packet) < 16 :
|
|
raise PacketError(str(self.__class__)[7:-2] + " packets must have a length of atleast 16 bytes")
|
|
elif packet < 16:
|
|
raise PacketError(str(self.__class__)[7:-2] + " packets must have a length of atleast 16 bytes")
|
|
TransferBuffer.__init__(self, packet)
|
|
|
|
|
|
class FileProperties(Answer):
|
|
|
|
""" Defines the structure of packets that contain size, date and permissions information about files/directories. """
|
|
|
|
file_size = field(start=16, fmt=DDWORD) #: Size in bytes of the file
|
|
file_type = field(start=24, fmt=DWORD) #: 1 == file, 2 == dir
|
|
ctime = field(start=28, fmt=DWORD) #: Creation time
|
|
wtime = field(start=32, fmt=DWORD) #: Modification time
|
|
permissions = field(start=36, fmt=DWORD) #: 0 = default permissions, 4 = read only
|
|
|
|
@apply
|
|
def is_dir():
|
|
doc = """True if path points to a directory, False if it points to a file."""
|
|
|
|
def fget(self):
|
|
return (self.file_type == 2)
|
|
|
|
def fset(self, val):
|
|
if val: val = 2
|
|
else: val = 1
|
|
self.file_type = val
|
|
|
|
return property(**locals())
|
|
|
|
|
|
@apply
|
|
def is_readonly():
|
|
doc = """ Whether this file is readonly."""
|
|
|
|
def fget(self):
|
|
return self.unpack(start=36, fmt=DWORD)[0] != 0
|
|
|
|
def fset(self, val):
|
|
if val: val = 4
|
|
else: val = 0
|
|
self.pack(val, start=36, fmt=DWORD)
|
|
|
|
return property(**locals())
|
|
|
|
|
|
class USBProtocolVersion(Answer):
|
|
version = field(start=16, fmt=DDWORD)
|
|
|
|
class IdAnswer(Answer):
|
|
|
|
""" Defines the structure of packets that contain identifiers for queries. """
|
|
|
|
@apply
|
|
def id():
|
|
doc =\
|
|
""" 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. """
|
|
|
|
def fget(self):
|
|
return self.unpack(start=16, fmt=DWORD)[0]
|
|
|
|
def fset(self, val):
|
|
self.pack(val, start=16, fmt=DWORD)
|
|
|
|
return property(**locals())
|
|
|
|
class DeviceInfo(Answer):
|
|
""" Defines the structure of the packet containing information about the device """
|
|
device_name = field(start=16, fmt="<32s")
|
|
device_version = field(start=48, fmt="<32s")
|
|
software_version = field(start=80, fmt="<24s")
|
|
mime_type = field(start=104, fmt="<32s")
|
|
|
|
|
|
class FreeSpaceAnswer(Answer):
|
|
total = field(start=24, fmt=DDWORD)
|
|
free_space = field(start=32, fmt=DDWORD)
|
|
|
|
|
|
class ListAnswer(Answer):
|
|
""" Defines the structure of packets that contain items in a list. """
|
|
name_length = field(start=20, fmt=DWORD)
|
|
name = stringfield(name_length, start=24)
|
|
|
|
@apply
|
|
def is_dir():
|
|
doc =\
|
|
""" 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. """
|
|
|
|
def fget(self):
|
|
return (self.unpack(start=16, fmt=DWORD)[0] == 2)
|
|
|
|
def fset(self, val):
|
|
if val: val = 2
|
|
else: val = 1
|
|
self.pack(val, start=16, fmt=DWORD)
|
|
|
|
return property(**locals())
|
|
|
|
|