blob: fb006f19bb5281e644fed10c1909b1d447f8deff [file] [log] [blame]
// Copyright (c) 2013 The Chromium 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 "chrome/browser/devtools/devtools_adb_bridge.h"
#include <map>
#include <vector>
#include "base/bind.h"
#include "base/compiler_specific.h"
#include "base/json/json_reader.h"
#include "base/lazy_instance.h"
#include "base/logging.h"
#include "base/message_loop/message_loop_proxy.h"
#include "base/rand_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/threading/thread.h"
#include "base/values.h"
#include "chrome/browser/devtools/adb_client_socket.h"
#include "chrome/browser/devtools/devtools_window.h"
#include "chrome/browser/devtools/tethering_adb_filter.h"
#include "chrome/browser/profiles/profile.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/devtools_agent_host.h"
#include "content/public/browser/devtools_client_host.h"
#include "content/public/browser/devtools_external_agent_proxy.h"
#include "content/public/browser/devtools_external_agent_proxy_delegate.h"
#include "content/public/browser/devtools_manager.h"
#include "net/base/net_errors.h"
#include "net/server/web_socket.h"
using content::BrowserThread;
using net::WebSocket;
namespace {
static const char kDevToolsAdbBridgeThreadName[] = "Chrome_DevToolsADBThread";
static const char kDevToolsChannelPattern[] = "devtools_remote";
static const char kHostDevicesCommand[] = "host:devices";
static const char kDeviceModelCommand[] =
"host:transport:%s|shell:getprop ro.product.model";
static const char kUnknownModel[] = "Unknown";
static const char kOpenedUnixSocketsCommand[] =
"host:transport:%s|shell:cat /proc/net/unix";
static const char kPageListRequest[] = "GET /json HTTP/1.1\r\n\r\n";
static const char kVersionRequest[] = "GET /json/version HTTP/1.1\r\n\r\n";
static const char kWebSocketUpgradeRequest[] = "GET %s HTTP/1.1\r\n"
"Upgrade: WebSocket\r\n"
"Connection: Upgrade\r\n"
"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
"Sec-WebSocket-Version: 13\r\n"
"\r\n";
const int kAdbPort = 5037;
const int kBufferSize = 16 * 1024;
typedef DevToolsAdbBridge::Callback Callback;
typedef DevToolsAdbBridge::PagesCallback PagesCallback;
class AdbQueryCommand : public base::RefCounted<AdbQueryCommand> {
public:
AdbQueryCommand(const std::string& query,
const Callback& callback)
: query_(query),
callback_(callback) {
}
void Run() {
AdbClientSocket::AdbQuery(kAdbPort, query_,
base::Bind(&AdbQueryCommand::Handle, this));
}
private:
friend class base::RefCounted<AdbQueryCommand>;
virtual ~AdbQueryCommand() {}
void Handle(int result, const std::string& response) {
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::Bind(&AdbQueryCommand::Respond, this, result, response));
}
void Respond(int result, const std::string& response) {
callback_.Run(result, response);
}
std::string query_;
Callback callback_;
};
class AdbPagesCommand : public base::RefCounted<AdbPagesCommand> {
public:
explicit AdbPagesCommand(const PagesCallback& callback)
: callback_(callback) {
pages_.reset(new DevToolsAdbBridge::RemotePages());
#if defined(DEBUG_DEVTOOLS)
serials_.push_back(std::string()); // For desktop remote debugging.
#endif // defined(DEBUG_DEVTOOLS)
}
void Run() {
AdbClientSocket::AdbQuery(
kAdbPort, kHostDevicesCommand,
base::Bind(&AdbPagesCommand::ReceivedDevices, this));
}
private:
friend class base::RefCounted<AdbPagesCommand>;
virtual ~AdbPagesCommand() {}
void ReceivedDevices(int result, const std::string& response) {
if (result != net::OK) {
ProcessSerials();
return;
}
std::vector<std::string> devices;
Tokenize(response, "\n", &devices);
for (size_t i = 0; i < devices.size(); ++i) {
std::vector<std::string> tokens;
Tokenize(devices[i], "\t ", &tokens);
std::string serial = tokens[0];
serials_.push_back(serial);
}
ProcessSerials();
}
void ProcessSerials() {
if (serials_.size() == 0) {
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::Bind(&AdbPagesCommand::Respond, this));
return;
}
AdbClientSocket::AdbQuery(
kAdbPort,
base::StringPrintf(kDeviceModelCommand, serials_.back().c_str()),
base::Bind(&AdbPagesCommand::ReceivedModel, this));
}
void ReceivedModel(int result, const std::string& response) {
std::string model;
if (result == net::OK) {
model = response;
} else {
model = kUnknownModel;
#if defined(DEBUG_DEVTOOLS)
// For desktop remote debugging.
if (serials_.back().empty()) {
sockets_.push_back(std::string());
AdbClientSocket::HttpQuery(
kAdbPort, serials_.back(), sockets_.back(), kVersionRequest,
base::Bind(&AdbPagesCommand::ReceivedVersion, this, model));
return;
}
#endif // defined(DEBUG_DEVTOOLS)
}
AdbClientSocket::AdbQuery(
kAdbPort,
base::StringPrintf(kOpenedUnixSocketsCommand, serials_.back().c_str()),
base::Bind(&AdbPagesCommand::ReceivedSockets, this, model));
}
void ReceivedSockets(const std::string& model,
int result,
const std::string& response) {
if (result < 0) {
serials_.pop_back();
ProcessSerials();
return;
}
ParseSocketsList(response);
if (sockets_.size() == 0) {
serials_.pop_back();
ProcessSerials();
} else {
ProcessSockets(model);
}
}
void ProcessSockets(const std::string& model) {
if (sockets_.size() == 0) {
serials_.pop_back();
ProcessSerials();
} else {
AdbClientSocket::HttpQuery(
kAdbPort, serials_.back(), sockets_.back(), kVersionRequest,
base::Bind(&AdbPagesCommand::ReceivedVersion, this, model));
}
}
void ReceivedVersion(const std::string& model,
int result,
const std::string& response) {
if (result < 0) {
sockets_.pop_back();
ProcessSockets(model);
return;
}
// Parse version, append to package name if available,
std::string body = response.substr(result);
scoped_ptr<base::Value> value(base::JSONReader::Read(body));
base::DictionaryValue* dict;
if (value && value->GetAsDictionary(&dict)) {
std::string browser;
if (dict->GetString("Browser", &browser)) {
socket_to_package_[sockets_.back()] = base::StringPrintf(
"%s (%s)", socket_to_package_[sockets_.back()].c_str(),
browser.c_str());
}
}
AdbClientSocket::HttpQuery(
kAdbPort, serials_.back(), sockets_.back(), kPageListRequest,
base::Bind(&AdbPagesCommand::ReceivedPages, this, model));
}
void ReceivedPages(const std::string& model,
int result,
const std::string& response) {
std::string socket = sockets_.back();
sockets_.pop_back();
if (result < 0) {
ProcessSockets(model);
return;
}
std::string serial = serials_.back();
std::string body = response.substr(result);
scoped_ptr<base::Value> value(base::JSONReader::Read(body));
base::ListValue* list_value;
if (!value || !value->GetAsList(&list_value)) {
ProcessSockets(model);
return;
}
base::Value* item;
for (size_t i = 0; i < list_value->GetSize(); ++i) {
list_value->Get(i, &item);
base::DictionaryValue* dict;
if (!item || !item->GetAsDictionary(&dict))
continue;
pages_->push_back(
new DevToolsAdbBridge::RemotePage(
serial, model, socket_to_package_[socket], socket, *dict));
}
ProcessSockets(model);
}
void Respond() {
callback_.Run(net::OK, pages_.release());
}
void ParseSocketsList(const std::string& response) {
// On Android, '/proc/net/unix' looks like this:
//
// Num RefCount Protocol Flags Type St Inode Path
// 00000000: 00000002 00000000 00010000 0001 01 331813 /dev/socket/zygote
// 00000000: 00000002 00000000 00010000 0001 01 358606 @xxx_devtools_remote
// 00000000: 00000002 00000000 00010000 0001 01 347300 @yyy_devtools_remote
//
// We need to find records with paths starting from '@' (abstract socket)
// and containing "devtools_remote". We have to extract the inode number
// in order to find the owning process name.
socket_to_package_.clear();
std::vector<std::string> entries;
Tokenize(response, "\n", &entries);
const std::string channel_pattern = kDevToolsChannelPattern;
for (size_t i = 1; i < entries.size(); ++i) {
std::vector<std::string> fields;
Tokenize(entries[i], " ", &fields);
if (fields.size() < 8)
continue;
if (fields[3] != "00010000" || fields[5] != "01")
continue;
std::string path_field = fields[7];
if (path_field.size() < 1 || path_field[0] != '@')
continue;
size_t socket_name_pos = path_field.find(channel_pattern);
if (socket_name_pos == std::string::npos)
continue;
std::string socket = path_field.substr(1, path_field.size() - 2);
sockets_.push_back(socket);
std::string package = path_field.substr(1, socket_name_pos - 2);
if (socket_name_pos + channel_pattern.size() < path_field.size() - 1) {
package += path_field.substr(
socket_name_pos + channel_pattern.size(), path_field.size() - 1);
}
package[0] = base::ToUpperASCII(package[0]);
socket_to_package_[socket] = package;
}
}
PagesCallback callback_;
std::vector<std::string> serials_;
std::vector<std::string> sockets_;
std::map<std::string, std::string> socket_to_package_;
scoped_ptr<DevToolsAdbBridge::RemotePages> pages_;
};
} // namespace
class AgentHostDelegate;
typedef std::map<std::string, AgentHostDelegate*> AgentHostDelegates;
base::LazyInstance<AgentHostDelegates>::Leaky g_host_delegates =
LAZY_INSTANCE_INITIALIZER;
class AgentHostDelegate : public base::RefCountedThreadSafe<AgentHostDelegate>,
public content::DevToolsExternalAgentProxyDelegate {
public:
AgentHostDelegate(
const std::string& id,
const std::string& serial,
scoped_refptr<DevToolsAdbBridge::RefCountedAdbThread> adb_thread,
net::StreamSocket* socket)
: id_(id),
serial_(serial),
adb_thread_(adb_thread),
socket_(socket),
tethering_adb_filter_(kAdbPort, serial) {
AddRef(); // Balanced in SelfDestruct.
proxy_.reset(content::DevToolsExternalAgentProxy::Create(this));
g_host_delegates.Get()[id] = this;
}
scoped_refptr<content::DevToolsAgentHost> GetAgentHost() {
return proxy_->GetAgentHost();
}
private:
friend class base::RefCountedThreadSafe<AgentHostDelegate>;
virtual ~AgentHostDelegate() {
g_host_delegates.Get().erase(id_);
}
virtual void Attach() OVERRIDE {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
adb_thread_->message_loop()->PostTask(
FROM_HERE,
base::Bind(&AgentHostDelegate::StartListeningOnHandlerThread, this));
}
virtual void Detach() OVERRIDE {
adb_thread_->message_loop()->PostTask(
FROM_HERE,
base::Bind(&AgentHostDelegate::CloseConnection, this, net::OK, false));
}
virtual void SendMessageToBackend(const std::string& message) OVERRIDE {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
adb_thread_->message_loop()->PostTask(
FROM_HERE,
base::Bind(&AgentHostDelegate::SendFrameOnHandlerThread, this,
message));
}
void StartListeningOnHandlerThread() {
scoped_refptr<net::IOBuffer> response_buffer =
new net::IOBuffer(kBufferSize);
int result = socket_->Read(
response_buffer.get(),
kBufferSize,
base::Bind(&AgentHostDelegate::OnBytesRead, this, response_buffer));
if (result != net::ERR_IO_PENDING)
OnBytesRead(response_buffer, result);
}
void OnBytesRead(scoped_refptr<net::IOBuffer> response_buffer, int result) {
if (!socket_)
return;
if (result <= 0) {
CloseIfNecessary(net::ERR_CONNECTION_CLOSED);
return;
}
std::string data = std::string(response_buffer->data(), result);
response_buffer_ += data;
int bytes_consumed;
std::string output;
WebSocket::ParseResult parse_result = WebSocket::DecodeFrameHybi17(
response_buffer_, false, &bytes_consumed, &output);
while (parse_result == WebSocket::FRAME_OK) {
response_buffer_ = response_buffer_.substr(bytes_consumed);
if (!tethering_adb_filter_.ProcessIncomingMessage(output)) {
BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
base::Bind(&AgentHostDelegate::OnFrameRead, this, output));
}
parse_result = WebSocket::DecodeFrameHybi17(
response_buffer_, false, &bytes_consumed, &output);
}
if (parse_result == WebSocket::FRAME_ERROR ||
parse_result == WebSocket::FRAME_CLOSE) {
CloseIfNecessary(net::ERR_CONNECTION_CLOSED);
return;
}
result = socket_->Read(
response_buffer.get(),
kBufferSize,
base::Bind(&AgentHostDelegate::OnBytesRead, this, response_buffer));
if (result != net::ERR_IO_PENDING)
OnBytesRead(response_buffer, result);
}
void SendFrameOnHandlerThread(const std::string& data) {
tethering_adb_filter_.ProcessOutgoingMessage(data);
int mask = base::RandInt(0, 0x7FFFFFFF);
std::string encoded_frame = WebSocket::EncodeFrameHybi17(data, mask);
scoped_refptr<net::StringIOBuffer> request_buffer =
new net::StringIOBuffer(encoded_frame);
if (!socket_)
return;
int result =
socket_->Write(request_buffer.get(),
request_buffer->size(),
base::Bind(&AgentHostDelegate::CloseIfNecessary, this));
if (result != net::ERR_IO_PENDING)
CloseIfNecessary(result);
}
void CloseConnection(int result, bool initiated_by_me) {
if (!socket_)
return;
socket_->Disconnect();
socket_.reset();
if (initiated_by_me) {
BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
base::Bind(&AgentHostDelegate::OnSocketClosed, this, result));
}
BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
base::Bind(&AgentHostDelegate::SelfDestruct, this));
}
void SelfDestruct() {
Release(); // Balanced in constructor.
}
void CloseIfNecessary(int result) {
if (result >= 0)
return;
CloseConnection(result, true);
}
void OnFrameRead(const std::string& message) {
proxy_->DispatchOnClientHost(message);
}
void OnSocketClosed(int result) {
proxy_->ConnectionClosed();
}
std::string id_;
std::string serial_;
scoped_refptr<DevToolsAdbBridge::RefCountedAdbThread> adb_thread_;
scoped_ptr<net::StreamSocket> socket_;
scoped_ptr<content::DevToolsExternalAgentProxy> proxy_;
std::string response_buffer_;
TetheringAdbFilter tethering_adb_filter_;
DISALLOW_COPY_AND_ASSIGN(AgentHostDelegate);
};
class AdbAttachCommand : public base::RefCounted<AdbAttachCommand> {
public:
AdbAttachCommand(const base::WeakPtr<DevToolsAdbBridge>& bridge,
const std::string& serial,
const std::string& socket,
const std::string& debug_url,
const std::string& frontend_url)
: bridge_(bridge),
serial_(serial),
socket_(socket),
debug_url_(debug_url),
frontend_url_(frontend_url) {
}
void Run() {
AdbClientSocket::HttpQuery(
kAdbPort, serial_, socket_,
base::StringPrintf(kWebSocketUpgradeRequest, debug_url_.c_str()),
base::Bind(&AdbAttachCommand::Handle, this));
}
private:
friend class base::RefCounted<AdbAttachCommand>;
virtual ~AdbAttachCommand() {}
void Handle(int result, net::StreamSocket* socket) {
if (result != net::OK || socket == NULL)
return;
BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
base::Bind(&AdbAttachCommand::OpenDevToolsWindow, this, socket));
}
void OpenDevToolsWindow(net::StreamSocket* socket) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
DevToolsAdbBridge* bridge = bridge_.get();
if (!bridge)
return;
std::string id = base::StringPrintf("%s:%s", serial_.c_str(),
debug_url_.c_str());
AgentHostDelegates::iterator it = g_host_delegates.Get().find(id);
AgentHostDelegate* delegate;
if (it != g_host_delegates.Get().end())
delegate = it->second;
else
delegate = new AgentHostDelegate(id, serial_, bridge->adb_thread_,
socket);
DevToolsWindow::OpenExternalFrontend(
bridge->profile_, frontend_url_, delegate->GetAgentHost().get());
}
base::WeakPtr<DevToolsAdbBridge> bridge_;
std::string serial_;
std::string socket_;
std::string debug_url_;
std::string frontend_url_;
};
DevToolsAdbBridge::RemotePage::RemotePage(const std::string& serial,
const std::string& model,
const std::string& package,
const std::string& socket,
const base::DictionaryValue& value)
: serial_(serial),
model_(model),
package_(package),
socket_(socket) {
value.GetString("id", &id_);
value.GetString("url", &url_);
value.GetString("title", &title_);
value.GetString("descirption", &description_);
value.GetString("faviconUrl", &favicon_url_);
value.GetString("webSocketDebuggerUrl", &debug_url_);
value.GetString("devtoolsFrontendUrl", &frontend_url_);
if (debug_url_.find("ws://") == 0)
debug_url_ = debug_url_.substr(5);
else
debug_url_ = "";
size_t ws_param = frontend_url_.find("?ws");
if (ws_param != std::string::npos)
frontend_url_ = frontend_url_.substr(0, ws_param);
if (frontend_url_.find("http:") == 0)
frontend_url_ = "https:" + frontend_url_.substr(5);
}
DevToolsAdbBridge::RemotePage::~RemotePage() {
}
DevToolsAdbBridge::RefCountedAdbThread*
DevToolsAdbBridge::RefCountedAdbThread::instance_ = NULL;
// static
scoped_refptr<DevToolsAdbBridge::RefCountedAdbThread>
DevToolsAdbBridge::RefCountedAdbThread::GetInstance() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
if (!instance_)
new RefCountedAdbThread();
return instance_;
}
DevToolsAdbBridge::RefCountedAdbThread::RefCountedAdbThread() {
instance_ = this;
thread_ = new base::Thread(kDevToolsAdbBridgeThreadName);
base::Thread::Options options;
options.message_loop_type = base::MessageLoop::TYPE_IO;
if (!thread_->StartWithOptions(options)) {
delete thread_;
thread_ = NULL;
}
}
base::MessageLoop* DevToolsAdbBridge::RefCountedAdbThread::message_loop() {
return thread_ ? thread_->message_loop() : NULL;
}
// static
void DevToolsAdbBridge::RefCountedAdbThread::StopThread(base::Thread* thread) {
thread->Stop();
}
DevToolsAdbBridge::RefCountedAdbThread::~RefCountedAdbThread() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
instance_ = NULL;
if (!thread_)
return;
// Shut down thread on FILE thread to join into IO.
BrowserThread::PostTask(
BrowserThread::FILE, FROM_HERE,
base::Bind(&RefCountedAdbThread::StopThread, thread_));
}
DevToolsAdbBridge::DevToolsAdbBridge(Profile* profile)
: profile_(profile),
adb_thread_(RefCountedAdbThread::GetInstance()),
weak_factory_(this),
has_message_loop_(adb_thread_->message_loop() != NULL) {
}
DevToolsAdbBridge::~DevToolsAdbBridge() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
}
void DevToolsAdbBridge::Query(
const std::string query,
const Callback& callback) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
if (!has_message_loop_) {
callback.Run(net::ERR_FAILED, "Could not start ADB thread");
return;
}
scoped_refptr<AdbQueryCommand> command(new AdbQueryCommand(query, callback));
adb_thread_->message_loop()->PostTask(FROM_HERE,
base::Bind(&AdbQueryCommand::Run, command));
}
void DevToolsAdbBridge::Pages(const PagesCallback& callback) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
if (!has_message_loop_)
return;
scoped_refptr<AdbPagesCommand> command(new AdbPagesCommand(callback));
adb_thread_->message_loop()->PostTask(FROM_HERE,
base::Bind(&AdbPagesCommand::Run, command));
}
void DevToolsAdbBridge::Attach(const std::string& serial,
const std::string& socket,
const std::string& debug_url,
const std::string& frontend_url) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
if (!has_message_loop_)
return;
scoped_refptr<AdbAttachCommand> command(
new AdbAttachCommand(weak_factory_.GetWeakPtr(), serial, socket,
debug_url, frontend_url));
adb_thread_->message_loop()->PostTask(
FROM_HERE,
base::Bind(&AdbAttachCommand::Run, command));
}