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

#include "mojo/public/c/system/trap.h"

#include <stdint.h>

#include <array>
#include <map>
#include <memory>
#include <set>

#include "base/compiler_specific.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/memory/ptr_util.h"
#include "base/synchronization/waitable_event.h"
#include "base/test/bind.h"
#include "base/threading/platform_thread.h"
#include "base/threading/simple_thread.h"
#include "base/time/time.h"
#include "mojo/core/test/mojo_test_base.h"
#include "mojo/public/c/system/data_pipe.h"
#include "mojo/public/c/system/types.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace mojo {
namespace core {
namespace {

using TrapTest = test::MojoTestBase;

class TriggerHelper {
 public:
  using ContextCallback = base::RepeatingCallback<void(const MojoTrapEvent&)>;

  TriggerHelper() = default;

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

  ~TriggerHelper() = default;

  MojoResult CreateTrap(MojoHandle* handle) {
    return MojoCreateTrap(&Notify, nullptr, handle);
  }

  template <typename Handler>
  uintptr_t CreateContext(Handler handler) {
    return CreateContextWithCancel(handler, [] {});
  }

  template <typename Handler, typename CancelHandler>
  uintptr_t CreateContextWithCancel(Handler handler,
                                    CancelHandler cancel_handler) {
    auto* context =
        new NotificationContext(base::BindLambdaForTesting(handler));
    context->SetCancelCallback(
        base::BindLambdaForTesting([cancel_handler, context] {
          cancel_handler();
          delete context;
        }));
    return reinterpret_cast<uintptr_t>(context);
  }

 private:
  class NotificationContext {
   public:
    explicit NotificationContext(const ContextCallback& callback)
        : callback_(callback) {}

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

    ~NotificationContext() = default;

    void SetCancelCallback(base::OnceClosure cancel_callback) {
      cancel_callback_ = std::move(cancel_callback);
    }

    void Notify(const MojoTrapEvent& event) {
      if (event.result == MOJO_RESULT_CANCELLED && cancel_callback_) {
        std::move(cancel_callback_).Run();
      } else {
        callback_.Run(event);
      }
    }

   private:
    const ContextCallback callback_;
    base::OnceClosure cancel_callback_;
  };

  static void Notify(const MojoTrapEvent* event) {
    reinterpret_cast<NotificationContext*>(event->trigger_context)
        ->Notify(*event);
  }
};

class ThreadedRunner : public base::SimpleThread {
 public:
  explicit ThreadedRunner(base::OnceClosure callback)
      : SimpleThread("ThreadedRunner"), callback_(std::move(callback)) {}

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

  ~ThreadedRunner() override = default;

  void Run() override { std::move(callback_).Run(); }

 private:
  base::OnceClosure callback_;
};

void ExpectNoNotification(const MojoTrapEvent* event) {
  NOTREACHED();
}

void ExpectOnlyCancel(const MojoTrapEvent* event) {
  EXPECT_EQ(event->result, MOJO_RESULT_CANCELLED);
}

TEST_F(TrapTest, InvalidArguments) {
  EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
            MojoCreateTrap(&ExpectNoNotification, nullptr, nullptr));
  MojoHandle t;
  EXPECT_EQ(MOJO_RESULT_OK, MojoCreateTrap(&ExpectNoNotification, nullptr, &t));

  // Try to add triggers for handles which don't raise trappable signals.
  EXPECT_EQ(
      MOJO_RESULT_INVALID_ARGUMENT,
      MojoAddTrigger(t, t, MOJO_HANDLE_SIGNAL_READABLE,
                     MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED, 0, nullptr));
  MojoHandle buffer_handle = CreateBuffer(42);
  EXPECT_EQ(
      MOJO_RESULT_INVALID_ARGUMENT,
      MojoAddTrigger(t, buffer_handle, MOJO_HANDLE_SIGNAL_READABLE,
                     MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED, 0, nullptr));

  // Try to remove a trigger on a non-trap handle.
  EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
            MojoRemoveTrigger(buffer_handle, 0, nullptr));

  // Try to arm an invalid or non-trap handle.
  EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
            MojoArmTrap(MOJO_HANDLE_INVALID, nullptr, nullptr, nullptr));
  EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
            MojoArmTrap(buffer_handle, nullptr, nullptr, nullptr));
  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(buffer_handle));

  // Try to arm with a non-null count but a null output buffer.
  uint32_t num_blocking_events = 1;
  EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
            MojoArmTrap(t, nullptr, &num_blocking_events, nullptr));

  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(t));
}

TEST_F(TrapTest, TrapMessagePipeReadable) {
  MojoHandle a, b;
  CreateMessagePipe(&a, &b);

  base::WaitableEvent wait(base::WaitableEvent::ResetPolicy::MANUAL,
                           base::WaitableEvent::InitialState::NOT_SIGNALED);
  TriggerHelper helper;
  int num_expected_notifications = 1;
  const uintptr_t readable_a_context =
      helper.CreateContext([&](const MojoTrapEvent& event) {
        EXPECT_GT(num_expected_notifications, 0);
        num_expected_notifications -= 1;

        EXPECT_EQ(MOJO_RESULT_OK, event.result);
        wait.Signal();
      });

  MojoHandle t;
  EXPECT_EQ(MOJO_RESULT_OK, helper.CreateTrap(&t));
  EXPECT_EQ(MOJO_RESULT_OK,
            MojoAddTrigger(t, a, MOJO_HANDLE_SIGNAL_READABLE,
                           MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED,
                           readable_a_context, nullptr));
  EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(t, nullptr, nullptr, nullptr));

  const char kMessage1[] = "hey hey hey hey";
  const char kMessage2[] = "i said hey";
  const char kMessage3[] = "what's goin' on?";

  // Writing to |b| multiple times should notify exactly once.
  WriteMessage(b, kMessage1);
  WriteMessage(b, kMessage2);
  wait.Wait();

  // This also shouldn't fire a notification; the trap is still disarmed.
  WriteMessage(b, kMessage3);

  // Arming should fail with relevant information.
  constexpr size_t kMaxBlockingEvents = 3;
  uint32_t num_blocking_events = kMaxBlockingEvents;
  MojoTrapEvent blocking_events[kMaxBlockingEvents] = {
      {sizeof(blocking_events[0])},
      {sizeof(blocking_events[0])},
      {sizeof(blocking_events[0])}};
  EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
            MojoArmTrap(t, nullptr, &num_blocking_events, &blocking_events[0]));
  EXPECT_EQ(1u, num_blocking_events);
  EXPECT_EQ(readable_a_context, blocking_events[0].trigger_context);
  EXPECT_EQ(MOJO_RESULT_OK, blocking_events[0].result);

  // Flush the three messages from above.
  EXPECT_EQ(kMessage1, ReadMessage(a));
  EXPECT_EQ(kMessage2, ReadMessage(a));
  EXPECT_EQ(kMessage3, ReadMessage(a));

  // Now we can rearm the trap.
  EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(t, nullptr, nullptr, nullptr));

  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(t));
  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b));
  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a));
}

TEST_F(TrapTest, CloseWatchedMessagePipeHandle) {
  MojoHandle a, b;
  CreateMessagePipe(&a, &b);

  base::WaitableEvent wait(base::WaitableEvent::ResetPolicy::MANUAL,
                           base::WaitableEvent::InitialState::NOT_SIGNALED);
  TriggerHelper helper;
  const uintptr_t readable_a_context = helper.CreateContextWithCancel(
      [](const MojoTrapEvent&) {}, [&] { wait.Signal(); });

  MojoHandle t;
  EXPECT_EQ(MOJO_RESULT_OK, helper.CreateTrap(&t));
  EXPECT_EQ(MOJO_RESULT_OK,
            MojoAddTrigger(t, a, MOJO_HANDLE_SIGNAL_READABLE,
                           MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED,
                           readable_a_context, nullptr));

  // Test that closing a watched handle fires an appropriate notification, even
  // when the trap is unarmed.
  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a));
  wait.Wait();

  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b));
  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(t));
}

TEST_F(TrapTest, CloseWatchedMessagePipeHandlePeer) {
  MojoHandle a, b;
  CreateMessagePipe(&a, &b);

  base::WaitableEvent wait(base::WaitableEvent::ResetPolicy::MANUAL,
                           base::WaitableEvent::InitialState::NOT_SIGNALED);
  TriggerHelper helper;
  const uintptr_t readable_a_context =
      helper.CreateContext([&](const MojoTrapEvent& event) {
        EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, event.result);
        wait.Signal();
      });

  MojoHandle t;
  EXPECT_EQ(MOJO_RESULT_OK, helper.CreateTrap(&t));
  EXPECT_EQ(MOJO_RESULT_OK,
            MojoAddTrigger(t, a, MOJO_HANDLE_SIGNAL_READABLE,
                           MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED,
                           readable_a_context, nullptr));

  // Test that closing a watched handle's peer with an armed trap fires an
  // appropriate notification.
  EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(t, nullptr, nullptr, nullptr));
  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b));
  wait.Wait();

  // And now arming should fail with correct information about |a|'s state.
  constexpr size_t kMaxBlockingEvents = 3;
  uint32_t num_blocking_events = kMaxBlockingEvents;
  MojoTrapEvent blocking_events[kMaxBlockingEvents] = {
      {sizeof(blocking_events[0])},
      {sizeof(blocking_events[0])},
      {sizeof(blocking_events[0])}};
  EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
            MojoArmTrap(t, nullptr, &num_blocking_events, &blocking_events[0]));
  EXPECT_EQ(1u, num_blocking_events);
  EXPECT_EQ(readable_a_context, blocking_events[0].trigger_context);
  EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, blocking_events[0].result);
  EXPECT_TRUE(blocking_events[0].signals_state.satisfied_signals &
              MOJO_HANDLE_SIGNAL_PEER_CLOSED);
  EXPECT_FALSE(blocking_events[0].signals_state.satisfiable_signals &
               MOJO_HANDLE_SIGNAL_READABLE);

  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(t));
  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a));
}

TEST_F(TrapTest, TrapDataPipeConsumerReadable) {
  constexpr size_t kTestPipeCapacity = 64;
  MojoHandle producer, consumer;
  CreateDataPipe(&producer, &consumer, kTestPipeCapacity);

  base::WaitableEvent wait(base::WaitableEvent::ResetPolicy::MANUAL,
                           base::WaitableEvent::InitialState::NOT_SIGNALED);
  TriggerHelper helper;
  int num_expected_notifications = 1;
  const uintptr_t readable_consumer_context =
      helper.CreateContext([&](const MojoTrapEvent& event) {
        EXPECT_GT(num_expected_notifications, 0);
        num_expected_notifications -= 1;

        EXPECT_EQ(MOJO_RESULT_OK, event.result);
        wait.Signal();
      });

  MojoHandle t;
  EXPECT_EQ(MOJO_RESULT_OK, helper.CreateTrap(&t));
  EXPECT_EQ(MOJO_RESULT_OK,
            MojoAddTrigger(t, consumer, MOJO_HANDLE_SIGNAL_READABLE,
                           MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED,
                           readable_consumer_context, nullptr));
  EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(t, nullptr, nullptr, nullptr));

  const char kMessage1[] = "hey hey hey hey";
  const char kMessage2[] = "i said hey";
  const char kMessage3[] = "what's goin' on?";

  // Writing to |producer| multiple times should notify exactly once.
  WriteData(producer, kMessage1);
  WriteData(producer, kMessage2);
  wait.Wait();

  // This also shouldn't fire a notification; the trap is still disarmed.
  WriteData(producer, kMessage3);

  // Arming should fail with relevant information.
  constexpr size_t kMaxBlockingEvents = 3;
  uint32_t num_blocking_events = kMaxBlockingEvents;
  MojoTrapEvent blocking_events[kMaxBlockingEvents] = {
      {sizeof(blocking_events[0])},
      {sizeof(blocking_events[0])},
      {sizeof(blocking_events[0])}};
  EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
            MojoArmTrap(t, nullptr, &num_blocking_events, &blocking_events[0]));
  EXPECT_EQ(1u, num_blocking_events);
  EXPECT_EQ(readable_consumer_context, blocking_events[0].trigger_context);
  EXPECT_EQ(MOJO_RESULT_OK, blocking_events[0].result);

  // Flush the three messages from above.
  EXPECT_EQ(kMessage1, ReadData(consumer, sizeof(kMessage1) - 1));
  EXPECT_EQ(kMessage2, ReadData(consumer, sizeof(kMessage2) - 1));
  EXPECT_EQ(kMessage3, ReadData(consumer, sizeof(kMessage3) - 1));

  // Now we can rearm the trap.
  EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(t, nullptr, nullptr, nullptr));

  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(t));
  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(producer));
  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(consumer));
}

TEST_F(TrapTest, TrapDataPipeConsumerNewDataReadable) {
  constexpr size_t kTestPipeCapacity = 64;
  MojoHandle producer, consumer;
  CreateDataPipe(&producer, &consumer, kTestPipeCapacity);

  base::WaitableEvent wait(base::WaitableEvent::ResetPolicy::MANUAL,
                           base::WaitableEvent::InitialState::NOT_SIGNALED);
  TriggerHelper helper;
  int num_new_data_notifications = 0;
  const uintptr_t new_data_context =
      helper.CreateContext([&](const MojoTrapEvent& event) {
        num_new_data_notifications += 1;

        EXPECT_EQ(MOJO_RESULT_OK, event.result);
        wait.Signal();
      });

  MojoHandle t;
  EXPECT_EQ(MOJO_RESULT_OK, helper.CreateTrap(&t));
  EXPECT_EQ(MOJO_RESULT_OK,
            MojoAddTrigger(t, consumer, MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE,
                           MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED,
                           new_data_context, nullptr));
  EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(t, nullptr, nullptr, nullptr));

  const char kMessage1[] = "hey hey hey hey";
  const char kMessage2[] = "i said hey";
  const char kMessage3[] = "what's goin' on?";

  // Writing to |producer| multiple times should notify exactly once.
  WriteData(producer, kMessage1);
  WriteData(producer, kMessage2);
  wait.Wait();

  // This also shouldn't fire a notification; the trap is still disarmed.
  WriteData(producer, kMessage3);

  // Arming should fail with relevant information.
  constexpr size_t kMaxBlockingEvents = 3;
  uint32_t num_blocking_events = kMaxBlockingEvents;
  MojoTrapEvent blocking_events[kMaxBlockingEvents] = {
      {sizeof(blocking_events[0])},
      {sizeof(blocking_events[0])},
      {sizeof(blocking_events[0])}};
  EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
            MojoArmTrap(t, nullptr, &num_blocking_events, &blocking_events[0]));
  EXPECT_EQ(1u, num_blocking_events);
  EXPECT_EQ(new_data_context, blocking_events[0].trigger_context);
  EXPECT_EQ(MOJO_RESULT_OK, blocking_events[0].result);

  // Attempt to read more data than is available. Should fail but clear the
  // NEW_DATA_READABLE signal.
  char large_buffer[512];
  uint32_t large_read_size = 512;
  MojoReadDataOptions options;
  options.struct_size = sizeof(options);
  options.flags = MOJO_READ_DATA_FLAG_ALL_OR_NONE;
  EXPECT_EQ(MOJO_RESULT_OUT_OF_RANGE,
            MojoReadData(consumer, &options, large_buffer, &large_read_size));

  // Attempt to arm again. Should succeed.
  EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(t, nullptr, nullptr, nullptr));

  // Write more data. Should notify.
  wait.Reset();
  WriteData(producer, kMessage1);
  wait.Wait();

  // Reading some data should clear NEW_DATA_READABLE again so we can rearm.
  EXPECT_EQ(kMessage1, ReadData(consumer, sizeof(kMessage1) - 1));

  EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(t, nullptr, nullptr, nullptr));

  EXPECT_EQ(2, num_new_data_notifications);

  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(t));
  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(producer));
  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(consumer));
}

TEST_F(TrapTest, TrapDataPipeProducerWritable) {
  constexpr size_t kTestPipeCapacity = 8;
  MojoHandle producer, consumer;
  CreateDataPipe(&producer, &consumer, kTestPipeCapacity);

  // Half the capacity of the data pipe.
  const char kTestData[] = "aaaa";
  static_assert((sizeof(kTestData) - 1) * 2 == kTestPipeCapacity,
                "Invalid test data for this test.");

  base::WaitableEvent wait(base::WaitableEvent::ResetPolicy::MANUAL,
                           base::WaitableEvent::InitialState::NOT_SIGNALED);
  TriggerHelper helper;
  int num_expected_notifications = 1;
  const uintptr_t writable_producer_context =
      helper.CreateContext([&](const MojoTrapEvent& event) {
        EXPECT_GT(num_expected_notifications, 0);
        num_expected_notifications -= 1;

        EXPECT_EQ(MOJO_RESULT_OK, event.result);
        wait.Signal();
      });

  MojoHandle t;
  EXPECT_EQ(MOJO_RESULT_OK, helper.CreateTrap(&t));
  EXPECT_EQ(MOJO_RESULT_OK,
            MojoAddTrigger(t, producer, MOJO_HANDLE_SIGNAL_WRITABLE,
                           MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED,
                           writable_producer_context, nullptr));

  // The producer is already writable, so arming should fail with relevant
  // information.
  constexpr size_t kMaxBlockingEvents = 3;
  uint32_t num_blocking_events = kMaxBlockingEvents;
  MojoTrapEvent blocking_events[kMaxBlockingEvents] = {
      {sizeof(blocking_events[0])},
      {sizeof(blocking_events[0])},
      {sizeof(blocking_events[0])}};
  EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
            MojoArmTrap(t, nullptr, &num_blocking_events, &blocking_events[0]));
  EXPECT_EQ(1u, num_blocking_events);
  EXPECT_EQ(writable_producer_context, blocking_events[0].trigger_context);
  EXPECT_EQ(MOJO_RESULT_OK, blocking_events[0].result);
  EXPECT_TRUE(blocking_events[0].signals_state.satisfied_signals &
              MOJO_HANDLE_SIGNAL_WRITABLE);

  // Write some data, but don't fill the pipe yet. Arming should fail again.
  WriteData(producer, kTestData);
  EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
            MojoArmTrap(t, nullptr, &num_blocking_events, &blocking_events[0]));
  EXPECT_EQ(1u, num_blocking_events);
  EXPECT_EQ(writable_producer_context, blocking_events[0].trigger_context);
  EXPECT_EQ(MOJO_RESULT_OK, blocking_events[0].result);
  EXPECT_TRUE(blocking_events[0].signals_state.satisfied_signals &
              MOJO_HANDLE_SIGNAL_WRITABLE);

  // Write more data, filling the pipe to capacity. Arming should succeed now.
  WriteData(producer, kTestData);
  EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(t, nullptr, nullptr, nullptr));

  // Now read from the pipe, making the producer writable again. Should notify.
  EXPECT_EQ(kTestData, ReadData(consumer, sizeof(kTestData) - 1));
  wait.Wait();

  // Arming should fail again.
  EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
            MojoArmTrap(t, nullptr, &num_blocking_events, &blocking_events[0]));
  EXPECT_EQ(1u, num_blocking_events);
  EXPECT_EQ(writable_producer_context, blocking_events[0].trigger_context);
  EXPECT_EQ(MOJO_RESULT_OK, blocking_events[0].result);
  EXPECT_TRUE(blocking_events[0].signals_state.satisfied_signals &
              MOJO_HANDLE_SIGNAL_WRITABLE);

  // Fill the pipe once more and arm the trap. Should succeed.
  WriteData(producer, kTestData);
  EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(t, nullptr, nullptr, nullptr));

  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(t));
  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(producer));
  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(consumer));
}

TEST_F(TrapTest, CloseWatchedDataPipeConsumerHandle) {
  constexpr size_t kTestPipeCapacity = 8;
  MojoHandle producer, consumer;
  CreateDataPipe(&producer, &consumer, kTestPipeCapacity);

  base::WaitableEvent wait(base::WaitableEvent::ResetPolicy::MANUAL,
                           base::WaitableEvent::InitialState::NOT_SIGNALED);
  TriggerHelper helper;
  const uintptr_t readable_consumer_context = helper.CreateContextWithCancel(
      [](const MojoTrapEvent&) {}, [&] { wait.Signal(); });

  MojoHandle t;
  EXPECT_EQ(MOJO_RESULT_OK, helper.CreateTrap(&t));
  EXPECT_EQ(MOJO_RESULT_OK,
            MojoAddTrigger(t, consumer, MOJO_HANDLE_SIGNAL_READABLE,
                           MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED,
                           readable_consumer_context, nullptr));

  // Closing the consumer should fire a cancellation notification.
  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(consumer));
  wait.Wait();

  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(producer));
  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(t));
}

TEST_F(TrapTest, CloseWatchedDataPipeConsumerHandlePeer) {
  constexpr size_t kTestPipeCapacity = 8;
  MojoHandle producer, consumer;
  CreateDataPipe(&producer, &consumer, kTestPipeCapacity);

  base::WaitableEvent wait(base::WaitableEvent::ResetPolicy::MANUAL,
                           base::WaitableEvent::InitialState::NOT_SIGNALED);
  TriggerHelper helper;
  const uintptr_t readable_consumer_context =
      helper.CreateContext([&](const MojoTrapEvent& event) {
        EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, event.result);
        wait.Signal();
      });

  MojoHandle t;
  EXPECT_EQ(MOJO_RESULT_OK, helper.CreateTrap(&t));
  EXPECT_EQ(MOJO_RESULT_OK,
            MojoAddTrigger(t, consumer, MOJO_HANDLE_SIGNAL_READABLE,
                           MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED,
                           readable_consumer_context, nullptr));
  EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(t, nullptr, nullptr, nullptr));

  // Closing the producer should fire a notification for an unsatisfiable
  // condition.
  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(producer));
  wait.Wait();

  // Now attempt to rearm and expect appropriate error feedback.
  constexpr size_t kMaxBlockingEvents = 3;
  uint32_t num_blocking_events = kMaxBlockingEvents;
  MojoTrapEvent blocking_events[kMaxBlockingEvents] = {
      {sizeof(blocking_events[0])},
      {sizeof(blocking_events[0])},
      {sizeof(blocking_events[0])}};
  EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
            MojoArmTrap(t, nullptr, &num_blocking_events, &blocking_events[0]));
  EXPECT_EQ(1u, num_blocking_events);
  EXPECT_EQ(readable_consumer_context, blocking_events[0].trigger_context);
  EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, blocking_events[0].result);
  EXPECT_FALSE(blocking_events[0].signals_state.satisfiable_signals &
               MOJO_HANDLE_SIGNAL_READABLE);

  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(t));
  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(consumer));
}

TEST_F(TrapTest, CloseWatchedDataPipeProducerHandle) {
  constexpr size_t kTestPipeCapacity = 8;
  MojoHandle producer, consumer;
  CreateDataPipe(&producer, &consumer, kTestPipeCapacity);

  base::WaitableEvent wait(base::WaitableEvent::ResetPolicy::MANUAL,
                           base::WaitableEvent::InitialState::NOT_SIGNALED);
  TriggerHelper helper;
  const uintptr_t writable_producer_context = helper.CreateContextWithCancel(
      [](const MojoTrapEvent&) {}, [&] { wait.Signal(); });

  MojoHandle t;
  EXPECT_EQ(MOJO_RESULT_OK, helper.CreateTrap(&t));
  EXPECT_EQ(MOJO_RESULT_OK,
            MojoAddTrigger(t, producer, MOJO_HANDLE_SIGNAL_WRITABLE,
                           MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED,
                           writable_producer_context, nullptr));

  // Closing the consumer should fire a cancellation notification.
  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(producer));
  wait.Wait();

  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(consumer));
  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(t));
}

TEST_F(TrapTest, CloseWatchedDataPipeProducerHandlePeer) {
  constexpr size_t kTestPipeCapacity = 8;
  MojoHandle producer, consumer;
  CreateDataPipe(&producer, &consumer, kTestPipeCapacity);

  const char kTestMessageFullCapacity[] = "xxxxxxxx";
  static_assert(sizeof(kTestMessageFullCapacity) - 1 == kTestPipeCapacity,
                "Invalid test message size for this test.");

  // Make the pipe unwritable initially.
  WriteData(producer, kTestMessageFullCapacity);

  base::WaitableEvent wait(base::WaitableEvent::ResetPolicy::MANUAL,
                           base::WaitableEvent::InitialState::NOT_SIGNALED);
  TriggerHelper helper;
  const uintptr_t writable_producer_context =
      helper.CreateContext([&](const MojoTrapEvent& event) {
        EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, event.result);
        wait.Signal();
      });

  MojoHandle t;
  EXPECT_EQ(MOJO_RESULT_OK, helper.CreateTrap(&t));
  EXPECT_EQ(MOJO_RESULT_OK,
            MojoAddTrigger(t, producer, MOJO_HANDLE_SIGNAL_WRITABLE,
                           MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED,
                           writable_producer_context, nullptr));
  EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(t, nullptr, nullptr, nullptr));

  // Closing the consumer should fire a notification for an unsatisfiable
  // condition, as the full data pipe can never be read from again and is
  // therefore permanently full and unwritable.
  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(consumer));
  wait.Wait();

  // Now attempt to rearm and expect appropriate error feedback.
  constexpr size_t kMaxBlockingEvents = 3;
  uint32_t num_blocking_events = kMaxBlockingEvents;
  MojoTrapEvent blocking_events[kMaxBlockingEvents] = {
      {sizeof(blocking_events[0])},
      {sizeof(blocking_events[0])},
      {sizeof(blocking_events[0])}};
  EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
            MojoArmTrap(t, nullptr, &num_blocking_events, &blocking_events[0]));
  EXPECT_EQ(1u, num_blocking_events);
  EXPECT_EQ(writable_producer_context, blocking_events[0].trigger_context);
  EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, blocking_events[0].result);
  EXPECT_FALSE(blocking_events[0].signals_state.satisfiable_signals &
               MOJO_HANDLE_SIGNAL_WRITABLE);

  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(t));
  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(producer));
}

TEST_F(TrapTest, ArmWithNoTriggers) {
  MojoHandle t;
  EXPECT_EQ(MOJO_RESULT_OK, MojoCreateTrap(&ExpectNoNotification, nullptr, &t));
  EXPECT_EQ(MOJO_RESULT_NOT_FOUND, MojoArmTrap(t, nullptr, nullptr, nullptr));
  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(t));
}

TEST_F(TrapTest, DuplicateTriggerContext) {
  MojoHandle a, b;
  CreateMessagePipe(&a, &b);

  MojoHandle t;
  EXPECT_EQ(MOJO_RESULT_OK, MojoCreateTrap(&ExpectOnlyCancel, nullptr, &t));
  EXPECT_EQ(
      MOJO_RESULT_OK,
      MojoAddTrigger(t, a, MOJO_HANDLE_SIGNAL_READABLE,
                     MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED, 0, nullptr));
  EXPECT_EQ(
      MOJO_RESULT_ALREADY_EXISTS,
      MojoAddTrigger(t, b, MOJO_HANDLE_SIGNAL_READABLE,
                     MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED, 0, nullptr));

  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(t));
  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a));
  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b));
}

TEST_F(TrapTest, RemoveUnknownTrigger) {
  MojoHandle t;
  EXPECT_EQ(MOJO_RESULT_OK, MojoCreateTrap(&ExpectNoNotification, nullptr, &t));
  EXPECT_EQ(MOJO_RESULT_NOT_FOUND, MojoRemoveTrigger(t, 1234, nullptr));
  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(t));
}

TEST_F(TrapTest, ArmWithTriggerConditionAlreadySatisfied) {
  MojoHandle a, b;
  CreateMessagePipe(&a, &b);

  MojoHandle t;
  EXPECT_EQ(MOJO_RESULT_OK, MojoCreateTrap(&ExpectOnlyCancel, nullptr, &t));
  EXPECT_EQ(
      MOJO_RESULT_OK,
      MojoAddTrigger(t, a, MOJO_HANDLE_SIGNAL_WRITABLE,
                     MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED, 0, nullptr));

  // |a| is always writable, so we can never arm this trap.
  constexpr size_t kMaxBlockingEvents = 3;
  uint32_t num_blocking_events = kMaxBlockingEvents;
  MojoTrapEvent blocking_events[kMaxBlockingEvents] = {
      {sizeof(blocking_events[0])},
      {sizeof(blocking_events[0])},
      {sizeof(blocking_events[0])}};
  EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
            MojoArmTrap(t, nullptr, &num_blocking_events, &blocking_events[0]));
  EXPECT_EQ(1u, num_blocking_events);
  EXPECT_EQ(0u, blocking_events[0].trigger_context);
  EXPECT_EQ(MOJO_RESULT_OK, blocking_events[0].result);
  EXPECT_TRUE(blocking_events[0].signals_state.satisfied_signals &
              MOJO_HANDLE_SIGNAL_WRITABLE);

  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(t));
  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b));
  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a));
}

TEST_F(TrapTest, ArmWithTriggerConditionAlreadyUnsatisfiable) {
  MojoHandle a, b;
  CreateMessagePipe(&a, &b);

  MojoHandle t;
  EXPECT_EQ(MOJO_RESULT_OK, MojoCreateTrap(&ExpectOnlyCancel, nullptr, &t));
  EXPECT_EQ(
      MOJO_RESULT_OK,
      MojoAddTrigger(t, a, MOJO_HANDLE_SIGNAL_READABLE,
                     MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED, 0, nullptr));

  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b));

  // |b| is closed and never wrote any messages, so |a| won't be readable again.
  // MojoArmTrap() should fail, incidcating as much.
  constexpr size_t kMaxBlockingEvents = 3;
  uint32_t num_blocking_events = kMaxBlockingEvents;
  MojoTrapEvent blocking_events[kMaxBlockingEvents] = {
      {sizeof(blocking_events[0])},
      {sizeof(blocking_events[0])},
      {sizeof(blocking_events[0])}};
  EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
            MojoArmTrap(t, nullptr, &num_blocking_events, &blocking_events[0]));
  EXPECT_EQ(1u, num_blocking_events);
  EXPECT_EQ(0u, blocking_events[0].trigger_context);
  EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, blocking_events[0].result);
  EXPECT_TRUE(blocking_events[0].signals_state.satisfied_signals &
              MOJO_HANDLE_SIGNAL_PEER_CLOSED);
  EXPECT_FALSE(blocking_events[0].signals_state.satisfiable_signals &
               MOJO_HANDLE_SIGNAL_READABLE);

  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(t));
  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a));
}

TEST_F(TrapTest, MultipleTriggers) {
  MojoHandle a, b;
  CreateMessagePipe(&a, &b);

  base::WaitableEvent a_wait(base::WaitableEvent::ResetPolicy::MANUAL,
                             base::WaitableEvent::InitialState::NOT_SIGNALED);
  base::WaitableEvent b_wait(base::WaitableEvent::ResetPolicy::MANUAL,
                             base::WaitableEvent::InitialState::NOT_SIGNALED);
  TriggerHelper helper;
  int num_a_notifications = 0;
  int num_b_notifications = 0;
  uintptr_t readable_a_context =
      helper.CreateContext([&](const MojoTrapEvent& event) {
        num_a_notifications += 1;
        EXPECT_EQ(MOJO_RESULT_OK, event.result);
        a_wait.Signal();
      });
  uintptr_t readable_b_context =
      helper.CreateContext([&](const MojoTrapEvent& event) {
        num_b_notifications += 1;
        EXPECT_EQ(MOJO_RESULT_OK, event.result);
        b_wait.Signal();
      });

  MojoHandle t;
  EXPECT_EQ(MOJO_RESULT_OK, helper.CreateTrap(&t));

  // Add two independent triggers to trap |a| or |b| readability.
  EXPECT_EQ(MOJO_RESULT_OK,
            MojoAddTrigger(t, a, MOJO_HANDLE_SIGNAL_READABLE,
                           MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED,
                           readable_a_context, nullptr));
  EXPECT_EQ(MOJO_RESULT_OK,
            MojoAddTrigger(t, b, MOJO_HANDLE_SIGNAL_READABLE,
                           MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED,
                           readable_b_context, nullptr));

  EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(t, nullptr, nullptr, nullptr));

  const char kMessage1[] = "things are happening";
  const char kMessage2[] = "ok. ok. ok. ok.";
  const char kMessage3[] = "plz wake up";

  // Writing to |b| should signal |a|'s watch.
  WriteMessage(b, kMessage1);
  a_wait.Wait();
  a_wait.Reset();

  // Subsequent messages on |b| should not trigger another notification.
  WriteMessage(b, kMessage2);
  WriteMessage(b, kMessage3);

  // Messages on |a| also shouldn't trigger |b|'s notification, since the
  // trap should be disarmed by now.
  WriteMessage(a, kMessage1);
  WriteMessage(a, kMessage2);
  WriteMessage(a, kMessage3);

  // Arming should fail. Since we only ask for at most one context's information
  // that's all we should get back. Which one we get is unspecified.
  constexpr size_t kMaxBlockingEvents = 3;
  uint32_t num_blocking_events = 1;
  MojoTrapEvent blocking_events[kMaxBlockingEvents] = {
      {sizeof(blocking_events[0])},
      {sizeof(blocking_events[0])},
      {sizeof(blocking_events[0])}};
  EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
            MojoArmTrap(t, nullptr, &num_blocking_events, &blocking_events[0]));
  EXPECT_EQ(1u, num_blocking_events);
  EXPECT_TRUE(blocking_events[0].trigger_context == readable_a_context ||
              blocking_events[0].trigger_context == readable_b_context);
  EXPECT_EQ(MOJO_RESULT_OK, blocking_events[0].result);
  EXPECT_TRUE(blocking_events[0].signals_state.satisfied_signals &
              MOJO_HANDLE_SIGNAL_WRITABLE);

  // Now try arming again, verifying that both contexts are returned.
  num_blocking_events = kMaxBlockingEvents;
  EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
            MojoArmTrap(t, nullptr, &num_blocking_events, &blocking_events[0]));
  EXPECT_EQ(2u, num_blocking_events);
  EXPECT_EQ(MOJO_RESULT_OK, blocking_events[0].result);
  EXPECT_EQ(MOJO_RESULT_OK, blocking_events[1].result);
  EXPECT_TRUE(blocking_events[0].signals_state.satisfied_signals &
              MOJO_HANDLE_SIGNAL_WRITABLE);
  EXPECT_TRUE(blocking_events[1].signals_state.satisfied_signals &
              MOJO_HANDLE_SIGNAL_WRITABLE);
  EXPECT_TRUE((blocking_events[0].trigger_context == readable_a_context &&
               blocking_events[1].trigger_context == readable_b_context) ||
              (blocking_events[0].trigger_context == readable_b_context &&
               blocking_events[1].trigger_context == readable_a_context));

  // Flush out the test messages so we should be able to successfully rearm.
  EXPECT_EQ(kMessage1, ReadMessage(a));
  EXPECT_EQ(kMessage2, ReadMessage(a));
  EXPECT_EQ(kMessage3, ReadMessage(a));
  EXPECT_EQ(kMessage1, ReadMessage(b));
  EXPECT_EQ(kMessage2, ReadMessage(b));
  EXPECT_EQ(kMessage3, ReadMessage(b));

  // Add a trigger whose condition is always satisfied so we can't arm. Arming
  // should fail with only this new watch's information.
  uintptr_t writable_c_context =
      helper.CreateContext([](const MojoTrapEvent&) { NOTREACHED(); });
  MojoHandle c, d;
  CreateMessagePipe(&c, &d);

  EXPECT_EQ(MOJO_RESULT_OK,
            MojoAddTrigger(t, c, MOJO_HANDLE_SIGNAL_WRITABLE,
                           MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED,
                           writable_c_context, nullptr));
  num_blocking_events = kMaxBlockingEvents;
  EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
            MojoArmTrap(t, nullptr, &num_blocking_events, &blocking_events[0]));
  EXPECT_EQ(1u, num_blocking_events);
  EXPECT_EQ(writable_c_context, blocking_events[0].trigger_context);
  EXPECT_EQ(MOJO_RESULT_OK, blocking_events[0].result);
  EXPECT_TRUE(blocking_events[0].signals_state.satisfied_signals &
              MOJO_HANDLE_SIGNAL_WRITABLE);

  // Remove the new trigger and arming should succeed once again.
  EXPECT_EQ(MOJO_RESULT_OK, MojoRemoveTrigger(t, writable_c_context, nullptr));
  EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(t, nullptr, nullptr, nullptr));

  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(t));
  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a));
  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b));
  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(c));
  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(d));
}

TEST_F(TrapTest, ActivateOtherTriggerFromEventHandler) {
  MojoHandle a, b;
  CreateMessagePipe(&a, &b);

  static const char kTestMessageToA[] = "hello a";
  static const char kTestMessageToB[] = "hello b";

  base::WaitableEvent wait(base::WaitableEvent::ResetPolicy::MANUAL,
                           base::WaitableEvent::InitialState::NOT_SIGNALED);

  TriggerHelper helper;
  MojoHandle t;
  EXPECT_EQ(MOJO_RESULT_OK, helper.CreateTrap(&t));

  uintptr_t readable_a_context =
      helper.CreateContext([&](const MojoTrapEvent& event) {
        EXPECT_EQ(MOJO_RESULT_OK, event.result);
        EXPECT_EQ("hello a", ReadMessage(a));

        // Re-arm the trap and signal |b|.
        EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(t, nullptr, nullptr, nullptr));
        WriteMessage(a, kTestMessageToB);
      });

  uintptr_t readable_b_context =
      helper.CreateContext([&](const MojoTrapEvent& event) {
        EXPECT_EQ(MOJO_RESULT_OK, event.result);
        EXPECT_EQ(kTestMessageToB, ReadMessage(b));
        EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(t, nullptr, nullptr, nullptr));
        wait.Signal();
      });

  EXPECT_EQ(MOJO_RESULT_OK,
            MojoAddTrigger(t, a, MOJO_HANDLE_SIGNAL_READABLE,
                           MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED,
                           readable_a_context, nullptr));
  EXPECT_EQ(MOJO_RESULT_OK,
            MojoAddTrigger(t, b, MOJO_HANDLE_SIGNAL_READABLE,
                           MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED,
                           readable_b_context, nullptr));
  EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(t, nullptr, nullptr, nullptr));

  // Send a message to |a|. The relevant trigger should be notified and the
  // event handler should send a message to |b|, in turn notifying the other
  // trigger. The second event handler will signal |wait|.
  WriteMessage(b, kTestMessageToA);
  wait.Wait();
  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(t));
  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a));
  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b));
}

TEST_F(TrapTest, ActivateSameTriggerFromEventHandler) {
  MojoHandle a, b;
  CreateMessagePipe(&a, &b);

  static const char kTestMessageToA[] = "hello a";

  base::WaitableEvent wait(base::WaitableEvent::ResetPolicy::MANUAL,
                           base::WaitableEvent::InitialState::NOT_SIGNALED);

  TriggerHelper helper;
  MojoHandle t;
  EXPECT_EQ(MOJO_RESULT_OK, helper.CreateTrap(&t));

  int expected_notifications = 10;
  uintptr_t readable_a_context =
      helper.CreateContext([&](const MojoTrapEvent& event) {
        EXPECT_EQ(MOJO_RESULT_OK, event.result);
        EXPECT_EQ("hello a", ReadMessage(a));

        EXPECT_GT(expected_notifications, 0);
        expected_notifications -= 1;
        if (expected_notifications == 0) {
          wait.Signal();
          return;
        } else {
          // Re-arm the trap and signal |a| again.
          EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(t, nullptr, nullptr, nullptr));
          WriteMessage(b, kTestMessageToA);
        }
      });

  EXPECT_EQ(MOJO_RESULT_OK,
            MojoAddTrigger(t, a, MOJO_HANDLE_SIGNAL_READABLE,
                           MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED,
                           readable_a_context, nullptr));
  EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(t, nullptr, nullptr, nullptr));

  // Send a message to |a|. When the trigger above is activated, the event
  // handler will rearm the trap and send another message to |a|. This will
  // happen until |expected_notifications| reaches 0.
  WriteMessage(b, kTestMessageToA);
  wait.Wait();
  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a));
  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b));
  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(t));
}

TEST_F(TrapTest, ImplicitRemoveOtherTriggerWithinEventHandler) {
  MojoHandle a, b;
  CreateMessagePipe(&a, &b);

  MojoHandle c, d;
  CreateMessagePipe(&c, &d);

  static const char kTestMessageToA[] = "hi a";
  static const char kTestMessageToC[] = "hi c";

  base::WaitableEvent wait(base::WaitableEvent::ResetPolicy::MANUAL,
                           base::WaitableEvent::InitialState::NOT_SIGNALED);

  TriggerHelper helper;
  MojoHandle t;
  EXPECT_EQ(MOJO_RESULT_OK, helper.CreateTrap(&t));

  uintptr_t readable_a_context = helper.CreateContextWithCancel(
      [](const MojoTrapEvent&) { NOTREACHED(); }, [&] { wait.Signal(); });

  uintptr_t readable_c_context =
      helper.CreateContext([&](const MojoTrapEvent& event) {
        EXPECT_EQ(MOJO_RESULT_OK, event.result);
        EXPECT_EQ(kTestMessageToC, ReadMessage(c));

        EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(t, nullptr, nullptr, nullptr));

        // Must result in exactly ONE notification from the above trigger, for
        // CANCELLED only. Because we cannot dispatch notifications until the
        // stack unwinds, and because we must never dispatch non-cancellation
        // notifications for a handle once it's been closed, we must be certain
        // that cancellation due to closure preemptively invalidates any
        // pending non-cancellation notifications queued on the current
        // RequestContext, such as the one resulting from the WriteMessage here.
        WriteMessage(b, kTestMessageToA);
        EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a));

        // Rearming should be fine since |a|'s trigger should already be
        // implicitly removed (even though the notification will not have
        // been invoked yet.)
        EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(t, nullptr, nullptr, nullptr));

        // Nothing interesting should happen as a result of this.
        EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b));
      });

  EXPECT_EQ(MOJO_RESULT_OK,
            MojoAddTrigger(t, a, MOJO_HANDLE_SIGNAL_READABLE,
                           MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED,
                           readable_a_context, nullptr));
  EXPECT_EQ(MOJO_RESULT_OK,
            MojoAddTrigger(t, c, MOJO_HANDLE_SIGNAL_READABLE,
                           MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED,
                           readable_c_context, nullptr));
  EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(t, nullptr, nullptr, nullptr));

  WriteMessage(d, kTestMessageToC);
  wait.Wait();

  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(t));
  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(c));
  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(d));
}

TEST_F(TrapTest, ExplicitRemoveOtherTriggerWithinEventHandler) {
  MojoHandle a, b;
  CreateMessagePipe(&a, &b);

  MojoHandle c, d;
  CreateMessagePipe(&c, &d);

  static const char kTestMessageToA[] = "hi a";
  static const char kTestMessageToC[] = "hi c";

  base::WaitableEvent wait(base::WaitableEvent::ResetPolicy::MANUAL,
                           base::WaitableEvent::InitialState::NOT_SIGNALED);

  TriggerHelper helper;
  MojoHandle t;
  EXPECT_EQ(MOJO_RESULT_OK, helper.CreateTrap(&t));

  uintptr_t readable_a_context =
      helper.CreateContext([](const MojoTrapEvent&) { NOTREACHED(); });

  uintptr_t readable_c_context =
      helper.CreateContext([&](const MojoTrapEvent& event) {
        EXPECT_EQ(MOJO_RESULT_OK, event.result);
        EXPECT_EQ(kTestMessageToC, ReadMessage(c));

        // Now rearm the trap.
        EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(t, nullptr, nullptr, nullptr));

        // Should result in no notifications from the above trigger, because the
        // trigger will have been removed by the time the event handler can
        // execute.
        WriteMessage(b, kTestMessageToA);
        WriteMessage(b, kTestMessageToA);
        EXPECT_EQ(MOJO_RESULT_OK,
                  MojoRemoveTrigger(t, readable_a_context, nullptr));

        // Rearming should be fine now.
        EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(t, nullptr, nullptr, nullptr));

        // Nothing interesting should happen as a result of these.
        EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a));
        EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b));

        wait.Signal();
      });

  EXPECT_EQ(MOJO_RESULT_OK,
            MojoAddTrigger(t, a, MOJO_HANDLE_SIGNAL_READABLE,
                           MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED,
                           readable_a_context, nullptr));
  EXPECT_EQ(MOJO_RESULT_OK,
            MojoAddTrigger(t, c, MOJO_HANDLE_SIGNAL_READABLE,
                           MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED,
                           readable_c_context, nullptr));
  EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(t, nullptr, nullptr, nullptr));

  WriteMessage(d, kTestMessageToC);
  wait.Wait();

  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(t));
  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(c));
  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(d));
}

TEST_F(TrapTest, RemoveSelfWithinEventHandler) {
  MojoHandle a, b;
  CreateMessagePipe(&a, &b);

  static const char kTestMessageToA[] = "hey a";

  MojoHandle t;
  TriggerHelper helper;
  EXPECT_EQ(MOJO_RESULT_OK, helper.CreateTrap(&t));

  base::WaitableEvent wait(base::WaitableEvent::ResetPolicy::MANUAL,
                           base::WaitableEvent::InitialState::NOT_SIGNALED);

  static uintptr_t readable_a_context =
      helper.CreateContext([&](const MojoTrapEvent& event) {
        EXPECT_EQ(MOJO_RESULT_OK, event.result);

        // Subtle: Captures may be invalidated by MojoRemoveTrigger() below,
        // since it may destroy this lambda. Copy the values we'll need for the
        // remainder of this function.
        MojoHandle a_handle = a;
        MojoHandle trap = t;
        uintptr_t context = readable_a_context;
        auto& wait_event = wait;

        // There should be no problem removing this trigger from its own
        // notification invocation.
        EXPECT_EQ(MOJO_RESULT_OK, MojoRemoveTrigger(trap, context, nullptr));
        EXPECT_EQ(kTestMessageToA, ReadMessage(a_handle));

        // Arming should fail because there are no longer any registered
        // triggers on the trap.
        EXPECT_EQ(MOJO_RESULT_NOT_FOUND,
                  MojoArmTrap(trap, nullptr, nullptr, nullptr));

        // And closing |a| should be fine (and should not invoke this
        // notification with MOJO_RESULT_CANCELLED) for the same reason.
        EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a_handle));

        wait_event.Signal();
      });

  EXPECT_EQ(MOJO_RESULT_OK,
            MojoAddTrigger(t, a, MOJO_HANDLE_SIGNAL_READABLE,
                           MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED,
                           readable_a_context, nullptr));
  EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(t, nullptr, nullptr, nullptr));

  WriteMessage(b, kTestMessageToA);
  wait.Wait();

  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b));
  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(t));
}

TEST_F(TrapTest, CloseTrapWithinEventHandler) {
  MojoHandle a, b;
  CreateMessagePipe(&a, &b);

  static const char kTestMessageToA1[] = "hey a";
  static const char kTestMessageToA2[] = "hey a again";

  MojoHandle t;
  TriggerHelper helper;
  EXPECT_EQ(MOJO_RESULT_OK, helper.CreateTrap(&t));

  base::WaitableEvent wait(base::WaitableEvent::ResetPolicy::MANUAL,
                           base::WaitableEvent::InitialState::NOT_SIGNALED);

  uintptr_t readable_a_context =
      helper.CreateContext([&](const MojoTrapEvent& event) {
        EXPECT_EQ(MOJO_RESULT_OK, event.result);
        EXPECT_EQ(kTestMessageToA1, ReadMessage(a));
        EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(t, nullptr, nullptr, nullptr));

        // There should be no problem closing this trap from its own
        // notification callback. Note that it may however delete this lambda
        // and invalidate any captures, so we copy some captured values for use
        // below.
        MojoHandle a_handle = a;
        MojoHandle b_handle = b;
        base::WaitableEvent& wait_event = wait;
        EXPECT_EQ(MOJO_RESULT_OK, MojoClose(t));

        // And these should not trigger more notifications, because |t| has been
        // closed already.
        WriteMessage(b_handle, kTestMessageToA2);
        EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b_handle));
        EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a_handle));

        wait_event.Signal();
      });

  EXPECT_EQ(MOJO_RESULT_OK,
            MojoAddTrigger(t, a, MOJO_HANDLE_SIGNAL_READABLE,
                           MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED,
                           readable_a_context, nullptr));
  EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(t, nullptr, nullptr, nullptr));

  WriteMessage(b, kTestMessageToA1);
  wait.Wait();
}

TEST_F(TrapTest, CloseTrapAfterImplicitTriggerRemoval) {
  MojoHandle a, b;
  CreateMessagePipe(&a, &b);

  static const char kTestMessageToA[] = "hey a";

  MojoHandle t;
  TriggerHelper helper;
  EXPECT_EQ(MOJO_RESULT_OK, helper.CreateTrap(&t));

  base::WaitableEvent wait(base::WaitableEvent::ResetPolicy::MANUAL,
                           base::WaitableEvent::InitialState::NOT_SIGNALED);

  uintptr_t readable_a_context =
      helper.CreateContext([&](const MojoTrapEvent& event) {
        EXPECT_EQ(MOJO_RESULT_OK, event.result);
        EXPECT_EQ(kTestMessageToA, ReadMessage(a));
        EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(t, nullptr, nullptr, nullptr));

        // Closing `a` deletes its trigger which may in turn delete this lambda
        // and invalidate any captures. Copy the values we need first.
        base::WaitableEvent& wait_event = wait;
        MojoHandle trap = t;
        EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a));
        EXPECT_EQ(MOJO_RESULT_OK, MojoClose(trap));
        wait_event.Signal();
      });

  EXPECT_EQ(MOJO_RESULT_OK,
            MojoAddTrigger(t, a, MOJO_HANDLE_SIGNAL_READABLE,
                           MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED,
                           readable_a_context, nullptr));
  EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(t, nullptr, nullptr, nullptr));

  WriteMessage(b, kTestMessageToA);
  wait.Wait();

  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b));
}

TEST_F(TrapTest, OtherThreadRemovesTriggerDuringEventHandler) {
  MojoHandle a, b;
  CreateMessagePipe(&a, &b);

  static const char kTestMessageToA[] = "hey a";

  MojoHandle t;
  TriggerHelper helper;
  EXPECT_EQ(MOJO_RESULT_OK, helper.CreateTrap(&t));

  base::WaitableEvent wait_for_notification(
      base::WaitableEvent::ResetPolicy::MANUAL,
      base::WaitableEvent::InitialState::NOT_SIGNALED);

  base::WaitableEvent wait_for_cancellation(
      base::WaitableEvent::ResetPolicy::MANUAL,
      base::WaitableEvent::InitialState::NOT_SIGNALED);

  static bool callback_done = false;
  uintptr_t readable_a_context = helper.CreateContextWithCancel(
      [&](const MojoTrapEvent& event) {
        EXPECT_EQ(MOJO_RESULT_OK, event.result);
        EXPECT_EQ(kTestMessageToA, ReadMessage(a));

        wait_for_notification.Signal();

        // Give the other thread sufficient time to race with the completion
        // of this callback. There should be no race, since the cancellation
        // notification must be mutually exclusive to this notification.
        base::PlatformThread::Sleep(base::Seconds(1));

        callback_done = true;
      },
      [&] {
        EXPECT_TRUE(callback_done);
        wait_for_cancellation.Signal();
      });

  ThreadedRunner runner(base::BindLambdaForTesting([&] {
    wait_for_notification.Wait();

    // Cancel the watch while the notification is still running.
    EXPECT_EQ(MOJO_RESULT_OK,
              MojoRemoveTrigger(t, readable_a_context, nullptr));

    wait_for_cancellation.Wait();

    EXPECT_TRUE(callback_done);
  }));
  runner.Start();

  EXPECT_EQ(MOJO_RESULT_OK,
            MojoAddTrigger(t, a, MOJO_HANDLE_SIGNAL_READABLE,
                           MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED,
                           readable_a_context, nullptr));
  EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(t, nullptr, nullptr, nullptr));

  WriteMessage(b, kTestMessageToA);
  runner.Join();

  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(t));
  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a));
  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b));
}

TEST_F(TrapTest, AlwaysCancel) {
  // Basic sanity check to ensure that all possible ways to remove a trigger
  // result in a final MOJO_RESULT_CANCELLED notification.

  MojoHandle a, b;
  CreateMessagePipe(&a, &b);

  MojoHandle t;
  TriggerHelper helper;
  EXPECT_EQ(MOJO_RESULT_OK, helper.CreateTrap(&t));

  base::WaitableEvent wait(base::WaitableEvent::ResetPolicy::MANUAL,
                           base::WaitableEvent::InitialState::NOT_SIGNALED);
  auto ignore_event = [](const MojoTrapEvent&) {};
  auto signal_wait = [&] { wait.Signal(); };

  // Cancel via |MojoRemoveTrigger()|.
  uintptr_t context = helper.CreateContextWithCancel(ignore_event, signal_wait);
  EXPECT_EQ(MOJO_RESULT_OK,
            MojoAddTrigger(t, a, MOJO_HANDLE_SIGNAL_READABLE,
                           MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED, context,
                           nullptr));
  EXPECT_EQ(MOJO_RESULT_OK, MojoRemoveTrigger(t, context, nullptr));
  wait.Wait();
  wait.Reset();

  // Cancel by closing the trigger's watched handle.
  context = helper.CreateContextWithCancel(ignore_event, signal_wait);
  EXPECT_EQ(MOJO_RESULT_OK,
            MojoAddTrigger(t, a, MOJO_HANDLE_SIGNAL_READABLE,
                           MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED, context,
                           nullptr));
  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a));
  wait.Wait();
  wait.Reset();

  // Cancel by closing the trap handle.
  context = helper.CreateContextWithCancel(ignore_event, signal_wait);
  EXPECT_EQ(MOJO_RESULT_OK,
            MojoAddTrigger(t, b, MOJO_HANDLE_SIGNAL_READABLE,
                           MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED, context,
                           nullptr));
  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(t));
  wait.Wait();

  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b));
}

TEST_F(TrapTest, ArmFailureCirculation) {
  // Sanity check to ensure that all ready trigger events will eventually be
  // returned over a finite number of calls to MojoArmTrap().

  constexpr size_t kNumTestPipes = 100;
  constexpr size_t kNumTestHandles = kNumTestPipes * 2;
  std::array<MojoHandle, kNumTestHandles> handles;

  // Create a bunch of pipes and make sure they're all readable.
  for (size_t i = 0; i < kNumTestPipes; ++i) {
    CreateMessagePipe(&handles[i], &handles[i + kNumTestPipes]);
    WriteMessage(handles[i], "hey");
    WriteMessage(handles[i + kNumTestPipes], "hay");
    WaitForSignals(handles[i], MOJO_HANDLE_SIGNAL_READABLE);
    WaitForSignals(handles[i + kNumTestPipes], MOJO_HANDLE_SIGNAL_READABLE);
  }

  // Create a trap and watch all of them for readability.
  MojoHandle t;
  EXPECT_EQ(MOJO_RESULT_OK, MojoCreateTrap(&ExpectOnlyCancel, nullptr, &t));
  for (size_t i = 0; i < kNumTestHandles; ++i) {
    EXPECT_EQ(
        MOJO_RESULT_OK,
        MojoAddTrigger(t, handles[i], MOJO_HANDLE_SIGNAL_READABLE,
                       MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED, i, nullptr));
  }

  // Keep trying to arm |t| until every trigger gets an entry in
  // |ready_contexts|. If MojoArmTrap() is well-behaved, this should terminate
  // eventually.
  std::set<uintptr_t> ready_contexts;
  while (ready_contexts.size() < kNumTestHandles) {
    uint32_t num_blocking_events = 1;
    MojoTrapEvent blocking_event = {sizeof(blocking_event)};
    EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
              MojoArmTrap(t, nullptr, &num_blocking_events, &blocking_event));
    EXPECT_EQ(1u, num_blocking_events);
    EXPECT_EQ(MOJO_RESULT_OK, blocking_event.result);
    ready_contexts.insert(blocking_event.trigger_context);
  }

  for (size_t i = 0; i < kNumTestHandles; ++i) {
    EXPECT_EQ(MOJO_RESULT_OK, MojoClose(handles[i]));
  }
  EXPECT_EQ(MOJO_RESULT_OK, MojoClose(t));
}

TEST_F(TrapTest, TriggerDuringDestruction) {
  // Regression test for races between trap event firing and trap destruction.
  // See https://crbug.com/1385643.
  MojoHandle a, b;
  CreateMessagePipe(&a, &b);

  auto drain_a = [&](const MojoTrapEvent& event) {
    MojoMessageHandle m;
    while (MojoReadMessage(a, nullptr, &m) == MOJO_RESULT_OK) {
      MojoDestroyMessage(m);
    }
  };

  constexpr size_t kNumIterations = 1000;
  for (size_t i = 0; i < kNumIterations; ++i) {
    MojoHandle t;
    TriggerHelper helper;
    EXPECT_EQ(MOJO_RESULT_OK, helper.CreateTrap(&t));
    EXPECT_EQ(MOJO_RESULT_OK,
              MojoAddTrigger(t, a, MOJO_HANDLE_SIGNAL_READABLE,
                             MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED,
                             helper.CreateContext(drain_a), nullptr));

    drain_a(MojoTrapEvent{});
    EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(t, nullptr, nullptr, nullptr));

    ThreadedRunner writer(
        base::BindLambdaForTesting([&] { WriteMessage(b, "ping!"); }));
    ThreadedRunner closer(base::BindLambdaForTesting([&] { MojoClose(t); }));
    closer.Start();
    writer.Start();
    writer.Join();
    closer.Join();
  }

  MojoClose(a);
  MojoClose(b);
}

TEST_F(TrapTest, RaceDispatchAndBlockedCancel) {
  // Regression test for https://crbug.com/1508753. This bug was caused by
  // reordering of a MOJO_RESULT_CANCELLED event to before some other event for
  // the same trap context, violating an API constraint that must be upheld for
  // memory safety in application code. The scenario which could elicit the bug
  // was as follows:
  //
  //   1. A single trap is watching two pipes, P and Q.
  //   2. Thread A closes pipe P, triggering a CANCELLED event.
  //   3. Thread A re-arms the trap from within the CANCELLED event handler.
  //   4. Thread B changes Q's state to elicit a event for Q (not CANCELLED).
  //   5. Thread B dispatch is blocked because thread A is still dispatching.
  //   6. Before thread B gets a chance to be scheduled, thread A closes Q.
  //   7. Thread A dispatches a CANCELLED event for Q.
  //   8. Thread B is scheduled and proceeds to dispatch its Q event. [BAD]

  struct State;

  struct Pipe {
    explicit Pipe(State* state) : state(state) { CreateMessagePipe(&a, &b); }

    uintptr_t context() const { return reinterpret_cast<uintptr_t>(this); }

    MojoHandle a;
    MojoHandle b;
    bool trigger_cancelled = false;

    // Back-reference to common state so it's reachable from the event handler.
    const raw_ptr<State> state;
  };

  struct State {
    Pipe pipe0{this};
    Pipe pipe1{this};
    MojoHandle trap;
    base::WaitableEvent event;
  };
  State state;

  // NOTE: + to turn the lambda into a function pointer.
  const MojoTrapEventHandler event_handler = +[](const MojoTrapEvent* event) {
    auto& pipe = *reinterpret_cast<Pipe*>(event->trigger_context);
    auto& state = *pipe.state;

    // If the bug is present, this expectation can fail flakily. No event should
    // fire for a pipe after its watch has been cancelled.
    EXPECT_FALSE(pipe.trigger_cancelled);

    if (event->result == MOJO_RESULT_CANCELLED) {
      pipe.trigger_cancelled = true;

      if (&pipe == &state.pipe0) {
        // When pipe0's watch is cancelled (on the main thread by closure down
        // below) we re-arm the trap immediately. This must succeed because
        // `pipe1.a` is now the only handle being watched, and it's still in an
        // uninteresting state.
        EXPECT_EQ(MOJO_RESULT_OK,
                  MojoArmTrap(state.trap, nullptr, nullptr, nullptr));

        // Unblock the other thread so it can elicit a trap event on pipe1 now
        // that the trap is re-armed. It will still block just before
        // dispatching as long as we're still in this event handler on the main
        // thread.
        state.event.Signal();

        // A nice long delay to make it very likely for the waiting
        // ThreadedRunner to progress right up to its event dispatch.
        base::PlatformThread::Sleep(base::Milliseconds(10));

        // Trigger cancellation for pipe1 by closing its `a`. This will queue a
        // CANCELLED event to fire on the same thread immediately after we
        // return from this handler.
        MojoClose(state.pipe1.a);
      }
    }
  };

  EXPECT_EQ(MOJO_RESULT_OK,
            MojoCreateTrap(event_handler, nullptr, &state.trap));
  EXPECT_EQ(
      MOJO_RESULT_OK,
      MojoAddTrigger(state.trap, state.pipe0.a, MOJO_HANDLE_SIGNAL_READABLE,
                     MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED,
                     state.pipe0.context(), nullptr));
  EXPECT_EQ(
      MOJO_RESULT_OK,
      MojoAddTrigger(state.trap, state.pipe1.a, MOJO_HANDLE_SIGNAL_READABLE,
                     MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED,
                     state.pipe1.context(), nullptr));
  EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(state.trap, nullptr, nullptr, nullptr));

  ThreadedRunner close_pipe1_b(base::BindLambdaForTesting([&] {
    state.event.Wait();
    MojoClose(state.pipe1.b);
  }));
  close_pipe1_b.Start();

  // Trigger cancellation of the watch on `pipe0.a`. See event_handler above.
  MojoClose(state.pipe0.a);

  close_pipe1_b.Join();
  MojoClose(state.pipe0.b);
  MojoClose(state.trap);
}

}  // namespace
}  // namespace core
}  // namespace mojo
