// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "test_util.h"

#include <array>
#include <optional>
#include <string>

#include "base/check.h"
#include "base/compiler_specific.h"
#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/path_service.h"
#include "base/process/launch.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/string_view_util.h"
#include "device/fido/enclave/types.h"
#include "device/fido/public/fido_constants.h"
#include "net/base/port_util.h"
#include "url/gurl.h"

#if BUILDFLAG(IS_WIN)
#include <windows.h>
#endif

base::span<const uint8_t, 16> TestProtobufCredId() {
  return base::span<const uint8_t>(kTestProtobuf).subspan<20, 16>();
}

base::span<const uint8_t, 1> TestProtobufUserId() {
  return base::span<const uint8_t>(kTestProtobuf).subspan<55, 1>();
}

std::pair<base::Process, uint16_t> StartWebAuthnEnclave(base::FilePath cwd) {
  base::FilePath data_root;
  CHECK(base::PathService::Get(base::DIR_OUT_TEST_DATA_ROOT, &data_root));
  const base::FilePath enclave_bin_path =
      data_root.AppendASCII("cloud_authenticator_test_service");
  base::LaunchOptions subprocess_opts;
  subprocess_opts.current_directory = cwd;

  std::optional<base::Process> enclave_process;
  uint16_t port;
  std::array<char, 6> port_str;

  for (int i = 0; i < 10; i++) {
#if BUILDFLAG(IS_WIN)
    HANDLE read_handle;
    HANDLE write_handle;
    SECURITY_ATTRIBUTES security_attributes;

    security_attributes.nLength = sizeof(SECURITY_ATTRIBUTES);
    security_attributes.bInheritHandle = TRUE;
    security_attributes.lpSecurityDescriptor = NULL;
    CHECK(CreatePipe(&read_handle, &write_handle, &security_attributes, 0));

    subprocess_opts.stdin_handle = INVALID_HANDLE_VALUE;
    subprocess_opts.stdout_handle = write_handle;
    subprocess_opts.stderr_handle = INVALID_HANDLE_VALUE;
    subprocess_opts.handles_to_inherit.push_back(write_handle);
    enclave_process = base::LaunchProcess(base::CommandLine(enclave_bin_path),
                                          subprocess_opts);
    CloseHandle(write_handle);
    CHECK(enclave_process->IsValid());

    DWORD read_bytes;
    CHECK(ReadFile(read_handle, port_str.data(), sizeof(port_str), &read_bytes,
                   NULL));
    CloseHandle(read_handle);
#else
    int fds[2];
    CHECK(!pipe(fds));
    subprocess_opts.fds_to_remap.emplace_back(fds[1], 1);
    enclave_process = base::LaunchProcess(base::CommandLine(enclave_bin_path),
                                          subprocess_opts);
    CHECK(enclave_process->IsValid());
    close(fds[1]);

    const ssize_t read_bytes =
        HANDLE_EINTR(read(fds[0], port_str.data(), sizeof(port_str)));
    close(fds[0]);
#endif

    CHECK(read_bytes > 0);
    // We don't need to include the whitespace character in the string view so
    // subtract one byte.
    const size_t port_str_len = read_bytes - 1;
    CHECK(base::IsAsciiWhitespace(port_str[port_str_len]));
    auto port_str_view =
        base::as_string_view(base::span(port_str).first(port_str_len));
    unsigned u_port;
    CHECK(base::StringToUint(port_str_view, &u_port)) << port_str_view;
    port = base::checked_cast<uint16_t>(u_port);

    if (net::IsPortAllowedForScheme(port, "wss")) {
      break;
    }
    LOG(INFO) << "Port " << port << " not allowed. Trying again.";

    // The kernel randomly picked a port that Chromium will refuse to connect
    // to. Try again.
    enclave_process->Terminate(/*exit_code=*/1, /*wait=*/false);
  }

  return std::make_pair(std::move(*enclave_process), port);
}

device::enclave::ScopedEnclaveOverride TestWebAuthnEnclaveIdentity(
    uint16_t port) {
  constexpr std::array<uint8_t, device::kP256X962Length> kTestPublicKey = {
      0x04, 0x6b, 0x17, 0xd1, 0xf2, 0xe1, 0x2c, 0x42, 0x47, 0xf8, 0xbc,
      0xe6, 0xe5, 0x63, 0xa4, 0x40, 0xf2, 0x77, 0x03, 0x7d, 0x81, 0x2d,
      0xeb, 0x33, 0xa0, 0xf4, 0xa1, 0x39, 0x45, 0xd8, 0x98, 0xc2, 0x96,
      0x4f, 0xe3, 0x42, 0xe2, 0xfe, 0x1a, 0x7f, 0x9b, 0x8e, 0xe7, 0xeb,
      0x4a, 0x7c, 0x0f, 0x9e, 0x16, 0x2b, 0xce, 0x33, 0x57, 0x6b, 0x31,
      0x5e, 0xce, 0xcb, 0xb6, 0x40, 0x68, 0x37, 0xbf, 0x51, 0xf5,
  };
  const std::string url = "ws://127.0.0.1:" + base::NumberToString(port);
  device::enclave::EnclaveIdentity identity;
  identity.url = GURL(url);
  identity.public_key = kTestPublicKey;

  return device::enclave::ScopedEnclaveOverride(std::move(identity));
}
