├── .gitignore ├── .travis.yml ├── CONTRIBUTORS ├── LICENSE ├── README.md ├── adb ├── __init__.py ├── adb_commands.py ├── adb_debug.py ├── adb_protocol.py ├── common.py ├── common_cli.py ├── fastboot.py ├── fastboot_debug.py ├── filesync_protocol.py ├── sign_cryptography.py ├── sign_pycryptodome.py ├── sign_pythonrsa.py └── usb_exceptions.py ├── fastboot_protocol.txt ├── filesync_protocol.txt ├── make_tools.py ├── setup.py ├── test ├── adb_test.py ├── common_stub.py └── fastboot_test.py └── tox.ini /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | .cache/ 3 | .coverage 4 | adb.egg-info/ 5 | .tox/ 6 | /adb.zip 7 | /fastboot.zip 8 | .idea/ 9 | *.DS_Store* -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | 3 | sudo: false 4 | 5 | python: 6 | - 3.6 7 | 8 | addons: 9 | apt: 10 | packages: 11 | - swig 12 | - libusb-1.0-0-dev 13 | 14 | cache: 15 | pip: true 16 | apt: true 17 | directories: 18 | - $HOME/.cache/pip 19 | 20 | install: 21 | - pip install tox coveralls 22 | 23 | env: 24 | - TOXENV=py36 25 | - TOXENV=py27 26 | 27 | script: tox 28 | 29 | after_success: 30 | - coveralls 31 | -------------------------------------------------------------------------------- /CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | Fahrzin Hemmati 2 | Alex Lusco 3 | Simon Ye 4 | Jamey Hicks 5 | Marc-Antoine Ruel 6 | Max Borghino 7 | Mohammad Abu-Garbeyyeh 8 | Josip Delic 9 | Greg E. 10 | Or Barzilay 11 | Jeff Irion 12 | tuxuser 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | python-adb 2 | ========== 3 | [![Coverage Status][coverage_img]][coverage_link] 4 | [![Build Status][build_img]][build_link] 5 | 6 | Note: This is not an official Google project. It is maintained by ex-Google engineers. For a better maintained option, look at [adb_shell](https://github.com/JeffLIrion/adb_shell). 7 | 8 | This repository contains a pure-python implementation of the ADB and Fastboot 9 | protocols, using libusb1 for USB communications. 10 | 11 | This is a complete replacement and rearchitecture of the Android project's [ADB 12 | and fastboot code](https://github.com/android/platform_system_core/tree/master/adb) 13 | 14 | This code is mainly targeted to users that need to communicate with Android 15 | devices in an automated fashion, such as in automated testing. It does not have 16 | a daemon between the client and the device, and therefore does not support 17 | multiple simultaneous commands to the same device. It does support any number of 18 | devices and _never_ communicates with a device that it wasn't intended to, 19 | unlike the Android project's ADB. 20 | 21 | 22 | ### Using as standalone tool 23 | 24 | Install using pip: 25 | 26 | ```sh 27 | pip install adb 28 | ``` 29 | 30 | Once installed, two new binaries should be available: `pyadb` and `pyfastboot`. 31 | 32 | ```sh 33 | pyadb devices 34 | pyadb shell ls /sdcard 35 | ``` 36 | 37 | Running `./make_tools.py` creates two files: `adb.zip` and `fastboot.zip`. They 38 | can be run similar to native `adb` and `fastboot` via the python interpreter: 39 | 40 | python adb.zip devices 41 | python adb.zip shell ls /sdcard 42 | 43 | ### Using as a Python Library 44 | 45 | A [presentation was made at PyCon 2016][pycon_preso], and here's some demo code: 46 | 47 | ```python 48 | import os.path as op 49 | 50 | from adb import adb_commands 51 | from adb import sign_cryptography 52 | 53 | 54 | # KitKat+ devices require authentication 55 | signer = sign_cryptography.CryptographySigner( 56 | op.expanduser('~/.android/adbkey')) 57 | # Connect to the device 58 | device = adb_commands.AdbCommands() 59 | device.ConnectDevice( 60 | rsa_keys=[signer]) 61 | # Now we can use Shell, Pull, Push, etc! 62 | for i in xrange(10): 63 | print device.Shell('echo %d' % i) 64 | ``` 65 | 66 | ### Pros 67 | 68 | * Simpler code due to use of libusb1 and Python. 69 | * API can be used by other Python code easily. 70 | * Errors are propagated with tracebacks, helping debug connectivity issues. 71 | * No daemon outliving the command. 72 | * Can be packaged as standalone zips that can be run independent of the CPU 73 | architecture (e.g. x86 vs ARM). 74 | 75 | 76 | ### Cons 77 | 78 | * Technically slower due to Python, mitigated by no daemon. 79 | * Only one command per device at a time. 80 | * More dependencies than Android's ADB. 81 | 82 | 83 | ### Dependencies 84 | 85 | * libusb1 (1.0.16+) 86 | * python-libusb1 (1.2.0+) 87 | * `adb.zip`: one of: 88 | * py-cryptography 89 | * python-rsa (3.2+) 90 | * `fastboot.zip` (optional): 91 | * python-progressbar (2.3+) 92 | 93 | ### History 94 | 95 | #### 1.0.0 96 | 97 | * Initial version 98 | 99 | #### 1.1.0 100 | 101 | * Added TcpHandle (jameyhicks) 102 | * Various timing and other changes (alusco) 103 | 104 | #### 1.2.0 105 | 106 | * Update to libusb1 1.6+ (bytearray output) 107 | * Add support for Python 3.6 108 | * Create adb.zip and fastboot.zip as executable tools. 109 | * Add Travis CI integration 110 | * Support multiple crypto libraries (M2Crypto + python-rsa) 111 | * Push directories 112 | 113 | #### 1.3.0 114 | 115 | ##### Backwards Incompatible changes 116 | `adb_commands.AdbCommands()` is now a normal class rather than a collection of staticmethods. Using the following example code to get started: 117 | ```py 118 | device = adb_commands.AdbCommands() 119 | device.ConnectDevice(rsa_keys=[signer]) 120 | ``` 121 | 122 | ##### Other changes/fixes 123 | Many changes since 1.2.0! 124 | 125 | * New entrypoints exposed by pip: pyadb and pyfastboot 126 | * Lots of Python 2/3 compatibility fixes 127 | * Windows compatibility fixes 128 | * Transfer progress available (`Push`, `Pull`, `Install`) 129 | * Handle some misbehaving devices (double CLSE bug) 130 | * New options for `Push` and `Install` (`st_mode` and `grant_permissions`) 131 | 132 | 133 | [coverage_img]: https://coveralls.io/repos/github/google/python-adb/badge.svg?branch=master 134 | [coverage_link]: https://coveralls.io/github/google/python-adb?branch=master 135 | [build_img]: https://travis-ci.org/google/python-adb.svg?branch=master 136 | [build_link]: https://travis-ci.org/google/python-adb 137 | [pycon_preso]: https://docs.google.com/presentation/d/1bv8pmm8TZp4aFxoq2ohA-ms_a3BWci7D3tYvVGIm8T0/pub?start=false&loop=false&delayms=10000 138 | -------------------------------------------------------------------------------- /adb/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/python-adb/f4e597fe55900651a8a91fccc1e09061fd96b5e9/adb/__init__.py -------------------------------------------------------------------------------- /adb/adb_commands.py: -------------------------------------------------------------------------------- 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 | """A libusb1-based ADB reimplementation. 15 | 16 | ADB was giving us trouble with its client/server architecture, which is great 17 | for users and developers, but not so great for reliable scripting. This will 18 | allow us to more easily catch errors as Python exceptions instead of checking 19 | random exit codes, and all the other great benefits from not going through 20 | subprocess and a network socket. 21 | 22 | All timeouts are in milliseconds. 23 | """ 24 | 25 | import io 26 | import os 27 | import socket 28 | import posixpath 29 | 30 | from adb import adb_protocol 31 | from adb import common 32 | from adb import filesync_protocol 33 | 34 | # From adb.h 35 | CLASS = 0xFF 36 | SUBCLASS = 0x42 37 | PROTOCOL = 0x01 38 | # pylint: disable=invalid-name 39 | DeviceIsAvailable = common.InterfaceMatcher(CLASS, SUBCLASS, PROTOCOL) 40 | 41 | try: 42 | # Imported locally to keep compatibility with previous code. 43 | from adb.sign_cryptography import CryptographySigner 44 | except ImportError: 45 | # Ignore this error when cryptography is not installed, there are other options. 46 | pass 47 | 48 | 49 | class AdbCommands(object): 50 | """Exposes adb-like methods for use. 51 | 52 | Some methods are more-pythonic and/or have more options. 53 | """ 54 | protocol_handler = adb_protocol.AdbMessage 55 | filesync_handler = filesync_protocol.FilesyncProtocol 56 | 57 | def __init__(self): 58 | 59 | self.__reset() 60 | 61 | def __reset(self): 62 | self.build_props = None 63 | self._handle = None 64 | self._device_state = None 65 | 66 | # Connection table tracks each open AdbConnection objects per service type for program functions 67 | # that choose to persist an AdbConnection object for their functionality, using 68 | # self._get_service_connection 69 | self._service_connections = {} 70 | 71 | def _get_service_connection(self, service, service_command=None, create=True, timeout_ms=None): 72 | """ 73 | Based on the service, get the AdbConnection for that service or create one if it doesnt exist 74 | 75 | :param service: 76 | :param service_command: Additional service parameters to append 77 | :param create: If False, dont create a connection if it does not exist 78 | :return: 79 | """ 80 | 81 | connection = self._service_connections.get(service, None) 82 | 83 | if connection: 84 | return connection 85 | 86 | if not connection and not create: 87 | return None 88 | 89 | if service_command: 90 | destination_str = b'%s:%s' % (service, service_command) 91 | else: 92 | destination_str = service 93 | 94 | connection = self.protocol_handler.Open( 95 | self._handle, destination=destination_str, timeout_ms=timeout_ms) 96 | 97 | self._service_connections.update({service: connection}) 98 | 99 | return connection 100 | 101 | def ConnectDevice(self, port_path=None, serial=None, default_timeout_ms=None, **kwargs): 102 | """Convenience function to setup a transport handle for the adb device from 103 | usb path or serial then connect to it. 104 | 105 | Args: 106 | port_path: The filename of usb port to use. 107 | serial: The serial number of the device to use. 108 | default_timeout_ms: The default timeout in milliseconds to use. 109 | kwargs: handle: Device handle to use (instance of common.TcpHandle or common.UsbHandle) 110 | banner: Connection banner to pass to the remote device 111 | rsa_keys: List of AuthSigner subclass instances to be used for 112 | authentication. The device can either accept one of these via the Sign 113 | method, or we will send the result of GetPublicKey from the first one 114 | if the device doesn't accept any of them. 115 | auth_timeout_ms: Timeout to wait for when sending a new public key. This 116 | is only relevant when we send a new public key. The device shows a 117 | dialog and this timeout is how long to wait for that dialog. If used 118 | in automation, this should be low to catch such a case as a failure 119 | quickly; while in interactive settings it should be high to allow 120 | users to accept the dialog. We default to automation here, so it's low 121 | by default. 122 | 123 | If serial specifies a TCP address:port, then a TCP connection is 124 | used instead of a USB connection. 125 | """ 126 | 127 | # If there isnt a handle override (used by tests), build one here 128 | if 'handle' in kwargs: 129 | self._handle = kwargs.pop('handle') 130 | else: 131 | # if necessary, convert serial to a unicode string 132 | if isinstance(serial, (bytes, bytearray)): 133 | serial = serial.decode('utf-8') 134 | 135 | if serial and ':' in serial: 136 | self._handle = common.TcpHandle(serial, timeout_ms=default_timeout_ms) 137 | else: 138 | self._handle = common.UsbHandle.FindAndOpen( 139 | DeviceIsAvailable, port_path=port_path, serial=serial, 140 | timeout_ms=default_timeout_ms) 141 | 142 | self._Connect(**kwargs) 143 | 144 | return self 145 | 146 | def Close(self): 147 | for conn in list(self._service_connections.values()): 148 | if conn: 149 | try: 150 | conn.Close() 151 | except: 152 | pass 153 | 154 | if self._handle: 155 | self._handle.Close() 156 | 157 | self.__reset() 158 | 159 | def _Connect(self, banner=None, **kwargs): 160 | """Connect to the device. 161 | 162 | Args: 163 | banner: See protocol_handler.Connect. 164 | **kwargs: See protocol_handler.Connect and adb_commands.ConnectDevice for kwargs. 165 | Includes handle, rsa_keys, and auth_timeout_ms. 166 | Returns: 167 | An instance of this class if the device connected successfully. 168 | """ 169 | 170 | if not banner: 171 | banner = socket.gethostname().encode() 172 | 173 | conn_str = self.protocol_handler.Connect(self._handle, banner=banner, **kwargs) 174 | 175 | # Remove banner and colons after device state (state::banner) 176 | parts = conn_str.split(b'::') 177 | self._device_state = parts[0] 178 | 179 | # Break out the build prop info 180 | self.build_props = str(parts[1].split(b';')) 181 | 182 | return True 183 | 184 | @classmethod 185 | def Devices(cls): 186 | """Get a generator of UsbHandle for devices available.""" 187 | return common.UsbHandle.FindDevices(DeviceIsAvailable) 188 | 189 | def GetState(self): 190 | return self._device_state 191 | 192 | def Install(self, apk_path, destination_dir='', replace_existing=True, 193 | grant_permissions=False, timeout_ms=None, transfer_progress_callback=None): 194 | """Install an apk to the device. 195 | 196 | Doesn't support verifier file, instead allows destination directory to be 197 | overridden. 198 | 199 | Args: 200 | apk_path: Local path to apk to install. 201 | destination_dir: Optional destination directory. Use /system/app/ for 202 | persistent applications. 203 | replace_existing: whether to replace existing application 204 | grant_permissions: If True, grant all permissions to the app specified in its manifest 205 | timeout_ms: Expected timeout for pushing and installing. 206 | transfer_progress_callback: callback method that accepts filename, bytes_written and total_bytes of APK transfer 207 | 208 | Returns: 209 | The pm install output. 210 | """ 211 | if not destination_dir: 212 | destination_dir = '/data/local/tmp/' 213 | basename = os.path.basename(apk_path) 214 | destination_path = posixpath.join(destination_dir, basename) 215 | self.Push(apk_path, destination_path, timeout_ms=timeout_ms, progress_callback=transfer_progress_callback) 216 | 217 | cmd = ['pm install'] 218 | if grant_permissions: 219 | cmd.append('-g') 220 | if replace_existing: 221 | cmd.append('-r') 222 | cmd.append('"{}"'.format(destination_path)) 223 | 224 | ret = self.Shell(' '.join(cmd), timeout_ms=timeout_ms) 225 | 226 | # Remove the apk 227 | rm_cmd = ['rm', destination_path] 228 | rmret = self.Shell(' '.join(rm_cmd), timeout_ms=timeout_ms) 229 | 230 | return ret 231 | 232 | def Uninstall(self, package_name, keep_data=False, timeout_ms=None): 233 | """Removes a package from the device. 234 | 235 | Args: 236 | package_name: Package name of target package. 237 | keep_data: whether to keep the data and cache directories 238 | timeout_ms: Expected timeout for pushing and installing. 239 | 240 | Returns: 241 | The pm uninstall output. 242 | """ 243 | cmd = ['pm uninstall'] 244 | if keep_data: 245 | cmd.append('-k') 246 | cmd.append('"%s"' % package_name) 247 | 248 | return self.Shell(' '.join(cmd), timeout_ms=timeout_ms) 249 | 250 | def Push(self, source_file, device_filename, mtime='0', timeout_ms=None, progress_callback=None, st_mode=None): 251 | """Push a file or directory to the device. 252 | 253 | Args: 254 | source_file: Either a filename, a directory or file-like object to push to 255 | the device. 256 | device_filename: Destination on the device to write to. 257 | mtime: Optional, modification time to set on the file. 258 | timeout_ms: Expected timeout for any part of the push. 259 | st_mode: stat mode for filename 260 | progress_callback: callback method that accepts filename, bytes_written and total_bytes, 261 | total_bytes will be -1 for file-like objects 262 | """ 263 | 264 | if isinstance(source_file, str): 265 | if os.path.isdir(source_file): 266 | self.Shell("mkdir " + device_filename) 267 | for f in os.listdir(source_file): 268 | self.Push(os.path.join(source_file, f), device_filename + '/' + f, 269 | progress_callback=progress_callback) 270 | return 271 | source_file = open(source_file, "rb") 272 | 273 | with source_file: 274 | connection = self.protocol_handler.Open( 275 | self._handle, destination=b'sync:', timeout_ms=timeout_ms) 276 | kwargs={} 277 | if st_mode is not None: 278 | kwargs['st_mode'] = st_mode 279 | self.filesync_handler.Push(connection, source_file, device_filename, 280 | mtime=int(mtime), progress_callback=progress_callback, **kwargs) 281 | connection.Close() 282 | 283 | def Pull(self, device_filename, dest_file=None, timeout_ms=None, progress_callback=None): 284 | """Pull a file from the device. 285 | 286 | Args: 287 | device_filename: Filename on the device to pull. 288 | dest_file: If set, a filename or writable file-like object. 289 | timeout_ms: Expected timeout for any part of the pull. 290 | progress_callback: callback method that accepts filename, bytes_written and total_bytes, 291 | total_bytes will be -1 for file-like objects 292 | 293 | Returns: 294 | The file data if dest_file is not set. Otherwise, True if the destination file exists 295 | """ 296 | if not dest_file: 297 | dest_file = io.BytesIO() 298 | elif isinstance(dest_file, str): 299 | dest_file = open(dest_file, 'wb') 300 | elif isinstance(dest_file, file): 301 | pass 302 | else: 303 | raise ValueError("destfile is of unknown type") 304 | 305 | conn = self.protocol_handler.Open( 306 | self._handle, destination=b'sync:', timeout_ms=timeout_ms) 307 | 308 | self.filesync_handler.Pull(conn, device_filename, dest_file, progress_callback) 309 | 310 | conn.Close() 311 | if isinstance(dest_file, io.BytesIO): 312 | return dest_file.getvalue() 313 | else: 314 | dest_file.close() 315 | if hasattr(dest_file, 'name'): 316 | return os.path.exists(dest_file.name) 317 | # We don't know what the path is, so we just assume it exists. 318 | return True 319 | 320 | def Stat(self, device_filename): 321 | """Get a file's stat() information.""" 322 | connection = self.protocol_handler.Open(self._handle, destination=b'sync:') 323 | mode, size, mtime = self.filesync_handler.Stat( 324 | connection, device_filename) 325 | connection.Close() 326 | return mode, size, mtime 327 | 328 | def List(self, device_path): 329 | """Return a directory listing of the given path. 330 | 331 | Args: 332 | device_path: Directory to list. 333 | """ 334 | connection = self.protocol_handler.Open(self._handle, destination=b'sync:') 335 | listing = self.filesync_handler.List(connection, device_path) 336 | connection.Close() 337 | return listing 338 | 339 | def Reboot(self, destination=b''): 340 | """Reboot the device. 341 | 342 | Args: 343 | destination: Specify 'bootloader' for fastboot. 344 | """ 345 | self.protocol_handler.Open(self._handle, b'reboot:%s' % destination) 346 | 347 | def RebootBootloader(self): 348 | """Reboot device into fastboot.""" 349 | self.Reboot(b'bootloader') 350 | 351 | def Remount(self): 352 | """Remount / as read-write.""" 353 | return self.protocol_handler.Command(self._handle, service=b'remount') 354 | 355 | def Root(self): 356 | """Restart adbd as root on the device.""" 357 | return self.protocol_handler.Command(self._handle, service=b'root') 358 | 359 | def EnableVerity(self): 360 | """Re-enable dm-verity checking on userdebug builds""" 361 | return self.protocol_handler.Command(self._handle, service=b'enable-verity') 362 | 363 | def DisableVerity(self): 364 | """Disable dm-verity checking on userdebug builds""" 365 | return self.protocol_handler.Command(self._handle, service=b'disable-verity') 366 | 367 | def Shell(self, command, timeout_ms=None): 368 | """Run command on the device, returning the output. 369 | 370 | Args: 371 | command: Shell command to run 372 | timeout_ms: Maximum time to allow the command to run. 373 | """ 374 | return self.protocol_handler.Command( 375 | self._handle, service=b'shell', command=command, 376 | timeout_ms=timeout_ms) 377 | 378 | def StreamingShell(self, command, timeout_ms=None): 379 | """Run command on the device, yielding each line of output. 380 | 381 | Args: 382 | command: Command to run on the target. 383 | timeout_ms: Maximum time to allow the command to run. 384 | 385 | Yields: 386 | The responses from the shell command. 387 | """ 388 | return self.protocol_handler.StreamingCommand( 389 | self._handle, service=b'shell', command=command, 390 | timeout_ms=timeout_ms) 391 | 392 | def Logcat(self, options, timeout_ms=None): 393 | """Run 'shell logcat' and stream the output to stdout. 394 | 395 | Args: 396 | options: Arguments to pass to 'logcat'. 397 | timeout_ms: Maximum time to allow the command to run. 398 | """ 399 | return self.StreamingShell('logcat %s' % options, timeout_ms) 400 | 401 | def InteractiveShell(self, cmd=None, strip_cmd=True, delim=None, strip_delim=True): 402 | """Get stdout from the currently open interactive shell and optionally run a command 403 | on the device, returning all output. 404 | 405 | Args: 406 | cmd: Optional. Command to run on the target. 407 | strip_cmd: Optional (default True). Strip command name from stdout. 408 | delim: Optional. Delimiter to look for in the output to know when to stop expecting more output 409 | (usually the shell prompt) 410 | strip_delim: Optional (default True): Strip the provided delimiter from the output 411 | 412 | Returns: 413 | The stdout from the shell command. 414 | """ 415 | conn = self._get_service_connection(b'shell:') 416 | 417 | return self.protocol_handler.InteractiveShellCommand( 418 | conn, cmd=cmd, strip_cmd=strip_cmd, 419 | delim=delim, strip_delim=strip_delim) 420 | -------------------------------------------------------------------------------- /adb/adb_debug.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Copyright 2014 Google Inc. All rights reserved. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | """Daemon-less ADB client in python.""" 17 | 18 | import argparse 19 | import functools 20 | import logging 21 | import os 22 | import stat 23 | import sys 24 | import time 25 | 26 | from adb import adb_commands 27 | from adb import common_cli 28 | 29 | try: 30 | from adb import sign_cryptography 31 | 32 | rsa_signer = sign_cryptography.CryptographySigner 33 | except ImportError: 34 | try: 35 | from adb import sign_pythonrsa 36 | 37 | rsa_signer = sign_pythonrsa.PythonRSASigner.FromRSAKeyPath 38 | except ImportError: 39 | try: 40 | from adb import sign_pycryptodome 41 | 42 | rsa_signer = sign_pycryptodome.PycryptodomeAuthSigner 43 | except ImportError: 44 | rsa_signer = None 45 | 46 | 47 | def Devices(args): 48 | """Lists the available devices. 49 | 50 | Mimics 'adb devices' output: 51 | List of devices attached 52 | 015DB7591102001A device 1,2 53 | """ 54 | for d in adb_commands.AdbCommands.Devices(): 55 | if args.output_port_path: 56 | print('%s\tdevice\t%s' % ( 57 | d.serial_number, ','.join(str(p) for p in d.port_path))) 58 | else: 59 | print('%s\tdevice' % d.serial_number) 60 | return 0 61 | 62 | 63 | def List(device, device_path): 64 | """Prints a directory listing. 65 | 66 | Args: 67 | device_path: Directory to list. 68 | """ 69 | files = device.List(device_path) 70 | files.sort(key=lambda x: x.filename) 71 | maxname = max(len(f.filename) for f in files) 72 | maxsize = max(len(str(f.size)) for f in files) 73 | for f in files: 74 | mode = ( 75 | ('d' if stat.S_ISDIR(f.mode) else '-') + 76 | ('r' if f.mode & stat.S_IRUSR else '-') + 77 | ('w' if f.mode & stat.S_IWUSR else '-') + 78 | ('x' if f.mode & stat.S_IXUSR else '-') + 79 | ('r' if f.mode & stat.S_IRGRP else '-') + 80 | ('w' if f.mode & stat.S_IWGRP else '-') + 81 | ('x' if f.mode & stat.S_IXGRP else '-') + 82 | ('r' if f.mode & stat.S_IROTH else '-') + 83 | ('w' if f.mode & stat.S_IWOTH else '-') + 84 | ('x' if f.mode & stat.S_IXOTH else '-')) 85 | t = time.gmtime(f.mtime) 86 | yield '%s %*d %04d-%02d-%02d %02d:%02d:%02d %-*s\n' % ( 87 | mode, maxsize, f.size, 88 | t.tm_year, t.tm_mon, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec, 89 | maxname, f.filename) 90 | 91 | 92 | @functools.wraps(adb_commands.AdbCommands.Logcat) 93 | def Logcat(device, *options): 94 | return device.Logcat( 95 | device, ' '.join(options), timeout_ms=0) 96 | 97 | 98 | def Shell(device, *command): 99 | """Runs a command on the device and prints the stdout. 100 | 101 | Args: 102 | command: Command to run on the target. 103 | """ 104 | if command: 105 | return device.StreamingShell(' '.join(command)) 106 | else: 107 | # Retrieve the initial terminal prompt to use as a delimiter for future reads 108 | terminal_prompt = device.InteractiveShell() 109 | print(terminal_prompt.decode('utf-8')) 110 | 111 | # Accept user input in a loop and write that into the interactive shells stdin, then print output 112 | while True: 113 | cmd = input('> ') 114 | if not cmd: 115 | continue 116 | elif cmd == 'exit': 117 | break 118 | else: 119 | stdout = device.InteractiveShell(cmd, strip_cmd=True, delim=terminal_prompt, strip_delim=True) 120 | if stdout: 121 | if isinstance(stdout, bytes): 122 | stdout = stdout.decode('utf-8') 123 | print(stdout) 124 | 125 | device.Close() 126 | 127 | 128 | def main(): 129 | common = common_cli.GetCommonArguments() 130 | common.add_argument( 131 | '--rsa_key_path', action='append', default=[], 132 | metavar='~/.android/adbkey', 133 | help='RSA key(s) to use, use multiple times to load mulitple keys') 134 | common.add_argument( 135 | '--auth_timeout_s', default=60., metavar='60', type=int, 136 | help='Seconds to wait for the dialog to be accepted when using ' 137 | 'authenticated ADB.') 138 | device = common_cli.GetDeviceArguments() 139 | parents = [common, device] 140 | 141 | parser = argparse.ArgumentParser( 142 | description=sys.modules[__name__].__doc__, parents=[common]) 143 | subparsers = parser.add_subparsers(title='Commands', dest='command_name') 144 | 145 | subparser = subparsers.add_parser( 146 | name='help', help='Prints the commands available') 147 | subparser = subparsers.add_parser( 148 | name='devices', help='Lists the available devices', parents=[common]) 149 | subparser.add_argument( 150 | '--output_port_path', action='store_true', 151 | help='Outputs the port_path alongside the serial') 152 | 153 | common_cli.MakeSubparser( 154 | subparsers, parents, adb_commands.AdbCommands.Install) 155 | common_cli.MakeSubparser(subparsers, parents, adb_commands.AdbCommands.Uninstall) 156 | common_cli.MakeSubparser(subparsers, parents, List) 157 | common_cli.MakeSubparser(subparsers, parents, Logcat) 158 | common_cli.MakeSubparser( 159 | subparsers, parents, adb_commands.AdbCommands.Push, 160 | {'source_file': 'Filename or directory to push to the device.'}) 161 | common_cli.MakeSubparser( 162 | subparsers, parents, adb_commands.AdbCommands.Pull, 163 | { 164 | 'dest_file': 'Filename to write to on the host, if not specified, ' 165 | 'prints the content to stdout.', 166 | }) 167 | common_cli.MakeSubparser( 168 | subparsers, parents, adb_commands.AdbCommands.Reboot) 169 | common_cli.MakeSubparser( 170 | subparsers, parents, adb_commands.AdbCommands.RebootBootloader) 171 | common_cli.MakeSubparser( 172 | subparsers, parents, adb_commands.AdbCommands.Remount) 173 | common_cli.MakeSubparser(subparsers, parents, adb_commands.AdbCommands.Root) 174 | common_cli.MakeSubparser(subparsers, parents, adb_commands.AdbCommands.EnableVerity) 175 | common_cli.MakeSubparser(subparsers, parents, adb_commands.AdbCommands.DisableVerity) 176 | common_cli.MakeSubparser(subparsers, parents, Shell) 177 | 178 | if len(sys.argv) == 1: 179 | parser.print_help() 180 | return 2 181 | 182 | args = parser.parse_args() 183 | if args.verbose: 184 | logging.basicConfig(level=logging.DEBUG) 185 | if not args.rsa_key_path: 186 | default = os.path.expanduser('~/.android/adbkey') 187 | if os.path.isfile(default): 188 | args.rsa_key_path = [default] 189 | if args.rsa_key_path and not rsa_signer: 190 | parser.error('Please install either cryptography, python-rsa, or PycryptoDome') 191 | 192 | # Hacks so that the generated doc is nicer. 193 | if args.command_name == 'devices': 194 | return Devices(args) 195 | if args.command_name == 'help': 196 | parser.print_help() 197 | return 0 198 | if args.command_name == 'logcat': 199 | args.positional = args.options 200 | elif args.command_name == 'shell': 201 | args.positional = args.command 202 | 203 | return common_cli.StartCli( 204 | args, 205 | adb_commands.AdbCommands, 206 | auth_timeout_ms=int(args.auth_timeout_s * 1000), 207 | rsa_keys=[rsa_signer(path) for path in args.rsa_key_path]) 208 | 209 | 210 | if __name__ == '__main__': 211 | sys.exit(main()) 212 | -------------------------------------------------------------------------------- /adb/adb_protocol.py: -------------------------------------------------------------------------------- 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 | 16 | Implements the ADB protocol as seen in android's adb/adbd binaries, but only the 17 | host side. 18 | """ 19 | 20 | import struct 21 | import time 22 | from io import BytesIO 23 | from adb import usb_exceptions 24 | 25 | # Maximum amount of data in an ADB packet. 26 | MAX_ADB_DATA = 4096 27 | # ADB protocol version. 28 | VERSION = 0x01000000 29 | 30 | # AUTH constants for arg0. 31 | AUTH_TOKEN = 1 32 | AUTH_SIGNATURE = 2 33 | AUTH_RSAPUBLICKEY = 3 34 | 35 | 36 | def find_backspace_runs(stdout_bytes, start_pos): 37 | first_backspace_pos = stdout_bytes[start_pos:].find(b'\x08') 38 | if first_backspace_pos == -1: 39 | return -1, 0 40 | 41 | end_backspace_pos = (start_pos + first_backspace_pos) + 1 42 | while True: 43 | if chr(stdout_bytes[end_backspace_pos]) == '\b': 44 | end_backspace_pos += 1 45 | else: 46 | break 47 | 48 | num_backspaces = end_backspace_pos - (start_pos + first_backspace_pos) 49 | 50 | return (start_pos + first_backspace_pos), num_backspaces 51 | 52 | 53 | class InvalidCommandError(Exception): 54 | """Got an invalid command over USB.""" 55 | 56 | def __init__(self, message, response_header, response_data): 57 | if response_header == b'FAIL': 58 | message = 'Command failed, device said so. (%s)' % message 59 | super(InvalidCommandError, self).__init__( 60 | message, response_header, response_data) 61 | 62 | 63 | class InvalidResponseError(Exception): 64 | """Got an invalid response to our command.""" 65 | 66 | 67 | class InvalidChecksumError(Exception): 68 | """Checksum of data didn't match expected checksum.""" 69 | 70 | 71 | class InterleavedDataError(Exception): 72 | """We only support command sent serially.""" 73 | 74 | 75 | def MakeWireIDs(ids): 76 | id_to_wire = { 77 | cmd_id: sum(c << (i * 8) for i, c in enumerate(bytearray(cmd_id))) 78 | for cmd_id in ids 79 | } 80 | wire_to_id = {wire: cmd_id for cmd_id, wire in id_to_wire.items()} 81 | return id_to_wire, wire_to_id 82 | 83 | 84 | class AuthSigner(object): 85 | """Signer for use with authenticated ADB, introduced in 4.4.x/KitKat.""" 86 | 87 | def Sign(self, data): 88 | """Signs given data using a private key.""" 89 | raise NotImplementedError() 90 | 91 | def GetPublicKey(self): 92 | """Returns the public key in PEM format without headers or newlines.""" 93 | raise NotImplementedError() 94 | 95 | 96 | class _AdbConnection(object): 97 | """ADB Connection.""" 98 | 99 | def __init__(self, usb, local_id, remote_id, timeout_ms): 100 | self.usb = usb 101 | self.local_id = local_id 102 | self.remote_id = remote_id 103 | self.timeout_ms = timeout_ms 104 | 105 | def _Send(self, command, arg0, arg1, data=b''): 106 | message = AdbMessage(command, arg0, arg1, data) 107 | message.Send(self.usb, self.timeout_ms) 108 | 109 | def Write(self, data): 110 | """Write a packet and expect an Ack.""" 111 | self._Send(b'WRTE', arg0=self.local_id, arg1=self.remote_id, data=data) 112 | # Expect an ack in response. 113 | cmd, okay_data = self.ReadUntil(b'OKAY') 114 | if cmd != b'OKAY': 115 | if cmd == b'FAIL': 116 | raise usb_exceptions.AdbCommandFailureException( 117 | 'Command failed.', okay_data) 118 | raise InvalidCommandError( 119 | 'Expected an OKAY in response to a WRITE, got %s (%s)', 120 | cmd, okay_data) 121 | return len(data) 122 | 123 | def Okay(self): 124 | self._Send(b'OKAY', arg0=self.local_id, arg1=self.remote_id) 125 | 126 | def ReadUntil(self, *expected_cmds): 127 | """Read a packet, Ack any write packets.""" 128 | cmd, remote_id, local_id, data = AdbMessage.Read( 129 | self.usb, expected_cmds, self.timeout_ms) 130 | if local_id != 0 and self.local_id != local_id: 131 | raise InterleavedDataError("We don't support multiple streams...") 132 | if remote_id != 0 and self.remote_id != remote_id: 133 | raise InvalidResponseError( 134 | 'Incorrect remote id, expected %s got %s' % ( 135 | self.remote_id, remote_id)) 136 | # Ack write packets. 137 | if cmd == b'WRTE': 138 | self.Okay() 139 | return cmd, data 140 | 141 | def ReadUntilClose(self): 142 | """Yield packets until a Close packet is received.""" 143 | while True: 144 | cmd, data = self.ReadUntil(b'CLSE', b'WRTE') 145 | if cmd == b'CLSE': 146 | self._Send(b'CLSE', arg0=self.local_id, arg1=self.remote_id) 147 | break 148 | if cmd != b'WRTE': 149 | if cmd == b'FAIL': 150 | raise usb_exceptions.AdbCommandFailureException( 151 | 'Command failed.', data) 152 | raise InvalidCommandError('Expected a WRITE or a CLOSE, got %s (%s)', 153 | cmd, data) 154 | yield data 155 | 156 | def Close(self): 157 | self._Send(b'CLSE', arg0=self.local_id, arg1=self.remote_id) 158 | cmd, data = self.ReadUntil(b'CLSE') 159 | if cmd != b'CLSE': 160 | if cmd == b'FAIL': 161 | raise usb_exceptions.AdbCommandFailureException('Command failed.', data) 162 | raise InvalidCommandError('Expected a CLSE response, got %s (%s)', 163 | cmd, data) 164 | 165 | 166 | class AdbMessage(object): 167 | """ADB Protocol and message class. 168 | 169 | Protocol Notes 170 | 171 | local_id/remote_id: 172 | Turns out the documentation is host/device ambidextrous, so local_id is the 173 | id for 'the sender' and remote_id is for 'the recipient'. So since we're 174 | only on the host, we'll re-document with host_id and device_id: 175 | 176 | OPEN(host_id, 0, 'shell:XXX') 177 | READY/OKAY(device_id, host_id, '') 178 | WRITE(0, host_id, 'data') 179 | CLOSE(device_id, host_id, '') 180 | """ 181 | 182 | ids = [b'SYNC', b'CNXN', b'AUTH', b'OPEN', b'OKAY', b'CLSE', b'WRTE'] 183 | commands, constants = MakeWireIDs(ids) 184 | # An ADB message is 6 words in little-endian. 185 | format = b'<6I' 186 | 187 | connections = 0 188 | 189 | def __init__(self, command=None, arg0=None, arg1=None, data=b''): 190 | self.command = self.commands[command] 191 | self.magic = self.command ^ 0xFFFFFFFF 192 | self.arg0 = arg0 193 | self.arg1 = arg1 194 | self.data = data 195 | 196 | @property 197 | def checksum(self): 198 | return self.CalculateChecksum(self.data) 199 | 200 | @staticmethod 201 | def CalculateChecksum(data): 202 | # The checksum is just a sum of all the bytes. I swear. 203 | if isinstance(data, bytearray): 204 | total = sum(data) 205 | elif isinstance(data, bytes): 206 | if data and isinstance(data[0], bytes): 207 | # Python 2 bytes (str) index as single-character strings. 208 | total = sum(map(ord, data)) 209 | else: 210 | # Python 3 bytes index as numbers (and PY2 empty strings sum() to 0) 211 | total = sum(data) 212 | else: 213 | # Unicode strings (should never see?) 214 | total = sum(map(ord, data)) 215 | return total & 0xFFFFFFFF 216 | 217 | def Pack(self): 218 | """Returns this message in an over-the-wire format.""" 219 | return struct.pack(self.format, self.command, self.arg0, self.arg1, 220 | len(self.data), self.checksum, self.magic) 221 | 222 | @classmethod 223 | def Unpack(cls, message): 224 | try: 225 | cmd, arg0, arg1, data_length, data_checksum, unused_magic = struct.unpack( 226 | cls.format, message) 227 | except struct.error as e: 228 | raise ValueError('Unable to unpack ADB command.', cls.format, message, e) 229 | return cmd, arg0, arg1, data_length, data_checksum 230 | 231 | def Send(self, usb, timeout_ms=None): 232 | """Send this message over USB.""" 233 | usb.BulkWrite(self.Pack(), timeout_ms) 234 | usb.BulkWrite(self.data, timeout_ms) 235 | 236 | @classmethod 237 | def Read(cls, usb, expected_cmds, timeout_ms=None, total_timeout_ms=None): 238 | """Receive a response from the device.""" 239 | total_timeout_ms = usb.Timeout(total_timeout_ms) 240 | start = time.time() 241 | while True: 242 | msg = usb.BulkRead(24, timeout_ms) 243 | cmd, arg0, arg1, data_length, data_checksum = cls.Unpack(msg) 244 | command = cls.constants.get(cmd) 245 | if not command: 246 | raise InvalidCommandError( 247 | 'Unknown command: %x' % cmd, cmd, (arg0, arg1)) 248 | if command in expected_cmds: 249 | break 250 | 251 | if time.time() - start > total_timeout_ms: 252 | raise InvalidCommandError( 253 | 'Never got one of the expected responses (%s)' % expected_cmds, 254 | cmd, (timeout_ms, total_timeout_ms)) 255 | 256 | if data_length > 0: 257 | data = bytearray() 258 | while data_length > 0: 259 | temp = usb.BulkRead(data_length, timeout_ms) 260 | if len(temp) != data_length: 261 | print( 262 | "Data_length {} does not match actual number of bytes read: {}".format(data_length, len(temp))) 263 | data += temp 264 | 265 | data_length -= len(temp) 266 | 267 | actual_checksum = cls.CalculateChecksum(data) 268 | if actual_checksum != data_checksum: 269 | raise InvalidChecksumError( 270 | 'Received checksum %s != %s', (actual_checksum, data_checksum)) 271 | else: 272 | data = b'' 273 | return command, arg0, arg1, bytes(data) 274 | 275 | @classmethod 276 | def Connect(cls, usb, banner=b'notadb', rsa_keys=None, auth_timeout_ms=100): 277 | """Establish a new connection to the device. 278 | 279 | Args: 280 | usb: A USBHandle with BulkRead and BulkWrite methods. 281 | banner: A string to send as a host identifier. 282 | rsa_keys: List of AuthSigner subclass instances to be used for 283 | authentication. The device can either accept one of these via the Sign 284 | method, or we will send the result of GetPublicKey from the first one 285 | if the device doesn't accept any of them. 286 | auth_timeout_ms: Timeout to wait for when sending a new public key. This 287 | is only relevant when we send a new public key. The device shows a 288 | dialog and this timeout is how long to wait for that dialog. If used 289 | in automation, this should be low to catch such a case as a failure 290 | quickly; while in interactive settings it should be high to allow 291 | users to accept the dialog. We default to automation here, so it's low 292 | by default. 293 | 294 | Returns: 295 | The device's reported banner. Always starts with the state (device, 296 | recovery, or sideload), sometimes includes information after a : with 297 | various product information. 298 | 299 | Raises: 300 | usb_exceptions.DeviceAuthError: When the device expects authentication, 301 | but we weren't given any valid keys. 302 | InvalidResponseError: When the device does authentication in an 303 | unexpected way. 304 | """ 305 | # In py3, convert unicode to bytes. In py2, convert str to bytes. 306 | # It's later joined into a byte string, so in py2, this ends up kind of being a no-op. 307 | if isinstance(banner, str): 308 | banner = bytearray(banner, 'utf-8') 309 | 310 | msg = cls( 311 | command=b'CNXN', arg0=VERSION, arg1=MAX_ADB_DATA, 312 | data=b'host::%s\0' % banner) 313 | msg.Send(usb) 314 | cmd, arg0, arg1, banner = cls.Read(usb, [b'CNXN', b'AUTH']) 315 | if cmd == b'AUTH': 316 | if not rsa_keys: 317 | raise usb_exceptions.DeviceAuthError( 318 | 'Device authentication required, no keys available.') 319 | # Loop through our keys, signing the last 'banner' or token. 320 | for rsa_key in rsa_keys: 321 | if arg0 != AUTH_TOKEN: 322 | raise InvalidResponseError( 323 | 'Unknown AUTH response: %s %s %s' % (arg0, arg1, banner)) 324 | 325 | # Do not mangle the banner property here by converting it to a string 326 | signed_token = rsa_key.Sign(banner) 327 | msg = cls( 328 | command=b'AUTH', arg0=AUTH_SIGNATURE, arg1=0, data=signed_token) 329 | msg.Send(usb) 330 | cmd, arg0, unused_arg1, banner = cls.Read(usb, [b'CNXN', b'AUTH']) 331 | if cmd == b'CNXN': 332 | return banner 333 | # None of the keys worked, so send a public key. 334 | msg = cls( 335 | command=b'AUTH', arg0=AUTH_RSAPUBLICKEY, arg1=0, 336 | data=rsa_keys[0].GetPublicKey() + b'\0') 337 | msg.Send(usb) 338 | try: 339 | cmd, arg0, unused_arg1, banner = cls.Read( 340 | usb, [b'CNXN'], timeout_ms=auth_timeout_ms) 341 | except usb_exceptions.ReadFailedError as e: 342 | if e.usb_error.value == -7: # Timeout. 343 | raise usb_exceptions.DeviceAuthError( 344 | 'Accept auth key on device, then retry.') 345 | raise 346 | # This didn't time-out, so we got a CNXN response. 347 | return banner 348 | return banner 349 | 350 | @classmethod 351 | def Open(cls, usb, destination, timeout_ms=None): 352 | """Opens a new connection to the device via an OPEN message. 353 | 354 | Not the same as the posix 'open' or any other google3 Open methods. 355 | 356 | Args: 357 | usb: USB device handle with BulkRead and BulkWrite methods. 358 | destination: The service:command string. 359 | timeout_ms: Timeout in milliseconds for USB packets. 360 | 361 | Raises: 362 | InvalidResponseError: Wrong local_id sent to us. 363 | InvalidCommandError: Didn't get a ready response. 364 | 365 | Returns: 366 | The local connection id. 367 | """ 368 | local_id = 1 369 | msg = cls( 370 | command=b'OPEN', arg0=local_id, arg1=0, 371 | data=destination + b'\0') 372 | msg.Send(usb, timeout_ms) 373 | cmd, remote_id, their_local_id, _ = cls.Read(usb, [b'CLSE', b'OKAY'], 374 | timeout_ms=timeout_ms) 375 | if local_id != their_local_id: 376 | raise InvalidResponseError( 377 | 'Expected the local_id to be {}, got {}'.format(local_id, their_local_id)) 378 | if cmd == b'CLSE': 379 | # Some devices seem to be sending CLSE once more after a request, this *should* handle it 380 | cmd, remote_id, their_local_id, _ = cls.Read(usb, [b'CLSE', b'OKAY'], 381 | timeout_ms=timeout_ms) 382 | # Device doesn't support this service. 383 | if cmd == b'CLSE': 384 | return None 385 | if cmd != b'OKAY': 386 | raise InvalidCommandError('Expected a ready response, got {}'.format(cmd), 387 | cmd, (remote_id, their_local_id)) 388 | return _AdbConnection(usb, local_id, remote_id, timeout_ms) 389 | 390 | @classmethod 391 | def Command(cls, usb, service, command='', timeout_ms=None): 392 | """One complete set of USB packets for a single command. 393 | 394 | Sends service:command in a new connection, reading the data for the 395 | response. All the data is held in memory, large responses will be slow and 396 | can fill up memory. 397 | 398 | Args: 399 | usb: USB device handle with BulkRead and BulkWrite methods. 400 | service: The service on the device to talk to. 401 | command: The command to send to the service. 402 | timeout_ms: Timeout for USB packets, in milliseconds. 403 | 404 | Raises: 405 | InterleavedDataError: Multiple streams running over usb. 406 | InvalidCommandError: Got an unexpected response command. 407 | 408 | Returns: 409 | The response from the service. 410 | """ 411 | return ''.join(cls.StreamingCommand(usb, service, command, timeout_ms)) 412 | 413 | @classmethod 414 | def StreamingCommand(cls, usb, service, command='', timeout_ms=None): 415 | """One complete set of USB packets for a single command. 416 | 417 | Sends service:command in a new connection, reading the data for the 418 | response. All the data is held in memory, large responses will be slow and 419 | can fill up memory. 420 | 421 | Args: 422 | usb: USB device handle with BulkRead and BulkWrite methods. 423 | service: The service on the device to talk to. 424 | command: The command to send to the service. 425 | timeout_ms: Timeout for USB packets, in milliseconds. 426 | 427 | Raises: 428 | InterleavedDataError: Multiple streams running over usb. 429 | InvalidCommandError: Got an unexpected response command. 430 | 431 | Yields: 432 | The responses from the service. 433 | """ 434 | if not isinstance(command, bytes): 435 | command = command.encode('utf8') 436 | connection = cls.Open( 437 | usb, destination=b'%s:%s' % (service, command), 438 | timeout_ms=timeout_ms) 439 | for data in connection.ReadUntilClose(): 440 | yield data.decode('utf8') 441 | 442 | @classmethod 443 | def InteractiveShellCommand(cls, conn, cmd=None, strip_cmd=True, delim=None, strip_delim=True, clean_stdout=True): 444 | """Retrieves stdout of the current InteractiveShell and sends a shell command if provided 445 | TODO: Should we turn this into a yield based function so we can stream all output? 446 | 447 | Args: 448 | conn: Instance of AdbConnection 449 | cmd: Optional. Command to run on the target. 450 | strip_cmd: Optional (default True). Strip command name from stdout. 451 | delim: Optional. Delimiter to look for in the output to know when to stop expecting more output 452 | (usually the shell prompt) 453 | strip_delim: Optional (default True): Strip the provided delimiter from the output 454 | clean_stdout: Cleanup the stdout stream of any backspaces and the characters that were deleted by the backspace 455 | Returns: 456 | The stdout from the shell command. 457 | """ 458 | 459 | if delim is not None and not isinstance(delim, bytes): 460 | delim = delim.encode('utf-8') 461 | 462 | # Delimiter may be shell@hammerhead:/ $ 463 | # The user or directory could change, making the delimiter somthing like root@hammerhead:/data/local/tmp $ 464 | # Handle a partial delimiter to search on and clean up 465 | if delim: 466 | user_pos = delim.find(b'@') 467 | dir_pos = delim.rfind(b':/') 468 | if user_pos != -1 and dir_pos != -1: 469 | partial_delim = delim[user_pos:dir_pos + 1] # e.g. @hammerhead: 470 | else: 471 | partial_delim = delim 472 | else: 473 | partial_delim = None 474 | 475 | stdout = '' 476 | stdout_stream = BytesIO() 477 | original_cmd = '' 478 | 479 | try: 480 | 481 | if cmd: 482 | original_cmd = str(cmd) 483 | cmd += '\r' # Required. Send a carriage return right after the cmd 484 | cmd = cmd.encode('utf8') 485 | 486 | # Send the cmd raw 487 | bytes_written = conn.Write(cmd) 488 | 489 | if delim: 490 | # Expect multiple WRTE cmds until the delim (usually terminal prompt) is detected 491 | 492 | data = b'' 493 | while partial_delim not in data: 494 | cmd, data = conn.ReadUntil(b'WRTE') 495 | stdout_stream.write(data) 496 | 497 | else: 498 | # Otherwise, expect only a single WRTE 499 | cmd, data = conn.ReadUntil(b'WRTE') 500 | 501 | # WRTE cmd from device will follow with stdout data 502 | stdout_stream.write(data) 503 | 504 | else: 505 | 506 | # No cmd provided means we should just expect a single line from the terminal. Use this sparingly 507 | cmd, data = conn.ReadUntil(b'WRTE') 508 | if cmd == b'WRTE': 509 | # WRTE cmd from device will follow with stdout data 510 | stdout_stream.write(data) 511 | else: 512 | print("Unhandled cmd: {}".format(cmd)) 513 | 514 | cleaned_stdout_stream = BytesIO() 515 | if clean_stdout: 516 | stdout_bytes = stdout_stream.getvalue() 517 | 518 | bsruns = {} # Backspace runs tracking 519 | next_start_pos = 0 520 | last_run_pos, last_run_len = find_backspace_runs(stdout_bytes, next_start_pos) 521 | 522 | if last_run_pos != -1 and last_run_len != 0: 523 | bsruns.update({last_run_pos: last_run_len}) 524 | cleaned_stdout_stream.write(stdout_bytes[next_start_pos:(last_run_pos - last_run_len)]) 525 | next_start_pos += last_run_pos + last_run_len 526 | 527 | while last_run_pos != -1: 528 | last_run_pos, last_run_len = find_backspace_runs(stdout_bytes[next_start_pos:], next_start_pos) 529 | 530 | if last_run_pos != -1: 531 | bsruns.update({last_run_pos: last_run_len}) 532 | cleaned_stdout_stream.write(stdout_bytes[next_start_pos:(last_run_pos - last_run_len)]) 533 | next_start_pos += last_run_pos + last_run_len 534 | 535 | cleaned_stdout_stream.write(stdout_bytes[next_start_pos:]) 536 | 537 | else: 538 | cleaned_stdout_stream.write(stdout_stream.getvalue()) 539 | 540 | stdout = cleaned_stdout_stream.getvalue() 541 | 542 | # Strip original cmd that will come back in stdout 543 | if original_cmd and strip_cmd: 544 | findstr = original_cmd.encode('utf-8') + b'\r\r\n' 545 | pos = stdout.find(findstr) 546 | while pos >= 0: 547 | stdout = stdout.replace(findstr, b'') 548 | pos = stdout.find(findstr) 549 | 550 | if b'\r\r\n' in stdout: 551 | stdout = stdout.split(b'\r\r\n')[1] 552 | 553 | # Strip delim if requested 554 | # TODO: Handling stripping partial delims here - not a deal breaker the way we're handling it now 555 | if delim and strip_delim: 556 | stdout = stdout.replace(delim, b'') 557 | 558 | stdout = stdout.rstrip() 559 | 560 | except Exception as e: 561 | print("InteractiveShell exception (most likely timeout): {}".format(e)) 562 | 563 | return stdout 564 | -------------------------------------------------------------------------------- /adb/common.py: -------------------------------------------------------------------------------- 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 | """Common code for ADB and Fastboot. 15 | 16 | Common usb browsing, and usb communication. 17 | """ 18 | import logging 19 | import platform 20 | import socket 21 | import threading 22 | import weakref 23 | import select 24 | 25 | import libusb1 26 | import usb1 27 | 28 | from adb import usb_exceptions 29 | 30 | DEFAULT_TIMEOUT_MS = 10000 31 | 32 | _LOG = logging.getLogger('android_usb') 33 | 34 | 35 | def GetInterface(setting): 36 | """Get the class, subclass, and protocol for the given USB setting.""" 37 | return (setting.getClass(), setting.getSubClass(), setting.getProtocol()) 38 | 39 | 40 | def InterfaceMatcher(clazz, subclass, protocol): 41 | """Returns a matcher that returns the setting with the given interface.""" 42 | interface = (clazz, subclass, protocol) 43 | 44 | def Matcher(device): 45 | for setting in device.iterSettings(): 46 | if GetInterface(setting) == interface: 47 | return setting 48 | 49 | return Matcher 50 | 51 | 52 | class UsbHandle(object): 53 | """USB communication object. Not thread-safe. 54 | 55 | Handles reading and writing over USB with the proper endpoints, exceptions, 56 | and interface claiming. 57 | 58 | Important methods: 59 | FlushBuffers() 60 | BulkRead(int length) 61 | BulkWrite(bytes data) 62 | """ 63 | 64 | _HANDLE_CACHE = weakref.WeakValueDictionary() 65 | _HANDLE_CACHE_LOCK = threading.Lock() 66 | 67 | def __init__(self, device, setting, usb_info=None, timeout_ms=None): 68 | """Initialize USB Handle. 69 | 70 | Arguments: 71 | device: libusb_device to connect to. 72 | setting: libusb setting with the correct endpoints to communicate with. 73 | usb_info: String describing the usb path/serial/device, for debugging. 74 | timeout_ms: Timeout in milliseconds for all I/O. 75 | """ 76 | self._setting = setting 77 | self._device = device 78 | self._handle = None 79 | 80 | self._usb_info = usb_info or '' 81 | self._timeout_ms = timeout_ms if timeout_ms else DEFAULT_TIMEOUT_MS 82 | self._max_read_packet_len = 0 83 | 84 | @property 85 | def usb_info(self): 86 | try: 87 | sn = self.serial_number 88 | except libusb1.USBError: 89 | sn = '' 90 | if sn and sn != self._usb_info: 91 | return '%s %s' % (self._usb_info, sn) 92 | return self._usb_info 93 | 94 | def Open(self): 95 | """Opens the USB device for this setting, and claims the interface.""" 96 | # Make sure we close any previous handle open to this usb device. 97 | port_path = tuple(self.port_path) 98 | with self._HANDLE_CACHE_LOCK: 99 | old_handle = self._HANDLE_CACHE.get(port_path) 100 | if old_handle is not None: 101 | old_handle.Close() 102 | 103 | self._read_endpoint = None 104 | self._write_endpoint = None 105 | 106 | for endpoint in self._setting.iterEndpoints(): 107 | address = endpoint.getAddress() 108 | if address & libusb1.USB_ENDPOINT_DIR_MASK: 109 | self._read_endpoint = address 110 | self._max_read_packet_len = endpoint.getMaxPacketSize() 111 | else: 112 | self._write_endpoint = address 113 | 114 | assert self._read_endpoint is not None 115 | assert self._write_endpoint is not None 116 | 117 | handle = self._device.open() 118 | iface_number = self._setting.getNumber() 119 | try: 120 | if (platform.system() != 'Windows' 121 | and handle.kernelDriverActive(iface_number)): 122 | handle.detachKernelDriver(iface_number) 123 | except libusb1.USBError as e: 124 | if e.value == libusb1.LIBUSB_ERROR_NOT_FOUND: 125 | _LOG.warning('Kernel driver not found for interface: %s.', iface_number) 126 | else: 127 | raise 128 | handle.claimInterface(iface_number) 129 | self._handle = handle 130 | self._interface_number = iface_number 131 | 132 | with self._HANDLE_CACHE_LOCK: 133 | self._HANDLE_CACHE[port_path] = self 134 | # When this object is deleted, make sure it's closed. 135 | weakref.ref(self, self.Close) 136 | 137 | @property 138 | def serial_number(self): 139 | return self._device.getSerialNumber() 140 | 141 | @property 142 | def port_path(self): 143 | return [self._device.getBusNumber()] + self._device.getPortNumberList() 144 | 145 | def Close(self): 146 | if self._handle is None: 147 | return 148 | try: 149 | self._handle.releaseInterface(self._interface_number) 150 | self._handle.close() 151 | except libusb1.USBError: 152 | _LOG.info('USBError while closing handle %s: ', 153 | self.usb_info, exc_info=True) 154 | finally: 155 | self._handle = None 156 | 157 | def Timeout(self, timeout_ms): 158 | return timeout_ms if timeout_ms is not None else self._timeout_ms 159 | 160 | def FlushBuffers(self): 161 | while True: 162 | try: 163 | self.BulkRead(self._max_read_packet_len, timeout_ms=10) 164 | except usb_exceptions.ReadFailedError as e: 165 | if e.usb_error.value == libusb1.LIBUSB_ERROR_TIMEOUT: 166 | break 167 | raise 168 | 169 | def BulkWrite(self, data, timeout_ms=None): 170 | if self._handle is None: 171 | raise usb_exceptions.WriteFailedError( 172 | 'This handle has been closed, probably due to another being opened.', 173 | None) 174 | try: 175 | return self._handle.bulkWrite( 176 | self._write_endpoint, data, timeout=self.Timeout(timeout_ms)) 177 | except libusb1.USBError as e: 178 | raise usb_exceptions.WriteFailedError( 179 | 'Could not send data to %s (timeout %sms)' % ( 180 | self.usb_info, self.Timeout(timeout_ms)), e) 181 | 182 | def BulkRead(self, length, timeout_ms=None): 183 | if self._handle is None: 184 | raise usb_exceptions.ReadFailedError( 185 | 'This handle has been closed, probably due to another being opened.', 186 | None) 187 | try: 188 | # python-libusb1 > 1.6 exposes bytearray()s now instead of bytes/str. 189 | # To support older and newer versions, we ensure everything's bytearray() 190 | # from here on out. 191 | return bytearray(self._handle.bulkRead( 192 | self._read_endpoint, length, timeout=self.Timeout(timeout_ms))) 193 | except libusb1.USBError as e: 194 | raise usb_exceptions.ReadFailedError( 195 | 'Could not receive data from %s (timeout %sms)' % ( 196 | self.usb_info, self.Timeout(timeout_ms)), e) 197 | 198 | def BulkReadAsync(self, length, timeout_ms=None): 199 | # See: https://pypi.python.org/pypi/libusb1 "Asynchronous I/O" section 200 | return 201 | 202 | @classmethod 203 | def PortPathMatcher(cls, port_path): 204 | """Returns a device matcher for the given port path.""" 205 | if isinstance(port_path, str): 206 | # Convert from sysfs path to port_path. 207 | port_path = [int(part) for part in SYSFS_PORT_SPLIT_RE.split(port_path)] 208 | return lambda device: device.port_path == port_path 209 | 210 | @classmethod 211 | def SerialMatcher(cls, serial): 212 | """Returns a device matcher for the given serial.""" 213 | return lambda device: device.serial_number == serial 214 | 215 | @classmethod 216 | def FindAndOpen(cls, setting_matcher, 217 | port_path=None, serial=None, timeout_ms=None): 218 | dev = cls.Find( 219 | setting_matcher, port_path=port_path, serial=serial, 220 | timeout_ms=timeout_ms) 221 | dev.Open() 222 | dev.FlushBuffers() 223 | return dev 224 | 225 | @classmethod 226 | def Find(cls, setting_matcher, port_path=None, serial=None, timeout_ms=None): 227 | """Gets the first device that matches according to the keyword args.""" 228 | if port_path: 229 | device_matcher = cls.PortPathMatcher(port_path) 230 | usb_info = port_path 231 | elif serial: 232 | device_matcher = cls.SerialMatcher(serial) 233 | usb_info = serial 234 | else: 235 | device_matcher = None 236 | usb_info = 'first' 237 | return cls.FindFirst(setting_matcher, device_matcher, 238 | usb_info=usb_info, timeout_ms=timeout_ms) 239 | 240 | @classmethod 241 | def FindFirst(cls, setting_matcher, device_matcher=None, **kwargs): 242 | """Find and return the first matching device. 243 | 244 | Args: 245 | setting_matcher: See cls.FindDevices. 246 | device_matcher: See cls.FindDevices. 247 | **kwargs: See cls.FindDevices. 248 | 249 | Returns: 250 | An instance of UsbHandle. 251 | 252 | Raises: 253 | DeviceNotFoundError: Raised if the device is not available. 254 | """ 255 | try: 256 | return next(cls.FindDevices( 257 | setting_matcher, device_matcher=device_matcher, **kwargs)) 258 | except StopIteration: 259 | raise usb_exceptions.DeviceNotFoundError( 260 | 'No device available, or it is in the wrong configuration.') 261 | 262 | @classmethod 263 | def FindDevices(cls, setting_matcher, device_matcher=None, 264 | usb_info='', timeout_ms=None): 265 | """Find and yield the devices that match. 266 | 267 | Args: 268 | setting_matcher: Function that returns the setting to use given a 269 | usb1.USBDevice, or None if the device doesn't have a valid setting. 270 | device_matcher: Function that returns True if the given UsbHandle is 271 | valid. None to match any device. 272 | usb_info: Info string describing device(s). 273 | timeout_ms: Default timeout of commands in milliseconds. 274 | 275 | Yields: 276 | UsbHandle instances 277 | """ 278 | ctx = usb1.USBContext() 279 | for device in ctx.getDeviceList(skip_on_error=True): 280 | setting = setting_matcher(device) 281 | if setting is None: 282 | continue 283 | 284 | handle = cls(device, setting, usb_info=usb_info, timeout_ms=timeout_ms) 285 | if device_matcher is None or device_matcher(handle): 286 | yield handle 287 | 288 | 289 | class TcpHandle(object): 290 | """TCP connection object. 291 | 292 | Provides same interface as UsbHandle. """ 293 | 294 | def __init__(self, serial, timeout_ms=None): 295 | """Initialize the TCP Handle. 296 | Arguments: 297 | serial: Android device serial of the form host or host:port. 298 | 299 | Host may be an IP address or a host name. 300 | """ 301 | # if necessary, convert serial to a unicode string 302 | if isinstance(serial, (bytes, bytearray)): 303 | serial = serial.decode('utf-8') 304 | 305 | if ':' in serial: 306 | self.host, self.port = serial.split(':') 307 | else: 308 | self.host = serial 309 | self.port = 5555 310 | 311 | self._connection = None 312 | self._serial_number = '%s:%s' % (self.host, self.port) 313 | self._timeout_ms = float(timeout_ms) if timeout_ms else None 314 | 315 | self._connect() 316 | 317 | def _connect(self): 318 | timeout = self.TimeoutSeconds(self._timeout_ms) 319 | self._connection = socket.create_connection((self.host, self.port), 320 | timeout=timeout) 321 | if timeout: 322 | self._connection.setblocking(0) 323 | 324 | @property 325 | def serial_number(self): 326 | return self._serial_number 327 | 328 | def BulkWrite(self, data, timeout=None): 329 | t = self.TimeoutSeconds(timeout) 330 | _, writeable, _ = select.select([], [self._connection], [], t) 331 | if writeable: 332 | return self._connection.send(data) 333 | msg = 'Sending data to {} timed out after {}s. No data was sent.'.format( 334 | self.serial_number, t) 335 | raise usb_exceptions.TcpTimeoutException(msg) 336 | 337 | def BulkRead(self, numbytes, timeout=None): 338 | t = self.TimeoutSeconds(timeout) 339 | readable, _, _ = select.select([self._connection], [], [], t) 340 | if readable: 341 | return self._connection.recv(numbytes) 342 | msg = 'Reading from {} timed out (Timeout {}s)'.format( 343 | self._serial_number, t) 344 | raise usb_exceptions.TcpTimeoutException(msg) 345 | 346 | def Timeout(self, timeout_ms): 347 | return float(timeout_ms) if timeout_ms is not None else self._timeout_ms 348 | 349 | def TimeoutSeconds(self, timeout_ms): 350 | timeout = self.Timeout(timeout_ms) 351 | return timeout / 1000.0 if timeout is not None else timeout 352 | 353 | def Close(self): 354 | return self._connection.close() 355 | -------------------------------------------------------------------------------- /adb/common_cli.py: -------------------------------------------------------------------------------- 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 | """Common code for ADB and Fastboot CLI. 15 | 16 | Usage introspects the given class for methods, args, and docs to show the user. 17 | 18 | StartCli handles connecting to a device, calling the expected method, and 19 | outputting the results. 20 | """ 21 | 22 | from __future__ import print_function 23 | import argparse 24 | import io 25 | import inspect 26 | import logging 27 | import re 28 | import sys 29 | import types 30 | 31 | from adb import usb_exceptions 32 | 33 | 34 | class _PortPathAction(argparse.Action): 35 | def __call__(self, parser, namespace, values, option_string=None): 36 | setattr( 37 | namespace, self.dest, 38 | [int(i) for i in values.replace('/', ',').split(',')]) 39 | 40 | 41 | class PositionalArg(argparse.Action): 42 | def __call__(self, parser, namespace, values, option_string=None): 43 | namespace.positional.append(values) 44 | 45 | 46 | def GetDeviceArguments(): 47 | group = argparse.ArgumentParser('Device', add_help=False) 48 | group.add_argument( 49 | '--timeout_ms', default=10000, type=int, metavar='10000', 50 | help='Timeout in milliseconds.') 51 | group.add_argument( 52 | '--port_path', action=_PortPathAction, 53 | help='USB port path integers (eg 1,2 or 2,1,1)') 54 | group.add_argument( 55 | '-s', '--serial', 56 | help='Device serial to look for (host:port or USB serial)') 57 | return group 58 | 59 | 60 | def GetCommonArguments(): 61 | group = argparse.ArgumentParser('Common', add_help=False) 62 | group.add_argument('--verbose', action='store_true', help='Enable logging') 63 | return group 64 | 65 | 66 | def _DocToArgs(doc): 67 | """Converts a docstring documenting arguments into a dict.""" 68 | m = None 69 | offset = None 70 | in_arg = False 71 | out = {} 72 | for l in doc.splitlines(): 73 | if l.strip() == 'Args:': 74 | in_arg = True 75 | elif in_arg: 76 | if not l.strip(): 77 | break 78 | if offset is None: 79 | offset = len(l) - len(l.lstrip()) 80 | l = l[offset:] 81 | if l[0] == ' ' and m: 82 | out[m.group(1)] += ' ' + l.lstrip() 83 | else: 84 | m = re.match(r'^([a-z_]+): (.+)$', l.strip()) 85 | out[m.group(1)] = m.group(2) 86 | return out 87 | 88 | 89 | def MakeSubparser(subparsers, parents, method, arguments=None): 90 | """Returns an argparse subparser to create a 'subcommand' to adb.""" 91 | name = ('-'.join(re.split(r'([A-Z][a-z]+)', method.__name__)[1:-1:2])).lower() 92 | help = method.__doc__.splitlines()[0] 93 | subparser = subparsers.add_parser( 94 | name=name, description=help, help=help.rstrip('.'), parents=parents) 95 | subparser.set_defaults(method=method, positional=[]) 96 | argspec = inspect.getargspec(method) 97 | 98 | # Figure out positionals and default argument, if any. Explicitly includes 99 | # arguments that default to '' but excludes arguments that default to None. 100 | offset = len(argspec.args) - len(argspec.defaults or []) - 1 101 | positional = [] 102 | for i in range(1, len(argspec.args)): 103 | if i > offset and argspec.defaults[i - offset - 1] is None: 104 | break 105 | positional.append(argspec.args[i]) 106 | defaults = [None] * offset + list(argspec.defaults or []) 107 | 108 | # Add all arguments so they append to args.positional. 109 | args_help = _DocToArgs(method.__doc__) 110 | for name, default in zip(positional, defaults): 111 | if not isinstance(default, (None.__class__, str)): 112 | continue 113 | subparser.add_argument( 114 | name, help=(arguments or {}).get(name, args_help.get(name)), 115 | default=default, nargs='?' if default is not None else None, 116 | action=PositionalArg) 117 | if argspec.varargs: 118 | subparser.add_argument( 119 | argspec.varargs, nargs=argparse.REMAINDER, 120 | help=(arguments or {}).get(argspec.varargs, args_help.get(argspec.varargs))) 121 | return subparser 122 | 123 | 124 | def _RunMethod(dev, args, extra): 125 | """Runs a method registered via MakeSubparser.""" 126 | logging.info('%s(%s)', args.method.__name__, ', '.join(args.positional)) 127 | result = args.method(dev, *args.positional, **extra) 128 | if result is not None: 129 | if isinstance(result, io.StringIO): 130 | sys.stdout.write(result.getvalue()) 131 | elif isinstance(result, (list, types.GeneratorType)): 132 | r = '' 133 | for r in result: 134 | r = str(r) 135 | sys.stdout.write(r) 136 | if not r.endswith('\n'): 137 | sys.stdout.write('\n') 138 | else: 139 | result = str(result) 140 | sys.stdout.write(result) 141 | if not result.endswith('\n'): 142 | sys.stdout.write('\n') 143 | return 0 144 | 145 | 146 | def StartCli(args, adb_commands, extra=None, **device_kwargs): 147 | """Starts a common CLI interface for this usb path and protocol.""" 148 | try: 149 | dev = adb_commands() 150 | dev.ConnectDevice(port_path=args.port_path, serial=args.serial, default_timeout_ms=args.timeout_ms, 151 | **device_kwargs) 152 | except usb_exceptions.DeviceNotFoundError as e: 153 | print('No device found: {}'.format(e), file=sys.stderr) 154 | return 1 155 | except usb_exceptions.CommonUsbError as e: 156 | print('Could not connect to device: {}'.format(e), file=sys.stderr) 157 | return 1 158 | try: 159 | return _RunMethod(dev, args, extra or {}) 160 | except Exception as e: # pylint: disable=broad-except 161 | sys.stdout.write(str(e)) 162 | return 1 163 | finally: 164 | dev.Close() 165 | -------------------------------------------------------------------------------- /adb/fastboot.py: -------------------------------------------------------------------------------- 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 | """A libusb1-based fastboot implementation.""" 15 | 16 | import binascii 17 | import collections 18 | import io 19 | import logging 20 | import os 21 | import struct 22 | 23 | from adb import common 24 | from adb import usb_exceptions 25 | 26 | _LOG = logging.getLogger('fastboot') 27 | 28 | DEFAULT_MESSAGE_CALLBACK = lambda m: logging.info('Got %s from device', m) 29 | FastbootMessage = collections.namedtuple( # pylint: disable=invalid-name 30 | 'FastbootMessage', ['message', 'header']) 31 | 32 | # From fastboot.c 33 | VENDORS = {0x18D1, 0x0451, 0x0502, 0x0FCE, 0x05C6, 0x22B8, 0x0955, 34 | 0x413C, 0x2314, 0x0BB4, 0x8087} 35 | CLASS = 0xFF 36 | SUBCLASS = 0x42 37 | PROTOCOL = 0x03 38 | # pylint: disable=invalid-name 39 | DeviceIsAvailable = common.InterfaceMatcher(CLASS, SUBCLASS, PROTOCOL) 40 | 41 | 42 | # pylint doesn't understand cross-module exception baseclasses. 43 | # pylint: disable=nonstandard-exception 44 | class FastbootTransferError(usb_exceptions.FormatMessageWithArgumentsException): 45 | """Transfer error.""" 46 | 47 | 48 | class FastbootRemoteFailure(usb_exceptions.FormatMessageWithArgumentsException): 49 | """Remote error.""" 50 | 51 | 52 | class FastbootStateMismatch(usb_exceptions.FormatMessageWithArgumentsException): 53 | """Fastboot and uboot's state machines are arguing. You Lose.""" 54 | 55 | 56 | class FastbootInvalidResponse( 57 | usb_exceptions.FormatMessageWithArgumentsException): 58 | """Fastboot responded with a header we didn't expect.""" 59 | 60 | 61 | class FastbootProtocol(object): 62 | """Encapsulates the fastboot protocol.""" 63 | FINAL_HEADERS = {b'OKAY', b'DATA'} 64 | 65 | def __init__(self, usb, chunk_kb=1024): 66 | """Constructs a FastbootProtocol instance. 67 | 68 | Args: 69 | usb: UsbHandle instance. 70 | chunk_kb: Packet size. For older devices, 4 may be required. 71 | """ 72 | self.usb = usb 73 | self.chunk_kb = chunk_kb 74 | 75 | @property 76 | def usb_handle(self): 77 | return self.usb 78 | 79 | def SendCommand(self, command, arg=None): 80 | """Sends a command to the device. 81 | 82 | Args: 83 | command: The command to send. 84 | arg: Optional argument to the command. 85 | """ 86 | if arg is not None: 87 | if not isinstance(arg, bytes): 88 | arg = arg.encode('utf8') 89 | command = b'%s:%s' % (command, arg) 90 | 91 | self._Write(io.BytesIO(command), len(command)) 92 | 93 | def HandleSimpleResponses( 94 | self, timeout_ms=None, info_cb=DEFAULT_MESSAGE_CALLBACK): 95 | """Accepts normal responses from the device. 96 | 97 | Args: 98 | timeout_ms: Timeout in milliseconds to wait for each response. 99 | info_cb: Optional callback for text sent from the bootloader. 100 | 101 | Returns: 102 | OKAY packet's message. 103 | """ 104 | return self._AcceptResponses(b'OKAY', info_cb, timeout_ms=timeout_ms) 105 | 106 | def HandleDataSending(self, source_file, source_len, 107 | info_cb=DEFAULT_MESSAGE_CALLBACK, 108 | progress_callback=None, timeout_ms=None): 109 | """Handles the protocol for sending data to the device. 110 | 111 | Args: 112 | source_file: File-object to read from for the device. 113 | source_len: Amount of data, in bytes, to send to the device. 114 | info_cb: Optional callback for text sent from the bootloader. 115 | progress_callback: Callback that takes the current and the total progress 116 | of the current file. 117 | timeout_ms: Timeout in milliseconds to wait for each response. 118 | 119 | Raises: 120 | FastbootTransferError: When fastboot can't handle this amount of data. 121 | FastbootStateMismatch: Fastboot responded with the wrong packet type. 122 | FastbootRemoteFailure: Fastboot reported failure. 123 | FastbootInvalidResponse: Fastboot responded with an unknown packet type. 124 | 125 | Returns: 126 | OKAY packet's message. 127 | """ 128 | accepted_size = self._AcceptResponses( 129 | b'DATA', info_cb, timeout_ms=timeout_ms) 130 | 131 | accepted_size = binascii.unhexlify(accepted_size[:8]) 132 | accepted_size, = struct.unpack(b'>I', accepted_size) 133 | if accepted_size != source_len: 134 | raise FastbootTransferError( 135 | 'Device refused to download %s bytes of data (accepts %s bytes)', 136 | source_len, accepted_size) 137 | self._Write(source_file, accepted_size, progress_callback) 138 | return self._AcceptResponses(b'OKAY', info_cb, timeout_ms=timeout_ms) 139 | 140 | def _AcceptResponses(self, expected_header, info_cb, timeout_ms=None): 141 | """Accepts responses until the expected header or a FAIL. 142 | 143 | Args: 144 | expected_header: OKAY or DATA 145 | info_cb: Optional callback for text sent from the bootloader. 146 | timeout_ms: Timeout in milliseconds to wait for each response. 147 | 148 | Raises: 149 | FastbootStateMismatch: Fastboot responded with the wrong packet type. 150 | FastbootRemoteFailure: Fastboot reported failure. 151 | FastbootInvalidResponse: Fastboot responded with an unknown packet type. 152 | 153 | Returns: 154 | OKAY packet's message. 155 | """ 156 | while True: 157 | response = self.usb.BulkRead(64, timeout_ms=timeout_ms) 158 | header = bytes(response[:4]) 159 | remaining = bytes(response[4:]) 160 | 161 | if header == b'INFO': 162 | info_cb(FastbootMessage(remaining, header)) 163 | elif header in self.FINAL_HEADERS: 164 | if header != expected_header: 165 | raise FastbootStateMismatch( 166 | 'Expected %s, got %s', expected_header, header) 167 | if header == b'OKAY': 168 | info_cb(FastbootMessage(remaining, header)) 169 | return remaining 170 | elif header == b'FAIL': 171 | info_cb(FastbootMessage(remaining, header)) 172 | raise FastbootRemoteFailure('FAIL: %s', remaining) 173 | else: 174 | raise FastbootInvalidResponse( 175 | 'Got unknown header %s and response %s', header, remaining) 176 | 177 | def _HandleProgress(self, total, progress_callback): 178 | """Calls the callback with the current progress and total .""" 179 | current = 0 180 | while True: 181 | current += yield 182 | try: 183 | progress_callback(current, total) 184 | except Exception: # pylint: disable=broad-except 185 | _LOG.exception('Progress callback raised an exception. %s', 186 | progress_callback) 187 | continue 188 | 189 | def _Write(self, data, length, progress_callback=None): 190 | """Sends the data to the device, tracking progress with the callback.""" 191 | if progress_callback: 192 | progress = self._HandleProgress(length, progress_callback) 193 | next(progress) 194 | while length: 195 | tmp = data.read(self.chunk_kb * 1024) 196 | length -= len(tmp) 197 | self.usb.BulkWrite(tmp) 198 | 199 | if progress_callback and progress: 200 | progress.send(len(tmp)) 201 | 202 | 203 | class FastbootCommands(object): 204 | """Encapsulates the fastboot commands.""" 205 | 206 | def __init__(self): 207 | """Constructs a FastbootCommands instance. 208 | 209 | Args: 210 | usb: UsbHandle instance. 211 | """ 212 | self.__reset() 213 | 214 | def __reset(self): 215 | self._handle = None 216 | self._protocol = None 217 | 218 | @property 219 | def usb_handle(self): 220 | return self._handle 221 | 222 | def Close(self): 223 | self._handle.Close() 224 | 225 | def ConnectDevice(self, port_path=None, serial=None, default_timeout_ms=None, chunk_kb=1024, **kwargs): 226 | """Convenience function to get an adb device from usb path or serial. 227 | 228 | Args: 229 | port_path: The filename of usb port to use. 230 | serial: The serial number of the device to use. 231 | default_timeout_ms: The default timeout in milliseconds to use. 232 | chunk_kb: Amount of data, in kilobytes, to break fastboot packets up into 233 | kwargs: handle: Device handle to use (instance of common.TcpHandle or common.UsbHandle) 234 | banner: Connection banner to pass to the remote device 235 | rsa_keys: List of AuthSigner subclass instances to be used for 236 | authentication. The device can either accept one of these via the Sign 237 | method, or we will send the result of GetPublicKey from the first one 238 | if the device doesn't accept any of them. 239 | auth_timeout_ms: Timeout to wait for when sending a new public key. This 240 | is only relevant when we send a new public key. The device shows a 241 | dialog and this timeout is how long to wait for that dialog. If used 242 | in automation, this should be low to catch such a case as a failure 243 | quickly; while in interactive settings it should be high to allow 244 | users to accept the dialog. We default to automation here, so it's low 245 | by default. 246 | 247 | If serial specifies a TCP address:port, then a TCP connection is 248 | used instead of a USB connection. 249 | """ 250 | 251 | if 'handle' in kwargs: 252 | self._handle = kwargs['handle'] 253 | 254 | else: 255 | self._handle = common.UsbHandle.FindAndOpen( 256 | DeviceIsAvailable, port_path=port_path, serial=serial, 257 | timeout_ms=default_timeout_ms) 258 | 259 | self._protocol = FastbootProtocol(self._handle, chunk_kb) 260 | 261 | return self 262 | 263 | @classmethod 264 | def Devices(cls): 265 | """Get a generator of UsbHandle for devices available.""" 266 | return common.UsbHandle.FindDevices(DeviceIsAvailable) 267 | 268 | def _SimpleCommand(self, command, arg=None, **kwargs): 269 | self._protocol.SendCommand(command, arg) 270 | return self._protocol.HandleSimpleResponses(**kwargs) 271 | 272 | def FlashFromFile(self, partition, source_file, source_len=0, 273 | info_cb=DEFAULT_MESSAGE_CALLBACK, progress_callback=None): 274 | """Flashes a partition from the file on disk. 275 | 276 | Args: 277 | partition: Partition name to flash to. 278 | source_file: Filename to download to the device. 279 | source_len: Optional length of source_file, uses os.stat if not provided. 280 | info_cb: See Download. 281 | progress_callback: See Download. 282 | 283 | Returns: 284 | Download and flash responses, normally nothing. 285 | """ 286 | if source_len == 0: 287 | # Fall back to stat. 288 | source_len = os.stat(source_file).st_size 289 | download_response = self.Download( 290 | source_file, source_len=source_len, info_cb=info_cb, 291 | progress_callback=progress_callback) 292 | flash_response = self.Flash(partition, info_cb=info_cb) 293 | return download_response + flash_response 294 | 295 | def Download(self, source_file, source_len=0, 296 | info_cb=DEFAULT_MESSAGE_CALLBACK, progress_callback=None): 297 | """Downloads a file to the device. 298 | 299 | Args: 300 | source_file: A filename or file-like object to download to the device. 301 | source_len: Optional length of source_file. If source_file is a file-like 302 | object and source_len is not provided, source_file is read into 303 | memory. 304 | info_cb: Optional callback accepting FastbootMessage for text sent from 305 | the bootloader. 306 | progress_callback: Optional callback called with the percent of the 307 | source_file downloaded. Note, this doesn't include progress of the 308 | actual flashing. 309 | 310 | Returns: 311 | Response to a download request, normally nothing. 312 | """ 313 | if isinstance(source_file, str): 314 | source_len = os.stat(source_file).st_size 315 | source_file = open(source_file) 316 | 317 | with source_file: 318 | if source_len == 0: 319 | # Fall back to storing it all in memory :( 320 | data = source_file.read() 321 | source_file = io.BytesIO(data.encode('utf8')) 322 | source_len = len(data) 323 | 324 | self._protocol.SendCommand(b'download', b'%08x' % source_len) 325 | return self._protocol.HandleDataSending( 326 | source_file, source_len, info_cb, progress_callback=progress_callback) 327 | 328 | def Flash(self, partition, timeout_ms=0, info_cb=DEFAULT_MESSAGE_CALLBACK): 329 | """Flashes the last downloaded file to the given partition. 330 | 331 | Args: 332 | partition: Partition to overwrite with the new image. 333 | timeout_ms: Optional timeout in milliseconds to wait for it to finish. 334 | info_cb: See Download. Usually no messages. 335 | 336 | Returns: 337 | Response to a download request, normally nothing. 338 | """ 339 | return self._SimpleCommand(b'flash', arg=partition, info_cb=info_cb, 340 | timeout_ms=timeout_ms) 341 | 342 | def Erase(self, partition, timeout_ms=None): 343 | """Erases the given partition. 344 | 345 | Args: 346 | partition: Partition to clear. 347 | """ 348 | self._SimpleCommand(b'erase', arg=partition, timeout_ms=timeout_ms) 349 | 350 | def Getvar(self, var, info_cb=DEFAULT_MESSAGE_CALLBACK): 351 | """Returns the given variable's definition. 352 | 353 | Args: 354 | var: A variable the bootloader tracks. Use 'all' to get them all. 355 | info_cb: See Download. Usually no messages. 356 | 357 | Returns: 358 | Value of var according to the current bootloader. 359 | """ 360 | return self._SimpleCommand(b'getvar', arg=var, info_cb=info_cb) 361 | 362 | def Oem(self, command, timeout_ms=None, info_cb=DEFAULT_MESSAGE_CALLBACK): 363 | """Executes an OEM command on the device. 364 | 365 | Args: 366 | command: Command to execute, such as 'poweroff' or 'bootconfig read'. 367 | timeout_ms: Optional timeout in milliseconds to wait for a response. 368 | info_cb: See Download. Messages vary based on command. 369 | 370 | Returns: 371 | The final response from the device. 372 | """ 373 | if not isinstance(command, bytes): 374 | command = command.encode('utf8') 375 | return self._SimpleCommand( 376 | b'oem %s' % command, timeout_ms=timeout_ms, info_cb=info_cb) 377 | 378 | def Continue(self): 379 | """Continues execution past fastboot into the system.""" 380 | return self._SimpleCommand(b'continue') 381 | 382 | def Reboot(self, target_mode=b'', timeout_ms=None): 383 | """Reboots the device. 384 | 385 | Args: 386 | target_mode: Normal reboot when unspecified. Can specify other target 387 | modes such as 'recovery' or 'bootloader'. 388 | timeout_ms: Optional timeout in milliseconds to wait for a response. 389 | 390 | Returns: 391 | Usually the empty string. Depends on the bootloader and the target_mode. 392 | """ 393 | return self._SimpleCommand( 394 | b'reboot', arg=target_mode or None, timeout_ms=timeout_ms) 395 | 396 | def RebootBootloader(self, timeout_ms=None): 397 | """Reboots into the bootloader, usually equiv to Reboot('bootloader').""" 398 | return self._SimpleCommand(b'reboot-bootloader', timeout_ms=timeout_ms) 399 | -------------------------------------------------------------------------------- /adb/fastboot_debug.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Copyright 2014 Google Inc. All rights reserved. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | """Fastboot in python. 17 | 18 | Call it similar to how you call android's fastboot. Call it similar to how you 19 | call android's fastboot, but this only accepts usb paths and no serials. 20 | """ 21 | 22 | import argparse 23 | import inspect 24 | import logging 25 | import sys 26 | 27 | from adb import common_cli 28 | from adb import fastboot 29 | 30 | try: 31 | import progressbar 32 | except ImportError: 33 | # progressbar is optional. 34 | progressbar = None 35 | 36 | 37 | def Devices(args): 38 | """Lists the available devices. 39 | 40 | List of devices attached 41 | 015DB7591102001A device 42 | """ 43 | for device in fastboot.FastbootCommands.Devices(): 44 | print('%s\tdevice' % device.serial_number) 45 | return 0 46 | 47 | 48 | def _InfoCb(message): 49 | # Use an unbuffered version of stdout. 50 | if not message.message: 51 | return 52 | sys.stdout.write('%s: %s\n' % (message.header, message.message)) 53 | sys.stdout.flush() 54 | 55 | 56 | def main(): 57 | common = common_cli.GetCommonArguments() 58 | device = common_cli.GetDeviceArguments() 59 | device.add_argument( 60 | '--chunk_kb', type=int, default=1024, metavar='1024', 61 | help='Size of packets to write in Kb. For older devices, it may be ' 62 | 'required to use 4.') 63 | parents = [common, device] 64 | 65 | parser = argparse.ArgumentParser( 66 | description=sys.modules[__name__].__doc__, parents=[common]) 67 | subparsers = parser.add_subparsers(title='Commands', dest='command_name') 68 | 69 | subparser = subparsers.add_parser( 70 | name='help', help='Prints the commands available') 71 | subparser = subparsers.add_parser( 72 | name='devices', help='Lists the available devices', parents=[common]) 73 | common_cli.MakeSubparser( 74 | subparsers, parents, fastboot.FastbootCommands.Continue) 75 | 76 | common_cli.MakeSubparser( 77 | subparsers, parents, fastboot.FastbootCommands.Download, 78 | {'source_file': 'Filename on the host to push'}) 79 | common_cli.MakeSubparser( 80 | subparsers, parents, fastboot.FastbootCommands.Erase) 81 | common_cli.MakeSubparser( 82 | subparsers, parents, fastboot.FastbootCommands.Flash) 83 | common_cli.MakeSubparser( 84 | subparsers, parents, fastboot.FastbootCommands.Getvar) 85 | common_cli.MakeSubparser( 86 | subparsers, parents, fastboot.FastbootCommands.Oem) 87 | common_cli.MakeSubparser( 88 | subparsers, parents, fastboot.FastbootCommands.Reboot) 89 | 90 | if len(sys.argv) == 1: 91 | parser.print_help() 92 | return 2 93 | 94 | args = parser.parse_args() 95 | if args.verbose: 96 | logging.basicConfig(level=logging.DEBUG) 97 | if args.command_name == 'devices': 98 | return Devices(args) 99 | if args.command_name == 'help': 100 | parser.print_help() 101 | return 0 102 | 103 | kwargs = {} 104 | argspec = inspect.getargspec(args.method) 105 | if 'info_cb' in argspec.args: 106 | kwargs['info_cb'] = _InfoCb 107 | if 'progress_callback' in argspec.args and progressbar: 108 | bar = progressbar.ProgessBar( 109 | widgets=[progressbar.Bar(), progressbar.Percentage()]) 110 | bar.start() 111 | 112 | def SetProgress(current, total): 113 | bar.update(current / total * 100.0) 114 | if current == total: 115 | bar.finish() 116 | 117 | kwargs['progress_callback'] = SetProgress 118 | 119 | return common_cli.StartCli( 120 | args, 121 | fastboot.FastbootCommands, 122 | chunk_kb=args.chunk_kb, 123 | extra=kwargs) 124 | 125 | 126 | if __name__ == '__main__': 127 | sys.exit(main()) 128 | -------------------------------------------------------------------------------- /adb/filesync_protocol.py: -------------------------------------------------------------------------------- 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 | 16 | Implements the ADB protocol as seen in android's adb/adbd binaries, but only the 17 | host side. 18 | """ 19 | 20 | import collections 21 | import os 22 | import stat 23 | import struct 24 | import time 25 | 26 | import libusb1 27 | 28 | from adb import adb_protocol 29 | from adb import usb_exceptions 30 | 31 | # Default mode for pushed files. 32 | DEFAULT_PUSH_MODE = stat.S_IFREG | stat.S_IRWXU | stat.S_IRWXG 33 | # Maximum size of a filesync DATA packet. 34 | MAX_PUSH_DATA = 2 * 1024 35 | 36 | 37 | class InvalidChecksumError(Exception): 38 | """Checksum of data didn't match expected checksum.""" 39 | 40 | 41 | class InterleavedDataError(Exception): 42 | """We only support command sent serially.""" 43 | 44 | 45 | class PushFailedError(Exception): 46 | """Pushing a file failed for some reason.""" 47 | 48 | 49 | class PullFailedError(Exception): 50 | """Pulling a file failed for some reason.""" 51 | 52 | 53 | DeviceFile = collections.namedtuple('DeviceFile', [ 54 | 'filename', 'mode', 'size', 'mtime']) 55 | 56 | 57 | class FilesyncProtocol(object): 58 | """Implements the FileSync protocol as described in sync.txt.""" 59 | 60 | @staticmethod 61 | def Stat(connection, filename): 62 | cnxn = FileSyncConnection(connection, b'<4I') 63 | cnxn.Send(b'STAT', filename) 64 | command, (mode, size, mtime) = cnxn.Read((b'STAT',), read_data=False) 65 | 66 | if command != b'STAT': 67 | raise adb_protocol.InvalidResponseError( 68 | 'Expected STAT response to STAT, got %s' % command) 69 | return mode, size, mtime 70 | 71 | @classmethod 72 | def List(cls, connection, path): 73 | cnxn = FileSyncConnection(connection, b'<5I') 74 | cnxn.Send(b'LIST', path) 75 | files = [] 76 | for cmd_id, header, filename in cnxn.ReadUntil((b'DENT',), b'DONE'): 77 | if cmd_id == b'DONE': 78 | break 79 | mode, size, mtime = header 80 | files.append(DeviceFile(filename, mode, size, mtime)) 81 | return files 82 | 83 | @classmethod 84 | def Pull(cls, connection, filename, dest_file, progress_callback): 85 | """Pull a file from the device into the file-like dest_file.""" 86 | if progress_callback: 87 | total_bytes = cls.Stat(connection, filename)[1] 88 | progress = cls._HandleProgress(lambda current: progress_callback(filename, current, total_bytes)) 89 | next(progress) 90 | 91 | cnxn = FileSyncConnection(connection, b'<2I') 92 | try: 93 | cnxn.Send(b'RECV', filename) 94 | for cmd_id, _, data in cnxn.ReadUntil((b'DATA',), b'DONE'): 95 | if cmd_id == b'DONE': 96 | break 97 | dest_file.write(data) 98 | if progress_callback: 99 | progress.send(len(data)) 100 | except usb_exceptions.CommonUsbError as e: 101 | raise PullFailedError('Unable to pull file %s due to: %s' % (filename, e)) 102 | 103 | @classmethod 104 | def _HandleProgress(cls, progress_callback): 105 | """Calls the callback with the current progress and total bytes written/received. 106 | 107 | Args: 108 | progress_callback: callback method that accepts filename, bytes_written and total_bytes, 109 | total_bytes will be -1 for file-like objects 110 | """ 111 | current = 0 112 | while True: 113 | current += yield 114 | try: 115 | progress_callback(current) 116 | except Exception: # pylint: disable=broad-except 117 | continue 118 | 119 | @classmethod 120 | def Push(cls, connection, datafile, filename, 121 | st_mode=DEFAULT_PUSH_MODE, mtime=0, progress_callback=None): 122 | """Push a file-like object to the device. 123 | 124 | Args: 125 | connection: ADB connection 126 | datafile: File-like object for reading from 127 | filename: Filename to push to 128 | st_mode: stat mode for filename 129 | mtime: modification time 130 | progress_callback: callback method that accepts filename, bytes_written and total_bytes 131 | 132 | Raises: 133 | PushFailedError: Raised on push failure. 134 | """ 135 | 136 | fileinfo = ('{},{}'.format(filename, int(st_mode))).encode('utf-8') 137 | 138 | cnxn = FileSyncConnection(connection, b'<2I') 139 | cnxn.Send(b'SEND', fileinfo) 140 | 141 | if progress_callback: 142 | total_bytes = os.fstat(datafile.fileno()).st_size if isinstance(datafile, file) else -1 143 | progress = cls._HandleProgress(lambda current: progress_callback(filename, current, total_bytes)) 144 | next(progress) 145 | 146 | while True: 147 | data = datafile.read(MAX_PUSH_DATA) 148 | if data: 149 | cnxn.Send(b'DATA', data) 150 | 151 | if progress_callback: 152 | progress.send(len(data)) 153 | else: 154 | break 155 | 156 | if mtime == 0: 157 | mtime = int(time.time()) 158 | # DONE doesn't send data, but it hides the last bit of data in the size 159 | # field. 160 | cnxn.Send(b'DONE', size=mtime) 161 | for cmd_id, _, data in cnxn.ReadUntil((), b'OKAY', b'FAIL'): 162 | if cmd_id == b'OKAY': 163 | return 164 | raise PushFailedError(data) 165 | 166 | 167 | class FileSyncConnection(object): 168 | """Encapsulate a FileSync service connection.""" 169 | 170 | ids = [ 171 | b'STAT', b'LIST', b'SEND', b'RECV', b'DENT', b'DONE', b'DATA', b'OKAY', 172 | b'FAIL', b'QUIT', 173 | ] 174 | id_to_wire, wire_to_id = adb_protocol.MakeWireIDs(ids) 175 | 176 | def __init__(self, adb_connection, recv_header_format): 177 | self.adb = adb_connection 178 | 179 | # Sending 180 | # Using a bytearray() saves a copy later when using libusb. 181 | self.send_buffer = bytearray(adb_protocol.MAX_ADB_DATA) 182 | self.send_idx = 0 183 | self.send_header_len = struct.calcsize(b'<2I') 184 | 185 | # Receiving 186 | self.recv_buffer = bytearray() 187 | self.recv_header_format = recv_header_format 188 | self.recv_header_len = struct.calcsize(recv_header_format) 189 | 190 | def Send(self, command_id, data=b'', size=0): 191 | """Send/buffer FileSync packets. 192 | 193 | Packets are buffered and only flushed when this connection is read from. All 194 | messages have a response from the device, so this will always get flushed. 195 | 196 | Args: 197 | command_id: Command to send. 198 | data: Optional data to send, must set data or size. 199 | size: Optionally override size from len(data). 200 | """ 201 | if data: 202 | if not isinstance(data, bytes): 203 | data = data.encode('utf8') 204 | size = len(data) 205 | 206 | if not self._CanAddToSendBuffer(len(data)): 207 | self._Flush() 208 | buf = struct.pack(b'<2I', self.id_to_wire[command_id], size) + data 209 | self.send_buffer[self.send_idx:self.send_idx + len(buf)] = buf 210 | self.send_idx += len(buf) 211 | 212 | def Read(self, expected_ids, read_data=True): 213 | """Read ADB messages and return FileSync packets.""" 214 | if self.send_idx: 215 | self._Flush() 216 | 217 | # Read one filesync packet off the recv buffer. 218 | header_data = self._ReadBuffered(self.recv_header_len) 219 | header = struct.unpack(self.recv_header_format, header_data) 220 | # Header is (ID, ...). 221 | command_id = self.wire_to_id[header[0]] 222 | 223 | if command_id not in expected_ids: 224 | if command_id == b'FAIL': 225 | reason = '' 226 | if self.recv_buffer: 227 | reason = self.recv_buffer.decode('utf-8', errors='ignore') 228 | raise usb_exceptions.AdbCommandFailureException('Command failed: {}'.format(reason)) 229 | raise adb_protocol.InvalidResponseError( 230 | 'Expected one of %s, got %s' % (expected_ids, command_id)) 231 | 232 | if not read_data: 233 | return command_id, header[1:] 234 | 235 | # Header is (ID, ..., size). 236 | size = header[-1] 237 | data = self._ReadBuffered(size) 238 | return command_id, header[1:-1], data 239 | 240 | def ReadUntil(self, expected_ids, *finish_ids): 241 | """Useful wrapper around Read.""" 242 | while True: 243 | cmd_id, header, data = self.Read(expected_ids + finish_ids) 244 | yield cmd_id, header, data 245 | if cmd_id in finish_ids: 246 | break 247 | 248 | def _CanAddToSendBuffer(self, data_len): 249 | added_len = self.send_header_len + data_len 250 | return self.send_idx + added_len < adb_protocol.MAX_ADB_DATA 251 | 252 | def _Flush(self): 253 | try: 254 | self.adb.Write(self.send_buffer[:self.send_idx]) 255 | except libusb1.USBError as e: 256 | raise adb_protocol.SendFailedError( 257 | 'Could not send data %s' % self.send_buffer, e) 258 | self.send_idx = 0 259 | 260 | def _ReadBuffered(self, size): 261 | # Ensure recv buffer has enough data. 262 | while len(self.recv_buffer) < size: 263 | _, data = self.adb.ReadUntil(b'WRTE') 264 | self.recv_buffer += data 265 | 266 | result = self.recv_buffer[:size] 267 | self.recv_buffer = self.recv_buffer[size:] 268 | return result 269 | -------------------------------------------------------------------------------- /adb/sign_cryptography.py: -------------------------------------------------------------------------------- 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 | 15 | from adb import adb_protocol 16 | 17 | from cryptography.hazmat.backends import default_backend 18 | from cryptography.hazmat.primitives import hashes 19 | from cryptography.hazmat.primitives import serialization 20 | from cryptography.hazmat.primitives.asymmetric import padding 21 | from cryptography.hazmat.primitives.asymmetric import utils 22 | 23 | 24 | class CryptographySigner(adb_protocol.AuthSigner): 25 | """AuthSigner using cryptography.io.""" 26 | 27 | def __init__(self, rsa_key_path): 28 | with open(rsa_key_path + '.pub') as rsa_pub_file: 29 | self.public_key = rsa_pub_file.read() 30 | 31 | with open(rsa_key_path) as rsa_prv_file: 32 | self.rsa_key = serialization.load_pem_private_key( 33 | rsa_prv_file.read(), None, default_backend()) 34 | 35 | def Sign(self, data): 36 | return self.rsa_key.sign( 37 | data, padding.PKCS1v15(), utils.Prehashed(hashes.SHA1())) 38 | 39 | def GetPublicKey(self): 40 | return self.public_key 41 | -------------------------------------------------------------------------------- /adb/sign_pycryptodome.py: -------------------------------------------------------------------------------- 1 | from adb import adb_protocol 2 | 3 | from Crypto.Hash import SHA256 4 | from Crypto.PublicKey import RSA 5 | from Crypto.Signature import pkcs1_15 6 | 7 | 8 | class PycryptodomeAuthSigner(adb_protocol.AuthSigner): 9 | 10 | def __init__(self, rsa_key_path=None): 11 | super(PycryptodomeAuthSigner, self).__init__() 12 | 13 | if rsa_key_path: 14 | with open(rsa_key_path + '.pub', 'rb') as rsa_pub_file: 15 | self.public_key = rsa_pub_file.read() 16 | 17 | with open(rsa_key_path, 'rb') as rsa_priv_file: 18 | self.rsa_key = RSA.import_key(rsa_priv_file.read()) 19 | 20 | def Sign(self, data): 21 | h = SHA256.new(data) 22 | return pkcs1_15.new(self.rsa_key).sign(h) 23 | 24 | def GetPublicKey(self): 25 | return self.public_key 26 | -------------------------------------------------------------------------------- /adb/sign_pythonrsa.py: -------------------------------------------------------------------------------- 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 | 15 | import rsa 16 | 17 | from pyasn1.codec.der import decoder 18 | from pyasn1.type import univ 19 | from rsa import pkcs1 20 | 21 | 22 | # python-rsa lib hashes all messages it signs. ADB does it already, we just 23 | # need to slap a signature on top of already hashed message. Introduce "fake" 24 | # hashing algo for this. 25 | class _Accum(object): 26 | def __init__(self): 27 | self._buf = b'' 28 | 29 | def update(self, msg): 30 | self._buf += msg 31 | 32 | def digest(self): 33 | return self._buf 34 | 35 | 36 | pkcs1.HASH_METHODS['SHA-1-PREHASHED'] = _Accum 37 | pkcs1.HASH_ASN1['SHA-1-PREHASHED'] = pkcs1.HASH_ASN1['SHA-1'] 38 | 39 | 40 | def _load_rsa_private_key(pem): 41 | """PEM encoded PKCS#8 private key -> rsa.PrivateKey.""" 42 | # ADB uses private RSA keys in pkcs#8 format. 'rsa' library doesn't support 43 | # them natively. Do some ASN unwrapping to extract naked RSA key 44 | # (in der-encoded form). See https://www.ietf.org/rfc/rfc2313.txt. 45 | # Also http://superuser.com/a/606266. 46 | try: 47 | der = rsa.pem.load_pem(pem, 'PRIVATE KEY') 48 | keyinfo, _ = decoder.decode(der) 49 | if keyinfo[1][0] != univ.ObjectIdentifier( 50 | '1.2.840.113549.1.1.1'): # pragma: no cover 51 | raise ValueError('Not a DER-encoded OpenSSL private RSA key') 52 | private_key_der = keyinfo[2].asOctets() 53 | except IndexError: # pragma: no cover 54 | raise ValueError('Not a DER-encoded OpenSSL private RSA key') 55 | return rsa.PrivateKey.load_pkcs1(private_key_der, format='DER') 56 | 57 | 58 | class PythonRSASigner(object): 59 | """Implements adb_protocol.AuthSigner using http://stuvel.eu/rsa.""" 60 | 61 | @classmethod 62 | def FromRSAKeyPath(cls, rsa_key_path): 63 | with open(rsa_key_path + '.pub') as f: 64 | pub = f.read() 65 | with open(rsa_key_path) as f: 66 | priv = f.read() 67 | return cls(pub, priv) 68 | 69 | def __init__(self, pub=None, priv=None): 70 | self.priv_key = _load_rsa_private_key(priv) 71 | self.pub_key = pub 72 | 73 | def Sign(self, data): 74 | return rsa.sign(data, self.priv_key, 'SHA-1-PREHASHED') 75 | 76 | def GetPublicKey(self): 77 | return self.pub_key 78 | -------------------------------------------------------------------------------- /adb/usb_exceptions.py: -------------------------------------------------------------------------------- 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 | """Common exceptions for ADB and Fastboot.""" 15 | 16 | 17 | class CommonUsbError(Exception): 18 | """Base class for usb communication errors.""" 19 | 20 | 21 | class FormatMessageWithArgumentsException(CommonUsbError): 22 | """Exception that both looks good and is functional. 23 | 24 | Okay, not that kind of functional, it's still a class. 25 | 26 | This interpolates the message with the given arguments to make it 27 | human-readable, but keeps the arguments in case other code try-excepts it. 28 | """ 29 | 30 | def __init__(self, message, *args): 31 | message %= args 32 | super(FormatMessageWithArgumentsException, self).__init__(message, *args) 33 | 34 | 35 | class DeviceNotFoundError(FormatMessageWithArgumentsException): 36 | """Device isn't on USB.""" 37 | 38 | 39 | class DeviceAuthError(FormatMessageWithArgumentsException): 40 | """Device authentication failed.""" 41 | 42 | 43 | class LibusbWrappingError(CommonUsbError): 44 | """Wraps libusb1 errors while keeping its original usefulness. 45 | 46 | Attributes: 47 | usb_error: Instance of libusb1.USBError 48 | """ 49 | 50 | def __init__(self, msg, usb_error): 51 | super(LibusbWrappingError, self).__init__(msg) 52 | self.usb_error = usb_error 53 | 54 | def __str__(self): 55 | return '%s: %s' % ( 56 | super(LibusbWrappingError, self).__str__(), str(self.usb_error)) 57 | 58 | 59 | class WriteFailedError(LibusbWrappingError): 60 | """Raised when the device doesn't accept our command.""" 61 | 62 | 63 | class ReadFailedError(LibusbWrappingError): 64 | """Raised when the device doesn't respond to our commands.""" 65 | 66 | 67 | class AdbCommandFailureException(Exception): 68 | """ADB Command returned a FAIL.""" 69 | 70 | 71 | class AdbOperationException(Exception): 72 | """Failed to communicate over adb with device after multiple retries.""" 73 | 74 | 75 | class TcpTimeoutException(FormatMessageWithArgumentsException): 76 | """TCP connection timed out in the time out given.""" 77 | -------------------------------------------------------------------------------- /fastboot_protocol.txt: -------------------------------------------------------------------------------- 1 | Fastboot Protocol Documentation 2 | 3 | Fastboot's protocol is similar to ADB in only a few ways. However, to make the 4 | code simpler to be inside a bootloader, it basically was completely altered. 5 | 6 | Commands: 7 | getvar:%(variable)s 8 | download:%08x 9 | verify:%08x 10 | flash:%(partition)s 11 | erase:%(partition)s 12 | oem %(stuff)s 13 | boot 14 | continue 15 | reboot 16 | reboot-bootloader 17 | 18 | 19 | Responses: 20 | These are 4-64 bytes long. The first 4 bytes is the header, the rest is 21 | header-specific but only up to 60 bytes. 22 | 23 | INFO + data[0-60] 24 | Arbitrary data returned from the bootloader. 25 | OKAY + reason[0-60] 26 | Last response, says the command succeeded. 27 | FAIL + reason[0-60] 28 | Last response, says the command failed. 29 | DATA + size[8] 30 | Only in response to a download command, says the bootloader is ready to 31 | accept `size` amount of data. 32 | 33 | -------------------------------------------------------------------------------- /filesync_protocol.txt: -------------------------------------------------------------------------------- 1 | The missing sync.txt from android's ADB. 2 | 3 | Message Format: 4 | Every message starts with a single 32-bit word. (Everything is little-endian). 5 | Depending on that first word, the rest of the data can have various meanings. 6 | Messages from the host/desktop to the device always start with a 'request' 7 | message of a u32 command, u32 size, and size more bytes that's a filename, 8 | directory or symlink. 9 | The command here is referred to as 'id' in the code and the 4-letter codes 10 | are prefixed by ID_, eg ID_STAT. 11 | The command IDs are the little endian hexadecimal representations of the 12 | 4-letter command codes. 13 | 14 | 15 | STAT: 16 | request: 17 | u32 command = 'STAT' == 0x54415453 18 | u32 size = len(filename) < 1024 19 | u8 data[size] = filename (no null) 20 | 21 | response: 22 | struct stat st = lstat(filename) 23 | u32 command = 'STAT' 24 | u32 mode = st.st_mode 25 | u32 size = st.st_size 26 | u32 time = st.st_mtime 27 | 28 | LIST: 29 | request: 30 | u32 command = 'LIST' == 0x5453494C 31 | u32 size = len(path) < 1024 32 | u8 data[size] = path (no null) 33 | 34 | response: 35 | for each filename in listing of path: 36 | struct stat st = lstat(filename) 37 | u32 command = 'DENT' == 0x544E4544 38 | u32 mode = st.st_mode 39 | u32 size = st.st_size 40 | u32 time = st.st_mtime 41 | u32 namelen = len(filename) 42 | u8 data[namelen] = filename 43 | 44 | done (device -> host): 45 | u32 command = 'DONE' == 0x454E4F44 46 | u32[4] = 0 47 | 48 | SEND: 49 | struct stat st = lstat(filename) 50 | request: 51 | fileinfo = sprintf(',%d', st.st_mode) 52 | u32 command = 'SEND' == 0x444E4553 53 | u32 size = len(filename) + len(fileinfo) < 1024 54 | u8 data[size] = filename + fileinfo 55 | 56 | repeated data command (host -> device): 57 | u32 command = 'DATA' == 0x41544144 58 | u32 size < (64 * 1024) 59 | u8 data[size] = file contents 60 | 61 | finish command (host -> device): 62 | u32 command = 'DONE' == 0x454E4F44 63 | u32 timestamp = st.st_mtime 64 | 65 | response (device -> host): 66 | u32 command = 'OKAY' == 0x59414BF4 or 'FAIL' == 0x4C494146 67 | u32 size = 0 if 'OKAY' else len(fail_message) 68 | u8 data[] = fail_message 69 | 70 | RECV: 71 | request: 72 | u32 command = 'RECV' == 0x56434552 73 | u32 size = len(filename) 74 | u8 data[size] = filename 75 | 76 | repeated data response (device -> host): 77 | u32 command = 'DATA' == 0x41544144 78 | u32 size < (64 * 1024) 79 | u8 data[size] = file contents 80 | 81 | finish response (device -> host): 82 | u32 command = 'DONE' == 0x454E4F44 83 | u32 size = 0 84 | 85 | -------------------------------------------------------------------------------- /make_tools.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Copyright 2016 Google Inc. All rights reserved. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | """Creates adb.zip and fastboot.zip as standalone executables. 17 | 18 | These files can be executed via: 19 | python adb.zip devices 20 | 21 | The same way one would have run: 22 | python adb_debug.py devices 23 | 24 | The zips can be transferred to other computers (and other CPU architectures) for 25 | CPU and OS agnostic execution. 26 | """ 27 | 28 | import os 29 | import sys 30 | import zipfile 31 | 32 | 33 | THIS_DIR = os.path.dirname(os.path.abspath(__file__)) 34 | 35 | 36 | def main(): 37 | os.chdir(THIS_DIR) 38 | with zipfile.ZipFile('adb.zip', 'w', zipfile.ZIP_DEFLATED) as z: 39 | z.write('adb/__init__.py') 40 | z.write('adb/adb_commands.py') 41 | z.write('adb/adb_debug.py', '__main__.py') 42 | z.write('adb/adb_protocol.py') 43 | z.write('adb/common.py') 44 | z.write('adb/common_cli.py') 45 | z.write('adb/filesync_protocol.py') 46 | z.write('adb/sign_cryptography.py') 47 | z.write('adb/sign_pythonrsa.py') 48 | z.write('adb/usb_exceptions.py') 49 | with zipfile.ZipFile('fastboot.zip', 'w', zipfile.ZIP_DEFLATED) as z: 50 | z.write('adb/__init__.py') 51 | z.write('adb/common.py') 52 | z.write('adb/common_cli.py') 53 | z.write('adb/fastboot.py') 54 | z.write('adb/fastboot_debug.py', '__main__.py') 55 | z.write('adb/usb_exceptions.py') 56 | return 0 57 | 58 | 59 | if __name__ == '__main__': 60 | sys.exit(main()) 61 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 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 | 15 | from setuptools import setup 16 | 17 | # Figure out if the system already has a supported Crypto library 18 | rsa_signer_library = 'cryptography' 19 | try: 20 | import rsa 21 | 22 | rsa_signer_library = 'rsa' 23 | except ImportError: 24 | try: 25 | from Crypto.Hash import SHA256 26 | from Crypto.PublicKey import RSA 27 | from Crypto.Signature import pkcs1_15 28 | 29 | rsa_signer_library = 'pycryptodome' 30 | except ImportError: 31 | pass 32 | 33 | 34 | setup( 35 | name = 'adb', 36 | packages = ['adb'], 37 | version = '1.3.0', 38 | author = 'Fahrzin Hemmati', 39 | author_email = 'fahhem@gmail.com', 40 | maintainer = 'Fahrzin Hemmati', 41 | maintainer_email = 'fahhem@google.com', 42 | url = 'https://github.com/google/python-adb', 43 | description = 'A pure python implementation of the Android ADB and Fastboot protocols', 44 | long_description = ''' 45 | This repository contains a pure-python implementation of the Android 46 | ADB and Fastboot protocols, using libusb1 for USB communications. 47 | 48 | This is a complete replacement and rearchitecture of the Android 49 | project's ADB and fastboot code available at 50 | https://github.com/android/platform_system_core/tree/master/adb 51 | 52 | This code is mainly targeted to users that need to communicate with 53 | Android devices in an automated fashion, such as in automated 54 | testing. It does not have a daemon between the client and the device, 55 | and therefore does not support multiple simultaneous commands to the 56 | same device. It does support any number of devices and never 57 | communicates with a device that it wasn't intended to, unlike the 58 | Android project's ADB. 59 | ''', 60 | 61 | keywords = ['android', 'adb', 'fastboot'], 62 | 63 | install_requires = [ 64 | 'libusb1>=1.0.16', 65 | rsa_signer_library 66 | ], 67 | 68 | extra_requires = { 69 | 'fastboot': 'progressbar>=2.3' 70 | }, 71 | 72 | ## classifier list https://pypi.python.org/pypi?:action=list_classifiers 73 | classifiers = [ 74 | 'Development Status :: 4 - Beta', 75 | 'License :: OSI Approved :: Apache Software License', 76 | 'Programming Language :: Python', 77 | 'Programming Language :: Python :: 2', 78 | 'Programming Language :: Python :: 3', 79 | 'Topic :: Software Development :: Testing' 80 | ], 81 | entry_points={ 82 | "console_scripts": [ 83 | "pyadb = adb.adb_debug:main", 84 | "pyfastboot = adb.fastboot_debug:main", 85 | ], 86 | } 87 | 88 | ) 89 | -------------------------------------------------------------------------------- /test/adb_test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Copyright 2014 Google Inc. All rights reserved. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | """Tests for adb.""" 16 | 17 | from io import BytesIO 18 | import struct 19 | import unittest 20 | from mock import mock 21 | 22 | 23 | from adb import common 24 | from adb import adb_commands 25 | from adb import adb_protocol 26 | from adb.usb_exceptions import TcpTimeoutException, DeviceNotFoundError 27 | import common_stub 28 | 29 | 30 | BANNER = b'blazetest' 31 | LOCAL_ID = 1 32 | REMOTE_ID = 2 33 | 34 | 35 | class BaseAdbTest(unittest.TestCase): 36 | 37 | @classmethod 38 | def _ExpectWrite(cls, usb, command, arg0, arg1, data): 39 | usb.ExpectWrite(cls._MakeHeader(command, arg0, arg1, data)) 40 | usb.ExpectWrite(data) 41 | if command == b'WRTE': 42 | cls._ExpectRead(usb, b'OKAY', 0, 0) 43 | 44 | @classmethod 45 | def _ExpectRead(cls, usb, command, arg0, arg1, data=b''): 46 | usb.ExpectRead(cls._MakeHeader(command, arg0, arg1, data)) 47 | if data: 48 | usb.ExpectRead(data) 49 | if command == b'WRTE': 50 | cls._ExpectWrite(usb, b'OKAY', LOCAL_ID, REMOTE_ID, b'') 51 | 52 | @classmethod 53 | def _ConvertCommand(cls, command): 54 | return sum(c << (i * 8) for i, c in enumerate(bytearray(command))) 55 | 56 | @classmethod 57 | def _MakeHeader(cls, command, arg0, arg1, data): 58 | command = cls._ConvertCommand(command) 59 | magic = command ^ 0xFFFFFFFF 60 | checksum = adb_protocol.AdbMessage.CalculateChecksum(data) 61 | return struct.pack(b'<6I', command, arg0, arg1, len(data), checksum, magic) 62 | 63 | @classmethod 64 | def _ExpectConnection(cls, usb): 65 | cls._ExpectWrite(usb, b'CNXN', 0x01000000, 4096, b'host::%s\0' % BANNER) 66 | cls._ExpectRead(usb, b'CNXN', 0, 0, b'device::\0') 67 | 68 | @classmethod 69 | def _ExpectOpen(cls, usb, service): 70 | cls._ExpectWrite(usb, b'OPEN', LOCAL_ID, 0, service) 71 | cls._ExpectRead(usb, b'OKAY', REMOTE_ID, LOCAL_ID) 72 | 73 | @classmethod 74 | def _ExpectClose(cls, usb): 75 | cls._ExpectRead(usb, b'CLSE', REMOTE_ID, 0) 76 | cls._ExpectWrite(usb, b'CLSE', LOCAL_ID, REMOTE_ID, b'') 77 | 78 | @classmethod 79 | def _Connect(cls, usb): 80 | return adb_commands.AdbCommands.Connect(usb, BANNER) 81 | 82 | 83 | class AdbTest(BaseAdbTest): 84 | @classmethod 85 | def _ExpectCommand(cls, service, command, *responses): 86 | usb = common_stub.StubUsb(device=None, setting=None) 87 | cls._ExpectConnection(usb) 88 | cls._ExpectOpen(usb, b'%s:%s\0' % (service, command)) 89 | 90 | for response in responses: 91 | cls._ExpectRead(usb, b'WRTE', REMOTE_ID, 0, response) 92 | cls._ExpectClose(usb) 93 | return usb 94 | 95 | def testConnect(self): 96 | usb = common_stub.StubUsb(device=None, setting=None) 97 | self._ExpectConnection(usb) 98 | 99 | dev = adb_commands.AdbCommands() 100 | dev.ConnectDevice(handle=usb, banner=BANNER) 101 | 102 | def testConnectSerialString(self): 103 | dev = adb_commands.AdbCommands() 104 | 105 | with mock.patch.object(common.UsbHandle, 'FindAndOpen', return_value=None): 106 | with mock.patch.object(adb_commands.AdbCommands, '_Connect', return_value=None): 107 | dev.ConnectDevice(serial='/dev/invalidHandle') 108 | 109 | def testSmallResponseShell(self): 110 | command = b'keepin it real' 111 | response = 'word.' 112 | usb = self._ExpectCommand(b'shell', command, response) 113 | 114 | dev = adb_commands.AdbCommands() 115 | dev.ConnectDevice(handle=usb, banner=BANNER) 116 | self.assertEqual(response, dev.Shell(command)) 117 | 118 | def testBigResponseShell(self): 119 | command = b'keepin it real big' 120 | # The data doesn't have to be big, the point is that it just concatenates 121 | # the data from different WRTEs together. 122 | responses = [b'other stuff, ', b'and some words.'] 123 | 124 | usb = self._ExpectCommand(b'shell', command, *responses) 125 | 126 | dev = adb_commands.AdbCommands() 127 | dev.ConnectDevice(handle=usb, banner=BANNER) 128 | self.assertEqual(b''.join(responses).decode('utf8'), 129 | dev.Shell(command)) 130 | 131 | def testUninstall(self): 132 | package_name = "com.test.package" 133 | response = 'Success' 134 | 135 | usb = self._ExpectCommand(b'shell', ('pm uninstall "%s"' % package_name).encode('utf8'), response) 136 | 137 | dev = adb_commands.AdbCommands() 138 | dev.ConnectDevice(handle=usb, banner=BANNER) 139 | self.assertEqual(response, dev.Uninstall(package_name)) 140 | 141 | def testStreamingResponseShell(self): 142 | command = b'keepin it real big' 143 | # expect multiple lines 144 | 145 | responses = ['other stuff, ', 'and some words.'] 146 | 147 | usb = self._ExpectCommand(b'shell', command, *responses) 148 | 149 | dev = adb_commands.AdbCommands() 150 | dev.ConnectDevice(handle=usb, banner=BANNER) 151 | response_count = 0 152 | for (expected,actual) in zip(responses, dev.StreamingShell(command)): 153 | self.assertEqual(expected, actual) 154 | response_count = response_count + 1 155 | self.assertEqual(len(responses), response_count) 156 | 157 | def testReboot(self): 158 | usb = self._ExpectCommand(b'reboot', b'', b'') 159 | dev = adb_commands.AdbCommands() 160 | dev.ConnectDevice(handle=usb, banner=BANNER) 161 | dev.Reboot() 162 | 163 | def testRebootBootloader(self): 164 | usb = self._ExpectCommand(b'reboot', b'bootloader', b'') 165 | dev = adb_commands.AdbCommands() 166 | dev.ConnectDevice(handle=usb, banner=BANNER) 167 | dev.RebootBootloader() 168 | 169 | def testRemount(self): 170 | usb = self._ExpectCommand(b'remount', b'', b'') 171 | dev = adb_commands.AdbCommands() 172 | dev.ConnectDevice(handle=usb, banner=BANNER) 173 | dev.Remount() 174 | 175 | def testRoot(self): 176 | usb = self._ExpectCommand(b'root', b'', b'') 177 | dev = adb_commands.AdbCommands() 178 | dev.ConnectDevice(handle=usb, banner=BANNER) 179 | dev.Root() 180 | 181 | def testEnableVerity(self): 182 | usb = self._ExpectCommand(b'enable-verity', b'', b'') 183 | dev = adb_commands.AdbCommands() 184 | dev.ConnectDevice(handle=usb, banner=BANNER) 185 | dev.EnableVerity() 186 | 187 | def testDisableVerity(self): 188 | usb = self._ExpectCommand(b'disable-verity', b'', b'') 189 | dev = adb_commands.AdbCommands() 190 | dev.ConnectDevice(handle=usb, banner=BANNER) 191 | dev.DisableVerity() 192 | 193 | class FilesyncAdbTest(BaseAdbTest): 194 | 195 | @classmethod 196 | def _MakeSyncHeader(cls, command, *int_parts): 197 | command = cls._ConvertCommand(command) 198 | return struct.pack(b'<%dI' % (len(int_parts) + 1), command, *int_parts) 199 | 200 | @classmethod 201 | def _MakeWriteSyncPacket(cls, command, data=b'', size=None): 202 | if not isinstance(data, bytes): 203 | data = data.encode('utf8') 204 | return cls._MakeSyncHeader(command, size or len(data)) + data 205 | 206 | @classmethod 207 | def _ExpectSyncCommand(cls, write_commands, read_commands): 208 | usb = common_stub.StubUsb(device=None, setting=None) 209 | cls._ExpectConnection(usb) 210 | cls._ExpectOpen(usb, b'sync:\0') 211 | 212 | while write_commands or read_commands: 213 | if write_commands: 214 | command = write_commands.pop(0) 215 | cls._ExpectWrite(usb, b'WRTE', LOCAL_ID, REMOTE_ID, command) 216 | 217 | if read_commands: 218 | command = read_commands.pop(0) 219 | cls._ExpectRead(usb, b'WRTE', REMOTE_ID, LOCAL_ID, command) 220 | 221 | cls._ExpectClose(usb) 222 | return usb 223 | 224 | def testPush(self): 225 | filedata = b'alo there, govnah' 226 | mtime = 100 227 | 228 | send = [ 229 | self._MakeWriteSyncPacket(b'SEND', b'/data,33272'), 230 | self._MakeWriteSyncPacket(b'DATA', filedata), 231 | self._MakeWriteSyncPacket(b'DONE', size=mtime), 232 | ] 233 | data = b'OKAY\0\0\0\0' 234 | usb = self._ExpectSyncCommand([b''.join(send)], [data]) 235 | 236 | dev = adb_commands.AdbCommands() 237 | dev.ConnectDevice(handle=usb, banner=BANNER) 238 | dev.Push(BytesIO(filedata), '/data', mtime=mtime) 239 | 240 | def testPull(self): 241 | filedata = b"g'ddayta, govnah" 242 | 243 | recv = self._MakeWriteSyncPacket(b'RECV', b'/data') 244 | data = [ 245 | self._MakeWriteSyncPacket(b'DATA', filedata), 246 | self._MakeWriteSyncPacket(b'DONE'), 247 | ] 248 | usb = self._ExpectSyncCommand([recv], [b''.join(data)]) 249 | dev = adb_commands.AdbCommands() 250 | dev.ConnectDevice(handle=usb, banner=BANNER) 251 | self.assertEqual(filedata, dev.Pull('/data')) 252 | 253 | 254 | class TcpTimeoutAdbTest(BaseAdbTest): 255 | 256 | @classmethod 257 | def _ExpectCommand(cls, service, command, *responses): 258 | tcp = common_stub.StubTcp('10.0.0.123') 259 | cls._ExpectConnection(tcp) 260 | cls._ExpectOpen(tcp, b'%s:%s\0' % (service, command)) 261 | 262 | for response in responses: 263 | cls._ExpectRead(tcp, b'WRTE', REMOTE_ID, 0, response) 264 | cls._ExpectClose(tcp) 265 | return tcp 266 | 267 | def _run_shell(self, cmd, timeout_ms=None): 268 | tcp = self._ExpectCommand(b'shell', cmd) 269 | dev = adb_commands.AdbCommands() 270 | dev.ConnectDevice(handle=tcp, banner=BANNER) 271 | dev.Shell(cmd, timeout_ms=timeout_ms) 272 | 273 | def testConnect(self): 274 | tcp = common_stub.StubTcp('10.0.0.123') 275 | self._ExpectConnection(tcp) 276 | dev = adb_commands.AdbCommands() 277 | dev.ConnectDevice(handle=tcp, banner=BANNER) 278 | 279 | def testTcpTimeout(self): 280 | timeout_ms = 1 281 | command = b'i_need_a_timeout' 282 | self.assertRaises( 283 | TcpTimeoutException, 284 | self._run_shell, 285 | command, 286 | timeout_ms=timeout_ms) 287 | 288 | 289 | class TcpHandleTest(unittest.TestCase): 290 | def testInitWithHost(self): 291 | tcp = common_stub.StubTcp('10.11.12.13') 292 | 293 | self.assertEqual('10.11.12.13:5555', tcp._serial_number) 294 | self.assertEqual(None, tcp._timeout_ms) 295 | 296 | def testInitWithHostAndPort(self): 297 | tcp = common_stub.StubTcp('10.11.12.13:5678') 298 | 299 | self.assertEqual('10.11.12.13:5678', tcp._serial_number) 300 | self.assertEqual(None, tcp._timeout_ms) 301 | 302 | def testInitWithTimeout(self): 303 | tcp = common_stub.StubTcp('10.0.0.2', timeout_ms=234.5) 304 | 305 | self.assertEqual('10.0.0.2:5555', tcp._serial_number) 306 | self.assertEqual(234.5, tcp._timeout_ms) 307 | 308 | def testInitWithTimeoutInt(self): 309 | tcp = common_stub.StubTcp('10.0.0.2', timeout_ms=234) 310 | 311 | self.assertEqual('10.0.0.2:5555', tcp._serial_number) 312 | self.assertEqual(234.0, tcp._timeout_ms) 313 | 314 | if __name__ == '__main__': 315 | unittest.main() 316 | -------------------------------------------------------------------------------- /test/common_stub.py: -------------------------------------------------------------------------------- 1 | """Stubs for tests using common's usb handling.""" 2 | 3 | import binascii 4 | import signal 5 | import string 6 | import sys 7 | import time 8 | from mock import mock 9 | 10 | from adb.common import TcpHandle, UsbHandle 11 | from adb.usb_exceptions import TcpTimeoutException 12 | 13 | PRINTABLE_DATA = set(string.printable) - set(string.whitespace) 14 | 15 | 16 | def _Dotify(data): 17 | if sys.version_info.major == 3: 18 | data = (chr(char) for char in data) 19 | return ''.join(char if char in PRINTABLE_DATA else '.' for char in data) 20 | 21 | 22 | class StubHandleBase(object): 23 | def __init__(self, timeout_ms, is_tcp=False): 24 | self.written_data = [] 25 | self.read_data = [] 26 | self.is_tcp = is_tcp 27 | self.timeout_ms = timeout_ms 28 | 29 | def _signal_handler(self, signum, frame): 30 | raise TcpTimeoutException('End of time') 31 | 32 | def _return_seconds(self, time_ms): 33 | return (float(time_ms)/1000) if time_ms else 0 34 | 35 | def _alarm_sounder(self, timeout_ms): 36 | signal.signal(signal.SIGALRM, self._signal_handler) 37 | signal.setitimer(signal.ITIMER_REAL, 38 | self._return_seconds(timeout_ms)) 39 | 40 | def ExpectWrite(self, data): 41 | if not isinstance(data, bytes): 42 | data = data.encode('utf8') 43 | self.written_data.append(data) 44 | 45 | def ExpectRead(self, data): 46 | if not isinstance(data, bytes): 47 | data = data.encode('utf8') 48 | self.read_data.append(data) 49 | 50 | def BulkWrite(self, data, timeout_ms=None): 51 | expected_data = self.written_data.pop(0) 52 | if isinstance(data, bytearray): 53 | data = bytes(data) 54 | if not isinstance(data, bytes): 55 | data = data.encode('utf8') 56 | if expected_data != data: 57 | raise ValueError('Expected %s (%s) got %s (%s)' % ( 58 | binascii.hexlify(expected_data), _Dotify(expected_data), 59 | binascii.hexlify(data), _Dotify(data))) 60 | if self.is_tcp and b'i_need_a_timeout' in data: 61 | self._alarm_sounder(timeout_ms) 62 | time.sleep(2*self._return_seconds(timeout_ms)) 63 | 64 | def BulkRead(self, length, 65 | timeout_ms=None): # pylint: disable=unused-argument 66 | data = self.read_data.pop(0) 67 | if length < len(data): 68 | raise ValueError( 69 | 'Overflow packet length. Read %d bytes, got %d bytes: %s', 70 | length, len(data)) 71 | if self.is_tcp and b'i_need_a_timeout' in data: 72 | self._alarm_sounder(timeout_ms) 73 | time.sleep(2*self._return_seconds(timeout_ms)) 74 | return bytearray(data) 75 | 76 | def Timeout(self, timeout_ms): 77 | return timeout_ms if timeout_ms is not None else self.timeout_ms 78 | 79 | 80 | class StubUsb(UsbHandle): 81 | """UsbHandle stub.""" 82 | def __init__(self, device, setting, usb_info=None, timeout_ms=None): 83 | super(StubUsb, self).__init__(device, setting, usb_info, timeout_ms) 84 | self.stub_base = StubHandleBase(0) 85 | 86 | def ExpectWrite(self, data): 87 | return self.stub_base.ExpectWrite(data) 88 | 89 | def ExpectRead(self, data): 90 | return self.stub_base.ExpectRead(data) 91 | 92 | def BulkWrite(self, data, unused_timeout_ms=None): 93 | return self.stub_base.BulkWrite(data, unused_timeout_ms) 94 | 95 | def BulkRead(self, length, timeout_ms=None): 96 | return self.stub_base.BulkRead(length, timeout_ms) 97 | 98 | def Timeout(self, timeout_ms): 99 | return self.stub_base.Timeout(timeout_ms) 100 | 101 | 102 | class StubTcp(TcpHandle): 103 | def __init__(self, serial, timeout_ms=None): 104 | """TcpHandle stub.""" 105 | self._connect = mock.MagicMock(return_value=None) 106 | 107 | super(StubTcp, self).__init__(serial, timeout_ms) 108 | self.stub_base = StubHandleBase(0, is_tcp=True) 109 | 110 | def ExpectWrite(self, data): 111 | return self.stub_base.ExpectWrite(data) 112 | 113 | def ExpectRead(self, data): 114 | return self.stub_base.ExpectRead(data) 115 | 116 | def BulkWrite(self, data, unused_timeout_ms=None): 117 | return self.stub_base.BulkWrite(data, unused_timeout_ms) 118 | 119 | def BulkRead(self, length, timeout_ms=None): 120 | return self.stub_base.BulkRead(length, timeout_ms) 121 | 122 | def Timeout(self, timeout_ms): 123 | return self.stub_base.Timeout(timeout_ms) 124 | -------------------------------------------------------------------------------- /test/fastboot_test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Copyright 2014 Google Inc. All rights reserved. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | """Tests for adb.fastboot.""" 16 | 17 | import io 18 | import os 19 | import tempfile 20 | import unittest 21 | 22 | import common_stub 23 | from adb import fastboot 24 | 25 | 26 | class FastbootTest(unittest.TestCase): 27 | 28 | def setUp(self): 29 | self.usb = common_stub.StubUsb(device=None, setting=None) 30 | 31 | @staticmethod 32 | def _SumLengths(items): 33 | return sum(len(item) for item in items) 34 | 35 | def ExpectDownload(self, writes, succeed=True, accept_data=True): 36 | self.usb.ExpectWrite(b'download:%08x' % self._SumLengths(writes)) 37 | 38 | if accept_data: 39 | self.usb.ExpectRead(b'DATA%08x' % self._SumLengths(writes)) 40 | else: 41 | self.usb.ExpectRead(b'DATA%08x' % (self._SumLengths(writes) - 2)) 42 | 43 | for data in writes: 44 | self.usb.ExpectWrite(data) 45 | 46 | if succeed: 47 | self.usb.ExpectRead(b'OKAYResult') 48 | else: 49 | self.usb.ExpectRead(b'FAILResult') 50 | 51 | def ExpectFlash(self, partition, succeed=True): 52 | self.usb.ExpectWrite(b'flash:%s' % partition) 53 | self.usb.ExpectRead(b'INFORandom info from the bootloader') 54 | if succeed: 55 | self.usb.ExpectRead(b'OKAYDone') 56 | else: 57 | self.usb.ExpectRead(b'FAILDone') 58 | 59 | def testDownload(self): 60 | raw = u'aoeuidhtnsqjkxbmwpyfgcrl' 61 | data = io.StringIO(raw) 62 | 63 | self.ExpectDownload([raw]) 64 | dev = fastboot.FastbootCommands() 65 | dev.ConnectDevice(handle=self.usb) 66 | 67 | response = dev.Download(data) 68 | self.assertEqual(b'Result', response) 69 | 70 | def testDownloadFail(self): 71 | raw = u'aoeuidhtnsqjkxbmwpyfgcrl' 72 | data = io.StringIO(raw) 73 | 74 | self.ExpectDownload([raw], succeed=False) 75 | dev = fastboot.FastbootCommands() 76 | dev.ConnectDevice(handle=self.usb) 77 | with self.assertRaises(fastboot.FastbootRemoteFailure): 78 | dev.Download(data) 79 | 80 | data = io.StringIO(raw) 81 | self.ExpectDownload([raw], accept_data=False) 82 | with self.assertRaises(fastboot.FastbootTransferError): 83 | dev.Download(data) 84 | 85 | def testFlash(self): 86 | partition = b'yarr' 87 | 88 | self.ExpectFlash(partition) 89 | dev = fastboot.FastbootCommands() 90 | dev.ConnectDevice(handle=self.usb) 91 | 92 | output = io.BytesIO() 93 | def InfoCb(message): 94 | if message.header == b'INFO': 95 | output.write(message.message) 96 | response = dev.Flash(partition, info_cb=InfoCb) 97 | self.assertEqual(b'Done', response) 98 | self.assertEqual(b'Random info from the bootloader', output.getvalue()) 99 | 100 | def testFlashFail(self): 101 | partition = b'matey' 102 | 103 | self.ExpectFlash(partition, succeed=False) 104 | dev = fastboot.FastbootCommands() 105 | dev.ConnectDevice(handle=self.usb) 106 | 107 | with self.assertRaises(fastboot.FastbootRemoteFailure): 108 | dev.Flash(partition) 109 | 110 | def testFlashFromFile(self): 111 | partition = b'somewhere' 112 | # More than one packet, ends somewhere into the 3rd packet. 113 | raw = b'SOMETHING' * 1086 114 | tmp = tempfile.NamedTemporaryFile(delete=False) 115 | tmp.write(raw) 116 | tmp.close() 117 | progresses = [] 118 | 119 | pieces = [] 120 | chunk_size = fastboot.FastbootProtocol(None).chunk_kb * 1024 121 | while raw: 122 | pieces.append(raw[:chunk_size]) 123 | raw = raw[chunk_size:] 124 | self.ExpectDownload(pieces) 125 | self.ExpectFlash(partition) 126 | 127 | cb = lambda progress, total: progresses.append((progress, total)) 128 | 129 | dev = fastboot.FastbootCommands() 130 | dev.ConnectDevice(handle=self.usb) 131 | dev.FlashFromFile( 132 | partition, tmp.name, progress_callback=cb) 133 | self.assertEqual(len(pieces), len(progresses)) 134 | os.remove(tmp.name) 135 | 136 | def testSimplerCommands(self): 137 | dev = fastboot.FastbootCommands() 138 | dev.ConnectDevice(handle=self.usb) 139 | 140 | self.usb.ExpectWrite(b'erase:vector') 141 | self.usb.ExpectRead(b'OKAY') 142 | dev.Erase('vector') 143 | 144 | self.usb.ExpectWrite(b'getvar:variable') 145 | self.usb.ExpectRead(b'OKAYstuff') 146 | self.assertEqual(b'stuff', dev.Getvar('variable')) 147 | 148 | self.usb.ExpectWrite(b'continue') 149 | self.usb.ExpectRead(b'OKAY') 150 | dev.Continue() 151 | 152 | self.usb.ExpectWrite(b'reboot') 153 | self.usb.ExpectRead(b'OKAY') 154 | dev.Reboot() 155 | 156 | self.usb.ExpectWrite(b'reboot-bootloader') 157 | self.usb.ExpectRead(b'OKAY') 158 | dev.RebootBootloader() 159 | 160 | self.usb.ExpectWrite(b'oem a little somethin') 161 | self.usb.ExpectRead(b'OKAYsomethin') 162 | self.assertEqual(b'somethin', dev.Oem('a little somethin')) 163 | 164 | def testVariousFailures(self): 165 | dev = fastboot.FastbootCommands() 166 | dev.ConnectDevice(handle=self.usb) 167 | 168 | self.usb.ExpectWrite(b'continue') 169 | self.usb.ExpectRead(b'BLEH') 170 | with self.assertRaises(fastboot.FastbootInvalidResponse): 171 | dev.Continue() 172 | 173 | self.usb.ExpectWrite(b'continue') 174 | self.usb.ExpectRead(b'DATA000000') 175 | with self.assertRaises(fastboot.FastbootStateMismatch): 176 | dev.Continue() 177 | 178 | 179 | if __name__ == '__main__': 180 | unittest.main() 181 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | # Tox (http://tox.testrun.org/) is a tool for running tests in multiple 2 | # virtualenvs. This configuration file will run the test suite on all 3 | # supported python versions. To use it, "pip install tox" and then run 4 | # "tox" from this directory. 5 | 6 | [tox] 7 | envlist = 8 | py36 9 | py27 10 | 11 | [testenv] 12 | deps = 13 | pytest 14 | pytest-cov 15 | mock 16 | usedevelop = True 17 | commands = py.test --cov adb test 18 | --------------------------------------------------------------------------------