/*
 * Copyright (C) 2021 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "src/websocket_bridge/websocket_bridge.h"

#include <stdint.h>

#include <cstdlib>
#include <map>
#include <memory>
#include <vector>

#include "perfetto/base/build_config.h"
#include "perfetto/ext/base/getopt.h"
#include "perfetto/ext/base/http/http_server.h"
#include "perfetto/ext/base/lock_free_task_runner.h"
#include "perfetto/ext/base/string_utils.h"
#include "perfetto/ext/base/unix_socket.h"
#include "perfetto/tracing/default_socket.h"

namespace perfetto {
namespace {

constexpr int kWebsocketPort = 8037;

struct Endpoint {
  const char* uri;
  const char* endpoint;
  base::SockFamily family;
};

class WSBridge : public base::HttpRequestHandler,
                 public base::UnixSocket::EventListener {
 public:
  void Main(int argc, char** argv);

  // base::HttpRequestHandler implementation.
  void OnHttpRequest(const base::HttpRequest&) override;
  void OnWebsocketMessage(const base::WebsocketMessage&) override;
  void OnHttpConnectionClosed(base::HttpServerConnection*) override;

  // base::UnixSocket::EventListener implementation.
  void OnNewIncomingConnection(base::UnixSocket*,
                               std::unique_ptr<base::UnixSocket>) override;
  void OnConnect(base::UnixSocket*, bool) override;
  void OnDisconnect(base::UnixSocket*) override;
  void OnDataAvailable(base::UnixSocket* self) override;

 private:
  base::HttpServerConnection* GetWebsocket(base::UnixSocket*);

  base::MaybeLockFreeTaskRunner task_runner_;
  std::vector<Endpoint> endpoints_;
  std::map<base::HttpServerConnection*, std::unique_ptr<base::UnixSocket>>
      conns_;
};

void PrintUsage(char** argv) {
  PERFETTO_ELOG(R"(
Websocket<->ADB/traced bridge. Allows WebUI to talk to ADB on Android and \
traced on Linux respectively.

Usage: %s [FLAGS]

General purpose:
 -h, --help                           Prints this help text.

Behavioural:
 --http-additional-cors-origins origin1,origin2,...
                                      Specify a comma-separated list of
                                      additional allowed origins for the
                                      Websocket server. These are in addition to
                                      the default origins: [https://ui.perfetto.dev,
                                      http://localhost:10000, http://127.0.0.1:10000]
)",
                argv[0]);
}

struct CommandLineOptions {
  std::vector<std::string> additional_allowed_origins;
};

CommandLineOptions ParseCommandLineOptions(int argc, char** argv) {
  CommandLineOptions command_line_options;
  enum LongOption {
    OPT_HTTP_ADDITIONAL_CORS_ORIGINS,
  };

  static const option long_options[] = {
      {"help", no_argument, nullptr, 'h'},
      {"http-additional-cors-origins", required_argument, nullptr,
       OPT_HTTP_ADDITIONAL_CORS_ORIGINS}};

  for (;;) {
    int option = getopt_long(argc, argv, "h", long_options, nullptr);

    if (option == -1)
      break;  // EOF.

    if (option == OPT_HTTP_ADDITIONAL_CORS_ORIGINS) {
      command_line_options.additional_allowed_origins =
          base::SplitString(optarg, ",");
      continue;
    }

    PrintUsage(argv);
    exit(option == 'h' ? 0 : 1);
  }

  return command_line_options;
}

void WSBridge::Main(int argc, char** argv) {
#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
  // On Windows traced used a TCP socket.
  const auto kTracedFamily = base::SockFamily::kInet;
#else
  const auto kTracedFamily = base::SockFamily::kUnix;
#endif
  // The ADB_SERVER_SOCKET environment variable is sourced from
  // the commandline.cpp file in the ADB module of the Android platform.
  // Examples: tcp:localhost:5037 or tcp:10.52.8.53:5037.
  std::string adb_socket_endpoint;
  if (const char* adb_ss = getenv("ADB_SERVER_SOCKET"); adb_ss) {
    base::StringView adb_ss_sv(adb_ss);
    adb_socket_endpoint = base::StripPrefix(adb_ss, "tcp:");

    // Ensure that ADB_SERVER_SOCKET actually starts with tcp:
    PERFETTO_CHECK(adb_socket_endpoint.size() != adb_ss_sv.size());
  } else {
    adb_socket_endpoint = "127.0.0.1:5037";
  }
  PERFETTO_LOG("[WSBridge] adb server socket is:%s.",
               adb_socket_endpoint.c_str());
  endpoints_.push_back({"/traced", GetConsumerSocket(), kTracedFamily});
  endpoints_.push_back(
      {"/adb", adb_socket_endpoint.c_str(), base::SockFamily::kInet});

  base::HttpServer srv(&task_runner_, this);
  srv.AddAllowedOrigin("http://localhost:10000");
  srv.AddAllowedOrigin("http://127.0.0.1:10000");
  srv.AddAllowedOrigin("https://ui.perfetto.dev");

  std::vector<std::string> additional_cors_origins = base::SplitString(
      PERFETTO_BUILDFLAG(PERFETTO_HTTP_ADDITIONAL_CORS_ORIGINS), ",");

  for (const auto& origin : additional_cors_origins) {
    srv.AddAllowedOrigin(origin);
  }

  CommandLineOptions cmd_line_options = ParseCommandLineOptions(argc, argv);

  for (const auto& origin : cmd_line_options.additional_allowed_origins) {
    srv.AddAllowedOrigin(origin);
    PERFETTO_LOG("[WSBridge] Added additional allowed CORS origin: %s",
                 origin.c_str());
  }

  srv.Start("localhost", kWebsocketPort);
  PERFETTO_LOG("[WSBridge] Listening on 127.0.0.1:%d", kWebsocketPort);
  task_runner_.Run();
}

void WSBridge::OnHttpRequest(const base::HttpRequest& req) {
  for (const auto& ep : endpoints_) {
    if (req.uri != ep.uri || !req.is_websocket_handshake)
      continue;

    // Connect to the endpoint in blocking mode.
    auto sock_raw =
        base::UnixSocketRaw::CreateMayFail(ep.family, base::SockType::kStream);
    if (!sock_raw) {
      PERFETTO_PLOG("socket() failed");
      req.conn->SendResponseAndClose("500 Server Error");
      return;
    }
    PERFETTO_LOG("[WSBridge] New connection from \"%.*s\"",
                 static_cast<int>(req.origin.size()), req.origin.data());
    sock_raw.SetTxTimeout(3000);
    sock_raw.SetBlocking(true);

    if (!sock_raw.Connect(ep.endpoint)) {
      PERFETTO_ELOG("[WSBridge] Connection to %s failed", ep.endpoint);
      req.conn->SendResponseAndClose("503 Service Unavailable");
      return;
    }
    sock_raw.SetBlocking(false);

    PERFETTO_DLOG("[WSBridge] Connected to %s", ep.endpoint);
    conns_[req.conn] = base::UnixSocket::AdoptConnected(
        sock_raw.ReleaseFd(), this, &task_runner_, ep.family,
        base::SockType::kStream);

    req.conn->UpgradeToWebsocket(req);
    return;
  }  // for (endpoint)
  req.conn->SendResponseAndClose("404 Not Found");
}

// Called when an inbound websocket message is received from the browser.
void WSBridge::OnWebsocketMessage(const base::WebsocketMessage& msg) {
  auto it = conns_.find(msg.conn);
  PERFETTO_CHECK(it != conns_.end());
  // Pass through the websocket message onto the endpoint TCP socket.
  base::UnixSocket& sock = *it->second;
  sock.Send(msg.data.data(), msg.data.size());
}

// Called when a TCP message is received from the endpoint.
void WSBridge::OnDataAvailable(base::UnixSocket* sock) {
  base::HttpServerConnection* websocket = GetWebsocket(sock);
  PERFETTO_CHECK(websocket);

  char buf[8192];
  auto rsize = sock->Receive(buf, sizeof(buf));
  if (rsize > 0) {
    websocket->SendWebsocketMessage(buf, static_cast<size_t>(rsize));
  } else {
    // Connection closed or errored.
    sock->Shutdown(/*notify=*/true);  // Will trigger OnDisconnect().
    websocket->Close();
  }
}

// Called when the browser terminates the websocket connection.
void WSBridge::OnHttpConnectionClosed(base::HttpServerConnection* websocket) {
  PERFETTO_DLOG("[WSBridge] Websocket connection closed");
  auto it = conns_.find(websocket);
  if (it == conns_.end())
    return;  // Can happen if ADB closed first.
  base::UnixSocket& sock = *it->second;
  sock.Shutdown(/*notify=*/true);
  conns_.erase(websocket);
}

void WSBridge::OnDisconnect(base::UnixSocket* sock) {
  base::HttpServerConnection* websocket = GetWebsocket(sock);
  if (!websocket)
    return;
  websocket->Close();
  sock->Shutdown(/*notify=*/false);
  conns_.erase(websocket);
  PERFETTO_DLOG("[WSBridge] Socket connection closed");
}

base::HttpServerConnection* WSBridge::GetWebsocket(base::UnixSocket* sock) {
  for (const auto& it : conns_) {
    if (it.second.get() == sock) {
      return it.first;
    }
  }
  return nullptr;
}

void WSBridge::OnConnect(base::UnixSocket*, bool) {}
void WSBridge::OnNewIncomingConnection(base::UnixSocket*,
                                       std::unique_ptr<base::UnixSocket>) {}

}  // namespace

int PERFETTO_EXPORT_ENTRYPOINT WebsocketBridgeMain(int argc, char** argv) {
  perfetto::WSBridge ws_bridge;
  ws_bridge.Main(argc, argv);
  return 0;
}

}  // namespace perfetto
