123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268 |
- # Copyright 2014 Google Inc. All rights reserved.
- #
- # Licensed under the Apache License, Version 2.0 (the "License");
- # you may not use this file except in compliance with the License.
- # You may obtain a copy of the License at
- #
- # http://www.apache.org/licenses/LICENSE-2.0
- #
- # Unless required by applicable law or agreed to in writing, software
- # distributed under the License is distributed on an "AS IS" BASIS,
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- # See the License for the specific language governing permissions and
- # limitations under the License.
- """ADB protocol implementation.
- Implements the ADB protocol as seen in android's adb/adbd binaries, but only the
- host side.
- """
- import collections
- import os
- import stat
- import struct
- import time
- import libusb1
- from adb import adb_protocol
- from adb import usb_exceptions
- # Default mode for pushed files.
- DEFAULT_PUSH_MODE = stat.S_IFREG | stat.S_IRWXU | stat.S_IRWXG
- # Maximum size of a filesync DATA packet.
- MAX_PUSH_DATA = 2 * 1024
- class InvalidChecksumError(Exception):
- """Checksum of data didn't match expected checksum."""
- class InterleavedDataError(Exception):
- """We only support command sent serially."""
- class PushFailedError(Exception):
- """Pushing a file failed for some reason."""
- class PullFailedError(Exception):
- """Pulling a file failed for some reason."""
- DeviceFile = collections.namedtuple('DeviceFile', [
- 'filename', 'mode', 'size', 'mtime'])
- class FilesyncProtocol(object):
- """Implements the FileSync protocol as described in sync.txt."""
- @staticmethod
- def Stat(connection, filename):
- cnxn = FileSyncConnection(connection, b'<4I')
- cnxn.Send(b'STAT', filename)
- command, (mode, size, mtime) = cnxn.Read((b'STAT',), read_data=False)
- if command != b'STAT':
- raise adb_protocol.InvalidResponseError(
- 'Expected STAT response to STAT, got %s' % command)
- return mode, size, mtime
- @classmethod
- def List(cls, connection, path):
- cnxn = FileSyncConnection(connection, b'<5I')
- cnxn.Send(b'LIST', path)
- files = []
- for cmd_id, header, filename in cnxn.ReadUntil((b'DENT',), b'DONE'):
- if cmd_id == b'DONE':
- break
- mode, size, mtime = header
- files.append(DeviceFile(filename, mode, size, mtime))
- return files
- @classmethod
- def Pull(cls, connection, filename, dest_file, progress_callback):
- """Pull a file from the device into the file-like dest_file."""
- if progress_callback:
- total_bytes = cls.Stat(connection, filename)[1]
- progress = cls._HandleProgress(lambda current: progress_callback(filename, current, total_bytes))
- next(progress)
- cnxn = FileSyncConnection(connection, b'<2I')
- try:
- cnxn.Send(b'RECV', filename)
- for cmd_id, _, data in cnxn.ReadUntil((b'DATA',), b'DONE'):
- if cmd_id == b'DONE':
- break
- dest_file.write(data)
- if progress_callback:
- progress.send(len(data))
- except usb_exceptions.CommonUsbError as e:
- raise PullFailedError('Unable to pull file %s due to: %s' % (filename, e))
- @classmethod
- def _HandleProgress(cls, progress_callback):
- """Calls the callback with the current progress and total bytes written/received.
- Args:
- progress_callback: callback method that accepts filename, bytes_written and total_bytes,
- total_bytes will be -1 for file-like objects
- """
- current = 0
- while True:
- current += yield
- try:
- progress_callback(current)
- except Exception: # pylint: disable=broad-except
- continue
- @classmethod
- def Push(cls, connection, datafile, filename,
- st_mode=DEFAULT_PUSH_MODE, mtime=0, progress_callback=None):
- """Push a file-like object to the device.
- Args:
- connection: ADB connection
- datafile: File-like object for reading from
- filename: Filename to push to
- st_mode: stat mode for filename
- mtime: modification time
- progress_callback: callback method that accepts filename, bytes_written and total_bytes
- Raises:
- PushFailedError: Raised on push failure.
- """
- fileinfo = ('{},{}'.format(filename, int(st_mode))).encode('utf-8')
- cnxn = FileSyncConnection(connection, b'<2I')
- cnxn.Send(b'SEND', fileinfo)
- if progress_callback:
- total_bytes = os.fstat(datafile.fileno()).st_size if isinstance(datafile, file) else -1
- progress = cls._HandleProgress(lambda current: progress_callback(filename, current, total_bytes))
- next(progress)
- while True:
- data = datafile.read(MAX_PUSH_DATA)
- if data:
- cnxn.Send(b'DATA', data)
- if progress_callback:
- progress.send(len(data))
- else:
- break
- if mtime == 0:
- mtime = int(time.time())
- # DONE doesn't send data, but it hides the last bit of data in the size
- # field.
- cnxn.Send(b'DONE', size=mtime)
- for cmd_id, _, data in cnxn.ReadUntil((), b'OKAY', b'FAIL'):
- if cmd_id == b'OKAY':
- return
- raise PushFailedError(data)
- class FileSyncConnection(object):
- """Encapsulate a FileSync service connection."""
- ids = [
- b'STAT', b'LIST', b'SEND', b'RECV', b'DENT', b'DONE', b'DATA', b'OKAY',
- b'FAIL', b'QUIT',
- ]
- id_to_wire, wire_to_id = adb_protocol.MakeWireIDs(ids)
- def __init__(self, adb_connection, recv_header_format):
- self.adb = adb_connection
- # Sending
- # Using a bytearray() saves a copy later when using libusb.
- self.send_buffer = bytearray(adb_protocol.MAX_ADB_DATA)
- self.send_idx = 0
- self.send_header_len = struct.calcsize(b'<2I')
- # Receiving
- self.recv_buffer = bytearray()
- self.recv_header_format = recv_header_format
- self.recv_header_len = struct.calcsize(recv_header_format)
- def Send(self, command_id, data=b'', size=0):
- """Send/buffer FileSync packets.
- Packets are buffered and only flushed when this connection is read from. All
- messages have a response from the device, so this will always get flushed.
- Args:
- command_id: Command to send.
- data: Optional data to send, must set data or size.
- size: Optionally override size from len(data).
- """
- if data:
- if not isinstance(data, bytes):
- data = data.encode('utf8')
- size = len(data)
- if not self._CanAddToSendBuffer(len(data)):
- self._Flush()
- buf = struct.pack(b'<2I', self.id_to_wire[command_id], size) + data
- self.send_buffer[self.send_idx:self.send_idx + len(buf)] = buf
- self.send_idx += len(buf)
- def Read(self, expected_ids, read_data=True):
- """Read ADB messages and return FileSync packets."""
- if self.send_idx:
- self._Flush()
- # Read one filesync packet off the recv buffer.
- header_data = self._ReadBuffered(self.recv_header_len)
- header = struct.unpack(self.recv_header_format, header_data)
- # Header is (ID, ...).
- command_id = self.wire_to_id[header[0]]
- if command_id not in expected_ids:
- if command_id == b'FAIL':
- reason = ''
- if self.recv_buffer:
- reason = self.recv_buffer.decode('utf-8', errors='ignore')
- raise usb_exceptions.AdbCommandFailureException('Command failed: {}'.format(reason))
- raise adb_protocol.InvalidResponseError(
- 'Expected one of %s, got %s' % (expected_ids, command_id))
- if not read_data:
- return command_id, header[1:]
- # Header is (ID, ..., size).
- size = header[-1]
- data = self._ReadBuffered(size)
- return command_id, header[1:-1], data
- def ReadUntil(self, expected_ids, *finish_ids):
- """Useful wrapper around Read."""
- while True:
- cmd_id, header, data = self.Read(expected_ids + finish_ids)
- yield cmd_id, header, data
- if cmd_id in finish_ids:
- break
- def _CanAddToSendBuffer(self, data_len):
- added_len = self.send_header_len + data_len
- return self.send_idx + added_len < adb_protocol.MAX_ADB_DATA
- def _Flush(self):
- try:
- self.adb.Write(self.send_buffer[:self.send_idx])
- except libusb1.USBError as e:
- raise adb_protocol.SendFailedError(
- 'Could not send data %s' % self.send_buffer, e)
- self.send_idx = 0
- def _ReadBuffered(self, size):
- # Ensure recv buffer has enough data.
- while len(self.recv_buffer) < size:
- _, data = self.adb.ReadUntil(b'WRTE')
- self.recv_buffer += data
- result = self.recv_buffer[:size]
- self.recv_buffer = self.recv_buffer[size:]
- return result
|