Merge "fastboot: add TCP protocol."
This commit is contained in:
commit
80dc9d8584
|
@ -31,6 +31,7 @@ LOCAL_SRC_FILES := \
|
|||
fs.cpp\
|
||||
protocol.cpp \
|
||||
socket.cpp \
|
||||
tcp.cpp \
|
||||
util.cpp \
|
||||
|
||||
LOCAL_MODULE := fastboot
|
||||
|
@ -111,6 +112,8 @@ LOCAL_SRC_FILES := \
|
|||
socket.cpp \
|
||||
socket_mock.cpp \
|
||||
socket_test.cpp \
|
||||
tcp.cpp \
|
||||
tcp_test.cpp \
|
||||
|
||||
LOCAL_STATIC_LIBRARIES := libbase libcutils
|
||||
|
||||
|
|
|
@ -42,22 +42,22 @@
|
|||
#include <sys/time.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <functional>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <android-base/parseint.h>
|
||||
#include <android-base/parsenetaddress.h>
|
||||
#include <android-base/strings.h>
|
||||
#include <sparse/sparse.h>
|
||||
#include <ziparchive/zip_archive.h>
|
||||
|
||||
#include <android-base/strings.h>
|
||||
#include <android-base/parseint.h>
|
||||
|
||||
#include "bootimg_utils.h"
|
||||
#include "diagnose_usb.h"
|
||||
#include "fastboot.h"
|
||||
#include "fs.h"
|
||||
#include "tcp.h"
|
||||
#include "transport.h"
|
||||
#include "usb.h"
|
||||
|
||||
|
@ -69,9 +69,9 @@
|
|||
|
||||
char cur_product[FB_RESPONSE_SZ + 1];
|
||||
|
||||
static const char *serial = 0;
|
||||
static const char *product = 0;
|
||||
static const char *cmdline = 0;
|
||||
static const char* serial = nullptr;
|
||||
static const char* product = nullptr;
|
||||
static const char* cmdline = nullptr;
|
||||
static unsigned short vendor_id = 0;
|
||||
static int long_listing = 0;
|
||||
static int64_t sparse_limit = -1;
|
||||
|
@ -227,17 +227,51 @@ static int list_devices_callback(usb_ifc_info* info) {
|
|||
return -1;
|
||||
}
|
||||
|
||||
// Opens a new Transport connected to a device. If |serial| is non-null it will be used to identify
|
||||
// a specific device, otherwise the first USB device found will be used.
|
||||
//
|
||||
// If |serial| is non-null but invalid, this prints an error message to stderr and returns nullptr.
|
||||
// Otherwise it blocks until the target is available.
|
||||
//
|
||||
// The returned Transport is a singleton, so multiple calls to this function will return the same
|
||||
// object, and the caller should not attempt to delete the returned Transport.
|
||||
static Transport* open_device() {
|
||||
static Transport* transport = nullptr;
|
||||
int announce = 1;
|
||||
bool announce = true;
|
||||
|
||||
if (transport) return transport;
|
||||
if (transport != nullptr) {
|
||||
return transport;
|
||||
}
|
||||
|
||||
std::string host;
|
||||
int port = tcp::kDefaultPort;
|
||||
if (serial != nullptr && android::base::StartsWith(serial, "tcp:")) {
|
||||
std::string error;
|
||||
const char* address = serial + strlen("tcp:");
|
||||
|
||||
if (!android::base::ParseNetAddress(address, &host, &port, nullptr, &error)) {
|
||||
fprintf(stderr, "error: Invalid network address '%s': %s\n", address, error.c_str());
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
while (true) {
|
||||
if (!host.empty()) {
|
||||
std::string error;
|
||||
transport = tcp::Connect(host, port, &error).release();
|
||||
if (transport == nullptr && announce) {
|
||||
fprintf(stderr, "error: %s\n", error.c_str());
|
||||
}
|
||||
} else {
|
||||
transport = usb_open(match_fastboot);
|
||||
}
|
||||
|
||||
if (transport != nullptr) {
|
||||
return transport;
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
transport = usb_open(match_fastboot);
|
||||
if (transport) return transport;
|
||||
if (announce) {
|
||||
announce = 0;
|
||||
announce = false;
|
||||
fprintf(stderr, "< waiting for %s >\n", serial ? serial : "any device");
|
||||
}
|
||||
usleep(1000);
|
||||
|
@ -299,8 +333,10 @@ static void usage() {
|
|||
" if supported by partition type).\n"
|
||||
" -u Do not erase partition before\n"
|
||||
" formatting.\n"
|
||||
" -s <specific device> Specify device serial number\n"
|
||||
" or path to device port.\n"
|
||||
" -s <specific device> Specify a device. For USB, provide either\n"
|
||||
" a serial number or path to device port.\n"
|
||||
" For TCP, provide an address in the form\n"
|
||||
" tcp:<hostname>[:port].\n"
|
||||
" -p <product> Specify product name.\n"
|
||||
" -c <cmdline> Override kernel commandline.\n"
|
||||
" -i <vendor id> Specify a custom USB vendor id.\n"
|
||||
|
@ -1263,6 +1299,10 @@ int main(int argc, char **argv)
|
|||
}
|
||||
|
||||
Transport* transport = open_device();
|
||||
if (transport == nullptr) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (slot_override != "")
|
||||
slot_override = verify_slot(transport, slot_override.c_str());
|
||||
if (next_active != "")
|
||||
|
|
|
@ -3,19 +3,25 @@ FastBoot Version 0.4
|
|||
----------------------
|
||||
|
||||
The fastboot protocol is a mechanism for communicating with bootloaders
|
||||
over USB. It is designed to be very straightforward to implement, to
|
||||
allow it to be used across a wide range of devices and from hosts running
|
||||
over USB or ethernet. It is designed to be very straightforward to implement,
|
||||
to allow it to be used across a wide range of devices and from hosts running
|
||||
Linux, Windows, or OSX.
|
||||
|
||||
|
||||
Basic Requirements
|
||||
------------------
|
||||
|
||||
* Two bulk endpoints (in, out) are required
|
||||
* Max packet size must be 64 bytes for full-speed, 512 bytes for
|
||||
high-speed and 1024 bytes for Super Speed USB.
|
||||
* The protocol is entirely host-driven and synchronous (unlike the
|
||||
multi-channel, bi-directional, asynchronous ADB protocol)
|
||||
* USB
|
||||
* Two bulk endpoints (in, out) are required
|
||||
* Max packet size must be 64 bytes for full-speed, 512 bytes for
|
||||
high-speed and 1024 bytes for Super Speed USB.
|
||||
* The protocol is entirely host-driven and synchronous (unlike the
|
||||
multi-channel, bi-directional, asynchronous ADB protocol)
|
||||
|
||||
* TCP
|
||||
* Device must be reachable via IP.
|
||||
* Device will act as the TCP server, fastboot will be the client.
|
||||
* Fastboot data is wrapped in a simple protocol; see below for details.
|
||||
|
||||
|
||||
Transport and Framing
|
||||
|
@ -171,3 +177,44 @@ specification. OEM-specific names should not start with lowercase
|
|||
characters.
|
||||
|
||||
|
||||
TCP Protocol v1
|
||||
---------------
|
||||
|
||||
The TCP protocol is designed to be a simple way to use the fastboot protocol
|
||||
over ethernet if USB is not available.
|
||||
|
||||
The device will open a TCP server on port 5554 and wait for a fastboot client
|
||||
to connect.
|
||||
|
||||
-- Handshake --
|
||||
Upon connecting, both sides will send a 4-byte handshake message to ensure they
|
||||
are speaking the same protocol. This consists of the ASCII characters "FB"
|
||||
followed by a 2-digit base-10 ASCII version number. For example, the version 1
|
||||
handshake message will be [FB01].
|
||||
|
||||
If either side detects a malformed handshake, it should disconnect.
|
||||
|
||||
The protocol version to use must be the minimum of the versions sent by each
|
||||
side; if either side cannot speak this protocol version, it should disconnect.
|
||||
|
||||
-- Fastboot Data --
|
||||
Once the handshake is complete, fastboot data will be sent as follows:
|
||||
|
||||
[data_size][data]
|
||||
|
||||
Where data_size is an unsigned 8-byte big-endian binary value, and data is the
|
||||
fastboot packet. The 8-byte length is intended to provide future-proofing even
|
||||
though currently fastboot packets have a 4-byte maximum length.
|
||||
|
||||
-- Example --
|
||||
In this example the fastboot host queries the device for two variables,
|
||||
"version" and "none".
|
||||
|
||||
Host <connect to the device on port 5555>
|
||||
Host FB01
|
||||
Device FB01
|
||||
Host [0x00][0x00][0x00][0x00][0x00][0x00][0x00][0x0E]getvar:version
|
||||
Device [0x00][0x00][0x00][0x00][0x00][0x00][0x00][0x07]OKAY0.4
|
||||
Host [0x00][0x00][0x00][0x00][0x00][0x00][0x00][0x0B]getvar:none
|
||||
Device [0x00][0x00][0x00][0x00][0x00][0x00][0x00][0x04]OKAY
|
||||
Host <disconnect>
|
||||
|
|
|
@ -0,0 +1,196 @@
|
|||
/*
|
||||
* Copyright (C) 2016 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 "tcp.h"
|
||||
|
||||
#include <android-base/stringprintf.h>
|
||||
|
||||
namespace tcp {
|
||||
|
||||
static constexpr int kProtocolVersion = 1;
|
||||
static constexpr size_t kHandshakeLength = 4;
|
||||
static constexpr int kHandshakeTimeoutMs = 2000;
|
||||
|
||||
// Extract the big-endian 8-byte message length into a 64-bit number.
|
||||
static uint64_t ExtractMessageLength(const void* buffer) {
|
||||
uint64_t ret = 0;
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
ret |= uint64_t{reinterpret_cast<const uint8_t*>(buffer)[i]} << (56 - i * 8);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Encode the 64-bit number into a big-endian 8-byte message length.
|
||||
static void EncodeMessageLength(uint64_t length, void* buffer) {
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
reinterpret_cast<uint8_t*>(buffer)[i] = length >> (56 - i * 8);
|
||||
}
|
||||
}
|
||||
|
||||
class TcpTransport : public Transport {
|
||||
public:
|
||||
// Factory function so we can return nullptr if initialization fails.
|
||||
static std::unique_ptr<TcpTransport> NewTransport(std::unique_ptr<Socket> socket,
|
||||
std::string* error);
|
||||
|
||||
~TcpTransport() override = default;
|
||||
|
||||
ssize_t Read(void* data, size_t length) override;
|
||||
ssize_t Write(const void* data, size_t length) override;
|
||||
int Close() override;
|
||||
|
||||
private:
|
||||
TcpTransport(std::unique_ptr<Socket> sock) : socket_(std::move(sock)) {}
|
||||
|
||||
// Connects to the device and performs the initial handshake. Returns false and fills |error|
|
||||
// on failure.
|
||||
bool InitializeProtocol(std::string* error);
|
||||
|
||||
std::unique_ptr<Socket> socket_;
|
||||
uint64_t message_bytes_left_ = 0;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(TcpTransport);
|
||||
};
|
||||
|
||||
std::unique_ptr<TcpTransport> TcpTransport::NewTransport(std::unique_ptr<Socket> socket,
|
||||
std::string* error) {
|
||||
std::unique_ptr<TcpTransport> transport(new TcpTransport(std::move(socket)));
|
||||
|
||||
if (!transport->InitializeProtocol(error)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return transport;
|
||||
}
|
||||
|
||||
// These error strings are checked in tcp_test.cpp and should be kept in sync.
|
||||
bool TcpTransport::InitializeProtocol(std::string* error) {
|
||||
std::string handshake_message(android::base::StringPrintf("FB%02d", kProtocolVersion));
|
||||
|
||||
if (!socket_->Send(handshake_message.c_str(), kHandshakeLength)) {
|
||||
*error = android::base::StringPrintf("Failed to send initialization message (%s)",
|
||||
Socket::GetErrorMessage().c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
char buffer[kHandshakeLength];
|
||||
if (socket_->ReceiveAll(buffer, kHandshakeLength, kHandshakeTimeoutMs) != kHandshakeLength) {
|
||||
*error = android::base::StringPrintf(
|
||||
"No initialization message received (%s). Target may not support TCP fastboot",
|
||||
Socket::GetErrorMessage().c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (memcmp(buffer, "FB", 2) != 0) {
|
||||
*error = "Unrecognized initialization message. Target may not support TCP fastboot";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (memcmp(buffer + 2, "01", 2) != 0) {
|
||||
*error = android::base::StringPrintf("Unknown TCP protocol version %s (host version %02d)",
|
||||
std::string(buffer + 2, 2).c_str(), kProtocolVersion);
|
||||
return false;
|
||||
}
|
||||
|
||||
error->clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
ssize_t TcpTransport::Read(void* data, size_t length) {
|
||||
if (socket_ == nullptr) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Unless we're mid-message, read the next 8-byte message length.
|
||||
if (message_bytes_left_ == 0) {
|
||||
char buffer[8];
|
||||
if (socket_->ReceiveAll(buffer, 8, 0) != 8) {
|
||||
Close();
|
||||
return -1;
|
||||
}
|
||||
message_bytes_left_ = ExtractMessageLength(buffer);
|
||||
}
|
||||
|
||||
// Now read the message (up to |length| bytes).
|
||||
if (length > message_bytes_left_) {
|
||||
length = message_bytes_left_;
|
||||
}
|
||||
ssize_t bytes_read = socket_->ReceiveAll(data, length, 0);
|
||||
if (bytes_read == -1) {
|
||||
Close();
|
||||
} else {
|
||||
message_bytes_left_ -= bytes_read;
|
||||
}
|
||||
return bytes_read;
|
||||
}
|
||||
|
||||
ssize_t TcpTransport::Write(const void* data, size_t length) {
|
||||
if (socket_ == nullptr) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Use multi-buffer writes for better performance.
|
||||
char header[8];
|
||||
EncodeMessageLength(length, header);
|
||||
if (!socket_->Send(std::vector<cutils_socket_buffer_t>{{header, 8}, {data, length}})) {
|
||||
Close();
|
||||
return -1;
|
||||
}
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
int TcpTransport::Close() {
|
||||
if (socket_ == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int result = socket_->Close();
|
||||
socket_.reset();
|
||||
return result;
|
||||
}
|
||||
|
||||
std::unique_ptr<Transport> Connect(const std::string& hostname, int port, std::string* error) {
|
||||
return internal::Connect(Socket::NewClient(Socket::Protocol::kTcp, hostname, port, error),
|
||||
error);
|
||||
}
|
||||
|
||||
namespace internal {
|
||||
|
||||
std::unique_ptr<Transport> Connect(std::unique_ptr<Socket> sock, std::string* error) {
|
||||
if (sock == nullptr) {
|
||||
// If Socket creation failed |error| is already set.
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return TcpTransport::NewTransport(std::move(sock), error);
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
|
||||
} // namespace tcp
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* Copyright (C) 2016 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.
|
||||
*/
|
||||
|
||||
#ifndef TCP_H_
|
||||
#define TCP_H_
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include <android-base/macros.h>
|
||||
|
||||
#include "socket.h"
|
||||
#include "transport.h"
|
||||
|
||||
namespace tcp {
|
||||
|
||||
constexpr int kDefaultPort = 5554;
|
||||
|
||||
// Returns a newly allocated Transport object connected to |hostname|:|port|. On failure, |error| is
|
||||
// filled and nullptr is returned.
|
||||
std::unique_ptr<Transport> Connect(const std::string& hostname, int port, std::string* error);
|
||||
|
||||
// Internal namespace for test use only.
|
||||
namespace internal {
|
||||
|
||||
// Creates a TCP Transport object but using a given Socket instead of connecting to a hostname.
|
||||
// Used for unit tests to create a Transport object that uses a SocketMock.
|
||||
std::unique_ptr<Transport> Connect(std::unique_ptr<Socket> sock, std::string* error);
|
||||
|
||||
} // namespace internal
|
||||
|
||||
} // namespace tcp
|
||||
|
||||
#endif // TCP_H_
|
|
@ -0,0 +1,217 @@
|
|||
/*
|
||||
* Copyright (C) 2016 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 "tcp.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "socket_mock.h"
|
||||
|
||||
TEST(TcpConnectTest, TestSuccess) {
|
||||
std::unique_ptr<SocketMock> mock(new SocketMock);
|
||||
mock->ExpectSend("FB01");
|
||||
mock->AddReceive("FB01");
|
||||
|
||||
std::string error;
|
||||
EXPECT_NE(nullptr, tcp::internal::Connect(std::move(mock), &error));
|
||||
EXPECT_EQ("", error);
|
||||
}
|
||||
|
||||
TEST(TcpConnectTest, TestSendFailure) {
|
||||
std::unique_ptr<SocketMock> mock(new SocketMock);
|
||||
mock->ExpectSendFailure("FB01");
|
||||
|
||||
std::string error;
|
||||
EXPECT_EQ(nullptr, tcp::internal::Connect(std::move(mock), &error));
|
||||
EXPECT_NE(std::string::npos, error.find("Failed to send initialization message"));
|
||||
}
|
||||
|
||||
TEST(TcpConnectTest, TestNoResponseFailure) {
|
||||
std::unique_ptr<SocketMock> mock(new SocketMock);
|
||||
mock->ExpectSend("FB01");
|
||||
mock->AddReceiveFailure();
|
||||
|
||||
std::string error;
|
||||
EXPECT_EQ(nullptr, tcp::internal::Connect(std::move(mock), &error));
|
||||
EXPECT_NE(std::string::npos, error.find("No initialization message received"));
|
||||
}
|
||||
|
||||
TEST(TcpConnectTest, TestBadResponseFailure) {
|
||||
std::unique_ptr<SocketMock> mock(new SocketMock);
|
||||
mock->ExpectSend("FB01");
|
||||
mock->AddReceive("XX01");
|
||||
|
||||
std::string error;
|
||||
EXPECT_EQ(nullptr, tcp::internal::Connect(std::move(mock), &error));
|
||||
EXPECT_NE(std::string::npos, error.find("Unrecognized initialization message"));
|
||||
}
|
||||
|
||||
TEST(TcpConnectTest, TestUnknownVersionFailure) {
|
||||
std::unique_ptr<SocketMock> mock(new SocketMock);
|
||||
mock->ExpectSend("FB01");
|
||||
mock->AddReceive("FB02");
|
||||
|
||||
std::string error;
|
||||
EXPECT_EQ(nullptr, tcp::internal::Connect(std::move(mock), &error));
|
||||
EXPECT_EQ("Unknown TCP protocol version 02 (host version 01)", error);
|
||||
}
|
||||
|
||||
// Fixture to configure a SocketMock for a successful TCP connection.
|
||||
class TcpTest : public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
mock_ = new SocketMock;
|
||||
mock_->ExpectSend("FB01");
|
||||
mock_->AddReceive("FB01");
|
||||
|
||||
std::string error;
|
||||
transport_ = tcp::internal::Connect(std::unique_ptr<Socket>(mock_), &error);
|
||||
ASSERT_NE(nullptr, transport_);
|
||||
ASSERT_EQ("", error);
|
||||
};
|
||||
|
||||
// Writes |message| to |transport_|, returns true on success.
|
||||
bool Write(const std::string& message) {
|
||||
return transport_->Write(message.data(), message.length()) ==
|
||||
static_cast<ssize_t>(message.length());
|
||||
}
|
||||
|
||||
// Reads from |transport_|, returns true if it matches |message|.
|
||||
bool Read(const std::string& message) {
|
||||
std::string buffer(message.length(), '\0');
|
||||
return transport_->Read(&buffer[0], buffer.length()) ==
|
||||
static_cast<ssize_t>(message.length()) &&
|
||||
buffer == message;
|
||||
}
|
||||
|
||||
// Use a raw SocketMock* here because we pass ownership to the Transport object, but we still
|
||||
// need access to configure mock expectations.
|
||||
SocketMock* mock_ = nullptr;
|
||||
std::unique_ptr<Transport> transport_;
|
||||
};
|
||||
|
||||
TEST_F(TcpTest, TestWriteSuccess) {
|
||||
mock_->ExpectSend(std::string{0, 0, 0, 0, 0, 0, 0, 3} + "foo");
|
||||
|
||||
EXPECT_TRUE(Write("foo"));
|
||||
}
|
||||
|
||||
TEST_F(TcpTest, TestReadSuccess) {
|
||||
mock_->AddReceive(std::string{0, 0, 0, 0, 0, 0, 0, 3});
|
||||
mock_->AddReceive("foo");
|
||||
|
||||
EXPECT_TRUE(Read("foo"));
|
||||
}
|
||||
|
||||
// Tests that fragmented TCP reads are handled properly.
|
||||
TEST_F(TcpTest, TestReadFragmentSuccess) {
|
||||
mock_->AddReceive(std::string{0, 0, 0, 0});
|
||||
mock_->AddReceive(std::string{0, 0, 0, 3});
|
||||
mock_->AddReceive("f");
|
||||
mock_->AddReceive("o");
|
||||
mock_->AddReceive("o");
|
||||
|
||||
EXPECT_TRUE(Read("foo"));
|
||||
}
|
||||
|
||||
TEST_F(TcpTest, TestLargeWriteSuccess) {
|
||||
// 0x100000 = 1MiB.
|
||||
std::string data(0x100000, '\0');
|
||||
for (size_t i = 0; i < data.length(); ++i) {
|
||||
data[i] = i;
|
||||
}
|
||||
mock_->ExpectSend(std::string{0, 0, 0, 0, 0, 0x10, 0, 0} + data);
|
||||
|
||||
EXPECT_TRUE(Write(data));
|
||||
}
|
||||
|
||||
TEST_F(TcpTest, TestLargeReadSuccess) {
|
||||
// 0x100000 = 1MiB.
|
||||
std::string data(0x100000, '\0');
|
||||
for (size_t i = 0; i < data.length(); ++i) {
|
||||
data[i] = i;
|
||||
}
|
||||
mock_->AddReceive(std::string{0, 0, 0, 0, 0, 0x10, 0, 0});
|
||||
mock_->AddReceive(data);
|
||||
|
||||
EXPECT_TRUE(Read(data));
|
||||
}
|
||||
|
||||
// Tests a few sample fastboot protocol commands.
|
||||
TEST_F(TcpTest, TestFastbootProtocolSuccess) {
|
||||
mock_->ExpectSend(std::string{0, 0, 0, 0, 0, 0, 0, 14} + "getvar:version");
|
||||
mock_->AddReceive(std::string{0, 0, 0, 0, 0, 0, 0, 7});
|
||||
mock_->AddReceive("OKAY0.4");
|
||||
|
||||
mock_->ExpectSend(std::string{0, 0, 0, 0, 0, 0, 0, 10} + "getvar:all");
|
||||
mock_->AddReceive(std::string{0, 0, 0, 0, 0, 0, 0, 16});
|
||||
mock_->AddReceive("INFOversion: 0.4");
|
||||
mock_->AddReceive(std::string{0, 0, 0, 0, 0, 0, 0, 12});
|
||||
mock_->AddReceive("INFOfoo: bar");
|
||||
mock_->AddReceive(std::string{0, 0, 0, 0, 0, 0, 0, 4});
|
||||
mock_->AddReceive("OKAY");
|
||||
|
||||
EXPECT_TRUE(Write("getvar:version"));
|
||||
EXPECT_TRUE(Read("OKAY0.4"));
|
||||
|
||||
EXPECT_TRUE(Write("getvar:all"));
|
||||
EXPECT_TRUE(Read("INFOversion: 0.4"));
|
||||
EXPECT_TRUE(Read("INFOfoo: bar"));
|
||||
EXPECT_TRUE(Read("OKAY"));
|
||||
}
|
||||
|
||||
TEST_F(TcpTest, TestReadLengthFailure) {
|
||||
mock_->AddReceiveFailure();
|
||||
|
||||
char buffer[16];
|
||||
EXPECT_EQ(-1, transport_->Read(buffer, sizeof(buffer)));
|
||||
}
|
||||
|
||||
TEST_F(TcpTest, TestReadDataFailure) {
|
||||
mock_->AddReceive(std::string{0, 0, 0, 0, 0, 0, 0, 3});
|
||||
mock_->AddReceiveFailure();
|
||||
|
||||
char buffer[16];
|
||||
EXPECT_EQ(-1, transport_->Read(buffer, sizeof(buffer)));
|
||||
}
|
||||
|
||||
TEST_F(TcpTest, TestWriteFailure) {
|
||||
mock_->ExpectSendFailure(std::string{0, 0, 0, 0, 0, 0, 0, 3} + "foo");
|
||||
|
||||
EXPECT_EQ(-1, transport_->Write("foo", 3));
|
||||
}
|
||||
|
||||
TEST_F(TcpTest, TestTransportClose) {
|
||||
EXPECT_EQ(0, transport_->Close());
|
||||
|
||||
// After closing, Transport Read()/Write() should return -1 without actually attempting any
|
||||
// network operations.
|
||||
char buffer[16];
|
||||
EXPECT_EQ(-1, transport_->Read(buffer, sizeof(buffer)));
|
||||
EXPECT_EQ(-1, transport_->Write("foo", 3));
|
||||
}
|
Loading…
Reference in New Issue