Merge "fastboot: add UDP protocol."
This commit is contained in:
commit
c7f43c6ae1
6 changed files with 1262 additions and 12 deletions
|
@ -32,6 +32,7 @@ LOCAL_SRC_FILES := \
|
|||
protocol.cpp \
|
||||
socket.cpp \
|
||||
tcp.cpp \
|
||||
udp.cpp \
|
||||
util.cpp \
|
||||
|
||||
LOCAL_MODULE := fastboot
|
||||
|
@ -114,6 +115,8 @@ LOCAL_SRC_FILES := \
|
|||
socket_test.cpp \
|
||||
tcp.cpp \
|
||||
tcp_test.cpp \
|
||||
udp.cpp \
|
||||
udp_test.cpp \
|
||||
|
||||
LOCAL_STATIC_LIBRARIES := libbase libcutils
|
||||
|
||||
|
|
|
@ -59,6 +59,7 @@
|
|||
#include "fs.h"
|
||||
#include "tcp.h"
|
||||
#include "transport.h"
|
||||
#include "udp.h"
|
||||
#include "usb.h"
|
||||
|
||||
#ifndef O_BINARY
|
||||
|
@ -243,22 +244,41 @@ static Transport* open_device() {
|
|||
return transport;
|
||||
}
|
||||
|
||||
Socket::Protocol protocol = Socket::Protocol::kTcp;
|
||||
std::string host;
|
||||
int port = tcp::kDefaultPort;
|
||||
if (serial != nullptr && android::base::StartsWith(serial, "tcp:")) {
|
||||
std::string error;
|
||||
const char* address = serial + strlen("tcp:");
|
||||
int port = 0;
|
||||
if (serial != nullptr) {
|
||||
const char* net_address = nullptr;
|
||||
|
||||
if (!android::base::ParseNetAddress(address, &host, &port, nullptr, &error)) {
|
||||
fprintf(stderr, "error: Invalid network address '%s': %s\n", address, error.c_str());
|
||||
return nullptr;
|
||||
if (android::base::StartsWith(serial, "tcp:")) {
|
||||
protocol = Socket::Protocol::kTcp;
|
||||
port = tcp::kDefaultPort;
|
||||
net_address = serial + strlen("tcp:");
|
||||
} else if (android::base::StartsWith(serial, "udp:")) {
|
||||
protocol = Socket::Protocol::kUdp;
|
||||
port = udp::kDefaultPort;
|
||||
net_address = serial + strlen("udp:");
|
||||
}
|
||||
|
||||
if (net_address != nullptr) {
|
||||
std::string error;
|
||||
if (!android::base::ParseNetAddress(net_address, &host, &port, nullptr, &error)) {
|
||||
fprintf(stderr, "error: Invalid network address '%s': %s\n", net_address,
|
||||
error.c_str());
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while (true) {
|
||||
if (!host.empty()) {
|
||||
std::string error;
|
||||
transport = tcp::Connect(host, port, &error).release();
|
||||
if (protocol == Socket::Protocol::kTcp) {
|
||||
transport = tcp::Connect(host, port, &error).release();
|
||||
} else if (protocol == Socket::Protocol::kUdp) {
|
||||
transport = udp::Connect(host, port, &error).release();
|
||||
}
|
||||
|
||||
if (transport == nullptr && announce) {
|
||||
fprintf(stderr, "error: %s\n", error.c_str());
|
||||
}
|
||||
|
@ -335,8 +355,9 @@ static void usage() {
|
|||
" formatting.\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"
|
||||
" For ethernet, provide an address in the"
|
||||
" form <protocol>:<hostname>[:port] where"
|
||||
" <protocol> is either tcp or udp.\n"
|
||||
" -p <product> Specify product name.\n"
|
||||
" -c <cmdline> Override kernel commandline.\n"
|
||||
" -i <vendor id> Specify a custom USB vendor id.\n"
|
||||
|
|
|
@ -17,9 +17,9 @@ Basic Requirements
|
|||
* The protocol is entirely host-driven and synchronous (unlike the
|
||||
multi-channel, bi-directional, asynchronous ADB protocol)
|
||||
|
||||
* TCP
|
||||
* TCP or UDP
|
||||
* Device must be reachable via IP.
|
||||
* Device will act as the TCP server, fastboot will be the client.
|
||||
* Device will act as the server, fastboot will be the client.
|
||||
* Fastboot data is wrapped in a simple protocol; see below for details.
|
||||
|
||||
|
||||
|
@ -217,3 +217,226 @@ 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>
|
||||
|
||||
|
||||
UDP Protocol v1
|
||||
---------------
|
||||
|
||||
The UDP protocol is more complex than TCP since we must implement reliability
|
||||
to ensure no packets are lost, but the general concept of wrapping the fastboot
|
||||
protocol is the same.
|
||||
|
||||
Overview:
|
||||
1. As with TCP, the device will listen on UDP port 5554.
|
||||
2. Maximum UDP packet size is negotiated during initialization.
|
||||
3. The host drives all communication; the device may only send a packet as a
|
||||
response to a host packet.
|
||||
4. If the host does not receive a response in 500ms it will re-transmit.
|
||||
|
||||
-- UDP Packet format --
|
||||
+----------+----+-------+-------+--------------------+
|
||||
| Byte # | 0 | 1 | 2 - 3 | 4+ |
|
||||
+----------+----+-------+-------+--------------------+
|
||||
| Contents | ID | Flags | Seq # | Data |
|
||||
+----------+----+-------+-------+--------------------+
|
||||
|
||||
ID Packet ID:
|
||||
0x00: Error.
|
||||
0x01: Query.
|
||||
0x02: Initialization.
|
||||
0x03: Fastboot.
|
||||
|
||||
Packet types are described in more detail below.
|
||||
|
||||
Flags Packet flags: 0 0 0 0 0 0 0 C
|
||||
C=1 indicates a continuation packet; the data is too large and will
|
||||
continue in the next packet.
|
||||
|
||||
Remaining bits are reserved for future use and must be set to 0.
|
||||
|
||||
Seq # 2-byte packet sequence number (big-endian). The host will increment
|
||||
this by 1 with each new packet, and the device must provide the
|
||||
corresponding sequence number in the response packets.
|
||||
|
||||
Data Packet data, not present in all packets.
|
||||
|
||||
-- Packet Types --
|
||||
Query The host sends a query packet once on startup to sync with the device.
|
||||
The host will not know the current sequence number, so the device must
|
||||
respond to all query packets regardless of sequence number.
|
||||
|
||||
The response data field should contain a 2-byte big-endian value
|
||||
giving the next expected sequence number.
|
||||
|
||||
Init The host sends an init packet once the query response is returned. The
|
||||
device must abort any in-progress operation and prepare for a new
|
||||
fastboot session. This message is meant to allow recovery if a
|
||||
previous session failed, e.g. due to network error or user Ctrl+C.
|
||||
|
||||
The data field contains two big-endian 2-byte values, a protocol
|
||||
version and the max UDP packet size (including the 4-byte header).
|
||||
Both the host and device will send these values, and in each case
|
||||
the minimum of the sent values must be used.
|
||||
|
||||
Fastboot These packets wrap the fastboot protocol. To write, the host will
|
||||
send a packet with fastboot data, and the device will reply with an
|
||||
empty packet as an ACK. To read, the host will send an empty packet,
|
||||
and the device will reply with fastboot data. The device may not give
|
||||
any data in the ACK packet.
|
||||
|
||||
Error The device may respond to any packet with an error packet to indicate
|
||||
a UDP protocol error. The data field should contain an ASCII string
|
||||
describing the error. This is the only case where a device is allowed
|
||||
to return a packet ID other than the one sent by the host.
|
||||
|
||||
-- Packet Size --
|
||||
The maximum packet size is negotiated by the host and device in the Init packet.
|
||||
Devices must support at least 512-byte packets, but packet size has a direct
|
||||
correlation with download speed, so devices are strongly suggested to support at
|
||||
least 1024-byte packets. On a local network with 0.5ms round-trip time this will
|
||||
provide transfer rates of ~2MB/s. Over WiFi it will likely be significantly
|
||||
less.
|
||||
|
||||
Query and Initialization packets, which are sent before size negotiation is
|
||||
complete, must always be 512 bytes or less.
|
||||
|
||||
-- Packet Re-Transmission --
|
||||
The host will re-transmit any packet that does not receive a response. The
|
||||
requirement of exactly one device response packet per host packet is how we
|
||||
achieve reliability and in-order delivery of packets.
|
||||
|
||||
For simplicity of implementation, there is no windowing of multiple
|
||||
unacknowledged packets in this version of the protocol. The host will continue
|
||||
to send the same packet until a response is received. Windowing functionality
|
||||
may be implemented in future versions if necessary to increase performance.
|
||||
|
||||
The first Query packet will only be attempted a small number of times, but
|
||||
subsequent packets will attempt to retransmit for at least 1 minute before
|
||||
giving up. This means a device may safely ignore host UDP packets for up to 1
|
||||
minute during long operations, e.g. writing to flash.
|
||||
|
||||
-- Continuation Packets --
|
||||
Any packet may set the continuation flag to indicate that the data is
|
||||
incomplete. Large data such as downloading an image may require many
|
||||
continuation packets. The receiver should respond to a continuation packet with
|
||||
an empty packet to acknowledge receipt. See examples below.
|
||||
|
||||
-- Summary --
|
||||
The host starts with a Query packet, then an Initialization packet, after
|
||||
which only Fastboot packets are sent. Fastboot packets may contain data from
|
||||
the host for writes, or from the device for reads, but not both.
|
||||
|
||||
Given a next expected sequence number S and a received packet P, the device
|
||||
behavior should be:
|
||||
if P is a Query packet:
|
||||
* respond with a Query packet with S in the data field
|
||||
else if P has sequence == S:
|
||||
* process P and take any required action
|
||||
* create a response packet R with the same ID and sequence as P, containing
|
||||
any response data required.
|
||||
* transmit R and save it in case of re-transmission
|
||||
* increment S
|
||||
else if P has sequence == S - 1:
|
||||
* re-transmit the saved response packet R from above
|
||||
else:
|
||||
* ignore the packet
|
||||
|
||||
-- Examples --
|
||||
In the examples below, S indicates the starting client sequence number.
|
||||
|
||||
Host Client
|
||||
======================================================================
|
||||
[Initialization, S = 0x55AA]
|
||||
[Host: version 1, 2048-byte packets. Client: version 2, 1024-byte packets.]
|
||||
[Resulting values to use: version = 1, max packet size = 1024]
|
||||
ID Flag SeqH SeqL Data ID Flag SeqH SeqL Data
|
||||
----------------------------------------------------------------------
|
||||
0x01 0x00 0x00 0x00
|
||||
0x01 0x00 0x00 0x00 0x55 0xAA
|
||||
0x02 0x00 0x55 0xAA 0x00 0x01 0x08 0x00
|
||||
0x02 0x00 0x55 0xAA 0x00 0x02 0x04 0x00
|
||||
|
||||
----------------------------------------------------------------------
|
||||
[fastboot "getvar" commands, S = 0x0001]
|
||||
ID Flags SeqH SeqL Data ID Flags SeqH SeqL Data
|
||||
----------------------------------------------------------------------
|
||||
0x03 0x00 0x00 0x01 getvar:version
|
||||
0x03 0x00 0x00 0x01
|
||||
0x03 0x00 0x00 0x02
|
||||
0x03 0x00 0x00 0x02 OKAY0.4
|
||||
0x03 0x00 0x00 0x03 getvar:foo
|
||||
0x03 0x00 0x00 0x03
|
||||
0x03 0x00 0x00 0x04
|
||||
0x03 0x00 0x00 0x04 OKAY
|
||||
|
||||
----------------------------------------------------------------------
|
||||
[fastboot "INFO" responses, S = 0x0000]
|
||||
ID Flags SeqH SeqL Data ID Flags SeqH SeqL Data
|
||||
----------------------------------------------------------------------
|
||||
0x03 0x00 0x00 0x00 <command>
|
||||
0x03 0x00 0x00 0x00
|
||||
0x03 0x00 0x00 0x01
|
||||
0x03 0x00 0x00 0x01 INFOWait1
|
||||
0x03 0x00 0x00 0x02
|
||||
0x03 0x00 0x00 0x02 INFOWait2
|
||||
0x03 0x00 0x00 0x03
|
||||
0x03 0x00 0x00 0x03 OKAY
|
||||
|
||||
----------------------------------------------------------------------
|
||||
[Chunking 2100 bytes of data, max packet size = 1024, S = 0xFFFF]
|
||||
ID Flag SeqH SeqL Data ID Flag SeqH SeqL Data
|
||||
----------------------------------------------------------------------
|
||||
0x03 0x00 0xFF 0xFF download:0000834
|
||||
0x03 0x00 0xFF 0xFF
|
||||
0x03 0x00 0x00 0x00
|
||||
0x03 0x00 0x00 0x00 DATA0000834
|
||||
0x03 0x01 0x00 0x01 <1020 bytes>
|
||||
0x03 0x00 0x00 0x01
|
||||
0x03 0x01 0x00 0x02 <1020 bytes>
|
||||
0x03 0x00 0x00 0x02
|
||||
0x03 0x00 0x00 0x03 <60 bytes>
|
||||
0x03 0x00 0x00 0x03
|
||||
0x03 0x00 0x00 0x04
|
||||
0x03 0x00 0x00 0x04 OKAY
|
||||
|
||||
----------------------------------------------------------------------
|
||||
[Unknown ID error, S = 0x0000]
|
||||
ID Flags SeqH SeqL Data ID Flags SeqH SeqL Data
|
||||
----------------------------------------------------------------------
|
||||
0x10 0x00 0x00 0x00
|
||||
0x00 0x00 0x00 0x00 <error message>
|
||||
|
||||
----------------------------------------------------------------------
|
||||
[Host packet loss and retransmission, S = 0x0000]
|
||||
ID Flags SeqH SeqL Data ID Flags SeqH SeqL Data
|
||||
----------------------------------------------------------------------
|
||||
0x03 0x00 0x00 0x00 getvar:version [lost]
|
||||
0x03 0x00 0x00 0x00 getvar:version [lost]
|
||||
0x03 0x00 0x00 0x00 getvar:version
|
||||
0x03 0x00 0x00 0x00
|
||||
0x03 0x00 0x00 0x01
|
||||
0x03 0x00 0x00 0x01 OKAY0.4
|
||||
|
||||
----------------------------------------------------------------------
|
||||
[Client packet loss and retransmission, S = 0x0000]
|
||||
ID Flags SeqH SeqL Data ID Flags SeqH SeqL Data
|
||||
----------------------------------------------------------------------
|
||||
0x03 0x00 0x00 0x00 getvar:version
|
||||
0x03 0x00 0x00 0x00 [lost]
|
||||
0x03 0x00 0x00 0x00 getvar:version
|
||||
0x03 0x00 0x00 0x00 [lost]
|
||||
0x03 0x00 0x00 0x00 getvar:version
|
||||
0x03 0x00 0x00 0x00
|
||||
0x03 0x00 0x00 0x01
|
||||
0x03 0x00 0x00 0x01 OKAY0.4
|
||||
|
||||
----------------------------------------------------------------------
|
||||
[Host packet delayed, S = 0x0000]
|
||||
ID Flags SeqH SeqL Data ID Flags SeqH SeqL Data
|
||||
----------------------------------------------------------------------
|
||||
0x03 0x00 0x00 0x00 getvar:version [delayed]
|
||||
0x03 0x00 0x00 0x00 getvar:version
|
||||
0x03 0x00 0x00 0x00
|
||||
0x03 0x00 0x00 0x01
|
||||
0x03 0x00 0x00 0x01 OKAY0.4
|
||||
0x03 0x00 0x00 0x00 getvar:version [arrives late with old seq#, is ignored]
|
||||
|
|
391
fastboot/udp.cpp
Normal file
391
fastboot/udp.cpp
Normal file
|
@ -0,0 +1,391 @@
|
|||
/*
|
||||
* Copyright (C) 2015 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.
|
||||
*/
|
||||
|
||||
// This file implements the fastboot UDP protocol; see fastboot_protocol.txt for documentation.
|
||||
|
||||
#include "udp.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include <android-base/macros.h>
|
||||
#include <android-base/stringprintf.h>
|
||||
|
||||
#include "socket.h"
|
||||
|
||||
namespace udp {
|
||||
|
||||
using namespace internal;
|
||||
|
||||
constexpr size_t kMinPacketSize = 512;
|
||||
constexpr size_t kHeaderSize = 4;
|
||||
|
||||
enum Index {
|
||||
kIndexId = 0,
|
||||
kIndexFlags = 1,
|
||||
kIndexSeqH = 2,
|
||||
kIndexSeqL = 3,
|
||||
};
|
||||
|
||||
// Extracts a big-endian uint16_t from a byte array.
|
||||
static uint16_t ExtractUint16(const uint8_t* bytes) {
|
||||
return (static_cast<uint16_t>(bytes[0]) << 8) | bytes[1];
|
||||
}
|
||||
|
||||
// Packet header handling.
|
||||
class Header {
|
||||
public:
|
||||
Header();
|
||||
~Header() = default;
|
||||
|
||||
uint8_t id() const { return bytes_[kIndexId]; }
|
||||
const uint8_t* bytes() const { return bytes_; }
|
||||
|
||||
void Set(uint8_t id, uint16_t sequence, Flag flag);
|
||||
|
||||
// Checks whether |response| is a match for this header.
|
||||
bool Matches(const uint8_t* response);
|
||||
|
||||
private:
|
||||
uint8_t bytes_[kHeaderSize];
|
||||
};
|
||||
|
||||
Header::Header() {
|
||||
Set(kIdError, 0, kFlagNone);
|
||||
}
|
||||
|
||||
void Header::Set(uint8_t id, uint16_t sequence, Flag flag) {
|
||||
bytes_[kIndexId] = id;
|
||||
bytes_[kIndexFlags] = flag;
|
||||
bytes_[kIndexSeqH] = sequence >> 8;
|
||||
bytes_[kIndexSeqL] = sequence;
|
||||
}
|
||||
|
||||
bool Header::Matches(const uint8_t* response) {
|
||||
// Sequence numbers must be the same to match, but the response ID can either be the same
|
||||
// or an error response which is always accepted.
|
||||
return bytes_[kIndexSeqH] == response[kIndexSeqH] &&
|
||||
bytes_[kIndexSeqL] == response[kIndexSeqL] &&
|
||||
(bytes_[kIndexId] == response[kIndexId] || response[kIndexId] == kIdError);
|
||||
}
|
||||
|
||||
// Implements the Transport interface to work with the fastboot engine.
|
||||
class UdpTransport : public Transport {
|
||||
public:
|
||||
// Factory function so we can return nullptr if initialization fails.
|
||||
static std::unique_ptr<UdpTransport> NewTransport(std::unique_ptr<Socket> socket,
|
||||
std::string* error);
|
||||
~UdpTransport() 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:
|
||||
UdpTransport(std::unique_ptr<Socket> socket) : socket_(std::move(socket)) {}
|
||||
|
||||
// Performs the UDP initialization procedure. Returns true on success.
|
||||
bool InitializeProtocol(std::string* error);
|
||||
|
||||
// Sends |length| bytes from |data| and waits for the response packet up to |attempts| times.
|
||||
// Continuation packets are handled automatically and any return data is written to |rx_data|.
|
||||
// Excess bytes that cannot fit in |rx_data| are dropped.
|
||||
// On success, returns the number of response data bytes received, which may be greater than
|
||||
// |rx_length|. On failure, returns -1 and fills |error| on failure.
|
||||
ssize_t SendData(Id id, const uint8_t* tx_data, size_t tx_length, uint8_t* rx_data,
|
||||
size_t rx_length, int attempts, std::string* error);
|
||||
|
||||
// Helper for SendData(); sends a single packet and handles the response. |header| specifies
|
||||
// the initial outgoing packet information but may be modified by this function.
|
||||
ssize_t SendSinglePacketHelper(Header* header, const uint8_t* tx_data, size_t tx_length,
|
||||
uint8_t* rx_data, size_t rx_length, int attempts,
|
||||
std::string* error);
|
||||
|
||||
std::unique_ptr<Socket> socket_;
|
||||
int sequence_ = -1;
|
||||
size_t max_data_length_ = kMinPacketSize - kHeaderSize;
|
||||
std::vector<uint8_t> rx_packet_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(UdpTransport);
|
||||
};
|
||||
|
||||
std::unique_ptr<UdpTransport> UdpTransport::NewTransport(std::unique_ptr<Socket> socket,
|
||||
std::string* error) {
|
||||
std::unique_ptr<UdpTransport> transport(new UdpTransport(std::move(socket)));
|
||||
|
||||
if (!transport->InitializeProtocol(error)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return transport;
|
||||
}
|
||||
|
||||
bool UdpTransport::InitializeProtocol(std::string* error) {
|
||||
uint8_t rx_data[4];
|
||||
|
||||
sequence_ = 0;
|
||||
rx_packet_.resize(kMinPacketSize);
|
||||
|
||||
// First send the query packet to sync with the target. Only attempt this a small number of
|
||||
// times so we can fail out quickly if the target isn't available.
|
||||
ssize_t rx_bytes = SendData(kIdDeviceQuery, nullptr, 0, rx_data, sizeof(rx_data),
|
||||
kMaxConnectAttempts, error);
|
||||
if (rx_bytes == -1) {
|
||||
return false;
|
||||
} else if (rx_bytes < 2) {
|
||||
*error = "invalid query response from target";
|
||||
return false;
|
||||
}
|
||||
// The first two bytes contain the next expected sequence number.
|
||||
sequence_ = ExtractUint16(rx_data);
|
||||
|
||||
// Now send the initialization packet with our version and maximum packet size.
|
||||
uint8_t init_data[] = {kProtocolVersion >> 8, kProtocolVersion & 0xFF,
|
||||
kHostMaxPacketSize >> 8, kHostMaxPacketSize & 0xFF};
|
||||
rx_bytes = SendData(kIdInitialization, init_data, sizeof(init_data), rx_data, sizeof(rx_data),
|
||||
kMaxTransmissionAttempts, error);
|
||||
if (rx_bytes == -1) {
|
||||
return false;
|
||||
} else if (rx_bytes < 4) {
|
||||
*error = "invalid initialization response from target";
|
||||
return false;
|
||||
}
|
||||
|
||||
// The first two data bytes contain the version, the second two bytes contain the target max
|
||||
// supported packet size, which must be at least 512 bytes.
|
||||
uint16_t version = ExtractUint16(rx_data);
|
||||
if (version < kProtocolVersion) {
|
||||
*error = android::base::StringPrintf("target reported invalid protocol version %d",
|
||||
version);
|
||||
return false;
|
||||
}
|
||||
uint16_t packet_size = ExtractUint16(rx_data + 2);
|
||||
if (packet_size < kMinPacketSize) {
|
||||
*error = android::base::StringPrintf("target reported invalid packet size %d", packet_size);
|
||||
return false;
|
||||
}
|
||||
|
||||
packet_size = std::min(kHostMaxPacketSize, packet_size);
|
||||
max_data_length_ = packet_size - kHeaderSize;
|
||||
rx_packet_.resize(packet_size);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// SendData() is just responsible for chunking |data| into packets until it's all been sent.
|
||||
// Per-packet timeout/retransmission logic is done in SendSinglePacketHelper().
|
||||
ssize_t UdpTransport::SendData(Id id, const uint8_t* tx_data, size_t tx_length, uint8_t* rx_data,
|
||||
size_t rx_length, int attempts, std::string* error) {
|
||||
if (socket_ == nullptr) {
|
||||
*error = "socket is closed";
|
||||
return -1;
|
||||
}
|
||||
|
||||
Header header;
|
||||
size_t packet_data_length;
|
||||
ssize_t ret = 0;
|
||||
// We often send header-only packets with no data as part of the protocol, so always send at
|
||||
// least once even if |length| == 0, then repeat until we've sent all of |data|.
|
||||
do {
|
||||
// Set the continuation flag and truncate packet data if needed.
|
||||
if (tx_length > max_data_length_) {
|
||||
packet_data_length = max_data_length_;
|
||||
header.Set(id, sequence_, kFlagContinuation);
|
||||
} else {
|
||||
packet_data_length = tx_length;
|
||||
header.Set(id, sequence_, kFlagNone);
|
||||
}
|
||||
|
||||
ssize_t bytes = SendSinglePacketHelper(&header, tx_data, packet_data_length, rx_data,
|
||||
rx_length, attempts, error);
|
||||
|
||||
// Advance our read and write buffers for the next packet. Keep going even if we run out
|
||||
// of receive buffer space so we can detect overflows.
|
||||
if (bytes == -1) {
|
||||
return -1;
|
||||
} else if (static_cast<size_t>(bytes) < rx_length) {
|
||||
rx_data += bytes;
|
||||
rx_length -= bytes;
|
||||
} else {
|
||||
rx_data = nullptr;
|
||||
rx_length = 0;
|
||||
}
|
||||
|
||||
tx_length -= packet_data_length;
|
||||
tx_data += packet_data_length;
|
||||
|
||||
ret += bytes;
|
||||
} while (tx_length > 0);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
ssize_t UdpTransport::SendSinglePacketHelper(
|
||||
Header* header, const uint8_t* tx_data, size_t tx_length, uint8_t* rx_data,
|
||||
size_t rx_length, const int attempts, std::string* error) {
|
||||
ssize_t total_data_bytes = 0;
|
||||
error->clear();
|
||||
|
||||
int attempts_left = attempts;
|
||||
while (attempts_left > 0) {
|
||||
if (!socket_->Send({{header->bytes(), kHeaderSize}, {tx_data, tx_length}})) {
|
||||
*error = Socket::GetErrorMessage();
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Keep receiving until we get a matching response or we timeout.
|
||||
ssize_t bytes = 0;
|
||||
do {
|
||||
bytes = socket_->Receive(rx_packet_.data(), rx_packet_.size(), kResponseTimeoutMs);
|
||||
if (bytes == -1) {
|
||||
if (socket_->ReceiveTimedOut()) {
|
||||
break;
|
||||
}
|
||||
*error = Socket::GetErrorMessage();
|
||||
return -1;
|
||||
} else if (bytes < static_cast<ssize_t>(kHeaderSize)) {
|
||||
*error = "protocol error: incomplete header";
|
||||
return -1;
|
||||
}
|
||||
} while (!header->Matches(rx_packet_.data()));
|
||||
|
||||
if (socket_->ReceiveTimedOut()) {
|
||||
--attempts_left;
|
||||
continue;
|
||||
}
|
||||
++sequence_;
|
||||
|
||||
// Save to |error| or |rx_data| as appropriate.
|
||||
if (rx_packet_[kIndexId] == kIdError) {
|
||||
error->append(rx_packet_.data() + kHeaderSize, rx_packet_.data() + bytes);
|
||||
} else {
|
||||
total_data_bytes += bytes - kHeaderSize;
|
||||
size_t rx_data_bytes = std::min<size_t>(bytes - kHeaderSize, rx_length);
|
||||
if (rx_data_bytes > 0) {
|
||||
memcpy(rx_data, rx_packet_.data() + kHeaderSize, rx_data_bytes);
|
||||
rx_data += rx_data_bytes;
|
||||
rx_length -= rx_data_bytes;
|
||||
}
|
||||
}
|
||||
|
||||
// If the response has a continuation flag we need to prompt for more data by sending
|
||||
// an empty packet.
|
||||
if (rx_packet_[kIndexFlags] & kFlagContinuation) {
|
||||
// We got a valid response so reset our attempt counter.
|
||||
attempts_left = attempts;
|
||||
header->Set(rx_packet_[kIndexId], sequence_, kFlagNone);
|
||||
tx_data = nullptr;
|
||||
tx_length = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (attempts_left <= 0) {
|
||||
*error = "no response from target";
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (rx_packet_[kIndexId] == kIdError) {
|
||||
*error = "target reported error: " + *error;
|
||||
return -1;
|
||||
}
|
||||
|
||||
return total_data_bytes;
|
||||
}
|
||||
|
||||
ssize_t UdpTransport::Read(void* data, size_t length) {
|
||||
// Read from the target by sending an empty packet.
|
||||
std::string error;
|
||||
ssize_t bytes = SendData(kIdFastboot, nullptr, 0, reinterpret_cast<uint8_t*>(data), length,
|
||||
kMaxTransmissionAttempts, &error);
|
||||
|
||||
if (bytes == -1) {
|
||||
fprintf(stderr, "UDP error: %s\n", error.c_str());
|
||||
return -1;
|
||||
} else if (static_cast<size_t>(bytes) > length) {
|
||||
// Fastboot protocol error: the target sent more data than our fastboot engine was prepared
|
||||
// to receive.
|
||||
fprintf(stderr, "UDP error: receive overflow, target sent too much fastboot data\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
ssize_t UdpTransport::Write(const void* data, size_t length) {
|
||||
std::string error;
|
||||
ssize_t bytes = SendData(kIdFastboot, reinterpret_cast<const uint8_t*>(data), length, nullptr,
|
||||
0, kMaxTransmissionAttempts, &error);
|
||||
|
||||
if (bytes == -1) {
|
||||
fprintf(stderr, "UDP error: %s\n", error.c_str());
|
||||
return -1;
|
||||
} else if (bytes > 0) {
|
||||
// UDP protocol error: only empty ACK packets are allowed when writing to a device.
|
||||
fprintf(stderr, "UDP error: target sent fastboot data out-of-turn\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
int UdpTransport::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::kUdp, 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 UdpTransport::NewTransport(std::move(sock), error);
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
|
||||
} // namespace udp
|
81
fastboot/udp.h
Normal file
81
fastboot/udp.h
Normal file
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
* Copyright (C) 2015 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 UDP_H_
|
||||
#define UDP_H_
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "socket.h"
|
||||
#include "transport.h"
|
||||
|
||||
namespace udp {
|
||||
|
||||
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 {
|
||||
|
||||
constexpr uint16_t kProtocolVersion = 1;
|
||||
|
||||
// This will be negotiated with the device so may end up being smaller.
|
||||
constexpr uint16_t kHostMaxPacketSize = 8192;
|
||||
|
||||
// Retransmission constants. Retransmission timeout must be at least 500ms, and the host must
|
||||
// attempt to send packets for at least 1 minute once the device has connected. See
|
||||
// fastboot_protocol.txt for more information.
|
||||
constexpr int kResponseTimeoutMs = 500;
|
||||
constexpr int kMaxConnectAttempts = 4;
|
||||
constexpr int kMaxTransmissionAttempts = 60 * 1000 / kResponseTimeoutMs;
|
||||
|
||||
enum Id : uint8_t {
|
||||
kIdError = 0x00,
|
||||
kIdDeviceQuery = 0x01,
|
||||
kIdInitialization = 0x02,
|
||||
kIdFastboot = 0x03
|
||||
};
|
||||
|
||||
enum Flag : uint8_t {
|
||||
kFlagNone = 0x00,
|
||||
kFlagContinuation = 0x01
|
||||
};
|
||||
|
||||
// Creates a UDP Transport object using a given Socket. 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 udp
|
||||
|
||||
#endif // UDP_H_
|
531
fastboot/udp_test.cpp
Normal file
531
fastboot/udp_test.cpp
Normal file
|
@ -0,0 +1,531 @@
|
|||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "udp.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "socket.h"
|
||||
#include "socket_mock.h"
|
||||
|
||||
using namespace udp;
|
||||
using namespace udp::internal;
|
||||
|
||||
// Some possible corner case sequence numbers we want to check.
|
||||
static const uint16_t kTestSequenceNumbers[] = {0x0000, 0x0001, 0x00FF, 0x0100,
|
||||
0x7FFF, 0x8000, 0xFFFF};
|
||||
|
||||
// Converts |value| to a binary big-endian string.
|
||||
static std::string PacketValue(uint16_t value) {
|
||||
return std::string{static_cast<char>(value >> 8), static_cast<char>(value)};
|
||||
}
|
||||
|
||||
// Returns an Error packet.
|
||||
static std::string ErrorPacket(uint16_t sequence, const std::string& message = "",
|
||||
char flags = kFlagNone) {
|
||||
return std::string{kIdError, flags} + PacketValue(sequence) + message;
|
||||
}
|
||||
|
||||
// Returns a Query packet with no data.
|
||||
static std::string QueryPacket(uint16_t sequence) {
|
||||
return std::string{kIdDeviceQuery, kFlagNone} + PacketValue(sequence);
|
||||
}
|
||||
|
||||
// Returns a Query packet with a 2-byte |new_sequence|.
|
||||
static std::string QueryPacket(uint16_t sequence, uint16_t new_sequence) {
|
||||
return std::string{kIdDeviceQuery, kFlagNone} + PacketValue(sequence) +
|
||||
PacketValue(new_sequence);
|
||||
}
|
||||
|
||||
// Returns an Init packet with a 2-byte |version| and |max_packet_size|.
|
||||
static std::string InitPacket(uint16_t sequence, uint16_t version, uint16_t max_packet_size) {
|
||||
return std::string{kIdInitialization, kFlagNone} + PacketValue(sequence) +
|
||||
PacketValue(version) + PacketValue(max_packet_size);
|
||||
}
|
||||
|
||||
// Returns a Fastboot packet with |data|.
|
||||
static std::string FastbootPacket(uint16_t sequence, const std::string& data = "",
|
||||
char flags = kFlagNone) {
|
||||
return std::string{kIdFastboot, flags} + PacketValue(sequence) + data;
|
||||
}
|
||||
|
||||
// Fixture class to test protocol initialization. Usage is to set up the expected calls to the
|
||||
// SocketMock object then call UdpConnect() and check the result.
|
||||
class UdpConnectTest : public ::testing::Test {
|
||||
public:
|
||||
UdpConnectTest() : mock_socket_(new SocketMock) {}
|
||||
|
||||
// Run the initialization, return whether it was successful or not. This passes ownership of
|
||||
// the current |mock_socket_| but allocates a new one for re-use.
|
||||
bool UdpConnect(std::string* error = nullptr) {
|
||||
std::string local_error;
|
||||
if (error == nullptr) {
|
||||
error = &local_error;
|
||||
}
|
||||
std::unique_ptr<Transport> transport(Connect(std::move(mock_socket_), error));
|
||||
mock_socket_.reset(new SocketMock);
|
||||
return transport != nullptr && error->empty();
|
||||
}
|
||||
|
||||
protected:
|
||||
std::unique_ptr<SocketMock> mock_socket_;
|
||||
};
|
||||
|
||||
// Tests a successful protocol initialization with various starting sequence numbers.
|
||||
TEST_F(UdpConnectTest, InitializationSuccess) {
|
||||
for (uint16_t seq : kTestSequenceNumbers) {
|
||||
mock_socket_->ExpectSend(QueryPacket(0));
|
||||
mock_socket_->AddReceive(QueryPacket(0, seq));
|
||||
mock_socket_->ExpectSend(InitPacket(seq, kProtocolVersion, kHostMaxPacketSize));
|
||||
mock_socket_->AddReceive(InitPacket(seq, kProtocolVersion, 1024));
|
||||
|
||||
EXPECT_TRUE(UdpConnect());
|
||||
}
|
||||
}
|
||||
|
||||
// Tests continuation packets during initialization.
|
||||
TEST_F(UdpConnectTest, InitializationContinuationSuccess) {
|
||||
mock_socket_->ExpectSend(QueryPacket(0));
|
||||
mock_socket_->AddReceive(std::string{kIdDeviceQuery, kFlagContinuation, 0, 0, 0x44});
|
||||
mock_socket_->ExpectSend(std::string{kIdDeviceQuery, kFlagNone, 0, 1});
|
||||
mock_socket_->AddReceive(std::string{kIdDeviceQuery, kFlagNone, 0, 1, 0x55});
|
||||
|
||||
mock_socket_->ExpectSend(InitPacket(0x4455, kProtocolVersion, kHostMaxPacketSize));
|
||||
mock_socket_->AddReceive(std::string{kIdInitialization, kFlagContinuation, 0x44, 0x55, 0});
|
||||
mock_socket_->ExpectSend(std::string{kIdInitialization, kFlagNone, 0x44, 0x56});
|
||||
mock_socket_->AddReceive(std::string{kIdInitialization, kFlagContinuation, 0x44, 0x56, 1});
|
||||
mock_socket_->ExpectSend(std::string{kIdInitialization, kFlagNone, 0x44, 0x57});
|
||||
mock_socket_->AddReceive(std::string{kIdInitialization, kFlagContinuation, 0x44, 0x57, 2});
|
||||
mock_socket_->ExpectSend(std::string{kIdInitialization, kFlagNone, 0x44, 0x58});
|
||||
mock_socket_->AddReceive(std::string{kIdInitialization, kFlagNone, 0x44, 0x58, 0});
|
||||
|
||||
EXPECT_TRUE(UdpConnect());
|
||||
}
|
||||
|
||||
|
||||
// Tests a mismatched version number; as long as the minimum of the two versions is supported
|
||||
// we should allow the connection.
|
||||
TEST_F(UdpConnectTest, InitializationVersionMismatch) {
|
||||
mock_socket_->ExpectSend(QueryPacket(0));
|
||||
mock_socket_->AddReceive(QueryPacket(0, 0));
|
||||
mock_socket_->ExpectSend(InitPacket(0, kProtocolVersion, kHostMaxPacketSize));
|
||||
mock_socket_->AddReceive(InitPacket(0, 2, 1024));
|
||||
|
||||
EXPECT_TRUE(UdpConnect());
|
||||
|
||||
mock_socket_->ExpectSend(QueryPacket(0));
|
||||
mock_socket_->AddReceive(QueryPacket(0, 0));
|
||||
mock_socket_->ExpectSend(InitPacket(0, kProtocolVersion, kHostMaxPacketSize));
|
||||
mock_socket_->AddReceive(InitPacket(0, 0, 1024));
|
||||
|
||||
EXPECT_FALSE(UdpConnect());
|
||||
}
|
||||
|
||||
TEST_F(UdpConnectTest, QueryResponseTimeoutFailure) {
|
||||
for (int i = 0; i < kMaxConnectAttempts; ++i) {
|
||||
mock_socket_->ExpectSend(QueryPacket(0));
|
||||
mock_socket_->AddReceiveTimeout();
|
||||
}
|
||||
|
||||
EXPECT_FALSE(UdpConnect());
|
||||
}
|
||||
|
||||
TEST_F(UdpConnectTest, QueryResponseReceiveFailure) {
|
||||
mock_socket_->ExpectSend(QueryPacket(0));
|
||||
mock_socket_->AddReceiveFailure();
|
||||
|
||||
EXPECT_FALSE(UdpConnect());
|
||||
}
|
||||
|
||||
TEST_F(UdpConnectTest, InitResponseTimeoutFailure) {
|
||||
mock_socket_->ExpectSend(QueryPacket(0));
|
||||
mock_socket_->AddReceive(QueryPacket(0, 0));
|
||||
for (int i = 0; i < kMaxTransmissionAttempts; ++i) {
|
||||
mock_socket_->ExpectSend(InitPacket(0, kProtocolVersion, kHostMaxPacketSize));
|
||||
mock_socket_->AddReceiveTimeout();
|
||||
}
|
||||
|
||||
EXPECT_FALSE(UdpConnect());
|
||||
}
|
||||
|
||||
TEST_F(UdpConnectTest, InitResponseReceiveFailure) {
|
||||
mock_socket_->ExpectSend(QueryPacket(0));
|
||||
mock_socket_->AddReceive(QueryPacket(0, 0));
|
||||
mock_socket_->ExpectSend(InitPacket(0, kProtocolVersion, kHostMaxPacketSize));
|
||||
mock_socket_->AddReceiveFailure();
|
||||
|
||||
EXPECT_FALSE(UdpConnect());
|
||||
}
|
||||
|
||||
// Tests that we can recover up to the maximum number of allowed retries.
|
||||
TEST_F(UdpConnectTest, ResponseRecovery) {
|
||||
// The device query packet can recover from up to (kMaxConnectAttempts - 1) timeouts.
|
||||
for (int i = 0; i < kMaxConnectAttempts - 1; ++i) {
|
||||
mock_socket_->ExpectSend(QueryPacket(0));
|
||||
mock_socket_->AddReceiveTimeout();
|
||||
}
|
||||
mock_socket_->ExpectSend(QueryPacket(0));
|
||||
mock_socket_->AddReceive(QueryPacket(0, 0));
|
||||
|
||||
// Subsequent packets try up to (kMaxTransmissionAttempts - 1) times.
|
||||
for (int i = 0; i < kMaxTransmissionAttempts - 1; ++i) {
|
||||
mock_socket_->ExpectSend(InitPacket(0, kProtocolVersion, kHostMaxPacketSize));
|
||||
mock_socket_->AddReceiveTimeout();
|
||||
}
|
||||
mock_socket_->ExpectSend(InitPacket(0, kProtocolVersion, kHostMaxPacketSize));
|
||||
mock_socket_->AddReceive(InitPacket(0, kProtocolVersion, 1024));
|
||||
|
||||
EXPECT_TRUE(UdpConnect());
|
||||
}
|
||||
|
||||
// Tests that the host can handle receiving additional bytes for forward compatibility.
|
||||
TEST_F(UdpConnectTest, ExtraResponseDataSuccess) {
|
||||
mock_socket_->ExpectSend(QueryPacket(0));
|
||||
mock_socket_->AddReceive(QueryPacket(0, 0) + "foo");
|
||||
mock_socket_->ExpectSend(InitPacket(0, kProtocolVersion, kHostMaxPacketSize));
|
||||
mock_socket_->AddReceive(InitPacket(0, kProtocolVersion, 1024) + "bar");
|
||||
|
||||
EXPECT_TRUE(UdpConnect());
|
||||
}
|
||||
|
||||
// Tests mismatched response sequence numbers. A wrong sequence number is interpreted as a previous
|
||||
// retransmission and just ignored so we should be able to recover.
|
||||
TEST_F(UdpConnectTest, WrongSequenceRecovery) {
|
||||
mock_socket_->ExpectSend(QueryPacket(0));
|
||||
mock_socket_->AddReceive(QueryPacket(1, 0));
|
||||
mock_socket_->AddReceive(QueryPacket(0, 0));
|
||||
|
||||
mock_socket_->ExpectSend(InitPacket(0, kProtocolVersion, kHostMaxPacketSize));
|
||||
mock_socket_->AddReceive(InitPacket(1, kProtocolVersion, 1024));
|
||||
mock_socket_->AddReceive(InitPacket(0, kProtocolVersion, 1024));
|
||||
|
||||
EXPECT_TRUE(UdpConnect());
|
||||
}
|
||||
|
||||
// Tests mismatched response IDs. This should also be interpreted as a retransmission and ignored.
|
||||
TEST_F(UdpConnectTest, WrongIdRecovery) {
|
||||
mock_socket_->ExpectSend(QueryPacket(0));
|
||||
mock_socket_->AddReceive(FastbootPacket(0));
|
||||
mock_socket_->AddReceive(QueryPacket(0, 0));
|
||||
|
||||
mock_socket_->ExpectSend(InitPacket(0, kProtocolVersion, kHostMaxPacketSize));
|
||||
mock_socket_->AddReceive(FastbootPacket(0));
|
||||
mock_socket_->AddReceive(InitPacket(0, kProtocolVersion, 1024));
|
||||
|
||||
EXPECT_TRUE(UdpConnect());
|
||||
}
|
||||
|
||||
// Tests an invalid query response. Query responses must have at least 2 bytes of data.
|
||||
TEST_F(UdpConnectTest, InvalidQueryResponseFailure) {
|
||||
std::string error;
|
||||
|
||||
mock_socket_->ExpectSend(QueryPacket(0));
|
||||
mock_socket_->AddReceive(QueryPacket(0));
|
||||
|
||||
EXPECT_FALSE(UdpConnect(&error));
|
||||
EXPECT_EQ("invalid query response from target", error);
|
||||
|
||||
mock_socket_->ExpectSend(QueryPacket(0));
|
||||
mock_socket_->AddReceive(QueryPacket(0) + std::string{0x00});
|
||||
|
||||
EXPECT_FALSE(UdpConnect(&error));
|
||||
EXPECT_EQ("invalid query response from target", error);
|
||||
}
|
||||
|
||||
// Tests an invalid initialization response. Max packet size must be at least 512 bytes.
|
||||
TEST_F(UdpConnectTest, InvalidInitResponseFailure) {
|
||||
std::string error;
|
||||
|
||||
mock_socket_->ExpectSend(QueryPacket(0));
|
||||
mock_socket_->AddReceive(QueryPacket(0, 0));
|
||||
mock_socket_->ExpectSend(InitPacket(0, kProtocolVersion, kHostMaxPacketSize));
|
||||
mock_socket_->AddReceive(InitPacket(0, kProtocolVersion, 511));
|
||||
|
||||
EXPECT_FALSE(UdpConnect(&error));
|
||||
EXPECT_EQ("target reported invalid packet size 511", error);
|
||||
|
||||
mock_socket_->ExpectSend(QueryPacket(0));
|
||||
mock_socket_->AddReceive(QueryPacket(0, 0));
|
||||
mock_socket_->ExpectSend(InitPacket(0, kProtocolVersion, kHostMaxPacketSize));
|
||||
mock_socket_->AddReceive(InitPacket(0, 0, 1024));
|
||||
|
||||
EXPECT_FALSE(UdpConnect(&error));
|
||||
EXPECT_EQ("target reported invalid protocol version 0", error);
|
||||
}
|
||||
|
||||
TEST_F(UdpConnectTest, ErrorResponseFailure) {
|
||||
std::string error;
|
||||
|
||||
mock_socket_->ExpectSend(QueryPacket(0));
|
||||
mock_socket_->AddReceive(ErrorPacket(0, "error1"));
|
||||
|
||||
EXPECT_FALSE(UdpConnect(&error));
|
||||
EXPECT_NE(std::string::npos, error.find("error1"));
|
||||
|
||||
mock_socket_->ExpectSend(QueryPacket(0));
|
||||
mock_socket_->AddReceive(QueryPacket(0, 0));
|
||||
mock_socket_->ExpectSend(InitPacket(0, kProtocolVersion, kHostMaxPacketSize));
|
||||
mock_socket_->AddReceive(ErrorPacket(0, "error2"));
|
||||
|
||||
EXPECT_FALSE(UdpConnect(&error));
|
||||
EXPECT_NE(std::string::npos, error.find("error2"));
|
||||
}
|
||||
|
||||
// Tests an error response with continuation flag.
|
||||
TEST_F(UdpConnectTest, ErrorContinuationFailure) {
|
||||
std::string error;
|
||||
|
||||
mock_socket_->ExpectSend(QueryPacket(0));
|
||||
mock_socket_->AddReceive(ErrorPacket(0, "error1", kFlagContinuation));
|
||||
mock_socket_->ExpectSend(ErrorPacket(1));
|
||||
mock_socket_->AddReceive(ErrorPacket(1, " ", kFlagContinuation));
|
||||
mock_socket_->ExpectSend(ErrorPacket(2));
|
||||
mock_socket_->AddReceive(ErrorPacket(2, "error2"));
|
||||
|
||||
EXPECT_FALSE(UdpConnect(&error));
|
||||
EXPECT_NE(std::string::npos, error.find("error1 error2"));
|
||||
}
|
||||
|
||||
// Fixture class to test UDP Transport read/write functionality.
|
||||
class UdpTest : public ::testing::Test {
|
||||
public:
|
||||
void SetUp() override {
|
||||
// Create |transport_| starting at sequence 0 with 512 byte max packet size. Tests can call
|
||||
// InitializeTransport() again to change settings.
|
||||
ASSERT_TRUE(InitializeTransport(0, 512));
|
||||
}
|
||||
|
||||
// Sets up |mock_socket_| to correctly initialize the protocol and creates |transport_|. This
|
||||
// can be called multiple times in a test if needed.
|
||||
bool InitializeTransport(uint16_t starting_sequence, int device_max_packet_size = 512) {
|
||||
mock_socket_ = new SocketMock;
|
||||
mock_socket_->ExpectSend(QueryPacket(0));
|
||||
mock_socket_->AddReceive(QueryPacket(0, starting_sequence));
|
||||
mock_socket_->ExpectSend(
|
||||
InitPacket(starting_sequence, kProtocolVersion, kHostMaxPacketSize));
|
||||
mock_socket_->AddReceive(
|
||||
InitPacket(starting_sequence, kProtocolVersion, device_max_packet_size));
|
||||
|
||||
std::string error;
|
||||
transport_ = Connect(std::unique_ptr<Socket>(mock_socket_), &error);
|
||||
return transport_ != nullptr && error.empty();
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
protected:
|
||||
// |mock_socket_| is a raw pointer here because we transfer ownership to |transport_| but we
|
||||
// need to retain a pointer to set send and receive expectations.
|
||||
SocketMock* mock_socket_ = nullptr;
|
||||
std::unique_ptr<Transport> transport_;
|
||||
};
|
||||
|
||||
// Tests sequence behavior with various starting sequence numbers.
|
||||
TEST_F(UdpTest, SequenceIncrementCheck) {
|
||||
for (uint16_t seq : kTestSequenceNumbers) {
|
||||
ASSERT_TRUE(InitializeTransport(seq));
|
||||
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
mock_socket_->ExpectSend(FastbootPacket(++seq, "foo"));
|
||||
mock_socket_->AddReceive(FastbootPacket(seq, ""));
|
||||
mock_socket_->ExpectSend(FastbootPacket(++seq, ""));
|
||||
mock_socket_->AddReceive(FastbootPacket(seq, "bar"));
|
||||
|
||||
EXPECT_TRUE(Write("foo"));
|
||||
EXPECT_TRUE(Read("bar"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tests sending and receiving a few small packets.
|
||||
TEST_F(UdpTest, ReadAndWriteSmallPackets) {
|
||||
mock_socket_->ExpectSend(FastbootPacket(1, "foo"));
|
||||
mock_socket_->AddReceive(FastbootPacket(1, ""));
|
||||
mock_socket_->ExpectSend(FastbootPacket(2, ""));
|
||||
mock_socket_->AddReceive(FastbootPacket(2, "bar"));
|
||||
|
||||
EXPECT_TRUE(Write("foo"));
|
||||
EXPECT_TRUE(Read("bar"));
|
||||
|
||||
mock_socket_->ExpectSend(FastbootPacket(3, "12345 67890"));
|
||||
mock_socket_->AddReceive(FastbootPacket(3));
|
||||
mock_socket_->ExpectSend(FastbootPacket(4, "\x01\x02\x03\x04\x05"));
|
||||
mock_socket_->AddReceive(FastbootPacket(4));
|
||||
|
||||
EXPECT_TRUE(Write("12345 67890"));
|
||||
EXPECT_TRUE(Write("\x01\x02\x03\x04\x05"));
|
||||
|
||||
// Reads are done by sending empty packets.
|
||||
mock_socket_->ExpectSend(FastbootPacket(5));
|
||||
mock_socket_->AddReceive(FastbootPacket(5, "foo bar baz"));
|
||||
mock_socket_->ExpectSend(FastbootPacket(6));
|
||||
mock_socket_->AddReceive(FastbootPacket(6, "\x01\x02\x03\x04\x05"));
|
||||
|
||||
EXPECT_TRUE(Read("foo bar baz"));
|
||||
EXPECT_TRUE(Read("\x01\x02\x03\x04\x05"));
|
||||
}
|
||||
|
||||
TEST_F(UdpTest, ResponseTimeoutFailure) {
|
||||
for (int i = 0; i < kMaxTransmissionAttempts; ++i) {
|
||||
mock_socket_->ExpectSend(FastbootPacket(1, "foo"));
|
||||
mock_socket_->AddReceiveTimeout();
|
||||
}
|
||||
|
||||
EXPECT_FALSE(Write("foo"));
|
||||
}
|
||||
|
||||
TEST_F(UdpTest, ResponseReceiveFailure) {
|
||||
mock_socket_->ExpectSend(FastbootPacket(1, "foo"));
|
||||
mock_socket_->AddReceiveFailure();
|
||||
|
||||
EXPECT_FALSE(Write("foo"));
|
||||
}
|
||||
|
||||
TEST_F(UdpTest, ResponseTimeoutRecovery) {
|
||||
for (int i = 0; i < kMaxTransmissionAttempts - 1; ++i) {
|
||||
mock_socket_->ExpectSend(FastbootPacket(1, "foo"));
|
||||
mock_socket_->AddReceiveTimeout();
|
||||
}
|
||||
mock_socket_->ExpectSend(FastbootPacket(1, "foo"));
|
||||
mock_socket_->AddReceive(FastbootPacket(1, ""));
|
||||
|
||||
EXPECT_TRUE(Write("foo"));
|
||||
}
|
||||
|
||||
// Tests continuation packets for various max packet sizes.
|
||||
// The important part of this test is that regardless of what kind of packet fragmentation happens
|
||||
// at the socket layer, a single call to Transport::Read() and Transport::Write() is all the
|
||||
// fastboot code needs to do.
|
||||
TEST_F(UdpTest, ContinuationPackets) {
|
||||
for (uint16_t max_packet_size : {512, 1024, 1200}) {
|
||||
ASSERT_TRUE(InitializeTransport(0, max_packet_size));
|
||||
|
||||
// Initialize the data we want to send. Use (size - 4) to leave room for the header.
|
||||
size_t max_data_size = max_packet_size - 4;
|
||||
std::string data(max_data_size * 3, '\0');
|
||||
for (size_t i = 0; i < data.length(); ++i) {
|
||||
data[i] = i;
|
||||
}
|
||||
std::string chunks[] = {data.substr(0, max_data_size),
|
||||
data.substr(max_data_size, max_data_size),
|
||||
data.substr(max_data_size * 2, max_data_size)};
|
||||
|
||||
// Write data: split into 3 UDP packets, each of which will be ACKed.
|
||||
mock_socket_->ExpectSend(FastbootPacket(1, chunks[0], kFlagContinuation));
|
||||
mock_socket_->AddReceive(FastbootPacket(1));
|
||||
mock_socket_->ExpectSend(FastbootPacket(2, chunks[1], kFlagContinuation));
|
||||
mock_socket_->AddReceive(FastbootPacket(2));
|
||||
mock_socket_->ExpectSend(FastbootPacket(3, chunks[2]));
|
||||
mock_socket_->AddReceive(FastbootPacket(3));
|
||||
EXPECT_TRUE(Write(data));
|
||||
|
||||
// Same thing for reading the data.
|
||||
mock_socket_->ExpectSend(FastbootPacket(4));
|
||||
mock_socket_->AddReceive(FastbootPacket(4, chunks[0], kFlagContinuation));
|
||||
mock_socket_->ExpectSend(FastbootPacket(5));
|
||||
mock_socket_->AddReceive(FastbootPacket(5, chunks[1], kFlagContinuation));
|
||||
mock_socket_->ExpectSend(FastbootPacket(6));
|
||||
mock_socket_->AddReceive(FastbootPacket(6, chunks[2]));
|
||||
EXPECT_TRUE(Read(data));
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that the continuation bit is respected even if the packet isn't max size.
|
||||
TEST_F(UdpTest, SmallContinuationPackets) {
|
||||
mock_socket_->ExpectSend(FastbootPacket(1));
|
||||
mock_socket_->AddReceive(FastbootPacket(1, "foo", kFlagContinuation));
|
||||
mock_socket_->ExpectSend(FastbootPacket(2));
|
||||
mock_socket_->AddReceive(FastbootPacket(2, "bar"));
|
||||
|
||||
EXPECT_TRUE(Read("foobar"));
|
||||
}
|
||||
|
||||
// Tests receiving an error packet mid-continuation.
|
||||
TEST_F(UdpTest, ContinuationPacketError) {
|
||||
mock_socket_->ExpectSend(FastbootPacket(1));
|
||||
mock_socket_->AddReceive(FastbootPacket(1, "foo", kFlagContinuation));
|
||||
mock_socket_->ExpectSend(FastbootPacket(2));
|
||||
mock_socket_->AddReceive(ErrorPacket(2, "test error"));
|
||||
|
||||
EXPECT_FALSE(Read("foo"));
|
||||
}
|
||||
|
||||
// Tests timeout during a continuation sequence.
|
||||
TEST_F(UdpTest, ContinuationTimeoutRecovery) {
|
||||
mock_socket_->ExpectSend(FastbootPacket(1));
|
||||
mock_socket_->AddReceive(FastbootPacket(1, "foo", kFlagContinuation));
|
||||
mock_socket_->ExpectSend(FastbootPacket(2));
|
||||
mock_socket_->AddReceiveTimeout();
|
||||
mock_socket_->ExpectSend(FastbootPacket(2));
|
||||
mock_socket_->AddReceive(FastbootPacket(2, "bar"));
|
||||
|
||||
EXPECT_TRUE(Read("foobar"));
|
||||
}
|
||||
|
||||
// Tests read overflow returns -1 to indicate the failure.
|
||||
TEST_F(UdpTest, MultipleReadPacket) {
|
||||
mock_socket_->ExpectSend(FastbootPacket(1));
|
||||
mock_socket_->AddReceive(FastbootPacket(1, "foobarbaz"));
|
||||
|
||||
char buffer[3];
|
||||
EXPECT_EQ(-1, transport_->Read(buffer, 3));
|
||||
}
|
||||
|
||||
// Tests that packets arriving out-of-order are ignored.
|
||||
TEST_F(UdpTest, IgnoreOutOfOrderPackets) {
|
||||
mock_socket_->ExpectSend(FastbootPacket(1));
|
||||
mock_socket_->AddReceive(FastbootPacket(0, "sequence too low"));
|
||||
mock_socket_->AddReceive(FastbootPacket(2, "sequence too high"));
|
||||
mock_socket_->AddReceive(QueryPacket(1));
|
||||
mock_socket_->AddReceive(FastbootPacket(1, "correct"));
|
||||
|
||||
EXPECT_TRUE(Read("correct"));
|
||||
}
|
||||
|
||||
// Tests that an error response with the correct sequence number causes immediate failure.
|
||||
TEST_F(UdpTest, ErrorResponse) {
|
||||
// Error packets with the wrong sequence number should be ignored like any other packet.
|
||||
mock_socket_->ExpectSend(FastbootPacket(1, "foo"));
|
||||
mock_socket_->AddReceive(ErrorPacket(0, "ignored error"));
|
||||
mock_socket_->AddReceive(FastbootPacket(1));
|
||||
|
||||
EXPECT_TRUE(Write("foo"));
|
||||
|
||||
// Error packets with the correct sequence should abort immediately without retransmission.
|
||||
mock_socket_->ExpectSend(FastbootPacket(2, "foo"));
|
||||
mock_socket_->AddReceive(ErrorPacket(2, "test error"));
|
||||
|
||||
EXPECT_FALSE(Write("foo"));
|
||||
}
|
||||
|
||||
// Tests that attempting to use a closed transport returns -1 without making any socket calls.
|
||||
TEST_F(UdpTest, CloseTransport) {
|
||||
char buffer[32];
|
||||
EXPECT_EQ(0, transport_->Close());
|
||||
EXPECT_EQ(-1, transport_->Write("foo", 3));
|
||||
EXPECT_EQ(-1, transport_->Read(buffer, sizeof(buffer)));
|
||||
}
|
Loading…
Reference in a new issue