Full Code of JeffLIrion/adb_shell for AI

master 149ffbcff47b cached
82 files
444.2 KB
106.4k tokens
458 symbols
1 requests
Download .txt
Showing preview only (469K chars total). Download the full file or copy to clipboard to get everything.
Repository: JeffLIrion/adb_shell
Branch: master
Commit: 149ffbcff47b
Files: 82
Total size: 444.2 KB

Directory structure:
gitextract_gtoem9ut/

├── .flake8
├── .gitattributes
├── .github/
│   ├── ISSUE_TEMPLATE/
│   │   └── bug_report.md
│   └── workflows/
│       └── python-package.yml
├── .gitignore
├── .pylintrc
├── .readthedocs.yml
├── .travis.yml
├── LICENSE
├── MANIFEST.in
├── Makefile
├── README.rst
├── adb_shell/
│   ├── __init__.py
│   ├── adb_device.py
│   ├── adb_device_async.py
│   ├── adb_message.py
│   ├── auth/
│   │   ├── __init__.py
│   │   ├── keygen.py
│   │   ├── sign_cryptography.py
│   │   ├── sign_pycryptodome.py
│   │   └── sign_pythonrsa.py
│   ├── constants.py
│   ├── exceptions.py
│   ├── hidden_helpers.py
│   └── transport/
│       ├── __init__.py
│       ├── base_transport.py
│       ├── base_transport_async.py
│       ├── tcp_transport.py
│       ├── tcp_transport_async.py
│       └── usb_transport.py
├── docs/
│   ├── Makefile
│   ├── make.bat
│   ├── requirements.txt
│   └── source/
│       ├── adb_shell.adb_device.rst
│       ├── adb_shell.adb_device_async.rst
│       ├── adb_shell.adb_message.rst
│       ├── adb_shell.auth.keygen.rst
│       ├── adb_shell.auth.rst
│       ├── adb_shell.auth.sign_cryptography.rst
│       ├── adb_shell.auth.sign_pycryptodome.rst
│       ├── adb_shell.auth.sign_pythonrsa.rst
│       ├── adb_shell.constants.rst
│       ├── adb_shell.exceptions.rst
│       ├── adb_shell.hidden_helpers.rst
│       ├── adb_shell.rst
│       ├── adb_shell.transport.base_transport.rst
│       ├── adb_shell.transport.base_transport_async.rst
│       ├── adb_shell.transport.rst
│       ├── adb_shell.transport.tcp_transport.rst
│       ├── adb_shell.transport.tcp_transport_async.rst
│       ├── adb_shell.transport.usb_transport.rst
│       ├── conf.py
│       ├── index.rst
│       └── modules.rst
├── scripts/
│   ├── bumpversion.sh
│   ├── get_package_name.sh
│   ├── get_version.sh
│   ├── git_retag.sh
│   ├── git_tag.sh
│   ├── pre-commit.sh
│   └── rename_package.sh
├── setup.py
├── tests/
│   ├── __init__.py
│   ├── async_patchers.py
│   ├── async_wrapper.py
│   ├── filesync_helpers.py
│   ├── keygen_stub.py
│   ├── patchers.py
│   ├── test_adb_device.py
│   ├── test_adb_device_async.py
│   ├── test_adb_message.py
│   ├── test_exceptions.py
│   ├── test_hidden_helpers.py
│   ├── test_keygen.py
│   ├── test_sign_cryptography.py
│   ├── test_sign_pycryptodome.py
│   ├── test_sign_pythonrsa.py
│   ├── test_tcp_transport.py
│   ├── test_tcp_transport_async.py
│   ├── test_usb_importerror.py
│   └── test_usb_transport.py
└── venv_requirements.txt

================================================
FILE CONTENTS
================================================

================================================
FILE: .flake8
================================================
[flake8]
ignore = E501,W504


================================================
FILE: .gitattributes
================================================
Makefile eol=lf
*.sh eol=lf
*.ipynb eol=lf


================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''

---

### Description

<!--What is the bug and how to reproduce it-->

### Log

<!--A log from when the issue occurred-->

To enable debug logging in Python:

```python
import logging

logging.basicConfig(level=logging.DEBUG)
```

To enable debug logging in Home Assistant:

#### Approach 1: configuration.yaml

```yaml
logger:
  default: warning  # or whatever
    logs:
      adb_shell: debug
```

#### Approach 2: `logger.set_level` service

```yaml
adb_shell: debug
```


================================================
FILE: .github/workflows/python-package.yml
================================================
# This workflow will install Python dependencies, run tests and lint with a variety of Python versions
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions

name: Python package

on:
  push:
    branches: [ master ]
  pull_request:
    branches: [ master ]

env:
  ENV_GITHUB_ACTIONS: 'ENV_GITHUB_ACTIONS'

jobs:
  build:

    runs-on: ubuntu-latest
    strategy:
      matrix:
        python-version: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12']

    steps:
    - uses: actions/checkout@v2
    - name: Set up Python ${{ matrix.python-version }}
      uses: actions/setup-python@v2
      with:
        python-version: ${{ matrix.python-version }}
    - name: Install dependencies
      run: |
        python -m pip install --upgrade pip
        make venv
    - name: Linting checks with pylint, flake8, and (soon) black
      run: |
        make lint-flake8 lint-pylint
    - name: Test with pytest
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        COVERALLS_SERVICE_NAME: github
      run: |
        make coverage && coveralls
    - name: Upload wheel as a workflow artifact
      uses: actions/upload-artifact@v4
      with:
        name: wheel
        path: dist/*.whl


================================================
FILE: .gitignore
================================================
# Python files
*.idea
*.pyc
**/__pycache__/
adb_shell.egg-info

# Build files
build/
dist/

# Documentation
docs/build/
docs/html/

# Coverage
.coverage
htmlcov/


================================================
FILE: .pylintrc
================================================
[MASTER]

# A comma-separated list of package or module names from where C extensions may
# be loaded. Extensions are loading into the active Python interpreter and may
# run arbitrary code.
extension-pkg-whitelist=

# Add files or directories to the blacklist. They should be base names, not
# paths.
ignore=CVS

# Add files or directories matching the regex patterns to the blacklist. The
# regex matches against base names, not paths.
ignore-patterns=

# Python code to execute, usually for sys.path manipulation such as
# pygtk.require().
#init-hook=

# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the
# number of processors available to use.
jobs=1

# Control the amount of potential inferred values when inferring a single
# object. This can help the performance when dealing with large functions or
# complex, nested conditions.
limit-inference-results=100

# List of plugins (as comma separated values of python modules names) to load,
# usually to register additional checkers.
load-plugins=

# Pickle collected data for later comparisons.
persistent=yes

# Specify a configuration file.
#rcfile=

# When enabled, pylint would attempt to guess common misconfiguration and emit
# user-friendly hints instead of false-positive error messages.
suggestion-mode=yes

# Allow loading of arbitrary C extensions. Extensions are imported into the
# active Python interpreter and may run arbitrary code.
unsafe-load-any-extension=no


[MESSAGES CONTROL]

# Only show warnings with the listed confidence levels. Leave empty to show
# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED.
confidence=

# Disable the message, report, category or checker with the given id(s). You
# can either give multiple identifiers separated by comma (,) or put this
# option multiple times (only on the command line, not in the configuration
# file where it should appear only once). You can also use "--disable=all" to
# disable everything first and then reenable specific checks. For example, if
# you want to run only the similarities checker, you can use "--disable=all
# --enable=similarities". If you want to run only the classes checker, but have
# no Warning level messages displayed, use "--disable=all --enable=classes
# --disable=W".
disable=consider-using-f-string,
        duplicate-code,
        invalid-name,
        line-too-long,
        raise-missing-from,
        super-with-arguments,
        too-many-arguments,
        too-many-branches,
        too-many-instance-attributes,
        too-many-lines,
        too-many-locals,
        too-many-nested-blocks,
        too-many-positional-arguments,
        too-many-public-methods,
        too-many-return-statements,
        too-many-statements,
        useless-object-inheritance,
        unspecified-encoding

# Enable the message, report, category or checker with the given id(s). You can
# either give multiple identifier separated by comma (,) or put this option
# multiple time (only on the command line, not in the configuration file where
# it should appear only once). See also the "--disable" option for examples.
enable=c-extension-no-member


[REPORTS]

# Python expression which should return a note less than 10 (10 is the highest
# note). You have access to the variables errors warning, statement which
# respectively contain the number of errors / warnings messages and the total
# number of statements analyzed. This is used by the global evaluation report
# (RP0004).
evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)

# Template used to display messages. This is a python new-style format string
# used to format the message information. See doc for all details.
#msg-template=

# Set the output format. Available formats are text, parseable, colorized, json
# and msvs (visual studio). You can also give a reporter class, e.g.
# mypackage.mymodule.MyReporterClass.
output-format=text

# Tells whether to display a full report or only the messages.
reports=no

# Activate the evaluation score.
score=yes


[REFACTORING]

# Maximum number of nested blocks for function / method body
max-nested-blocks=5

# Complete name of functions that never returns. When checking for
# inconsistent-return-statements if a never returning function is called then
# it will be considered as an explicit return statement and no message will be
# printed.
never-returning-functions=sys.exit


[LOGGING]

# Format style used to check logging format string. `old` means using %
# formatting, while `new` is for `{}` formatting.
logging-format-style=old

# Logging modules to check that the string format arguments are in logging
# function parameter format.
logging-modules=logging


[FORMAT]

# Expected format of line ending, e.g. empty (any line ending), LF or CRLF.
expected-line-ending-format=

# Regexp for a line that is allowed to be longer than the limit.
ignore-long-lines=^\s*(# )?<?https?://\S+>?$

# Number of spaces of indent required inside a hanging or continued line.
indent-after-paren=4

# String used as indentation unit. This is usually "    " (4 spaces) or "\t" (1
# tab).
indent-string='    '

# Maximum number of characters on a single line.
max-line-length=100

# Maximum number of lines in a module.
max-module-lines=1000

# Allow the body of a class to be on the same line as the declaration if body
# contains single statement.
single-line-class-stmt=no

# Allow the body of an if to be on the same line as the test if there is no
# else.
single-line-if-stmt=no


[MISCELLANEOUS]

# List of note tags to take in consideration, separated by a comma.
notes=FIXME,
      XXX,
      TODO


[BASIC]

# Naming style matching correct argument names.
argument-naming-style=snake_case

# Regular expression matching correct argument names. Overrides argument-
# naming-style.
#argument-rgx=

# Naming style matching correct attribute names.
attr-naming-style=snake_case

# Regular expression matching correct attribute names. Overrides attr-naming-
# style.
#attr-rgx=

# Bad variable names which should always be refused, separated by a comma.
bad-names=foo,
          bar,
          baz,
          toto,
          tutu,
          tata

# Naming style matching correct class attribute names.
class-attribute-naming-style=any

# Regular expression matching correct class attribute names. Overrides class-
# attribute-naming-style.
#class-attribute-rgx=

# Naming style matching correct class names.
class-naming-style=PascalCase

# Regular expression matching correct class names. Overrides class-naming-
# style.
#class-rgx=

# Naming style matching correct constant names.
const-naming-style=UPPER_CASE

# Regular expression matching correct constant names. Overrides const-naming-
# style.
#const-rgx=

# Minimum line length for functions/classes that require docstrings, shorter
# ones are exempt.
docstring-min-length=-1

# Naming style matching correct function names.
function-naming-style=snake_case

# Regular expression matching correct function names. Overrides function-
# naming-style.
#function-rgx=

# Good variable names which should always be accepted, separated by a comma.
good-names=i,
           j,
           k,
           ex,
           Run,
           _

# Include a hint for the correct naming format with invalid-name.
include-naming-hint=no

# Naming style matching correct inline iteration names.
inlinevar-naming-style=any

# Regular expression matching correct inline iteration names. Overrides
# inlinevar-naming-style.
#inlinevar-rgx=

# Naming style matching correct method names.
method-naming-style=snake_case

# Regular expression matching correct method names. Overrides method-naming-
# style.
#method-rgx=

# Naming style matching correct module names.
module-naming-style=snake_case

# Regular expression matching correct module names. Overrides module-naming-
# style.
#module-rgx=

# Colon-delimited sets of names that determine each other's naming style when
# the name regexes allow several styles.
name-group=

# Regular expression which should only match function or class names that do
# not require a docstring.
no-docstring-rgx=^_

# List of decorators that produce properties, such as abc.abstractproperty. Add
# to this list to register other decorators that produce valid properties.
# These decorators are taken in consideration only for invalid-name.
property-classes=abc.abstractproperty

# Naming style matching correct variable names.
variable-naming-style=snake_case

# Regular expression matching correct variable names. Overrides variable-
# naming-style.
#variable-rgx=


[TYPECHECK]

# List of decorators that produce context managers, such as
# contextlib.contextmanager. Add to this list to register other decorators that
# produce valid context managers.
contextmanager-decorators=contextlib.contextmanager

# List of members which are set dynamically and missed by pylint inference
# system, and so shouldn't trigger E1101 when accessed. Python regular
# expressions are accepted.
generated-members=

# Tells whether missing members accessed in mixin class should be ignored. A
# mixin class is detected if its name ends with "mixin" (case insensitive).
ignore-mixin-members=yes

# Tells whether to warn about missing members when the owner of the attribute
# is inferred to be None.
ignore-none=yes

# This flag controls whether pylint should warn about no-member and similar
# checks whenever an opaque object is returned when inferring. The inference
# can return multiple potential results while evaluating a Python object, but
# some branches might not be evaluated, which results in partial inference. In
# that case, it might be useful to still emit no-member and other checks for
# the rest of the inferred objects.
ignore-on-opaque-inference=yes

# List of class names for which member attributes should not be checked (useful
# for classes with dynamically set attributes). This supports the use of
# qualified names.
ignored-classes=optparse.Values,thread._local,_thread._local,_socketobject

# List of module names for which member attributes should not be checked
# (useful for modules/projects where namespaces are manipulated during runtime
# and thus existing member attributes cannot be deduced by static analysis. It
# supports qualified module names, as well as Unix pattern matching.
ignored-modules=

# Show a hint with possible names when a member name was not found. The aspect
# of finding the hint is based on edit distance.
missing-member-hint=yes

# The minimum edit distance a name should have in order to be considered a
# similar match for a missing member name.
missing-member-hint-distance=1

# The total number of similar names that should be taken in consideration when
# showing a hint for a missing member.
missing-member-max-choices=1


[STRING]

# This flag controls whether the implicit-str-concat-in-sequence should
# generate a warning on implicit string concatenation in sequences defined over
# several lines.
check-str-concat-over-line-jumps=no


[SPELLING]

# Limits count of emitted suggestions for spelling mistakes.
max-spelling-suggestions=4

# Spelling dictionary name. Available dictionaries: none. To make it working
# install python-enchant package..
spelling-dict=

# List of comma separated words that should not be checked.
spelling-ignore-words=

# A path to a file that contains private dictionary; one word per line.
spelling-private-dict-file=

# Tells whether to store unknown words to indicated private dictionary in
# --spelling-private-dict-file option instead of raising a message.
spelling-store-unknown-words=no


[SIMILARITIES]

# Ignore comments when computing similarities.
ignore-comments=yes

# Ignore docstrings when computing similarities.
ignore-docstrings=yes

# Ignore imports when computing similarities.
ignore-imports=no

# Minimum lines number of a similarity.
min-similarity-lines=4


[VARIABLES]

# List of additional names supposed to be defined in builtins. Remember that
# you should avoid defining new builtins when possible.
additional-builtins=

# Tells whether unused global variables should be treated as a violation.
allow-global-unused-variables=yes

# List of strings which can identify a callback function by name. A callback
# name must start or end with one of those strings.
callbacks=cb_,
          _cb

# A regular expression matching the name of dummy variables (i.e. expected to
# not be used).
dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_

# Argument names that match this expression will be ignored. Default to name
# with leading underscore.
ignored-argument-names=_.*|^ignored_|^unused_

# Tells whether we should check for unused import in __init__ files.
init-import=no

# List of qualified module names which can have objects that can redefine
# builtins.
redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io


[DESIGN]

# Maximum number of arguments for function / method.
max-args=5

# Maximum number of attributes for a class (see R0902).
max-attributes=7

# Maximum number of boolean expressions in an if statement.
max-bool-expr=5

# Maximum number of branch for function / method body.
max-branches=12

# Maximum number of locals for function / method body.
max-locals=15

# Maximum number of parents for a class (see R0901).
max-parents=7

# Maximum number of public methods for a class (see R0904).
max-public-methods=20

# Maximum number of return / yield for function / method body.
max-returns=6

# Maximum number of statements in function / method body.
max-statements=50

# Minimum number of public methods for a class (see R0903).
min-public-methods=2


[IMPORTS]

# Allow wildcard imports from modules that define __all__.
allow-wildcard-with-all=no

# Analyse import fallback blocks. This can be used to support both Python 2 and
# 3 compatible code, which means that the block might have code that exists
# only in one or another interpreter, leading to false positives when analysed.
analyse-fallback-blocks=no

# Deprecated modules which should not be used, separated by a comma.
deprecated-modules=optparse,tkinter.tix

# Create a graph of external dependencies in the given file (report RP0402 must
# not be disabled).
ext-import-graph=

# Create a graph of every (i.e. internal and external) dependencies in the
# given file (report RP0402 must not be disabled).
import-graph=

# Create a graph of internal dependencies in the given file (report RP0402 must
# not be disabled).
int-import-graph=

# Force import order to recognize a module as part of the standard
# compatibility libraries.
known-standard-library=

# Force import order to recognize a module as part of a third party library.
known-third-party=enchant


[CLASSES]

# List of method names used to declare (i.e. assign) instance attributes.
defining-attr-methods=__init__,
                      __new__,
                      setUp

# List of member names, which should be excluded from the protected access
# warning.
exclude-protected=_asdict,
                  _fields,
                  _replace,
                  _source,
                  _make

# List of valid names for the first argument in a class method.
valid-classmethod-first-arg=cls

# List of valid names for the first argument in a metaclass class method.
valid-metaclass-classmethod-first-arg=cls


[EXCEPTIONS]

# Exceptions that will emit a warning when being caught. Defaults to
# "BaseException, Exception".
overgeneral-exceptions=builtins.Exception


================================================
FILE: .readthedocs.yml
================================================
# .readthedocs.yml
# Read the Docs configuration file
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details

# Required
version: 2

# Set the OS
build:
  os: ubuntu-22.04
  tools:
    python: "3.11"

# Build documentation in the docs/ directory with Sphinx
sphinx:
  configuration: docs/source/conf.py

# Optionally build your docs in additional formats such as PDF and ePub
formats: all

# Optionally set the version of Python and requirements required to build your docs
python:
  install:
    - requirements: docs/requirements.txt


================================================
FILE: .travis.yml
================================================
language: python
python:
  - "2.7"
  - "3.5"
  - "3.6"
  - "3.7"
  - "3.8"
  - "3.9"
addons:
  apt:
    packages:
      - swig
      - libusb-1.0-0-dev
install:
  - pip install .
  - pip install flake8 pylint coveralls cryptography libusb1>=1.0.16 pycryptodome
  - python --version 2>&1 | grep -q "Python 2" && pip install mock || true
  - if python --version 2>&1 | grep -q "Python 3.7" || python --version 2>&1 | grep -q "Python 3.8" || python --version 2>&1 | grep -q "Python 3.9"; then pip install aiofiles; fi
script:
  - if python --version 2>&1 | grep -q "Python 2" || python --version 2>&1 | grep -q "Python 3.5" || python --version 2>&1 | grep -q "Python 3.6"; then flake8 adb_shell/ --exclude="adb_shell/adb_device_async.py,adb_shell/transport/base_transport_async.py,adb_shell/transport/tcp_transport_async.py" && pylint --ignore="adb_device_async.py,base_transport_async.py,tcp_transport_async.py" adb_shell/; fi
  - if python --version 2>&1 | grep -q "Python 3.7" || python --version 2>&1 | grep -q "Python 3.8" || python --version 2>&1 | grep -q "Python 3.9"; then flake8 adb_shell/ && pylint adb_shell/; fi
  - if python --version 2>&1 | grep -q "Python 2" || python --version 2>&1 | grep -q "Python 3.5" || python --version 2>&1 | grep -q "Python 3.6"; then for synctest in $(cd tests && ls test*.py | grep -v async); do python -m unittest discover -s tests/ -t . -p "$synctest" || exit 1; done; fi
  - if python --version 2>&1 | grep -q "Python 3.7" || python --version 2>&1 | grep -q "Python 3.8" || python --version 2>&1 | grep -q "Python 3.9"; then coverage run --source adb_shell -m unittest discover -s tests/ -t . && coverage report -m; fi
after_success:
  - if python --version 2>&1 | grep -q "Python 3.7" || python --version 2>&1 | grep -q "Python 3.8" || python --version 2>&1 | grep -q "Python 3.9"; then coveralls; fi


================================================
FILE: LICENSE
================================================

                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS


================================================
FILE: MANIFEST.in
================================================
include LICENSE


================================================
FILE: Makefile
================================================
#-------------------- ONLY MODIFY CODE IN THIS SECTION --------------------#
PACKAGE_DIR := adb_shell
TEST_DIR := tests
DOCS_DIR := docs

# Change to false if you don't want to use pytest
USE_PYTEST := true

# Change this to false if you don't want to run linting checks on the tests
LINT_TEST_DIR := false
#-------------------- DO NOT MODIFY CODE BELOW!!!!!!!! --------------------#

export PATH := $(abspath venv)/bin:${PATH}

# Binaries to run
BLACK := $(abspath venv)/bin/black
COVERAGE := $(abspath venv)/bin/coverage
FLAKE8 := $(abspath venv)/bin/flake8
PIP := $(abspath venv)/bin/pip3
PYLINT := $(abspath venv)/bin/pylint
PYTEST := $(abspath venv)/bin/pytest
PYTHON := $(abspath venv)/bin/python
SPHINX_APIDOC := $(abspath venv)/bin/sphinx-apidoc
TWINE := $(abspath venv)/bin/twine

# Whether to include "*_async.py" files
INCLUDE_ASYNC = $(shell $(PYTHON) --version | grep -q "Python 3.[7891]" && echo "true" || echo "false")

# Async vs. Sync files
PACKAGE_ASYNC_FILES = $(shell ls -m $(PACKAGE_DIR)/*_async.py 2>/dev/null)
TEST_ASYNC_FILES = $(shell ls -m $(TEST_DIR)/*_async.py 2>/dev/null)
TEST_SYNC_FILES = $(shell cd $(TEST_DIR) && ls test*.py | grep -v async)

# Target prerequisites that may or may not exist
VENV_REQUIREMENTS_TXT := $(wildcard venv_requirements.txt)
SETUP_PY := $(wildcard setup.py)


# A prerequisite for forcing targets to run
FORCE:

# Help!
help:  ## Show this help menu
	@echo "\n\033[1mUsage:\033[0m"; \
	awk -F ':|##' '/^[^\t].+?:.*?##/ { printf "\033[36m  make %-20s\033[0m %s\n", $$1, $$NF }' $(MAKEFILE_LIST) | grep -v "make venv/\." | sort
	@echo ""
	@echo "NOTES:"
	@echo "- The 'venv/.bin' target may fail because newer Python versions include the 'venv' package.  Follow the instructions to create the virtual environment manually."
ifneq ("$(wildcard scripts/pre-commit.sh)", "")
	@echo "- To install the git pre-commit hook:\n\n    scripts/pre-commit.sh\n"
endif
	@echo "- You may need to activate the virtual environment prior to running any Make commands:\n\n    source venv/bin/activate\n"


# Virtual environment targets
.PHONY: clean-venv
clean-venv:  ## Remove the virtual environment
	rm -rf venv

venv: venv/.bin venv/.requirements venv/.setup .git/hooks/pre-commit  ## Create the virtual environment and install all necessary packages

venv/.bin:  ## Create the virtual environment
	if [ -z "$$ENV_GITHUB_ACTIONS" ]; then \
	  echo -e "If this target fails, you can perform this action manually via:\n\n    make clean-venv && python3 -m venv venv && source venv/bin/activate && pip install -U setuptools && echo -e '*.*\\\n**/' > venv/.gitignore && touch venv/.bin\n\n"; \
	  apt list -a --installed python3-venv 2>&1 | grep -q installed || sudo apt update && sudo apt install python3-venv; \
	  python3 -m venv venv; \
	  $(PIP) install -U setuptools; \
	else \
	  mkdir -p venv/bin; \
	  ln -s $$(which pip) $(PIP); \
	  ln -s $$(which python) $(PYTHON); \
	fi
	mkdir -p venv/bin
	echo '*.*\n**/' > venv/.gitignore
	touch venv/.bin

venv/.requirements: venv/.bin $(VENV_REQUIREMENTS_TXT)  ## Install the requirements from 'venv_requirements.txt' in the virtual environment
ifneq ("$(wildcard venv_requirements.txt)", "")
	$(PIP) install -U -r venv_requirements.txt
	if ! [ -z "$$ENV_GITHUB_ACTIONS" ]; then \
	  ln -s $$(which black) $(BLACK); \
	  ln -s $$(which coverage) $(COVERAGE); \
	  ln -s $$(which flake8) $(FLAKE8); \
	  ln -s $$(which pylint) $(PYLINT); \
	  ln -s $$(which pytest) $(PYTEST); \
	  ln -s $$(which sphinx-apidoc) $(SPHINX_APIDOC); \
	  ln -s $$(which twine) $(TWINE); \
	fi
endif
	touch venv/.requirements

# Install the package in the virtual environment
venv/.setup: venv/.bin $(SETUP_PY)
ifneq ("$(wildcard setup.py)", "")
	$(PIP) install .
endif
	touch venv/.setup

.PHONY: uninstall
uninstall:
	rm -f venv/.setup

.PHONY: install
install: uninstall venv/.setup  ## Install the package in the virtual environment

# Create the pre-commit hook
.git/hooks/pre-commit:
	./scripts/pre-commit.sh MAKE_PRECOMMIT_HOOK

.PHONY: pre-commit
pre-commit: .git/hooks/pre-commit  ## Create the pre-commit hook

# Linting and code analysis
.PHONY: black
black: venv  ## Format the code using black
	$(BLACK) --safe --line-length 120 --target-version py35 $(PACKAGE_DIR)
	$(BLACK) --safe --line-length 120 --target-version py35 $(TEST_DIR)
ifneq ("$(wildcard setup.py)", "")
	$(BLACK) --safe --line-length 120 --target-version py35 setup.py
endif

.PHONY: lint-black
lint-black: venv  ## Check that the code is formatted using black
	$(BLACK) --check --line-length 120 --safe --target-version py35 $(PACKAGE_DIR)
	$(BLACK) --check --line-length 120 --safe --target-version py35 $(TEST_DIR)
ifneq ("$(wildcard setup.py)", "")
	$(BLACK) --check --line-length 120 --safe --target-version py35 setup.py
endif

.PHONY: lint-flake8
lint-flake8: venv  ## Check the code using flake8
ifeq ($(INCLUDE_ASYNC), true)
	$(FLAKE8) $(PACKAGE_DIR)
ifeq ($(LINT_TEST_DIR), true)
	$(FLAKE8) $(TEST_DIR)
endif
else
	$(FLAKE8) $(PACKAGE_DIR) --exclude="$(PACKAGE_ASYNC_FILES)"
ifeq ($(LINT_TEST_DIR), true)
	$(FLAKE8) $(TEST_DIR) --exclude="$(TEST_ASYNC_FILES)"
endif
endif
ifneq ("$(wildcard setup.py)", "")
	$(FLAKE8) setup.py
endif

.PHONY: lint-pylint
lint-pylint: venv  ## Check the code using pylint
ifeq ($(INCLUDE_ASYNC), true)
	$(PYLINT) $(PACKAGE_DIR)
ifeq ($(LINT_TEST_DIR), true)
	$(PYLINT) $(TEST_DIR)
endif
else
	$(PYLINT) $(PACKAGE_DIR) --ignore="$(PACKAGE_ASYNC_FILES)"
ifeq ($(LINT_TEST_DIR), true)
	$(PYLINT) $(TEST_DIR) --ignore="$(TEST_ASYNC_FILES)"
endif
endif
ifneq ("$(wildcard setup.py)", "")
	$(PYLINT) setup.py
endif

.PHONY: lint
lint: lint-black lint-flake8 lint-pylint  ## Run all linting checks on the code


# Testing and coverage.
.PHONY: test
test: venv  ## Run the unit tests
ifeq ($(INCLUDE_ASYNC), true)
ifeq ($(USE_PYTEST), true)
	$(PYTEST) $(TEST_DIR)
else
	$(PYTHON) -m unittest discover -s $(TEST_DIR)/ -t .
endif
else
ifeq ($(USE_PYTEST), true)
	$(PYTEST) $(TEST_DIR) --ignore-glob="*async.py"
else
	for synctest in $(TEST_SYNC_FILES); do echo "\033[1;32m$(TEST_DIR)/$$synctest\033[0m" && $(PYTHON) -m unittest "$(TEST_DIR)/$$synctest"; done
endif
endif

.PHONY: coverage
coverage: venv  ## Run the unit tests and produce coverage info
ifeq ($(INCLUDE_ASYNC), true)
ifeq ($(USE_PYTEST), true)
	$(COVERAGE) run --source $(PACKAGE_DIR) -m pytest $(TEST_DIR)/ && $(COVERAGE) report -m
else
	$(COVERAGE) run --source $(PACKAGE_DIR) -m unittest discover -s $(TEST_DIR) -t . && $(COVERAGE) report -m
endif
else
ifeq ($(USE_PYTEST), true)
	$(COVERAGE) run --source $(PACKAGE_DIR) -m pytest $(TEST_DIR)/ --ignore-glob="*async.py" && $(COVERAGE) report -m
else
	for synctest in $(TEST_SYNC_FILES); do echo "\033[1;32m$(TEST_DIR)/$$synctest\033[0m" && $(COVERAGE) run --source $(PACKAGE_DIR) -m unittest "$(TEST_DIR)/$$synctest"; done
	$(COVERAGE) report -m
endif
endif

.PHONY: htmlcov
htmlcov: coverage  ## Produce a coverage report
	$(COVERAGE) html


# Documentation
.PHONY: docs
docs: venv  ## Build the documentation
	rm -rf $(DOCS_DIR)/build
	@cd $(DOCS_DIR) && $(SPHINX_APIDOC) -f -e -o source/ $(CURDIR)/$(PACKAGE_DIR)/
	@cd $(DOCS_DIR) && make html && make html


.PHONY: release
release:  ## Make a release and upload it to pypi
	rm -rf dist
	scripts/git_tag.sh
	$(PYTHON) setup.py sdist bdist_wheel
	$(TWINE) upload dist/*


.PHONY: all
all: lint htmlcov  ## Run all linting checks and unit tests and produce a coverage report


================================================
FILE: README.rst
================================================
adb\_shell
==========

.. image:: https://travis-ci.com/JeffLIrion/adb_shell.svg?branch=master
   :target: https://travis-ci.com/JeffLIrion/adb_shell

.. image:: https://coveralls.io/repos/github/JeffLIrion/adb_shell/badge.svg?branch=master
   :target: https://coveralls.io/github/JeffLIrion/adb_shell?branch=master

.. image:: https://pepy.tech/badge/adb-shell
   :target: https://pepy.tech/project/adb-shell


Documentation for this package can be found at https://adb-shell.readthedocs.io/.

Prebuilt wheel can be downloaded from `nightly.link <https://nightly.link/JeffLIrion/adb_shell/workflows/python-package/master/wheel.zip>`_.

This Python package implements ADB shell and FileSync functionality.  It originated from `python-adb <https://github.com/google/python-adb>`_.

Installation
------------

.. code-block::

   pip install adb-shell


Async
*****

To utilize the async version of this code, you must install into a Python 3.7+ environment via:

.. code-block::

   pip install adb-shell[async]


USB Support (Experimental)
**************************

To connect to a device via USB, install this package via:

.. code-block::

   pip install adb-shell[usb]


Example Usage
-------------

(Based on `androidtv/adb_manager.py <https://github.com/JeffLIrion/python-androidtv/blob/133063c8d6793a88259af405d6a69ceb301a0ca0/androidtv/adb_manager.py#L67>`_)

.. code-block:: python

   from adb_shell.adb_device import AdbDeviceTcp, AdbDeviceUsb
   from adb_shell.auth.sign_pythonrsa import PythonRSASigner

   # Load the public and private keys
   adbkey = 'path/to/adbkey'
   with open(adbkey) as f:
       priv = f.read()
   with open(adbkey + '.pub') as f:
        pub = f.read()
   signer = PythonRSASigner(pub, priv)

   # Connect
   device1 = AdbDeviceTcp('192.168.0.222', 5555, default_transport_timeout_s=9.)
   device1.connect(rsa_keys=[signer], auth_timeout_s=0.1)

   # Connect via USB (package must be installed via `pip install adb-shell[usb])`
   device2 = AdbDeviceUsb()
   device2.connect(rsa_keys=[signer], auth_timeout_s=0.1)

   # Send a shell command
   response1 = device1.shell('echo TEST1')
   response2 = device2.shell('echo TEST2')


Generate ADB Key Files
**********************

If you need to generate a key, you can do so as follows.

.. code-block:: python

  from adb_shell.auth.keygen import keygen

  keygen('path/to/adbkey')


================================================
FILE: adb_shell/__init__.py
================================================
# Copyright (c) 2021 Jeff Irion and contributors
#
# This file is part of the adb-shell package.

"""ADB shell functionality.

"""


__version__ = "0.4.4"


================================================
FILE: adb_shell/adb_device.py
================================================
# Copyright (c) 2021 Jeff Irion and contributors
#
# This file is part of the adb-shell package.  It incorporates work
# covered by the following license notice:
#
#
#   Copyright 2014 Google Inc. All rights reserved.
#
#   Licensed under the Apache License, Version 2.0 (the "License");
#   you may not use this file except in compliance with the License.
#   You may obtain a copy of the License at
#
#       http://www.apache.org/licenses/LICENSE-2.0
#
#   Unless required by applicable law or agreed to in writing, software
#   distributed under the License is distributed on an "AS IS" BASIS,
#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#   See the License for the specific language governing permissions and
#   limitations under the License.

"""Implement the :class:`AdbDevice` class, which can connect to a device and run ADB shell commands.

.. rubric:: Contents

* :class:`_AdbIOManager`

    * :meth:`_AdbIOManager._read_bytes_from_device`
    * :meth:`_AdbIOManager._read_expected_packet_from_device`
    * :meth:`_AdbIOManager._read_packet_from_device`
    * :meth:`_AdbIOManager._send`
    * :meth:`_AdbIOManager.close`
    * :meth:`_AdbIOManager.connect`
    * :meth:`_AdbIOManager.read`
    * :meth:`_AdbIOManager.send`

* :func:`_open_bytesio`

* :class:`AdbDevice`

    * :meth:`AdbDevice._clse`
    * :meth:`AdbDevice._filesync_flush`
    * :meth:`AdbDevice._filesync_read`
    * :meth:`AdbDevice._filesync_read_buffered`
    * :meth:`AdbDevice._filesync_read_until`
    * :meth:`AdbDevice._filesync_send`
    * :meth:`AdbDevice._okay`
    * :meth:`AdbDevice._open`
    * :meth:`AdbDevice._pull`
    * :meth:`AdbDevice._push`
    * :meth:`AdbDevice._read_until`
    * :meth:`AdbDevice._read_until_close`
    * :meth:`AdbDevice._service`
    * :meth:`AdbDevice._streaming_command`
    * :meth:`AdbDevice._streaming_service`
    * :attr:`AdbDevice.available`
    * :meth:`AdbDevice.close`
    * :meth:`AdbDevice.connect`
    * :meth:`AdbDevice.list`
    * :attr:`AdbDevice.max_chunk_size`
    * :meth:`AdbDevice.pull`
    * :meth:`AdbDevice.push`
    * :meth:`AdbDevice.root`
    * :meth:`AdbDevice.shell`
    * :meth:`AdbDevice.stat`
    * :meth:`AdbDevice.streaming_shell`

* :class:`AdbDeviceTcp`
* :class:`AdbDeviceUsb`

"""


from contextlib import contextmanager
from io import BytesIO
import logging
import os
import struct
import sys
from threading import Lock
import time

from . import constants
from . import exceptions
from .adb_message import AdbMessage, checksum, int_to_cmd, unpack
from .transport.base_transport import BaseTransport
from .transport.tcp_transport import TcpTransport
from .hidden_helpers import DeviceFile, _AdbPacketStore, _AdbTransactionInfo, _FileSyncTransactionInfo, get_banner, get_files_to_push

try:
    from .transport.usb_transport import UsbTransport
except (ImportError, OSError):
    UsbTransport = None


_LOGGER = logging.getLogger(__name__)

_DECODE_ERRORS = "backslashreplace" if sys.version_info[0] > 2 else "replace"


@contextmanager
def _open_bytesio(stream, *args, **kwargs):  # pylint: disable=unused-argument
    """A context manager for a BytesIO object that does nothing.

    Parameters
    ----------
    stream : BytesIO
        The BytesIO stream
    args : list
        Unused positional arguments
    kwargs : dict
        Unused keyword arguments

    Yields
    ------
    stream : BytesIO
        The `stream` input parameter

    """
    yield stream


class _AdbIOManager(object):
    """A class for handling all ADB I/O.

    Notes
    -----
    When the ``self._store_lock`` and ``self._transport_lock`` locks are held at the same time, it must always be the
    case that the ``self._transport_lock`` is acquired first.  This ensures that  there is no potential for deadlock.

    Parameters
    ----------
    transport : BaseTransport
        A transport for communicating with the device; must be an instance of a subclass of :class:`~adb_shell.transport.base_transport.BaseTransport`

    Attributes
    ----------
    _packet_store : _AdbPacketStore
        A store for holding packets that correspond to different ADB streams
    _store_lock : Lock
        A lock for protecting ``self._packet_store`` (this lock is never held for long)
    _transport : BaseTransport
        A transport for communicating with the device; must be an instance of a subclass of :class:`~adb_shell.transport.base_transport.BaseTransport`
    _transport_lock : Lock
        A lock for protecting ``self._transport``

    """

    def __init__(self, transport):
        self._packet_store = _AdbPacketStore()
        self._transport = transport

        self._store_lock = Lock()
        self._transport_lock = Lock()

    def close(self):
        """Close the connection via the provided transport's ``close()`` method and clear the packet store.

        """
        with self._transport_lock:
            self._transport.close()

            with self._store_lock:
                self._packet_store.clear_all()

    def connect(self, banner, rsa_keys, auth_timeout_s, auth_callback, adb_info):
        """Establish an ADB connection to the device.

        1. Use the transport to establish a connection
        2. Send a ``b'CNXN'`` message
        3. Read the response from the device
        4. If ``cmd`` is not ``b'AUTH'``, then authentication is not necesary and so we are done
        5. If no ``rsa_keys`` are provided, raise an exception
        6. Loop through our keys, signing the last ``banner2`` that we received

            1. If the last ``arg0`` was not :const:`adb_shell.constants.AUTH_TOKEN`, raise an exception
            2. Sign the last ``banner2`` and send it in an ``b'AUTH'`` message
            3. Read the response from the device
            4. If ``cmd`` is ``b'CNXN'``, we are done

        7. None of the keys worked, so send ``rsa_keys[0]``'s public key; if the response does not time out, we must have connected successfully


        Parameters
        ----------
        banner : bytearray, bytes
            The hostname of the machine where the Python interpreter is currently running (:attr:`adb_shell.adb_device.AdbDevice._banner`)
        rsa_keys : list, None
            A list of signers of type :class:`~adb_shell.auth.sign_cryptography.CryptographySigner`,
            :class:`~adb_shell.auth.sign_pycryptodome.PycryptodomeAuthSigner`, or :class:`~adb_shell.auth.sign_pythonrsa.PythonRSASigner`
        auth_timeout_s : float, None
            The time in seconds to wait for a ``b'CNXN'`` authentication response
        auth_callback : function, None
            Function callback invoked when the connection needs to be accepted on the device
        adb_info : _AdbTransactionInfo
            Info and settings for this connection attempt

        Returns
        -------
        bool
            Whether the connection was established
        maxdata : int
            Maximum amount of data in an ADB packet

        Raises
        ------
        adb_shell.exceptions.DeviceAuthError
            Device authentication required, no keys available
        adb_shell.exceptions.InvalidResponseError
            Invalid auth response from the device

        """
        with self._transport_lock:
            # 0. Close the connection and clear the store
            self._transport.close()

            with self._store_lock:
                # We can release this lock because packets are only added to the store when the transport lock is held
                self._packet_store.clear_all()

            # 1. Use the transport to establish a connection
            self._transport.connect(adb_info.transport_timeout_s)

            # 2. Send a ``b'CNXN'`` message
            msg = AdbMessage(constants.CNXN, constants.VERSION, constants.MAX_ADB_DATA, b'host::%s\0' % banner)
            self._send(msg, adb_info)

            # 3. Read the response from the device
            cmd, arg0, maxdata, banner2 = self._read_expected_packet_from_device([constants.AUTH, constants.CNXN], adb_info)

            # 4. If ``cmd`` is not ``b'AUTH'``, then authentication is not necesary and so we are done
            if cmd != constants.AUTH:
                return True, maxdata

            # 5. If no ``rsa_keys`` are provided, raise an exception
            if not rsa_keys:
                self._transport.close()
                raise exceptions.DeviceAuthError('Device authentication required, no keys available.')

            # 6. Loop through our keys, signing the last ``banner2`` that we received
            for rsa_key in rsa_keys:
                # 6.1. If the last ``arg0`` was not :const:`adb_shell.constants.AUTH_TOKEN`, raise an exception
                if arg0 != constants.AUTH_TOKEN:
                    self._transport.close()
                    raise exceptions.InvalidResponseError('Unknown AUTH response: %s %s %s' % (arg0, maxdata, banner2))

                # 6.2. Sign the last ``banner2`` and send it in an ``b'AUTH'`` message
                signed_token = rsa_key.Sign(banner2)
                msg = AdbMessage(constants.AUTH, constants.AUTH_SIGNATURE, 0, signed_token)
                self._send(msg, adb_info)

                # 6.3. Read the response from the device
                cmd, arg0, maxdata, banner2 = self._read_expected_packet_from_device([constants.CNXN, constants.AUTH], adb_info)

                # 6.4. If ``cmd`` is ``b'CNXN'``, we are done
                if cmd == constants.CNXN:
                    return True, maxdata

            # 7. None of the keys worked, so send ``rsa_keys[0]``'s public key; if the response does not time out, we must have connected successfully
            pubkey = rsa_keys[0].GetPublicKey()
            if not isinstance(pubkey, (bytes, bytearray)):
                pubkey = bytearray(pubkey, 'utf-8')

            if auth_callback is not None:
                auth_callback(self)

            msg = AdbMessage(constants.AUTH, constants.AUTH_RSAPUBLICKEY, 0, pubkey + b'\0')
            self._send(msg, adb_info)

            adb_info.transport_timeout_s = auth_timeout_s
            _, _, maxdata, _ = self._read_expected_packet_from_device([constants.CNXN], adb_info)
            return True, maxdata

    def read(self, expected_cmds, adb_info, allow_zeros=False):
        """Read packets from the device until we get an expected packet type.

        1. See if the expected packet is in the packet store
        2. While the time limit has not been exceeded:

            1. See if the expected packet is in the packet store
            2. Read a packet from the device.  If it matches what we are looking for, we are done.  If it corresponds to a different stream, add it to the store.

        3. Raise a timeout exception


        Parameters
        ----------
        expected_cmds : list[bytes]
            We will read packets until we encounter one whose "command" field is in ``expected_cmds``
        adb_info : _AdbTransactionInfo
            Info and settings for this ADB transaction
        allow_zeros : bool
            Whether to allow the received ``arg0`` and ``arg1`` values to match with 0, in addition to ``adb_info.remote_id`` and ``adb_info.local_id``, respectively

        Returns
        -------
        cmd : bytes
            The received command, which is in :const:`adb_shell.constants.WIRE_TO_ID` and must be in ``expected_cmds``
        arg0 : int
            TODO
        arg1 : int
            TODO
        data : bytes
            The data that was read

        Raises
        ------
        adb_shell.exceptions.AdbTimeoutError
            Never got one of the expected responses

        """
        # First, try reading from the store. This way, you won't be waiting for the transport if it isn't needed
        with self._store_lock:
            # Recall that `arg0` from the device corresponds to `adb_info.remote_id` and `arg1` from the device corresponds to `adb_info.local_id`
            arg0_arg1 = self._packet_store.find(adb_info.remote_id, adb_info.local_id) if not allow_zeros else self._packet_store.find_allow_zeros(adb_info.remote_id, adb_info.local_id)
            while arg0_arg1:
                cmd, arg0, arg1, data = self._packet_store.get(arg0_arg1[0], arg0_arg1[1])
                if cmd in expected_cmds:
                    return cmd, arg0, arg1, data

                arg0_arg1 = self._packet_store.find(adb_info.remote_id, adb_info.local_id) if not allow_zeros else self._packet_store.find_allow_zeros(adb_info.remote_id, adb_info.local_id)

        # Start the timer
        start = time.time()

        while True:
            with self._transport_lock:
                # Try reading from the store (again) in case a packet got added while waiting to acquire the transport lock
                with self._store_lock:
                    # Recall that `arg0` from the device corresponds to `adb_info.remote_id` and `arg1` from the device corresponds to `adb_info.local_id`
                    arg0_arg1 = self._packet_store.find(adb_info.remote_id, adb_info.local_id) if not allow_zeros else self._packet_store.find_allow_zeros(adb_info.remote_id, adb_info.local_id)
                    while arg0_arg1:
                        cmd, arg0, arg1, data = self._packet_store.get(arg0_arg1[0], arg0_arg1[1])
                        if cmd in expected_cmds:
                            return cmd, arg0, arg1, data

                        arg0_arg1 = self._packet_store.find(adb_info.remote_id, adb_info.local_id) if not allow_zeros else self._packet_store.find_allow_zeros(adb_info.remote_id, adb_info.local_id)

                # Read from the device
                cmd, arg0, arg1, data = self._read_packet_from_device(adb_info)

                if not adb_info.args_match(arg0, arg1, allow_zeros):
                    # The packet is not a match -> put it in the store
                    with self._store_lock:
                        self._packet_store.put(arg0, arg1, cmd, data)

                else:
                    # The packet is a match for this `(adb_info.local_id, adb_info.remote_id)` pair
                    if cmd == constants.CLSE:
                        # Clear the entry in the store
                        with self._store_lock:
                            self._packet_store.clear(arg0, arg1)

                    # If `cmd` is a match, then we are done
                    if cmd in expected_cmds:
                        return cmd, arg0, arg1, data

            # Check if time is up
            if time.time() - start > adb_info.read_timeout_s:
                break

        # Timeout
        raise exceptions.AdbTimeoutError("Never got one of the expected responses: {} (transport_timeout_s = {}, read_timeout_s = {})".format(expected_cmds, adb_info.transport_timeout_s, adb_info.read_timeout_s))

    def send(self, msg, adb_info):
        """Send a message to the device.

        Parameters
        ----------
        msg : AdbMessage
            The data that will be sent
        adb_info : _AdbTransactionInfo
            Info and settings for this ADB transaction

        """
        with self._transport_lock:
            self._send(msg, adb_info)

    def _read_expected_packet_from_device(self, expected_cmds, adb_info):
        """Read packets from the device until we get an expected packet type.

        Parameters
        ----------
        expected_cmds : list[bytes]
            We will read packets until we encounter one whose "command" field is in ``expected_cmds``
        adb_info : _AdbTransactionInfo
            Info and settings for this ADB transaction

        Returns
        -------
        cmd : bytes
            The received command, which is in :const:`adb_shell.constants.WIRE_TO_ID` and must be in ``expected_cmds``
        arg0 : int
            TODO
        arg1 : int
            TODO
        data : bytes
            The data that was read

        Raises
        ------
        adb_shell.exceptions.AdbTimeoutError
            Never got one of the expected responses

        """
        start = time.time()

        while True:
            cmd, arg0, arg1, data = self._read_packet_from_device(adb_info)

            if cmd in expected_cmds:
                return cmd, arg0, arg1, data

            if time.time() - start > adb_info.read_timeout_s:
                # Timeout
                raise exceptions.AdbTimeoutError("Never got one of the expected responses: {} (transport_timeout_s = {}, read_timeout_s = {})".format(expected_cmds, adb_info.transport_timeout_s, adb_info.read_timeout_s))

    def _read_bytes_from_device(self, length, adb_info):
        """Read ``length`` bytes from the device.

        Parameters
        ----------
        length : int
            We will read packets until we get this length of data
        adb_info : _AdbTransactionInfo
            Info and settings for this ADB transaction

        Returns
        -------
        bytes
            The data that was read

        Raises
        ------
        adb_shell.exceptions.AdbTimeoutError
            Did not read ``length`` bytes in time

        """
        start = time.time()
        data = bytearray()

        while length > 0:
            temp = self._transport.bulk_read(length, adb_info.transport_timeout_s)
            if temp:
                # Only log if `temp` is not empty
                _LOGGER.debug("bulk_read(%d): %.1000r", length, temp)

            data += temp
            length -= len(temp)

            if length == 0:
                break

            if time.time() - start > adb_info.read_timeout_s:
                # Timeout
                raise exceptions.AdbTimeoutError("Timeout: read {} of {} bytes (transport_timeout_s = {}, read_timeout_s = {})".format(len(data), len(data) + length, adb_info.transport_timeout_s, adb_info.read_timeout_s))

        return bytes(data)

    def _read_packet_from_device(self, adb_info):
        """Read a complete ADB packet (header + data) from the device.

        Parameters
        ----------
        adb_info : _AdbTransactionInfo
            Info and settings for this ADB transaction

        Returns
        -------
        cmd : bytes
            The received command, which is in :const:`adb_shell.constants.WIRE_TO_ID` and must be in ``expected_cmds``
        arg0 : int
            TODO
        arg1 : int
            TODO
        bytes
            The data that was read

        Raises
        ------
        adb_shell.exceptions.InvalidCommandError
            Unknown command
        adb_shell.exceptions.InvalidChecksumError
            Received checksum does not match the expected checksum

       """
        msg = self._read_bytes_from_device(constants.MESSAGE_SIZE, adb_info)
        cmd, arg0, arg1, data_length, data_checksum = unpack(msg)
        command = constants.WIRE_TO_ID.get(cmd)

        if not command:
            raise exceptions.InvalidCommandError("Unknown command: %d = '%s' (arg0 = %d, arg1 = %d, msg = '%s')" % (cmd, int_to_cmd(cmd), arg0, arg1, msg))

        if data_length == 0:
            return command, arg0, arg1, b""

        data = self._read_bytes_from_device(data_length, adb_info)
        actual_checksum = checksum(data)
        if actual_checksum != data_checksum:
            raise exceptions.InvalidChecksumError("Received checksum {} != {}".format(actual_checksum, data_checksum))

        return command, arg0, arg1, data

    def _send(self, msg, adb_info):
        """Send a message to the device.

        1. Send the message header (:meth:`adb_shell.adb_message.AdbMessage.pack <AdbMessage.pack>`)
        2. Send the message data


        Parameters
        ----------
        msg : AdbMessage
            The data that will be sent
        adb_info : _AdbTransactionInfo
            Info and settings for this ADB transaction

        """
        packed = msg.pack()
        _LOGGER.debug("bulk_write(%d): %r", len(packed), packed)
        self._transport.bulk_write(packed, adb_info.transport_timeout_s)

        if msg.data:
            _LOGGER.debug("bulk_write(%d): %r", len(msg.data), msg.data)
            self._transport.bulk_write(msg.data, adb_info.transport_timeout_s)


class AdbDevice(object):
    """A class with methods for connecting to a device and executing ADB commands.

    Parameters
    ----------
    transport : BaseTransport
        A user-provided transport for communicating with the device; must be an instance of a subclass of :class:`~adb_shell.transport.base_transport.BaseTransport`
    default_transport_timeout_s : float, None
        Default timeout in seconds for transport packets, or ``None``
    banner : str, bytes, None
        The hostname of the machine where the Python interpreter is currently running; if
        it is not provided, it will be determined via ``socket.gethostname()``

    Raises
    ------
    adb_shell.exceptions.InvalidTransportError
        The passed ``transport`` is not an instance of a subclass of :class:`~adb_shell.transport.base_transport.BaseTransport`

    Attributes
    ----------
    _available : bool
        Whether an ADB connection to the device has been established
    _banner : bytearray, bytes
        The hostname of the machine where the Python interpreter is currently running
    _default_transport_timeout_s : float, None
        Default timeout in seconds for transport packets, or ``None``
    _io_manager : _AdbIOManager
        Used for handling all ADB I/O
    _local_id : int
        The local ID that is used for ADB transactions; the value is incremented each time and is always in the range ``[1, 2^32)``
    _local_id_lock : Lock
        A lock for protecting ``_local_id``; this is never held for long
    _maxdata: int
        Maximum amount of data in an ADB packet

    """

    def __init__(self, transport, default_transport_timeout_s=None, banner=None):
        if banner and not isinstance(banner, (bytes, bytearray)):
            self._banner = bytearray(banner, 'utf-8')
        else:
            self._banner = banner

        if not isinstance(transport, BaseTransport):
            raise exceptions.InvalidTransportError("`transport` must be an instance of a subclass of `BaseTransport`")

        self._io_manager = _AdbIOManager(transport)

        self._available = False
        self._default_transport_timeout_s = default_transport_timeout_s
        self._local_id = 0
        self._local_id_lock = Lock()
        self._maxdata = constants.MAX_PUSH_DATA

    # ======================================================================= #
    #                                                                         #
    #                       Properties & simple methods                       #
    #                                                                         #
    # ======================================================================= #
    @property
    def available(self):
        """Whether or not an ADB connection to the device has been established.

        Returns
        -------
        bool
            ``self._available``

        """
        return self._available

    @property
    def max_chunk_size(self):
        """Maximum chunk size for filesync operations

        Returns
        -------
        int
            Minimum value based on :const:`adb_shell.constants.MAX_CHUNK_SIZE` and ``_max_data / 2``, fallback to legacy :const:`adb_shell.constants.MAX_PUSH_DATA`

        """
        return min(constants.MAX_CHUNK_SIZE, self._maxdata // 2) or constants.MAX_PUSH_DATA

    def _get_transport_timeout_s(self, transport_timeout_s):
        """Use the provided ``transport_timeout_s`` if it is not ``None``; otherwise, use ``self._default_transport_timeout_s``

        Parameters
        ----------
        transport_timeout_s : float, None
            The potential transport timeout

        Returns
        -------
        float
            ``transport_timeout_s`` if it is not ``None``; otherwise, ``self._default_transport_timeout_s``

        """
        return transport_timeout_s if transport_timeout_s is not None else self._default_transport_timeout_s

    # ======================================================================= #
    #                                                                         #
    #                             Close & Connect                             #
    #                                                                         #
    # ======================================================================= #
    def close(self):
        """Close the connection via the provided transport's ``close()`` method.

        """
        self._available = False
        self._io_manager.close()

    def connect(self, rsa_keys=None, transport_timeout_s=None, auth_timeout_s=constants.DEFAULT_AUTH_TIMEOUT_S, read_timeout_s=constants.DEFAULT_READ_TIMEOUT_S, auth_callback=None):
        """Establish an ADB connection to the device.

        See :meth:`_AdbIOManager.connect`.

        Parameters
        ----------
        rsa_keys : list, None
            A list of signers of type :class:`~adb_shell.auth.sign_cryptography.CryptographySigner`,
            :class:`~adb_shell.auth.sign_pycryptodome.PycryptodomeAuthSigner`, or :class:`~adb_shell.auth.sign_pythonrsa.PythonRSASigner`
        transport_timeout_s : float, None
            Timeout in seconds for sending and receiving data, or ``None``; see :meth:`BaseTransport.bulk_read() <adb_shell.transport.base_transport.BaseTransport.bulk_read>`
            and :meth:`BaseTransport.bulk_write() <adb_shell.transport.base_transport.BaseTransport.bulk_write>`
        auth_timeout_s : float, None
            The time in seconds to wait for a ``b'CNXN'`` authentication response
        read_timeout_s : float
            The total time in seconds to wait for expected commands in :meth:`_AdbIOManager._read_expected_packet_from_device`
        auth_callback : function, None
            Function callback invoked when the connection needs to be accepted on the device

        Returns
        -------
        bool
            Whether the connection was established (:attr:`AdbDevice.available`)

        """
        # Get `self._banner` if it was not provided in the constructor
        if not self._banner:
            self._banner = get_banner()

        # Instantiate the `_AdbTransactionInfo`
        adb_info = _AdbTransactionInfo(None, None, self._get_transport_timeout_s(transport_timeout_s), read_timeout_s, None)

        # Mark the device as unavailable
        self._available = False

        # Use the IO manager to connect
        self._available, self._maxdata = self._io_manager.connect(self._banner, rsa_keys, auth_timeout_s, auth_callback, adb_info)

        return self._available

    # ======================================================================= #
    #                                                                         #
    #                                 Services                                #
    #                                                                         #
    # ======================================================================= #
    def _service(self, service, command, transport_timeout_s=None, read_timeout_s=constants.DEFAULT_READ_TIMEOUT_S, timeout_s=None, decode=True):
        """Send an ADB command to the device.

        Parameters
        ----------
        service : bytes
            The ADB service to talk to (e.g., ``b'shell'``)
        command : bytes
            The command that will be sent
        transport_timeout_s : float, None
            Timeout in seconds for sending and receiving data, or ``None``; see :meth:`BaseTransport.bulk_read() <adb_shell.transport.base_transport.BaseTransport.bulk_read>`
            and :meth:`BaseTransport.bulk_write() <adb_shell.transport.base_transport.BaseTransport.bulk_write>`
        read_timeout_s : float
            The total time in seconds to wait for a ``b'CLSE'`` or ``b'OKAY'`` command in :meth:`_AdbIOManager.read`
        timeout_s : float, None
            The total time in seconds to wait for the ADB command to finish
        decode : bool
            Whether to decode the output to utf8 before returning

        Returns
        -------
        bytes, str
            The output of the ADB command as a string if ``decode`` is True, otherwise as bytes.

        """
        if decode:
            return b''.join(self._streaming_command(service, command, transport_timeout_s, read_timeout_s, timeout_s)).decode('utf8', _DECODE_ERRORS)
        return b''.join(self._streaming_command(service, command, transport_timeout_s, read_timeout_s, timeout_s))

    def _streaming_service(self, service, command, transport_timeout_s=None, read_timeout_s=constants.DEFAULT_READ_TIMEOUT_S, decode=True):
        """Send an ADB command to the device, yielding each line of output.

        Parameters
        ----------
        service : bytes
            The ADB service to talk to (e.g., ``b'shell'``)
        command : bytes
            The command that will be sent
        transport_timeout_s : float, None
            Timeout in seconds for sending and receiving data, or ``None``; see :meth:`BaseTransport.bulk_read() <adb_shell.transport.base_transport.BaseTransport.bulk_read>`
            and :meth:`BaseTransport.bulk_write() <adb_shell.transport.base_transport.BaseTransport.bulk_write>`
        read_timeout_s : float
            The total time in seconds to wait for a ``b'CLSE'`` or ``b'OKAY'`` command in :meth:`_AdbIOManager.read`
        decode : bool
            Whether to decode the output to utf8 before returning

        Yields
        -------
        bytes, str
            The line-by-line output of the ADB command as a string if ``decode`` is True, otherwise as bytes.

        """
        stream = self._streaming_command(service, command, transport_timeout_s, read_timeout_s, None)
        if decode:
            yield from (stream_line.decode('utf8', _DECODE_ERRORS) for stream_line in stream)
        else:
            yield from stream

    def exec_out(self, command, transport_timeout_s=None, read_timeout_s=constants.DEFAULT_READ_TIMEOUT_S, timeout_s=None, decode=True):
        """Send an ADB ``exec-out`` command to the device.

        https://www.linux-magazine.com/Issues/2017/195/Ask-Klaus

        Parameters
        ----------
        command : str
            The exec-out command that will be sent
        transport_timeout_s : float, None
            Timeout in seconds for sending and receiving data, or ``None``; see :meth:`BaseTransport.bulk_read() <adb_shell.transport.base_transport.BaseTransport.bulk_read>`
            and :meth:`BaseTransport.bulk_write() <adb_shell.transport.base_transport.BaseTransport.bulk_write>`
        read_timeout_s : float
            The total time in seconds to wait for a ``b'CLSE'`` or ``b'OKAY'`` command in :meth:`_AdbIOManager.read`
        timeout_s : float, None
            The total time in seconds to wait for the ADB command to finish
        decode : bool
            Whether to decode the output to utf8 before returning

        Returns
        -------
        bytes, str
            The output of the ADB exec-out command as a string if ``decode`` is True, otherwise as bytes.

        """
        if not self.available:
            raise exceptions.AdbConnectionError("ADB command not sent because a connection to the device has not been established.  (Did you call `AdbDevice.connect()`?)")

        return self._service(b'exec', command.encode('utf8'), transport_timeout_s, read_timeout_s, timeout_s, decode)

    def reboot(self, fastboot=False, transport_timeout_s=None, read_timeout_s=constants.DEFAULT_READ_TIMEOUT_S, timeout_s=None):
        """Reboot the device.

        Parameters
        ----------
        fastboot : bool
            Whether to reboot the device into fastboot
        transport_timeout_s : float, None
            Timeout in seconds for sending and receiving data, or ``None``; see :meth:`BaseTransport.bulk_read() <adb_shell.transport.base_transport.BaseTransport.bulk_read>`
            and :meth:`BaseTransport.bulk_write() <adb_shell.transport.base_transport.BaseTransport.bulk_write>`
        read_timeout_s : float
            The total time in seconds to wait for a ``b'CLSE'`` or ``b'OKAY'`` command in :meth:`_AdbIOManager.read`
        timeout_s : float, None
            The total time in seconds to wait for the ADB command to finish

        """
        if not self.available:
            raise exceptions.AdbConnectionError("ADB command not sent because a connection to the device has not been established.  (Did you call `AdbDevice.connect()`?)")

        self._open(b'reboot:bootloader' if fastboot else b'reboot:', transport_timeout_s, read_timeout_s, timeout_s)

    def root(self, transport_timeout_s=None, read_timeout_s=constants.DEFAULT_READ_TIMEOUT_S, timeout_s=None):
        """Gain root access.

        The device must be rooted in order for this to work.

        Parameters
        ----------
        transport_timeout_s : float, None
            Timeout in seconds for sending and receiving data, or ``None``; see :meth:`BaseTransport.bulk_read() <adb_shell.transport.base_transport.BaseTransport.bulk_read>`
            and :meth:`BaseTransport.bulk_write() <adb_shell.transport.base_transport.BaseTransport.bulk_write>`
        read_timeout_s : float
            The total time in seconds to wait for a ``b'CLSE'`` or ``b'OKAY'`` command in :meth:`_AdbIOManager.read`
        timeout_s : float, None
            The total time in seconds to wait for the ADB command to finish

        """
        if not self.available:
            raise exceptions.AdbConnectionError("ADB command not sent because a connection to the device has not been established.  (Did you call `AdbDevice.connect()`?)")

        self._service(b'root', b'', transport_timeout_s, read_timeout_s, timeout_s, False)

    def shell(self, command, transport_timeout_s=None, read_timeout_s=constants.DEFAULT_READ_TIMEOUT_S, timeout_s=None, decode=True):
        """Send an ADB shell command to the device.

        Parameters
        ----------
        command : str
            The shell command that will be sent
        transport_timeout_s : float, None
            Timeout in seconds for sending and receiving data, or ``None``; see :meth:`BaseTransport.bulk_read() <adb_shell.transport.base_transport.BaseTransport.bulk_read>`
            and :meth:`BaseTransport.bulk_write() <adb_shell.transport.base_transport.BaseTransport.bulk_write>`
        read_timeout_s : float
            The total time in seconds to wait for a ``b'CLSE'`` or ``b'OKAY'`` command in :meth:`_AdbIOManager.read`
        timeout_s : float, None
            The total time in seconds to wait for the ADB command to finish
        decode : bool
            Whether to decode the output to utf8 before returning

        Returns
        -------
        bytes, str
            The output of the ADB shell command as a string if ``decode`` is True, otherwise as bytes.

        """
        if not self.available:
            raise exceptions.AdbConnectionError("ADB command not sent because a connection to the device has not been established.  (Did you call `AdbDevice.connect()`?)")

        return self._service(b'shell', command.encode('utf8'), transport_timeout_s, read_timeout_s, timeout_s, decode)

    def streaming_shell(self, command, transport_timeout_s=None, read_timeout_s=constants.DEFAULT_READ_TIMEOUT_S, decode=True):
        """Send an ADB shell command to the device, yielding each line of output.

        Parameters
        ----------
        command : str
            The shell command that will be sent
        transport_timeout_s : float, None
            Timeout in seconds for sending and receiving data, or ``None``; see :meth:`BaseTransport.bulk_read() <adb_shell.transport.base_transport.BaseTransport.bulk_read>`
            and :meth:`BaseTransport.bulk_write() <adb_shell.transport.base_transport.BaseTransport.bulk_write>`
        read_timeout_s : float
            The total time in seconds to wait for a ``b'CLSE'`` or ``b'OKAY'`` command in :meth:`_AdbIOManager.read`
        decode : bool
            Whether to decode the output to utf8 before returning

        Yields
        -------
        bytes, str
            The line-by-line output of the ADB shell command as a string if ``decode`` is True, otherwise as bytes.

        """
        if not self.available:
            raise exceptions.AdbConnectionError("ADB command not sent because a connection to the device has not been established.  (Did you call `AdbDevice.connect()`?)")

        yield from self._streaming_service(b'shell', command.encode('utf8'), transport_timeout_s, read_timeout_s, decode)

    # ======================================================================= #
    #                                                                         #
    #                                 FileSync                                #
    #                                                                         #
    # ======================================================================= #
    def list(self, device_path, transport_timeout_s=None, read_timeout_s=constants.DEFAULT_READ_TIMEOUT_S):
        """Return a directory listing of the given path.

        Parameters
        ----------
        device_path : str
            Directory to list.
        transport_timeout_s : float, None
            Expected timeout for any part of the pull.
        read_timeout_s : float
            The total time in seconds to wait for a ``b'CLSE'`` or ``b'OKAY'`` command in :meth:`_AdbIOManager.read`

        Returns
        -------
        files : list[DeviceFile]
            Filename, mode, size, and mtime info for the files in the directory

        """
        if not device_path:
            raise exceptions.DevicePathInvalidError("Cannot list an empty device path")
        if not self.available:
            raise exceptions.AdbConnectionError("ADB command not sent because a connection to the device has not been established.  (Did you call `AdbDevice.connect()`?)")

        adb_info = self._open(b"sync:", transport_timeout_s, read_timeout_s, None)
        filesync_info = _FileSyncTransactionInfo(constants.FILESYNC_LIST_FORMAT, maxdata=self._maxdata)

        self._filesync_send(constants.LIST, adb_info, filesync_info, data=device_path)
        files = []

        for cmd_id, header, filename in self._filesync_read_until([constants.DENT], [constants.DONE], adb_info, filesync_info):
            if cmd_id == constants.DONE:
                break

            mode, size, mtime = header
            files.append(DeviceFile(filename, mode, size, mtime))

        self._clse(adb_info)

        return files

    def pull(self, device_path, local_path, progress_callback=None, transport_timeout_s=None, read_timeout_s=constants.DEFAULT_READ_TIMEOUT_S):
        """Pull a file from the device.

        Parameters
        ----------
        device_path : str
            The file on the device that will be pulled
        local_path : str, BytesIO
            The path or BytesIO stream where the file will be downloaded
        progress_callback : function, None
            Callback method that accepts ``device_path``, ``bytes_written``, and ``total_bytes``
        transport_timeout_s : float, None
            Expected timeout for any part of the pull.
        read_timeout_s : float
            The total time in seconds to wait for a ``b'CLSE'`` or ``b'OKAY'`` command in :meth:`_AdbIOManager.read`

        """
        if not device_path:
            raise exceptions.DevicePathInvalidError("Cannot pull from an empty device path")
        if not self.available:
            raise exceptions.AdbConnectionError("ADB command not sent because a connection to the device has not been established.  (Did you call `AdbDevice.connect()`?)")

        opener = _open_bytesio if isinstance(local_path, BytesIO) else open
        with opener(local_path, 'wb') as stream:
            adb_info = self._open(b'sync:', transport_timeout_s, read_timeout_s, None)
            filesync_info = _FileSyncTransactionInfo(constants.FILESYNC_PULL_FORMAT, maxdata=self._maxdata)

            try:
                self._pull(device_path, stream, progress_callback, adb_info, filesync_info)
            finally:
                self._clse(adb_info)

    def _pull(self, device_path, stream, progress_callback, adb_info, filesync_info):
        """Pull a file from the device into the file-like ``local_path``.

        Parameters
        ----------
        device_path : str
            The file on the device that will be pulled
        stream : _io.BytesIO
            File-like object for writing to
        progress_callback : function, None
            Callback method that accepts ``device_path``, ``bytes_written``, and ``total_bytes``
        adb_info : _AdbTransactionInfo
            Info and settings for this ADB transaction
        filesync_info : _FileSyncTransactionInfo
            Data and storage for this FileSync transaction

        """
        if progress_callback:
            total_bytes = self.stat(device_path)[1]

        self._filesync_send(constants.RECV, adb_info, filesync_info, data=device_path)
        for cmd_id, _, data in self._filesync_read_until([constants.DATA], [constants.DONE], adb_info, filesync_info):
            if cmd_id == constants.DONE:
                break

            stream.write(data)
            if progress_callback:
                try:
                    progress_callback(device_path, len(data), total_bytes)
                except:  # noqa pylint: disable=bare-except
                    pass

    def push(self, local_path, device_path, st_mode=constants.DEFAULT_PUSH_MODE, mtime=0, progress_callback=None, transport_timeout_s=None, read_timeout_s=constants.DEFAULT_READ_TIMEOUT_S):
        """Push a file or directory to the device.

        Parameters
        ----------
        local_path : str, BytesIO
            A filename, directory, or BytesIO stream to push to the device
        device_path : str
            Destination on the device to write to.
        st_mode : int
            Stat mode for ``local_path``
        mtime : int
            Modification time to set on the file.
        progress_callback : function, None
            Callback method that accepts ``device_path``, ``bytes_written``, and ``total_bytes``
        transport_timeout_s : float, None
            Expected timeout for any part of the push.
        read_timeout_s : float
            The total time in seconds to wait for a ``b'CLSE'`` or ``b'OKAY'`` command in :meth:`_AdbIOManager.read`

        """
        if not device_path:
            raise exceptions.DevicePathInvalidError("Cannot push to an empty device path")
        if not self.available:
            raise exceptions.AdbConnectionError("ADB command not sent because a connection to the device has not been established.  (Did you call `AdbDevice.connect()`?)")

        local_path_is_dir, local_paths, device_paths = get_files_to_push(local_path, device_path)

        if local_path_is_dir:
            self.shell("mkdir " + device_path, transport_timeout_s, read_timeout_s)

        for _local_path, _device_path in zip(local_paths, device_paths):
            opener = _open_bytesio if isinstance(local_path, BytesIO) else open
            with opener(_local_path, 'rb') as stream:
                adb_info = self._open(b'sync:', transport_timeout_s, read_timeout_s, None)
                filesync_info = _FileSyncTransactionInfo(constants.FILESYNC_PUSH_FORMAT, maxdata=self._maxdata)

                self._push(stream, _device_path, st_mode, mtime, progress_callback, adb_info, filesync_info)

            self._clse(adb_info)

    def _push(self, stream, device_path, st_mode, mtime, progress_callback, adb_info, filesync_info):
        """Push a file-like object to the device.

        Parameters
        ----------
        stream : _io.BytesIO
            File-like object for reading from
        device_path : str
            Destination on the device to write to
        st_mode : int
            Stat mode for the file
        mtime : int
            Modification time
        progress_callback : function, None
            Callback method that accepts ``device_path``, ``bytes_written``, and ``total_bytes``
        adb_info : _AdbTransactionInfo
            Info and settings for this ADB transaction

        Raises
        ------
        PushFailedError
            Raised on push failure.

        """
        fileinfo = ('{},{}'.format(device_path, int(st_mode))).encode('utf-8')

        self._filesync_send(constants.SEND, adb_info, filesync_info, data=fileinfo)

        if progress_callback:
            total_bytes = os.fstat(stream.fileno()).st_size

        while True:
            data = stream.read(self.max_chunk_size)
            if data:
                self._filesync_send(constants.DATA, adb_info, filesync_info, data=data)

                if progress_callback:
                    try:
                        progress_callback(device_path, len(data), total_bytes)
                    except:  # noqa pylint: disable=bare-except
                        pass
            else:
                break

        if mtime == 0:
            mtime = int(time.time())

        # DONE doesn't send data, but it hides the last bit of data in the size field.
        self._filesync_send(constants.DONE, adb_info, filesync_info, size=mtime)
        for cmd_id, _, data in self._filesync_read_until([], [constants.OKAY, constants.FAIL], adb_info, filesync_info):
            if cmd_id == constants.OKAY:
                return

            raise exceptions.PushFailedError(data)

    def stat(self, device_path, transport_timeout_s=None, read_timeout_s=constants.DEFAULT_READ_TIMEOUT_S):
        """Get a file's ``stat()`` information.

        Parameters
        ----------
        device_path : str
            The file on the device for which we will get information.
        transport_timeout_s : float, None
            Expected timeout for any part of the pull.
        read_timeout_s : float
            The total time in seconds to wait for a ``b'CLSE'`` or ``b'OKAY'`` command in :meth:`_AdbIOManager.read`

        Returns
        -------
        mode : int
            The octal permissions for the file
        size : int
            The size of the file
        mtime : int
            The last modified time for the file

        """
        if not device_path:
            raise exceptions.DevicePathInvalidError("Cannot stat an empty device path")
        if not self.available:
            raise exceptions.AdbConnectionError("ADB command not sent because a connection to the device has not been established.  (Did you call `AdbDevice.connect()`?)")

        adb_info = self._open(b'sync:', transport_timeout_s, read_timeout_s, None)
        filesync_info = _FileSyncTransactionInfo(constants.FILESYNC_STAT_FORMAT, maxdata=self._maxdata)

        self._filesync_send(constants.STAT, adb_info, filesync_info, data=device_path)
        _, (mode, size, mtime), _ = self._filesync_read([constants.STAT], adb_info, filesync_info)
        self._clse(adb_info)

        return mode, size, mtime

    # ======================================================================= #
    #                                                                         #
    #                       Hidden Methods: send packets                      #
    #                                                                         #
    # ======================================================================= #
    def _clse(self, adb_info):
        """Send a ``b'CLSE'`` message and then read a ``b'CLSE'`` message.

        .. warning::

           This is not to be confused with the :meth:`AdbDevice.close` method!


        Parameters
        ----------
        adb_info : _AdbTransactionInfo
            Info and settings for this ADB transaction

        """
        msg = AdbMessage(constants.CLSE, adb_info.local_id, adb_info.remote_id)
        self._io_manager.send(msg, adb_info)
        self._read_until([constants.CLSE], adb_info)

    def _okay(self, adb_info):
        """Send an ``b'OKAY'`` mesage.

        Parameters
        ----------
        adb_info : _AdbTransactionInfo
            Info and settings for this ADB transaction

        """
        msg = AdbMessage(constants.OKAY, adb_info.local_id, adb_info.remote_id)
        self._io_manager.send(msg, adb_info)

    # ======================================================================= #
    #                                                                         #
    #                              Hidden Methods                             #
    #                                                                         #
    # ======================================================================= #
    def _open(self, destination, transport_timeout_s, read_timeout_s, timeout_s):
        """Opens a new connection to the device via an ``b'OPEN'`` message.

        1. :meth:`~_AdbIOManager.send` an ``b'OPEN'`` command to the device that specifies the ``local_id``
        2. :meth:`~_AdbIOManager.read` the response from the device and fill in the ``adb_info.remote_id`` attribute


        Parameters
        ----------
        destination : bytes
            ``b'SERVICE:COMMAND'``
        transport_timeout_s : float, None
            Timeout in seconds for sending and receiving data, or ``None``; see :meth:`BaseTransport.bulk_read() <adb_shell.transport.base_transport.BaseTransport.bulk_read>`
            and :meth:`BaseTransport.bulk_write() <adb_shell.transport.base_transport.BaseTransport.bulk_write>`
        read_timeout_s : float
            The total time in seconds to wait for a ``b'CLSE'`` or ``b'OKAY'`` command in :meth:`_AdbIOManager.read`
        timeout_s : float, None
            The total time in seconds to wait for the ADB command to finish

        Returns
        -------
        adb_info : _AdbTransactionInfo
            Info and settings for this ADB transaction

        """
        with self._local_id_lock:
            self._local_id += 1
            if self._local_id == 2**32:
                self._local_id = 1

            adb_info = _AdbTransactionInfo(self._local_id, None, self._get_transport_timeout_s(transport_timeout_s), read_timeout_s, timeout_s)

        msg = AdbMessage(constants.OPEN, adb_info.local_id, 0, destination + b'\0')
        self._io_manager.send(msg, adb_info)
        _, adb_info.remote_id, _, _ = self._io_manager.read([constants.OKAY], adb_info)

        return adb_info

    def _read_until(self, expected_cmds, adb_info):
        """Read a packet, acknowledging any write packets.

        1. Read data via :meth:`_AdbIOManager.read`
        2. If a ``b'WRTE'`` packet is received, send an ``b'OKAY'`` packet via :meth:`AdbDevice._okay`
        3. Return the ``cmd`` and ``data`` that were read by :meth:`_AdbIOManager.read`


        Parameters
        ----------
        expected_cmds : list[bytes]
            :meth:`_AdbIOManager.read` will look for a packet whose command is in ``expected_cmds``
        adb_info : _AdbTransactionInfo
            Info and settings for this ADB transaction

        Returns
        -------
        cmd : bytes
            The command that was received by :meth:`_AdbIOManager.read`, which is in :const:`adb_shell.constants.WIRE_TO_ID` and must be in ``expected_cmds``
        data : bytes
            The data that was received by :meth:`_AdbIOManager.read`

        """
        cmd, _, _, data = self._io_manager.read(expected_cmds, adb_info, allow_zeros=True)

        # Acknowledge write packets
        if cmd == constants.WRTE:
            self._okay(adb_info)

        return cmd, data

    def _read_until_close(self, adb_info):
        """Yield packets until a ``b'CLSE'`` packet is received.

        1. Read the ``cmd`` and ``data`` fields from a ``b'CLSE'`` or ``b'WRTE'`` packet via :meth:`AdbDevice._read_until`
        2. If ``cmd`` is ``b'CLSE'``, then send a ``b'CLSE'`` message and stop
        3. Yield ``data`` and repeat


        Parameters
        ----------
        adb_info : _AdbTransactionInfo
            Info and settings for this ADB transaction

        Yields
        ------
        data : bytes
            The data that was read by :meth:`AdbDevice._read_until`

        """
        start = time.time()

        while True:
            cmd, data = self._read_until([constants.CLSE, constants.WRTE], adb_info)

            if cmd == constants.CLSE:
                msg = AdbMessage(constants.CLSE, adb_info.local_id, adb_info.remote_id)
                self._io_manager.send(msg, adb_info)
                break

            yield data

            # Make sure the ADB command has not timed out
            if adb_info.timeout_s is not None and time.time() - start > adb_info.timeout_s:
                raise exceptions.AdbTimeoutError("The command did not complete within {} seconds".format(adb_info.timeout_s))

    def _streaming_command(self, service, command, transport_timeout_s, read_timeout_s, timeout_s):
        """One complete set of packets for a single command.

        1. :meth:`~AdbDevice._open` a new connection to the device, where the ``destination`` parameter is ``service:command``
        2. Read the response data via :meth:`AdbDevice._read_until_close`


        .. note::

           All the data is held in memory, and thus large responses will be slow and can fill up memory.


        Parameters
        ----------
        service : bytes
            The ADB service (e.g., ``b'shell'``, as used by :meth:`AdbDevice.shell`)
        command : bytes
            The service command
        transport_timeout_s : float, None
            Timeout in seconds for sending and receiving data, or ``None``; see :meth:`BaseTransport.bulk_read() <adb_shell.transport.base_transport.BaseTransport.bulk_read>`
            and :meth:`BaseTransport.bulk_write() <adb_shell.transport.base_transport.BaseTransport.bulk_write>`
        read_timeout_s : float
            The total time in seconds to wait for a ``b'CLSE'`` or ``b'OKAY'`` command in :meth:`_AdbIOManager.read`
        timeout_s : float, None
            The total time in seconds to wait for the ADB command to finish

        Yields
        ------
        bytes
            The responses from the service.

        """
        adb_info = self._open(b'%s:%s' % (service, command), transport_timeout_s, read_timeout_s, timeout_s)

        yield from self._read_until_close(adb_info)

    # ======================================================================= #
    #                                                                         #
    #                         FileSync Hidden Methods                         #
    #                                                                         #
    # ======================================================================= #
    def _filesync_flush(self, adb_info, filesync_info):
        """Write the data in the buffer up to ``filesync_info.send_idx``, then set ``filesync_info.send_idx`` to 0.

        Parameters
        ----------
        adb_info : _AdbTransactionInfo
            Info and settings for this ADB transaction
        filesync_info : _FileSyncTransactionInfo
            Data and storage for this FileSync transaction

        """
        # Send the buffer
        msg = AdbMessage(constants.WRTE, adb_info.local_id, adb_info.remote_id, filesync_info.send_buffer[:filesync_info.send_idx])
        self._io_manager.send(msg, adb_info)

        # Expect an 'OKAY' in response
        self._read_until([constants.OKAY], adb_info)

        # Reset the send index
        filesync_info.send_idx = 0

    def _filesync_read(self, expected_ids, adb_info, filesync_info):
        """Read ADB messages and return FileSync packets.

        Parameters
        ----------
        expected_ids : tuple[bytes]
            If the received header ID is not in ``expected_ids``, an exception will be raised
        adb_info : _AdbTransactionInfo
            Info and settings for this ADB transaction
        filesync_info : _FileSyncTransactionInfo
            Data and storage for this FileSync transaction

        Returns
        -------
        command_id : bytes
            The received header ID
        tuple
            The contents of the header
        data : bytearray, None
            The received data, or ``None`` if the command ID is :const:`adb_shell.constants.STAT`

        Raises
        ------
        adb_shell.exceptions.AdbCommandFailureException
            Command failed
        adb_shell.exceptions.InvalidResponseError
            Received response was not in ``expected_ids``

        """
        if filesync_info.send_idx:
            self._filesync_flush(adb_info, filesync_info)

        # Read one filesync packet off the recv buffer.
        header_data = self._filesync_read_buffered(filesync_info.recv_message_size, adb_info, filesync_info)
        header = struct.unpack(filesync_info.recv_message_format, header_data)

        # Header is (ID, ...).
        command_id = constants.FILESYNC_WIRE_TO_ID[header[0]]

        # Whether there is data to read
        read_data = command_id != constants.STAT

        if read_data:
            # Header is (ID, ..., size) --> read the data
            data = self._filesync_read_buffered(header[-1], adb_info, filesync_info)
        else:
            # No data to be read
            data = bytearray()

        if command_id not in expected_ids:
            if command_id == constants.FAIL:
                reason = data.decode('utf-8', errors=_DECODE_ERRORS)

                raise exceptions.AdbCommandFailureException('Command failed: {}'.format(reason))

            raise exceptions.InvalidResponseError('Expected one of %s, got %s' % (expected_ids, command_id))

        if not read_data:
            return command_id, header[1:], None

        return command_id, header[1:-1], data

    def _filesync_read_buffered(self, size, adb_info, filesync_info):
        """Read ``size`` bytes of data from ``self.recv_buffer``.

        Parameters
        ----------
        size : int
            The amount of data to read
        adb_info : _AdbTransactionInfo
            Info and settings for this ADB transaction
        filesync_info : _FileSyncTransactionInfo
            Data and storage for this FileSync transaction

        Returns
        -------
        result : bytearray
            The read data

        """
        # Ensure recv buffer has enough data.
        while len(filesync_info.recv_buffer) < size:
            _, data = self._read_until([constants.WRTE], adb_info)
            filesync_info.recv_buffer += data

        result = filesync_info.recv_buffer[:size]
        filesync_info.recv_buffer = filesync_info.recv_buffer[size:]
        return result

    def _filesync_read_until(self, expected_ids, finish_ids, adb_info, filesync_info):
        """Useful wrapper around :meth:`AdbDevice._filesync_read`.

        Parameters
        ----------
        expected_ids : tuple[bytes]
            If the received header ID is not in ``expected_ids``, an exception will be raised
        finish_ids : tuple[bytes]
            We will read until we find a header ID that is in ``finish_ids``
        adb_info : _AdbTransactionInfo
            Info and settings for this ADB transaction
        filesync_info : _FileSyncTransactionInfo
            Data and storage for this FileSync transaction

        Yields
        ------
        cmd_id : bytes
            The received header ID
        header : tuple
            TODO
        data : bytearray
            The received data

        """
        while True:
            cmd_id, header, data = self._filesync_read(expected_ids + finish_ids, adb_info, filesync_info)
            yield cmd_id, header, data

            # These lines are not reachable because whenever this method is called and `cmd_id` is in `finish_ids`, the code
            # either breaks (`list` and `_pull`), returns (`_push`), or raises an exception (`_push`)
            if cmd_id in finish_ids:  # pragma: no cover
                break

    def _filesync_send(self, command_id, adb_info, filesync_info, data=b'', size=None):
        """Send/buffer FileSync packets.

        Packets are buffered and only flushed when this connection is read from. All
        messages have a response from the device, so this will always get flushed.

        Parameters
        ----------
        command_id : bytes
            Command to send.
        adb_info : _AdbTransactionInfo
            Info and settings for this ADB transaction
        filesync_info : _FileSyncTransactionInfo
            Data and storage for this FileSync transaction
        data : str, bytes
            Optional data to send, must set data or size.
        size : int, None
            Optionally override size from len(data).

        """
        if not isinstance(data, bytes):
            data = data.encode('utf8')
        if size is None:
            size = len(data)

        if not filesync_info.can_add_to_send_buffer(len(data)):
            self._filesync_flush(adb_info, filesync_info)

        buf = struct.pack(b'<2I', constants.FILESYNC_ID_TO_WIRE[command_id], size) + data
        filesync_info.send_buffer[filesync_info.send_idx:filesync_info.send_idx + len(buf)] = buf
        filesync_info.send_idx += len(buf)


class AdbDeviceTcp(AdbDevice):
    """A class with methods for connecting to a device via TCP and executing ADB commands.

    Parameters
    ----------
    host : str
        The address of the device; may be an IP address or a host name
    port : int
        The device port to which we are connecting (default is 5555)
    default_transport_timeout_s : float, None
        Default timeout in seconds for TCP packets, or ``None``
    banner : str, bytes, None
        The hostname of the machine where the Python interpreter is currently running; if
        it is not provided, it will be determined via ``socket.gethostname()``

    Attributes
    ----------
    _available : bool
        Whether an ADB connection to the device has been established
    _banner : bytearray, bytes
        The hostname of the machine where the Python interpreter is currently running
    _default_transport_timeout_s : float, None
        Default timeout in seconds for TCP packets, or ``None``
    _local_id : int
        The local ID that is used for ADB transactions; the value is incremented each time and is always in the range ``[1, 2^32)``
    _maxdata : int
        Maximum amount of data in an ADB packet
    _transport : TcpTransport
        The transport that is used to connect to the device

    """

    def __init__(self, host, port=5555, default_transport_timeout_s=None, banner=None):
        transport = TcpTransport(host, port)
        super(AdbDeviceTcp, self).__init__(transport, default_transport_timeout_s, banner)


class AdbDeviceUsb(AdbDevice):
    """A class with methods for connecting to a device via USB and executing ADB commands.

    Parameters
    ----------
    serial : str, None
        The USB device serial ID
    port_path : TODO, None
        TODO
    default_transport_timeout_s : float, None
        Default timeout in seconds for USB packets, or ``None``
    banner : str, bytes, None
        The hostname of the machine where the Python interpreter is currently running; if
        it is not provided, it will be determined via ``socket.gethostname()``

    Raises
    ------
    adb_shell.exceptions.InvalidTransportError
        Raised if package was not installed with the "usb" extras option (``pip install adb-shell[usb]``)

    Attributes
    ----------
    _available : bool
        Whether an ADB connection to the device has been established
    _banner : bytearray, bytes
        The hostname of the machine where the Python interpreter is currently running
    _default_transport_timeout_s : float, None
        Default timeout in seconds for USB packets, or ``None``
    _local_id : int
        The local ID that is used for ADB transactions; the value is incremented each time and is always in the range ``[1, 2^32)``
    _maxdata : int
        Maximum amount of data in an ADB packet
    _transport : UsbTransport
        The transport that is used to connect to the device

    """

    def __init__(self, serial=None, port_path=None, default_transport_timeout_s=None, banner=None):
        if UsbTransport is None:
            raise exceptions.InvalidTransportError("To enable USB support you must install this package via `pip install adb-shell[usb]`")

        transport = UsbTransport.find_adb(serial, port_path, default_transport_timeout_s)
        super(AdbDeviceUsb, self).__init__(transport, default_transport_timeout_s, banner)


================================================
FILE: adb_shell/adb_device_async.py
================================================
# Copyright (c) 2021 Jeff Irion and contributors
#
# This file is part of the adb-shell package.  It incorporates work
# covered by the following license notice:
#
#
#   Copyright 2014 Google Inc. All rights reserved.
#
#   Licensed under the Apache License, Version 2.0 (the "License");
#   you may not use this file except in compliance with the License.
#   You may obtain a copy of the License at
#
#       http://www.apache.org/licenses/LICENSE-2.0
#
#   Unless required by applicable law or agreed to in writing, software
#   distributed under the License is distributed on an "AS IS" BASIS,
#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#   See the License for the specific language governing permissions and
#   limitations under the License.

"""Implement the :class:`AdbDeviceAsync` class, which can connect to a device and run ADB shell commands.

* :class:`_AdbIOManagerAsync`

    * :meth:`_AdbIOManagerAsync._read_bytes_from_device`
    * :meth:`_AdbIOManagerAsync._read_expected_packet_from_device`
    * :meth:`_AdbIOManagerAsync._read_packet_from_device`
    * :meth:`_AdbIOManagerAsync._send`
    * :meth:`_AdbIOManagerAsync.close`
    * :meth:`_AdbIOManagerAsync.connect`
    * :meth:`_AdbIOManagerAsync.read`
    * :meth:`_AdbIOManagerAsync.send`

* :class:`_AsyncBytesIO`

    * :meth:`_AsyncBytesIO.read`
    * :meth:`_AsyncBytesIO.write`

* :func:`_open_bytesio`

* :class:`AdbDeviceAsync`

    * :meth:`AdbDeviceAsync._clse`
    * :meth:`AdbDeviceAsync._filesync_flush`
    * :meth:`AdbDeviceAsync._filesync_read`
    * :meth:`AdbDeviceAsync._filesync_read_buffered`
    * :meth:`AdbDeviceAsync._filesync_read_until`
    * :meth:`AdbDeviceAsync._filesync_send`
    * :meth:`AdbDeviceAsync._okay`
    * :meth:`AdbDeviceAsync._open`
    * :meth:`AdbDeviceAsync._pull`
    * :meth:`AdbDeviceAsync._push`
    * :meth:`AdbDeviceAsync._read_until`
    * :meth:`AdbDeviceAsync._read_until_close`
    * :meth:`AdbDeviceAsync._service`
    * :meth:`AdbDeviceAsync._streaming_command`
    * :meth:`AdbDeviceAsync._streaming_service`
    * :attr:`AdbDeviceAsync.available`
    * :meth:`AdbDeviceAsync.close`
    * :meth:`AdbDeviceAsync.connect`
    * :meth:`AdbDeviceAsync.list`
    * :attr:`AdbDeviceAsync.max_chunk_size`
    * :meth:`AdbDeviceAsync.pull`
    * :meth:`AdbDeviceAsync.push`
    * :meth:`AdbDeviceAsync.root`
    * :meth:`AdbDeviceAsync.shell`
    * :meth:`AdbDeviceAsync.stat`
    * :meth:`AdbDeviceAsync.streaming_shell`

* :class:`AdbDeviceTcpAsync`

"""


from contextlib import asynccontextmanager
from io import BytesIO
from asyncio import Lock, get_running_loop
import logging
import os
import struct
import time

import aiofiles

from . import constants
from . import exceptions
from .adb_message import AdbMessage, checksum, int_to_cmd, unpack
from .transport.base_transport_async import BaseTransportAsync
from .transport.tcp_transport_async import TcpTransportAsync
from .hidden_helpers import DeviceFile, _AdbPacketStore, _AdbTransactionInfo, _FileSyncTransactionInfo, get_banner, get_files_to_push


_LOGGER = logging.getLogger(__name__)


class _AsyncBytesIO:
    """An async wrapper for `BytesIO`.

    Parameters
    ----------
    bytesio : BytesIO
        The BytesIO object that is wrapped

    """

    def __init__(self, bytesio):
        self._bytesio = bytesio

    async def read(self, size=-1):
        """Read data.

        Parameters
        ----------
        size : int
            The size of the data to be read

        Returns
        -------
        bytes
            The data that was read

        """
        return self._bytesio.read(size)

    async def write(self, data):
        """Write data.

        Parameters
        ----------
        data : bytes
            The data to be written

        """
        self._bytesio.write(data)


@asynccontextmanager
async def _open_bytesio(stream, *args, **kwargs):  # pylint: disable=unused-argument
    """An async context manager for a BytesIO object that does nothing.

    Parameters
    ----------
    stream : BytesIO
        The BytesIO stream
    args : list
        Unused positional arguments
    kwargs : dict
        Unused keyword arguments

    Yields
    ------
    _AsyncBytesIO
        The wrapped `stream` input parameter

    """
    yield _AsyncBytesIO(stream)


class _AdbIOManagerAsync(object):
    """A class for handling all ADB I/O.

    Notes
    -----
    When the ``self._store_lock`` and ``self._transport_lock`` locks are held at the same time, it must always be the
    case that the ``self._transport_lock`` is acquired first.  This ensures that there is no potential for deadlock.

    Parameters
    ----------
    transport : BaseTransportAsync
        A transport for communicating with the device; must be an instance of a subclass of :class:`~adb_shell.transport.base_transport_async.BaseTransportAsync`

    Attributes
    ----------
    _packet_store : _AdbPacketStore
        A store for holding packets that correspond to different ADB streams
    _store_lock : Lock
        A lock for protecting ``self._packet_store`` (this lock is never held for long)
    _transport : BaseTransportAsync
        A transport for communicating with the device; must be an instance of a subclass of :class:`~adb_shell.transport.base_transport_async.BaseTransportAsync`
    _transport_lock : Lock
        A lock for protecting ``self._transport``

    """

    def __init__(self, transport):
        self._packet_store = _AdbPacketStore()
        self._transport = transport

        self._store_lock = Lock()
        self._transport_lock = Lock()

    async def close(self):
        """Close the connection via the provided transport's ``close()`` method and clear the packet store.

        """
        async with self._transport_lock:
            await self._transport.close()

            async with self._store_lock:
                self._packet_store.clear_all()

    async def connect(self, banner, rsa_keys, auth_timeout_s, auth_callback, adb_info):
        """Establish an ADB connection to the device.

        1. Use the transport to establish a connection
        2. Send a ``b'CNXN'`` message
        3. Read the response from the device
        4. If ``cmd`` is not ``b'AUTH'``, then authentication is not necesary and so we are done
        5. If no ``rsa_keys`` are provided, raise an exception
        6. Loop through our keys, signing the last ``banner2`` that we received

            1. If the last ``arg0`` was not :const:`adb_shell.constants.AUTH_TOKEN`, raise an exception
            2. Sign the last ``banner2`` and send it in an ``b'AUTH'`` message
            3. Read the response from the device
            4. If ``cmd`` is ``b'CNXN'``, we are done

        7. None of the keys worked, so send ``rsa_keys[0]``'s public key; if the response does not time out, we must have connected successfully


        Parameters
        ----------
        banner : bytearray, bytes
            The hostname of the machine where the Python interpreter is currently running (:attr:`adb_shell.adb_device_async.AdbDeviceAsync._banner`)
        rsa_keys : list, None
            A list of signers of type :class:`~adb_shell.auth.sign_cryptography.CryptographySigner`,
            :class:`~adb_shell.auth.sign_pycryptodome.PycryptodomeAuthSigner`, or :class:`~adb_shell.auth.sign_pythonrsa.PythonRSASigner`
        auth_timeout_s : float, None
            The time in seconds to wait for a ``b'CNXN'`` authentication response
        auth_callback : function, None
            Function callback invoked when the connection needs to be accepted on the device
        adb_info : _AdbTransactionInfo
            Info and settings for this connection attempt

        Returns
        -------
        bool
            Whether the connection was established
        maxdata : int
            Maximum amount of data in an ADB packet

        Raises
        ------
        adb_shell.exceptions.DeviceAuthError
            Device authentication required, no keys available
        adb_shell.exceptions.InvalidResponseError
            Invalid auth response from the device

        """
        async with self._transport_lock:
            # 0. Close the connection and clear the store
            await self._transport.close()

            async with self._store_lock:
                # We can release this lock because packets are only added to the store when the transport lock is held
                self._packet_store.clear_all()

            # 1. Use the transport to establish a connection
            await self._transport.connect(adb_info.transport_timeout_s)

            # 2. Send a ``b'CNXN'`` message
            msg = AdbMessage(constants.CNXN, constants.VERSION, constants.MAX_ADB_DATA, b'host::%s\0' % banner)
            await self._send(msg, adb_info)

            # 3. Read the response from the device
            cmd, arg0, maxdata, banner2 = await self._read_expected_packet_from_device([constants.AUTH, constants.CNXN], adb_info)

            # 4. If ``cmd`` is not ``b'AUTH'``, then authentication is not necesary and so we are done
            if cmd != constants.AUTH:
                return True, maxdata

            # 5. If no ``rsa_keys`` are provided, raise an exception
            if not rsa_keys:
                await self._transport.close()
                raise exceptions.DeviceAuthError('Device authentication required, no keys available.')

            # 6. Loop through our keys, signing the last ``banner2`` that we received
            for rsa_key in rsa_keys:
                # 6.1. If the last ``arg0`` was not :const:`adb_shell.constants.AUTH_TOKEN`, raise an exception
                if arg0 != constants.AUTH_TOKEN:
                    await self._transport.close()
                    raise exceptions.InvalidResponseError('Unknown AUTH response: %s %s %s' % (arg0, maxdata, banner2))

                # 6.2. Sign the last ``banner2`` and send it in an ``b'AUTH'`` message
                signed_token = rsa_key.Sign(banner2)
                msg = AdbMessage(constants.AUTH, constants.AUTH_SIGNATURE, 0, signed_token)
                await self._send(msg, adb_info)

                # 6.3. Read the response from the device
                cmd, arg0, maxdata, banner2 = await self._read_expected_packet_from_device([constants.CNXN, constants.AUTH], adb_info)

                # 6.4. If ``cmd`` is ``b'CNXN'``, we are done
                if cmd == constants.CNXN:
                    return True, maxdata

            # 7. None of the keys worked, so send ``rsa_keys[0]``'s public key; if the response does not time out, we must have connected successfully
            pubkey = rsa_keys[0].GetPublicKey()
            if not isinstance(pubkey, (bytes, bytearray)):
                pubkey = bytearray(pubkey, 'utf-8')

            if auth_callback is not None:
                auth_callback(self)

            msg = AdbMessage(constants.AUTH, constants.AUTH_RSAPUBLICKEY, 0, pubkey + b'\0')
            await self._send(msg, adb_info)

            adb_info.transport_timeout_s = auth_timeout_s
            _, _, maxdata, _ = await self._read_expected_packet_from_device([constants.CNXN], adb_info)
            return True, maxdata

    async def read(self, expected_cmds, adb_info, allow_zeros=False):
        """Read packets from the device until we get an expected packet type.

        1. See if the expected packet is in the packet store
        2. While the time limit has not been exceeded:

            1. See if the expected packet is in the packet store
            2. Read a packet from the device.  If it matches what we are looking for, we are done.  If it corresponds to a different stream, add it to the store.

        3. Raise a timeout exception


        Parameters
        ----------
        expected_cmds : list[bytes]
            We will read packets until we encounter one whose "command" field is in ``expected_cmds``
        adb_info : _AdbTransactionInfo
            Info and settings for this ADB transaction
        allow_zeros : bool
            Whether to allow the received ``arg0`` and ``arg1`` values to match with 0, in addition to ``adb_info.remote_id`` and ``adb_info.local_id``, respectively

        Returns
        -------
        cmd : bytes
            The received command, which is in :const:`adb_shell.constants.WIRE_TO_ID` and must be in ``expected_cmds``
        arg0 : int
            TODO
        arg1 : int
            TODO
        data : bytes
            The data that was read

        Raises
        ------
        adb_shell.exceptions.AdbTimeoutError
            Never got one of the expected responses

        """
        # First, try reading from the store. This way, you won't be waiting for the transport if it isn't needed
        async with self._store_lock:
            # Recall that `arg0` from the device corresponds to `adb_info.remote_id` and `arg1` from the device corresponds to `adb_info.local_id`
            arg0_arg1 = self._packet_store.find(adb_info.remote_id, adb_info.local_id) if not allow_zeros else self._packet_store.find_allow_zeros(adb_info.remote_id, adb_info.local_id)
            while arg0_arg1:
                cmd, arg0, arg1, data = self._packet_store.get(arg0_arg1[0], arg0_arg1[1])
                if cmd in expected_cmds:
                    return cmd, arg0, arg1, data

                arg0_arg1 = self._packet_store.find(adb_info.remote_id, adb_info.local_id) if not allow_zeros else self._packet_store.find_allow_zeros(adb_info.remote_id, adb_info.local_id)

        # Start the timer
        start = time.time()

        while True:
            async with self._transport_lock:
                # Try reading from the store (again) in case a packet got added while waiting to acquire the transport lock
                async with self._store_lock:
                    # Recall that `arg0` from the device corresponds to `adb_info.remote_id` and `arg1` from the device corresponds to `adb_info.local_id`
                    arg0_arg1 = self._packet_store.find(adb_info.remote_id, adb_info.local_id) if not allow_zeros else self._packet_store.find_allow_zeros(adb_info.remote_id, adb_info.local_id)
                    while arg0_arg1:
                        cmd, arg0, arg1, data = self._packet_store.get(arg0_arg1[0], arg0_arg1[1])
                        if cmd in expected_cmds:
                            return cmd, arg0, arg1, data

                        arg0_arg1 = self._packet_store.find(adb_info.remote_id, adb_info.local_id) if not allow_zeros else self._packet_store.find_allow_zeros(adb_info.remote_id, adb_info.local_id)

                # Read from the device
                cmd, arg0, arg1, data = await self._read_packet_from_device(adb_info)

                if not adb_info.args_match(arg0, arg1, allow_zeros):
                    # The packet is not a match -> put it in the store
                    async with self._store_lock:
                        self._packet_store.put(arg0, arg1, cmd, data)

                else:
                    # The packet is a match for this `(adb_info.local_id, adb_info.remote_id)` pair
                    if cmd == constants.CLSE:
                        # Clear the entry in the store
                        async with self._store_lock:
                            self._packet_store.clear(arg0, arg1)

                    # If `cmd` is a match, then we are done
                    if cmd in expected_cmds:
                        return cmd, arg0, arg1, data

            # Check if time is up
            if time.time() - start > adb_info.read_timeout_s:
                break

        # Timeout
        raise exceptions.AdbTimeoutError("Never got one of the expected responses: {} (transport_timeout_s = {}, read_timeout_s = {})".format(expected_cmds, adb_info.transport_timeout_s, adb_info.read_timeout_s))

    async def send(self, msg, adb_info):
        """Send a message to the device.

        Parameters
        ----------
        msg : AdbMessage
            The data that will be sent
        adb_info : _AdbTransactionInfo
            Info and settings for this ADB transaction

        """
        async with self._transport_lock:
            await self._send(msg, adb_info)

    async def _read_expected_packet_from_device(self, expected_cmds, adb_info):
        """Read packets from the device until we get an expected packet type.

        Parameters
        ----------
        expected_cmds : list[bytes]
            We will read packets until we encounter one whose "command" field is in ``expected_cmds``
        adb_info : _AdbTransactionInfo
            Info and settings for this ADB transaction

        Returns
        -------
        cmd : bytes
            The received command, which is in :const:`adb_shell.constants.WIRE_TO_ID` and must be in ``expected_cmds``
        arg0 : int
            TODO
        arg1 : int
            TODO
        data : bytes
            The data that was read

        Raises
        ------
        adb_shell.exceptions.AdbTimeoutError
            Never got one of the expected responses

        """
        start = time.time()

        while True:
            cmd, arg0, arg1, data = await self._read_packet_from_device(adb_info)

            if cmd in expected_cmds:
                return cmd, arg0, arg1, data

            if time.time() - start > adb_info.read_timeout_s:
                # Timeout
                raise exceptions.AdbTimeoutError("Never got one of the expected responses: {} (transport_timeout_s = {}, read_timeout_s = {})".format(expected_cmds, adb_info.transport_timeout_s, adb_info.read_timeout_s))

    async def _read_bytes_from_device(self, length, adb_info):
        """Read ``length`` bytes from the device.

        Parameters
        ----------
        length : int
            We will read packets until we get this length of data
        adb_info : _AdbTransactionInfo
            Info and settings for this ADB transaction

        Returns
        -------
        bytes
            The data that was read

        Raises
        ------
        adb_shell.exceptions.AdbTimeoutError
            Did not read ``length`` bytes in time

        """
        start = time.time()
        data = bytearray()

        while length > 0:
            temp = await self._transport.bulk_read(length, adb_info.transport_timeout_s)
            if temp:
                # Only log if `temp` is not empty
                _LOGGER.debug("bulk_read(%d): %.1000r", length, temp)

            data += temp
            length -= len(temp)

            if length == 0:
                break

            if time.time() - start > adb_info.read_timeout_s:
                # Timeout
                raise exceptions.AdbTimeoutError("Timeout: read {} of {} bytes (transport_timeout_s = {}, read_timeout_s = {})".format(len(data), len(data) + length, adb_info.transport_timeout_s, adb_info.read_timeout_s))

        return bytes(data)

    async def _read_packet_from_device(self, adb_info):
        """Read a complete ADB packet (header + data) from the device.

        Parameters
        ----------
        adb_info : _AdbTransactionInfo
            Info and settings for this ADB transaction

        Returns
        -------
        cmd : bytes
            The received command, which is in :const:`adb_shell.constants.WIRE_TO_ID` and must be in ``expected_cmds``
        arg0 : int
            TODO
        arg1 : int
            TODO
        bytes
            The data that was read

        Raises
        ------
        adb_shell.exceptions.InvalidCommandError
            Unknown command
        adb_shell.exceptions.InvalidChecksumError
            Received checksum does not match the expected checksum

       """
        msg = await self._read_bytes_from_device(constants.MESSAGE_SIZE, adb_info)
        cmd, arg0, arg1, data_length, data_checksum = unpack(msg)
        command = constants.WIRE_TO_ID.get(cmd)

        if not command:
            raise exceptions.InvalidCommandError("Unknown command: %d = '%s' (arg0 = %d, arg1 = %d, msg = '%s')" % (cmd, int_to_cmd(cmd), arg0, arg1, msg))

        if data_length == 0:
            return command, arg0, arg1, b""

        data = await self._read_bytes_from_device(data_length, adb_info)
        actual_checksum = checksum(data)
        if actual_checksum != data_checksum:
            raise exceptions.InvalidChecksumError("Received checksum {} != {}".format(actual_checksum, data_checksum))

        return command, arg0, arg1, data

    async def _send(self, msg, adb_info):
        """Send a message to the device.

        1. Send the message header (:meth:`adb_shell.adb_message.AdbMessage.pack <AdbMessage.pack>`)
        2. Send the message data


        Parameters
        ----------
        msg : AdbMessage
            The data that will be sent
        adb_info : _AdbTransactionInfo
            Info and settings for this ADB transaction

        """
        packed = msg.pack()
        _LOGGER.debug("bulk_write(%d): %r", len(packed), packed)
        await self._transport.bulk_write(packed, adb_info.transport_timeout_s)

        if msg.data:
            _LOGGER.debug("bulk_write(%d): %r", len(msg.data), msg.data)
            await self._transport.bulk_write(msg.data, adb_info.transport_timeout_s)


class AdbDeviceAsync(object):
    """A class with methods for connecting to a device and executing ADB commands.

    Parameters
    ----------
    transport : BaseTransportAsync
        A user-provided transport for communicating with the device; must be an instance of a subclass of :class:`~adb_shell.transport.base_transport_async.BaseTransportAsync`
    default_transport_timeout_s : float, None
        Default timeout in seconds for transport packets, or ``None``
    banner : str, bytes, None
        The hostname of the machine where the Python interpreter is currently running; if
        it is not provided, it will be determined via ``socket.gethostname()``

    Raises
    ------
    adb_shell.exceptions.InvalidTransportError
        The passed ``transport`` is not an instance of a subclass of :class:`~adb_shell.transport.base_transport_async.BaseTransportAsync`

    Attributes
    ----------
    _available : bool
        Whether an ADB connection to the device has been established
    _banner : bytearray, bytes
        The hostname of the machine where the Python interpreter is currently running
    _default_transport_timeout_s : float, None
        Default timeout in seconds for transport packets, or ``None``
    _io_manager : _AdbIOManagerAsync
        Used for handling all ADB I/O
    _local_id : int
        The local ID that is used for ADB transactions; the value is incremented each time and is always in the range ``[1, 2^32)``
    _local_id_lock : Lock
        A lock for protecting ``_local_id``; this is never held for long
    _maxdata: int
        Maximum amount of data in an ADB packet
    _transport : BaseTransportAsync
        The transport that is used to connect to the device; must be a subclass of :class:`~adb_shell.transport.base_transport_async.BaseTransportAsync`

    """

    def __init__(self, transport, default_transport_timeout_s=None, banner=None):
        if banner and not isinstance(banner, (bytes, bytearray)):
            self._banner = bytearray(banner, 'utf-8')
        else:
            self._banner = banner

        if not isinstance(transport, BaseTransportAsync):
            raise exceptions.InvalidTransportError("`transport` must be an instance of a subclass of `BaseTransportAsync`")

        self._io_manager = _AdbIOManagerAsync(transport)

        self._available = False
        self._default_transport_timeout_s = default_transport_timeout_s
        self._local_id = 0
        self._local_id_lock = Lock()
        self._maxdata = constants.MAX_PUSH_DATA

    # ======================================================================= #
    #                                                                         #
    #                       Properties & simple methods                       #
    #                                                                         #
    # ======================================================================= #
    @property
    def available(self):
        """Whether or not an ADB connection to the device has been established.

        Returns
        -------
        bool
            ``self._available``

        """
        return self._available

    @property
    def max_chunk_size(self):
        """Maximum chunk size for filesync operations

        Returns
        -------
        int
            Minimum value based on :const:`adb_shell.constants.MAX_CHUNK_SIZE` and ``_max_data / 2``, fallback to legacy :const:`adb_shell.constants.MAX_PUSH_DATA`

        """
        return min(constants.MAX_CHUNK_SIZE, self._maxdata // 2) or constants.MAX_PUSH_DATA

    def _get_transport_timeout_s(self, transport_timeout_s):
        """Use the provided ``transport_timeout_s`` if it is not ``None``; otherwise, use ``self._default_transport_timeout_s``

        Parameters
        ----------
        transport_timeout_s : float, None
            The potential transport timeout

        Returns
        -------
        float
            ``transport_timeout_s`` if it is not ``None``; otherwise, ``self._default_transport_timeout_s``

        """
        return transport_timeout_s if transport_timeout_s is not None else self._default_transport_timeout_s

    # ======================================================================= #
    #                                                                         #
    #                             Close & Connect                             #
    #                                                                         #
    # ======================================================================= #
    async def close(self):
        """Close the connection via the provided transport's ``close()`` method.

        """
        self._available = False
        await self._io_manager.close()

    async def connect(self, rsa_keys=None, transport_timeout_s=None, auth_timeout_s=constants.DEFAULT_AUTH_TIMEOUT_S, read_timeout_s=constants.DEFAULT_READ_TIMEOUT_S, auth_callback=None):
        """Establish an ADB connection to the device.

        See :meth:`_AdbIOManagerAsync.connect`.

        Parameters
        ----------
        rsa_keys : list, None
            A list of signers of type :class:`~adb_shell.auth.sign_cryptography.CryptographySigner`,
            :class:`~adb_shell.auth.sign_pycryptodome.PycryptodomeAuthSigner`, or :class:`~adb_shell.auth.sign_pythonrsa.PythonRSASigner`
        transport_timeout_s : float, None
            Timeout in seconds for sending and receiving data, or ``None``; see :meth:`BaseTransportAsync.bulk_read() <adb_shell.transport.base_transport_async.BaseTransportAsync.bulk_read>`
            and :meth:`BaseTransportAsync.bulk_write() <adb_shell.transport.base_transport_async.BaseTransportAsync.bulk_write>`
        auth_timeout_s : float, None
            The time in seconds to wait for a ``b'CNXN'`` authentication response
        read_timeout_s : float
            The total time in seconds to wait for expected commands in :meth:`_AdbIOManagerAsync._read_expected_packet_from_device`
        auth_callback : function, None
            Function callback invoked when the connection needs to be accepted on the device

        Returns
        -------
        bool
            Whether the connection was established (:attr:`AdbDeviceAsync.available`)

        """
        # Get `self._banner` if it was not provided in the constructor
        if not self._banner:
            self._banner = await get_running_loop().run_in_executor(None, get_banner)

        # Instantiate the `_AdbTransactionInfo`
        adb_info = _AdbTransactionInfo(None, None, self._get_transport_timeout_s(transport_timeout_s), read_timeout_s, None)

        # Mark the device as unavailable
        self._available = False

        # Use the IO manager to connect
        self._available, self._maxdata = await self._io_manager.connect(self._banner, rsa_keys, auth_timeout_s, auth_callback, adb_info)

        return self._available

    # ======================================================================= #
    #                                                                         #
    #                                 Services                                #
    #                                                                         #
    # ======================================================================= #
    async def _service(self, service, command, transport_timeout_s=None, read_timeout_s=constants.DEFAULT_READ_TIMEOUT_S, timeout_s=None, decode=True):
        """Send an ADB command to the device.

        Parameters
        ----------
        service : bytes
            The ADB service to talk to (e.g., ``b'shell'``)
        command : bytes
            The command that will be sent
        transport_timeout_s : float, None
            Timeout in seconds for sending and receiving data, or ``None``; see :meth:`BaseTransportAsync.bulk_read() <adb_shell.transport.base_transport_async.BaseTransportAsync.bulk_read>`
            and :meth:`BaseTransportAsync.bulk_write() <adb_shell.transport.base_transport_async.BaseTransportAsync.bulk_write>`
        read_timeout_s : float
            The total time in seconds to wait for a ``b'CLSE'`` or ``b'OKAY'`` command in :meth:`_AdbIOManagerAsync.read`
        timeout_s : float, None
            The total time in seconds to wait for the ADB command to finish
        decode : bool
            Whether to decode the output to utf8 before returning

        Returns
        -------
        bytes, str
            The output of the ADB command as a string if ``decode`` is True, otherwise as bytes.

        """
        if decode:
            return b''.join([x async for x in self._streaming_command(service, command, transport_timeout_s, read_timeout_s, timeout_s)]).decode('utf8', 'backslashreplace')
        return b''.join([x async for x in self._streaming_command(service, command, transport_timeout_s, read_timeout_s, timeout_s)])

    async def _streaming_service(self, service, command, transport_timeout_s=None, read_timeout_s=constants.DEFAULT_READ_TIMEOUT_S, decode=True):
        """Send an ADB command to the device, yielding each line of output.

        Parameters
        ----------
        service : bytes
            The ADB service to talk to (e.g., ``b'shell'``)
        command : bytes
            The command that will be sent
        transport_timeout_s : float, None
            Timeout in seconds for sending and receiving data, or ``None``; see :meth:`BaseTransportAsync.bulk_read() <adb_shell.transport.base_transport_async.BaseTransportAsync.bulk_read>`
            and :meth:`BaseTransportAsync.bulk_write() <adb_shell.transport.base_transport_async.BaseTransportAsync.bulk_write>`
        read_timeout_s : float
            The total time in seconds to wait for a ``b'CLSE'`` or ``b'OKAY'`` command in :meth:`_AdbIOManagerAsync.read`
        decode : bool
            Whether to decode the output to utf8 before returning

        Yields
        -------
        bytes, str
            The line-by-line output of the ADB command as a string if ``decode`` is True, otherwise as bytes.

        """
        stream = self._streaming_command(service, command, transport_timeout_s, read_timeout_s, None)
        if decode:
            async for line in (stream_line.decode('utf8', 'backslashreplace') async for stream_line in stream):
                yield line
        else:
            async for line in stream:
                yield line

    async def exec_out(self, command, transport_timeout_s=None, read_timeout_s=constants.DEFAULT_READ_TIMEOUT_S, timeout_s=None, decode=True):
        """Send an ADB ``exec-out`` command to the device.

        https://www.linux-magazine.com/Issues/2017/195/Ask-Klaus

        Parameters
        ----------
        command : str
            The exec-out command that will be sent
        transport_timeout_s : float, None
            Timeout in seconds for sending and receiving data, or ``None``; see :meth:`BaseTransportAsync.bulk_read() <adb_shell.transport.base_transport_async.BaseTransportAsync.bulk_read>`
            and :meth:`BaseTransportAsync.bulk_write() <adb_shell.transport.base_transport_async.BaseTransportAsync.bulk_write>`
        read_timeout_s : float
            The total time in seconds to wait for a ``b'CLSE'`` or ``b'OKAY'`` command in :meth:`_AdbIOManagerAsync.read`
        timeout_s : float, None
            The total time in seconds to wait for the ADB command to finish
        decode : bool
            Whether to decode the output to utf8 before returning

        Returns
        -------
        bytes, str
            The output of the ADB exec-out command as a string if ``decode`` is True, otherwise as bytes.

        """
        if not self.available:
            raise exceptions.AdbConnectionError("ADB command not sent because a connection to the device has not been established.  (Did you call `AdbDeviceAsync.connect()`?)")

        return await self._service(b'exec', command.encode('utf8'), transport_timeout_s, read_timeout_s, timeout_s, decode)

    async def reboot(self, fastboot=False, transport_timeout_s=None, read_timeout_s=constants.DEFAULT_READ_TIMEOUT_S, timeout_s=None):
        """Reboot the device.

        Parameters
        ----------
        fastboot : bool
            Whether to reboot the device into fastboot
        transport_timeout_s : float, None
            Timeout in seconds for sending and receiving data, or ``None``; see :meth:`BaseTransportAsync.bulk_read() <adb_shell.transport.base_transport_async.BaseTransportAsync.bulk_read>`
            and :meth:`BaseTransportAsync.bulk_write() <adb_shell.transport.base_transport_async.BaseTransportAsync.bulk_write>`
        read_timeout_s : float
            The total time in seconds to wait for a ``b'CLSE'`` or ``b'OKAY'`` command in :meth:`_AdbIOManager.read`
        timeout_s : float, None
            The total time in seconds to wait for the ADB command to finish

        """
        if not self.available:
            raise exceptions.AdbConnectionError("ADB command not sent because a connection to the device has not been established.  (Did you call `AdbDeviceAsync.connect()`?)")

        await self._open(b'reboot:bootloader' if fastboot else b'reboot:', transport_timeout_s, read_timeout_s, timeout_s)

    async def root(self, transport_timeout_s=None, read_timeout_s=constants.DEFAULT_READ_TIMEOUT_S, timeout_s=None):
        """Gain root access.

        The device must be rooted in order for this to work.

        Parameters
        ----------
        transport_timeout_s : float, None
            Timeout in seconds for sending and receiving data, or ``None``; see :meth:`BaseTransportAsync.bulk_read() <adb_shell.transport.base_transport_async.BaseTransportAsync.bulk_read>`
            and :meth:`BaseTransportAsync.bulk_write() <adb_shell.transport.base_transport_async.BaseTransportAsync.bulk_write>`
        read_timeout_s : float
            The total time in seconds to wait for a ``b'CLSE'`` or ``b'OKAY'`` command in :meth:`_AdbIOManagerAsync.read`
        timeout_s : float, None
            The total time in seconds to wait for the ADB command to finish

        """
        if not self.available:
            raise exceptions.AdbConnectionError("ADB command not sent because a connection to the device has not been established.  (Did you call `AdbDeviceAsync.connect()`?)")

        await self._service(b'root', b'', transport_timeout_s, read_timeout_s, timeout_s, False)

    async def shell(self, command, transport_timeout_s=None, read_timeout_s=constants.DEFAULT_READ_TIMEOUT_S, timeout_s=None, decode=True):
        """Send an ADB shell command to the device.

        Parameters
        ----------
        command : str
            The shell command that will be sent
        transport_timeout_s : float, None
            Timeout in seconds for sending and receiving data, or ``None``; see :meth:`BaseTransportAsync.bulk_read() <adb_shell.transport.base_transport_async.BaseTransportAsync.bulk_read>`
            and :meth:`BaseTransportAsync.bulk_write() <adb_shell.transport.base_transport_async.BaseTransportAsync.bulk_write>`
        read_timeout_s : float
            The total time in seconds to wait for a ``b'CLSE'`` or ``b'OKAY'`` command in :meth:`_AdbIOManagerAsync.read`
        timeout_s : float, None
            The total time in seconds to wait for the ADB command to finish
        decode : bool
            Whether to decode the output to utf8 before returning

        Returns
        -------
        bytes, str
            The output of the ADB shell command as a string if ``decode`` is True, otherwise as bytes.

        """
        if not self.available:
            raise exceptions.AdbConnectionError("ADB command not sent because a connection to the device has not been established.  (Did you call `AdbDeviceAsync.connect()`?)")

        return await self._service(b'shell', command.encode('utf8'), transport_timeout_s, read_timeout_s, timeout_s, decode)

    async def streaming_shell(self, command, transport_timeout_s=None, read_timeout_s=constants.DEFAULT_READ_TIMEOUT_S, decode=True):
        """Send an ADB shell command to the device, yielding each line of output.

        Parameters
        ----------
        command : str
            The shell command that will be sent
        transport_timeout_s : float, None
            Timeout in seconds for sending and receiving data, or ``None``; see :meth:`BaseTransportAsync.bulk_read() <adb_shell.transport.base_transport_async.BaseTransportAsync.bulk_read>`
            and :meth:`BaseTransportAsync.bulk_write() <adb_shell.transport.base_transport_async.BaseTransportAsync.bulk_write>`
        read_timeout_s : float
            The total time in seconds to wait for a ``b'CLSE'`` or ``b'OKAY'`` command in :meth:`_AdbIOManagerAsync.read`
        decode : bool
            Whether to decode the output to utf8 before returning

        Yields
        -------
        bytes, str
            The line-by-line output of the ADB shell command as a string if ``decode`` is True, otherwise as bytes.

        """
        if not self.available:
            raise exceptions.AdbConnectionError("ADB command not sent because a connection to the device has not been established.  (Did you call `AdbDeviceAsync.connect()`?)")

        async for line in self._streaming_service(b'shell', command.encode('utf8'), transport_timeout_s, read_timeout_s, decode):
            yield line

    # ======================================================================= #
    #                                                                         #
    #                                 FileSync                                #
    #                                                                         #
    # ======================================================================= #
    async def list(self, device_path, transport_timeout_s=None, read_timeout_s=constants.DEFAULT_READ_TIMEOUT_S):
        """Return a directory listing of the given path.

        Parameters
        ----------
        device_path : str
            Directory to list.
        transport_timeout_s : float, None
            Expected timeout for any part of the pull.
        read_timeout_s : float
            The total time in seconds to wait for a ``b'CLSE'`` or ``b'OKAY'`` command in :meth:`_AdbIOManagerAsync.read`

        Returns
        -------
        files : list[DeviceFile]
            Filename, mode, size, and mtime info for the files in the directory

        """
        if not device_path:
            raise exceptions.DevicePathInvalidError("Cannot list an empty device path")
        if not self.available:
            raise exceptions.AdbConnectionError("ADB command not sent because a connection to the device has not been established.  (Did you call `AdbDeviceAsync.connect()`?)")

        adb_info = await self._open(b'sync:', transport_timeout_s, read_timeout_s, None)
        filesync_info = _FileSyncTransactionInfo(constants.FILESYNC_LIST_FORMAT, maxdata=self._maxdata)

        await self._filesync_send(constants.LIST, adb_info, filesync_info, data=device_path)
        files = []

        async for cmd_id, header, filename in self._filesync_read_until([constants.DENT], [constants.DONE], adb_info, filesync_info):
            if cmd_id == constants.DONE:
                break

            mode, size, mtime = header
            files.append(DeviceFile(filename, mode, size, mtime))

        await self._clse(adb_info)

        return files

    async def pull(self, device_path, local_path, progress_callback=None, transport_timeout_s=None, read_timeout_s=constants.DEFAULT_READ_TIMEOUT_S):
        """Pull a file from the device.

        Parameters
        ----------
        device_path : str
            The file on the device that will be pulled
        local_path : str, BytesIO
            The path or BytesIO stream where the file will be downloaded
        progress_callback : function, None
            Callback method that accepts ``device_path``, ``bytes_written``, and ``total_bytes``
        transport_timeout_s : float, None
            Expected timeout for any part of the pull.
        read_timeout_s : float
            The total time in seconds to wait for a ``b'CLSE'`` or ``b'OKAY'`` command in :meth:`_AdbIOManagerAsync.read`

        """
        if not device_path:
            raise exceptions.DevicePathInvalidError("Cannot pull from an empty device path")
        if not self.available:
            raise exceptions.AdbConnectionError("ADB command not sent because a connection to the device has not been established.  (Did you call `AdbDeviceAsync.connect()`?)")

        opener = _open_bytesio if isinstance(local_path, BytesIO) else aiofiles.open
        async with opener(local_path, 'wb') as stream:
            adb_info = await self._open(b'sync:', transport_timeout_s, read_timeout_s, None)
            filesync_info = _FileSyncTransactionInfo(constants.FILESYNC_PULL_FORMAT, maxdata=self._maxdata)

            try:
                await self._pull(device_path, stream, progress_callback, adb_info, filesync_info)
            finally:
                await self._clse(adb_info)

    async def _pull(self, device_path, stream, progress_callback, adb_info, filesync_info):
        """Pull a file from the device into the file-like ``local_path``.

        Parameters
        ----------
        device_path : str
            The file on the device that will be pulled
        stream : AsyncBufferedIOBase, _AsyncBytesIO
            File-like object for writing to
        progress_callback : function, None
            Callback method that accepts ``device_path``, ``bytes_written``, and ``total_bytes``
        adb_info : _AdbTransactionInfo
            Info and settings for this ADB transaction
        filesync_info : _FileSyncTransactionInfo
            Data and storage for this FileSync transaction

        """
        if progress_callback:
            total_bytes = (await self.stat(device_path))[1]

        await self._filesync_send(constants.RECV, adb_info, filesync_info, data=device_path)
        async for cmd_id, _, data in self._filesync_read_until([constants.DATA], [constants.DONE], adb_info, filesync_info):
            if cmd_id == constants.DONE:
                break

            await stream.write(data)
            if progress_callback:
                try:
                    await progress_callback(device_path, len(data), total_bytes)
                except:  # noqa pylint: disable=bare-except
                    pass

    async def push(self, local_path, device_path, st_mode=constants.DEFAULT_PUSH_MODE, mtime=0, progress_callback=None, transport_timeout_s=None, read_timeout_s=constants.DEFAULT_READ_TIMEOUT_S):
        """Push a file or directory to the device.

        Parameters
        ----------
        local_path : str, BytesIO
            A filename, directory, or BytesIO stream to push to the device
        device_path : str
            Destination on the device to write to
        st_mode : int
            Stat mode for ``local_path``
        mtime : int
            Modification time to set on the file
        progress_callback : function, None
            Callback method that accepts ``device_path``, ``bytes_written``, and ``total_bytes``
        transport_timeout_s : float, None
            Expected timeout for any part of the push
        read_timeout_s : float
            The total time in seconds to wait for a ``b'CLSE'`` or ``b'OKAY'`` command in :meth:`_AdbIOManagerAsync.read`

        """
        if not device_path:
            raise exceptions.DevicePathInvalidError("Cannot push to an empty device path")
        if not self.available:
            raise exceptions.AdbConnectionError("ADB command not sent because a connection to the device has not been established.  (Did you call `AdbDeviceAsync.connect()`?)")

        local_path_is_dir, local_paths, device_paths = await get_running_loop().run_in_executor(None, get_files_to_push, local_path, device_path)

        if local_path_is_dir:
            await self.shell("mkdir " + device_path, transport_timeout_s, read_timeout_s)

        for _local_path, _device_path in zip(local_paths, device_paths):
            opener = _open_bytesio if isinstance(local_path, BytesIO) else aiofiles.open
            async with opener(_local_path, 'rb') as stream:
                adb_info = await self._open(b'sync:', transport_timeout_s, read_timeout_s, None)
                filesync_info = _FileSyncTransactionInfo(constants.FILESYNC_PUSH_FORMAT, maxdata=self._maxdata)

                await self._push(stream, _device_path, st_mode, mtime, progress_callback, adb_info, filesync_info)

            await self._clse(adb_info)

    async def _push(self, stream, device_path, st_mode, mtime, progress_callback, adb_info, filesync_info):
        """Push a file-like object to the device.

        Parameters
        ----------
        stream : AsyncBufferedReader, _AsyncBytesIO
            File-like object for reading from
        device_path : str
            Destination on the device to write to
        st_mode : int
            Stat mode for the file
        mtime : int
            Modification time
        progress_callback : function, None
            Callback method that accepts ``device_path``, ``bytes_written``, and ``total_bytes``
        adb_info : _AdbTransactionInfo
            Info and settings for this ADB transaction

        Raises
        ------
        PushFailedError
            Raised on push failure.

        """
        fileinfo = ('{},{}'.format(device_path, int(st_mode))).encode('utf-8')

        await self._filesync_send(constants.SEND, adb_info, filesync_info, data=fileinfo)

        if progress_callback:
            total_bytes = (await get_running_loop().run_in_executor(None, os.fstat, stream.fileno())).st_size

        while True:
            data = await stream.read(self.max_chunk_size)
            if data:
                await self._filesync_send(constants.DATA, adb_info, filesync_info, data=data)

                if progress_callback:
                    try:
                        await progress_callback(device_path, len(data), total_bytes)
                    except:  # noqa pylint: disable=bare-except
                        pass
            else:
                break

        if mtime == 0:
            mtime = int(time.time())

        # DONE doesn't send data, but it hides the last bit of data in the size field.
        await self._filesync_send(constants.DONE, adb_info, filesync_info, size=mtime)
        async for cmd_id, _, data in self._filesync_read_until([], [constants.OKAY, constants.FAIL], adb_info, filesync_info):
            if cmd_id == constants.OKAY:
                return

            raise exceptions.PushFailedError(data)

    async def stat(self, device_path, transport_timeout_s=None, read_timeout_s=constants.DEFAULT_READ_TIMEOUT_S):
        """Get a file's ``stat()`` information.

        Parameters
        ----------
        device_path : str
            The file on the device for which we will get information.
        transport_timeout_s : float, None
            Expected timeout for any part of the pull.
        read_timeout_s : float
            The total time in seconds to wait for a ``b'CLSE'`` or ``b'OKAY'`` command in :meth:`_AdbIOManagerAsync.read`

        Returns
        -------
        mode : int
            The octal permissions for the file
        size : int
            The size of the file
        mtime : int
            The last modified time for the file

        """
        if not device_path:
            raise exceptions.DevicePathInvalidError("Cannot stat an empty device path")
        if not self.available:
            raise exceptions.AdbConnectionError("ADB command not sent because a connection to the device has not been established.  (Did you call `AdbDeviceAsync.connect()`?)")

        adb_info = await self._open(b'sync:', transport_timeout_s, read_timeout_s, None)
        filesync_info = _FileSyncTransactionInfo(constants.FILESYNC_STAT_FORMAT, maxdata=self._maxdata)

        await self._filesync_send(constants.STAT, adb_info, filesync_info, data=device_path)
        _, (mode, size, mtime), _ = await self._filesync_read([constants.STAT], adb_info, filesync_info)
        await self._clse(adb_info)

        return mode, size, mtime

    # ======================================================================= #
    #                                                                         #
    #                       Hidden Methods: send packets                      #
    #                                                                         #
    # ======================================================================= #
    async def _clse(self, adb_info):
        """Send a ``b'CLSE'`` message and then read a ``b'CLSE'`` message.

        .. warning::

           This is not to be confused with the :meth:`AdbDeviceAsync.close` method!


        Parameters
        ----------
        adb_info : _AdbTransactionInfo
            Info and settings for this ADB transaction

        """
        msg = AdbMessage(constants.CLSE, adb_info.local_id, adb_info.remote_id)
        await self._io_manager.send(msg, adb_info)
        await self._read_until([constants.CLSE], adb_info)

    async def _okay(self, adb_info):
        """Send an ``b'OKAY'`` mesage.

        Parameters
        ----------
        adb_info : _AdbTransactionInfo
            Info and settings for this ADB transaction

        """
        msg = AdbMessage(constants.OKAY, adb_info.local_id, adb_info.remote_id)
        await self._io_manager.send(msg, adb_info)

    # ======================================================================= #
    #                                                                         #
    #                              Hidden Methods                             #
    #                                                                         #
    # ======================================================================= #
    async def _open(self, destination, transport_timeout_s, read_timeout_s, timeout_s):
        """Opens a new connection to the device via an ``b'OPEN'`` message.

        1. :meth:`~_AdbIOManagerAsync.send` an ``b'OPEN'`` command to the device that specifies the ``local_id``
        2. :meth:`~_AdbIOManagerAsync.read` the response from the device and fill in the ``adb_info.remote_id`` attribute


        Parameters
        ----------
        destination : bytes
            ``b'SERVICE:COMMAND'``
        transport_timeout_s : float, None
            Timeout in seconds for sending and receiving data, or ``None``; see :meth:`BaseTransportAsync.bulk_read() <adb_shell.transport.base_transport_async.BaseTransportAsync.bulk_read>`
            and :meth:`BaseTransportAsync.bulk_write() <adb_shell.transport.base_transport_async.BaseTransportAsync.bulk_write>`
        read_timeout_s : float
            The total time in seconds to wait for a ``b'CLSE'`` or ``b'OKAY'`` command in :meth:`_AdbIOManagerAsync.read`
        timeout_s : float, None
            The total time in seconds to wait for the ADB command to finish

        Returns
        -------
        adb_info : _AdbTransactionInfo
            Info and settings for this ADB transaction

        """
        async with self._local_id_lock:
            self._local_id += 1
            if self._local_id == 2**32:
                self._local_id = 1

            adb_info = _AdbTransactionInfo(self._local_id, None, self._get_transport_timeout_s(transport_timeout_s), read_timeout_s, timeout_s)

        msg = AdbMessage(constants.OPEN, adb_info.local_id, 0, destination + b'\0')
        await self._io_manager.send(msg, adb_info)
        _, adb_info.remote_id, _, _ = await self._io_manager.read([constants.OKAY], adb_info)

        return adb_info

    async def _read_until(self, expected_cmds, adb_info):
        """Read a packet, acknowledging any write packets.

        1. Read data via :meth:`_AdbIOManagerAsync.read`
        2. If a ``b'WRTE'`` packet is received, send an ``b'OKAY'`` packet via :meth:`AdbDeviceAsync._okay`
        3. Return the ``cmd`` and ``data`` that were read by :meth:`_AdbIOManagerAsync.read`


        Parameters
        ----------
        expected_cmds : list[bytes]
            :meth:`_AdbIOManagerAsync.read` will look for a packet whose command is in ``expected_cmds``
        adb_info : _AdbTransactionInfo
            Info and settings for this ADB transaction

        Returns
        -------
        cmd : bytes
            The command that was received by :meth:`_AdbIOManagerAsync.read`, which is in :const:`adb_shell.constants.WIRE_TO_ID` and must be in ``expected_cmds``
        data : bytes
            The data that was received by :meth:`_AdbIOManagerAsync.read`

        """
        cmd, _, _, data = await self._io_manager.read(expected_cmds, adb_info, allow_zeros=True)

        # Acknowledge write packets
        if cmd == constants.WRTE:
            await self._okay(adb_info)

        return cmd, data

    async def _read_until_close(self, adb_info):
        """Yield packets until a ``b'CLSE'`` packet is received.

        1. Read the ``cmd`` and ``data`` fields from a ``b'CLSE'`` or ``b'WRTE'`` packet via :meth:`AdbDeviceAsync._read_until`
        2. If ``cmd`` is ``b'CLSE'``, then send a ``b'CLSE'`` message and stop
        3. Yield ``data`` and repeat


        Parameters
        ----------
        adb_info : _AdbTransactionInfo
            Info and settings for this ADB transaction

        Yields
        ------
        data : bytes
            The data that was read by :meth:`AdbDeviceAsync._read_until`

        """
        start = time.time()

        while True:
            cmd, data = await self._read_until([constants.CLSE, constants.WRTE], adb_info)

            if cmd == constants.CLSE:
                msg = AdbMessage(constants.CLSE, adb_info.local_id, adb_info.remote_id)
                await self._io_manager.send(msg, adb_info)
                break

            yield data

            # Make sure the ADB command has not timed out
            if adb_info.timeout_s is not None and time.time() - start > adb_info.timeout_s:
                raise exceptions.AdbTimeoutError("The command did not complete within {} seconds".format(adb_info.timeout_s))

    async def _streaming_command(self, service, command, transport_timeout_s, read_timeout_s, timeout_s):
        """One complete set of packets for a single command.

        1. :meth:`~AdbDeviceAsync._open` a new connection to the device, where the ``destination`` parameter is ``service:command``
        2. Read the response data via :meth:`AdbDeviceAsync._read_until_close`


        .. note::

           All the data is held in memory, and thus large responses will be slow and can fill up memory.


        Parameters
        ----------
        service : bytes
            The ADB service (e.g., ``b'shell'``, as used by :meth:`AdbDeviceAsync.shell`)
        command : bytes
            The service command
        transport_timeout_s : float, None
            Timeout in seconds for sending and receiving data, or ``None``; see :meth:`BaseTransportAsync.bulk_read() <adb_shell.transport.base_transport_async.BaseTransportAsync.bulk_read>`
            and :meth:`BaseTransportAsync.bulk_write() <adb_shell.transport.base_transport_async.BaseTransportAsync.bulk_write>`
        read_timeout_s : float
            The total time in seconds to wait for a ``b'CLSE'`` or ``b'OKAY'`` command in :meth:`_AdbIOManagerAsync.read`
        timeout_s : float, None
            The total time in seconds to wait for the ADB command to finish

        Yields
        ------
        bytes
            The responses from the service.

        """
        adb_info = await self._open(b'%s:%s' % (service, command), transport_timeout_s, read_timeout_s, timeout_s)

        async for data in self._read_until_close(adb_info):
            yield data

    # ======================================================================= #
    #                                                                         #
    #                         FileSync Hidden Methods                         #
    #                                                                         #
    # ======================================================================= #
    async def _filesync_flush(self, adb_info, filesync_info):
        """Write the data in the buffer up to ``filesync_info.send_idx``, then set ``filesync_info.send_idx`` to 0.

        Parameters
        ----------
        adb_info : _AdbTransactionInfo
            Info and settings for this ADB transaction
        filesync_info : _FileSyncTransactionInfo
            Data and storage for this FileSync transaction

        """
        # Send the buffer
        msg = AdbMessage(constants.WRTE, adb_info.local_id, adb_info.remote_id, filesync_info.send_buffer[:filesync_info.send_idx])
        await self._io_manager.send(msg, adb_info)

        # Expect an 'OKAY' in response
        await self._read_until([constants.OKAY], adb_info)

        # Reset the send index
        filesync_info.send_idx = 0

    async def _filesync_read(self, expected_ids, adb_info, filesync_info):
        """Read ADB messages and return FileSync packets.

        Parameters
        ----------
        expected_ids : tuple[bytes]
            If the received header ID is not in ``expected_ids``, an exception will be raised
        adb_info : _AdbTransactionInfo
            Info and settings for this ADB transaction
        filesync_info : _FileSyncTransactionInfo
            Data and storage for this FileSync transaction

        Returns
        -------
        command_id : bytes
            The received header ID
        tuple
            The contents of the header
        data : bytearray, None
            The received data, or ``None`` if the command ID is :const:`adb_shell.constants.STAT`

        Raises
        ------
        adb_shell.exceptions.AdbCommandFailureException
            Command failed
        adb_shell.exceptions.InvalidResponseError
            Received response was not in ``expected_ids``

        """
        if filesync_info.send_idx:
            await self._filesync_flush(adb_info, filesync_info)

        # Read one filesync packet off the recv buffer.
        header_data = await self._filesync_read_buffered(filesync_info.recv_message_size, adb_info, filesync_info)
        header = struct.unpack(filesync_info.recv_message_format, header_data)

        # Header is (ID, ...).
        command_id = constants.FILESYNC_WIRE_TO_ID[header[0]]

        # Whether there is data to read
        read_data = command_id != constants.STAT

        if read_data:
            # Header is (ID, ..., size) --> read the data
            data = await self._filesync_read_buffered(header[-1], adb_info, filesync_info)
        else:
            # No data to be read
            data = bytearray()

        if command_id not in expected_ids:
            if command_id == constants.FAIL:
                reason = data.decode('utf-8', errors='backslashreplace')

                raise exceptions.AdbCommandFailureException('Command failed: {}'.format(reason))

            raise exceptions.InvalidResponseError('Expected one of %s, got %s' % (expected_ids, command_id))

        if not read_data:
            return command_id, header[1:], None

        return command_id, header[1:-1], data

    async def _filesync_read_buffered(self, size, adb_info, filesync_info):
        """Read ``size`` bytes of data from ``self.recv_buffer``.

        Parameters
        ----------
        size : int
            The amount of data to read
        adb_info : _AdbTransactionInfo
            Info and settings for this ADB transaction
        filesync_info : _FileSyncTransactionInfo
            Data and storage for this FileSync transaction

        Returns
        -------
        result : bytearray
            The read data

        """
        # Ensure recv buffer has enough data.
        while len(filesync_info.recv_buffer) < size:
            _, data = await self._read_until([constants.WRTE], adb_info)
            filesync_info.recv_buffer += data

        result = filesync_info.recv_buffer[:size]
        filesync_info.recv_buffer = filesync_info.recv_buffer[size:]
        return result

    async def _filesync_read_until(self, expected_ids, finish_ids, adb_info, filesync_info):
        """Useful wrapper around :meth:`AdbDeviceAsync._filesync_read`.

        Parameters
        ----------
        expected_ids : tuple[bytes]
            If the received header ID is not in ``expected_ids``, an exception will be raised
        finish_ids : tuple[bytes]
            We will read until we find a header ID that is in ``finish_ids``
        adb_info : _AdbTransactionInfo
            Info and settings for this ADB transaction
        filesync_info : _FileSyncTransactionInfo
            Data and storage for this FileSync transaction

        Yields
        ------
        cmd_id : bytes
            The received header ID
        header : tuple
            TODO
        data : bytearray
            The received data

        """
        while True:
            cmd_id, header, data = await self._filesync_read(expected_ids + finish_ids, adb_info, filesync_info)
            yield cmd_id, header, data

            # These lines are not reachable because whenever this method is called and `cmd_id` is in `finish_ids`, the code
            # either breaks (`list` and `_pull`), returns (`_push`), or raises an exception (`_push`)
            if cmd_id in finish_ids:  # pragma: no cover
                break

    async def _filesync_send(self, command_id, adb_info, filesync_info, data=b'', size=None):
        """Send/buffer FileSync packets.

        Packets are buffered and only flushed when this connection is read from. All
        messages have a response from the device, so this will always get flushed.

        Parameters
        ----------
        command_id : bytes
            Command to send.
        adb_info : _AdbTransactionInfo
            Info and settings for this ADB transaction
        filesync_info : _FileSyncTransactionInfo
            Data and storage for this FileSync transaction
        data : str, bytes
            Optional data to send, must set data or size.
        size : int, None
            Optionally override size from len(data).

        """
        if not isinstance(data, bytes):
            data = data.encode('utf8')
        if size is None:
            size = len(data)

        if not filesync_info.can_add_to_send_buffer(len(data)):
            await self._filesync_flush(adb_info, filesync_info)

        buf = struct.pack(b'<2I', constants.FILESYNC_ID_TO_WIRE[command_id], size) + data
        filesync_info.send_buffer[filesync_info.send_idx:filesync_info.send_idx + len(buf)] = buf
        filesync_info.send_idx += len(buf)


class AdbDeviceTcpAsync(AdbDeviceAsync):
    """A class with methods for connecting to a device via TCP and executing ADB commands.

    Parameters
    ----------
    host : str
        The address of the device; may be an IP address or a host name
    port : int
        The device port to which we are connecting (default is 5555)
    default_transport_timeout_s : float, None
        Default timeout in seconds for TCP packets, or ``None``
    banner : str, bytes, None
        The hostname of the machine where the Python interpreter is currently running; if
        it is not provided, it will be determined via ``socket.gethostname()``

    Attributes
    ----------
    _available : bool
        Whether an ADB connection to the device has been established
    _banner : bytearray, bytes
        The hostname of the machine where the Python interpreter is currently running
    _default_transport_timeout_s : float, None
        Default timeout in seconds for TCP packets, or ``None``
    _local_id : int
        The local ID that is used for ADB transactions; the value is incremented each time and is always in the range ``[1, 2^32)``
    _maxdata : int
        Maximum amount of data in an ADB packet
    _transport : TcpTransportAsync
        The transport that is used to connect to the device

    """

    def __init__(self, host, port=5555, default_transport_timeout_s=None, banner=None):
        transport = TcpTransportAsync(host, port)
        super(AdbDeviceTcpAsync, self).__init__(transport, default_transport_timeout_s, banner)


================================================
FILE: adb_shell/adb_message.py
================================================
# Copyright (c) 2021 Jeff Irion and contributors
#
# This file is part of the adb-shell package.  It incorporates work
# covered by the following license notice:
#
#
#   Copyright 2014 Google Inc. All rights reserved.
#
#   Licensed under the Apache License, Version 2.0 (the "License");
#   you may not use this file except in compliance with the License.
#   You may obtain a copy of the License at
#
#       http://www.apache.org/licenses/LICENSE-2.0
#
#   Unless required by applicable law or agreed to in writing, software
#   distributed under the License is distributed on an "AS IS" BASIS,
#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#   See the License for the specific language governing permissions and
#   limitations under the License.

"""Functions and an :class:`AdbMessage` class for packing and unpacking ADB messages.

.. rubric:: Contents

* :class:`AdbMessage`

    * :attr:`AdbMessage.checksum`
    * :meth:`AdbMessage.pack`

* :func:`checksum`
* :func:`int_to_cmd`
* :func:`unpack`

"""


import struct

from . import constants


def checksum(data):
    """Calculate the checksum of the provided data.

    Parameters
    ----------
    data : bytearray, bytes, str
        The data

    Returns
    -------
    int
        The checksum

    """
    # The checksum is just a sum of all the bytes. I swear.
    if isinstance(data, bytearray):
        total = sum(data)

    elif isinstance(data, bytes):
        if data and isinstance(data[0], bytes):
            # Python 2 bytes (str) index as single-character strings.
            total = sum((ord(d) for d in data))  # pragma: no cover
        else:
            # Python 3 bytes index as numbers (and PY2 empty strings sum() to 0)
            total = sum(data)

    else:
        # Unicode strings (should never see?)
        total = sum((ord(d) for d in data))

    return total & 0xFFFFFFFF


def int_to_cmd(n):
    """Convert from an integer (4 bytes) to an ADB command.

    Parameters
    ----------
    n : int
        The integer that will be converted to an ADB command

    Returns
    -------
    str
        The ADB command (e.g., ``'CNXN'``)

    """
    return ''.join(chr((n >> (i * 8)) % 256) for i in range(4)).encode('utf-8')


def unpack(message):
    """Unpack a received ADB message.

    Parameters
    ----------
    message : bytes
        The received message

    Returns
    -------
    cmd : int
        The ADB command
    arg0 : int
        TODO
    arg1 : int
        TODO
    data_length : int
        The length of the message's data
    data_checksum : int
        The checksum of the message's data

    Raises
    ------
    ValueError
        Unable to unpack the ADB command.

    """
    try:
        cmd, arg0, arg1, data_length, data_checksum, _ = struct.unpack(constants.MESSAGE_FORMAT, message)
    except struct.error as e:
        raise ValueError('Unable to unpack ADB command. (length={})'.format(len(message)), constants.MESSAGE_FORMAT, message, e)

    return cmd, arg0, arg1, data_length, data_checksum


class AdbMessage(object):
    """A helper class for packing ADB messages.

    Parameters
    ----------
    command : bytes
        A command; examples used in this package include :const:`adb_shell.constants.AUTH`, :const:`adb_shell.constants.CNXN`, :const:`adb_shell.constants.CLSE`, :const:`adb_shell.constants.OPEN`, and :const:`adb_shell.constants.OKAY`
    arg0 : int
        Usually the local ID, but :meth:`~adb_shell.adb_device.AdbDevice.connect` and :meth:`~adb_shell.adb_device_async.AdbDeviceAsync.connect` provide :const:`adb_shell.constants.VERSION`, :const:`adb_shell.constants.AUTH_SIGNATURE`, and :const:`adb_shell.constants.AUTH_RSAPUBLICKEY`
    arg1 : int
        Usually the remote ID, but :meth:`~adb_shell.adb_device.AdbDevice.connect` and :meth:`~adb_shell.adb_device_async.AdbDeviceAsync.connect` provide :const:`adb_shell.constants.MAX_ADB_DATA`
    data : bytes
        The data that will be sent

    Attributes
    ----------
    arg0 : int
        Usually the local ID, but :meth:`~adb_shell.adb_device.AdbDevice.connect` and :meth:`~adb_shell.adb_device_async.AdbDeviceAsync.connect` provide :const:`adb_shell.constants.VERSION`, :const:`adb_shell.constants.AUTH_SIGNATURE`, and :const:`adb_shell.constants.AUTH_RSAPUBLICKEY`
    arg1 : int
        Usually the remote ID, but :meth:`~adb_shell.adb_device.AdbDevice.connect` and :meth:`~adb_shell.adb_device_async.AdbDeviceAsync.connect` provide :const:`adb_shell.constants.MAX_ADB_DATA`
    command : int
        The input parameter ``command`` converted to an integer via :const:`adb_shell.constants.ID_TO_WIRE`
    data : bytes
        The data that will be sent
    magic : int
        ``self.command`` with its bits flipped; in other words, ``self.command + self.magic == 2**32 - 1``

    """
    def __init__(self, command, arg0, arg1, data=b''):
        self.command = constants.ID_TO_WIRE[command]
        self.magic = self.command ^ 0xFFFFFFFF
        self.arg0 = arg0
        self.arg1 = arg1
        self.data = data

    def pack(self):
        """Returns this message in an over-the-wire format.

        Returns
        -------
        bytes
            The message packed into the format required by ADB

        """
        return struct.pack(constants.MESSAGE_FORMAT, self.command, self.arg0, self.arg1, len(self.data), self.checksum, self.magic)

    @property
    def checksum(self):
        """Return ``checksum(self.data)``

        Returns
        -------
        int
            The checksum of ``self.data``

        """
        return checksum(self.data)


================================================
FILE: adb_shell/auth/__init__.py
================================================
# Copyright (c) 2021 Jeff Irion and contributors
#
# This file is part of the adb-shell package.


================================================
FILE: adb_shell/auth/keygen.py
================================================
# Copyright (c) 2021 Jeff Irion and contributors
#
# This file is part of the adb-shell package.  It was originally written by
# @joeleong, and it was obtained from: https://github.com/google/python-adb/pull/144

"""This file implements encoding and decoding logic for Android's custom RSA
public key binary format. Public keys are stored as a sequence of
little-endian 32 bit words. Note that Android only supports little-endian
processors, so we don't do any byte order conversions when parsing the binary
struct.

Structure from:
https://github.com/aosp-mirror/platform_system_core/blob/c55fab4a59cfa461857c6a61d8a0f1ae4591900c/libcrypto_utils/android_pubkey.c

.. code-block:: c

   typedef struct RSAPublicKey {
       // Modulus length. This must be ANDROID_PUBKEY_MODULUS_SIZE_WORDS
       uint32_t modulus_size_words;

       // Precomputed montgomery parameter: -1 / n[0] mod 2^32
       uint32_t n0inv;

       // RSA modulus as a little-endian array
       uint8_t modulus[ANDROID_PUBKEY_MODULUS_SIZE];

       // Montgomery parameter R^2 as a little-endian array of little-endian words
       uint8_t rr[ANDROID_PUBKEY_MODULUS_SIZE];

       // RSA modulus: 3 or 65537
       uint32_t exponent;
   } RSAPublicKey;


.. rubric:: Contents

* :func:`_to_bytes`
* :func:`decode_pubkey`
* :func:`decode_pubkey_file`
* :func:`encode_pubkey`
* :func:`get_user_info`
* :func:`keygen`
* :func:`write_public_keyfile`

"""


import os
import base64
import logging
import socket
import struct
import sys

from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import rsa


_LOGGER = logging.getLogger(__name__)

if sys.version_info[0] == 2:  # pragma: no cover
    FileNotFoundError = IOError  # pylint: disable=redefined-builtin


#: Size of an RSA modulus such as an encrypted block or a signature.
ANDROID_PUBKEY_MODULUS_SIZE = 2048 // 8

#: Python representation of "struct RSAPublicKey"
ANDROID_RSAPUBLICKEY_STRUCT = (
    '<'                 # Little-endian
    'L'                 # uint32_t modulus_size_words;
    'L'                 # uint32_t n0inv;
    '{modulus_size}s'   # uint8_t modulus[ANDROID_PUBKEY_MODULUS_SIZE];
    '{modulus_size}s'   # uint8_t rr[ANDROID_PUBKEY_MODULUS_SIZE];
    'L'                 # uint32_t exponent;
).format(modulus_size=ANDROID_PUBKEY_MODULUS_SIZE)


#: Size of the RSA modulus in words.
ANDROID_PUBKEY_MODULUS_SIZE_WORDS = ANDROID_PUBKEY_MODULUS_SIZE // 4


def _to_bytes(n, length, endianess='big'):
    """Partial python2 compatibility with int.to_bytes

    https://stackoverflow.com/a/20793663

    Parameters
    ----------
    n : TODO
        TODO
    length : TODO
        TODO
    endianess : str, TODO
        TODO

    Returns
    -------
    TODO
        TODO

    """
    if not hasattr(n, 'to_bytes'):
        h = '{:x}'.format(n)
        s = ('0' * (len(h) % 2) + h).zfill(length * 2).decode('hex')
        return s if endianess == 'big' else s[::-1]
    return n.to_bytes(length, endianess)


def decode_pubkey(public_key):
    """Decode a public RSA key stored in Android's custom binary format.

    Parameters
    ----------
    public_key : TODO
        TODO

    """
    binary_key_data = base64.b64decode(public_key)
    modulus_size_words, n0inv, modulus_bytes, rr_bytes, exponent = struct.unpack(ANDROID_RSAPUBLICKEY_STRUCT, binary_key_data)
    assert modulus_size_words == ANDROID_PUBKEY_MODULUS_SIZE_WORDS
    modulus = reversed(modulus_bytes)
    rr = reversed(rr_bytes)
    _LOGGER.debug('modulus_size_words: %s', hex(modulus_size_words))
    _LOGGER.debug('n0inv: %s', hex(n0inv))
    _LOGGER.debug('modulus: %s', ':'.join((hex(m) for m in modulus)))
    _LOGGER.debug('rr: %s', ':'.join((hex(r) for r in rr)))
    _LOGGER.debug('exponent: %s', hex(exponent))


def decode_pubkey_file(public_key_path):
    """TODO

    Parameters
    ----------
    public_key_path : str
        TODO

    """
    with open(public_key_path, 'rb') as fd:
        decode_pubkey(fd.read())


def encode_pubkey(private_key_path):
    """Encodes a public RSA key into Android's custom binary format.

    Parameters
    ----------
    private_key_path : str
        TODO

    Returns
    -------
    TODO
        TODO

    """
    with open(private_key_path, 'rb') as key_file:
        key = serialization.load_pem_private_key(key_file.read(), password=None, backend=default_backend()).private_numbers().public_numbers

    # Compute and store n0inv = -1 / N[0] mod 2^32.
    # BN_set_bit(r32, 32)
    r32 = 1 << 32
    # BN_mod(n0inv, key->n, r32, ctx)
    n0inv = key.n % r32
    # BN_mod_inverse(n0inv, n0inv, r32, ctx)
    n0inv = rsa._modinv(n0inv, r32)  # pylint: disable=protected-access
    # BN_sub(n0inv, r32, n0inv)
    n0inv = r32 - n0inv

    # Compute and store rr = (2^(rsa_size)) ^ 2 mod N.
    # BN_set_bit(rr, ANDROID_PUBKEY_MODULUS_SIZE * 8)
    rr = 1 << (ANDROID_PUBKEY_MODULUS_SIZE * 8)
    # BN_mod_sqr(rr, rr, key->n, ctx)
    rr = (rr ** 2) % key.n

    return struct.pack(
        ANDROID_RSAPUBLICKEY_STRUCT,
        ANDROID_PUBKEY_MODULUS_SIZE_WORDS,
        n0inv,
        _to_bytes(key.n, ANDROID_PUBKEY_MODULUS_SIZE, 'little'),
        _to_bytes(rr, ANDROID_PUBKEY_MODULUS_SIZE, 'little'),
        key.e
    )


def get_user_info():
    """TODO

    Returns
    -------
    str
        ``' <username>@<hostname>``

    """
    try:
        username = os.getlogin()
    except (FileNotFoundError, OSError):
        username = 'unknown'

    if not username:
        username = 'unknown'

    hostname = socket.gethostname()
    if not hostname:
        hostname = 'unknown'

    return ' ' + username + '@' + hostname


def write_public_keyfile(private_key_path, public_key_path):
    """Write a public keyfile to ``public_key_path`` in Android's custom
    RSA public key format given a path to a private keyfile.

    Parameters
    ----------
    private_key_path : TODO
        TODO
    public_key_path : TODO
        TODO

    """
    public_key = encode_pubkey(private_key_path)
    assert len(public_key) == struct.calcsize(ANDROID_RSAPUBLICKEY_STRUCT)

    with open(public_key_path, 'wb') as public_key_file:
        public_key_file.write(base64.b64encode(public_key))
        public_key_file.write(get_user_info().encode())


def keygen(filepath):
    """Generate an ADB public/private key pair.

    * The private key is stored in ``filepath``.
    * The public key is stored in ``filepath + '.pub'``

    (Existing files will be overwritten.)

    Parameters
    ----------
    filepath : str
        File path to write the private/public keypair

    """
    private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048, backend=default_backend())

    with open(filepath, 'wb') as private_key_file:
        private_key_file.write(private_key.private_bytes(encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.PKCS8, encryption_algorithm=serialization.NoEncryption()))

    write_public_keyfile(filepath, filepath + '.pub')


================================================
FILE: adb_shell/auth/sign_cryptography.py
================================================
# Copyright 2014 Google Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""ADB authentication using the ``cryptography`` package.


.. rubric:: Contents

* :class:`CryptographySigner`

    * :meth:`CryptographySigner.GetPublicKey`
    * :meth:`CryptographySigner.Sign`

"""

from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives.asymmetric import utils


# pylint: disable=abstract-method
class CryptographySigner(object):
    """AuthSigner using cryptography.io.

    Parameters
    ----------
    rsa_key_path : str
        The path to the private key.

    Attributes
    ----------
    public_key : str
        The contents of the public key file
    rsa_key : cryptography.hazmat.backends.openssl.rsa._RSAPrivateKey
        The loaded private key

    """
    def __init__(self, rsa_key_path):
        with open(rsa_key_path + '.pub', 'rb') as rsa_pub_file:
            self.public_key = rsa_pub_file.read()

        with open(rsa_key_path, 'rb') as rsa_prv_file:
            self.rsa_key = serialization.load_pem_private_key(rsa_prv_file.read(), None, default_backend())

    def Sign(self, data):
        """Signs given data using a private key.

        Parameters
        ----------
        data : TODO
            TODO

        Returns
        -------
        TODO
            The signed ``data``

        """
        return self.rsa_key.sign(data, padding.PKCS1v15(), utils.Prehashed(hashes.SHA1()))

    def GetPublicKey(self):
        """Returns the public key in PEM format without headers or newlines.

        Returns
        -------
        self.public_key : str
            The contents of the public key file

        """
        return self.public_key


================================================
FILE: adb_shell/auth/sign_pycryptodome.py
================================================
# Copyright 2014 Google Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""ADB authentication using ``pycryptodome``.


.. rubric:: Contents

* :class:`PycryptodomeAuthSigner`

    * :meth:`PycryptodomeAuthSigner.GetPublicKey`
    * :meth:`PycryptodomeAuthSigner.Sign`

"""

from Crypto.Hash import SHA256
from Crypto.PublicKey import RSA
from Crypto.Signature import pkcs1_15


class PycryptodomeAuthSigner(object):
    """AuthSigner using the pycryptodome package.

    Parameters
    ----------
    rsa_key_path : str, None
        The path to the private key

    Attributes
    ----------
    public_key : str
        The contents of the public key file
    rsa_key : Crypto.PublicKey.RSA.RsaKey
        The contents of theprivate key

    """
    def __init__(self, rsa_key_path=None):
        super(PycryptodomeAuthSigner, self).__init__()

        if rsa_key_path:
            with open(rsa_key_path + '.pub', 'rb') as rsa_pub_file:
                self.public_key = rsa_pub_file.read()

            with open(rsa_key_path, 'rb') as rsa_priv_file:
                self.rsa_key = RSA.import_key(rsa_priv_file.read())

    def Sign(self, data):
        """Signs given data using a private key.

        Parameters
        ----------
        data : bytes, bytearray
            The data to be signed

        Returns
        -------
        bytes
            The signed ``data``

        """
        h = SHA256.new(data)
        return pkcs1_15.new(self.rsa_key).sign(h)

    def GetPublicKey(self):
        """Returns the public key in PEM format without headers or newlines.

        Returns
        -------
        self.public_key : str
            The contents of the public key file

        """
        return self.public_key


================================================
FILE: adb_shell/auth/sign_pythonrsa.py
================================================
# Copyright 2014 Google Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""ADB authentication using the ``rsa`` package.


.. rubric:: Contents

* :class:`_Accum`

    * :meth:`_Accum.digest`
    * :meth:`_Accum.update`

* :func:`_load_rsa_private_key`
* :class:`PythonRSASigner`

    * :meth:`PythonRSASigner.FromRSAKeyPath`
    * :meth:`PythonRSASigner.GetPublicKey`
    * :meth:`PythonRSASigner.Sign`

"""


from pyasn1.codec.der import decoder
from pyasn1.type import univ
import rsa
from rsa import pkcs1


class _Accum(object):
    """A fake hashing algorithm.

    The Python ``rsa`` lib hashes all messages it signs. ADB does it already, we just
    need to slap a signature on top of already hashed message.  Introduce a "fake"
    hashing algo for this.

    Attributes
    ----------
    _buf : bytes
        A buffer for storing data before it is signed

    """
    def __init__(self):
        self._buf = b''

    def update(self, msg):
        """Update this hash object's state with the provided ``msg``.

        Parameters
        ----------
        msg : bytes
            The message to be appended to ``self._buf``

        """
        self._buf += msg

    def digest(self):
        """Return the digest value as a string of binary data.

        Returns
        -------
        self._buf : bytes
            ``self._buf``

        """
        return self._buf


pkcs1.HASH_METHODS['SHA-1-PREHASHED'] = _Accum
pkcs1.HASH_ASN1['SHA-1-PREHASHED'] = pkcs1.HASH_ASN1['SHA-1']


def _load_rsa_private_key(pem):
    """PEM encoded PKCS#8 private key -> ``rsa.PrivateKey``.

    ADB uses private RSA keys in pkcs#8 format. The ``rsa`` library doesn't
    support them natively.  Do some ASN unwrapping to extract naked RSA key
    (in der-encoded form).

    See:

    * https://www.ietf.org/rfc/rfc2313.txt
    * http://superuser.com/a/606266

    Parameters
    ----------
    pem : str
        The private key to be loaded

    Returns
    -------
    rsa.key.PrivateKey
        The loaded private key

    """
    try:
        der = rsa.pem.load_pem(pem, 'PRIVATE KEY')
        keyinfo, _ = decoder.decode(der)

        if keyinfo[1][0] != univ.ObjectIdentifier('1.2.840.113549.1.1.1'):
            raise ValueError('Not a DER-encoded OpenSSL private RSA key')

        private_key_der = keyinfo[2].asOctets()

    except IndexError:
        raise ValueError('Not a DER-encoded OpenSSL private RSA key')

    return rsa.PrivateKey.load_pkcs1(private_key_der, format='DER')


class PythonRSASigner(object):
    """Implements :class:`adb_protocol.AuthSigner` using http://stuvel.eu/rsa.

    Parameters
    ----------
    pub : str, None
        The contents of the public key file
    priv : str, None
        The contents of the private key file

    Attributes
    ----------
    priv_key : rsa.key.PrivateKey
        The loaded private key
    pub_key : str, None
        The contents of the public key file

    """
    def __init__(self, pub=None, priv=None):
        self.priv_key = _load_rsa_private_key(priv)
        self.pub_key = pub

    @classmethod
    def FromRSAKeyPath(cls, rsa_key_path):
        """Create a :class:`PythonRSASigner` instance using the provided private key.

        Parameters
        ----------
        rsa_key_path : str
            The path to the private key; the public key must be ``rsa_key_path + '.pub'``.

        Returns
        -------
        PythonRSASigner
            A :class:`PythonRSASigner` with private key ``rsa_key_path`` and public key ``rsa_key_path + '.pub'``

        """
        with open(rsa_key_path + '.pub') as f:
            pub = f.read()
        with open(rsa_key_path) as f:
            priv = f.read()
        return cls(pub, priv)

    def Sign(self, data):
        """Signs given data using a private key.

        Parameters
        ----------
        data : bytes
            The data to be signed

        Returns
        -------
        bytes
            The signed ``data``

        """
        return rsa.sign(data, self.priv_key, 'SHA-1-PREHASHED')

    def GetPublicKey(self):
        """Returns the public key in PEM format without headers or newlines.

        Returns
        -------
        self.pub_key : str, None
            The contents of the public key file, or ``None`` if a public key was not provided.

        """
        return self.pub_key


================================================
FILE: adb_shell/constants.py
================================================
# Copyright (c) 2021 Jeff Irion and contributors
#
# This file is part of the adb-shell package.  It incorporates work
# covered by the following license notice:
#
#
#   Copyright 2014 Google Inc. All rights reserved.
#
#   Licensed under the Apache License, Version 2.0 (the "License");
#   you may not use this file except in compliance with the License.
#   You may obtain a copy of the License at
#
#       http://www.apache.org/licenses/LICENSE-2.0
#
#   Unless required by applicable law or agreed to in writing, software
#   distributed under the License is distributed on an "AS IS" BASIS,
#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#   See the License for the specific language governing permissions and
#   limitations under the License.

"""Constants used throughout the code.

"""


import stat
import struct


#: From adb.h
CLASS = 0xFF

#: From adb.h
SUBCLASS = 0x42

#: From adb.h
PROTOCOL = 0x01

#: ADB protocol version.
VERSION = 0x01000000

#: Maximum amount of data in an ADB packet. According to: https://android.googlesource.com/platform/system/core/+/master/adb/adb.h
MAX_ADB_DATA = 1024 * 1024
MAX_LEGACY_ADB_DATA = 4 * 1024

#: Maximum size of a filesync DATA packet. Default size.
MAX_PUSH_DATA = 2 * 1024

#: Maximum chunk size. According to https://android.googlesource.com/platform/system/core/+/master/adb/SYNC.TXT
MAX_CHUNK_SIZE = 64 * 1024

#: Default mode for pushed files.
DEFAULT_PUSH_MODE = stat.S_IFREG | stat.S_IRWXU | stat.S_IRWXG

#: AUTH constant for ``arg0``
AUTH_TOKEN = 1

#: AUTH constant for ``arg0``
AUTH_SIGNATURE = 2

#: AUTH constant for ``arg0``
AUTH_RSAPUBLICKEY = 3

AUTH = b'AUTH'
CLSE = b'CLSE'
CNXN = b'CNXN'
FAIL = b'FAIL'
OKAY = b'OKAY'
OPEN = b'OPEN'
SYNC = b'SYNC'
WRTE = b'WRTE'

DATA = b'DATA'
DENT = b'DENT'
DONE = b'DONE'
LIST = b'LIST'
QUIT = b'QUIT'
RECV = b'RECV'
SEND = b'SEND'
STAT = b'STAT'

#: Commands that are recognized by :meth:`adb_shell.adb_device._AdbIOManager._read_packet_from_device` and :meth:`adb_shell.adb_device_async._AdbIOManagerAsync._read_packet_from_device`
IDS = (AUTH, CLSE, CNXN, OKAY, OPEN, SYNC, WRTE)

#: A dictionary where the keys are the commands in :const:`IDS` and the values are the keys converted to integers
ID_TO_WIRE = {cmd_id: sum(c << (i * 8) for i, c in enumerate(bytearray(cmd_id))) for cmd_id in IDS}

#: A dictionary where the keys are integers and the values are their corresponding commands (type = bytes) from :const:`IDS`
WIRE_TO_ID = {wire: cmd_id for cmd_id, wire in ID_TO_WIRE.items()}

#: Commands that are recognized by :meth:`adb_shell.adb_device.AdbDevice._filesync_read` and :meth:`adb_shell.adb_device_async.AdbDeviceAsync._filesync_read`
FILESYNC_IDS = (DATA, DENT, DONE, FAIL, LIST, OKAY, QUIT, RECV, SEND, STAT)

#: A dictionary where the keys are the commands in :const:`FILESYNC_IDS` and the values are the keys converted to integers
FILESYNC_ID_TO_WIRE = {cmd_id: sum(c << (i * 8) for i, c in enumerate(bytearray(cmd_id))) for cmd_id in FILESYNC_IDS}

#: A dictionary where the keys are integers and the values are their corresponding commands (type = bytes) from :const:`FILESYNC_IDS`
FILESYNC_WIRE_TO_ID = {wire: cmd_id for cmd_id, wire in FILESYNC_ID_TO_WIRE.items()}

#: An ADB message is 6 words in little-endian.
MESSAGE_FORMAT = b'<6I'

#: The format for FileSync "list" messages
FILESYNC_LIST_FORMAT = b'<5I'

#: The format for FileSync "pull" messages
FILESYNC_PULL_FORMAT = b'<2I'

#: The format for FileSync "push" messages
FILESYNC_PUSH_FORMAT = b'<2I'

#: The format for FileSync "stat" messages
FILESYNC_STAT_FORMAT = b'<4I'

#: The size of an ADB message
MESSAGE_SIZE = struct.calcsize(MESSAGE_FORMAT)

#: Default authentication timeout (in s) for :meth:`adb_shell.adb_device.AdbDevice.connect`
Download .txt
gitextract_gtoem9ut/

├── .flake8
├── .gitattributes
├── .github/
│   ├── ISSUE_TEMPLATE/
│   │   └── bug_report.md
│   └── workflows/
│       └── python-package.yml
├── .gitignore
├── .pylintrc
├── .readthedocs.yml
├── .travis.yml
├── LICENSE
├── MANIFEST.in
├── Makefile
├── README.rst
├── adb_shell/
│   ├── __init__.py
│   ├── adb_device.py
│   ├── adb_device_async.py
│   ├── adb_message.py
│   ├── auth/
│   │   ├── __init__.py
│   │   ├── keygen.py
│   │   ├── sign_cryptography.py
│   │   ├── sign_pycryptodome.py
│   │   └── sign_pythonrsa.py
│   ├── constants.py
│   ├── exceptions.py
│   ├── hidden_helpers.py
│   └── transport/
│       ├── __init__.py
│       ├── base_transport.py
│       ├── base_transport_async.py
│       ├── tcp_transport.py
│       ├── tcp_transport_async.py
│       └── usb_transport.py
├── docs/
│   ├── Makefile
│   ├── make.bat
│   ├── requirements.txt
│   └── source/
│       ├── adb_shell.adb_device.rst
│       ├── adb_shell.adb_device_async.rst
│       ├── adb_shell.adb_message.rst
│       ├── adb_shell.auth.keygen.rst
│       ├── adb_shell.auth.rst
│       ├── adb_shell.auth.sign_cryptography.rst
│       ├── adb_shell.auth.sign_pycryptodome.rst
│       ├── adb_shell.auth.sign_pythonrsa.rst
│       ├── adb_shell.constants.rst
│       ├── adb_shell.exceptions.rst
│       ├── adb_shell.hidden_helpers.rst
│       ├── adb_shell.rst
│       ├── adb_shell.transport.base_transport.rst
│       ├── adb_shell.transport.base_transport_async.rst
│       ├── adb_shell.transport.rst
│       ├── adb_shell.transport.tcp_transport.rst
│       ├── adb_shell.transport.tcp_transport_async.rst
│       ├── adb_shell.transport.usb_transport.rst
│       ├── conf.py
│       ├── index.rst
│       └── modules.rst
├── scripts/
│   ├── bumpversion.sh
│   ├── get_package_name.sh
│   ├── get_version.sh
│   ├── git_retag.sh
│   ├── git_tag.sh
│   ├── pre-commit.sh
│   └── rename_package.sh
├── setup.py
├── tests/
│   ├── __init__.py
│   ├── async_patchers.py
│   ├── async_wrapper.py
│   ├── filesync_helpers.py
│   ├── keygen_stub.py
│   ├── patchers.py
│   ├── test_adb_device.py
│   ├── test_adb_device_async.py
│   ├── test_adb_message.py
│   ├── test_exceptions.py
│   ├── test_hidden_helpers.py
│   ├── test_keygen.py
│   ├── test_sign_cryptography.py
│   ├── test_sign_pycryptodome.py
│   ├── test_sign_pythonrsa.py
│   ├── test_tcp_transport.py
│   ├── test_tcp_transport_async.py
│   ├── test_usb_importerror.py
│   └── test_usb_transport.py
└── venv_requirements.txt
Download .txt
SYMBOL INDEX (458 symbols across 32 files)

FILE: adb_shell/adb_device.py
  function _open_bytesio (line 101) | def _open_bytesio(stream, *args, **kwargs):  # pylint: disable=unused-ar...
  class _AdbIOManager (line 122) | class _AdbIOManager(object):
    method __init__ (line 148) | def __init__(self, transport):
    method close (line 155) | def close(self):
    method connect (line 165) | def connect(self, banner, rsa_keys, auth_timeout_s, auth_callback, adb...
    method read (line 273) | def read(self, expected_cmds, adb_info, allow_zeros=False):
    method send (line 364) | def send(self, msg, adb_info):
    method _read_expected_packet_from_device (line 378) | def _read_expected_packet_from_device(self, expected_cmds, adb_info):
    method _read_bytes_from_device (line 417) | def _read_bytes_from_device(self, length, adb_info):
    method _read_packet_from_device (line 459) | def _read_packet_from_device(self, adb_info):
    method _send (line 503) | def _send(self, msg, adb_info):
  class AdbDevice (line 527) | class AdbDevice(object):
    method __init__ (line 564) | def __init__(self, transport, default_transport_timeout_s=None, banner...
    method available (line 587) | def available(self):
    method max_chunk_size (line 599) | def max_chunk_size(self):
    method _get_transport_timeout_s (line 610) | def _get_transport_timeout_s(self, transport_timeout_s):
    method close (line 631) | def close(self):
    method connect (line 638) | def connect(self, rsa_keys=None, transport_timeout_s=None, auth_timeou...
    method _service (line 684) | def _service(self, service, command, transport_timeout_s=None, read_ti...
    method _streaming_service (line 713) | def _streaming_service(self, service, command, transport_timeout_s=Non...
    method exec_out (line 742) | def exec_out(self, command, transport_timeout_s=None, read_timeout_s=c...
    method reboot (line 772) | def reboot(self, fastboot=False, transport_timeout_s=None, read_timeou...
    method root (line 793) | def root(self, transport_timeout_s=None, read_timeout_s=constants.DEFA...
    method shell (line 814) | def shell(self, command, transport_timeout_s=None, read_timeout_s=cons...
    method streaming_shell (line 842) | def streaming_shell(self, command, transport_timeout_s=None, read_time...
    method list (line 873) | def list(self, device_path, transport_timeout_s=None, read_timeout_s=c...
    method pull (line 913) | def pull(self, device_path, local_path, progress_callback=None, transp...
    method _pull (line 945) | def _pull(self, device_path, stream, progress_callback, adb_info, file...
    method push (line 977) | def push(self, local_path, device_path, st_mode=constants.DEFAULT_PUSH...
    method _push (line 1018) | def _push(self, stream, device_path, st_mode, mtime, progress_callback...
    method stat (line 1073) | def stat(self, device_path, transport_timeout_s=None, read_timeout_s=c...
    method _clse (line 1114) | def _clse(self, adb_info):
    method _okay (line 1132) | def _okay(self, adb_info):
    method _open (line 1149) | def _open(self, destination, transport_timeout_s, read_timeout_s, time...
    method _read_until (line 1187) | def _read_until(self, expected_cmds, adb_info):
    method _read_until_close (line 1218) | def _read_until_close(self, adb_info):
    method _streaming_command (line 1253) | def _streaming_command(self, service, command, transport_timeout_s, re...
    method _filesync_flush (line 1294) | def _filesync_flush(self, adb_info, filesync_info):
    method _filesync_read (line 1315) | def _filesync_read(self, expected_ids, adb_info, filesync_info):
    method _filesync_read_buffered (line 1377) | def _filesync_read_buffered(self, size, adb_info, filesync_info):
    method _filesync_read_until (line 1404) | def _filesync_read_until(self, expected_ids, finish_ids, adb_info, fil...
    method _filesync_send (line 1437) | def _filesync_send(self, command_id, adb_info, filesync_info, data=b''...
  class AdbDeviceTcp (line 1470) | class AdbDeviceTcp(AdbDevice):
    method __init__ (line 1502) | def __init__(self, host, port=5555, default_transport_timeout_s=None, ...
  class AdbDeviceUsb (line 1507) | class AdbDeviceUsb(AdbDevice):
    method __init__ (line 1544) | def __init__(self, serial=None, port_path=None, default_transport_time...

FILE: adb_shell/adb_device_async.py
  class _AsyncBytesIO (line 96) | class _AsyncBytesIO:
    method __init__ (line 106) | def __init__(self, bytesio):
    method read (line 109) | async def read(self, size=-1):
    method write (line 125) | async def write(self, data):
  function _open_bytesio (line 138) | async def _open_bytesio(stream, *args, **kwargs):  # pylint: disable=unu...
  class _AdbIOManagerAsync (line 159) | class _AdbIOManagerAsync(object):
    method __init__ (line 185) | def __init__(self, transport):
    method close (line 192) | async def close(self):
    method connect (line 202) | async def connect(self, banner, rsa_keys, auth_timeout_s, auth_callbac...
    method read (line 310) | async def read(self, expected_cmds, adb_info, allow_zeros=False):
    method send (line 401) | async def send(self, msg, adb_info):
    method _read_expected_packet_from_device (line 415) | async def _read_expected_packet_from_device(self, expected_cmds, adb_i...
    method _read_bytes_from_device (line 454) | async def _read_bytes_from_device(self, length, adb_info):
    method _read_packet_from_device (line 496) | async def _read_packet_from_device(self, adb_info):
    method _send (line 540) | async def _send(self, msg, adb_info):
  class AdbDeviceAsync (line 564) | class AdbDeviceAsync(object):
    method __init__ (line 603) | def __init__(self, transport, default_transport_timeout_s=None, banner...
    method available (line 626) | def available(self):
    method max_chunk_size (line 638) | def max_chunk_size(self):
    method _get_transport_timeout_s (line 649) | def _get_transport_timeout_s(self, transport_timeout_s):
    method close (line 670) | async def close(self):
    method connect (line 677) | async def connect(self, rsa_keys=None, transport_timeout_s=None, auth_...
    method _service (line 723) | async def _service(self, service, command, transport_timeout_s=None, r...
    method _streaming_service (line 752) | async def _streaming_service(self, service, command, transport_timeout...
    method exec_out (line 783) | async def exec_out(self, command, transport_timeout_s=None, read_timeo...
    method reboot (line 813) | async def reboot(self, fastboot=False, transport_timeout_s=None, read_...
    method root (line 834) | async def root(self, transport_timeout_s=None, read_timeout_s=constant...
    method shell (line 855) | async def shell(self, command, transport_timeout_s=None, read_timeout_...
    method streaming_shell (line 883) | async def streaming_shell(self, command, transport_timeout_s=None, rea...
    method list (line 915) | async def list(self, device_path, transport_timeout_s=None, read_timeo...
    method pull (line 955) | async def pull(self, device_path, local_path, progress_callback=None, ...
    method _pull (line 987) | async def _pull(self, device_path, stream, progress_callback, adb_info...
    method push (line 1019) | async def push(self, local_path, device_path, st_mode=constants.DEFAUL...
    method _push (line 1060) | async def _push(self, stream, device_path, st_mode, mtime, progress_ca...
    method stat (line 1115) | async def stat(self, device_path, transport_timeout_s=None, read_timeo...
    method _clse (line 1156) | async def _clse(self, adb_info):
    method _okay (line 1174) | async def _okay(self, adb_info):
    method _open (line 1191) | async def _open(self, destination, transport_timeout_s, read_timeout_s...
    method _read_until (line 1229) | async def _read_until(self, expected_cmds, adb_info):
    method _read_until_close (line 1260) | async def _read_until_close(self, adb_info):
    method _streaming_command (line 1295) | async def _streaming_command(self, service, command, transport_timeout...
    method _filesync_flush (line 1337) | async def _filesync_flush(self, adb_info, filesync_info):
    method _filesync_read (line 1358) | async def _filesync_read(self, expected_ids, adb_info, filesync_info):
    method _filesync_read_buffered (line 1420) | async def _filesync_read_buffered(self, size, adb_info, filesync_info):
    method _filesync_read_until (line 1447) | async def _filesync_read_until(self, expected_ids, finish_ids, adb_inf...
    method _filesync_send (line 1480) | async def _filesync_send(self, command_id, adb_info, filesync_info, da...
  class AdbDeviceTcpAsync (line 1513) | class AdbDeviceTcpAsync(AdbDeviceAsync):
    method __init__ (line 1545) | def __init__(self, host, port=5555, default_transport_timeout_s=None, ...

FILE: adb_shell/adb_message.py
  function checksum (line 42) | def checksum(data):
  function int_to_cmd (line 75) | def int_to_cmd(n):
  function unpack (line 92) | def unpack(message):
  class AdbMessage (line 127) | class AdbMessage(object):
    method __init__ (line 155) | def __init__(self, command, arg0, arg1, data=b''):
    method pack (line 162) | def pack(self):
    method checksum (line 174) | def checksum(self):

FILE: adb_shell/auth/keygen.py
  function _to_bytes (line 84) | def _to_bytes(n, length, endianess='big'):
  function decode_pubkey (line 111) | def decode_pubkey(public_key):
  function decode_pubkey_file (line 132) | def decode_pubkey_file(public_key_path):
  function encode_pubkey (line 145) | def encode_pubkey(private_key_path):
  function get_user_info (line 188) | def get_user_info():
  function write_public_keyfile (line 212) | def write_public_keyfile(private_key_path, public_key_path):
  function keygen (line 232) | def keygen(filepath):

FILE: adb_shell/auth/sign_cryptography.py
  class CryptographySigner (line 35) | class CryptographySigner(object):
    method __init__ (line 51) | def __init__(self, rsa_key_path):
    method Sign (line 58) | def Sign(self, data):
    method GetPublicKey (line 74) | def GetPublicKey(self):

FILE: adb_shell/auth/sign_pycryptodome.py
  class PycryptodomeAuthSigner (line 32) | class PycryptodomeAuthSigner(object):
    method __init__ (line 48) | def __init__(self, rsa_key_path=None):
    method Sign (line 58) | def Sign(self, data):
    method GetPublicKey (line 75) | def GetPublicKey(self):

FILE: adb_shell/auth/sign_pythonrsa.py
  class _Accum (line 41) | class _Accum(object):
    method __init__ (line 54) | def __init__(self):
    method update (line 57) | def update(self, msg):
    method digest (line 68) | def digest(self):
  function _load_rsa_private_key (line 84) | def _load_rsa_private_key(pem):
  class PythonRSASigner (line 122) | class PythonRSASigner(object):
    method __init__ (line 140) | def __init__(self, pub=None, priv=None):
    method FromRSAKeyPath (line 145) | def FromRSAKeyPath(cls, rsa_key_path):
    method Sign (line 165) | def Sign(self, data):
    method GetPublicKey (line 181) | def GetPublicKey(self):

FILE: adb_shell/exceptions.py
  class AdbCommandFailureException (line 26) | class AdbCommandFailureException(Exception):
  class AdbConnectionError (line 32) | class AdbConnectionError(Exception):
  class AdbTimeoutError (line 38) | class AdbTimeoutError(Exception):
  class DeviceAuthError (line 44) | class DeviceAuthError(Exception):
    method __init__ (line 48) | def __init__(self, message, *args):
  class InvalidChecksumError (line 53) | class InvalidChecksumError(Exception):
  class InvalidCommandError (line 59) | class InvalidCommandError(Exception):
  class InvalidTransportError (line 65) | class InvalidTransportError(Exception):
  class InvalidResponseError (line 71) | class InvalidResponseError(Exception):
  class DevicePathInvalidError (line 77) | class DevicePathInvalidError(Exception):
  class PushFailedError (line 83) | class PushFailedError(Exception):
  class TcpTimeoutException (line 89) | class TcpTimeoutException(Exception):
  class UsbDeviceNotFoundError (line 95) | class UsbDeviceNotFoundError(Exception):
  class UsbReadFailedError (line 101) | class UsbReadFailedError(Exception):
    method __init__ (line 117) | def __init__(self, msg, usb_error):
    method __str__ (line 121) | def __str__(self):
  class UsbWriteFailedError (line 125) | class UsbWriteFailedError(Exception):

FILE: adb_shell/hidden_helpers.py
  function get_files_to_push (line 70) | def get_files_to_push(local_path, device_path):
  function get_banner (line 97) | def get_banner():
  class _AdbTransactionInfo (line 112) | class _AdbTransactionInfo(object):  # pylint: disable=too-few-public-met...
    method __init__ (line 157) | def __init__(self, local_id, remote_id, transport_timeout_s=None, read...
    method args_match (line 164) | def args_match(self, arg0, arg1, allow_zeros=False):
  class _FileSyncTransactionInfo (line 189) | class _FileSyncTransactionInfo(object):  # pylint: disable=too-few-publi...
    method __init__ (line 215) | def __init__(self, recv_message_format, maxdata=constants.MAX_ADB_DATA):
    method can_add_to_send_buffer (line 225) | def can_add_to_send_buffer(self, data_len):
  class _AdbPacketStore (line 243) | class _AdbPacketStore(object):
    method __init__ (line 259) | def __init__(self):
    method __contains__ (line 262) | def __contains__(self, value):
    method __len__ (line 280) | def __len__(self):
    method clear (line 291) | def clear(self, arg0, arg1):
    method clear_all (line 309) | def clear_all(self):
    method find (line 313) | def find(self, arg0, arg1):
    method find_allow_zeros (line 352) | def find_allow_zeros(self, arg0, arg1):
    method get (line 375) | def get(self, arg0, arg1):
    method put (line 411) | def put(self, arg0, arg1, cmd, data):

FILE: adb_shell/transport/base_transport.py
  class ABC (line 22) | class ABC(object):  # pylint: disable=too-few-public-methods
  class BaseTransport (line 29) | class BaseTransport(ABC):
    method close (line 35) | def close(self):
    method connect (line 41) | def connect(self, transport_timeout_s):
    method bulk_read (line 52) | def bulk_read(self, numbytes, transport_timeout_s):
    method bulk_write (line 70) | def bulk_write(self, data, transport_timeout_s):

FILE: adb_shell/transport/base_transport_async.py
  class BaseTransportAsync (line 20) | class BaseTransportAsync(ABC):
    method close (line 26) | async def close(self):
    method connect (line 32) | async def connect(self, transport_timeout_s):
    method bulk_read (line 43) | async def bulk_read(self, numbytes, transport_timeout_s):
    method bulk_write (line 61) | async def bulk_write(self, data, transport_timeout_s):

FILE: adb_shell/transport/tcp_transport.py
  class TcpTransport (line 40) | class TcpTransport(BaseTransport):
    method __init__ (line 60) | def __init__(self, host, port=5555):
    method close (line 66) | def close(self):
    method connect (line 79) | def connect(self, transport_timeout_s):
    method bulk_read (line 94) | def bulk_read(self, numbytes, transport_timeout_s):
    method bulk_write (line 122) | def bulk_write(self, data, transport_timeout_s):

FILE: adb_shell/transport/tcp_transport_async.py
  class TcpTransportAsync (line 25) | class TcpTransportAsync(BaseTransportAsync):
    method __init__ (line 47) | def __init__(self, host, port=5555):
    method close (line 54) | async def close(self):
    method connect (line 68) | async def connect(self, transport_timeout_s):
    method bulk_read (line 84) | async def bulk_read(self, numbytes, transport_timeout_s):
    method bulk_write (line 112) | async def bulk_write(self, data, transport_timeout_s):

FILE: adb_shell/transport/usb_transport.py
  function get_interface (line 78) | def get_interface(setting):  # pragma: no cover
  function interface_matcher (line 99) | def interface_matcher(clazz, subclass, protocol):   # pragma: no cover
  class UsbTransport (line 141) | class UsbTransport(BaseTransport):   # pragma: no cover
    method __init__ (line 188) | def __init__(self, device, setting, usb_info=None, default_transport_t...
    method close (line 201) | def close(self):
    method connect (line 215) | def connect(self, transport_timeout_s=None):
    method bulk_read (line 256) | def bulk_read(self, numbytes, transport_timeout_s=None):
    method bulk_write (line 287) | def bulk_write(self, data, transport_timeout_s=None):
    method _open (line 319) | def _open(self):
    method _timeout_ms (line 360) | def _timeout_ms(self, transport_timeout_s):
    method _flush_buffers (line 371) | def _flush_buffers(self):
    method port_path (line 394) | def port_path(self):
    method serial_number (line 406) | def serial_number(self):
    method usb_info (line 418) | def usb_info(self):
    method _port_path_matcher (line 441) | def _port_path_matcher(cls, port_path):
    method _serial_matcher (line 461) | def _serial_matcher(cls, serial):
    method _find (line 483) | def _find(cls, setting_matcher, port_path=None, serial=None, default_t...
    method _find_and_open (line 515) | def _find_and_open(cls, setting_matcher, port_path=None, serial=None, ...
    method _find_devices (line 541) | def _find_devices(cls, setting_matcher, device_matcher=None, usb_info=...
    method _find_first (line 573) | def _find_first(cls, setting_matcher, device_matcher=None, usb_info=''...
    method find_adb (line 606) | def find_adb(cls, serial=None, port_path=None, default_transport_timeo...
    method find_all_adb_devices (line 632) | def find_all_adb_devices(cls, default_transport_timeout_s=None):

FILE: tests/async_patchers.py
  class AsyncMock (line 17) | class AsyncMock(MagicMock):
    method __call__ (line 18) | async def __call__(self, *args, **kwargs):
  function async_mock_open (line 22) | def async_mock_open(read_data=""):
  class FakeStreamWriter (line 58) | class FakeStreamWriter:
    method close (line 59) | def close(self):
    method wait_closed (line 62) | async def wait_closed(self):
    method write (line 65) | def write(self, data):
    method drain (line 68) | async def drain(self):
  class FakeStreamReader (line 72) | class FakeStreamReader:
    method read (line 73) | async def read(self, numbytes):
  class FakeTcpTransportAsync (line 77) | class FakeTcpTransportAsync(TcpTransportAsync):
    method __init__ (line 78) | def __init__(self, *args, **kwargs):
    method close (line 83) | async def close(self):
    method connect (line 87) | async def connect(self, transport_timeout_s=None):
    method bulk_read (line 91) | async def bulk_read(self, numbytes, transport_timeout_s=None):
    method bulk_write (line 97) | async def bulk_write(self, data, transport_timeout_s=None):
  function async_patch (line 106) | def async_patch(*args, **kwargs):

FILE: tests/async_wrapper.py
  function _await (line 6) | def _await(coro):
  function awaiter (line 20) | def awaiter(func):

FILE: tests/filesync_helpers.py
  class FileSyncMessage (line 6) | class FileSyncMessage(object):  # pylint: disable=too-few-public-methods
    method __init__ (line 28) | def __init__(self, command, arg0=None, data=b''):
    method pack (line 33) | def pack(self):
  class FileSyncListMessage (line 45) | class FileSyncListMessage(object):  # pylint: disable=too-few-public-met...
    method __init__ (line 77) | def __init__(self, command, arg0, arg1, arg2, data=b''):
    method pack (line 85) | def pack(self):
  class FileSyncStatMessage (line 97) | class FileSyncStatMessage(object):  # pylint: disable=too-few-public-met...
    method __init__ (line 125) | def __init__(self, command, arg0, arg1, arg2):
    method pack (line 132) | def pack(self):

FILE: tests/keygen_stub.py
  class FileReadWrite (line 8) | class FileReadWrite(object):
    method __init__ (line 10) | def __init__(self):
    method read (line 14) | def read(self):
    method write (line 25) | def write(self, content):
  function open_priv_pub (line 34) | def open_priv_pub(infile, mode='r'):

FILE: tests/patchers.py
  function mock_open (line 33) | def mock_open(read_data=""):
  class FakeSocket (line 68) | class FakeSocket(object):
    method __init__ (line 69) | def __init__(self):
    method close (line 72) | def close(self):
    method recv (line 75) | def recv(self, bufsize):
    method send (line 80) | def send(self, data):
    method setblocking (line 83) | def setblocking(self, *args, **kwargs):
    method shutdown (line 86) | def shutdown(self, how):
  class FakeTcpTransport (line 90) | class FakeTcpTransport(TcpTransport):
    method __init__ (line 91) | def __init__(self, *args, **kwargs):
    method close (line 96) | def close(self):
    method connect (line 99) | def connect(self, transport_timeout_s=None):
    method bulk_read (line 102) | def bulk_read(self, numbytes, transport_timeout_s=None):
    method bulk_write (line 108) | def bulk_write(self, data, transport_timeout_s=None):

FILE: tests/test_adb_device.py
  function to_int (line 31) | def to_int(cmd):
  function join_messages (line 34) | def join_messages(*messages):
  class AdbMessageForTesting (line 38) | class AdbMessageForTesting(AdbMessage):
    method __init__ (line 39) | def __init__(self, command, arg0=None, arg1=None, data=b''):
  class TestAdbDevice (line 47) | class TestAdbDevice(unittest.TestCase):
    method setUp (line 48) | def setUp(self):
    method tearDown (line 60) | def tearDown(self):
    method fake_stat (line 65) | def fake_stat(*args, **kwargs):
    method test_no_async_references (line 68) | def test_no_async_references(self):
    method test_adb_connection_error (line 79) | def test_adb_connection_error(self):
    method test_init_tcp (line 112) | def test_init_tcp(self):
    method test_init_banner (line 124) | def test_init_banner(self):
    method test_init_invalid_transport (line 139) | def test_init_invalid_transport(self):
    method test_available (line 146) | def test_available(self):
    method test_close (line 152) | def test_close(self):
    method test_connect (line 164) | def test_connect(self):
    method test_connect_no_keys (line 168) | def test_connect_no_keys(self):
    method test_connect_with_key_invalid_response (line 175) | def test_connect_with_key_invalid_response(self):
    method test_connect_with_key (line 187) | def test_connect_with_key(self):
    method test_connect_with_new_key (line 196) | def test_connect_with_new_key(self):
    method test_connect_with_new_key_and_callback (line 206) | def test_connect_with_new_key_and_callback(self):
    method test_connect_timeout (line 221) | def test_connect_timeout(self):
    method test_shell_no_return (line 233) | def test_shell_no_return(self):
    method test_shell_return_pass (line 242) | def test_shell_return_pass(self):
    method test_shell_local_id_wraparound (line 253) | def test_shell_local_id_wraparound(self):
    method test_shell_return_pass_with_unexpected_packet (line 268) | def test_shell_return_pass_with_unexpected_packet(self):
    method test_shell_dont_decode (line 280) | def test_shell_dont_decode(self):
    method test_shell_avoid_decode_error (line 291) | def test_shell_avoid_decode_error(self):
    method test_shell_data_length_exceeds_max (line 302) | def test_shell_data_length_exceeds_max(self):
    method test_shell_multibytes_sequence_exceeds_max (line 313) | def test_shell_multibytes_sequence_exceeds_max(self):
    method test_shell_with_multibytes_sequence_over_two_messages (line 323) | def test_shell_with_multibytes_sequence_over_two_messages(self):
    method test_shell_multiple_clse (line 334) | def test_shell_multiple_clse(self):
    method test_shell_multiple_streams (line 357) | def test_shell_multiple_streams(self):
    method test_shell_multiple_streams2 (line 373) | def test_shell_multiple_streams2(self):
    method test_shell_local_id2 (line 394) | def test_shell_local_id2(self):
    method test_shell_remote_id2 (line 408) | def test_shell_remote_id2(self):
    method test_shell_error_local_id_timeout (line 427) | def test_shell_error_local_id_timeout(self):
    method test_shell_error_unknown_command (line 440) | def test_shell_error_unknown_command(self):
    method test_shell_error_transport_timeout (line 449) | def test_shell_error_transport_timeout(self):
    method test_shell_error_read_timeout_multiple_clse (line 458) | def test_shell_error_read_timeout_multiple_clse(self):
    method test_shell_error_timeout (line 468) | def test_shell_error_timeout(self):
    method test_shell_error_checksum (line 488) | def test_shell_error_checksum(self):
    method test_issue29 (line 499) | def test_issue29(self):
    method test_streaming_shell_decode (line 581) | def test_streaming_shell_decode(self):
    method test_streaming_shell_dont_decode (line 595) | def test_streaming_shell_dont_decode(self):
    method test_reboot (line 615) | def test_reboot(self):
    method test_root (line 628) | def test_root(self):
    method test_exec_out (line 641) | def test_exec_out(self):
    method test_list (line 658) | def test_list(self):
    method test_list_empty_path (line 682) | def test_list_empty_path(self):
    method test_push_fail (line 694) | def test_push_fail(self):
    method test_push_file (line 709) | def test_push_file(self):
    method test_push_bytesio (line 737) | def test_push_bytesio(self):
    method test_push_file_exception (line 762) | def test_push_file_exception(self):
    method test_push_file_mtime0 (line 792) | def test_push_file_mtime0(self):
    method test_push_big_file (line 817) | def test_push_big_file(self):
    method test_push_dir (line 854) | def test_push_dir(self):
    method test_push_empty_path (line 878) | def test_push_empty_path(self):
    method test_pull_file (line 890) | def test_pull_file(self):
    method test_pull_bytesio (line 918) | def test_pull_bytesio(self):
    method test_pull_file_exception (line 943) | def test_pull_file_exception(self):
    method test_pull_big_file (line 972) | def test_pull_big_file(self):
    method test_pull_empty_path (line 1000) | def test_pull_empty_path(self):
    method test_pull_non_existant_path (line 1012) | def test_pull_non_existant_path(self):
    method test_pull_non_existant_path_2 (line 1033) | def test_pull_non_existant_path_2(self):
    method test_stat (line 1052) | def test_stat(self):
    method test_stat_empty_path (line 1072) | def test_stat_empty_path(self):
    method test_stat_issue155 (line 1084) | def test_stat_issue155(self):
    method test_filesync_read_adb_command_failure_exceptions (line 1108) | def test_filesync_read_adb_command_failure_exceptions(self):
    method test_filesync_read_invalid_response_error (line 1121) | def test_filesync_read_invalid_response_error(self):

FILE: tests/test_adb_device_async.py
  function to_int (line 28) | def to_int(cmd):
  function join_messages (line 31) | def join_messages(*messages):
  class AdbMessageForTesting (line 35) | class AdbMessageForTesting(AdbMessage):
    method __init__ (line 36) | def __init__(self, command, arg0=None, arg1=None, data=b''):
  class TestAdbDeviceAsync (line 45) | class TestAdbDeviceAsync(unittest.TestCase):
    method setUp (line 46) | def setUp(self):
    method tearDown (line 58) | def tearDown(self):
    method fake_stat (line 63) | async def fake_stat(*args, **kwargs):
    method test_no_sync_references (line 66) | def test_no_sync_references(self):
    method test_adb_connection_error (line 76) | async def test_adb_connection_error(self):
    method test_init_tcp (line 111) | async def test_init_tcp(self):
    method test_init_banner (line 125) | async def test_init_banner(self):
    method test_init_invalid_transport (line 141) | async def test_init_invalid_transport(self):
    method test_available (line 149) | async def test_available(self):
    method test_close (line 156) | async def test_close(self):
    method test_connect (line 169) | async def test_connect(self):
    method test_connect_no_keys (line 174) | async def test_connect_no_keys(self):
    method test_connect_with_key_invalid_response (line 182) | async def test_connect_with_key_invalid_response(self):
    method test_connect_with_key (line 195) | async def test_connect_with_key(self):
    method test_connect_with_new_key (line 205) | async def test_connect_with_new_key(self):
    method test_connect_with_new_key_and_callback (line 216) | async def test_connect_with_new_key_and_callback(self):
    method test_connect_timeout (line 232) | async def test_connect_timeout(self):
    method test_shell_no_return (line 245) | async def test_shell_no_return(self):
    method test_shell_return_pass (line 255) | async def test_shell_return_pass(self):
    method test_shell_local_id_wraparound (line 267) | async def test_shell_local_id_wraparound(self):
    method test_shell_return_pass_with_unexpected_packet (line 283) | async def test_shell_return_pass_with_unexpected_packet(self):
    method test_shell_dont_decode (line 296) | async def test_shell_dont_decode(self):
    method test_shell_avoid_decode_error (line 308) | async def test_shell_avoid_decode_error(self):
    method test_shell_data_length_exceeds_max (line 319) | async def test_shell_data_length_exceeds_max(self):
    method test_shell_multibytes_sequence_exceeds_max (line 331) | async def test_shell_multibytes_sequence_exceeds_max(self):
    method test_shell_with_multibytes_sequence_over_two_messages (line 342) | async def test_shell_with_multibytes_sequence_over_two_messages(self):
    method test_shell_multiple_clse (line 354) | async def test_shell_multiple_clse(self):
    method test_shell_multiple_streams (line 378) | async def test_shell_multiple_streams(self):
    method test_shell_multiple_streams2 (line 395) | async def test_shell_multiple_streams2(self):
    method test_shell_local_id2 (line 417) | async def test_shell_local_id2(self):
    method test_shell_remote_id2 (line 432) | async def test_shell_remote_id2(self):
    method test_shell_error_local_id_timeout (line 453) | async def test_shell_error_local_id_timeout(self):
    method test_shell_error_unknown_command (line 467) | async def test_shell_error_unknown_command(self):
    method test_shell_error_transport_timeout (line 477) | async def test_shell_error_transport_timeout(self):
    method test_shell_error_read_timeout_multiple_clse (line 487) | async def test_shell_error_read_timeout_multiple_clse(self):
    method test_shell_error_timeout (line 498) | async def test_shell_error_timeout(self):
    method test_shell_error_checksum (line 519) | async def test_shell_error_checksum(self):
    method test_issue29 (line 531) | async def test_issue29(self):
    method test_streaming_shell_decode (line 614) | async def test_streaming_shell_decode(self):
    method test_streaming_shell_dont_decode (line 629) | async def test_streaming_shell_dont_decode(self):
    method test_reboot (line 650) | async def test_reboot(self):
    method test_root (line 664) | async def test_root(self):
    method test_exec_out (line 678) | async def test_exec_out(self):
    method test_list (line 697) | async def test_list(self):
    method test_list_empty_path (line 722) | async def test_list_empty_path(self):
    method test_push_fail (line 736) | async def test_push_fail(self):
    method test_push_file (line 753) | async def test_push_file(self):
    method test_push_bytesio (line 783) | async def test_push_bytesio(self):
    method test_push_file_exception (line 810) | async def test_push_file_exception(self):
    method test_push_file_mtime0 (line 842) | async def test_push_file_mtime0(self):
    method test_push_big_file (line 869) | async def test_push_big_file(self):
    method test_push_dir (line 908) | async def test_push_dir(self):
    method test_push_empty_path (line 933) | async def test_push_empty_path(self):
    method test_pull_file (line 947) | async def test_pull_file(self):
    method test_pull_bytesio (line 977) | async def test_pull_bytesio(self):
    method test_pull_file_exception (line 1004) | async def test_pull_file_exception(self):
    method test_pull_big_file (line 1035) | async def test_pull_big_file(self):
    method test_pull_empty_path (line 1064) | async def test_pull_empty_path(self):
    method test_pull_non_existant_path (line 1077) | async def test_pull_non_existant_path(self):
    method test_pull_non_existant_path_2 (line 1099) | async def test_pull_non_existant_path_2(self):
    method test_stat (line 1119) | async def test_stat(self):
    method test_stat_empty_path (line 1140) | async def test_stat_empty_path(self):
    method test_stat_issue155 (line 1153) | async def test_stat_issue155(self):
    method test_filesync_read_adb_command_failure_exceptions (line 1178) | async def test_filesync_read_adb_command_failure_exceptions(self):
    method test_filesync_read_invalid_response_error (line 1192) | async def test_filesync_read_invalid_response_error(self):

FILE: tests/test_adb_message.py
  class TestAdbMessage (line 14) | class TestAdbMessage(unittest.TestCase):
    method test_checksum_bytearray (line 15) | def test_checksum_bytearray(self):
    method test_checksum_bytes (line 19) | def test_checksum_bytes(self):
    method test_checksum_unicode (line 23) | def test_checksum_unicode(self):
    method test_unpack_error (line 27) | def test_unpack_error(self):
    method test_constants (line 31) | def test_constants(self):

FILE: tests/test_exceptions.py
  class TestExceptionSerialization (line 24) | class TestExceptionSerialization(unittest.TestCase):
    method __test_serialize_one_exc_cls (line 25) | def __test_serialize_one_exc_cls(exc_cls):
    method test_usbreadfailederror_as_str (line 52) | def test_usbreadfailederror_as_str(self):
    method test_usbreadfailederror_as_repr (line 58) | def test_usbreadfailederror_as_repr(self):

FILE: tests/test_hidden_helpers.py
  class TestAdbPacketStore (line 7) | class TestAdbPacketStore(unittest.TestCase):
    method setUp (line 8) | def setUp(self):
    method test_init (line 11) | def test_init(self):
    method test_contains (line 15) | def test_contains(self):
    method test_put (line 27) | def test_put(self):
    method test_get (line 52) | def test_get(self):
    method test_get_clse (line 116) | def test_get_clse(self):
    method test_clear (line 141) | def test_clear(self):
    method test_clear_all (line 159) | def test_clear_all(self):
    method test_find_allow_zeros (line 166) | def test_find_allow_zeros(self):
  class TestAdbTransactionInfo (line 172) | class TestAdbTransactionInfo(unittest.TestCase):
    method test_args_match (line 174) | def test_args_match(self):

FILE: tests/test_keygen.py
  class TestKeygen (line 11) | class TestKeygen(unittest.TestCase):
    method test_get_user_info (line 12) | def test_get_user_info(self):

FILE: tests/test_sign_cryptography.py
  class TestCryptographySigner (line 18) | class TestCryptographySigner(unittest.TestCase):
    method setUp (line 19) | def setUp(self):
    method test_sign (line 24) | def test_sign(self):
    method test_get_public_key (line 36) | def test_get_public_key(self):

FILE: tests/test_sign_pycryptodome.py
  class TestPycryptodomeAuthSigner (line 15) | class TestPycryptodomeAuthSigner(unittest.TestCase):
    method setUp (line 16) | def setUp(self):
    method test_sign (line 21) | def test_sign(self):
    method test_get_public_key (line 26) | def test_get_public_key(self):

FILE: tests/test_sign_pythonrsa.py
  class TestPythonRSASigner (line 15) | class TestPythonRSASigner(unittest.TestCase):
    method setUp (line 16) | def setUp(self):
    method test_sign (line 21) | def test_sign(self):
    method test_get_public_key (line 26) | def test_get_public_key(self):
  class TestPythonRSASignerExceptions (line 35) | class TestPythonRSASignerExceptions(unittest.TestCase):
    method test_value_error (line 36) | def test_value_error(self):
    method test_index_error (line 43) | def test_index_error(self):

FILE: tests/test_tcp_transport.py
  class TestTcpTransport (line 14) | class TestTcpTransport(unittest.TestCase):
    method setUp (line 15) | def setUp(self):
    method tearDown (line 23) | def tearDown(self):
    method test_connect_with_timeout (line 27) | def test_connect_with_timeout(self):
    method test_bulk_read (line 36) | def test_bulk_read(self):
    method test_close_oserror (line 51) | def test_close_oserror(self):
    method test_bulk_write (line 58) | def test_bulk_write(self):

FILE: tests/test_tcp_transport_async.py
  class TestTcpTransportAsync (line 14) | class TestTcpTransportAsync(unittest.TestCase):
    method setUp (line 15) | def setUp(self):
    method test_close (line 22) | async def test_close(self):
    method test_close2 (line 26) | async def test_close2(self):
    method test_connect (line 30) | async def test_connect(self):
    method test_connect_close (line 35) | async def test_connect_close(self):
    method test_connect_close_catch_oserror (line 45) | async def test_connect_close_catch_oserror(self):
    method test_connect_with_timeout (line 56) | async def test_connect_with_timeout(self):
    method test_bulk_read (line 62) | async def test_bulk_read(self):
    method test_bulk_write (line 73) | async def test_bulk_write(self):

FILE: tests/test_usb_importerror.py
  class TestUsbImportError (line 14) | class TestUsbImportError(unittest.TestCase):
    method test_import_error (line 15) | def test_import_error(self):
    method test_import_successful (line 28) | def test_import_successful(self):

FILE: tests/test_usb_transport.py
  class TestUsbTransport (line 17) | class TestUsbTransport(unittest.TestCase):
    method setUp (line 18) | def setUp(self):
    method tearDown (line 30) | def tearDown(self):
    method test_connect_with_timeout (line 34) | def test_connect_with_timeout(self):
    method test_bulk_read (line 46) | def test_bulk_read(self):
    method test_bulk_write (line 64) | def test_bulk_write(self):
Condensed preview — 82 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (475K chars).
[
  {
    "path": ".flake8",
    "chars": 28,
    "preview": "[flake8]\nignore = E501,W504\n"
  },
  {
    "path": ".gitattributes",
    "chars": 43,
    "preview": "Makefile eol=lf\n*.sh eol=lf\n*.ipynb eol=lf\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "chars": 572,
    "preview": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n### Description\n"
  },
  {
    "path": ".github/workflows/python-package.yml",
    "chars": 1262,
    "preview": "# This workflow will install Python dependencies, run tests and lint with a variety of Python versions\n# For more inform"
  },
  {
    "path": ".gitignore",
    "chars": 162,
    "preview": "# Python files\n*.idea\n*.pyc\n**/__pycache__/\nadb_shell.egg-info\n\n# Build files\nbuild/\ndist/\n\n# Documentation\ndocs/build/\n"
  },
  {
    "path": ".pylintrc",
    "chars": 15501,
    "preview": "[MASTER]\n\n# A comma-separated list of package or module names from where C extensions may\n# be loaded. Extensions are lo"
  },
  {
    "path": ".readthedocs.yml",
    "chars": 556,
    "preview": "# .readthedocs.yml\n# Read the Docs configuration file\n# See https://docs.readthedocs.io/en/stable/config-file/v2.html fo"
  },
  {
    "path": ".travis.yml",
    "chars": 1846,
    "preview": "language: python\npython:\n  - \"2.7\"\n  - \"3.5\"\n  - \"3.6\"\n  - \"3.7\"\n  - \"3.8\"\n  - \"3.9\"\naddons:\n  apt:\n    packages:\n      "
  },
  {
    "path": "LICENSE",
    "chars": 10174,
    "preview": "\n                                 Apache License\n                           Version 2.0, January 2004\n                  "
  },
  {
    "path": "MANIFEST.in",
    "chars": 16,
    "preview": "include LICENSE\n"
  },
  {
    "path": "Makefile",
    "chars": 7425,
    "preview": "#-------------------- ONLY MODIFY CODE IN THIS SECTION --------------------#\nPACKAGE_DIR := adb_shell\nTEST_DIR := tests\n"
  },
  {
    "path": "README.rst",
    "chars": 2370,
    "preview": "adb\\_shell\n==========\n\n.. image:: https://travis-ci.com/JeffLIrion/adb_shell.svg?branch=master\n   :target: https://travi"
  },
  {
    "path": "adb_shell/__init__.py",
    "chars": 155,
    "preview": "# Copyright (c) 2021 Jeff Irion and contributors\n#\n# This file is part of the adb-shell package.\n\n\"\"\"ADB shell functiona"
  },
  {
    "path": "adb_shell/adb_device.py",
    "chars": 65035,
    "preview": "# Copyright (c) 2021 Jeff Irion and contributors\n#\n# This file is part of the adb-shell package.  It incorporates work\n#"
  },
  {
    "path": "adb_shell/adb_device_async.py",
    "chars": 65953,
    "preview": "# Copyright (c) 2021 Jeff Irion and contributors\n#\n# This file is part of the adb-shell package.  It incorporates work\n#"
  },
  {
    "path": "adb_shell/adb_message.py",
    "chars": 5623,
    "preview": "# Copyright (c) 2021 Jeff Irion and contributors\n#\n# This file is part of the adb-shell package.  It incorporates work\n#"
  },
  {
    "path": "adb_shell/auth/__init__.py",
    "chars": 97,
    "preview": "# Copyright (c) 2021 Jeff Irion and contributors\n#\n# This file is part of the adb-shell package.\n"
  },
  {
    "path": "adb_shell/auth/keygen.py",
    "chars": 7064,
    "preview": "# Copyright (c) 2021 Jeff Irion and contributors\n#\n# This file is part of the adb-shell package.  It was originally writ"
  },
  {
    "path": "adb_shell/auth/sign_cryptography.py",
    "chars": 2405,
    "preview": "# Copyright 2014 Google Inc. All rights reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# "
  },
  {
    "path": "adb_shell/auth/sign_pycryptodome.py",
    "chars": 2262,
    "preview": "# Copyright 2014 Google Inc. All rights reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# "
  },
  {
    "path": "adb_shell/auth/sign_pythonrsa.py",
    "chars": 4879,
    "preview": "# Copyright 2014 Google Inc. All rights reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# "
  },
  {
    "path": "adb_shell/constants.py",
    "chars": 3961,
    "preview": "# Copyright (c) 2021 Jeff Irion and contributors\n#\n# This file is part of the adb-shell package.  It incorporates work\n#"
  },
  {
    "path": "adb_shell/exceptions.py",
    "chars": 2847,
    "preview": "# Copyright (c) 2021 Jeff Irion and contributors\n#\n# This file is part of the adb-shell package.  It incorporates work\n#"
  },
  {
    "path": "adb_shell/hidden_helpers.py",
    "chars": 16491,
    "preview": "# Copyright (c) 2021 Jeff Irion and contributors\n#\n# This file is part of the adb-shell package.  It incorporates work\n#"
  },
  {
    "path": "adb_shell/transport/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "adb_shell/transport/base_transport.py",
    "chars": 1817,
    "preview": "# Copyright (c) 2021 Jeff Irion and contributors\n#\n# This file is part of the adb-shell package.\n\n\"\"\"A base class for tr"
  },
  {
    "path": "adb_shell/transport/base_transport_async.py",
    "chars": 1622,
    "preview": "# Copyright (c) 2021 Jeff Irion and contributors\n#\n# This file is part of the adb-shell package.\n\n\"\"\"A base class for tr"
  },
  {
    "path": "adb_shell/transport/tcp_transport.py",
    "chars": 4595,
    "preview": "# Copyright (c) 2021 Jeff Irion and contributors\n#\n# This file is part of the adb-shell package.  It incorporates work\n#"
  },
  {
    "path": "adb_shell/transport/tcp_transport_async.py",
    "chars": 4258,
    "preview": "# Copyright (c) 2021 Jeff Irion and contributors\n#\n# This file is part of the adb-shell package.\n\n\"\"\"A class for creatin"
  },
  {
    "path": "adb_shell/transport/usb_transport.py",
    "chars": 20235,
    "preview": "# Copyright (c) 2021 Jeff Irion and contributors\n#\n# This file is part of the adb-shell package.  It incorporates work\n#"
  },
  {
    "path": "docs/Makefile",
    "chars": 610,
    "preview": "# Minimal makefile for Sphinx documentation\n#\n\n# You can set these variables from the command line.\nSPHINXOPTS    =\nSPHI"
  },
  {
    "path": "docs/make.bat",
    "chars": 781,
    "preview": "@ECHO OFF\n\npushd %~dp0\n\nREM Command file for Sphinx documentation\n\nif \"%SPHINXBUILD%\" == \"\" (\n\tset SPHINXBUILD=sphinx-bu"
  },
  {
    "path": "docs/requirements.txt",
    "chars": 124,
    "preview": "# Standard requirements\nsphinx\nsphinx-rtd-theme\n\n# Specific requirements for this project\nadb-shell[async,usb]\npycryptod"
  },
  {
    "path": "docs/source/adb_shell.adb_device.rst",
    "chars": 152,
    "preview": "adb\\_shell.adb\\_device module\n=============================\n\n.. automodule:: adb_shell.adb_device\n   :members:\n   :undoc"
  },
  {
    "path": "docs/source/adb_shell.adb_device_async.rst",
    "chars": 172,
    "preview": "adb\\_shell.adb\\_device\\_async module\n====================================\n\n.. automodule:: adb_shell.adb_device_async\n  "
  },
  {
    "path": "docs/source/adb_shell.adb_message.rst",
    "chars": 155,
    "preview": "adb\\_shell.adb\\_message module\n==============================\n\n.. automodule:: adb_shell.adb_message\n   :members:\n   :un"
  },
  {
    "path": "docs/source/adb_shell.auth.keygen.rst",
    "chars": 153,
    "preview": "adb\\_shell.auth.keygen module\n=============================\n\n.. automodule:: adb_shell.auth.keygen\n   :members:\n   :undo"
  },
  {
    "path": "docs/source/adb_shell.auth.rst",
    "chars": 335,
    "preview": "adb\\_shell.auth package\n=======================\n\nSubmodules\n----------\n\n.. toctree::\n\n   adb_shell.auth.keygen\n   adb_sh"
  },
  {
    "path": "docs/source/adb_shell.auth.sign_cryptography.rst",
    "chars": 188,
    "preview": "adb\\_shell.auth.sign\\_cryptography module\n=========================================\n\n.. automodule:: adb_shell.auth.sign"
  },
  {
    "path": "docs/source/adb_shell.auth.sign_pycryptodome.rst",
    "chars": 188,
    "preview": "adb\\_shell.auth.sign\\_pycryptodome module\n=========================================\n\n.. automodule:: adb_shell.auth.sign"
  },
  {
    "path": "docs/source/adb_shell.auth.sign_pythonrsa.rst",
    "chars": 179,
    "preview": "adb\\_shell.auth.sign\\_pythonrsa module\n======================================\n\n.. automodule:: adb_shell.auth.sign_pytho"
  },
  {
    "path": "docs/source/adb_shell.constants.rst",
    "chars": 147,
    "preview": "adb\\_shell.constants module\n===========================\n\n.. automodule:: adb_shell.constants\n   :members:\n   :undoc-memb"
  },
  {
    "path": "docs/source/adb_shell.exceptions.rst",
    "chars": 150,
    "preview": "adb\\_shell.exceptions module\n============================\n\n.. automodule:: adb_shell.exceptions\n   :members:\n   :undoc-m"
  },
  {
    "path": "docs/source/adb_shell.hidden_helpers.rst",
    "chars": 164,
    "preview": "adb\\_shell.hidden\\_helpers module\n=================================\n\n.. automodule:: adb_shell.hidden_helpers\n   :member"
  },
  {
    "path": "docs/source/adb_shell.rst",
    "chars": 425,
    "preview": "adb\\_shell package\n==================\n\nSubpackages\n-----------\n\n.. toctree::\n\n   adb_shell.auth\n   adb_shell.transport\n\n"
  },
  {
    "path": "docs/source/adb_shell.transport.base_transport.rst",
    "chars": 194,
    "preview": "adb\\_shell.transport.base\\_transport module\n===========================================\n\n.. automodule:: adb_shell.trans"
  },
  {
    "path": "docs/source/adb_shell.transport.base_transport_async.rst",
    "chars": 214,
    "preview": "adb\\_shell.transport.base\\_transport\\_async module\n==================================================\n\n.. automodule:: a"
  },
  {
    "path": "docs/source/adb_shell.transport.rst",
    "chars": 419,
    "preview": "adb\\_shell.transport package\n============================\n\nSubmodules\n----------\n\n.. toctree::\n\n   adb_shell.transport.b"
  },
  {
    "path": "docs/source/adb_shell.transport.tcp_transport.rst",
    "chars": 191,
    "preview": "adb\\_shell.transport.tcp\\_transport module\n==========================================\n\n.. automodule:: adb_shell.transpo"
  },
  {
    "path": "docs/source/adb_shell.transport.tcp_transport_async.rst",
    "chars": 211,
    "preview": "adb\\_shell.transport.tcp\\_transport\\_async module\n=================================================\n\n.. automodule:: adb"
  },
  {
    "path": "docs/source/adb_shell.transport.usb_transport.rst",
    "chars": 191,
    "preview": "adb\\_shell.transport.usb\\_transport module\n==========================================\n\n.. automodule:: adb_shell.transpo"
  },
  {
    "path": "docs/source/conf.py",
    "chars": 5306,
    "preview": "# -*- coding: utf-8 -*-\n#\n# Configuration file for the Sphinx documentation builder.\n#\n# This file does only contain a s"
  },
  {
    "path": "docs/source/index.rst",
    "chars": 482,
    "preview": ".. Jeff Irion's Python package documentation master file, created by\n   sphinx-quickstart on Mon Sep 05 22:06:10 2016.\n "
  },
  {
    "path": "docs/source/modules.rst",
    "chars": 64,
    "preview": "adb_shell\n=========\n\n.. toctree::\n   :maxdepth: 4\n\n   adb_shell\n"
  },
  {
    "path": "scripts/bumpversion.sh",
    "chars": 914,
    "preview": "#!/bin/bash\n\n# Make sure there is only 1 argument passed\nif [ \"$#\" -ne 1 ]; then\n    echo \"You must provide a new versio"
  },
  {
    "path": "scripts/get_package_name.sh",
    "chars": 450,
    "preview": "#!/bin/bash\n\nset -e\n\n# get the directory of this script\nDIR=\"$( cd \"$( dirname \"${BASH_SOURCE[0]}\" )\" >/dev/null 2>&1 &&"
  },
  {
    "path": "scripts/get_version.sh",
    "chars": 525,
    "preview": "#!/bin/bash\n\nset -e\n\n# get the directory of this script\nDIR=\"$( cd \"$( dirname \"${BASH_SOURCE[0]}\" )\" >/dev/null 2>&1 &&"
  },
  {
    "path": "scripts/git_retag.sh",
    "chars": 431,
    "preview": "#!/bin/bash\n\n# get the directory of this script\nDIR=\"$( cd \"$( dirname \"${BASH_SOURCE[0]}\" )\" >/dev/null 2>&1 && pwd )\"\n"
  },
  {
    "path": "scripts/git_tag.sh",
    "chars": 290,
    "preview": "#!/bin/bash\n\n# get the directory of this script\nDIR=\"$( cd \"$( dirname \"${BASH_SOURCE[0]}\" )\" >/dev/null 2>&1 && pwd )\"\n"
  },
  {
    "path": "scripts/pre-commit.sh",
    "chars": 2106,
    "preview": "#!/bin/bash\n\nset -e\n\nfunction make_pre_commit() {\n  # setup pre-commit hook\n  DIR=\"$( cd \"$( dirname \"${BASH_SOURCE[0]}\""
  },
  {
    "path": "scripts/rename_package.sh",
    "chars": 950,
    "preview": "#!/bin/bash\n\nset -e\n\n# Make sure there is only 1 argument passed\nif [ \"$#\" -ne 1 ]; then\n    echo \"You must provide a ne"
  },
  {
    "path": "setup.py",
    "chars": 1000,
    "preview": "\"\"\"setup.py file for the adb_shell package.\"\"\"\n\nfrom setuptools import setup\n\nwith open(\"README.rst\") as f:\n    readme ="
  },
  {
    "path": "tests/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "tests/async_patchers.py",
    "chars": 2800,
    "preview": "try:\n    from contextlib import asynccontextmanager\nexcept ImportError:\n    asynccontextmanager = lambda func: func\n\nfro"
  },
  {
    "path": "tests/async_wrapper.py",
    "chars": 485,
    "preview": "import asyncio\nimport warnings\n\n\n\ndef _await(coro):\n    \"\"\"Create a new event loop, run the coroutine, then close the ev"
  },
  {
    "path": "tests/filesync_helpers.py",
    "chars": 3392,
    "preview": "import struct\n\nfrom adb_shell import constants\n\n\nclass FileSyncMessage(object):  # pylint: disable=too-few-public-method"
  },
  {
    "path": "tests/keygen_stub.py",
    "chars": 1005,
    "preview": "from contextlib import contextmanager\ntry:\n    from unittest.mock import patch\nexcept ImportError:\n    from mock import "
  },
  {
    "path": "tests/patchers.py",
    "chars": 4195,
    "preview": "from collections import namedtuple\nfrom contextlib import contextmanager\nimport sys\nimport unittest\n\ntry:\n    from unitt"
  },
  {
    "path": "tests/test_adb_device.py",
    "chars": 73139,
    "preview": "import inspect\nimport logging\nfrom io import BytesIO\nimport struct\nimport sys\nimport time\nimport unittest\n\ntry:\n    from"
  },
  {
    "path": "tests/test_adb_device_async.py",
    "chars": 75481,
    "preview": "import asyncio\nimport inspect\nimport logging\nfrom io import BytesIO\nimport sys\nimport unittest\nfrom unittest.mock import"
  },
  {
    "path": "tests/test_adb_message.py",
    "chars": 878,
    "preview": "import os\nimport unittest\n\ntry:\n    from unittest.mock import patch\nexcept ImportError:\n    from mock import patch\n\nfrom"
  },
  {
    "path": "tests/test_exceptions.py",
    "chars": 2529,
    "preview": "import functools\nimport inspect\nimport pickle\nimport re\nimport unittest\ntry:\n    from unittest import mock\nexcept Import"
  },
  {
    "path": "tests/test_hidden_helpers.py",
    "chars": 9858,
    "preview": "import unittest\n\nfrom adb_shell import constants\nfrom adb_shell.hidden_helpers import _AdbPacketStore, _AdbTransactionIn"
  },
  {
    "path": "tests/test_keygen.py",
    "chars": 716,
    "preview": "import unittest\n\ntry:\n    from unittest.mock import patch\nexcept ImportError:\n    from mock import patch\n\nfrom adb_shell"
  },
  {
    "path": "tests/test_sign_cryptography.py",
    "chars": 1488,
    "preview": "import os\nimport unittest\n\ntry:\n    from unittest.mock import patch\nexcept ImportError:\n    from mock import patch\n\nfrom"
  },
  {
    "path": "tests/test_sign_pycryptodome.py",
    "chars": 1050,
    "preview": "import os\nimport unittest\n\ntry:\n    from unittest.mock import patch\nexcept ImportError:\n    from mock import patch\n\nfrom"
  },
  {
    "path": "tests/test_sign_pythonrsa.py",
    "chars": 1979,
    "preview": "import os\nimport unittest\n\ntry:\n    from unittest.mock import patch\nexcept ImportError:\n    from mock import patch\n\nfrom"
  },
  {
    "path": "tests/test_tcp_transport.py",
    "chars": 2042,
    "preview": "import unittest\n\ntry:\n    from unittest.mock import patch\nexcept ImportError:\n    from mock import patch\n\nfrom adb_shell"
  },
  {
    "path": "tests/test_tcp_transport_async.py",
    "chars": 3246,
    "preview": "import asyncio\nimport unittest\nfrom unittest.mock import patch\n\nfrom adb_shell.exceptions import TcpTimeoutException\nfro"
  },
  {
    "path": "tests/test_usb_importerror.py",
    "chars": 1285,
    "preview": "import unittest\n\ntry:\n    from unittest.mock import patch\nexcept ImportError:\n    from mock import patch\n\ntry:\n    from "
  },
  {
    "path": "tests/test_usb_transport.py",
    "chars": 1954,
    "preview": "\"\"\"Tests for the `UsbTransport` class.\"\"\"\n\nimport unittest\n\nfrom adb_shell.exceptions import TcpTimeoutException\n\ntry:\n "
  },
  {
    "path": "venv_requirements.txt",
    "chars": 221,
    "preview": "# Standard requirements\nblack\ncoverage\ncoveralls\nflake8\npylint\npytest\nsetuptools\nsphinx\nsphinx-rtd-theme\ntwine\n\n# Specif"
  }
]

About this extraction

This page contains the full source code of the JeffLIrion/adb_shell GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 82 files (444.2 KB), approximately 106.4k tokens, and a symbol index with 458 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!