blob: c7f489bb862354d9e9541284d96dfd60461b1c67 [file]
// Copyright 2026 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/developer/adb/testing/client/adb_client.h"
#include <lib/component/incoming/cpp/directory.h>
#include <lib/component/incoming/cpp/service.h>
#include <lib/device-watcher/cpp/device-watcher.h>
#include <lib/syslog/cpp/macros.h>
#include <unistd.h>
#include <zircon/status.h>
#include <zircon/types.h>
#include <filesystem>
#include <iostream>
#include <string>
#include <vector>
#include <usb/usb.h>
#include "src/developer/adb/third_party/adb/adb-protocol.h"
#include "src/developer/adb/third_party/adb/types.h"
AdbClientImpl::AdbClientImpl(async_dispatcher_t* dispatcher) : dispatcher_(dispatcher) {}
void AdbClientImpl::Setup(AdbClientImpl::SetupCompleter::Sync& completer) {
FX_LOGS(INFO) << "AdbClientImpl::Setup called.";
if (usb_connected_) {
completer.Reply(fit::ok());
return;
}
if (auto status = DiscoverAndConnect(); status != ZX_OK) {
FX_LOGS(ERROR) << "DiscoverAndConnect failed: " << zx_status_get_string(status);
completer.Reply(fit::error(status));
return;
}
completer.Reply(fit::ok());
}
void AdbClientImpl::Connect(AdbClientImpl::ConnectCompleter::Sync& completer) {
FX_LOGS(INFO) << "AdbClientImpl::Connect called.";
if (handshake_complete_) {
completer.Reply(fit::ok());
return;
}
if (!usb_connected_) {
FX_LOGS(ERROR) << "USB not connected. Call Setup() first.";
completer.Reply(fit::error(ZX_ERR_BAD_STATE));
return;
}
// Construct A_CNXN packet
apacket* p = get_apacket();
FX_LOGS(INFO) << "Sending A_CNXN packet";
p->msg.command = A_CNXN;
p->msg.arg0 = A_VERSION;
p->msg.arg1 = MAX_PAYLOAD;
const char* payload = "host::\0";
size_t len = strlen(payload) + 1;
p->msg.data_length = static_cast<uint32_t>(len);
p->payload.resize(len);
memcpy(p->payload.data(), payload, len);
p->msg.data_check = calculate_apacket_checksum(p);
p->msg.magic = p->msg.command ^ 0xffffffff;
if (auto status = SendPacket(p); status != ZX_OK) {
FX_LOGS(ERROR) << "SendPacket failed: " << zx_status_get_string(status);
put_apacket(p);
completer.Reply(fit::error(status));
return;
}
put_apacket(p);
// Save the completer and wait for the response.
connect_completer_ = completer.ToAsync();
// Queue a read request for the handshake response.
if (auto status = QueueReadRequest(); status != ZX_OK) {
FX_LOGS(ERROR) << "Failed to queue read request: " << zx_status_get_string(status);
connect_completer_->Reply(fit::error(status));
connect_completer_.reset();
}
}
void AdbClientImpl::ExecuteCommand(AdbClientImpl::ExecuteCommandRequest& request,
AdbClientImpl::ExecuteCommandCompleter::Sync& completer) {
FX_LOGS(INFO) << "AdbClientImpl::ExecuteCommand called with " << request.command();
// TODO(puneetha): Support multiple simultaneous commands.
if (execute_completer_) {
completer.Reply(fit::error(ZX_ERR_SHOULD_WAIT));
return;
}
execute_completer_ = completer.ToAsync();
command_output_.clear();
// Open a shell stream for the command.
// Use a new local ID for each command session.
local_id_++;
std::string cmd = "shell:" + std::string(request.command());
apacket* p = get_apacket();
p->msg.command = A_OPEN;
p->msg.arg0 = local_id_;
p->msg.arg1 = 0;
p->msg.data_length = static_cast<uint32_t>(cmd.length() + 1);
p->payload.resize(p->msg.data_length);
memcpy(p->payload.data(), cmd.c_str(), cmd.length());
p->payload[cmd.length()] = '\0';
p->msg.data_check = calculate_apacket_checksum(p);
p->msg.magic = p->msg.command ^ 0xffffffff;
// Ensure we are listening for the response BEFORE we send the request.
if (auto status = QueueReadRequest(); status != ZX_OK) {
FX_LOGS(ERROR) << "Failed to queue read request: " << zx_status_get_string(status);
}
if (auto status = SendPacket(p); status != ZX_OK) {
execute_completer_->Reply(fit::error(status));
execute_completer_.reset();
put_apacket(p);
return;
}
put_apacket(p);
}
void AdbClientImpl::handle_unknown_method(
fidl::UnknownMethodMetadata<fuchsia_testing_adb::Client> metadata,
fidl::UnknownMethodCompleter::Sync& completer) {
FX_LOGS(WARNING) << "Unknown method called: " << metadata.method_ordinal;
}
void AdbClientImpl::OnCompletion(
fidl::Event<fuchsia_hardware_usb_endpoint::Endpoint::OnCompletion>& event) {
for (auto& completion : event.completion()) {
zx_status_t status = completion.status().value_or(ZX_OK);
if (status != ZX_OK) {
FX_LOGS(ERROR) << "Bulk completion error: " << zx_status_get_string(status);
if (connect_completer_) {
connect_completer_->Reply(fit::error(status));
connect_completer_.reset();
}
continue;
}
if (!completion.request().has_value() || !completion.request()->data().has_value() ||
completion.request()->data()->empty()) {
continue;
}
auto& region = (*completion.request()->data())[0];
if (!region.buffer().has_value() || !region.buffer()->data().has_value()) {
continue;
}
const uint8_t* data = region.buffer()->data()->data();
size_t len = static_cast<size_t>(completion.transfer_size().value_or(0));
if (expecting_payload_bytes_ > 0) {
size_t to_append = std::min(len, expecting_payload_bytes_);
command_output_.append(reinterpret_cast<const char*>(data), to_append);
expecting_payload_bytes_ -= to_append;
if (expecting_payload_bytes_ == 0) {
apacket* ack = get_apacket();
ack->msg.command = A_OKAY;
ack->msg.arg0 = local_id_;
ack->msg.arg1 = remote_id_;
ack->msg.magic = ack->msg.command ^ 0xffffffff;
SendPacket(ack);
put_apacket(ack);
}
if (auto status = QueueReadRequest(); status != ZX_OK) {
FX_LOGS(ERROR) << "Failed to re-queue read request: " << zx_status_get_string(status);
}
continue;
}
if (len < sizeof(amessage)) {
FX_LOGS(ERROR) << "Received packet too small for ADB header: " << len;
continue;
}
amessage msg;
memcpy(&msg, data, sizeof(amessage));
FX_LOGS(INFO) << "Received ADB packet: cmd=0x" << std::hex << msg.command << " arg0=0x"
<< msg.arg0 << " arg1=0x" << msg.arg1 << " len=" << std::dec << msg.data_length;
if (msg.command == A_CNXN) {
FX_LOGS(INFO) << "Received A_CNXN response";
handshake_complete_ = true;
if (connect_completer_) {
connect_completer_->Reply(fit::ok());
connect_completer_.reset();
}
} else if (msg.command == A_AUTH) {
FX_LOGS(INFO) << "Received A_AUTH response - auth not supported yet";
if (connect_completer_) {
connect_completer_->Reply(fit::error(ZX_ERR_NOT_SUPPORTED));
connect_completer_.reset();
}
} else if (msg.command == A_OKAY) {
if (msg.arg1 == local_id_) {
if (remote_id_ == 0) {
remote_id_ = msg.arg0;
FX_LOGS(INFO) << "ADB Stream opened (local=" << local_id_ << ", remote=" << remote_id_
<< ")";
} else {
FX_LOGS(INFO) << "ADB Device acknowledged write (local=" << local_id_ << ")";
}
}
} else if (msg.command == A_WRTE) {
if (msg.arg1 == local_id_) {
size_t payload_len = msg.data_length;
if (payload_len > 0) {
size_t payload_in_packet = len - sizeof(amessage);
if (payload_in_packet > 0) {
size_t to_append = std::min(payload_in_packet, payload_len);
command_output_.append(reinterpret_cast<const char*>(data + sizeof(amessage)),
to_append);
expecting_payload_bytes_ = payload_len - to_append;
} else {
expecting_payload_bytes_ = payload_len;
}
}
if (expecting_payload_bytes_ == 0) {
apacket* ack = get_apacket();
ack->msg.command = A_OKAY;
ack->msg.arg0 = local_id_;
ack->msg.arg1 = remote_id_;
ack->msg.magic = ack->msg.command ^ 0xffffffff;
SendPacket(ack);
put_apacket(ack);
}
}
} else if (msg.command == A_CLSE) {
if (msg.arg1 == local_id_) {
FX_LOGS(INFO) << "ADB Stream closed. Output size: " << command_output_.length();
if (execute_completer_) {
execute_completer_->Reply(fit::ok(std::move(command_output_)));
execute_completer_.reset();
}
}
} else {
FX_LOGS(INFO) << "Received unexpected command: " << std::hex << msg.command;
}
// Always queue a new read request to keep the bulk pipe filled.
if (auto status = QueueReadRequest(); status != ZX_OK) {
FX_LOGS(ERROR) << "Failed to re-queue read request: " << zx_status_get_string(status);
}
}
}
void AdbClientImpl::on_fidl_error(fidl::UnbindInfo error) {
FX_LOGS(ERROR) << "Bulk endpoint FIDL error: " << error;
if (connect_completer_) {
connect_completer_->Reply(fit::error(error.status()));
connect_completer_.reset();
}
}
zx_status_t AdbClientImpl::DiscoverAndConnect() {
FX_LOGS(INFO) << "Discovering ADB devices...";
// Log all available services for debugging
for (const char* svc_name :
{fuchsia_hardware_usb_device::Service::Name, fuchsia_hardware_usb::UsbService::Name}) {
std::string path = std::string("/svc/") + svc_name;
if (std::filesystem::exists(path)) {
FX_LOGS(INFO) << "Instances in " << path << ":";
for (const auto& entry : std::filesystem::directory_iterator(path)) {
FX_LOGS(INFO) << " " << entry.path().filename().string();
}
} else {
FX_LOGS(INFO) << path << " does not exist";
}
}
// Discover devices via ServiceFS
auto base_svc_dir = component::OpenDirectory("/svc");
if (base_svc_dir.is_error()) {
FX_LOGS(ERROR) << "Failed to open /svc: " << base_svc_dir.status_string();
return base_svc_dir.status_value();
}
std::string service_path = fuchsia_hardware_usb_device::Service::Name;
FX_LOGS(INFO) << "Watching for devices in /svc/" << service_path;
auto watcher_dir = component::OpenDirectoryAt(base_svc_dir->borrow(), service_path);
if (watcher_dir.is_ok()) {
auto result = device_watcher::WatchDirectoryForItems<zx_status_t>(
watcher_dir->borrow(),
[this, &base_svc_dir](std::string_view instance) -> std::optional<zx_status_t> {
if (instance == "." || instance == "..") {
return std::nullopt;
}
FX_LOGS(INFO) << "Trying service instance: " << instance;
auto open_result = component::OpenServiceAt<fuchsia_hardware_usb_device::Service>(
base_svc_dir->borrow(), instance);
if (open_result.is_error()) {
return std::nullopt;
}
auto connect_result = open_result->connect_device();
if (connect_result.is_error()) {
return std::nullopt;
}
fidl::SyncClient device{std::move(connect_result.value())};
if (auto status = ProcessDevice(device, instance); status == ZX_OK) {
FX_LOGS(INFO) << "Found ADB device via service: " << instance;
return ZX_OK;
}
return std::nullopt;
});
if (result.is_ok()) {
return ZX_OK;
}
FX_LOGS(ERROR) << "Failed to discover ADB device via service: " << result.error_value();
}
return ZX_ERR_NOT_FOUND;
}
zx_status_t AdbClientImpl::ProcessDevice(
fidl::SyncClient<fuchsia_hardware_usb_device::Device>& device, std::string_view instance) {
auto desc_result = device->GetDeviceDescriptor();
if (desc_result.is_error()) {
FX_LOGS(ERROR) << "GetDeviceDescriptor failed: " << desc_result.error_value().status();
return desc_result.error_value().status();
}
auto config_result = device->GetConfiguration();
if (config_result.is_error()) {
FX_LOGS(ERROR) << "GetConfiguration failed: " << config_result.error_value().status();
return config_result.error_value().status();
}
FX_LOGS(INFO) << "Active config: " << static_cast<uint32_t>(config_result->configuration());
fuchsia_hardware_usb_device::DeviceGetConfigurationDescriptorRequest full_desc_req;
full_desc_req.config(config_result->configuration());
auto full_desc_result = device->GetConfigurationDescriptor(full_desc_req);
if (full_desc_result.is_error()) {
FX_LOGS(ERROR) << "GetConfigurationDescriptor failed: "
<< full_desc_result.error_value().status();
return full_desc_result.error_value().status();
}
if (full_desc_result->s() != ZX_OK) {
FX_LOGS(ERROR) << "GetConfigurationDescriptor status error: " << full_desc_result->s();
return full_desc_result->s();
}
FX_LOGS(INFO) << "Searching for ADB interface in descriptors";
return FindAdbInterface(instance, full_desc_result->desc().data(),
full_desc_result->desc().size());
}
zx_status_t AdbClientImpl::FindAdbInterface(std::string_view instance, const uint8_t* data,
size_t len) {
usb_desc_iter_t iter;
if (usb_desc_iter_init_unowned(const_cast<uint8_t*>(data), len, &iter) != ZX_OK) {
return ZX_ERR_INTERNAL;
}
usb_interface_descriptor_t* intf = nullptr;
while ((intf = usb_desc_iter_next_interface(&iter, true)) != nullptr) {
// ADB interface: Class 0xFF (Vendor Specific), Subclass 0x42, Protocol 0x01
if (intf->b_interface_class == 0xFF && intf->b_interface_sub_class == 0x42 &&
intf->b_interface_protocol == 0x01) {
FX_LOGS(INFO) << "Found ADB interface";
usb_endpoint_descriptor_t* ep = nullptr;
while ((ep = usb_desc_iter_next_endpoint(&iter)) != nullptr) {
if (usb_ep_type(ep) == USB_ENDPOINT_BULK) {
if (usb_ep_direction(ep) == USB_ENDPOINT_IN) {
bulk_in_addr_ = ep->b_endpoint_address;
} else {
bulk_out_addr_ = ep->b_endpoint_address;
}
}
}
if (bulk_in_addr_ && bulk_out_addr_) {
FX_LOGS(INFO) << "Found ADB bulk endpoints: IN 0x" << std::hex
<< static_cast<uint32_t>(bulk_in_addr_) << ", OUT 0x"
<< static_cast<uint32_t>(bulk_out_addr_);
return ConnectEndpoints(instance);
}
}
}
return ZX_ERR_NOT_FOUND;
}
zx_status_t AdbClientImpl::ConnectEndpoints(std::string_view instance) {
FX_LOGS(INFO) << "Connecting to endpoints for instance: " << instance;
auto base_svc_dir = component::OpenDirectory("/svc");
if (base_svc_dir.is_error()) {
FX_LOGS(ERROR) << "Failed to open /svc: " << base_svc_dir.status_string();
return base_svc_dir.status_value();
}
std::string svc_name = fuchsia_hardware_usb::UsbService::Name;
std::string svc_path = std::string("/svc/") + svc_name;
// Try the provided instance first
std::vector<std::string> instances_to_try;
instances_to_try.push_back(std::string(instance));
// Also collect all other instances as fallbacks
if (std::filesystem::exists(svc_path)) {
for (const auto& entry : std::filesystem::directory_iterator(svc_path)) {
std::string name = entry.path().filename().string();
if (name != instance) {
instances_to_try.push_back(name);
}
}
}
// Iterate through available UsbService instances. We expect at least two:
// 1. One from the usb-bus (parent device). This will likely fail to connect to
// endpoints because the usb-composite driver has already bound to it and
// claimed the device.
// 2. One from usb-composite (interface node). This is the one we want to connect
// to, as it provides access to the scoped ADB interface endpoints.
for (const auto& try_instance : instances_to_try) {
FX_LOGS(INFO) << "Attempting to connect to UsbService instance: " << try_instance;
auto open_result = component::OpenServiceAt<fuchsia_hardware_usb::UsbService>(
base_svc_dir->borrow(), try_instance);
if (open_result.is_error()) {
continue;
}
auto connect_result = open_result->connect_device();
if (connect_result.is_error()) {
continue;
}
fidl::SyncClient usb{std::move(connect_result.value())};
auto in_endpoints = fidl::CreateEndpoints<fuchsia_hardware_usb_endpoint::Endpoint>();
if (in_endpoints.is_error()) {
continue;
}
auto out_endpoints = fidl::CreateEndpoints<fuchsia_hardware_usb_endpoint::Endpoint>();
if (out_endpoints.is_error()) {
continue;
}
fuchsia_hardware_usb::UsbConnectToEndpointRequest in_req;
in_req.ep_addr(bulk_in_addr_);
in_req.ep(std::move(in_endpoints->server));
auto in_res = usb->ConnectToEndpoint(std::move(in_req));
if (in_res.is_error()) {
FX_LOGS(WARNING) << "ConnectToEndpoint (IN) failed for " << try_instance << ": "
<< in_res.error_value().FormatDescription();
continue;
}
fuchsia_hardware_usb::UsbConnectToEndpointRequest out_req;
out_req.ep_addr(bulk_out_addr_);
out_req.ep(std::move(out_endpoints->server));
auto out_res = usb->ConnectToEndpoint(std::move(out_req));
if (out_res.is_error()) {
FX_LOGS(WARNING) << "ConnectToEndpoint (OUT) failed for " << try_instance << ": "
<< out_res.error_value().FormatDescription();
continue;
}
bulk_in_.Bind(std::move(in_endpoints->client), dispatcher_, this);
bulk_out_.Bind(std::move(out_endpoints->client), dispatcher_);
FX_LOGS(INFO) << "Successfully bound ADB endpoints using instance: " << try_instance;
usb_connected_ = true;
return ZX_OK;
}
FX_LOGS(ERROR) << "Failed to connect to any UsbService instance for ADB endpoints";
return ZX_ERR_NOT_FOUND;
}
zx_status_t AdbClientImpl::SendPacket(apacket* p) {
if (!bulk_out_.is_valid()) {
return ZX_ERR_BAD_STATE;
}
size_t data_len = sizeof(amessage) + p->msg.data_length;
std::vector<uint8_t> data(data_len);
memcpy(data.data(), &p->msg, sizeof(amessage));
memcpy(data.data() + sizeof(amessage), p->payload.data(), p->msg.data_length);
fuchsia_hardware_usb_request::Request req;
req.data().emplace();
fuchsia_hardware_usb_request::BufferRegion region;
region.buffer(fuchsia_hardware_usb_request::Buffer::WithData(std::move(data)));
region.offset(0);
region.size(data_len);
req.data()->emplace_back(std::move(region));
req.defer_completion(false);
req.information(fuchsia_hardware_usb_request::RequestInfo::WithBulk({}));
std::vector<fuchsia_hardware_usb_request::Request> requests;
requests.emplace_back(std::move(req));
fuchsia_hardware_usb_endpoint::EndpointQueueRequestsRequest queue_request;
queue_request.req(std::move(requests));
auto result = bulk_out_->QueueRequests(std::move(queue_request));
if (result.is_error()) {
return result.error_value().status();
}
return ZX_OK;
}
zx_status_t AdbClientImpl::QueueReadRequest() {
if (!bulk_in_.is_valid()) {
FX_LOGS(ERROR) << "Bulk IN endpoint is not valid";
return ZX_ERR_BAD_STATE;
}
fuchsia_hardware_usb_request::Request req;
req.data().emplace();
fuchsia_hardware_usb_request::BufferRegion region;
std::vector<uint8_t> data(1024);
region.buffer(fuchsia_hardware_usb_request::Buffer::WithData(std::move(data)));
region.offset(0);
region.size(1024);
req.data()->emplace_back(std::move(region));
req.defer_completion(false);
req.information(fuchsia_hardware_usb_request::RequestInfo::WithBulk({}));
std::vector<fuchsia_hardware_usb_request::Request> requests;
requests.emplace_back(std::move(req));
fuchsia_hardware_usb_endpoint::EndpointQueueRequestsRequest queue_request;
queue_request.req(std::move(requests));
auto result = bulk_in_->QueueRequests(std::move(queue_request));
if (result.is_error()) {
FX_LOGS(ERROR) << "Failed to queue read request: "
<< zx_status_get_string(result.error_value().status());
return result.error_value().status();
}
return ZX_OK;
}