blob: 1dff33f004580cd80d94781300653aa8332d2882 [file] [log] [blame]
// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/devtools/device/usb/android_usb_device.h"
#include <algorithm>
#include <set>
#include <utility>
#include <vector>
#include "base/barrier_closure.h"
#include "base/base64.h"
#include "base/compiler_specific.h"
#include "base/containers/contains.h"
#include "base/functional/bind.h"
#include "base/memory/ref_counted_memory.h"
#include "base/no_destructor.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/task/single_thread_task_runner.h"
#include "chrome/browser/devtools/device/usb/android_rsa.h"
#include "chrome/browser/devtools/device/usb/android_usb_socket.h"
#include "net/base/ip_endpoint.h"
#include "net/base/net_errors.h"
#include "net/socket/stream_socket.h"
using device::mojom::UsbTransferStatus;
namespace {
const size_t kHeaderSize = 24;
const int kUsbTimeout = 0;
const uint32_t kMaxPayload = 4096;
const uint32_t kVersion = 0x01000000;
static const char kHostConnectMessage[] = "host::";
// Stores android wrappers around claimed usb devices on caller thread.
std::vector<AndroidUsbDevice*>& GetDevices() {
static base::NoDestructor<std::vector<AndroidUsbDevice*>> devices;
return *devices;
}
// Stores the GUIDs of devices that are currently opened so that they are not
// re-probed.
std::vector<std::string>& GetOpenDevices() {
static base::NoDestructor<std::vector<std::string>> open_devices;
return *open_devices;
}
uint32_t Checksum(const std::string& data) {
unsigned char* x = (unsigned char*)data.data();
int count = data.length();
uint32_t sum = 0;
while (count-- > 0)
sum += *UNSAFE_TODO(x++);
return sum;
}
void DumpMessage(bool outgoing, const uint8_t* data, size_t length) {
#if 0
std::string result;
if (length == kHeaderSize) {
for (size_t i = 0; i < 24; ++i) {
result += base::StringPrintf("%02x", data[i]);
if ((i + 1) % 4 == 0)
result += " ";
}
for (size_t i = 0; i < 24; ++i) {
if (data[i] >= 0x20 && data[i] <= 0x7E)
result += data[i];
else
result += ".";
}
} else {
result = base::StringPrintf("%d: ", static_cast<int>(length));
for (size_t i = 0; i < length; ++i) {
if (data[i] >= 0x20 && data[i] <= 0x7E)
result += data[i];
else
result += ".";
}
}
LOG(ERROR) << (outgoing ? "[out] " : "[ in] ") << result;
#endif // 0
}
void OnProbeFinished(AndroidUsbDevicesCallback callback,
AndroidUsbDevices* new_devices) {
std::unique_ptr<AndroidUsbDevices> devices(new_devices);
// Add raw pointers to the newly claimed devices.
for (const scoped_refptr<AndroidUsbDevice>& device : *devices) {
GetDevices().push_back(device.get());
}
// Return all claimed devices.
AndroidUsbDevices result(GetDevices().begin(), GetDevices().end());
std::move(callback).Run(result);
}
void OnDeviceClosed(const std::string& guid,
mojo::Remote<device::mojom::UsbDevice> device) {
std::erase(GetOpenDevices(), guid);
}
void OnDeviceClosedWithBarrier(const std::string& guid,
mojo::Remote<device::mojom::UsbDevice> device,
const base::RepeatingClosure& barrier) {
std::erase(GetOpenDevices(), guid);
barrier.Run();
}
void CreateDeviceOnInterfaceClaimed(
AndroidUsbDevices* devices,
crypto::keypair::PrivateKey rsa_key,
AndroidDeviceInfo android_device_info,
mojo::Remote<device::mojom::UsbDevice> device,
const base::RepeatingClosure& barrier,
device::mojom::UsbClaimInterfaceResult result) {
if (result == device::mojom::UsbClaimInterfaceResult::kSuccess) {
devices->push_back(
new AndroidUsbDevice(rsa_key, android_device_info, std::move(device)));
barrier.Run();
} else {
auto* device_raw = device.get();
device_raw->Close(base::BindOnce(&OnDeviceClosedWithBarrier,
android_device_info.guid,
std::move(device), barrier));
}
}
void OnInterfaceReleased(mojo::Remote<device::mojom::UsbDevice> device,
const std::string& guid,
bool release_successful) {
auto* device_raw = device.get();
device_raw->Close(base::BindOnce(&OnDeviceClosed, guid, std::move(device)));
}
void OnDeviceOpened(AndroidUsbDevices* devices,
crypto::keypair::PrivateKey rsa_key,
AndroidDeviceInfo android_device_info,
mojo::Remote<device::mojom::UsbDevice> device,
const base::RepeatingClosure& barrier,
device::mojom::UsbOpenDeviceResultPtr result) {
// If the error is UsbOpenDeviceError::ALREADY_OPEN we all try to claim the
// interface because the device may be opened by other modules or extensions
// for different interface.
if (result->is_success() ||
result->get_error() == device::mojom::UsbOpenDeviceError::ALREADY_OPEN) {
DCHECK(device);
auto* device_raw = device.get();
device_raw->ClaimInterface(
android_device_info.interface_id,
base::BindOnce(&CreateDeviceOnInterfaceClaimed, devices, rsa_key,
android_device_info, std::move(device), barrier));
} else {
std::erase(GetOpenDevices(), android_device_info.guid);
barrier.Run();
}
}
void OpenAndroidDevices(crypto::keypair::PrivateKey rsa_key,
AndroidUsbDevicesCallback callback,
std::vector<AndroidDeviceInfo> device_info_list) {
// Add new devices.
AndroidUsbDevices* devices = new AndroidUsbDevices();
base::RepeatingClosure barrier = base::BarrierClosure(
device_info_list.size(),
base::BindOnce(&OnProbeFinished, std::move(callback), devices));
for (const auto& device_info : device_info_list) {
if (base::Contains(GetOpenDevices(), device_info.guid)) {
// This device is already open, do not make parallel attempts to connect
// to it.
barrier.Run();
continue;
}
GetOpenDevices().push_back(device_info.guid);
mojo::Remote<device::mojom::UsbDevice> device;
UsbDeviceManagerHelper::GetInstance()->GetDevice(
device_info.guid, device.BindNewPipeAndPassReceiver());
auto* device_raw = device.get();
device_raw->Open(base::BindOnce(&OnDeviceOpened, devices, rsa_key,
device_info, std::move(device), barrier));
}
}
} // namespace
AdbMessage::AdbMessage(uint32_t command,
uint32_t arg0,
uint32_t arg1,
const std::string& body)
: command(command), arg0(arg0), arg1(arg1), body(body) {}
AdbMessage::~AdbMessage() = default;
// static
void AndroidUsbDevice::Enumerate(crypto::keypair::PrivateKey rsa_key,
AndroidUsbDevicesCallback callback) {
UsbDeviceManagerHelper::GetInstance()->GetAndroidDevices(
base::BindOnce(&OpenAndroidDevices, rsa_key, std::move(callback)));
}
AndroidUsbDevice::AndroidUsbDevice(
crypto::keypair::PrivateKey rsa_key,
const AndroidDeviceInfo& android_device_info,
mojo::Remote<device::mojom::UsbDevice> device)
: rsa_key_(rsa_key),
device_(std::move(device)),
android_device_info_(android_device_info),
is_connected_(false),
signature_sent_(false),
last_socket_id_(256) {
DCHECK(device_);
device_.set_disconnect_handler(
base::BindOnce(&AndroidUsbDevice::Terminate, weak_factory_.GetWeakPtr()));
}
void AndroidUsbDevice::InitOnCallerThread() {
if (task_runner_)
return;
task_runner_ = base::SingleThreadTaskRunner::GetCurrentDefault();
Queue(std::make_unique<AdbMessage>(AdbMessage::kCommandCNXN, kVersion,
kMaxPayload, kHostConnectMessage));
ReadHeader();
}
net::StreamSocket* AndroidUsbDevice::CreateSocket(const std::string& command) {
if (!device_)
return nullptr;
uint32_t socket_id = ++last_socket_id_;
sockets_[socket_id] = new AndroidUsbSocket(
this, socket_id, command,
base::BindOnce(&AndroidUsbDevice::SocketDeleted, this, socket_id));
return sockets_[socket_id];
}
void AndroidUsbDevice::Send(uint32_t command,
uint32_t arg0,
uint32_t arg1,
const std::string& body) {
auto message = std::make_unique<AdbMessage>(command, arg0, arg1, body);
// Delay open request if not yet connected.
if (!is_connected_) {
pending_messages_.push_back(std::move(message));
return;
}
Queue(std::move(message));
}
AndroidUsbDevice::~AndroidUsbDevice() {
DCHECK(task_runner_->BelongsToCurrentThread());
Terminate();
}
void AndroidUsbDevice::Queue(std::unique_ptr<AdbMessage> message) {
DCHECK(task_runner_->BelongsToCurrentThread());
// Queue header.
std::vector<uint32_t> header;
header.push_back(message->command);
header.push_back(message->arg0);
header.push_back(message->arg1);
bool append_zero = true;
if (message->body.empty())
append_zero = false;
if (message->command == AdbMessage::kCommandAUTH &&
message->arg0 == AdbMessage::kAuthSignature)
append_zero = false;
if (message->command == AdbMessage::kCommandWRTE)
append_zero = false;
size_t body_length = message->body.length() + (append_zero ? 1 : 0);
header.push_back(body_length);
header.push_back(Checksum(message->body));
header.push_back(message->command ^ 0xffffffff);
DCHECK_EQ(kHeaderSize, base::as_byte_span(header).size());
// TODO(donna.wu@intel.com): eliminate the buffer copy here, needs to change
// type BulkMessage.
auto header_buffer =
base::MakeRefCounted<base::RefCountedBytes>(base::as_byte_span(header));
outgoing_queue_.push(header_buffer);
// Queue body.
if (!message->body.empty()) {
auto body_buffer = base::MakeRefCounted<base::RefCountedBytes>(body_length);
{
auto& v = body_buffer->as_vector();
UNSAFE_TODO(
memcpy(v.data(), message->body.data(), message->body.length()));
if (append_zero) {
v[body_length - 1] = 0;
}
}
outgoing_queue_.push(body_buffer);
if (android_device_info_.zero_mask &&
(body_length & android_device_info_.zero_mask) == 0) {
// Send a zero length packet.
outgoing_queue_.push(base::MakeRefCounted<base::RefCountedBytes>(0));
}
}
ProcessOutgoing();
}
void AndroidUsbDevice::ProcessOutgoing() {
DCHECK(task_runner_->BelongsToCurrentThread());
if (outgoing_queue_.empty() || !device_)
return;
BulkMessage message = outgoing_queue_.front();
outgoing_queue_.pop();
DumpMessage(true, message->data(), message->size());
device_->GenericTransferOut(
android_device_info_.outbound_address, message->as_vector(), kUsbTimeout,
base::BindOnce(&AndroidUsbDevice::OutgoingMessageSent,
weak_factory_.GetWeakPtr()));
}
void AndroidUsbDevice::OutgoingMessageSent(UsbTransferStatus status) {
if (status != UsbTransferStatus::COMPLETED)
return;
task_runner_->PostTask(
FROM_HERE, base::BindOnce(&AndroidUsbDevice::ProcessOutgoing, this));
}
void AndroidUsbDevice::ReadHeader() {
DCHECK(task_runner_->BelongsToCurrentThread());
if (!device_)
return;
device_->GenericTransferIn(android_device_info_.inbound_address, kHeaderSize,
kUsbTimeout,
base::BindOnce(&AndroidUsbDevice::ParseHeader,
weak_factory_.GetWeakPtr()));
}
void AndroidUsbDevice::ParseHeader(UsbTransferStatus status,
base::span<const uint8_t> buffer) {
DCHECK(task_runner_->BelongsToCurrentThread());
if (status == UsbTransferStatus::TIMEOUT) {
task_runner_->PostTask(FROM_HERE,
base::BindOnce(&AndroidUsbDevice::ReadHeader, this));
return;
}
if (status != UsbTransferStatus::COMPLETED || buffer.size() != kHeaderSize) {
TransferError(status);
return;
}
DumpMessage(false, buffer.data(), buffer.size());
const auto* header = reinterpret_cast<const uint32_t*>(buffer.data());
std::unique_ptr<AdbMessage> message(new AdbMessage(
header[0], UNSAFE_TODO(header[1]), UNSAFE_TODO(header[2]), ""));
uint32_t data_length = UNSAFE_TODO(header[3]);
uint32_t data_check = UNSAFE_TODO(header[4]);
uint32_t magic = UNSAFE_TODO(header[5]);
if ((message->command ^ 0xffffffff) != magic) {
TransferError(UsbTransferStatus::TRANSFER_ERROR);
return;
}
if (data_length == 0) {
task_runner_->PostTask(
FROM_HERE, base::BindOnce(&AndroidUsbDevice::HandleIncoming, this,
std::move(message)));
} else {
task_runner_->PostTask(
FROM_HERE, base::BindOnce(&AndroidUsbDevice::ReadBody, this,
std::move(message), data_length, data_check));
}
}
void AndroidUsbDevice::ReadBody(std::unique_ptr<AdbMessage> message,
uint32_t data_length,
uint32_t data_check) {
DCHECK(task_runner_->BelongsToCurrentThread());
if (!device_.get()) {
return;
}
device_->GenericTransferIn(
android_device_info_.inbound_address, data_length, kUsbTimeout,
base::BindOnce(&AndroidUsbDevice::ParseBody, weak_factory_.GetWeakPtr(),
std::move(message), data_length, data_check));
}
void AndroidUsbDevice::ParseBody(std::unique_ptr<AdbMessage> message,
uint32_t data_length,
uint32_t data_check,
UsbTransferStatus status,
base::span<const uint8_t> buffer) {
DCHECK(task_runner_->BelongsToCurrentThread());
if (status == UsbTransferStatus::TIMEOUT) {
task_runner_->PostTask(
FROM_HERE, base::BindOnce(&AndroidUsbDevice::ReadBody, this,
std::move(message), data_length, data_check));
return;
}
if (status != UsbTransferStatus::COMPLETED ||
static_cast<uint32_t>(buffer.size()) != data_length) {
TransferError(status);
return;
}
DumpMessage(false, buffer.data(), data_length);
message->body =
std::string(reinterpret_cast<const char*>(buffer.data()), buffer.size());
if (Checksum(message->body) != data_check) {
TransferError(UsbTransferStatus::TRANSFER_ERROR);
return;
}
task_runner_->PostTask(FROM_HERE,
base::BindOnce(&AndroidUsbDevice::HandleIncoming, this,
std::move(message)));
}
void AndroidUsbDevice::HandleIncoming(std::unique_ptr<AdbMessage> message) {
DCHECK(task_runner_->BelongsToCurrentThread());
switch (message->command) {
case AdbMessage::kCommandAUTH: {
if (message->arg0 != static_cast<uint32_t>(AdbMessage::kAuthToken)) {
TransferError(UsbTransferStatus::TRANSFER_ERROR);
return;
}
if (signature_sent_) {
std::optional<std::string> pub = AndroidRSAPublicKey(rsa_key_);
if (!pub) {
TransferError(UsbTransferStatus::TRANSFER_ERROR);
return;
}
Queue(std::make_unique<AdbMessage>(
AdbMessage::kCommandAUTH, AdbMessage::kAuthRSAPublicKey, 0, *pub));
} else {
signature_sent_ = true;
std::string signature = AndroidRSASign(rsa_key_, message->body);
if (signature.empty()) {
// This may fail if the device requests to sign a token that is not
// the same size as a SHA-1 hash. ADB does not use a standard
// signature scheme and instead treats an arbitrary peer-supplied
// token as the SHA-1 hash.
TransferError(UsbTransferStatus::TRANSFER_ERROR);
return;
}
Queue(std::make_unique<AdbMessage>(AdbMessage::kCommandAUTH,
AdbMessage::kAuthSignature, 0,
signature));
}
} break;
case AdbMessage::kCommandCNXN:
{
is_connected_ = true;
PendingMessages pending;
pending.swap(pending_messages_);
for (auto& msg : pending)
Queue(std::move(msg));
}
break;
case AdbMessage::kCommandOKAY:
case AdbMessage::kCommandWRTE:
case AdbMessage::kCommandCLSE:
{
auto it = sockets_.find(message->arg1);
if (it != sockets_.end())
it->second->HandleIncoming(std::move(message));
}
break;
default:
break;
}
ReadHeader();
}
void AndroidUsbDevice::TransferError(UsbTransferStatus status) {
DCHECK(task_runner_->BelongsToCurrentThread());
Terminate();
}
void AndroidUsbDevice::Terminate() {
DCHECK(task_runner_->BelongsToCurrentThread());
// Remove this AndroidUsbDevice from GetDevices().
auto it = std::ranges::find(GetDevices(), this);
if (it != GetDevices().end()) {
GetDevices().erase(it);
}
// For connection error, remove the guid from recored opening/opened list.
// For transfer errors, we'll do this after releasing the interface.
if (!device_) {
std::erase(GetOpenDevices(), android_device_info_.guid);
return;
}
// For Transfer error case.
// Make sure we zero-out |device_| so that closing connections did not
// open new socket connections.
mojo::Remote<device::mojom::UsbDevice> device = std::move(device_);
device_.reset();
// Iterate over copy.
AndroidUsbSockets sockets(sockets_);
for (auto socket_it = sockets.begin(); socket_it != sockets.end();
++socket_it) {
socket_it->second->Terminated(true);
}
DCHECK(sockets_.empty());
auto* device_raw = device.get();
device_raw->ReleaseInterface(
android_device_info_.interface_id,
base::BindOnce(&OnInterfaceReleased, std::move(device),
android_device_info_.guid));
}
void AndroidUsbDevice::SocketDeleted(uint32_t socket_id) {
DCHECK(task_runner_->BelongsToCurrentThread());
sockets_.erase(socket_id);
}