Skip to content
This repository was archived by the owner on Jan 10, 2023. It is now read-only.

Conversation

greateggsgreg
Copy link
Contributor

@greateggsgreg greateggsgreg commented Jan 31, 2018

  • Interactive Shell support (instead of single shell commands)
  • Changed the AdbCommands static class paradigm to an object oriented design
  • Added support for pycryptodome
  • Python3 compatibility fixes
  • Misc adb protocol bug fixes

@googlebot
Copy link

Thanks for your pull request. It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

📝 Please visit https://cla.developers.google.com/ to sign.

Once you've signed, please reply here (e.g. I signed it!) and we'll verify. Thanks.


  • If you've already signed a CLA, it's possible we don't have your GitHub username or you're using a different email address on your commit. Check your existing CLA data and verify that your email is set on your git commits.
  • If your company signed a CLA, they designated a Point of Contact who decides which employees are authorized to participate. You may need to contact the Point of Contact for your company and ask to be added to the group of authorized contributors. If you don't know who your Point of Contact is, direct the project maintainer to go/cla#troubleshoot. The email used to register you as an authorized contributor must be the email used for the Git commit.
  • In order to pass this check, please resolve this problem and have the pull request author add another comment and the bot will run again. If the bot doesn't comment, it means it doesn't think anything has changed.

@greateggsgreg
Copy link
Contributor Author

I signed it!

@googlebot
Copy link

CLAs look good, thanks!

@greateggsgreg
Copy link
Contributor Author

greateggsgreg commented Jan 31, 2018

EDIT: merged with master

Copy link
Contributor

@fahhem fahhem left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's really hard to review this because you changed the spacing as well as the code, so all I see is a wall of red and then green instead of line-by-line changes. Any chance you can undo the formatting changes? I'm not opposed to them, but I'd prefer them as a separate pull-request


conn = self._get_service_connection(b'shell:')

return self.protocol_handler.InteractiveShellCommand(conn, command=command, strip_command=strip_command,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you bring this under 80 chars easily?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Happy to make these formatting changes so its easier to see the diffs, give me a day or so to revisit it and I'll push up the changes.

@fahhem
Copy link
Contributor

fahhem commented Feb 1, 2018

I actually have a branch somewhere at home changing this to be a class, that staticmethods-on-a-class approach is definitely one of those "man, what was I thinking..." choices. I'm definitely in favor of fixing that to be a normal object-oriented design. In fact, I think it's necessary for a few things I've been wanting to do, like tracking multiple open connections (you've started that here).


signed_token = rsa_key.Sign(str(banner))
# Do not mangle the banner property here by converting it to a string
signed_token = rsa_key.Sign(banner)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before this was sending a mangled banner to the device instead of the true string. Removing the string cast fixed this.

destination_dir = '/data/local/tmp/'
basename = os.path.basename(apk_path)
destination_path = destination_dir + basename
destination_path = os.path.join(destination_dir, basename)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Concatenating the string often got the path wrong ('/data/local/tmptest' vs '/data/local/tmp/test'). Using os.path.join to ensure it produces a correct path.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem with os.path.join is that if you're on Windows, it'll use \ instead of /. We can use posixpath still though:

import posixpath; destination_path = posixpath.join(destination_dir, basename)

source_file = open(source_file, 'rb')

connection = self.protocol_handler.Open(
with source_file:
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bit cleaner than the current should_close variable solution in master. Allows an external caller to pass any file-like object and ensure its properly closed.

Jamey Hicks <jamey.hicks@gmail.com>
Marc-Antoine Ruel <maruel@chromium.org>
Max Borghino <fmborghino@gmail.com>
Mohammad Abu-Garbeyyeh <github@mohammadag.com>
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Manual merge from master

subparsers, parents, adb_commands.AdbCommands.Remount)
common_cli.MakeSubparser(subparsers, parents, adb_commands.AdbCommands.Root)
common_cli.MakeSubparser(subparsers, parents, adb_commands.AdbCommands.EnableVerity)
common_cli.MakeSubparser(subparsers, parents, adb_commands.AdbCommands.DisableVerity)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Manual merge from master

def PortPathMatcher(cls, port_path):
"""Returns a device matcher for the given port path."""
if isinstance(port_path, basestring):
if isinstance(port_path, str):
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

basestring was removed in Python3. Using str here instead for py2/3 compatibility.

"""
if data:
if not isinstance(data, bytes):
if isinstance(data, str):
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Strings are also bytes in Python3. Using str here instead for py2/3 compatibility then always encoding as bytes.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With this change, unicode in Python won't be encoded and we'll try to send unicode over the wire.

What was wrong with the previous line? We need bytes, so if it's not bytes, we encode using utf8 to get bytes.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed


from setuptools import setup

# Figure out if the system already has a supported Crypto library
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if you'll like this, but I wanted a way to not force M2Crypto to be installed in my environment if I had an alternative crypto library that was supported (pycryptodome). Happy to live with this not making it into master.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nah, but I wonder if there's an OR option in setuptools?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couldn't find an OR option in setuptools. Most just perform logic like this in the actual setup.py.

self.assertEqual(b''.join(responses).decode('utf8'),
adb_commands.Shell(command))

def testUninstall(self):
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Manual merge form master.

@coveralls
Copy link

coveralls commented Feb 4, 2018

Coverage Status

Coverage decreased (-4.4%) to 42.691% when pulling 5badca3 on greateggsgreg:python3-and-misc-fixes into c3f93fa on google:master.

@greateggsgreg
Copy link
Contributor Author

@fahhem I've updated all tests as well as Fastboot to conform to the new object model. All tests are passing again.

Copy link
Contributor

@fahhem fahhem left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the hard work! Sorry for the delays, but I hope to get back to this next weekend.

If there's anything you don't want to do, just put a TODO on me to do it instead :)

# Connection table tracks each open AdbConnection objects per service type
# By default, the only service connections that make sense to hold open is (interactive) shell
self._service_connections = {
b'shell:': None
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

falsey contents of this dict don't affect anything, why put anything in it? Also, the comment makes it seems like only interactive shells would be kept open, but that's not true.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I've read more and it looks like _get_service_connection is only used by shell: connections, so the logic in it is repeated. How about adding a cache=False kwarg to it and using it everywhere self.protocol_handler.Open() is called?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This becomes a lot more useful for another branch I'm working on right now for implementing the 'adb forward' functionality. There, we'll need to hold/cache the service connection for our proxy while the TCP connection is open. I'll push that up when we're happy with this service connections implementation

Having a cache param in Open() isn't a bad idea, but since the Protocol Handler (AdbMessage) is basically a static class, it cannot hold state data like our get_service_connections method within AdbCommands.

I could refactor calls to protocol_handler.Open() and make them pass through a CreateServiceConnection() sort of method, with a cache=False parameter. Or, leave it to the individual program functions to handle it on their own (as it is now). Fine with either way, let me know what you think.

Copy link
Contributor Author

@greateggsgreg greateggsgreg Feb 26, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've also updated the comments around self._service_connections so its a bit more clear that any service that needs to be persisted can take advantage of it, not just InteractiveShell. Again, this will be much more clear when the tcp:forward service uses this alongside shell:

destination_dir = '/data/local/tmp/'
basename = os.path.basename(apk_path)
destination_path = destination_dir + basename
destination_path = os.path.join(destination_dir, basename)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem with os.path.join is that if you're on Windows, it'll use \ instead of /. We can use posixpath still though:

import posixpath; destination_path = posixpath.join(destination_dir, basename)

self.handle, destination=b'sync:', timeout_ms=timeout_ms)
self.filesync_handler.Push(connection, source_file, device_filename,
self.filesync_handler.Push(connection, source_file, device_filename,
mtime=int(mtime), progress_callback=progress_callback)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

indentation

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is intentional so that the Push() call is within the 'with source_file' block, since Push() uses source_file

handle = common.TcpHandle(serial, timeout_ms=default_timeout_ms)

# If there isnt a handle override (used by tests), build one here
if not kwargs.get('handle', None):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if 'handle' in kwargs:

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

DeviceIsAvailable, port_path=port_path, serial=serial,
timeout_ms=default_timeout_ms)
return cls.Connect(handle, **kwargs)
self.handle = kwargs.pop('handle')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not in the docstring here or for fastboot. Also, if you reorder this a bit, you can combine the if/elifs:

if 'handle' in kwargs:
  self.handle = kwargs.pop('handle')
elif serial and b':' in serial:
  self.handle = common.TcpHandle(serial, timeout_ms=default_timeout_ms)
else:
  self.handle = common.UsbHandle.FindAndOpen(
      DeviceIsAvailable, port_path=port_path, serial=serial,
      timeout_ms=default_timeout_ms)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed, thanks for the suggestion!

from setuptools import setup

# Figure out if the system already has a supported Crypto library
rsa_signer_library = 'M2Crypto>=0.21.1,<=0.26.4'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is a no-op, set it to None here to avoid the double mention of the version string

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wasn't able to find an 'OR' option in setup tools without writing the logic in. I've removed one of the version strings to the order of preference is kept for the supported cryptolibraries (1. M2Crypto, 2. Python-Rsa, 3. Pycryptodome)


from setuptools import setup

# Figure out if the system already has a supported Crypto library
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nah, but I wonder if there's an OR option in setuptools?

setup.py Outdated

install_requires = ['libusb1>=1.0.16', 'M2Crypto>=0.21.1,<=0.26.4'],
install_requires = [
'future',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where is this used?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed, see comments below

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above

import time

from io import BytesIO
from future.utils import iteritems
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

@greateggsgreg greateggsgreg Feb 26, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just reverted to items below. Now there is no reliance on the future library

for cmd_id in ids
}
wire_to_id = {wire: cmd_id for cmd_id, wire in id_to_wire.items()}
wire_to_id = {wire: cmd_id for cmd_id, wire in iteritems(id_to_wire)}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why go from items to iteritems? This isn't a big object so the overhead of the copy is small, and it's correct in py 3 anyway

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, no real benefit to iterates here. Reverted to items()

@greateggsgreg
Copy link
Contributor Author

@fahhem I believe I've addressed every one of comments and made changes where appropriate. Please let me know if you have any additional questions and let me know if there's more I can do.

Thanks!

setup.py Outdated

install_requires = ['libusb1>=1.0.16', 'M2Crypto>=0.21.1,<=0.26.4'],
install_requires = [
'future',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above

DeviceIsAvailable, port_path=port_path, serial=serial,
timeout_ms=default_timeout_ms)

if 'handle' in kwargs:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Calling ConnectDevice(handle=...) is equivalent to Connect(handle), so why is Connect() private? I'm not a big fan of name-mangling-private in Python anyway, nothing's really private in Python anyway, so why not just use one _ and let anyone who wants to hang themselves easier access to the rope?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, updated the method to be protected ('_') and the documentation around it and its relationship to ConnectDevice

@fahhem
Copy link
Contributor

fahhem commented Mar 1, 2018

One last comment before it's ready, thanks for the diligent work!

@fahhem
Copy link
Contributor

fahhem commented Mar 1, 2018

Oh, and include a change to CONTRIBUTORS adding your info to it

…called externally. Fixed documentation around the method and its relationship to ConnectDevice()
@greateggsgreg
Copy link
Contributor Author

greateggsgreg commented Mar 3, 2018

@fahhem Thanks! Made the change to _Connect and contributors.

Edit: Just noticed your comment in setup.py about the 'future' bit, or were your referring to the M2Crypto requirement? Future has been removed from the requirements as its no long used, and I've kept the conditional logic for the crypto library based on whats already installed, defaulting to M2Crypto. Did you want that removed for the previous behavior and just keep other AuthSigners around for customization? Happy either way!

Copy link
Contributor

@fahhem fahhem left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, that was a comment I accidentally left on the previous version of your code where 'future' was still a requirement, ignore it :)

@fahhem fahhem merged commit 854be63 into google:master Mar 3, 2018
@fahhem
Copy link
Contributor

fahhem commented Mar 3, 2018

If you want to do the formatting changes as the next PR, that would be great :)

I'll make a new release once the Py3 compatibility PRs are merged or closed, hopefully this week

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants