/content/browser/devtools/devtools_pipe_handler.cc
C++ | 433 lines | 360 code | 57 blank | 16 comment | 39 complexity | 3e0d8d1aa127a5e07c4714a23312c7e5 MD5 | raw file
Possible License(s): MPL-2.0-no-copyleft-exception, Apache-2.0, BSD-3-Clause
- // Copyright (c) 2017 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 "content/browser/devtools/devtools_pipe_handler.h"
- #include "base/task/thread_pool.h"
- #include "build/build_config.h"
- #if BUILDFLAG(IS_WIN)
- #include <io.h>
- #include <windows.h>
- #else
- #include <sys/socket.h>
- #endif
- #include <stdio.h>
- #include <cstdlib>
- #include <memory>
- #include <string>
- #include <utility>
- #include "base/bind.h"
- #include "base/command_line.h"
- #include "base/files/file_util.h"
- #include "base/logging.h"
- #include "base/memory/ref_counted_memory.h"
- #include "base/message_loop/message_pump_type.h"
- #include "base/strings/string_util.h"
- #include "base/synchronization/atomic_flag.h"
- #include "base/task/sequenced_task_runner.h"
- #include "base/task/single_thread_task_runner.h"
- #include "base/threading/thread.h"
- #include "build/build_config.h"
- #include "content/public/browser/browser_task_traits.h"
- #include "content/public/browser/browser_thread.h"
- #include "content/public/browser/devtools_agent_host.h"
- #include "content/public/common/content_switches.h"
- #include "net/server/http_connection.h"
- #include "third_party/inspector_protocol/crdtp/cbor.h"
- const size_t kReceiveBufferSizeForDevTools = 100 * 1024 * 1024; // 100Mb
- const size_t kWritePacketSize = 1 << 16;
- const int kReadFD = 3;
- const int kWriteFD = 4;
- // Our CBOR (RFC 7049) based format starts with a tag 24 indicating
- // an envelope, that is, a byte string which as payload carries the
- // entire remaining message. Thereby, the length of the byte string
- // also tells us the message size on the wire.
- // The details of the encoding are implemented in
- // third_party/inspector_protocol/crdtp/cbor.h.
- namespace content {
- namespace {
- class PipeIOBase {
- public:
- explicit PipeIOBase(const char* thread_name)
- : thread_(new base::Thread(thread_name)) {}
- virtual ~PipeIOBase() = default;
- bool Start() {
- base::Thread::Options options;
- options.message_pump_type = base::MessagePumpType::IO;
- if (!thread_->StartWithOptions(std::move(options)))
- return false;
- StartMainLoop();
- return true;
- }
- static void Shutdown(std::unique_ptr<PipeIOBase> pipe_io) {
- if (!pipe_io)
- return;
- auto thread = std::move(pipe_io->thread_);
- pipe_io->shutting_down_.Set();
- pipe_io->ClosePipe();
- // Post self destruction on the custom thread if it's running.
- if (thread->task_runner()) {
- thread->task_runner()->DeleteSoon(FROM_HERE, std::move(pipe_io));
- } else {
- pipe_io.reset();
- }
- // Post background task that would join and destroy the thread.
- base::ThreadPool::CreateSequencedTaskRunner(
- {base::MayBlock(), base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN,
- base::WithBaseSyncPrimitives(), base::TaskPriority::BEST_EFFORT})
- ->DeleteSoon(FROM_HERE, std::move(thread));
- }
- protected:
- virtual void StartMainLoop() {}
- virtual void ClosePipe() = 0;
- std::unique_ptr<base::Thread> thread_;
- base::AtomicFlag shutting_down_;
- };
- } // namespace
- class PipeReaderBase : public PipeIOBase {
- public:
- PipeReaderBase(base::WeakPtr<DevToolsPipeHandler> devtools_handler,
- int read_fd)
- : PipeIOBase("DevToolsPipeHandlerReadThread"),
- devtools_handler_(std::move(devtools_handler)) {
- #if BUILDFLAG(IS_WIN)
- read_handle_ = reinterpret_cast<HANDLE>(_get_osfhandle(read_fd));
- #else
- read_fd_ = read_fd;
- #endif
- }
- protected:
- void StartMainLoop() override {
- thread_->task_runner()->PostTask(
- FROM_HERE,
- base::BindOnce(&PipeReaderBase::ReadLoop, base::Unretained(this)));
- }
- void ClosePipe() override {
- // Concurrently discard the pipe handles to successfully join threads.
- #if BUILDFLAG(IS_WIN)
- // Cancel pending synchronous read.
- CancelIoEx(read_handle_, nullptr);
- CloseHandle(read_handle_);
- #else
- shutdown(read_fd_, SHUT_RDWR);
- #endif
- }
- virtual void ReadLoopInternal() = 0;
- size_t ReadBytes(void* buffer, size_t size, bool exact_size) {
- size_t bytes_read = 0;
- while (bytes_read < size) {
- #if BUILDFLAG(IS_WIN)
- DWORD size_read = 0;
- bool had_error =
- !ReadFile(read_handle_, static_cast<char*>(buffer) + bytes_read,
- size - bytes_read, &size_read, nullptr);
- #else
- int size_read = read(read_fd_, static_cast<char*>(buffer) + bytes_read,
- size - bytes_read);
- if (size_read < 0 && errno == EINTR)
- continue;
- bool had_error = size_read <= 0;
- #endif
- if (had_error) {
- if (!shutting_down_.IsSet()) {
- LOG(ERROR) << "Connection terminated while reading from pipe";
- GetUIThreadTaskRunner({})->PostTask(
- FROM_HERE, base::BindOnce(&DevToolsPipeHandler::OnDisconnect,
- devtools_handler_));
- }
- return 0;
- }
- bytes_read += size_read;
- if (!exact_size)
- break;
- }
- return bytes_read;
- }
- void HandleMessage(std::vector<uint8_t> message) {
- GetUIThreadTaskRunner({})->PostTask(
- FROM_HERE, base::BindOnce(&DevToolsPipeHandler::HandleMessage,
- devtools_handler_, std::move(message)));
- }
- private:
- void ReadLoop() {
- ReadLoopInternal();
- GetUIThreadTaskRunner({})->PostTask(
- FROM_HERE,
- base::BindOnce(&DevToolsPipeHandler::Shutdown, devtools_handler_));
- }
- base::WeakPtr<DevToolsPipeHandler> devtools_handler_;
- #if BUILDFLAG(IS_WIN)
- HANDLE read_handle_;
- #else
- int read_fd_;
- #endif
- };
- class PipeWriterBase : public PipeIOBase {
- public:
- explicit PipeWriterBase(int write_fd)
- : PipeIOBase("DevToolsPipeHandlerWriteThread") {
- #if BUILDFLAG(IS_WIN)
- write_handle_ = reinterpret_cast<HANDLE>(_get_osfhandle(write_fd));
- #else
- write_fd_ = write_fd;
- #endif
- }
- void Write(base::span<const uint8_t> message) {
- base::TaskRunner* task_runner = thread_->task_runner().get();
- task_runner->PostTask(
- FROM_HERE,
- base::BindOnce(&PipeWriterBase::WriteIntoPipe, base::Unretained(this),
- std::string(message.begin(), message.end())));
- }
- protected:
- void ClosePipe() override {
- #if BUILDFLAG(IS_WIN)
- CloseHandle(write_handle_);
- #else
- shutdown(write_fd_, SHUT_RDWR);
- #endif
- }
- virtual void WriteIntoPipe(std::string message) = 0;
- void WriteBytes(const char* bytes, size_t size) {
- size_t total_written = 0;
- while (total_written < size) {
- size_t length = size - total_written;
- if (length > kWritePacketSize)
- length = kWritePacketSize;
- #if BUILDFLAG(IS_WIN)
- DWORD bytes_written = 0;
- bool had_error =
- !WriteFile(write_handle_, bytes + total_written,
- static_cast<DWORD>(length), &bytes_written, nullptr);
- #else
- int bytes_written = write(write_fd_, bytes + total_written, length);
- if (bytes_written < 0 && errno == EINTR)
- continue;
- bool had_error = bytes_written <= 0;
- #endif
- if (had_error) {
- if (!shutting_down_.IsSet())
- LOG(ERROR) << "Could not write into pipe";
- return;
- }
- total_written += bytes_written;
- }
- }
- private:
- #if BUILDFLAG(IS_WIN)
- HANDLE write_handle_;
- #else
- int write_fd_;
- #endif
- };
- namespace {
- class PipeWriterASCIIZ : public PipeWriterBase {
- public:
- explicit PipeWriterASCIIZ(int write_fd) : PipeWriterBase(write_fd) {}
- void WriteIntoPipe(std::string message) override {
- WriteBytes(message.data(), message.size());
- WriteBytes("\0", 1);
- }
- };
- class PipeWriterCBOR : public PipeWriterBase {
- public:
- explicit PipeWriterCBOR(int write_fd) : PipeWriterBase(write_fd) {}
- void WriteIntoPipe(std::string message) override {
- DCHECK(crdtp::cbor::IsCBORMessage(crdtp::SpanFrom(message)));
- WriteBytes(message.data(), message.size());
- }
- };
- class PipeReaderASCIIZ : public PipeReaderBase {
- public:
- PipeReaderASCIIZ(base::WeakPtr<DevToolsPipeHandler> devtools_handler,
- int read_fd)
- : PipeReaderBase(std::move(devtools_handler), read_fd) {
- read_buffer_ = new net::HttpConnection::ReadIOBuffer();
- read_buffer_->set_max_buffer_size(kReceiveBufferSizeForDevTools);
- }
- private:
- void ReadLoopInternal() override {
- while (true) {
- if (read_buffer_->RemainingCapacity() == 0 &&
- !read_buffer_->IncreaseCapacity()) {
- LOG(ERROR) << "Connection closed, not enough capacity";
- break;
- }
- size_t bytes_read = ReadBytes(read_buffer_->data(),
- read_buffer_->RemainingCapacity(), false);
- if (!bytes_read)
- break;
- read_buffer_->DidRead(bytes_read);
- // Go over the last read chunk, look for \0, extract messages.
- int offset = 0;
- for (int i = read_buffer_->GetSize() - bytes_read;
- i < read_buffer_->GetSize(); ++i) {
- if (read_buffer_->StartOfBuffer()[i] == '\0') {
- HandleMessage(
- std::vector<uint8_t>(read_buffer_->StartOfBuffer() + offset,
- read_buffer_->StartOfBuffer() + i));
- offset = i + 1;
- }
- }
- if (offset)
- read_buffer_->DidConsume(offset);
- }
- }
- scoped_refptr<net::HttpConnection::ReadIOBuffer> read_buffer_;
- };
- class PipeReaderCBOR : public PipeReaderBase {
- public:
- PipeReaderCBOR(base::WeakPtr<DevToolsPipeHandler> devtools_handler,
- int read_fd)
- : PipeReaderBase(std::move(devtools_handler), read_fd) {}
- private:
- static uint32_t UInt32FromCBOR(const uint8_t* buf) {
- return (buf[0] << 24) + (buf[1] << 16) + (buf[2] << 8) + buf[3];
- }
- void ReadLoopInternal() override {
- while (true) {
- const size_t kPeekSize =
- 8; // tag tag_type? byte_string length*4 map_start
- std::vector<uint8_t> buffer(kPeekSize);
- if (!ReadBytes(&buffer.front(), kPeekSize, true))
- break;
- auto status_or_header = crdtp::cbor::EnvelopeHeader::ParseFromFragment(
- crdtp::SpanFrom(buffer));
- if (!status_or_header.ok()) {
- LOG(ERROR) << "Error parsing CBOR envelope: "
- << status_or_header.status().ToASCIIString();
- return;
- }
- const size_t msg_size = (*status_or_header).outer_size();
- CHECK_GT(msg_size, kPeekSize);
- buffer.resize(msg_size);
- if (!ReadBytes(&buffer.front() + kPeekSize, msg_size - kPeekSize, true))
- return;
- HandleMessage(std::move(buffer));
- }
- }
- };
- } // namespace
- // DevToolsPipeHandler ---------------------------------------------------
- DevToolsPipeHandler::DevToolsPipeHandler(base::OnceClosure on_disconnect)
- : on_disconnect_(std::move(on_disconnect)),
- read_fd_(kReadFD),
- write_fd_(kWriteFD) {
- browser_target_ = DevToolsAgentHost::CreateForBrowser(
- nullptr, DevToolsAgentHost::CreateServerSocketCallback());
- browser_target_->AttachClient(this);
- std::string str_mode = base::ToLowerASCII(
- base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
- switches::kRemoteDebuggingPipe));
- mode_ = str_mode == "cbor" ? DevToolsPipeHandler::ProtocolMode::kCBOR
- : DevToolsPipeHandler::ProtocolMode::kASCIIZ;
- switch (mode_) {
- case ProtocolMode::kASCIIZ:
- pipe_reader_ = std::make_unique<PipeReaderASCIIZ>(
- weak_factory_.GetWeakPtr(), read_fd_);
- pipe_writer_ = std::make_unique<PipeWriterASCIIZ>(write_fd_);
- break;
- case ProtocolMode::kCBOR:
- pipe_reader_ = std::make_unique<PipeReaderCBOR>(
- weak_factory_.GetWeakPtr(), read_fd_);
- pipe_writer_ = std::make_unique<PipeWriterCBOR>(write_fd_);
- break;
- }
- if (!pipe_reader_->Start() || !pipe_writer_->Start())
- Shutdown();
- }
- void DevToolsPipeHandler::Shutdown() {
- if (shutting_down_)
- return;
- shutting_down_ = true;
- // Disconnect from the target.
- DCHECK(browser_target_);
- browser_target_->DetachClient(this);
- browser_target_ = nullptr;
- PipeIOBase::Shutdown(std::move(pipe_reader_));
- PipeIOBase::Shutdown(std::move(pipe_writer_));
- }
- DevToolsPipeHandler::~DevToolsPipeHandler() {
- Shutdown();
- }
- void DevToolsPipeHandler::HandleMessage(std::vector<uint8_t> message) {
- if (browser_target_)
- browser_target_->DispatchProtocolMessage(this, message);
- }
- void DevToolsPipeHandler::OnDisconnect() {
- if (on_disconnect_)
- std::move(on_disconnect_).Run();
- }
- void DevToolsPipeHandler::DispatchProtocolMessage(
- DevToolsAgentHost* agent_host,
- base::span<const uint8_t> message) {
- if (pipe_writer_)
- pipe_writer_->Write(message);
- }
- void DevToolsPipeHandler::AgentHostClosed(DevToolsAgentHost* agent_host) {}
- bool DevToolsPipeHandler::UsesBinaryProtocol() {
- return mode_ == ProtocolMode::kCBOR;
- }
- bool DevToolsPipeHandler::AllowUnsafeOperations() {
- return true;
- }
- std::string DevToolsPipeHandler::GetTypeForMetrics() {
- return "RemoteDebugger";
- }
- } // namespace content