Create fuzzy-fastboot pen tester

Test: Run on Sailfish, Walleye, and other devices
Bug: http://b/111126621
Change-Id: I309af79411d0a16d11874a048ce0db024770d7b2
This commit is contained in:
Aaron Wisner 2018-07-20 17:14:04 -05:00
parent 9812b58fc4
commit 00737b3939
16 changed files with 3548 additions and 0 deletions

View file

@ -56,6 +56,8 @@ enum RetCode : int {
};
class FastBootDriver {
friend class FastBootTest;
public:
static constexpr int RESP_TIMEOUT = 30; // 30 seconds
static constexpr uint32_t MAX_DOWNLOAD_SIZE = std::numeric_limits<uint32_t>::max();

View file

@ -0,0 +1,31 @@
cc_test_host {
name: "fuzzy_fastboot",
compile_multilib: "first",
srcs: [
"main.cpp",
"extensions.cpp",
"usb_transport_sniffer.cpp",
"fixtures.cpp",
"test_utils.cpp",
],
static_libs: [
"libfastboot2",
"libziparchive",
"libsparse",
"libutils",
"liblog",
"libz",
"libdiagnose_usb",
"libbase",
"libcutils",
"libgtest",
"libgtest_main",
"libbase",
"libadb_host",
"libtinyxml2",
"libsparse",
],
}

View file

@ -0,0 +1,394 @@
# Fuzzy Fastboot
Fuzzy Fastboot (FF) is a standalone automated conformance and penetration tester for
validating device-side fastboot protocol implementations.
The tool is completely generic, and uses a simple extensible XML
configuration file to auto-generate device-specific tests for any device.
Any Android device that uses the fastboot protocol should have fuzzy fastboot run on it prior to
release to find implementation bugs, make sure it conforms to the fastboot spec,
and that it safely handles malicious inputs.
## Background
The [fastboot protocol](../README.md) provides an easy way to manage low level
aspects of the device directly from bootloader. However, with great power comes
great responsibility. An improper or insecure fastboot implementation can
open the possibility for critical security exploits on the bootloader via fastboot
commands. Furthermore, an untrustworthy or insecure bootloader means nothing that is
either directly or indirectly bootstrapped by the bootloader can be trusted (including Android).
By checking a bootloader's conformance to the fastboot spec, as well as make sure
nefarious/malformed input is properly and gracefully handled, easy exploits of a
device's bootloaders can be mitigated.
Additionally, since the fastboot tool itself must support a myriad of fastboot
implementations, it is important to make sure an implementation is conforming to
avoid potential incompatibilities with the fastboot command line tool itself.
Thus, Fuzzy Fastboot also checks for proper conformance to the spec.
## Overview
Fuzzy Fastboot is written in C++ and uses [Google Test](https://github.com/google/googletest)
for the underlying test framework. This means that Fuzzy Fastboot supports all of
gtest's command line flags and options.
Additionally, by using gtest it makes it extremely easy to add additional C++ based
tests to Fuzzy Fastboot. However, in most cases the optional device specific
XML configuration file that is provided to Fuzzy Fastboot supports the necessary
features and hooks for testing device specific commands/features
without touching the underlying C++.
### Generic Tests
Without a provided device XML configuration, Fuzzy Fastboot can only perform
some basic tests that are generic to any fastboot device. These generic tests are
divided into several test suite categories:
1. **USBFunctionality** - Test USB communication
2. **Conformance** - Test the device properly handles well-formed fastboot commands
3. **UnlockPermissions** - Test commands only allowed in the unlocked state work
4. **LockPermissions** - Test commands only not allowed in the locked state are rejected
5. **Fuzz** - Test malicious and/or ill-formed commands are properly and gracefully handled
### XML Generated Tests
With a provided XML device configuration, Fuzzy Fastboot will be able to generate
many more additional tests cases.
The device config XML has five element pairs all inside a root level `<config>`:
#### `<getvar>` Element
Inside the `<getvar></getvar>` element pairs, one should list all the device's getvar
variables, with an associated ECMAScript regex you wish the returned variable to match on.
Each tested variable should appear in a `<var key="key" assert="regex"/>` format.
For example:
```xml
<getvar>
<var key="product" assert="superphone2000"/>
<var key="secure" assert="no|yes"/>
<var key="battery-voltage" assert="[34][[:digit:]]{3}"/>
<!-- etc... -->
</getvar>
```
#### `<partitions>` Element
Inside the `<partitions></partitions>` element pairs, one should list all the device's
partitions. Each device partition has should be put inside a `<part/>` element.
The `<part/>` element supports the following attributes:
| Attribute | Value | Purpose | Default |
|-----------|----------------|---------------------------------------------------------------------------------------------|----------|
| value | Partition name | The name of the partition | Required |
| slots | "yes" or "no" | Is this partition is slotted | "no" |
| test | "yes" or "no" | Is Fuzzy Fastboot is allowed to generate tests that overwrite this partition | Required |
| hashable | "yes" or "no" | Is this partition hashable with the hash command specified in `<checksum>` | "yes" |
| parsed | "yes" or "no" | Does the bootloader parse this partition, such as look for a header, look for magic, etc... | "no" |
For example:
```xml
<!-- All the device partitions should be listed here -->
<partitions>
<part value="boot" slots="yes" test="yes" hashable="yes" parsed="yes"/>
<part value="modem" slots="yes" test="yes" hashable="yes"/>
<part value="userdata" slots="no" test="yes" hashable="no"/>
<!-- etc... -->
</partitions>
```
#### `<packed>` Element
Most devices have pseudo partitions, such as a `bootloader` partition,
that in reality is composed of several real partitions.
When one of these pseudo partitions is flashed, the bootloader
will internally expand the image into the individual images for each underlying
partition. These pseudo partitions should be listed inside a `<part></part>`
element pair. Each element `<part>` has a mandatory attribute `value`,
which lists the name of this pseudo partition, and a `slots` attribute,
which can be yes or no if this pseudo partition is slotted.
Additionally, inside the `<part></part>` element pair, one should list
all the real partition that make up this pseudo partition inside of
`<child>PART_NAME</child>` element pairs.
An example is should below:
```xml
<!-- All the device packed partitions should be listed here -->
<packed>
<part value="bootloader" slots="yes">
<!-- We list the real partitions it is composed of -->
<child>foo1</child>
<child>foo2</child>
<child>bar3</child>
<!-- We list tests, expect defaults to 'okay' -->
<test packed="bootloader.img" unpacked="unpacked"/>
<test packed="bootloader_garbage.img" expect="fail"/>
</part>
</packed>
```
You might notice there are additional `<test/>` elements as well contained inside of
a `<part></part>` pair. This is because Fuzzy Fastboot allows (and recommends) one to specify
valid and invalid test packed images for flashing this particular pseudo partition.
Additionally, one should specify a folder with all the partitions' images
that the packed image unpacks to. If your device supports hashing partitions, this
will allow Fuzzy Fastboot to validate the images are unpacking correctly, and
the correct slots are being flashed.
Each `<test/>` element has the following supported attributes:
| Attribute | Value | Purpose | Default |
|-----------|---------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------|
| packed | The name of the packed test image | The image uploaded to the device. It is searched for in dir if --search_path=dir | Required |
| unpacked | The name of the directory containing the unpacked version of packed | Searched for in dir if --search_path=dir. This folder should have the all the images that packed unpacks to. The name of each of the images should be the name of the real partition it is flashed to. | Required if expect != "fail" |
| expect | "okay" or "fail" | If uploading a invalid or garbage image the bootloader should reject use "fail" otherwise "okay" | "okay" |
#### `<oem>` Element
Vendors can extend the fastboot protocol with oem commands. This allows vendors
to support device/vendor specific features/commands over the fastboot protocol.
Fuzzy Fastboot allows testing these oem commands as well.
Oem commands are specefied in `<command></command>` element pairs. Each command
element supports the following attributes:
| Attribute | Value | Purpose | Default |
|-------------|----------------------|---------------------------------------------------------------|----------|
| value | The oem command name | Ex: if value="foo", the oem command will start with "oem foo" | Required |
| permissions | "none" or "unlocked" | Whether the bootloader must be "unlocked" to perform command | "none" |
An example is should below:
```xml
<oem>
<command value="self_destruct" permissions="unlocked">
<!-- This will test that "oem self_destruct now" returns 'okay' -->
<test value="now" expect="okay"/>
<!-- This will test that "oem self_destruct yesterday" returns 'fail' -->
<test value="yesterday" expect="fail" />
</command>
<command value="foobar" permissions="unlocked">
<!-- FF will first stage test_image.img before running 'oem foobar use_staged' -->
<test value="use_staged" expect="okay" input="test_image.img" />
<!-- FF will run 'oem foobar send_response', upload data from device, then run the validator script -->
<test value="send_response" expect="fail" validate="python validator.py"/>
</command>
<oem/>
```
Again you will notice that one can, and should, specify tests to run with `<test/>` elements.
The test elements support the following attributes:
| Attribute | Value | Purpose | Default |
|-----------|--------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------|
| value | The oem command argument | Ex: if value="bar", and the oem command name was "foo", the full command will be "oem foo bar" | Empty String (no argument) |
| expect | "okay" or "fail" | Whether the bootloader should accept or reject this command | "okay" |
| input | A image filename | Some oem commands require staging files before the command is executed | Empty String (no argument) |
| validate | A program/script to run to validate the response | Some oem commands will stage data that can be downloaded afterwards and should be validated to be correct. Fuzzy Fastboot will launch the validation program with the first arg the oem command executed, the second arg the path to the downloaded image. Ex: "python validate.py'. If the program has a non-zero return code, the validation is marked as failed and anything from the launched programs stderr is logged in the test failure. | Empty String (no argument) |
| assert | A Regular expression | In the "okay" or "fail" response, Fuzzy Fastboot will assert the response matches this regular expression. | Empty String (no argument) |
| output | The name of the saved file | This is the name of the saved output file passed to the validation script. It is saved in whatever location is specified by the --output_path argument | out.img |
#### `<checksum/>` Element
If the bootloader supports hashing partitions (implementing this is strongly recommended), Fuzzy Fastboot can
use it to do a bunch more testing. Make sure this hash is a cryptographically secure hash, as a non-secure one
might reveal secrets about the partitions' contents.
The checksum element has no children and only two required attributes:
- **value** - The command that hashes a partition. Note that the name of the partition will be appended to the end of the command. For example, if `value="oem hash"`, hashing the partition `bar` would be issued with `oem hash bar`.
- **parser** - How the hash is returned is up to the vendor's implementation. It could be part of the `OKAY` response, or be encoded in `INFO` responses. Thus, the parser attribute is used to specify a program/script that will extract the hash. The first argument to the program will be the be the response from `OKAY`, the second argument will be all the `INFO` responses joined by newlines. The extracted hash should be sent back to Fuzzy Fastboot as a string written to stderr, and a return code of 0 to signal the parsing was successful. In the case of failure, return a non-zero return code, an optionally an associated error message written to stderr.
## Full Example XML Configuration
Here is a basic example configuration. This can also be found in the 'example' folder
as well as the associated python scripts 'checksum_parser.py' (used to extract partition hash),
and 'validator.py' (used to validate an oem command that returns data).
```xml
<?xml version="1.0"?>
<config>
<!-- All the device getvar variables should be listed here -->
<getvar>
<var key="product" assert="superphone2000"/>
<var key="secure" assert="no|yes"/>
</getvar>
<!-- All the device partitions should be listed here -->
<partitions>
<part value="boot" slots="yes" test="yes" hashable="yes" parsed="yes"/>
<part value="modem" slots="yes" test="yes" hashable="yes"/>
<part value="userdata" slots="no" test="yes" hashable="no"/>
<!-- Bootloader partitions -->
<part value="foo1" slots="yes" test="no" hashable="yes"/>
<part value="foo2" slots="yes" test="no" hashable="yes"/>
<part value="bar3" slots="yes" test="no" hashable="yes"/>
</partitions>
<!-- All the device packed partitions should be listed here -->
<packed>
<part value="bootloader" slots="yes">
<!-- We list the real partitions it is composed of -->
<child>foo1</child>
<child>foo2</child>
<child>bar3</child>
<!-- We list tests, expect defaults to 'okay' -->
<test packed="bootloader.img" unpacked="unpacked"/>
<test packed="bootloader_garbage.img" expect="fail"/>
</part>
</packed>
<!-- All the oem commands should be listed here -->
<oem>
<!-- The 'oem self_destruct' command requires an unlocked bootloader -->
<command value="self_destruct" permissions="unlocked">
<!-- This will test that "oem self_destruct now" returns 'okay' -->
<test value="now" expect="okay"/>
<test value="yesterday" expect="fail" />
</command>
<!-- Test a fictional 'oem get' command -->
<command value="get" permissions="none">
<test value="batch_id" expect="okay" assert="[[:digit:]]+"/>
<test value="device_color" expect="okay" assert="green|blue"/>
<test value="build_num" expect="okay" assert="[\w\-.]+"/>
<test value="garbage" expect="fail" assert="Invalid var '[\w ]+'"/>
</command>
<!-- Some oem commands might require staging or downloading data, or both -->
<command value="foobar" permissions="unlocked">
<!-- FF will first stage test_image.img before running 'oem foobar use_staged' -->
<test value="use_staged" expect="okay" input="test_image.img" />
<!-- FF will run 'oem foobar send_response', upload data from device, then run the validator script -->
<test value="send_response" expect="fail" validate="python validator.py"/>
</command>
</oem>
<!-- If there is a custom oem checksum command to hash partitions, add it here -->
<checksum value="oem sha1sum"/>
</config>
```
## Running Fuzzy Fastboot
Fuzzy Fastboot is built with the fastboot tool itself. It will appear in `out/host/linux-x86/testcases/fuzzy_fastboot/x86_64`.
### Command Line Arguments
- **--config=**: Specify the name of the configuration XML file. If omitted, only the generic tests will be available.
- **--search_path=**: Specify the path where Fuzzy Fastboot will look for files referenced in the XML. This includes all the test images and the referenced programs/scripts. This is also where the --config is searched for. If this argument is omitted it defaults to the current directory.
- **--output_path**: Some oem tests can download an image to the host for validation. This is the location where that image is stored. This deafults to '/tmp'.
- **--serial_port**: Many devices have a UART or serial log, that reports logging information. Fuzzy Fastboot can include this logging information in the backtraces it generates. This can make debugging far easier. If your device has this, it can be specified with the path to the tty device. Ex: "/dev/ttyUSB0".
- **--gtest_***: Any valid gtest argument (they all start with 'gtest_')
- **-h**: Print gtest's help message
## Using Fuzzy Fastboot on my Device
All Fuzzy Fastboot tests should pass on your device. No test should be able to
crash the bootloader. Invalid input MUST be handled gracefully. Using "asserts"
or panicking on invalid or malformed input is not an acceptable way to handle
these tests, as ungraceful forced termination of the bootloader can expose
vulnerabilities and leave the device in a bad state.
The following is the recommended workflow for using Fuzzy Fastboot on a new device:
### Step 1: Pass the generic Conformance tests
Begin with just the generic tests (i.e. no XML file). In particular, make sure all
the conformance tests are passing before you move on. All other tests require that
the basic generic conformance tests all pass for them to be valid. The conformance
tests can be run with `./fuzzy_fastboot --gtests_filter=Conformance.*`.
#### Understanding and Fixing Failed Tests
Whenever a test fails, it will print out to the console the reason for failure
and the lines and file where the error happened. At the end of each failure
block, there will usually be a message that Fuzzy Fastboot reports to gtest
explaining what went wrong. An example is shown below:
```
Expected equality of these values:
resp
Which is: "no"
unlock ? "yes" : "no"
Which is: "yes"
getvar:unlocked response was not 'no' or 'yes': no
system/core/fastboot/fuzzy_fastboot/fixtures.cpp:227: Failure
Expected: SetLockState(UNLOCKED) doesn't generate new fatal failures in the current thread.
Actual: it does.
[THERE WILL BE A MESSAGE HERE EXPLAINING WHY IT FAILED]
```
In most cases this message at the bottom is all that is needed to figure out why it failed.
If this is not enough information, below this, gtest will also print out a human readable
backtrace of the underlying fastboot commands leading up the failure in this test.
Here is an example:
```
<<<<<<<< TRACE BEGIN >>>>>>>>>
[WRITE 0ms](15 bytes): "getvar:unlocked"
[READ 20ms](6 bytes): "OKAYno"
<<<<<<<< TRACE END >>>>>>>>>
```
One can easily see the reason for the failure was the test expected the device to
be unlocked.
If it is still unclear why the failure is happening, the last thing to do is look
at what line number and file is generating the error. Gtest will always print this out.
You can then manually look through Fuzzy Fastboot's test source code, and figure out
what went wrong.
### Step 2: Pass all the other generic tests
Run all the other generic tests (still no XML file). A list of all of them can be
printed out with: "./fuzzy_fastboot --gtest_list_tests". As before, "--gtest_filter"
can be used to select certain tests to run, once you figure out which ones failed.
One particular set of tests to watch out for are the ones that involve USB resets.
USB resets effectively unplug and replug the device in software. Fuzzy Fastboot,
expects USB resets to cancel whatever transaction is currently going on.
This is also how Fuzzy Fastboot attempts to recover from errors when the device is
unresponsive.
### Step 3: Create a device XML configuration
Without a device specific configuration file, Fuzzy Fastboot will have poor test
coverage of your device. The vast majority of tests are auto-generated via the XML
configuration file. Use the guide above to generate a configuration for your device.
Make sure to be as thorough as possible, and list everything in the configuration
that can be tested. Finally, make sure that the packed pseudo partitions and
oem commands all have provided test cases. Be sure to test both the positive case
(i.e. with valid input), as well as the opposite. Make sure the failure tests
have good coverage by thinking about all the ways invalid and malicious inputs
could be formed. These means creating images with malformed headers, illegal chars,
and other evil inputs.
Now run fuzzy_fastboot with the supplied configuration file. If you do "--gtest_list_tests",
you should see a ton more tests that were autogenerated by Fuzzy Fastboot.
As before, run these tests till everything passes. Again, use "--gtest_filter"
to select specific tests to run once you know what fail,
as running the whole things with a large configuration can take over 30 minutes.
See the gtest documentation, for nifty tricks and command line options.
### Step 4: Figure out what Fuzzy Fastboot can't/isn't testing
While Fuzzy Fastboot with a XML configuration file, should provide good test coverage.
Think about what device specific things are not being tested, and test them manually.
In particular, things that if not handled carefully could create security exploits.
Don't be lazy here, as you already put in the time to get this far.
### Step 5: Celebrate
You're done :). Now you can be more confident that your implementation is sound, and
have piece of mind knowing you are protecting the users' security and data by
running these tests. Don't get too complacent. If the bootloader's source code
is modified in a way that could introduce bugs or security issues. Make sure to
test again. You might have to add to your existing configuration file.
## Limitations and Warnings
- Currently this only works on Linux (even if it builds on Mac)
- Only fastboot over USB is currently supported
- Passing these tests does not mean there are not bugs/security issues. For example, a buffer overrun might not always trigger a crash or have any noticeable side effects.
- **Be extremely careful of the Fuzzy Fastboot tests you are running. Know exactly what the tests do you are about to run before you run them. It is very possible to brick a device with many of these tests.**
## Fuzzy Fastboot Missing Features TODO's
The following are missing features that should eventually be added
- *Sparse Image Tests*: Currently there are no tests that tests sparse images. Both well-formed and malicious images need to be tested.
- *Unlocking/Locking Critical*: Currently there are no tests that tests that locking/unlocking critical functionality.
- *Saved Test Log*: Fuzzy Fastboot should be able to create a failure log for every failing test and save it to a file. This file should include the test information, the reason it failed, and the fastboot command trace (with the serial console logs). Currently it just prints it to the console at the end of every test.
- *Host Side Hashing*: One should be able to provide the hashing algorithm to the Fuzzy Fastboot, so it can be checked to agree with what the device is reporting.
## Author
Aaron Wisner - awisner@google.com

View file

@ -0,0 +1,37 @@
'''
Some bootloader's support hashing partitions. This is a great feature for testing
correctness. However, the format for the way the hash is returned depends on the
implementation. The hash could be send through an INFO response, or be as part
of the OKAY response itself. This script is called with the first argument
as the string mesage from the okay response. The second argument is each
info response joined by newlines into one argument.
'''
import sys
def main():
'''
Data is sent back to the parent fuzzy_fastboot process through the stderr pipe.
There are two interpretations of this data by FF.
0 return code:
Anything written to STDERR will be interpreted as part of the hash.
non-zero return code:
Anything written to STDERR is part of the error message that will logged by FF
to explain why hash extraction failed.
Feel free to print to to STDOUT with print() as usual to print info to console
'''
script, response, info = sys.argv
# the info responses are concated by newlines
infos = [s.strip() for s in info.splitlines()]
sys.stderr.write(infos[-1])
print("Extracted checksum: '%s'" % infos[-1])
# non-zero return code signals error
return 0
if __name__ == "__main__":
sys.exit(main())

View file

@ -0,0 +1,62 @@
<?xml version="1.0"?>
<config>
<!-- All the device getvar variables should be listed here -->
<getvar>
<var key="product" assert="superphone2000"/>
<var key="secure" assert="no|yes"/>
</getvar>
<!-- All the device partitions should be listed here -->
<partitions>
<part value="boot" slots="yes" test="yes" hashable="yes" parsed="yes"/>
<part value="modem" slots="yes" test="yes" hashable="yes"/>
<part value="userdata" slots="no" test="yes" hashable="no"/>
<!-- Bootloader partitions -->
<part value="foo1" slots="yes" test="no" hashable="yes"/>
<part value="foo2" slots="yes" test="no" hashable="yes"/>
<part value="bar3" slots="yes" test="no" hashable="yes"/>
</partitions>
<!-- All the device packed partitions should be listed here -->
<packed>
<part value="bootloader" slots="yes">
<!-- We list the real partitions it is composed of -->
<child>foo1</child>
<child>foo2</child>
<child>bar3</child>
<!-- We list tests, expect defaults to 'okay' -->
<test packed="bootloader.img" unpacked="unpacked"/>
<test packed="bootloader_garbage.img" expect="fail"/>
</part>
</packed>
<!-- All the oem commands should be listed here -->
<oem>
<!-- The 'oem self_destruct' command requires an unlocked bootloader -->
<command value="self_destruct" permissions="unlocked">
<!-- This will test that "oem self_destruct now" returns 'okay' -->
<test value="now" expect="okay"/>
<test value="yesterday" expect="fail" />
</command>
<!-- Test a fictional 'oem get' command -->
<command value="get" permissions="none">
<test value="batch_id" expect="okay" assert="[[:digit:]]+"/>
<test value="device_color" expect="okay" assert="green|blue"/>
<test value="build_num" expect="okay" assert="[\w\-.]+"/>
<test value="garbage" expect="fail" assert="Invalid var '[\w ]+'"/>
</command>
<!-- Some oem commands might require staging or downloading data, or both -->
<command value="foobar" permissions="unlocked">
<!-- FF will first stage test_image.img before running 'oem foobar use_staged' -->
<test value="use_staged" expect="okay" input="test_image.img" />
<!-- FF will run 'oem foobar send_response', upload data from device, then run the validator script -->
<test value="send_response" expect="fail" validate="python validator.py"/>
</command>
</oem>
<!-- If there is a custom oem checksum command to hash partitions, add it here -->
<checksum value="oem sha1sum"/>
</config>

View file

@ -0,0 +1,37 @@
'''
This is an example validator to be used with oem commands that allow you to
upload data afterwards that you wish to validate locally.
'''
import sys
def eprint(msg):
'''
A helper function for logging error messages to fuzzy_fastboot
Use this function as you would "print()"
'''
sys.stderr.write(msg + '\n')
def main():
'''
Data is sent back to the parent fuzzy_fastboot process through the stderr pipe.
If this script has a non-zero return code, anything written to STDERR is part of
the error message that will logged by FF to explain why this validation failed.
Feel free to print to to STDOUT with print() as usual to print info to console
'''
script, command, fname = sys.argv
eprint("Messages here will go to the parent testers logs")
eprint("Hello world")
print("This goes to stdout as expected")
with open(fname, "rb") as fd:
# Do some validation on the buffer
pass
# non-zero return code signals error
return -1
if __name__ == "__main__":
sys.exit(main())

View file

@ -0,0 +1,314 @@
/*
* Copyright (C) 2018 The Android Open Source Project
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <csignal>
#include <cstdlib>
#include <fstream>
#include "extensions.h"
#include "test_utils.h"
#include "tinyxml2.h"
namespace fastboot {
namespace extension {
namespace { // private to this file
// Since exceptions are disabled, a bad regex will trigger an abort in the constructor
// We at least need to print something out
std::regex MakeRegex(const std::string& regex_str, int line_num,
std::regex_constants::syntax_option_type type = std::regex::ECMAScript) {
// The signal handler can only access static vars
static std::string err_str;
err_str = android::base::StringPrintf("'%s' is not a valid regex string (line %d)\n",
regex_str.c_str(), line_num);
const auto sighandler = [](int) {
int nbytes = write(fileno(stderr), err_str.c_str(), err_str.length());
static_cast<void>(nbytes); // need to supress the unused nbytes/ or unused result
};
std::signal(SIGABRT, sighandler);
// Now attempt to create the regex
std::regex ret(regex_str, type);
// unregister
std::signal(SIGABRT, SIG_DFL);
return ret;
}
bool XMLAssert(bool cond, const tinyxml2::XMLElement* elem, const char* msg) {
if (!cond) {
printf("%s (line %d)\n", msg, elem->GetLineNum());
}
return !cond;
}
const std::string XMLAttribute(const tinyxml2::XMLElement* elem, const std::string key,
const std::string key_default = "") {
if (!elem->Attribute(key.c_str())) {
return key_default;
}
return elem->Attribute(key.c_str());
}
bool XMLYesNo(const tinyxml2::XMLElement* elem, const std::string key, bool* res,
bool def = false) {
if (!elem->Attribute(key.c_str())) {
*res = def;
return true;
}
const std::string val = elem->Attribute(key.c_str());
if (val != "yes" && val != "no") {
return false;
}
*res = (val == "yes");
return true;
}
bool ExtractPartitions(tinyxml2::XMLConstHandle handle, Configuration* config) {
// Extract partitions
const tinyxml2::XMLElement* part = handle.FirstChildElement("part").ToElement();
while (part) {
Configuration::PartitionInfo part_info;
const std::string name = XMLAttribute(part, "value");
const std::string test = XMLAttribute(part, "test");
if (XMLAssert(!name.empty(), part, "The name of a partition can not be empty") ||
XMLAssert(XMLYesNo(part, "slots", &part_info.slots), part,
"Slots attribute must be 'yes' or 'no'") ||
XMLAssert(XMLYesNo(part, "hashable", &part_info.hashable, true), part,
"Hashable attribute must be 'yes' or 'no'") ||
XMLAssert(XMLYesNo(part, "parsed", &part_info.parsed), part,
"Parsed attribute must be 'yes' or 'no'"))
return false;
bool allowed = test == "yes" || test == "no-writes" || test == "no";
if (XMLAssert(allowed, part, "The test attribute must be 'yes' 'no-writes' or 'no'"))
return false;
if (XMLAssert(config->partitions.find(name) == config->partitions.end(), part,
"The same partition name is listed twice"))
return false;
part_info.test = (test == "yes")
? Configuration::PartitionInfo::YES
: (test == "no-writes") ? Configuration::PartitionInfo::NO_WRITES
: Configuration::PartitionInfo::NO;
config->partitions[name] = part_info;
part = part->NextSiblingElement("part");
}
return true;
}
bool ExtractPacked(tinyxml2::XMLConstHandle handle, Configuration* config) {
// Extract partitions
const tinyxml2::XMLElement* part = handle.FirstChildElement("part").ToElement();
while (part) {
Configuration::PackedInfo packed_info;
const std::string name = XMLAttribute(part, "value");
if (XMLAssert(!name.empty(), part, "The name of a packed partition can not be empty") ||
XMLAssert(XMLYesNo(part, "slots", &packed_info.slots), part,
"Slots attribute must be 'yes' or 'no'") ||
XMLAssert(config->partitions.find(name) == config->partitions.end(), part,
"A packed partition can not have same name as a real one") ||
XMLAssert(config->partitions.find(name) == config->partitions.end(), part,
"The same partition name is listed twice"))
return false;
// Extract children partitions
const tinyxml2::XMLElement* child = part->FirstChildElement("child")
? part->FirstChildElement("child")->ToElement()
: nullptr;
while (child) {
const std::string text(child->GetText());
// Make sure child exists
if (XMLAssert(config->partitions.find(text) != config->partitions.end(), child,
"The child partition was not listed in <partitions>"))
return false;
packed_info.children.insert(text);
child = child->NextSiblingElement("child");
}
// Extract tests
const tinyxml2::XMLElement* test = part->FirstChildElement("test")
? part->FirstChildElement("test")->ToElement()
: nullptr;
while (test) {
Configuration::PackedInfoTest packed_test;
packed_test.packed_img = XMLAttribute(test, "packed");
packed_test.unpacked_dir = XMLAttribute(test, "unpacked");
const std::string expect = XMLAttribute(test, "expect", "okay");
if (XMLAssert(!packed_test.packed_img.empty(), test,
"The packed image location must be specified") ||
XMLAssert(CMD_EXPECTS.find(expect) != CMD_EXPECTS.end(), test,
"Expect attribute must be 'okay' or 'fail'"))
return false;
packed_test.expect = CMD_EXPECTS.at(expect);
// The expect is only unpacked directory is only needed if success
if (packed_test.expect == OKAY &&
XMLAssert(!packed_test.unpacked_dir.empty(), test,
"The unpacked image folder location must be specified"))
return false;
packed_info.tests.push_back(packed_test);
test = test->NextSiblingElement("test");
}
config->packed[name] = packed_info;
part = part->NextSiblingElement("part");
}
return true;
}
bool ExtractGetVars(tinyxml2::XMLConstHandle handle, Configuration* config) {
// Extract getvars
const tinyxml2::XMLElement* var = handle.FirstChildElement("var").ToElement();
while (var) {
const std::string key = XMLAttribute(var, "key");
const std::string reg = XMLAttribute(var, "assert");
if (XMLAssert(key.size(), var, "The var key name is empty")) return false;
if (XMLAssert(config->getvars.find(key) == config->getvars.end(), var,
"The same getvar variable name is listed twice"))
return false;
Configuration::GetVar getvar{reg, MakeRegex(reg, var->GetLineNum()), var->GetLineNum()};
config->getvars[key] = std::move(getvar);
var = var->NextSiblingElement("var");
}
return true;
}
bool ExtractOem(tinyxml2::XMLConstHandle handle, Configuration* config) {
// Extract getvars
// Extract oem commands
const tinyxml2::XMLElement* command = handle.FirstChildElement("command").ToElement();
while (command) {
const std::string cmd = XMLAttribute(command, "value");
const std::string permissions = XMLAttribute(command, "permissions");
if (XMLAssert(cmd.size(), command, "Empty command value")) return false;
if (XMLAssert(permissions == "none" || permissions == "unlocked", command,
"Permissions attribute must be 'none' or 'unlocked'"))
return false;
// Each command has tests
std::vector<Configuration::CommandTest> tests;
const tinyxml2::XMLElement* test = command->FirstChildElement("test");
while (test) { // iterate through tests
Configuration::CommandTest ctest;
ctest.line_num = test->GetLineNum();
const std::string default_name = "XMLTest-line-" + std::to_string(test->GetLineNum());
ctest.name = XMLAttribute(test, "name", default_name);
ctest.arg = XMLAttribute(test, "value");
ctest.input = XMLAttribute(test, "input");
ctest.output = XMLAttribute(test, "output");
ctest.validator = XMLAttribute(test, "validate");
ctest.regex_str = XMLAttribute(test, "assert");
const std::string expect = XMLAttribute(test, "expect");
if (XMLAssert(CMD_EXPECTS.find(expect) != CMD_EXPECTS.end(), test,
"Expect attribute must be 'okay' or 'fail'"))
return false;
ctest.expect = CMD_EXPECTS.at(expect);
std::regex regex;
if (expect == "okay" && ctest.regex_str.size()) {
ctest.regex = MakeRegex(ctest.regex_str, test->GetLineNum());
}
tests.push_back(std::move(ctest));
test = test->NextSiblingElement("test");
}
// Build the command struct
const Configuration::OemCommand oem_cmd{permissions == "unlocked", std::move(tests)};
config->oem[cmd] = std::move(oem_cmd);
command = command->NextSiblingElement("command");
}
return true;
}
bool ExtractChecksum(tinyxml2::XMLConstHandle handle, Configuration* config) {
const tinyxml2::XMLElement* checksum = handle.ToElement();
if (checksum && checksum->Attribute("value")) {
config->checksum = XMLAttribute(checksum, "value");
config->checksum_parser = XMLAttribute(checksum, "parser");
if (XMLAssert(config->checksum_parser != "", checksum,
"A checksum parser attribute is mandatory"))
return false;
}
return true;
}
} // anonymous namespace
bool ParseXml(const std::string& file, Configuration* config) {
tinyxml2::XMLDocument doc;
if (doc.LoadFile(file.c_str())) {
printf("Failed to open/parse XML file '%s'\nXMLError: %s\n", file.c_str(), doc.ErrorStr());
return false;
}
tinyxml2::XMLConstHandle handle(&doc);
tinyxml2::XMLConstHandle root(handle.FirstChildElement("config"));
// Extract the getvars
if (!ExtractGetVars(root.FirstChildElement("getvar"), config)) {
return false;
}
// Extract the partition info
if (!ExtractPartitions(root.FirstChildElement("partitions"), config)) {
return false;
}
// Extract packed
if (!ExtractPacked(root.FirstChildElement("packed"), config)) {
return false;
}
// Extract oem commands
if (!ExtractOem(root.FirstChildElement("oem"), config)) {
return false;
}
// Extract checksum
if (!ExtractChecksum(root.FirstChildElement("checksum"), config)) {
return false;
}
return true;
}
} // namespace extension
} // namespace fastboot

View file

@ -0,0 +1,132 @@
/*
* Copyright (C) 2018 The Android Open Source Project
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#pragma once
#include <regex>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>
namespace fastboot {
namespace extension {
enum Expect { OKAY = 0, FAIL, DATA };
static const std::unordered_map<std::string, Expect> CMD_EXPECTS = {
{"okay", OKAY},
{"fail", FAIL},
{"data", DATA},
};
static const std::unordered_map<Expect, std::string> EXPECTS_STR = {
{OKAY, "okay"},
{FAIL, "fail"},
{DATA, "data"},
};
struct Configuration {
struct GetVar {
std::string regex_str;
std::regex regex;
int line_num;
// So gtest can print me
friend ::std::ostream& operator<<(::std::ostream& os, const GetVar& self) {
return os << "<regex='" << self.regex_str << "' line_num=" << self.line_num << ">";
}
};
struct PartitionInfo {
enum TestConfig { NO = 0, NO_WRITES, YES };
bool hashable;
bool slots; // Does it have slots
bool parsed; // Does the bootloader do parsing on the img?
TestConfig test;
// So gtest can print me
friend ::std::ostream& operator<<(::std::ostream& os, const PartitionInfo& pinfo) {
return os << "<hashable=" << pinfo.hashable << " slots=" << pinfo.slots
<< " parsed=" << pinfo.parsed << ">";
}
};
struct PackedInfoTest {
Expect expect; // Does it have slots
std::string packed_img;
std::string unpacked_dir;
// So gtest can print me
friend ::std::ostream& operator<<(::std::ostream& os, const PackedInfoTest& pinfo) {
return os << "<"
<< "expect=" << EXPECTS_STR.at(pinfo.expect)
<< " packed_img=" << pinfo.packed_img
<< " unpacked_dir=" << pinfo.unpacked_dir << ">";
}
};
struct PackedInfo {
bool slots; // Does it have slots
std::unordered_set<std::string> children;
std::vector<PackedInfoTest> tests;
};
struct CommandTest {
std::string name;
int line_num;
std::string arg;
Expect expect;
std::string regex_str;
std::regex regex;
std::string input;
std::string output;
std::string validator;
// So gtest can print me
friend ::std::ostream& operator<<(::std::ostream& os, const CommandTest& self) {
return os << "test: " << self.name << " (line: " << self.line_num << ")";
}
};
struct OemCommand {
bool restricted; // Does device need to be unlocked?
std::vector<CommandTest> tests;
};
std::unordered_map<std::string, GetVar> getvars;
std::unordered_map<std::string, PartitionInfo> partitions;
std::unordered_map<std::string, PackedInfo> packed;
std::unordered_map<std::string, OemCommand> oem;
std::string checksum;
std::string checksum_parser;
};
bool ParseXml(const std::string& file, Configuration* config);
} // namespace extension
} // namespace fastboot

View file

@ -0,0 +1,288 @@
/*
* Copyright (C) 2018 The Android Open Source Project
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <errno.h>
#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <termios.h>
#include <unistd.h>
#include <chrono>
#include <cstdlib>
#include <fstream>
#include <map>
#include <random>
#include <regex>
#include <set>
#include <thread>
#include <vector>
#include <android-base/stringprintf.h>
#include <gtest/gtest.h>
#include "fastboot_driver.h"
#include "usb.h"
#include "extensions.h"
#include "fixtures.h"
#include "test_utils.h"
#include "usb_transport_sniffer.h"
namespace fastboot {
int FastBootTest::MatchFastboot(usb_ifc_info* info, const char* local_serial) {
if (info->ifc_class != 0xff || info->ifc_subclass != 0x42 || info->ifc_protocol != 0x03) {
return -1;
}
cb_scratch = info->device_path;
// require matching serial number or device path if requested
// at the command line with the -s option.
if (local_serial && (strcmp(local_serial, info->serial_number) != 0 &&
strcmp(local_serial, info->device_path) != 0))
return -1;
return 0;
}
bool FastBootTest::UsbStillAvailible() {
// For some reason someone decided to prefix the path with "usb:"
std::string prefix("usb:");
if (std::equal(prefix.begin(), prefix.end(), device_path.begin())) {
std::string fname(device_path.begin() + prefix.size(), device_path.end());
std::string real_path =
android::base::StringPrintf("/sys/bus/usb/devices/%s/serial", fname.c_str());
std::ifstream f(real_path.c_str());
return f.good();
}
exit(-1); // This should never happen
return true;
}
RetCode FastBootTest::DownloadCommand(uint32_t size, std::string* response,
std::vector<std::string>* info) {
return fb->DownloadCommand(size, response, info);
}
RetCode FastBootTest::SendBuffer(const std::vector<char>& buf) {
return fb->SendBuffer(buf);
}
RetCode FastBootTest::HandleResponse(std::string* response, std::vector<std::string>* info,
int* dsize) {
return fb->HandleResponse(response, info, dsize);
}
void FastBootTest::SetUp() {
if (device_path != "") { // make sure the device is still connected
ASSERT_TRUE(UsbStillAvailible()); // The device disconnected
}
const auto matcher = [](usb_ifc_info* info) -> int { return MatchFastboot(info, nullptr); };
for (int i = 0; i < MAX_USB_TRIES && !transport; i++) {
std::unique_ptr<UsbTransport> usb(usb_open(matcher, USB_TIMEOUT));
if (usb)
transport = std::unique_ptr<UsbTransportSniffer>(
new UsbTransportSniffer(std::move(usb), serial_port));
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
ASSERT_TRUE(transport); // no nullptr
if (device_path == "") { // We set it the first time, then make sure it never changes
device_path = cb_scratch;
} else {
ASSERT_EQ(device_path, cb_scratch); // The path can not change
}
fb = std::unique_ptr<FastBootDriver>(
new FastBootDriver(transport.get(), [](std::string&) {}, true));
}
void FastBootTest::TearDown() {
EXPECT_TRUE(UsbStillAvailible()) << USB_PORT_GONE;
TearDownSerial();
fb.reset();
if (transport) {
transport->Close();
transport.reset();
}
ASSERT_TRUE(UsbStillAvailible()) << USB_PORT_GONE;
}
// TODO, this should eventually be piped to a file instead of stdout
void FastBootTest::TearDownSerial() {
if (!transport) return;
// One last read from serial
transport->ProcessSerial();
if (HasFailure()) {
// TODO, print commands leading up
printf("<<<<<<<< TRACE BEGIN >>>>>>>>>\n");
printf("%s", transport->CreateTrace().c_str());
printf("<<<<<<<< TRACE END >>>>>>>>>\n");
// std::vector<std::pair<const TransferType, const std::vector<char>>> prev =
// transport->Transfers();
}
}
void FastBootTest::SetLockState(bool unlock, bool assert_change) {
if (!fb) {
return;
}
std::string resp;
std::vector<std::string> info;
// To avoid risk of bricking device, make sure unlock ability is set to 1
ASSERT_EQ(fb->RawCommand("flashing get_unlock_ability", &resp, &info), SUCCESS)
<< "'flashing get_unlock_ability' failed";
// There are two ways this can be reported, through info or the actual response
if (!resp.empty()) { // must be in the info response
ASSERT_EQ(resp.back(), '1')
<< "Unlock ability must be set to 1 to avoid bricking device, see "
"'https://source.android.com/devices/bootloader/unlock-trusty'";
} else {
ASSERT_FALSE(info.empty()) << "'flashing get_unlock_ability' returned empty response";
ASSERT_FALSE(info.back().empty()) << "Expected non-empty info response";
ASSERT_EQ(info.back().back(), '1')
<< "Unlock ability must be set to 1 to avoid bricking device, see "
"'https://source.android.com/devices/bootloader/unlock-trusty'";
}
EXPECT_EQ(fb->GetVar("unlocked", &resp), SUCCESS) << "getvar:unlocked failed";
ASSERT_TRUE(resp == "no" || resp == "yes")
<< "getvar:unlocked response was not 'no' or 'yes': " + resp;
if ((unlock && resp == "no") || (!unlock && resp == "yes")) {
std::string cmd = unlock ? "unlock" : "lock";
ASSERT_EQ(fb->RawCommand("flashing " + cmd, &resp), SUCCESS)
<< "Attempting to change locked state, but 'flashing" + cmd + "' command failed";
fb.reset();
transport->Close();
transport.reset();
printf("PLEASE RESPOND TO PROMPT FOR '%sing' BOOTLOADER ON DEVICE\n", cmd.c_str());
while (UsbStillAvailible())
; // Wait for disconnect
printf("WAITING FOR DEVICE");
// Need to wait for device
const auto matcher = [](usb_ifc_info* info) -> int { return MatchFastboot(info, nullptr); };
while (!transport) {
std::unique_ptr<UsbTransport> usb(usb_open(matcher, USB_TIMEOUT));
if (usb) {
transport = std::unique_ptr<UsbTransportSniffer>(
new UsbTransportSniffer(std::move(usb), serial_port));
}
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
putchar('.');
}
device_path = cb_scratch;
fb = std::unique_ptr<FastBootDriver>(
new FastBootDriver(transport.get(), [](std::string&) {}, true));
if (assert_change) {
ASSERT_EQ(fb->GetVar("unlocked", &resp), SUCCESS) << "getvar:unlocked failed";
ASSERT_EQ(resp, unlock ? "yes" : "no")
<< "getvar:unlocked response was not 'no' or 'yes': " + resp;
}
printf("SUCCESS\n");
}
}
std::string FastBootTest::device_path = "";
std::string FastBootTest::cb_scratch = "";
int FastBootTest::serial_port = 0;
template <bool UNLOCKED>
void ModeTest<UNLOCKED>::SetUp() {
ASSERT_NO_FATAL_FAILURE(FastBootTest::SetUp());
ASSERT_NO_FATAL_FAILURE(SetLockState(UNLOCKED));
}
// Need to instatiate it, so linker can find it later
template class ModeTest<true>;
template class ModeTest<false>;
void Fuzz::TearDown() {
ASSERT_TRUE(UsbStillAvailible()) << USB_PORT_GONE;
TearDownSerial();
std::string tmp;
if (fb->GetVar("product", &tmp) != SUCCESS) {
printf("DEVICE UNRESPONSE, attempting to recover...");
transport->Reset();
printf("issued USB reset...");
if (fb->GetVar("product", &tmp) != SUCCESS) {
printf("FAIL\n");
exit(-1);
}
printf("SUCCESS!\n");
}
if (transport) {
transport->Close();
transport.reset();
}
ASSERT_TRUE(UsbStillAvailible()) << USB_PORT_GONE;
}
template <bool UNLOCKED>
void ExtensionsPartition<UNLOCKED>::SetUp() {
ASSERT_NO_FATAL_FAILURE(FastBootTest::SetUp());
ASSERT_NO_FATAL_FAILURE(SetLockState(UNLOCKED));
if (!fb) {
return;
}
const std::string name = GetParam().first;
std::string var;
ASSERT_EQ(fb->GetVar("slot-count", &var), SUCCESS) << "Getting slot count failed";
int32_t num_slots = strtol(var.c_str(), nullptr, 10);
real_parts = GeneratePartitionNames(name, GetParam().second.slots ? num_slots : 0);
ASSERT_EQ(fb->GetVar("partition-size:" + real_parts.front(), &var), SUCCESS)
<< "Getting partition size failed";
part_size = strtoll(var.c_str(), nullptr, 16);
ASSERT_GT(part_size, 0) << "Partition size reported was invalid";
ASSERT_EQ(fb->GetVar("max-download-size", &var), SUCCESS) << "Getting max download size failed";
max_dl = strtoll(var.c_str(), nullptr, 16);
ASSERT_GT(max_dl, 0) << "Max download size reported was invalid";
max_flash = std::min(part_size, max_dl);
}
template class ExtensionsPartition<true>;
template class ExtensionsPartition<false>;
} // end namespace fastboot

View file

@ -0,0 +1,136 @@
/*
* Copyright (C) 2018 The Android Open Source Project
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#pragma once
#include <gtest/gtest.h>
#include "fastboot_driver.h"
#include "extensions.h"
#include "usb_transport_sniffer.h"
namespace fastboot {
const int USB_TIMEOUT = 30000;
constexpr char USB_PORT_GONE[] =
"The USB port has disappeared, this is usually due to the bootloader crashing";
class FastBootTest : public testing::Test {
public:
static int serial_port;
static constexpr int MAX_USB_TRIES = 10;
static int MatchFastboot(usb_ifc_info* info, const char* local_serial = nullptr);
bool UsbStillAvailible();
protected:
RetCode DownloadCommand(uint32_t size, std::string* response = nullptr,
std::vector<std::string>* info = nullptr);
RetCode SendBuffer(const std::vector<char>& buf);
RetCode HandleResponse(std::string* response = nullptr,
std::vector<std::string>* info = nullptr, int* dsize = nullptr);
void SetUp() override;
void TearDown() override;
void TearDownSerial();
void SetLockState(bool unlock, bool assert_change = true);
std::unique_ptr<UsbTransportSniffer> transport;
std::unique_ptr<FastBootDriver> fb;
private:
// This is an annoying hack
static std::string cb_scratch;
static std::string device_path;
};
template <bool UNLOCKED>
class ModeTest : public FastBootTest {
protected:
void SetUp() override;
};
class Fuzz : public ModeTest<true> {
protected:
void TearDown() override;
};
// These derived classes without overrides serve no purpose other than to allow gtest to name them
// differently
class BasicFunctionality : public ModeTest<true> {};
class Conformance : public ModeTest<true> {};
class UnlockPermissions : public ModeTest<true> {};
class LockPermissions : public ModeTest<false> {};
// Magic C++ double inheritance
class ExtensionsGetVarConformance
: public ModeTest<true>,
public ::testing::WithParamInterface<
std::pair<std::string, extension::Configuration::GetVar>> {};
class ExtensionsOemConformance
: public ModeTest<true>,
public ::testing::WithParamInterface<
std::tuple<std::string, bool, extension::Configuration::CommandTest>> {};
class ExtensionsPackedValid
: public ModeTest<true>,
public ::testing::WithParamInterface<
std::pair<std::string, extension::Configuration::PackedInfoTest>> {};
class ExtensionsPackedInvalid
: public ModeTest<true>,
public ::testing::WithParamInterface<
std::pair<std::string, extension::Configuration::PackedInfoTest>> {};
template <bool UNLOCKED>
class ExtensionsPartition
: public FastBootTest,
public ::testing::WithParamInterface<
std::pair<std::string, extension::Configuration::PartitionInfo>> {
protected:
void SetUp() override;
int64_t part_size;
int64_t max_flash;
int64_t max_dl;
std::vector<std::string> real_parts; // includes the slots
};
class AnyPartition : public ExtensionsPartition<true> {};
class WriteablePartition : public ExtensionsPartition<true> {};
class WriteHashablePartition : public ExtensionsPartition<true> {};
class WriteHashNonParsedPartition : public ExtensionsPartition<true> {};
class FuzzWriteablePartition : public ExtensionsPartition<true> {};
class FuzzWriteableParsedPartition : public ExtensionsPartition<true> {};
class FuzzAnyPartitionLocked : public ExtensionsPartition<false> {};
class UserdataPartition : public ExtensionsPartition<true> {};
} // end namespace fastboot

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,49 @@
/*
* Copyright (C) 2018 The Android Open Source Project
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#pragma once
// TODO, print out logs for each failed test with custom listner
class MinimalistPrinter : public ::testing::EmptyTestEventListener {
// Called before a test starts.
virtual void OnTestStart(const ::testing::TestInfo& test_info) {
printf("*** Test %s.%s starting.\n", test_info.test_case_name(), test_info.name());
}
// Called after a failed assertion or a SUCCESS().
virtual void OnTestPartResult(const ::testing::TestPartResult& test_part_result) {
printf("%s in %s:%d\n%s\n", test_part_result.failed() ? "*** Failure" : "Success",
test_part_result.file_name(), test_part_result.line_number(),
test_part_result.summary());
}
// Called after a test ends.
virtual void OnTestEnd(const ::testing::TestInfo& test_info) {
printf("*** Test %s.%s ending.\n", test_info.test_case_name(), test_info.name());
}
};

View file

@ -0,0 +1,208 @@
/*
* Copyright (C) 2018 The Android Open Source Project
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include "test_utils.h"
#include <fcntl.h>
#include <termios.h>
#include <sstream>
namespace fastboot {
namespace {
constexpr int rand_seed = 0;
std::default_random_engine rnd(rand_seed);
} // namespace
char rand_legal() {
return rnd() % 128;
}
char rand_illegal() {
return rand_legal() + 128;
}
char rand_char() {
return rnd() % 256;
}
int random_int(int start, int end) {
std::uniform_int_distribution<int> uni(start, end);
return uni(rnd);
}
std::string RandomString(size_t length, std::function<char(void)> provider) {
std::string str(length, 0);
std::generate_n(str.begin(), length, provider);
return str;
}
std::vector<char> RandomBuf(size_t length, std::function<char(void)> provider) {
std::vector<char> ret;
ret.resize(length);
std::generate_n(ret.begin(), length, provider);
return ret;
}
std::vector<std::string> SplitBySpace(const std::string& s) {
std::istringstream iss(s);
return std::vector<std::string>{std::istream_iterator<std::string>{iss},
std::istream_iterator<std::string>{}};
}
std::vector<std::string> GeneratePartitionNames(const std::string& base, int num_slots) {
if (!num_slots) {
return std::vector<std::string>{base};
}
std::vector<std::string> ret;
for (char c = 'a'; c < 'a' + num_slots; c++) {
ret.push_back(base + '_' + c);
}
return ret;
}
std::unordered_map<std::string, std::string> ParseArgs(int argc, char** argv,
std::string* err_msg) {
// We ignore any gtest stuff
std::unordered_map<std::string, std::string> ret;
for (int i = 1; i < argc - 1; i++) {
std::string arg(argv[i]);
const std::string gtest_start("--gtest");
// We found a non gtest argument
if (!arg.find("-h") ||
(!arg.find("--") && arg.find("--gtest") && arg.find("=") != arg.npos)) {
const std::string start(arg.begin() + 2, arg.begin() + arg.find("="));
const std::string end(arg.begin() + arg.find("=") + 1, arg.end());
ret[start] = end;
} else if (arg.find("--gtest") != 0) {
*err_msg = android::base::StringPrintf("Illegal argument '%s'\n", arg.c_str());
return ret;
}
}
return ret;
}
int ConfigureSerial(const std::string& port) {
int fd = open(port.c_str(), O_RDONLY | O_NOCTTY | O_NONBLOCK);
if (fd <= 0) {
return fd;
}
struct termios tty;
tcgetattr(fd, &tty);
cfsetospeed(&tty, (speed_t)B115200);
cfsetispeed(&tty, (speed_t)B115200);
tty.c_cflag &= ~PARENB;
tty.c_cflag &= ~CSTOPB;
tty.c_cflag &= ~CSIZE;
tty.c_cflag |= CS8;
tty.c_cflag &= ~CRTSCTS;
tty.c_cc[VMIN] = 0;
tty.c_cc[VTIME] = 2;
tty.c_cflag &= ~(ICANON | ECHO | ECHOE | ISIG);
cfmakeraw(&tty);
tcflush(fd, TCIFLUSH);
if (tcsetattr(fd, TCSANOW, &tty) != 0) {
return -1;
}
return fd;
}
int StartProgram(const std::string program, const std::vector<std::string> args, int* rpipe) {
int link[2];
if (pipe(link) < 0) {
return -1;
}
pid_t pid = fork();
if (pid < 0) { // error
return -1;
}
if (pid) { // parent
close(link[1]);
*rpipe = link[0];
fcntl(*rpipe, F_SETFL, O_NONBLOCK); // Non-blocking
} else { // child
std::vector<const char*> argv(args.size() + 2, nullptr);
argv[0] = program.c_str();
for (int i = 0; i < args.size(); i++) {
argv[i + 1] = args[i].c_str();
}
// We pipe any stderr writes to the parent test process
dup2(link[1], STDERR_FILENO); // close stdout and have it now be link[1]
// Close duplicates
close(link[0]);
close(link[1]);
execvp(program.c_str(), const_cast<char* const*>(argv.data()));
fprintf(stderr, "Launching validator process '%s' failed with: %s\n", program.c_str(),
strerror(errno));
exit(-1);
}
return pid;
}
int WaitProgram(const int pid, const int pipe, std::string* error_msg) {
int status;
if (waitpid(pid, &status, 0) != pid) {
close(pipe);
return -1;
}
// Read from pipe
char buf[1024];
int n;
while ((n = read(pipe, buf, sizeof(buf))) > 0) {
buf[n] = 0; /* terminate the string */
error_msg->append(buf, n);
}
close(pipe);
if (WIFEXITED(status)) {
// This WEXITSTATUS macro masks off lower bytes, with no sign extension
// casting it as a signed char fixes the sign extension issue
int retmask = WEXITSTATUS(status);
return reinterpret_cast<int8_t*>(&retmask)[0];
}
return -1;
}
} // namespace fastboot

View file

@ -0,0 +1,93 @@
/*
* Copyright (C) 2018 The Android Open Source Project
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#pragma once
#include <sparse/sparse.h>
#include <stdint.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <cstdlib>
#include <fstream>
#include <random>
#include <string>
#include <unordered_map>
#include "fastboot_driver.h"
namespace fastboot {
char rand_legal();
char rand_illegal();
char rand_char();
// start and end are inclusive
int random_int(int start, int end);
// I don't want to have to manage memory for this guy
struct SparseWrapper {
SparseWrapper(unsigned int block_size, int64_t len) {
sparse = sparse_file_new(block_size, len);
}
SparseWrapper(struct sparse_file* sf) { sparse = sf; }
~SparseWrapper() {
if (sparse) {
sparse_file_destroy(sparse);
}
}
const std::string Rep() {
unsigned bs = sparse_file_block_size(sparse);
unsigned len = sparse_file_len(sparse, true, false);
return android::base::StringPrintf("[block_size=%u, len=%u]", bs, len);
}
struct sparse_file* operator*() {
return sparse;
}
struct sparse_file* sparse;
};
std::string RandomString(size_t length, std::function<char(void)> provider);
// RVO will avoid copy
std::vector<char> RandomBuf(size_t length, std::function<char(void)> provider = rand_char);
std::vector<std::string> SplitBySpace(const std::string& s);
std::unordered_map<std::string, std::string> ParseArgs(int argc, char** argv, std::string* err_msg);
std::vector<std::string> GeneratePartitionNames(const std::string& base, int num_slots = 0);
int ConfigureSerial(const std::string& port);
int StartProgram(const std::string program, const std::vector<std::string> args, int* pipe);
int WaitProgram(const pid_t pid, const int pipe, std::string* error_msg);
} // namespace fastboot

View file

@ -0,0 +1,189 @@
#include "usb_transport_sniffer.h"
#include <android-base/stringprintf.h>
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <iomanip>
#include <sstream>
namespace fastboot {
UsbTransportSniffer::UsbTransportSniffer(std::unique_ptr<UsbTransport> transport,
const int serial_fd)
: transport_(std::move(transport)), serial_fd_(serial_fd) {}
ssize_t UsbTransportSniffer::Read(void* data, size_t len) {
ProcessSerial();
ssize_t ret = transport_->Read(data, len);
if (ret < 0) {
const char* err = strerror(errno);
std::vector<char> buf(err, err + strlen(err));
Event e(READ_ERROR, std::move(buf));
transfers_.push_back(e);
return ret;
}
char* cdata = static_cast<char*>(data);
std::vector<char> buf(cdata, cdata + ret);
Event e(READ, std::move(buf));
transfers_.push_back(e);
ProcessSerial();
return ret;
}
ssize_t UsbTransportSniffer::Write(const void* data, size_t len) {
ProcessSerial();
size_t ret = transport_->Write(data, len);
if (ret != len) {
const char* err = strerror(errno);
std::vector<char> buf(err, err + strlen(err));
Event e(WRITE_ERROR, std::move(buf));
transfers_.push_back(e);
return ret;
}
const char* cdata = static_cast<const char*>(data);
std::vector<char> buf(cdata, cdata + len);
Event e(WRITE, std::move(buf));
transfers_.push_back(e);
ProcessSerial();
return ret;
}
int UsbTransportSniffer::Close() {
return transport_->Close();
}
int UsbTransportSniffer::Reset() {
ProcessSerial();
int ret = transport_->Reset();
std::vector<char> buf;
Event e(RESET, std::move(buf));
transfers_.push_back(e);
ProcessSerial();
return ret;
}
const std::vector<UsbTransportSniffer::Event> UsbTransportSniffer::Transfers() {
return transfers_;
}
/*
* When a test fails, we want a human readable log of everything going on up until
* the failure. This method will look through its log of captured events, and
* create a clean printable string of everything that happened.
*/
std::string UsbTransportSniffer::CreateTrace() {
std::string ret;
const auto no_print = [](char c) -> bool { return !isprint(c); };
// This lambda creates a humand readable representation of a byte buffer
// It first attempts to figure out whether it should be interpreted as an ASCII buffer,
// and be printed as a string, or just a raw byte-buffer
const auto msg = [&ret, no_print](const std::vector<char>& buf) {
ret += android::base::StringPrintf("(%lu bytes): ", buf.size());
std::vector<const char>::iterator iter = buf.end();
const unsigned max_chars = 50;
if (buf.size() > max_chars) {
iter = buf.begin() + max_chars;
}
ret += '"';
if (std::count_if(buf.begin(), iter, no_print) == 0) { // print as ascii
ret.insert(ret.end(), buf.begin(), iter);
} else { // print as hex
std::stringstream ss;
for (auto c = buf.begin(); c < iter; c++) {
ss << std::hex << std::setw(2) << std::setfill('0')
<< static_cast<uint16_t>(static_cast<uint8_t>(*c));
ss << ',';
}
ret += ss.str();
}
if (buf.size() > max_chars) {
ret += android::base::StringPrintf("...\"(+%lu bytes)\n", buf.size() - max_chars);
} else {
ret += "\"\n";
}
};
// Now we just scan through the log of everything that happened and create a
// printable string for each one
for (const auto& event : transfers_) {
const std::vector<char>& cbuf = event.buf;
const std::string tmp(cbuf.begin(), cbuf.end());
auto start = transfers_.front().start;
auto durr = event.start - start;
auto millis = std::chrono::duration_cast<std::chrono::milliseconds>(durr).count();
switch (event.type) {
case READ:
ret += android::base::StringPrintf("[READ %lldms]", millis);
msg(cbuf);
break;
case WRITE:
ret += android::base::StringPrintf("[WRITE %lldms]", millis);
msg(cbuf);
break;
case RESET:
ret += android::base::StringPrintf("[RESET %lldms]\n", millis);
break;
case READ_ERROR:
ret += android::base::StringPrintf("[READ_ERROR %lldms] %s\n", millis, tmp.c_str());
break;
case WRITE_ERROR:
ret += android::base::StringPrintf("[WRITE_ERROR %lldms] %s\n", millis,
tmp.c_str());
break;
case SERIAL:
ret += android::base::StringPrintf("[SERIAL %lldms] %s", millis, tmp.c_str());
if (ret.back() != '\n') ret += '\n';
break;
}
}
return ret;
}
// This is a quick call to flush any UART logs the device might have sent
// to our internal event log. It will wait up to 10ms for data to appear
void UsbTransportSniffer::ProcessSerial() {
if (serial_fd_ <= 0) return;
fd_set set;
struct timeval timeout;
FD_ZERO(&set);
FD_SET(serial_fd_, &set);
timeout.tv_sec = 0;
timeout.tv_usec = 10000; // 10ms
int count = 0;
int n = 0;
std::vector<char> buf;
buf.resize(1000);
while (select(serial_fd_ + 1, &set, NULL, NULL, &timeout) > 0) {
n = read(serial_fd_, buf.data() + count, buf.size() - count);
if (n > 0) {
count += n;
} else {
break;
}
}
buf.resize(count);
if (count > 0) {
Event e(SERIAL, std::move(buf));
transfers_.push_back(e);
}
}
} // namespace fastboot

View file

@ -0,0 +1,87 @@
/*
* Copyright (C) 2018 The Android Open Source Project
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#pragma once
#include <sys/types.h>
#include <unistd.h>
#include <chrono>
#include <cstdlib>
#include <fstream>
#include <string>
#include <vector>
#include "usb.h"
namespace fastboot {
/* A special class for sniffing reads and writes
*
* A useful debugging tool is to see the raw fastboot transactions going between
* the host and device. This class wraps the UsbTransport class, and snoops and saves
* all the transactions going on. Additionally, if there is a console serial port
* from the device, this class can monitor it as well and capture the interleaving of
* transport transactions and UART log messages.
*/
class UsbTransportSniffer : public UsbTransport {
public:
enum EventType {
READ,
WRITE,
RESET,
SERIAL, // Serial log message from device
READ_ERROR,
WRITE_ERROR,
};
struct Event {
Event(EventType t, const std::vector<char> cbuf) : type(t), buf(cbuf) {
start = std::chrono::high_resolution_clock::now();
};
EventType type;
std::chrono::high_resolution_clock::time_point start;
const std::vector<char> buf;
};
UsbTransportSniffer(std::unique_ptr<UsbTransport> transport, const int serial_fd = 0);
virtual ssize_t Read(void* data, size_t len) override;
virtual ssize_t Write(const void* data, size_t len) override;
virtual int Close() override;
virtual int Reset() override;
const std::vector<Event> Transfers();
std::string CreateTrace();
void ProcessSerial();
private:
std::vector<Event> transfers_;
std::unique_ptr<UsbTransport> transport_;
const int serial_fd_;
};
} // End namespace fastboot