// Copyright 2015 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 "components/browser_watcher/window_hang_monitor_win.h"

#include <memory>
#include <utility>

#include "base/base_paths.h"
#include "base/base_switches.h"
#include "base/command_line.h"
#include "base/message_loop/message_loop.h"
#include "base/message_loop/message_loop_current.h"
#include "base/path_service.h"
#include "base/process/launch.h"
#include "base/process/process.h"
#include "base/strings/string_number_conversions.h"
#include "base/synchronization/waitable_event.h"
#include "base/test/multiprocess_test.h"
#include "base/threading/thread.h"
#include "base/win/message_window.h"
#include "base/win/scoped_handle.h"
#include "base/win/win_util.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/multiprocess_func_list.h"

namespace browser_watcher {

namespace {

const char kChildReadPipeSwitch[] = "child_read_pipe";
const char kChildWritePipeSwitch[] = "child_write_pipe";

// Signals used for IPC between the monitor process and the monitor.
enum IPCSignal {
  IPC_SIGNAL_INVALID,
  IPC_SIGNAL_READY,
  IPC_SIGNAL_TERMINATE_PROCESS,
  IPC_SIGNAL_CREATE_MESSAGE_WINDOW,
  IPC_SIGNAL_DELETE_MESSAGE_WINDOW,
  IPC_SIGNAL_HANG_MESSAGE_WINDOW,
};

// Sends |ipc_signal| through the |write_pipe|.
bool SendPipeSignal(HANDLE write_pipe, IPCSignal ipc_signal) {
  DWORD bytes_written = 0;
  if (!WriteFile(write_pipe, &ipc_signal, sizeof(ipc_signal), &bytes_written,
                 nullptr))
    return false;

  return bytes_written == sizeof(ipc_signal);
}

// Blocks on |read_pipe| until a signal is received into |ipc_signal|.
bool WaitForPipeSignal(HANDLE read_pipe, IPCSignal* ipc_signal) {
  CHECK(ipc_signal);
  DWORD bytes_read = 0;
  if (!ReadFile(read_pipe, ipc_signal, sizeof(*ipc_signal), &bytes_read,
                nullptr))
    return false;

  return bytes_read == sizeof(*ipc_signal);
}

// Blocks on |read_pipe| until a signal is received and returns true if it
// matches |expected_ipc_signal|.
bool WaitForSpecificPipeSignal(HANDLE read_pipe,
                               IPCSignal expected_ipc_signal) {
  IPCSignal received_signal = IPC_SIGNAL_INVALID;
  return WaitForPipeSignal(read_pipe, &received_signal) &&
         received_signal == expected_ipc_signal;
}

// Appends |handle| as a command line switch.
void AppendSwitchHandle(base::CommandLine* command_line,
                        std::string switch_name,
                        HANDLE handle) {
  command_line->AppendSwitchASCII(
      switch_name, base::UintToString(base::win::HandleToUint32(handle)));
}

// Retrieves the |handle| associated to |switch_name| from the command line.
HANDLE GetSwitchValueHandle(base::CommandLine* command_line,
                            std::string switch_name) {
  std::string switch_string = command_line->GetSwitchValueASCII(switch_name);
  unsigned int switch_uint = 0;
  if (switch_string.empty() ||
      !base::StringToUint(switch_string, &switch_uint)) {
    DLOG(ERROR) << "Missing or invalid " << switch_name << " argument.";
    return nullptr;
  }
  return reinterpret_cast<HANDLE>(switch_uint);
}

// An instance of this class lives in the monitored process and receives signals
// and executes their associated function.
class MonitoredProcessClient {
 public:
  MonitoredProcessClient()
      : message_window_thread_("Message window thread"),
        hang_event_(base::WaitableEvent::ResetPolicy::MANUAL,
                    base::WaitableEvent::InitialState::NOT_SIGNALED) {
    base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();

    read_pipe_.Set(GetSwitchValueHandle(command_line, kChildReadPipeSwitch));
    write_pipe_.Set(GetSwitchValueHandle(command_line, kChildWritePipeSwitch));
  }

  ~MonitoredProcessClient() {
    if (message_window_thread_.IsRunning()) {
      DeleteMessageWindow();
    }
  }

  void RunEventLoop() {
    bool running = true;
    IPCSignal ipc_signal = IPC_SIGNAL_INVALID;
    while (running) {
      CHECK(WaitForPipeSignal(read_pipe_.Get(), &ipc_signal));
      switch (ipc_signal) {
        // The parent process should never send those.
        case IPC_SIGNAL_INVALID:
        case IPC_SIGNAL_READY:
          CHECK(false);
          break;
        case IPC_SIGNAL_TERMINATE_PROCESS:
          running = false;
          break;
        case IPC_SIGNAL_CREATE_MESSAGE_WINDOW:
          CreateMessageWindow();
          break;
        case IPC_SIGNAL_DELETE_MESSAGE_WINDOW:
          DeleteMessageWindow();
          break;
        case IPC_SIGNAL_HANG_MESSAGE_WINDOW:
          HangMessageWindow();
          break;
      }
      SendSignalToParent(IPC_SIGNAL_READY);
    }
  }

  // Creates a thread then creates the message window on it.
  void CreateMessageWindow() {
    ASSERT_TRUE(message_window_thread_.StartWithOptions(
        base::Thread::Options(base::MessageLoop::TYPE_UI, 0)));

    bool succeeded = false;
    base::WaitableEvent created(
        base::WaitableEvent::ResetPolicy::MANUAL,
        base::WaitableEvent::InitialState::NOT_SIGNALED);
    ASSERT_TRUE(message_window_thread_.task_runner()->PostTask(
        FROM_HERE,
        base::Bind(&MonitoredProcessClient::CreateMessageWindowInWorkerThread,
                   base::Unretained(this), &succeeded, &created)));
    created.Wait();
    ASSERT_TRUE(succeeded);
  }

  // Creates a thread then creates the message window on it.
  void HangMessageWindow() {
    message_window_thread_.task_runner()->PostTask(
        FROM_HERE,
        base::Bind(&base::WaitableEvent::Wait, base::Unretained(&hang_event_)));
  }

  bool SendSignalToParent(IPCSignal ipc_signal) {
    return SendPipeSignal(write_pipe_.Get(), ipc_signal);
  }

 private:
  bool EmptyMessageCallback(UINT message,
                            WPARAM wparam,
                            LPARAM lparam,
                            LRESULT* result) {
    EXPECT_EQ(message_window_thread_.message_loop(),
              base::MessageLoopCurrent::Get());
    return false;  // Pass through to DefWindowProc.
  }

  void CreateMessageWindowInWorkerThread(bool* success,
                                         base::WaitableEvent* created) {
    CHECK(created);

    // As an alternative to checking if the name of the message window is the
    // user data directory, the hang watcher verifies that the window name is an
    // existing directory. DIR_CURRENT is used to meet this constraint.
    base::FilePath existing_dir;
    CHECK(base::PathService::Get(base::DIR_CURRENT, &existing_dir));

    message_window_.reset(new base::win::MessageWindow);
    *success = message_window_->CreateNamed(
        base::Bind(&MonitoredProcessClient::EmptyMessageCallback,
                   base::Unretained(this)),
        existing_dir.value());
    created->Signal();
  }

  void DeleteMessageWindow() {
    base::WaitableEvent deleted(
        base::WaitableEvent::ResetPolicy::MANUAL,
        base::WaitableEvent::InitialState::NOT_SIGNALED);
    message_window_thread_.task_runner()->PostTask(
        FROM_HERE,
        base::Bind(&MonitoredProcessClient::DeleteMessageWindowInWorkerThread,
                   base::Unretained(this), &deleted));
    deleted.Wait();

    message_window_thread_.Stop();
  }

  void DeleteMessageWindowInWorkerThread(base::WaitableEvent* deleted) {
    CHECK(deleted);
    message_window_.reset();
    deleted->Signal();
  }

  // The thread that holds the message window.
  base::Thread message_window_thread_;
  std::unique_ptr<base::win::MessageWindow> message_window_;

  // Event used to hang the message window.
  base::WaitableEvent hang_event_;

  // Anonymous pipe handles for IPC with the parent process.
  base::win::ScopedHandle read_pipe_;
  base::win::ScopedHandle write_pipe_;

  DISALLOW_COPY_AND_ASSIGN(MonitoredProcessClient);
};

// The monitored process main function.
MULTIPROCESS_TEST_MAIN(MonitoredProcess) {
  MonitoredProcessClient monitored_process_client;
  CHECK(monitored_process_client.SendSignalToParent(IPC_SIGNAL_READY));

  monitored_process_client.RunEventLoop();

  return 0;
}

// Manages a WindowHangMonitor that lives on a background thread.
class HangMonitorThread {
 public:
  // Instantiates the background thread.
  HangMonitorThread()
      : event_(WindowHangMonitor::WINDOW_NOT_FOUND),
        event_received_(base::WaitableEvent::ResetPolicy::AUTOMATIC,
                        base::WaitableEvent::InitialState::NOT_SIGNALED),
        thread_("Hang monitor thread") {}

  ~HangMonitorThread() {
    if (hang_monitor_)
      DestroyWatcher();
  }

  // Starts the background thread and the monitor to observe Chrome message
  // window for |process|. Blocks until the monitor has been initialized.
  bool Start(base::Process process) {
    if (!thread_.StartWithOptions(
            base::Thread::Options(base::MessageLoop::TYPE_UI, 0))) {
      return false;
    }

    base::WaitableEvent complete(
        base::WaitableEvent::ResetPolicy::AUTOMATIC,
        base::WaitableEvent::InitialState::NOT_SIGNALED);
    if (!thread_.task_runner()->PostTask(
            FROM_HERE,
            base::BindOnce(&HangMonitorThread::StartupOnThread,
                           base::Unretained(this), std::move(process),
                           base::Unretained(&complete)))) {
      return false;
    }

    complete.Wait();

    return true;
  }

  // Returns true if a window event is detected within |timeout|.
  bool TimedWaitForEvent(base::TimeDelta timeout) {
    return event_received_.TimedWait(timeout);
  }

  // Blocks indefinitely for a window event and returns it.
  WindowHangMonitor::WindowEvent WaitForEvent() {
    event_received_.Wait();
    return event_;
  }

 private:
  // Destroys the monitor and stops the background thread. Blocks until the
  // operation completes.
  void DestroyWatcher() {
    thread_.task_runner()->PostTask(
        FROM_HERE, base::Bind(&HangMonitorThread::ShutdownOnThread,
                              base::Unretained(this)));
    // This will block until the above-posted task completes.
    thread_.Stop();
  }

  // Invoked when the monitor signals an event. Unblocks a call to
  // TimedWaitForEvent or WaitForEvent.
  void EventCallback(WindowHangMonitor::WindowEvent event) {
    if (event_received_.IsSignaled())
      ADD_FAILURE() << "Multiple calls to EventCallback.";
    event_ = event;
    event_received_.Signal();
  }

  // Initializes the WindowHangMonitor to observe the Chrome message window for
  // |process|. Signals |complete| when done.
  void StartupOnThread(base::Process process, base::WaitableEvent* complete) {
    hang_monitor_.reset(new WindowHangMonitor(
        base::TimeDelta::FromMilliseconds(100),
        base::TimeDelta::FromMilliseconds(100),
        base::Bind(&HangMonitorThread::EventCallback, base::Unretained(this))));
    hang_monitor_->Initialize(std::move(process));
    complete->Signal();
  }

  // Destroys the WindowHangMonitor.
  void ShutdownOnThread() { hang_monitor_.reset(); }

  // The detected event. Invalid if |event_received_| has not been signaled.
  WindowHangMonitor::WindowEvent event_;
  // Indicates that |event_| has been assigned in response to a callback from
  // the WindowHangMonitor.
  base::WaitableEvent event_received_;
  // The WindowHangMonitor under test.
  std::unique_ptr<WindowHangMonitor> hang_monitor_;
  // The background thread.
  base::Thread thread_;

  DISALLOW_COPY_AND_ASSIGN(HangMonitorThread);
};

class WindowHangMonitorTest : public testing::Test {
 public:
  WindowHangMonitorTest() {}

  ~WindowHangMonitorTest() override {
    // Close process if running.
    monitored_process_.Terminate(1, false);
  }

  // Starts a child process that will be monitored. Handles to anonymous pipes
  // are passed to the command line to provide a way to communicate with the
  // child process. This function blocks until IPC_SIGNAL_READY is received.
  bool StartMonitoredProcess() {
    HANDLE child_read_pipe = nullptr;
    HANDLE child_write_pipe = nullptr;
    if (!CreatePipes(&child_read_pipe, &child_write_pipe))
      return false;

    base::CommandLine command_line =
        base::GetMultiProcessTestChildBaseCommandLine();
    command_line.AppendSwitchASCII(switches::kTestChildProcess,
                                   "MonitoredProcess");

    AppendSwitchHandle(&command_line, kChildReadPipeSwitch, child_read_pipe);
    AppendSwitchHandle(&command_line, kChildWritePipeSwitch, child_write_pipe);

    base::LaunchOptions options = {};
    // TODO(brettw) bug 748258: Share only explicit handles.
    options.inherit_mode = base::LaunchOptions::Inherit::kAll;
    monitored_process_ = base::LaunchProcess(command_line, options);
    if (!monitored_process_.IsValid())
      return false;

    return WaitForSignal(IPC_SIGNAL_READY);
  }

  void StartHangMonitor() {
    monitor_thread_.Start(monitored_process_.Duplicate());
  }

  // Sends the |ipc_signal| to the child process and wait for a IPC_SIGNAL_READY
  // response.
  bool SendSignal(IPCSignal ipc_signal) {
    if (!SendPipeSignal(write_pipe_.Get(), ipc_signal))
      return false;

    return WaitForSignal(IPC_SIGNAL_READY);
  }

  // Blocks until |ipc_signal| is received from the child process.
  bool WaitForSignal(IPCSignal ipc_signal) {
    return WaitForSpecificPipeSignal(read_pipe_.Get(), ipc_signal);
  }

  HangMonitorThread& monitor_thread() { return monitor_thread_; }

 private:
  // Creates pipes for IPC with the child process.
  bool CreatePipes(HANDLE* child_read_pipe, HANDLE* child_write_pipe) {
    CHECK(child_read_pipe);
    CHECK(child_write_pipe);
    SECURITY_ATTRIBUTES security_attributes = {
        sizeof(SECURITY_ATTRIBUTES), nullptr, true /* inherit handles */};

    HANDLE parent_read_pipe = nullptr;
    if (!CreatePipe(&parent_read_pipe, child_write_pipe, &security_attributes,
                    0)) {
      return false;
    }
    read_pipe_.Set(parent_read_pipe);

    HANDLE parent_write_pipe = nullptr;
    if (!CreatePipe(child_read_pipe, &parent_write_pipe, &security_attributes,
                    0)) {
      return false;
    }
    write_pipe_.Set(parent_write_pipe);
    return true;
  }

  // The thread that monitors the child process.
  HangMonitorThread monitor_thread_;
  // The process that is monitored.
  base::Process monitored_process_;

  // Anonymous pipe handles for IPC with the monitored process.
  base::win::ScopedHandle read_pipe_;
  base::win::ScopedHandle write_pipe_;

  DISALLOW_COPY_AND_ASSIGN(WindowHangMonitorTest);
};

}  // namespace

TEST_F(WindowHangMonitorTest, WindowNotFound) {
  ASSERT_TRUE(StartMonitoredProcess());

  StartHangMonitor();

  ASSERT_TRUE(SendSignal(IPC_SIGNAL_TERMINATE_PROCESS));

  EXPECT_EQ(WindowHangMonitor::WINDOW_NOT_FOUND,
            monitor_thread().WaitForEvent());
}

TEST_F(WindowHangMonitorTest, WindowVanished) {
  ASSERT_TRUE(StartMonitoredProcess());

  ASSERT_TRUE(SendSignal(IPC_SIGNAL_CREATE_MESSAGE_WINDOW));

  StartHangMonitor();

  ASSERT_FALSE(monitor_thread().TimedWaitForEvent(
      base::TimeDelta::FromMilliseconds(250)));

  ASSERT_TRUE(SendSignal(IPC_SIGNAL_DELETE_MESSAGE_WINDOW));

  EXPECT_EQ(WindowHangMonitor::WINDOW_VANISHED,
            monitor_thread().WaitForEvent());

  ASSERT_TRUE(SendSignal(IPC_SIGNAL_TERMINATE_PROCESS));
}

TEST_F(WindowHangMonitorTest, WindowHang) {
  ASSERT_TRUE(StartMonitoredProcess());

  ASSERT_TRUE(SendSignal(IPC_SIGNAL_CREATE_MESSAGE_WINDOW));

  StartHangMonitor();

  ASSERT_FALSE(monitor_thread().TimedWaitForEvent(
      base::TimeDelta::FromMilliseconds(250)));

  ASSERT_TRUE(SendSignal(IPC_SIGNAL_HANG_MESSAGE_WINDOW));

  EXPECT_EQ(WindowHangMonitor::WINDOW_HUNG,
            monitor_thread().WaitForEvent());

  ASSERT_TRUE(SendSignal(IPC_SIGNAL_TERMINATE_PROCESS));
}

}  // namespace browser_watcher
