Merge "Add support for USB_HVDCP chargers"
diff --git a/adb/file_sync_service.cpp b/adb/file_sync_service.cpp
index ef0418e..29c6629 100644
--- a/adb/file_sync_service.cpp
+++ b/adb/file_sync_service.cpp
@@ -130,6 +130,9 @@
return WriteFdExactly(s, &msg.dent, sizeof(msg.dent));
}
+// Make sure that SendFail from adb_io.cpp isn't accidentally used in this file.
+#pragma GCC poison SendFail
+
static bool SendSyncFail(int fd, const std::string& reason) {
D("sync: failure: %s", reason.c_str());
@@ -265,7 +268,7 @@
msg.status.msglen = 0;
if (!WriteFdExactly(s, &msg.status, sizeof(msg.status))) return false;
} else {
- SendFail(s, "invalid data message: expected ID_DONE");
+ SendSyncFail(s, "invalid data message: expected ID_DONE");
return false;
}
@@ -277,7 +280,7 @@
// 'spec' is of the form "/some/path,0755". Break it up.
size_t comma = spec.find_last_of(',');
if (comma == std::string::npos) {
- SendFail(s, "missing , in ID_SEND");
+ SendSyncFail(s, "missing , in ID_SEND");
return false;
}
@@ -286,7 +289,7 @@
errno = 0;
mode_t mode = strtoul(spec.substr(comma + 1).c_str(), nullptr, 0);
if (errno != 0) {
- SendFail(s, "bad mode");
+ SendSyncFail(s, "bad mode");
return false;
}
diff --git a/adb/shell_service.cpp b/adb/shell_service.cpp
index 491bb68..d080e09 100644
--- a/adb/shell_service.cpp
+++ b/adb/shell_service.cpp
@@ -192,7 +192,7 @@
// Sets up FDs, forks a subprocess, starts the subprocess manager thread,
// and exec's the child. Returns false on failure.
- bool ForkAndExec();
+ bool ForkAndExec(std::string* _Nonnull error);
private:
// Opens the file at |pts_name|.
@@ -250,7 +250,7 @@
WaitForExit();
}
-bool Subprocess::ForkAndExec() {
+bool Subprocess::ForkAndExec(std::string* error) {
ScopedFd child_stdinout_sfd, child_stderr_sfd;
ScopedFd parent_error_sfd, child_error_sfd;
char pts_name[PATH_MAX];
@@ -265,7 +265,9 @@
// use threads, logging directly from the child might deadlock due to locks held in another
// thread during the fork.
if (!CreateSocketpair(&parent_error_sfd, &child_error_sfd)) {
- LOG(ERROR) << "failed to create pipe for subprocess error reporting";
+ *error = android::base::StringPrintf(
+ "failed to create pipe for subprocess error reporting: %s", strerror(errno));
+ return false;
}
// Construct the environment for the child before we fork.
@@ -316,18 +318,22 @@
stdinout_sfd_.Reset(fd);
} else {
if (!CreateSocketpair(&stdinout_sfd_, &child_stdinout_sfd)) {
+ *error = android::base::StringPrintf("failed to create socketpair for stdin/out: %s",
+ strerror(errno));
return false;
}
// Raw subprocess + shell protocol allows for splitting stderr.
if (protocol_ == SubprocessProtocol::kShell &&
!CreateSocketpair(&stderr_sfd_, &child_stderr_sfd)) {
+ *error = android::base::StringPrintf("failed to create socketpair for stderr: %s",
+ strerror(errno));
return false;
}
pid_ = fork();
}
if (pid_ == -1) {
- PLOG(ERROR) << "fork failed";
+ *error = android::base::StringPrintf("fork failed: %s", strerror(errno));
return false;
}
@@ -357,7 +363,8 @@
} else {
execle(_PATH_BSHELL, _PATH_BSHELL, "-c", command_.c_str(), nullptr, cenv.data());
}
- WriteFdExactly(child_error_sfd.fd(), "exec '" _PATH_BSHELL "' failed");
+ WriteFdExactly(child_error_sfd.fd(), "exec '" _PATH_BSHELL "' failed: ");
+ WriteFdExactly(child_error_sfd.fd(), strerror(errno));
child_error_sfd.Reset();
_Exit(1);
}
@@ -370,7 +377,7 @@
child_error_sfd.Reset();
std::string error_message = ReadAll(parent_error_sfd.fd());
if (!error_message.empty()) {
- LOG(ERROR) << error_message;
+ *error = error_message;
return false;
}
@@ -382,6 +389,9 @@
} else {
// Shell protocol: create another socketpair to intercept data.
if (!CreateSocketpair(&protocol_sfd_, &local_socket_sfd_)) {
+ *error = android::base::StringPrintf(
+ "failed to create socketpair to intercept data: %s", strerror(errno));
+ kill(pid_, SIGKILL);
return false;
}
D("protocol FD = %d", protocol_sfd_.fd());
@@ -389,7 +399,8 @@
input_.reset(new ShellProtocol(protocol_sfd_.fd()));
output_.reset(new ShellProtocol(protocol_sfd_.fd()));
if (!input_ || !output_) {
- LOG(ERROR) << "failed to allocate shell protocol objects";
+ *error = "failed to allocate shell protocol objects";
+ kill(pid_, SIGKILL);
return false;
}
@@ -400,7 +411,9 @@
for (int fd : {stdinout_sfd_.fd(), stderr_sfd_.fd()}) {
if (fd >= 0) {
if (!set_file_block_mode(fd, false)) {
- LOG(ERROR) << "failed to set non-blocking mode for fd " << fd;
+ *error = android::base::StringPrintf(
+ "failed to set non-blocking mode for fd %d", fd);
+ kill(pid_, SIGKILL);
return false;
}
}
@@ -408,7 +421,9 @@
}
if (!adb_thread_create(ThreadHandler, this)) {
- PLOG(ERROR) << "failed to create subprocess thread";
+ *error =
+ android::base::StringPrintf("failed to create subprocess thread: %s", strerror(errno));
+ kill(pid_, SIGKILL);
return false;
}
@@ -710,6 +725,37 @@
} // namespace
+// Create a pipe containing the error.
+static int ReportError(SubprocessProtocol protocol, const std::string& message) {
+ int pipefd[2];
+ if (pipe(pipefd) != 0) {
+ LOG(ERROR) << "failed to create pipe to report error";
+ return -1;
+ }
+
+ std::string buf = android::base::StringPrintf("error: %s\n", message.c_str());
+ if (protocol == SubprocessProtocol::kShell) {
+ ShellProtocol::Id id = ShellProtocol::kIdStderr;
+ uint32_t length = buf.length();
+ WriteFdExactly(pipefd[1], &id, sizeof(id));
+ WriteFdExactly(pipefd[1], &length, sizeof(length));
+ }
+
+ WriteFdExactly(pipefd[1], buf.data(), buf.length());
+
+ if (protocol == SubprocessProtocol::kShell) {
+ ShellProtocol::Id id = ShellProtocol::kIdExit;
+ uint32_t length = 1;
+ char exit_code = 126;
+ WriteFdExactly(pipefd[1], &id, sizeof(id));
+ WriteFdExactly(pipefd[1], &length, sizeof(length));
+ WriteFdExactly(pipefd[1], &exit_code, sizeof(exit_code));
+ }
+
+ adb_close(pipefd[1]);
+ return pipefd[0];
+}
+
int StartSubprocess(const char* name, const char* terminal_type,
SubprocessType type, SubprocessProtocol protocol) {
D("starting %s subprocess (protocol=%s, TERM=%s): '%s'",
@@ -720,13 +766,14 @@
Subprocess* subprocess = new Subprocess(name, terminal_type, type, protocol);
if (!subprocess) {
LOG(ERROR) << "failed to allocate new subprocess";
- return -1;
+ return ReportError(protocol, "failed to allocate new subprocess");
}
- if (!subprocess->ForkAndExec()) {
- LOG(ERROR) << "failed to start subprocess";
+ std::string error;
+ if (!subprocess->ForkAndExec(&error)) {
+ LOG(ERROR) << "failed to start subprocess: " << error;
delete subprocess;
- return -1;
+ return ReportError(protocol, error);
}
D("subprocess creation successful: local_socket_fd=%d, pid=%d",
diff --git a/crash_reporter/crash_reporter.cc b/crash_reporter/crash_reporter.cc
index b69492a..16e70d8 100644
--- a/crash_reporter/crash_reporter.cc
+++ b/crash_reporter/crash_reporter.cc
@@ -43,7 +43,8 @@
#endif
static const char kCrashCounterHistogram[] = "Logging.CrashCounter";
-static const char kKernelCrashDetected[] = "/var/run/kernel-crash-detected";
+static const char kKernelCrashDetected[] =
+ "/data/misc/crash_reporter/run/kernel-crash-detected";
static const char kUncleanShutdownDetected[] =
"/var/run/unclean-shutdown-detected";
static const char kGUIDFileName[] = "/data/misc/crash_reporter/guid";
diff --git a/crash_reporter/crash_reporter.rc b/crash_reporter/crash_reporter.rc
index 57c1d40..e6d1ec5 100644
--- a/crash_reporter/crash_reporter.rc
+++ b/crash_reporter/crash_reporter.rc
@@ -13,6 +13,10 @@
# Remove any previous orphaned locks.
rmdir /data/misc/crash_reporter/lock/crash_sender
+ # Remove any previous run files.
+ rm /data/misc/crash_reporter/run/kernel-crash-detected
+ rmdir /data/misc/crash_reporter/run
+
# Create crash directories.
# These directories are group-writable by root so that crash_reporter can
# still access them when it switches users.
diff --git a/crash_reporter/kernel_collector.cc b/crash_reporter/kernel_collector.cc
index cb3a315..68f2d9e 100644
--- a/crash_reporter/kernel_collector.cc
+++ b/crash_reporter/kernel_collector.cc
@@ -30,8 +30,8 @@
namespace {
const char kDefaultKernelStackSignature[] = "kernel-UnspecifiedStackSignature";
-const char kDumpParentPath[] = "/dev";
-const char kDumpPath[] = "/dev/pstore";
+const char kDumpParentPath[] = "/sys/fs";
+const char kDumpPath[] = "/sys/fs/pstore";
const char kDumpFormat[] = "dmesg-ramoops-%zu";
const char kKernelExecName[] = "kernel";
// Maximum number of records to examine in the kDumpPath.
diff --git a/fastboot/Android.mk b/fastboot/Android.mk
index 65f4e01..e0f7c73 100644
--- a/fastboot/Android.mk
+++ b/fastboot/Android.mk
@@ -31,6 +31,7 @@
fs.cpp\
protocol.cpp \
socket.cpp \
+ tcp.cpp \
util.cpp \
LOCAL_MODULE := fastboot
@@ -65,6 +66,7 @@
libdiagnose_usb \
libbase \
libcutils \
+ libgtest_host \
# libf2fs_dlutils_host will dlopen("libf2fs_fmt_host_dyn")
LOCAL_CFLAGS_linux := -DUSE_F2FS
@@ -110,6 +112,8 @@
socket.cpp \
socket_mock.cpp \
socket_test.cpp \
+ tcp.cpp \
+ tcp_test.cpp \
LOCAL_STATIC_LIBRARIES := libbase libcutils
diff --git a/fastboot/fastboot.cpp b/fastboot/fastboot.cpp
index 4573da0..b219564 100644
--- a/fastboot/fastboot.cpp
+++ b/fastboot/fastboot.cpp
@@ -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 @@
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;
+ }
- for (;;) {
- transport = usb_open(match_fastboot);
- if (transport) 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;
+ }
+
if (announce) {
- announce = 0;
+ announce = false;
fprintf(stderr, "< waiting for %s >\n", serial ? serial : "any device");
}
usleep(1000);
@@ -299,8 +333,10 @@
" 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 @@
}
Transport* transport = open_device();
+ if (transport == nullptr) {
+ return 1;
+ }
+
if (slot_override != "")
slot_override = verify_slot(transport, slot_override.c_str());
if (next_active != "")
diff --git a/fastboot/fastboot_protocol.txt b/fastboot/fastboot_protocol.txt
index bb73d8a..4aa48b1 100644
--- a/fastboot/fastboot_protocol.txt
+++ b/fastboot/fastboot_protocol.txt
@@ -1,21 +1,26 @@
-
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
@@ -152,7 +157,7 @@
The various currently defined names are:
version Version of FastBoot protocol supported.
- It should be "0.3" for this document.
+ It should be "0.4" for this document.
version-bootloader Version string for the Bootloader.
@@ -171,3 +176,44 @@
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>
diff --git a/fastboot/socket.cpp b/fastboot/socket.cpp
index 0a3ddfa..d49f47f 100644
--- a/fastboot/socket.cpp
+++ b/fastboot/socket.cpp
@@ -89,7 +89,8 @@
UdpSocket(Type type, cutils_socket_t sock);
- ssize_t Send(const void* data, size_t length) override;
+ bool Send(const void* data, size_t length) override;
+ bool Send(std::vector<cutils_socket_buffer_t> buffers) override;
ssize_t Receive(void* data, size_t length, int timeout_ms) override;
private:
@@ -109,9 +110,20 @@
}
}
-ssize_t UdpSocket::Send(const void* data, size_t length) {
+bool UdpSocket::Send(const void* data, size_t length) {
return TEMP_FAILURE_RETRY(sendto(sock_, reinterpret_cast<const char*>(data), length, 0,
- reinterpret_cast<sockaddr*>(addr_.get()), addr_size_));
+ reinterpret_cast<sockaddr*>(addr_.get()), addr_size_)) ==
+ static_cast<ssize_t>(length);
+}
+
+bool UdpSocket::Send(std::vector<cutils_socket_buffer_t> buffers) {
+ size_t total_length = 0;
+ for (const auto& buffer : buffers) {
+ total_length += buffer.length;
+ }
+
+ return TEMP_FAILURE_RETRY(socket_send_buffers_function_(
+ sock_, buffers.data(), buffers.size())) == static_cast<ssize_t>(total_length);
}
ssize_t UdpSocket::Receive(void* data, size_t length, int timeout_ms) {
@@ -135,7 +147,8 @@
public:
TcpSocket(cutils_socket_t sock) : Socket(sock) {}
- ssize_t Send(const void* data, size_t length) override;
+ bool Send(const void* data, size_t length) override;
+ bool Send(std::vector<cutils_socket_buffer_t> buffers) override;
ssize_t Receive(void* data, size_t length, int timeout_ms) override;
std::unique_ptr<Socket> Accept() override;
@@ -144,23 +157,52 @@
DISALLOW_COPY_AND_ASSIGN(TcpSocket);
};
-ssize_t TcpSocket::Send(const void* data, size_t length) {
- size_t total = 0;
+bool TcpSocket::Send(const void* data, size_t length) {
+ while (length > 0) {
+ ssize_t sent =
+ TEMP_FAILURE_RETRY(send(sock_, reinterpret_cast<const char*>(data), length, 0));
- while (total < length) {
- ssize_t bytes = TEMP_FAILURE_RETRY(
- send(sock_, reinterpret_cast<const char*>(data) + total, length - total, 0));
-
- if (bytes == -1) {
- if (total == 0) {
- return -1;
- }
- break;
+ if (sent == -1) {
+ return false;
}
- total += bytes;
+ length -= sent;
}
- return total;
+ return true;
+}
+
+bool TcpSocket::Send(std::vector<cutils_socket_buffer_t> buffers) {
+ while (!buffers.empty()) {
+ ssize_t sent = TEMP_FAILURE_RETRY(
+ socket_send_buffers_function_(sock_, buffers.data(), buffers.size()));
+
+ if (sent == -1) {
+ return false;
+ }
+
+ // Adjust the buffers to skip past the bytes we've just sent.
+ auto iter = buffers.begin();
+ while (sent > 0) {
+ if (iter->length > static_cast<size_t>(sent)) {
+ // Incomplete buffer write; adjust the buffer to point to the next byte to send.
+ iter->length -= sent;
+ iter->data = reinterpret_cast<const char*>(iter->data) + sent;
+ break;
+ }
+
+ // Complete buffer write; move on to the next buffer.
+ sent -= iter->length;
+ ++iter;
+ }
+
+ // Shortcut the common case: we've written everything remaining.
+ if (iter == buffers.end()) {
+ break;
+ }
+ buffers.erase(buffers.begin(), iter);
+ }
+
+ return true;
}
ssize_t TcpSocket::Receive(void* data, size_t length, int timeout_ms) {
diff --git a/fastboot/socket.h b/fastboot/socket.h
index a7481db..c0bd7c9 100644
--- a/fastboot/socket.h
+++ b/fastboot/socket.h
@@ -33,11 +33,15 @@
#ifndef SOCKET_H_
#define SOCKET_H_
+#include <functional>
#include <memory>
#include <string>
+#include <utility>
+#include <vector>
#include <android-base/macros.h>
#include <cutils/sockets.h>
+#include <gtest/gtest_prod.h>
// Socket interface to be implemented for each platform.
class Socket {
@@ -64,8 +68,17 @@
virtual ~Socket();
// Sends |length| bytes of |data|. For TCP sockets this will continue trying to send until all
- // bytes are transmitted. Returns the number of bytes actually sent or -1 on error.
- virtual ssize_t Send(const void* data, size_t length) = 0;
+ // bytes are transmitted. Returns true on success.
+ virtual bool Send(const void* data, size_t length) = 0;
+
+ // Sends |buffers| using multi-buffer write, which can be significantly faster than making
+ // multiple calls. For UDP sockets |buffers| are all combined into a single datagram; for
+ // TCP sockets this will continue sending until all buffers are fully transmitted. Returns true
+ // on success.
+ //
+ // Note: This is non-functional for UDP server Sockets because it's not currently needed and
+ // would require an additional sendto() variation of multi-buffer write.
+ virtual bool Send(std::vector<cutils_socket_buffer_t> buffers) = 0;
// Waits up to |timeout_ms| to receive up to |length| bytes of data. |timout_ms| of 0 will
// block forever. Returns the number of bytes received or -1 on error/timeout. On timeout
@@ -94,9 +107,17 @@
cutils_socket_t sock_ = INVALID_SOCKET;
+ // Non-class functions we want to override during tests to verify functionality. Implementation
+ // should call this rather than using socket_send_buffers() directly.
+ std::function<ssize_t(cutils_socket_t, cutils_socket_buffer_t*, size_t)>
+ socket_send_buffers_function_ = &socket_send_buffers;
+
private:
int receive_timeout_ms_ = 0;
+ FRIEND_TEST(SocketTest, TestTcpSendBuffers);
+ FRIEND_TEST(SocketTest, TestUdpSendBuffers);
+
DISALLOW_COPY_AND_ASSIGN(Socket);
};
diff --git a/fastboot/socket_mock.cpp b/fastboot/socket_mock.cpp
index 8fea554..c962f30 100644
--- a/fastboot/socket_mock.cpp
+++ b/fastboot/socket_mock.cpp
@@ -38,28 +38,38 @@
}
}
-ssize_t SocketMock::Send(const void* data, size_t length) {
+bool SocketMock::Send(const void* data, size_t length) {
if (events_.empty()) {
ADD_FAILURE() << "Send() was called when no message was expected";
- return -1;
+ return false;
}
if (events_.front().type != EventType::kSend) {
ADD_FAILURE() << "Send() was called out-of-order";
- return -1;
+ return false;
}
std::string message(reinterpret_cast<const char*>(data), length);
if (events_.front().message != message) {
ADD_FAILURE() << "Send() expected " << events_.front().message << ", but got " << message;
- return -1;
+ return false;
}
- ssize_t return_value = events_.front().return_value;
+ bool return_value = events_.front().return_value;
events_.pop();
return return_value;
}
+// Mock out multi-buffer send to be one large send, since that's what it should looks like from
+// the user's perspective.
+bool SocketMock::Send(std::vector<cutils_socket_buffer_t> buffers) {
+ std::string data;
+ for (const auto& buffer : buffers) {
+ data.append(reinterpret_cast<const char*>(buffer.data), buffer.length);
+ }
+ return Send(data.data(), data.size());
+}
+
ssize_t SocketMock::Receive(void* data, size_t length, int /*timeout_ms*/) {
if (events_.empty()) {
ADD_FAILURE() << "Receive() was called when no message was ready";
@@ -106,12 +116,11 @@
}
void SocketMock::ExpectSend(std::string message) {
- ssize_t return_value = message.length();
- events_.push(Event(EventType::kSend, std::move(message), return_value, nullptr));
+ events_.push(Event(EventType::kSend, std::move(message), true, nullptr));
}
void SocketMock::ExpectSendFailure(std::string message) {
- events_.push(Event(EventType::kSend, std::move(message), -1, nullptr));
+ events_.push(Event(EventType::kSend, std::move(message), false, nullptr));
}
void SocketMock::AddReceive(std::string message) {
diff --git a/fastboot/socket_mock.h b/fastboot/socket_mock.h
index 3e62b33..41fe06d 100644
--- a/fastboot/socket_mock.h
+++ b/fastboot/socket_mock.h
@@ -56,7 +56,8 @@
SocketMock();
~SocketMock() override;
- ssize_t Send(const void* data, size_t length) override;
+ bool Send(const void* data, size_t length) override;
+ bool Send(std::vector<cutils_socket_buffer_t> buffers) override;
ssize_t Receive(void* data, size_t length, int timeout_ms) override;
int Close() override;
virtual std::unique_ptr<Socket> Accept();
@@ -64,7 +65,7 @@
// Adds an expectation for Send().
void ExpectSend(std::string message);
- // Adds an expectation for Send() that returns -1.
+ // Adds an expectation for Send() that returns false.
void ExpectSendFailure(std::string message);
// Adds data to provide for Receive().
diff --git a/fastboot/socket_test.cpp b/fastboot/socket_test.cpp
index 7bfe967..cc71075 100644
--- a/fastboot/socket_test.cpp
+++ b/fastboot/socket_test.cpp
@@ -23,8 +23,10 @@
#include "socket.h"
#include "socket_mock.h"
-#include <gtest/gtest.h>
+#include <list>
+
#include <gtest/gtest-spi.h>
+#include <gtest/gtest.h>
enum { kTestTimeoutMs = 3000 };
@@ -59,7 +61,7 @@
// Sends a string over a Socket. Returns true if the full string (without terminating char)
// was sent.
static bool SendString(Socket* sock, const std::string& message) {
- return sock->Send(message.c_str(), message.length()) == static_cast<ssize_t>(message.length());
+ return sock->Send(message.c_str(), message.length());
}
// Receives a string from a Socket. Returns true if the full string (without terminating char)
@@ -123,6 +125,116 @@
}
}
+// Tests UDP multi-buffer send.
+TEST(SocketTest, TestUdpSendBuffers) {
+ std::unique_ptr<Socket> sock = Socket::NewServer(Socket::Protocol::kUdp, 0);
+ std::vector<std::string> data{"foo", "bar", "12345"};
+ std::vector<cutils_socket_buffer_t> buffers{{data[0].data(), data[0].length()},
+ {data[1].data(), data[1].length()},
+ {data[2].data(), data[2].length()}};
+ ssize_t mock_return_value = 0;
+
+ // Mock out socket_send_buffers() to verify we're sending in the correct buffers and
+ // return |mock_return_value|.
+ sock->socket_send_buffers_function_ = [&buffers, &mock_return_value](
+ cutils_socket_t /*cutils_sock*/, cutils_socket_buffer_t* sent_buffers,
+ size_t num_sent_buffers) -> ssize_t {
+ EXPECT_EQ(buffers.size(), num_sent_buffers);
+ for (size_t i = 0; i < num_sent_buffers; ++i) {
+ EXPECT_EQ(buffers[i].data, sent_buffers[i].data);
+ EXPECT_EQ(buffers[i].length, sent_buffers[i].length);
+ }
+ return mock_return_value;
+ };
+
+ mock_return_value = strlen("foobar12345");
+ EXPECT_TRUE(sock->Send(buffers));
+
+ mock_return_value -= 1;
+ EXPECT_FALSE(sock->Send(buffers));
+
+ mock_return_value = 0;
+ EXPECT_FALSE(sock->Send(buffers));
+
+ mock_return_value = -1;
+ EXPECT_FALSE(sock->Send(buffers));
+}
+
+// Tests TCP re-sending until socket_send_buffers() sends all data. This is a little complicated,
+// but the general idea is that we intercept calls to socket_send_buffers() using a lambda mock
+// function that simulates partial writes.
+TEST(SocketTest, TestTcpSendBuffers) {
+ std::unique_ptr<Socket> sock = Socket::NewServer(Socket::Protocol::kTcp, 0);
+ std::vector<std::string> data{"foo", "bar", "12345"};
+ std::vector<cutils_socket_buffer_t> buffers{{data[0].data(), data[0].length()},
+ {data[1].data(), data[1].length()},
+ {data[2].data(), data[2].length()}};
+
+ // Test breaking up the buffered send at various points.
+ std::list<std::string> test_sends[] = {
+ // Successes.
+ {"foobar12345"},
+ {"f", "oob", "ar12345"},
+ {"fo", "obar12", "345"},
+ {"foo", "bar12345"},
+ {"foob", "ar123", "45"},
+ {"f", "o", "o", "b", "a", "r", "1", "2", "3", "4", "5"},
+
+ // Failures.
+ {},
+ {"f"},
+ {"foo", "bar"},
+ {"fo", "obar12"},
+ {"foobar1234"}
+ };
+
+ for (auto& test : test_sends) {
+ ssize_t bytes_sent = 0;
+ bool expect_success = true;
+
+ // Create a mock function for custom socket_send_buffers() behavior. This function will
+ // check to make sure the input buffers start at the next unsent byte, then return the
+ // number of bytes indicated by the next entry in |test|.
+ sock->socket_send_buffers_function_ = [&bytes_sent, &data, &expect_success, &test](
+ cutils_socket_t /*cutils_sock*/, cutils_socket_buffer_t* buffers,
+ size_t num_buffers) -> ssize_t {
+ EXPECT_TRUE(num_buffers > 0);
+
+ // Failure case - pretend we errored out before sending all the buffers.
+ if (test.empty()) {
+ expect_success = false;
+ return -1;
+ }
+
+ // Count the bytes we've sent to find where the next buffer should start and how many
+ // bytes should be left in it.
+ size_t byte_count = bytes_sent, data_index = 0;
+ while (data_index < data.size()) {
+ if (byte_count >= data[data_index].length()) {
+ byte_count -= data[data_index].length();
+ ++data_index;
+ } else {
+ break;
+ }
+ }
+ void* expected_next_byte = &data[data_index][byte_count];
+ size_t expected_next_size = data[data_index].length() - byte_count;
+
+ EXPECT_EQ(data.size() - data_index, num_buffers);
+ EXPECT_EQ(expected_next_byte, buffers[0].data);
+ EXPECT_EQ(expected_next_size, buffers[0].length);
+
+ std::string to_send = std::move(test.front());
+ test.pop_front();
+ bytes_sent += to_send.length();
+ return to_send.length();
+ };
+
+ EXPECT_EQ(expect_success, sock->Send(buffers));
+ EXPECT_TRUE(test.empty());
+ }
+}
+
TEST(SocketMockTest, TestSendSuccess) {
SocketMock mock;
@@ -138,6 +250,9 @@
TEST(SocketMockTest, TestSendFailure) {
SocketMock* mock = new SocketMock;
+ mock->ExpectSendFailure("foo");
+ EXPECT_FALSE(SendString(mock, "foo"));
+
EXPECT_NONFATAL_FAILURE(SendString(mock, "foo"), "no message was expected");
mock->ExpectSend("foo");
@@ -162,11 +277,24 @@
mock.AddReceive("123");
EXPECT_TRUE(ReceiveString(&mock, "abc"));
EXPECT_TRUE(ReceiveString(&mock, "123"));
+
+ // Make sure ReceiveAll() can piece together multiple receives.
+ mock.AddReceive("foo");
+ mock.AddReceive("bar");
+ mock.AddReceive("123");
+ EXPECT_TRUE(ReceiveString(&mock, "foobar123"));
}
TEST(SocketMockTest, TestReceiveFailure) {
SocketMock* mock = new SocketMock;
+ mock->AddReceiveFailure();
+ EXPECT_FALSE(ReceiveString(mock, "foo"));
+
+ mock->AddReceive("foo");
+ mock->AddReceiveFailure();
+ EXPECT_FALSE(ReceiveString(mock, "foobar"));
+
EXPECT_NONFATAL_FAILURE(ReceiveString(mock, "foo"), "no message was ready");
mock->ExpectSend("foo");
diff --git a/fastboot/tcp.cpp b/fastboot/tcp.cpp
new file mode 100644
index 0000000..da2880a
--- /dev/null
+++ b/fastboot/tcp.cpp
@@ -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
diff --git a/fastboot/tcp.h b/fastboot/tcp.h
new file mode 100644
index 0000000..aa3ef13
--- /dev/null
+++ b/fastboot/tcp.h
@@ -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_
diff --git a/fastboot/tcp_test.cpp b/fastboot/tcp_test.cpp
new file mode 100644
index 0000000..7d80d76
--- /dev/null
+++ b/fastboot/tcp_test.cpp
@@ -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));
+}
diff --git a/include/cutils/sockets.h b/include/cutils/sockets.h
index cb9b3ff..783bd0b 100644
--- a/include/cutils/sockets.h
+++ b/include/cutils/sockets.h
@@ -30,15 +30,12 @@
typedef int socklen_t;
typedef SOCKET cutils_socket_t;
-typedef WSABUF cutils_socket_buffer_t;
#else
#include <sys/socket.h>
-#include <sys/uio.h>
typedef int cutils_socket_t;
-typedef struct iovec cutils_socket_buffer_t;
#define INVALID_SOCKET (-1)
#endif
@@ -144,21 +141,24 @@
* on Windows. This can give significant speedup compared to calling send()
* multiple times.
*
- * Because Unix and Windows use different structs to hold buffers, we also
- * need a generic function to set up the buffers.
- *
* Example usage:
- * cutils_socket_buffer_t buffers[2] = {
- * make_cutils_socket_buffer(data0, len0),
- * make_cutils_socket_buffer(data1, len1)
- * };
+ * cutils_socket_buffer_t buffers[2] = { {data0, len0}, {data1, len1} };
* socket_send_buffers(sock, buffers, 2);
*
+ * If you try to pass more than SOCKET_SEND_BUFFERS_MAX_BUFFERS buffers into
+ * this function it will return -1 without sending anything.
+ *
* Returns the number of bytes written or -1 on error.
*/
-cutils_socket_buffer_t make_cutils_socket_buffer(void* data, size_t length);
+typedef struct {
+ const void* data;
+ size_t length;
+} cutils_socket_buffer_t;
+
+#define SOCKET_SEND_BUFFERS_MAX_BUFFERS 16
+
ssize_t socket_send_buffers(cutils_socket_t sock,
- cutils_socket_buffer_t* buffers,
+ const cutils_socket_buffer_t* buffers,
size_t num_buffers);
/*
diff --git a/libcutils/Android.mk b/libcutils/Android.mk
index 482e4dd..51c6d9d 100644
--- a/libcutils/Android.mk
+++ b/libcutils/Android.mk
@@ -46,7 +46,7 @@
socket_loopback_client_unix.c \
socket_loopback_server_unix.c \
socket_network_client_unix.c \
- sockets_unix.c \
+ sockets_unix.cpp \
str_parms.c \
libcutils_nonwindows_host_sources := \
@@ -56,7 +56,7 @@
libcutils_windows_host_sources := \
socket_inaddr_any_server_windows.c \
socket_network_client_windows.c \
- sockets_windows.c \
+ sockets_windows.cpp \
# Shared and static library for host
# Note: when linking this library on Windows, you must also link to Winsock2
diff --git a/libcutils/ashmem-dev.c b/libcutils/ashmem-dev.c
index a5203e1..d6a48c9 100644
--- a/libcutils/ashmem-dev.c
+++ b/libcutils/ashmem-dev.c
@@ -19,9 +19,11 @@
* ashmem-enabled kernel. See ashmem-sim.c for the "fake" tmp-based version,
* used by the simulator.
*/
+#define LOG_TAG "ashmem"
#include <errno.h>
#include <fcntl.h>
+#include <pthread.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
@@ -31,9 +33,105 @@
#include <linux/ashmem.h>
#include <cutils/ashmem.h>
+#include <log/log.h>
#define ASHMEM_DEVICE "/dev/ashmem"
+/* ashmem identity */
+static dev_t __ashmem_rdev;
+/*
+ * If we trigger a signal handler in the middle of locked activity and the
+ * signal handler calls ashmem, we could get into a deadlock state.
+ */
+static pthread_mutex_t __ashmem_lock = PTHREAD_MUTEX_INITIALIZER;
+
+/* logistics of getting file descriptor for ashmem */
+static int __ashmem_open_locked()
+{
+ int ret;
+ struct stat st;
+
+ int fd = TEMP_FAILURE_RETRY(open(ASHMEM_DEVICE, O_RDWR));
+ if (fd < 0) {
+ return fd;
+ }
+
+ ret = TEMP_FAILURE_RETRY(fstat(fd, &st));
+ if (ret < 0) {
+ int save_errno = errno;
+ close(fd);
+ errno = save_errno;
+ return ret;
+ }
+ if (!S_ISCHR(st.st_mode) || !st.st_rdev) {
+ close(fd);
+ errno = ENOTTY;
+ return -1;
+ }
+
+ __ashmem_rdev = st.st_rdev;
+ return fd;
+}
+
+static int __ashmem_open()
+{
+ int fd;
+
+ pthread_mutex_lock(&__ashmem_lock);
+ fd = __ashmem_open_locked();
+ pthread_mutex_unlock(&__ashmem_lock);
+
+ return fd;
+}
+
+/* Make sure file descriptor references ashmem, negative number means false */
+static int __ashmem_is_ashmem(int fd)
+{
+ dev_t rdev;
+ struct stat st;
+
+ if (TEMP_FAILURE_RETRY(fstat(fd, &st)) < 0) {
+ return -1;
+ }
+
+ rdev = 0; /* Too much complexity to sniff __ashmem_rdev */
+ if (S_ISCHR(st.st_mode) && st.st_rdev) {
+ pthread_mutex_lock(&__ashmem_lock);
+ rdev = __ashmem_rdev;
+ if (rdev) {
+ pthread_mutex_unlock(&__ashmem_lock);
+ } else {
+ int fd = __ashmem_open_locked();
+ if (fd < 0) {
+ pthread_mutex_unlock(&__ashmem_lock);
+ return -1;
+ }
+ rdev = __ashmem_rdev;
+ pthread_mutex_unlock(&__ashmem_lock);
+
+ close(fd);
+ }
+
+ if (st.st_rdev == rdev) {
+ return 0;
+ }
+ }
+
+ if (rdev) {
+ ALOGE("illegal fd=%d mode=0%o rdev=%d:%d expected 0%o %d:%d",
+ fd, st.st_mode, major(st.st_rdev), minor(st.st_rdev),
+ S_IFCHR | S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IRGRP,
+ major(rdev), minor(rdev));
+ } else {
+ ALOGE("illegal fd=%d mode=0%o rdev=%d:%d expected 0%o",
+ fd, st.st_mode, major(st.st_rdev), minor(st.st_rdev),
+ S_IFCHR | S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IRGRP);
+ }
+
+ errno = ENOTTY;
+ return -1;
+}
+
/*
* ashmem_create_region - creates a new ashmem region and returns the file
* descriptor, or <0 on error
@@ -45,7 +143,7 @@
{
int ret, save_errno;
- int fd = TEMP_FAILURE_RETRY(open(ASHMEM_DEVICE, O_RDWR));
+ int fd = __ashmem_open();
if (fd < 0) {
return fd;
}
@@ -76,22 +174,44 @@
int ashmem_set_prot_region(int fd, int prot)
{
+ int ret = __ashmem_is_ashmem(fd);
+ if (ret < 0) {
+ return ret;
+ }
+
return TEMP_FAILURE_RETRY(ioctl(fd, ASHMEM_SET_PROT_MASK, prot));
}
int ashmem_pin_region(int fd, size_t offset, size_t len)
{
struct ashmem_pin pin = { offset, len };
+
+ int ret = __ashmem_is_ashmem(fd);
+ if (ret < 0) {
+ return ret;
+ }
+
return TEMP_FAILURE_RETRY(ioctl(fd, ASHMEM_PIN, &pin));
}
int ashmem_unpin_region(int fd, size_t offset, size_t len)
{
struct ashmem_pin pin = { offset, len };
+
+ int ret = __ashmem_is_ashmem(fd);
+ if (ret < 0) {
+ return ret;
+ }
+
return TEMP_FAILURE_RETRY(ioctl(fd, ASHMEM_UNPIN, &pin));
}
int ashmem_get_size_region(int fd)
{
+ int ret = __ashmem_is_ashmem(fd);
+ if (ret < 0) {
+ return ret;
+ }
+
return TEMP_FAILURE_RETRY(ioctl(fd, ASHMEM_GET_SIZE, NULL));
}
diff --git a/libcutils/sockets_unix.c b/libcutils/sockets_unix.cpp
similarity index 69%
rename from libcutils/sockets_unix.c
rename to libcutils/sockets_unix.cpp
index 3e7cea0..8747d69 100644
--- a/libcutils/sockets_unix.c
+++ b/libcutils/sockets_unix.cpp
@@ -15,6 +15,9 @@
*/
#include <cutils/sockets.h>
+
+#include <sys/uio.h>
+
#include <log/log.h>
#if defined(__ANDROID__)
@@ -25,10 +28,9 @@
#define __android_unused __attribute__((__unused__))
#endif
-bool socket_peer_is_trusted(int fd __android_unused)
-{
+bool socket_peer_is_trusted(int fd __android_unused) {
#if defined(__ANDROID__)
- struct ucred cr;
+ ucred cr;
socklen_t len = sizeof(cr);
int n = getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cr, &len);
@@ -51,21 +53,27 @@
}
int socket_set_receive_timeout(cutils_socket_t sock, int timeout_ms) {
- struct timeval tv;
+ timeval tv;
tv.tv_sec = timeout_ms / 1000;
tv.tv_usec = (timeout_ms % 1000) * 1000;
return setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
}
-cutils_socket_buffer_t make_cutils_socket_buffer(void* data, size_t length) {
- cutils_socket_buffer_t buffer;
- buffer.iov_base = data;
- buffer.iov_len = length;
- return buffer;
-}
-
ssize_t socket_send_buffers(cutils_socket_t sock,
- cutils_socket_buffer_t* buffers,
+ const cutils_socket_buffer_t* buffers,
size_t num_buffers) {
- return writev(sock, buffers, num_buffers);
+ if (num_buffers > SOCKET_SEND_BUFFERS_MAX_BUFFERS) {
+ return -1;
+ }
+
+ iovec iovec_buffers[SOCKET_SEND_BUFFERS_MAX_BUFFERS];
+ for (size_t i = 0; i < num_buffers; ++i) {
+ // It's safe to cast away const here; iovec declares non-const
+ // void* because it's used for both send and receive, but since
+ // we're only sending, the data won't be modified.
+ iovec_buffers[i].iov_base = const_cast<void*>(buffers[i].data);
+ iovec_buffers[i].iov_len = buffers[i].length;
+ }
+
+ return writev(sock, iovec_buffers, num_buffers);
}
diff --git a/libcutils/sockets_windows.c b/libcutils/sockets_windows.cpp
similarity index 74%
rename from libcutils/sockets_windows.c
rename to libcutils/sockets_windows.cpp
index 8153688..ed6b1a7 100644
--- a/libcutils/sockets_windows.c
+++ b/libcutils/sockets_windows.cpp
@@ -37,7 +37,7 @@
// Both adb (1) and Chrome (2) purposefully avoid WSACleanup() with no issues.
// (1) https://android.googlesource.com/platform/system/core.git/+/master/adb/sysdeps_win32.cpp
// (2) https://code.google.com/p/chromium/codesearch#chromium/src/net/base/winsock_init.cc
-bool initialize_windows_sockets() {
+extern "C" bool initialize_windows_sockets() {
// There's no harm in calling WSAStartup() multiple times but no benefit
// either, we may as well skip it after the first.
static bool init_success = false;
@@ -55,25 +55,32 @@
}
int socket_set_receive_timeout(cutils_socket_t sock, int timeout_ms) {
- return setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout_ms,
- sizeof(timeout_ms));
-}
-
-cutils_socket_buffer_t make_cutils_socket_buffer(void* data, size_t length) {
- cutils_socket_buffer_t buffer;
- buffer.buf = data;
- buffer.len = length;
- return buffer;
+ return setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO,
+ reinterpret_cast<char*>(&timeout_ms), sizeof(timeout_ms));
}
ssize_t socket_send_buffers(cutils_socket_t sock,
- cutils_socket_buffer_t* buffers,
+ const cutils_socket_buffer_t* buffers,
size_t num_buffers) {
- DWORD bytes_sent = 0;
+ if (num_buffers > SOCKET_SEND_BUFFERS_MAX_BUFFERS) {
+ return -1;
+ }
- if (WSASend(sock, buffers, num_buffers, &bytes_sent, 0, NULL, NULL) !=
- SOCKET_ERROR) {
+ WSABUF wsa_buffers[SOCKET_SEND_BUFFERS_MAX_BUFFERS];
+ for (size_t i = 0; i < num_buffers; ++i) {
+ // It's safe to cast away const here; WSABUF declares non-const
+ // void* because it's used for both send and receive, but since
+ // we're only sending, the data won't be modified.
+ wsa_buffers[i].buf =
+ reinterpret_cast<char*>(const_cast<void*>(buffers[i].data));
+ wsa_buffers[i].len = buffers[i].length;
+ }
+
+ DWORD bytes_sent = 0;
+ if (WSASend(sock, wsa_buffers, num_buffers, &bytes_sent, 0, nullptr,
+ nullptr) != SOCKET_ERROR) {
return bytes_sent;
}
+
return -1;
}
diff --git a/libcutils/tests/sockets_test.cpp b/libcutils/tests/sockets_test.cpp
index 40fa9b1..0f682a2 100644
--- a/libcutils/tests/sockets_test.cpp
+++ b/libcutils/tests/sockets_test.cpp
@@ -60,11 +60,9 @@
// Send multiple buffers using socket_send_buffers().
std::string data[] = {"foo", "bar", "12345"};
- cutils_socket_buffer_t socket_buffers[3];
- for (int i = 0; i < 3; ++i) {
- socket_buffers[i] = make_cutils_socket_buffer(&data[i][0],
- data[i].length());
- }
+ cutils_socket_buffer_t socket_buffers[] = { {data[0].data(), data[0].length()},
+ {data[1].data(), data[1].length()},
+ {data[2].data(), data[2].length()} };
EXPECT_EQ(11, socket_send_buffers(client, socket_buffers, 3));
EXPECT_EQ(11, recv(server, buffer, sizeof(buffer), 0));
EXPECT_EQ(0, memcmp(buffer, "foobar12345", 11));
diff --git a/liblog/log_is_loggable.c b/liblog/log_is_loggable.c
index 0f81efc..2f8f886 100644
--- a/liblog/log_is_loggable.c
+++ b/liblog/log_is_loggable.c
@@ -110,7 +110,7 @@
* Where the missing tag matches all tags and becomes the
* system global default. We do not support ro.log.tag* .
*/
- static char *last_tag;
+ static char last_tag[PROP_NAME_MAX];
static uint32_t global_serial;
/* some compilers erroneously see uninitialized use. !not_locked */
uint32_t current_global_serial = 0;
@@ -149,20 +149,19 @@
if (taglen) {
int local_change_detected = change_detected;
if (!not_locked) {
- if (!last_tag
+ if (!last_tag[0]
|| (last_tag[0] != tag[0])
- || strcmp(last_tag + 1, tag + 1)) {
+ || strncmp(last_tag + 1, tag + 1, sizeof(last_tag) - 1)) {
/* invalidate log.tag.<tag> cache */
for (i = 0; i < (sizeof(tag_cache) / sizeof(tag_cache[0])); ++i) {
tag_cache[i].pinfo = NULL;
tag_cache[i].c = '\0';
}
- free(last_tag);
- last_tag = NULL;
+ last_tag[0] = '\0';
local_change_detected = 1;
}
- if (!last_tag) {
- last_tag = strdup(tag);
+ if (!last_tag[0]) {
+ strncpy(last_tag, tag, sizeof(last_tag));
}
}
strcpy(key + sizeof(log_namespace) - 1, tag);
diff --git a/logd/logd.rc b/logd/logd.rc
index 10f3553..31ed4df 100644
--- a/logd/logd.rc
+++ b/logd/logd.rc
@@ -1,5 +1,4 @@
service logd /system/bin/logd
- class core
socket logd stream 0666 logd logd
socket logdr seqpacket 0666 logd logd
socket logdw dgram 0222 logd logd
diff --git a/metricsd/metrics_collector.cc b/metricsd/metrics_collector.cc
index c3f42dc..45ae0a4 100644
--- a/metricsd/metrics_collector.cc
+++ b/metricsd/metrics_collector.cc
@@ -59,7 +59,8 @@
// Interval between calls to UpdateStats().
const uint32_t kUpdateStatsIntervalMs = 300000;
-const char kKernelCrashDetectedFile[] = "/var/run/kernel-crash-detected";
+const char kKernelCrashDetectedFile[] =
+ "/data/misc/crash_reporter/run/kernel-crash-detected";
const char kUncleanShutdownDetectedFile[] =
"/var/run/unclean-shutdown-detected";
diff --git a/rootdir/init.rc b/rootdir/init.rc
index 7bf309b..1f63fcf 100644
--- a/rootdir/init.rc
+++ b/rootdir/init.rc
@@ -297,8 +297,7 @@
# We restorecon /data in case the userdata partition has been reset.
restorecon /data
- # Start logd, then start debuggerd to make debugging early-boot crashes easier.
- start logd
+ # start debuggerd to make debugging early-boot crashes easier.
start debuggerd
start debuggerd64