// 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/adb/mock_adb_server.h"

#include <stddef.h>
#include <stdint.h>

#include <string_view>

#include "base/compiler_specific.h"
#include "base/containers/span.h"
#include "base/functional/bind.h"
#include "base/location.h"
#include "base/memory/weak_ptr.h"
#include "base/run_loop.h"
#include "base/sequence_checker.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/strings/string_view_util.h"
#include "base/strings/stringprintf.h"
#include "base/task/single_thread_task_runner.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/test_utils.h"
#include "net/base/io_buffer.h"
#include "net/base/ip_address.h"
#include "net/base/ip_endpoint.h"
#include "net/base/net_errors.h"
#include "net/log/net_log_source.h"
#include "net/socket/stream_socket.h"
#include "net/socket/tcp_server_socket.h"
#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"

using content::BrowserThread;

namespace {

const char kHostTransportPrefix[] = "host:transport:";
const char kLocalAbstractPrefix[] = "localabstract:";

const char kShellPrefix[] = "shell:";
const char kOpenedUnixSocketsCommand[] = "cat /proc/net/unix";
const char kDeviceModelCommand[] = "getprop ro.product.model";
const char kDumpsysCommand[] = "dumpsys window policy";
const char kListProcessesCommand[] = "ps -e";
const char kListUsersCommand[] = "dumpsys user";
const char kEchoCommandPrefix[] = "echo ";
const char kTrustCommand[] = "dumpsys trust";
const char kSizeCommand[] = "wm size";

const char kSerialOnline[] = "01498B321301A00A";
const char kSerialOffline[] = "01498B2B0D01300E";
const char kDeviceModel[] = "Nexus 6";

const char kJsonVersionPath[] = "/json/version";
const char kJsonPath[] = "/json";
const char kJsonListPath[] = "/json/list";

const char kHttpRequestTerminator[] = "\r\n\r\n";

const char kHttpResponse[] =
    "HTTP/1.1 200 OK\r\n"
    "Content-Length:%d\r\n"
    "Content-Type:application/json; charset=UTF-8\r\n\r\n%s";

const char kSampleOpenedUnixSockets[] =
    "Num       RefCount Protocol Flags    Type St Inode Path\n"
    "00000000: 00000004 00000000"
    " 00000000 0002 01  3328 /dev/socket/wpa_wlan0\n"
    "00000000: 00000002 00000000"
    " 00010000 0001 01  5394 /dev/socket/vold\n"
    "00000000: 00000002 00000000"
    " 00010000 0001 01 11810 @webview_devtools_remote_2425\n"
    "00000000: 00000002 00000000"
    " 00010000 0001 01 20893 @chrome_devtools_remote\n"
    "00000000: 00000002 00000000"
    " 00010000 0001 01 20894 @chrome_devtools_remote_1002\n"
    "00000000: 00000002 00000000"
    " 00010000 0001 01 20895 @noprocess_devtools_remote\n"
    "00000000: 00000002 00000000"
    " 00010000 0001 01 20895 @node_devtools_remote\n";

const char kSampleListProcesses[] =
    "USER    PID  PPID VSIZE  RSS    WCHAN    PC         NAME\n"
    "root    1    0    688    508    ffffffff 00000000 S /init\r\n"
    "u0_a75  2425 123  933736 193024 ffffffff 00000000 S com.sample.feed\r\n"
    "nfc     741  123  706448 26316  ffffffff 00000000 S com.android.nfc\r\n"
    "u0_a76  1001 124  111111 222222 ffffffff 00000000 S com.android.chrome\r\n"
    "u10_a77 1002 125  111111 222222 ffffffff 00000000 S com.chrome.beta\r\n"
    "u0_a78  1003 126  111111 222222 ffffffff 00000000 S com.noprocess.app\r\n";

const char kSampleDumpsys[] =
    "WINDOW MANAGER POLICY STATE (dumpsys window policy)\r\n"
    "    mSafeMode=false mSystemReady=true mSystemBooted=true\r\n"
    "    mStable=(0,50)-(720,1184)\r\n" // Only mStable parameter is parsed
    "    mForceStatusBar=false mForceStatusBarFromKeyguard=false\r\n";

const char kSampleListUsers[] =
    "Users:\r\n"
    "  UserInfo{0:Test User:13} serialNo=0\r\n"
    "    Created: <unknown>\r\n"
    "    Last logged in: +17m18s871ms ago\r\n"
    "  UserInfo{10:Test User : 2:10} serialNo=10\r\n"
    "    Created: +3d4h35m1s139ms ago\r\n"
    "    Last logged in: +17m26s287ms ago\r\n";

char kSampleChromeVersion[] = "{\n"
    "   \"Browser\": \"Chrome/32.0.1679.0\",\n"
    "   \"Protocol-Version\": \"1.0\",\n"
    "   \"User-Agent\": \"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 "
    "(KHTML, like Gecko) Chrome/32.0.1679.0 Safari/537.36\",\n"
    "   \"WebKit-Version\": \"537.36 (@160162)\"\n"
    "}";

char kSampleChromeBetaVersion[] = "{\n"
    "   \"Browser\": \"Chrome/31.0.1599.0\",\n"
    "   \"Protocol-Version\": \"1.0\",\n"
    "   \"User-Agent\": \"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 "
    "(KHTML, like Gecko) Chrome/32.0.1679.0 Safari/537.36\",\n"
    "   \"WebKit-Version\": \"537.36 (@160162)\"\n"
    "}";

char kSampleWebViewVersion[] = "{\n"
    "   \"Browser\": \"Version/4.0\",\n"
    "   \"Protocol-Version\": \"1.0\",\n"
    "   \"User-Agent\": \"Mozilla/5.0 (Linux; Android 4.3; Build/KRS74B) "
    "AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Safari/537.36\",\n"
    "   \"WebKit-Version\": \"537.36 (@157588)\"\n"
    "}";

char kSampleNodeVersion[] = "{\n"
    "   \"Browser\": \"node.js/v10.15.3\","
    "   \"Protocol-Version\": \"1.1\""
    "}";

char kSampleChromePages[] = "[ {\n"
    "   \"description\": \"\",\n"
    "   \"devtoolsFrontendUrl\": \"/devtools/devtools.html?"
    "ws=/devtools/page/0\",\n"
    "   \"id\": \"0\",\n"
    "   \"title\": \"The Chromium Projects\",\n"
    "   \"type\": \"page\",\n"
    "   \"url\": \"http://www.chromium.org/\",\n"
    "   \"webSocketDebuggerUrl\": \""
    "ws:///devtools/page/0\"\n"
    "} ]";

char kSampleChromeBetaPages[] = "[ {\n"
    "   \"description\": \"\",\n"
    "   \"devtoolsFrontendUrl\": \"/devtools/devtools.html?"
    "ws=/devtools/page/0\",\n"
    "   \"id\": \"0\",\n"
    "   \"title\": \"The Chromium Projects\",\n"
    "   \"type\": \"page\",\n"
    "   \"url\": \"http://www.chromium.org/\",\n"
    "   \"webSocketDebuggerUrl\": \""
    "ws:///devtools/page/0\"\n"
    "} ]";

char kSampleWebViewPages[] = "[ {\n"
    "   \"description\": \"{\\\"attached\\\":false,\\\"empty\\\":false,"
    "\\\"height\\\":1173,\\\"screenX\\\":0,\\\"screenY\\\":0,"
    "\\\"visible\\\":true,\\\"width\\\":800}\",\n"
    "   \"devtoolsFrontendUrl\": \"http://chrome-devtools-frontend.appspot.com/"
    "serve_rev/@157588/devtools.html?ws="
    "/devtools/page/3E962D4D-B676-182D-3BE8-FAE7CE224DE7\",\n"
    "   \"faviconUrl\": \"http://chromium.org/favicon.ico\",\n"
    "   \"id\": \"3E962D4D-B676-182D-3BE8-FAE7CE224DE7\",\n"
    "   \"title\": \"Blink - The Chromium Projects\",\n"
    "   \"type\": \"page\",\n"
    "   \"url\": \"http://www.chromium.org/blink\",\n"
    "   \"webSocketDebuggerUrl\": \"ws:///devtools/"
    "page/3E962D4D-B676-182D-3BE8-FAE7CE224DE7\"\n"
    "}, {\n"
    "   \"description\": \"{\\\"attached\\\":true,\\\"empty\\\":true,"
    "\\\"screenX\\\":0,\\\"screenY\\\":33,\\\"visible\\\":false}\",\n"
    "   \"devtoolsFrontendUrl\": \"http://chrome-devtools-frontend.appspot.com/"
    "serve_rev/@157588/devtools.html?ws="
    "/devtools/page/44681551-ADFD-2411-076B-3AB14C1C60E2\",\n"
    "   \"faviconUrl\": \"\",\n"
    "   \"id\": \"44681551-ADFD-2411-076B-3AB14C1C60E2\",\n"
    "   \"title\": \"More Activity\",\n"
    "   \"type\": \"page\",\n"
    "   \"url\": \"about:blank\",\n"
    "   \"webSocketDebuggerUrl\": \"ws:///devtools/page/"
    "44681551-ADFD-2411-076B-3AB14C1C60E2\"\n"
    "}]";

char kSampleNodePage[] = "[ {\n"
    "   \"description\": \"\","
    "   \"devtoolsFrontendUrl\": \"chrome-devtools://devtools/bundled/"
    "js_app.html?experiments=true&v8only=true&ws=192.168.86.1:33279/"
    "148b8b92-8ca0-43fd-b8c8-a351864644f8\","
    "   \"faviconUrl\": \"https://nodejs.org/static/favicon.ico\","
    "   \"id\": \"148b8b92-8ca0-43fd-b8c8-a351864644f8\","
    "   \"title\": \"a-node-process\","
    "   \"type\": \"node\","
    "   \"url\": \"about:blank\",\n"
    "   \"webSocketDebuggerUrl\": \"ws://192.168.86.1:33279/"
    "148b8b92-8ca0-43fd-b8c8-a351864644f8\""
    "} ]";

const char kSampleTrust[] =
    "Trust manager state:\n"
    " User \"Owner\" (id=0, flags=0x4c13) (current): trustState=UNTRUSTED, "
    "trustManaged=0, deviceLocked=0, isActiveUnlockRunning=0, "
    "strongAuthRequired=0x0\n"
    "   Enabled agents:\n"
    "   Events:\n";

const char kSampleSize[] = "Physical size: 720x1184\n";

static constexpr int kBufferSize = 16 * 1024;
static constexpr uint16_t kAdbPort = 5037;

static constexpr size_t kAdbMessageHeaderSize = 4;

class SimpleHttpServer {
 public:
  class Parser {
   public:
    virtual size_t Consume(base::span<const uint8_t> data) = 0;
    virtual ~Parser() = default;
  };

  using SendCallback = base::RepeatingCallback<void(const std::string&)>;
  using ParserFactory = base::RepeatingCallback<Parser*(const SendCallback&)>;

  SimpleHttpServer(const ParserFactory& factory, net::IPEndPoint endpoint);

  SimpleHttpServer(const SimpleHttpServer&) = delete;
  SimpleHttpServer& operator=(const SimpleHttpServer&) = delete;

  virtual ~SimpleHttpServer();

 private:
  class Connection {
   public:
    Connection(net::StreamSocket* socket, const ParserFactory& factory);

    Connection(const Connection&) = delete;
    Connection& operator=(const Connection&) = delete;

    virtual ~Connection();

   private:
    void Send(const std::string& message);
    void ReadData();
    void OnDataRead(int count);
    void WriteData();
    void OnDataWritten(int count);

    std::unique_ptr<net::StreamSocket> socket_;
    std::unique_ptr<Parser> parser_;
    scoped_refptr<net::GrowableIOBuffer> input_buffer_ =
        base::MakeRefCounted<net::GrowableIOBuffer>();
    scoped_refptr<net::GrowableIOBuffer> output_buffer_ =
        base::MakeRefCounted<net::GrowableIOBuffer>();
    size_t bytes_to_write_ = 0;
    bool read_closed_ = false;

    SEQUENCE_CHECKER(sequence_checker_);

    base::WeakPtrFactory<Connection> weak_factory_{this};
  };

  void OnConnect();
  void OnAccepted(int result);

  ParserFactory factory_;
  std::unique_ptr<net::TCPServerSocket> socket_ =
      std::make_unique<net::TCPServerSocket>(nullptr, net::NetLogSource());
  std::unique_ptr<net::StreamSocket> client_socket_;

  SEQUENCE_CHECKER(sequence_checker_);

  base::WeakPtrFactory<SimpleHttpServer> weak_factory_{this};
};

SimpleHttpServer::SimpleHttpServer(const ParserFactory& factory,
                                   net::IPEndPoint endpoint)
    : factory_(factory) {
  socket_->Listen(endpoint, 5, /*ipv6_only=*/std::nullopt);
  OnConnect();
}

SimpleHttpServer::~SimpleHttpServer() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}

SimpleHttpServer::Connection::Connection(net::StreamSocket* socket,
                                         const ParserFactory& factory)
    : socket_(socket),
      parser_(factory.Run(
          base::BindRepeating(&Connection::Send, base::Unretained(this)))) {
  input_buffer_->SetCapacity(kBufferSize);
  ReadData();
}

SimpleHttpServer::Connection::~Connection() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}

void SimpleHttpServer::Connection::Send(const std::string& message) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  const size_t size = message.size();
  const size_t total_size = bytes_to_write_ + size;
  const auto old_offset = base::checked_cast<size_t>(output_buffer_->offset());
  const auto old_capacity =
      base::checked_cast<size_t>(output_buffer_->capacity());
  if ((old_offset + total_size) > old_capacity) {
    // If not enough space without relocation
    if (old_capacity < total_size) {
      // If even buffer is not enough
      output_buffer_->SetCapacity(
          base::checked_cast<int>(std::max(old_capacity * 2, size * 2)));
    }
    output_buffer_->set_offset(0);
    output_buffer_->span().copy_prefix_from(
        output_buffer_->span().subspan(old_offset, bytes_to_write_));
  }

  output_buffer_->span()
      .subspan(bytes_to_write_, size)
      .copy_from(base::as_byte_span(message));
  bytes_to_write_ = total_size;

  if (total_size == size) {
    // If write loop wasn't yet started, then start it
    WriteData();
  }
}

void SimpleHttpServer::Connection::ReadData() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  if (input_buffer_->RemainingCapacity() == 0)
    input_buffer_->SetCapacity(input_buffer_->capacity() * 2);

  int read_result = socket_->Read(
      input_buffer_.get(), input_buffer_->RemainingCapacity(),
      base::BindOnce(&Connection::OnDataRead, base::Unretained(this)));

  if (read_result != net::ERR_IO_PENDING)
    OnDataRead(read_result);
}

void SimpleHttpServer::Connection::OnDataRead(int count) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (count <= 0) {
    if (bytes_to_write_ == 0)
      delete this;
    else
      read_closed_ = true;
    return;
  }
  input_buffer_->set_offset(input_buffer_->offset() + count);
  size_t bytes_processed;

  do {
    base::span<uint8_t> data_buffer = input_buffer_->span_before_offset();
    bytes_processed = parser_->Consume(data_buffer);

    if (bytes_processed) {
      const size_t unprocessed_size = data_buffer.size() - bytes_processed;
      input_buffer_->everything().copy_prefix_from(
          data_buffer.subspan(bytes_processed));
      input_buffer_->set_offset(unprocessed_size);
    }
  } while (bytes_processed);
  // Posting to avoid deep recursion in case of synchronous IO
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE,
      base::BindOnce(&Connection::ReadData, weak_factory_.GetWeakPtr()));
}

void SimpleHttpServer::Connection::WriteData() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  const auto bytes_to_write_i = base::checked_cast<int>(bytes_to_write_);
  CHECK_GE(output_buffer_->capacity(),
           output_buffer_->offset() + bytes_to_write_i)
      << "Overflow";

  int write_result = socket_->Write(
      output_buffer_.get(), bytes_to_write_i,
      base::BindOnce(&Connection::OnDataWritten, base::Unretained(this)),
      TRAFFIC_ANNOTATION_FOR_TESTS);

  if (write_result != net::ERR_IO_PENDING)
    OnDataWritten(write_result);
}

void SimpleHttpServer::Connection::OnDataWritten(int count) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (count < 0) {
    delete this;
    return;
  }
  CHECK_GT(count, 0);
  const auto bytes_to_write_i = base::checked_cast<int>(bytes_to_write_);
  CHECK_LE(count, bytes_to_write_i);
  CHECK_GE(output_buffer_->capacity(),
           output_buffer_->offset() + bytes_to_write_i)
      << "Overflow";

  bytes_to_write_ -= count;
  output_buffer_->set_offset(output_buffer_->offset() + count);

  if (bytes_to_write_ != 0) {
    // Posting to avoid deep recursion in case of synchronous IO
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE,
        base::BindOnce(&Connection::WriteData, weak_factory_.GetWeakPtr()));
  } else if (read_closed_) {
    delete this;
  }
}

void SimpleHttpServer::OnConnect() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  int accept_result = socket_->Accept(
      &client_socket_,
      base::BindOnce(&SimpleHttpServer::OnAccepted, base::Unretained(this)));

  if (accept_result != net::ERR_IO_PENDING)
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, base::BindOnce(&SimpleHttpServer::OnAccepted,
                                  weak_factory_.GetWeakPtr(), accept_result));
}

void SimpleHttpServer::OnAccepted(int result) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  ASSERT_EQ(result, 0);  // Fails if the socket is already in use.
  new Connection(client_socket_.release(), factory_);
  OnConnect();
}

class AdbParser : public SimpleHttpServer::Parser,
                  public MockAndroidConnection::Delegate {
 public:
  static Parser* Create(FlushMode flush_mode,
                        const SimpleHttpServer::SendCallback& callback) {
    return new AdbParser(flush_mode, callback);
  }

  ~AdbParser() override { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); }

 private:
  explicit AdbParser(FlushMode flush_mode,
                     const SimpleHttpServer::SendCallback& callback)
      : flush_mode_(flush_mode),
        callback_(callback) {
  }

  size_t Consume(base::span<const uint8_t> data) override {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
    const size_t size = data.size();
    if (mock_connection_) {
      mock_connection_->Receive(std::string(base::as_string_view(data)));
      return size;
    }
    if (size >= kAdbMessageHeaderSize) {
      std::string_view message_header =
          base::as_string_view(data.first(kAdbMessageHeaderSize));
      uint32_t message_size;

      EXPECT_TRUE(base::HexStringToUInt(message_header, &message_size));

      if (size >= message_size + kAdbMessageHeaderSize) {
        std::string message_body(base::as_string_view(
            data.subspan(kAdbMessageHeaderSize, message_size)));
        ProcessCommand(message_body);
        return kAdbMessageHeaderSize + message_size;
      }
    }
    return 0;
  }

  void ProcessCommand(const std::string& command) {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
    if (command == "host:devices") {
      SendSuccess(base::StringPrintf("%s\tdevice\n%s\toffline",
                                     kSerialOnline,
                                     kSerialOffline));
    } else if (base::StartsWith(command, kHostTransportPrefix,
                                base::CompareCase::SENSITIVE)) {
      serial_ = command.substr(sizeof(kHostTransportPrefix) - 1);
      SendSuccess(std::string());
    } else if (serial_ != kSerialOnline) {
      Send("FAIL", "device offline (x)");
    } else {
      mock_connection_ =
          std::make_unique<MockAndroidConnection>(this, serial_, command);
    }
  }

  void SendSuccess(const std::string& response) override {
    Send("OKAY", response);
  }

  void SendRaw(const std::string& data) override {
    callback_.Run(data);
  }

  void Send(const std::string& status, const std::string& response) {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
    CHECK_EQ(4U, status.size());
    std::string buffer = status;
    if (flush_mode_ == FlushWithoutSize) {
        callback_.Run(buffer);
        buffer = std::string();
    }

    if (size_t size = response.size(); size > 0) {
      CHECK_LE(size, 0xffffu);
      base::AppendHexEncodedByte(static_cast<uint8_t>(size >> 8), buffer);
      base::AppendHexEncodedByte(static_cast<uint8_t>(size), buffer);
      if (flush_mode_ == FlushWithSize) {
          callback_.Run(buffer);
          buffer = std::string();
      }
      buffer += response;
      callback_.Run(buffer);
    } else if (flush_mode_ != FlushWithoutSize) {
      callback_.Run(buffer);
    }
  }

  FlushMode flush_mode_;
  SimpleHttpServer::SendCallback callback_;
  std::string serial_;
  std::unique_ptr<MockAndroidConnection> mock_connection_;

  SEQUENCE_CHECKER(sequence_checker_);
};

static SimpleHttpServer* mock_adb_server_ = nullptr;

void StartMockAdbServerOnIOThread(FlushMode flush_mode) {
  CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
  CHECK(mock_adb_server_ == nullptr);
  net::IPEndPoint endpoint(net::IPAddress(127, 0, 0, 1), kAdbPort);
  mock_adb_server_ = new SimpleHttpServer(
      base::BindRepeating(&AdbParser::Create, flush_mode), endpoint);
}

void StopMockAdbServerOnIOThread() {
  CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
  CHECK(mock_adb_server_ != nullptr);
  delete mock_adb_server_;
  mock_adb_server_ = nullptr;
}

} // namespace

MockAndroidConnection::MockAndroidConnection(
    Delegate* delegate,
    const std::string& serial,
    const std::string& command)
    : delegate_(delegate),
      serial_(serial) {
  ProcessCommand(command);
}

MockAndroidConnection::~MockAndroidConnection() = default;

void MockAndroidConnection::Receive(const std::string& data) {
  request_ += data;
  size_t request_end_pos = data.find(kHttpRequestTerminator);
  if (request_end_pos == std::string::npos)
    return;

  std::string request(request_.substr(0, request_end_pos));
  std::vector<std::string_view> lines = base::SplitStringPieceUsingSubstr(
      request, "\r\n", base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
  CHECK_GE(2U, lines.size());
  std::vector<std::string> tokens = base::SplitString(
      lines[0], " ", base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
  CHECK_EQ(3U, tokens.size());
  CHECK_EQ("GET", tokens[0]);
  CHECK_EQ("HTTP/1.1", tokens[2]);

  std::string path(tokens[1]);
  if (path == kJsonPath)
    path = kJsonListPath;

  if (socket_name_ == "chrome_devtools_remote") {
    if (path == kJsonVersionPath)
      SendHTTPResponse(kSampleChromeVersion);
    else if (base::StartsWith(path, kJsonListPath))
      SendHTTPResponse(kSampleChromePages);
    else
      NOTREACHED() << "Unknown command " << request;
  } else if (socket_name_ == "chrome_devtools_remote_1002") {
    if (path == kJsonVersionPath)
      SendHTTPResponse(kSampleChromeBetaVersion);
    else if (base::StartsWith(path, kJsonListPath))
      SendHTTPResponse(kSampleChromeBetaPages);
    else
      NOTREACHED() << "Unknown command " << request;
  } else if (base::StartsWith(socket_name_, "noprocess_devtools_remote",
                              base::CompareCase::SENSITIVE)) {
    if (path == kJsonVersionPath)
      SendHTTPResponse("{}");
    else if (base::StartsWith(path, kJsonListPath))
      SendHTTPResponse("[]");
    else
      NOTREACHED() << "Unknown command " << request;
  } else if (socket_name_ == "webview_devtools_remote_2425") {
    if (path == kJsonVersionPath)
      SendHTTPResponse(kSampleWebViewVersion);
    else if (base::StartsWith(path, kJsonListPath))
      SendHTTPResponse(kSampleWebViewPages);
    else
      NOTREACHED() << "Unknown command " << request;
  } else if (socket_name_ == "node_devtools_remote") {
    if (path == kJsonVersionPath)
      SendHTTPResponse(kSampleNodeVersion);
    else if (base::StartsWith(path, kJsonListPath))
      SendHTTPResponse(kSampleNodePage);
    else
      NOTREACHED() << "Unknown command " << request;
  } else {
    NOTREACHED() << "Unknown socket " << socket_name_;
  }
}

void MockAndroidConnection::ProcessCommand(const std::string& command) {
  if (base::StartsWith(command, kLocalAbstractPrefix,
                       base::CompareCase::SENSITIVE)) {
    socket_name_ = command.substr(sizeof(kLocalAbstractPrefix) - 1);
    delegate_->SendSuccess(std::string());
    return;
  }

  if (base::StartsWith(command, kShellPrefix, base::CompareCase::SENSITIVE)) {
    std::string result;
    for (const auto& line :
         base::SplitString(command.substr(sizeof(kShellPrefix) - 1), "\n",
                           base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY)) {
      if (line == kDeviceModelCommand) {
        result += kDeviceModel;
        result += "\r\n";
      } else if (line == kOpenedUnixSocketsCommand) {
        result += kSampleOpenedUnixSockets;
      } else if (line == kDumpsysCommand) {
        result += kSampleDumpsys;
      } else if (line == kListProcessesCommand) {
        result += kSampleListProcesses;
      } else if (line == kListUsersCommand) {
        result += kSampleListUsers;
      } else if (line == kTrustCommand) {
        result += kSampleTrust;
      } else if (line == kSizeCommand) {
        result += kSampleSize;
      } else if (base::StartsWith(line, kEchoCommandPrefix,
                                  base::CompareCase::SENSITIVE)) {
        result += line.substr(sizeof(kEchoCommandPrefix) - 1);
        result += "\r\n";
      } else {
        NOTREACHED() << "Unknown shell command - " << command;
      }
    }
    delegate_->SendSuccess(result);
  } else {
    NOTREACHED() << "Unknown command - " << command;
  }
  delegate_->Close();
}

void MockAndroidConnection::SendHTTPResponse(const std::string& body) {
  std::string response_data(base::StringPrintf(kHttpResponse,
                                               static_cast<int>(body.size()),
                                               body.c_str()));
  delegate_->SendRaw(response_data);
}

void StartMockAdbServer(FlushMode flush_mode) {
  base::RunLoop run_loop;
  content::GetIOThreadTaskRunner({})->PostTaskAndReply(
      FROM_HERE, base::BindOnce(&StartMockAdbServerOnIOThread, flush_mode),
      run_loop.QuitClosure());
  run_loop.Run();
}

void StopMockAdbServer() {
  base::RunLoop run_loop;
  content::GetIOThreadTaskRunner({})->PostTaskAndReply(
      FROM_HERE, base::BindOnce(&StopMockAdbServerOnIOThread),
      run_loop.QuitClosure());
  run_loop.Run();
}
