Merge "fastboot: add TCP protocol."

This commit is contained in:
David Pursell 2016-02-03 21:59:31 +00:00 committed by Gerrit Code Review
commit 80dc9d8584
6 changed files with 583 additions and 21 deletions

View file

@ -31,6 +31,7 @@ LOCAL_SRC_FILES := \
fs.cpp\ fs.cpp\
protocol.cpp \ protocol.cpp \
socket.cpp \ socket.cpp \
tcp.cpp \
util.cpp \ util.cpp \
LOCAL_MODULE := fastboot LOCAL_MODULE := fastboot
@ -111,6 +112,8 @@ LOCAL_SRC_FILES := \
socket.cpp \ socket.cpp \
socket_mock.cpp \ socket_mock.cpp \
socket_test.cpp \ socket_test.cpp \
tcp.cpp \
tcp_test.cpp \
LOCAL_STATIC_LIBRARIES := libbase libcutils LOCAL_STATIC_LIBRARIES := libbase libcutils

View file

@ -42,22 +42,22 @@
#include <sys/time.h> #include <sys/time.h>
#include <sys/types.h> #include <sys/types.h>
#include <unistd.h> #include <unistd.h>
#include <functional> #include <functional>
#include <utility> #include <utility>
#include <vector> #include <vector>
#include <android-base/parseint.h> #include <android-base/parseint.h>
#include <android-base/parsenetaddress.h>
#include <android-base/strings.h> #include <android-base/strings.h>
#include <sparse/sparse.h> #include <sparse/sparse.h>
#include <ziparchive/zip_archive.h> #include <ziparchive/zip_archive.h>
#include <android-base/strings.h>
#include <android-base/parseint.h>
#include "bootimg_utils.h" #include "bootimg_utils.h"
#include "diagnose_usb.h" #include "diagnose_usb.h"
#include "fastboot.h" #include "fastboot.h"
#include "fs.h" #include "fs.h"
#include "tcp.h"
#include "transport.h" #include "transport.h"
#include "usb.h" #include "usb.h"
@ -69,9 +69,9 @@
char cur_product[FB_RESPONSE_SZ + 1]; char cur_product[FB_RESPONSE_SZ + 1];
static const char *serial = 0; static const char* serial = nullptr;
static const char *product = 0; static const char* product = nullptr;
static const char *cmdline = 0; static const char* cmdline = nullptr;
static unsigned short vendor_id = 0; static unsigned short vendor_id = 0;
static int long_listing = 0; static int long_listing = 0;
static int64_t sparse_limit = -1; static int64_t sparse_limit = -1;
@ -227,17 +227,51 @@ static int list_devices_callback(usb_ifc_info* info) {
return -1; 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* open_device() {
static Transport* transport = nullptr; 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) { if (announce) {
announce = 0; announce = false;
fprintf(stderr, "< waiting for %s >\n", serial ? serial : "any device"); fprintf(stderr, "< waiting for %s >\n", serial ? serial : "any device");
} }
usleep(1000); usleep(1000);
@ -299,8 +333,10 @@ static void usage() {
" if supported by partition type).\n" " if supported by partition type).\n"
" -u Do not erase partition before\n" " -u Do not erase partition before\n"
" formatting.\n" " formatting.\n"
" -s <specific device> Specify device serial number\n" " -s <specific device> Specify a device. For USB, provide either\n"
" or path to device port.\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" " -p <product> Specify product name.\n"
" -c <cmdline> Override kernel commandline.\n" " -c <cmdline> Override kernel commandline.\n"
" -i <vendor id> Specify a custom USB vendor id.\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(); Transport* transport = open_device();
if (transport == nullptr) {
return 1;
}
if (slot_override != "") if (slot_override != "")
slot_override = verify_slot(transport, slot_override.c_str()); slot_override = verify_slot(transport, slot_override.c_str());
if (next_active != "") if (next_active != "")

View file

@ -3,19 +3,25 @@ FastBoot Version 0.4
---------------------- ----------------------
The fastboot protocol is a mechanism for communicating with bootloaders The fastboot protocol is a mechanism for communicating with bootloaders
over USB. It is designed to be very straightforward to implement, to over USB or ethernet. It is designed to be very straightforward to implement,
allow it to be used across a wide range of devices and from hosts running to allow it to be used across a wide range of devices and from hosts running
Linux, Windows, or OSX. Linux, Windows, or OSX.
Basic Requirements Basic Requirements
------------------ ------------------
* Two bulk endpoints (in, out) are required * USB
* Max packet size must be 64 bytes for full-speed, 512 bytes for * Two bulk endpoints (in, out) are required
high-speed and 1024 bytes for Super Speed USB. * Max packet size must be 64 bytes for full-speed, 512 bytes for
* The protocol is entirely host-driven and synchronous (unlike the high-speed and 1024 bytes for Super Speed USB.
multi-channel, bi-directional, asynchronous ADB protocol) * 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 Transport and Framing
@ -171,3 +177,44 @@ specification. OEM-specific names should not start with lowercase
characters. 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>

196
fastboot/tcp.cpp Normal file
View file

@ -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

59
fastboot/tcp.h Normal file
View file

@ -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_

217
fastboot/tcp_test.cpp Normal file
View file

@ -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));
}