Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 80dc9d85 authored by David Pursell's avatar David Pursell Committed by Gerrit Code Review
Browse files

Merge "fastboot: add TCP protocol."

parents a2dd7342 2ec418a4
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -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

+54 −14
Original line number Diff line number Diff line
@@ -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 != 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 (transport) return transport;
        if (!android::base::ParseNetAddress(address, &host, &port, nullptr, &error)) {
            fprintf(stderr, "error: Invalid network address '%s': %s\n", address, error.c_str());
            return nullptr;
        }
    }

    for (;;) {
    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) return transport;
        }

        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 @@ 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 != "")
+54 −7
Original line number Diff line number Diff line
@@ -3,20 +3,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
------------------

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

fastboot/tcp.cpp

0 → 100644
+196 −0
Original line number Diff line number Diff line
/*
 * 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

fastboot/tcp.h

0 → 100644
+59 −0
Original line number Diff line number Diff line
/*
 * 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_
Loading