Skip to content
This repository was archived by the owner on Jan 10, 2023. It is now read-only.
Open
60 changes: 42 additions & 18 deletions adb/fastboot.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import logging
import os
import struct
from io import BytesIO

from adb import common
from adb import usb_exceptions
Expand Down Expand Up @@ -99,7 +100,7 @@ def HandleSimpleResponses(
info_cb: Optional callback for text sent from the bootloader.

Returns:
OKAY packet's message.
Tuple - OKAY packet's message, List of preceding Fastboot Messages
"""
return self._AcceptResponses(b'OKAY', info_cb, timeout_ms=timeout_ms)

Expand All @@ -123,9 +124,9 @@ def HandleDataSending(self, source_file, source_len,
FastbootInvalidResponse: Fastboot responded with an unknown packet type.

Returns:
OKAY packet's message.
Tuple - OKAY packet's message, List of preceding Fastboot Messages
"""
accepted_size = self._AcceptResponses(
accepted_size, _msgs = self._AcceptResponses(
b'DATA', info_cb, timeout_ms=timeout_ms)

accepted_size = binascii.unhexlify(accepted_size[:8])
Expand All @@ -151,24 +152,33 @@ def _AcceptResponses(self, expected_header, info_cb, timeout_ms=None):
FastbootInvalidResponse: Fastboot responded with an unknown packet type.

Returns:
OKAY packet's message.
Tuple - OKAY packet's message, List of preceding Fastboot Messages
Copy link
Contributor

Choose a reason for hiding this comment

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

What was this change for? It seems like it just makes it so the INFO messages are now returned as well but could that break people since the return format here is new? To be honest it's not a huge deal since I'd imagine not a ton of people using FastbootProtocol directly but just curious

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah I didn't like making this change because of API breakage, but in a few tools I've integrated this with I've found it's useful for callers to have a list of the FastbootMessages that were a part of the response to any SimpleCommands.

info_cb is useful for logging, but having a list of messages returned right after a call is made is good for making decisions based on specific returns from a command. 'OKAY' only returns if the command was successfully run, but does not capture if the command itself had issues (ie. fastboot boot may return 'OKAY', ['oem unlocking is not enabled'] )

"""

messages = []

while True:
response = self.usb.BulkRead(64, timeout_ms=timeout_ms)
header = bytes(response[:4])
remaining = bytes(response[4:])

if header == b'INFO':
info_cb(FastbootMessage(remaining, header))
fbm = FastbootMessage(remaining, header)
messages.append(fbm)
info_cb(fbm)
elif header in self.FINAL_HEADERS:
if header != expected_header:
raise FastbootStateMismatch(
'Expected %s, got %s', expected_header, header)
if header == b'OKAY':
info_cb(FastbootMessage(remaining, header))
return remaining
fbm = FastbootMessage(remaining, header)
messages.append(fbm)
info_cb(fbm)
return remaining, messages
elif header == b'FAIL':
info_cb(FastbootMessage(remaining, header))
fbm = FastbootMessage(remaining, header)
messages.append(fbm)
info_cb(fbm)
raise FastbootRemoteFailure('FAIL: %s', remaining)
else:
raise FastbootInvalidResponse(
Expand All @@ -188,6 +198,7 @@ def _HandleProgress(self, total, progress_callback):

def _Write(self, data, length, progress_callback=None):
"""Sends the data to the device, tracking progress with the callback."""
progress = None
if progress_callback:
progress = self._HandleProgress(length, progress_callback)
next(progress)
Expand Down Expand Up @@ -310,20 +321,19 @@ def Download(self, source_file, source_len=0,
Returns:
Response to a download request, normally nothing.
"""

if isinstance(source_file, str):
source_file_path = str(source_file)
source_len = os.stat(source_file).st_size
source_file = open(source_file)
with open(source_file_path, 'rb') as fh:
source_file = BytesIO(fh.read())
Copy link
Contributor

Choose a reason for hiding this comment

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

do we still need the data.encode('utf8') or was that a bug in the first place?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

By data.encode('utf-8') do you mean the BytesIO creation?

The behavior here was improved so that a caller to Download() can either pass in a file-like object directly or a string file path. In the event they pass in a string, we have to convert it into a file like object before it's passed into HandleDataSending(). This statement was previously leaving a file handle open, so we read the contents and put it into BytesIO so that it has file-like properties.

If this isn't what you meant then let me know!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh I see what you're referring too. The diff below shows us running data.encode('utf-8') on the file contents. This was a bug because the images that are Downloaded are almost always binary/outside the utf-8 space and would fail.


with source_file:
if source_len == 0:
# Fall back to storing it all in memory :(
data = source_file.read()
source_file = io.BytesIO(data.encode('utf8'))
source_len = len(data)
if not source_len:
source_len = len(source_file)

self._protocol.SendCommand(b'download', b'%08x' % source_len)
return self._protocol.HandleDataSending(
source_file, source_len, info_cb, progress_callback=progress_callback)
self._protocol.SendCommand(b'download', b'%08x' % source_len)
return self._protocol.HandleDataSending(
source_file, source_len, info_cb, progress_callback=progress_callback)

def Flash(self, partition, timeout_ms=0, info_cb=DEFAULT_MESSAGE_CALLBACK):
"""Flashes the last downloaded file to the given partition.
Expand Down Expand Up @@ -396,3 +406,17 @@ def Reboot(self, target_mode=b'', timeout_ms=None):
def RebootBootloader(self, timeout_ms=None):
"""Reboots into the bootloader, usually equiv to Reboot('bootloader')."""
return self._SimpleCommand(b'reboot-bootloader', timeout_ms=timeout_ms)

def Boot(self, source_file):
"""
Fastboot boot image by sending image from local file system then issuing the boot command

:param source_file:
:return:
"""

if not os.path.exists(source_file):
raise ValueError("source_file must exist")

self.Download(source_file)
self._SimpleCommand(b'boot')
3 changes: 3 additions & 0 deletions adb/fastboot_debug.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,9 @@ def main():
subparsers, parents, fastboot.FastbootCommands.Oem)
common_cli.MakeSubparser(
subparsers, parents, fastboot.FastbootCommands.Reboot)
common_cli.MakeSubparser(
subparsers, parents, fastboot.FastbootCommands.Boot,
{'source_file': 'Image file on the host to push and boot'})

if len(sys.argv) == 1:
parser.print_help()
Expand Down