filesync_protocol.py 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. # Copyright 2014 Google Inc. All rights reserved.
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. """ADB protocol implementation.
  15. Implements the ADB protocol as seen in android's adb/adbd binaries, but only the
  16. host side.
  17. """
  18. import collections
  19. import os
  20. import stat
  21. import struct
  22. import time
  23. import libusb1
  24. from adb import adb_protocol
  25. from adb import usb_exceptions
  26. # Default mode for pushed files.
  27. DEFAULT_PUSH_MODE = stat.S_IFREG | stat.S_IRWXU | stat.S_IRWXG
  28. # Maximum size of a filesync DATA packet.
  29. MAX_PUSH_DATA = 2 * 1024
  30. class InvalidChecksumError(Exception):
  31. """Checksum of data didn't match expected checksum."""
  32. class InterleavedDataError(Exception):
  33. """We only support command sent serially."""
  34. class PushFailedError(Exception):
  35. """Pushing a file failed for some reason."""
  36. class PullFailedError(Exception):
  37. """Pulling a file failed for some reason."""
  38. DeviceFile = collections.namedtuple('DeviceFile', [
  39. 'filename', 'mode', 'size', 'mtime'])
  40. class FilesyncProtocol(object):
  41. """Implements the FileSync protocol as described in sync.txt."""
  42. @staticmethod
  43. def Stat(connection, filename):
  44. cnxn = FileSyncConnection(connection, b'<4I')
  45. cnxn.Send(b'STAT', filename)
  46. command, (mode, size, mtime) = cnxn.Read((b'STAT',), read_data=False)
  47. if command != b'STAT':
  48. raise adb_protocol.InvalidResponseError(
  49. 'Expected STAT response to STAT, got %s' % command)
  50. return mode, size, mtime
  51. @classmethod
  52. def List(cls, connection, path):
  53. cnxn = FileSyncConnection(connection, b'<5I')
  54. cnxn.Send(b'LIST', path)
  55. files = []
  56. for cmd_id, header, filename in cnxn.ReadUntil((b'DENT',), b'DONE'):
  57. if cmd_id == b'DONE':
  58. break
  59. mode, size, mtime = header
  60. files.append(DeviceFile(filename, mode, size, mtime))
  61. return files
  62. @classmethod
  63. def Pull(cls, connection, filename, dest_file, progress_callback):
  64. """Pull a file from the device into the file-like dest_file."""
  65. if progress_callback:
  66. total_bytes = cls.Stat(connection, filename)[1]
  67. progress = cls._HandleProgress(lambda current: progress_callback(filename, current, total_bytes))
  68. next(progress)
  69. cnxn = FileSyncConnection(connection, b'<2I')
  70. try:
  71. cnxn.Send(b'RECV', filename)
  72. for cmd_id, _, data in cnxn.ReadUntil((b'DATA',), b'DONE'):
  73. if cmd_id == b'DONE':
  74. break
  75. dest_file.write(data)
  76. if progress_callback:
  77. progress.send(len(data))
  78. except usb_exceptions.CommonUsbError as e:
  79. raise PullFailedError('Unable to pull file %s due to: %s' % (filename, e))
  80. @classmethod
  81. def _HandleProgress(cls, progress_callback):
  82. """Calls the callback with the current progress and total bytes written/received.
  83. Args:
  84. progress_callback: callback method that accepts filename, bytes_written and total_bytes,
  85. total_bytes will be -1 for file-like objects
  86. """
  87. current = 0
  88. while True:
  89. current += yield
  90. try:
  91. progress_callback(current)
  92. except Exception: # pylint: disable=broad-except
  93. continue
  94. @classmethod
  95. def Push(cls, connection, datafile, filename,
  96. st_mode=DEFAULT_PUSH_MODE, mtime=0, progress_callback=None):
  97. """Push a file-like object to the device.
  98. Args:
  99. connection: ADB connection
  100. datafile: File-like object for reading from
  101. filename: Filename to push to
  102. st_mode: stat mode for filename
  103. mtime: modification time
  104. progress_callback: callback method that accepts filename, bytes_written and total_bytes
  105. Raises:
  106. PushFailedError: Raised on push failure.
  107. """
  108. fileinfo = ('{},{}'.format(filename, int(st_mode))).encode('utf-8')
  109. cnxn = FileSyncConnection(connection, b'<2I')
  110. cnxn.Send(b'SEND', fileinfo)
  111. if progress_callback:
  112. total_bytes = os.fstat(datafile.fileno()).st_size if isinstance(datafile, file) else -1
  113. progress = cls._HandleProgress(lambda current: progress_callback(filename, current, total_bytes))
  114. next(progress)
  115. while True:
  116. data = datafile.read(MAX_PUSH_DATA)
  117. if data:
  118. cnxn.Send(b'DATA', data)
  119. if progress_callback:
  120. progress.send(len(data))
  121. else:
  122. break
  123. if mtime == 0:
  124. mtime = int(time.time())
  125. # DONE doesn't send data, but it hides the last bit of data in the size
  126. # field.
  127. cnxn.Send(b'DONE', size=mtime)
  128. for cmd_id, _, data in cnxn.ReadUntil((), b'OKAY', b'FAIL'):
  129. if cmd_id == b'OKAY':
  130. return
  131. raise PushFailedError(data)
  132. class FileSyncConnection(object):
  133. """Encapsulate a FileSync service connection."""
  134. ids = [
  135. b'STAT', b'LIST', b'SEND', b'RECV', b'DENT', b'DONE', b'DATA', b'OKAY',
  136. b'FAIL', b'QUIT',
  137. ]
  138. id_to_wire, wire_to_id = adb_protocol.MakeWireIDs(ids)
  139. def __init__(self, adb_connection, recv_header_format):
  140. self.adb = adb_connection
  141. # Sending
  142. # Using a bytearray() saves a copy later when using libusb.
  143. self.send_buffer = bytearray(adb_protocol.MAX_ADB_DATA)
  144. self.send_idx = 0
  145. self.send_header_len = struct.calcsize(b'<2I')
  146. # Receiving
  147. self.recv_buffer = bytearray()
  148. self.recv_header_format = recv_header_format
  149. self.recv_header_len = struct.calcsize(recv_header_format)
  150. def Send(self, command_id, data=b'', size=0):
  151. """Send/buffer FileSync packets.
  152. Packets are buffered and only flushed when this connection is read from. All
  153. messages have a response from the device, so this will always get flushed.
  154. Args:
  155. command_id: Command to send.
  156. data: Optional data to send, must set data or size.
  157. size: Optionally override size from len(data).
  158. """
  159. if data:
  160. if not isinstance(data, bytes):
  161. data = data.encode('utf8')
  162. size = len(data)
  163. if not self._CanAddToSendBuffer(len(data)):
  164. self._Flush()
  165. buf = struct.pack(b'<2I', self.id_to_wire[command_id], size) + data
  166. self.send_buffer[self.send_idx:self.send_idx + len(buf)] = buf
  167. self.send_idx += len(buf)
  168. def Read(self, expected_ids, read_data=True):
  169. """Read ADB messages and return FileSync packets."""
  170. if self.send_idx:
  171. self._Flush()
  172. # Read one filesync packet off the recv buffer.
  173. header_data = self._ReadBuffered(self.recv_header_len)
  174. header = struct.unpack(self.recv_header_format, header_data)
  175. # Header is (ID, ...).
  176. command_id = self.wire_to_id[header[0]]
  177. if command_id not in expected_ids:
  178. if command_id == b'FAIL':
  179. reason = ''
  180. if self.recv_buffer:
  181. reason = self.recv_buffer.decode('utf-8', errors='ignore')
  182. raise usb_exceptions.AdbCommandFailureException('Command failed: {}'.format(reason))
  183. raise adb_protocol.InvalidResponseError(
  184. 'Expected one of %s, got %s' % (expected_ids, command_id))
  185. if not read_data:
  186. return command_id, header[1:]
  187. # Header is (ID, ..., size).
  188. size = header[-1]
  189. data = self._ReadBuffered(size)
  190. return command_id, header[1:-1], data
  191. def ReadUntil(self, expected_ids, *finish_ids):
  192. """Useful wrapper around Read."""
  193. while True:
  194. cmd_id, header, data = self.Read(expected_ids + finish_ids)
  195. yield cmd_id, header, data
  196. if cmd_id in finish_ids:
  197. break
  198. def _CanAddToSendBuffer(self, data_len):
  199. added_len = self.send_header_len + data_len
  200. return self.send_idx + added_len < adb_protocol.MAX_ADB_DATA
  201. def _Flush(self):
  202. try:
  203. self.adb.Write(self.send_buffer[:self.send_idx])
  204. except libusb1.USBError as e:
  205. raise adb_protocol.SendFailedError(
  206. 'Could not send data %s' % self.send_buffer, e)
  207. self.send_idx = 0
  208. def _ReadBuffered(self, size):
  209. # Ensure recv buffer has enough data.
  210. while len(self.recv_buffer) < size:
  211. _, data = self.adb.ReadUntil(b'WRTE')
  212. self.recv_buffer += data
  213. result = self.recv_buffer[:size]
  214. self.recv_buffer = self.recv_buffer[size:]
  215. return result