// Copyright 2022 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 "quiche/quic/core/io/quic_poll_event_loop.h"

#include <fcntl.h>
#include <unistd.h>

#include <cerrno>
#include <memory>
#include <vector>

#include "absl/memory/memory.h"
#include "quiche/quic/core/io/quic_event_loop.h"
#include "quiche/quic/core/quic_alarm.h"
#include "quiche/quic/core/quic_alarm_factory.h"
#include "quiche/quic/core/quic_time.h"
#include "quiche/quic/platform/api/quic_test.h"
#include "quiche/quic/test_tools/mock_clock.h"

namespace quic {

class QuicPollEventLoopPeer {
 public:
  static QuicTime::Delta ComputePollTimeout(const QuicPollEventLoop& loop,
                                            QuicTime now,
                                            QuicTime::Delta default_timeout) {
    return loop.ComputePollTimeout(now, default_timeout);
  }
};

}  // namespace quic

namespace quic::test {
namespace {

using testing::_;
using testing::AtMost;
using testing::ElementsAre;

constexpr QuicSocketEventMask kAllEvents =
    kSocketEventReadable | kSocketEventWritable | kSocketEventError;
constexpr QuicTime::Delta kDefaultTimeout = QuicTime::Delta::FromSeconds(100);

class MockQuicSocketEventListener : public QuicSocketEventListener {
 public:
  MOCK_METHOD(void, OnSocketEvent,
              (QuicEventLoop* /*event_loop*/, SocketFd /*fd*/,
               QuicSocketEventMask /*events*/),
              (override));
};

class MockDelegate : public QuicAlarm::Delegate {
 public:
  QuicConnectionContext* GetConnectionContext() override { return nullptr; }
  MOCK_METHOD(void, OnAlarm, (), (override));
};

class QuicPollEventLoopForTest : public QuicPollEventLoop {
 public:
  QuicPollEventLoopForTest(MockClock* clock)
      : QuicPollEventLoop(clock), clock_(clock) {}

  int PollSyscall(pollfd* fds, nfds_t nfds, int timeout) override {
    timeouts_.push_back(timeout);
    if (eintr_after_ != QuicTime::Delta::Infinite()) {
      errno = EINTR;
      clock_->AdvanceTime(eintr_after_);
      eintr_after_ = QuicTime::Delta::Infinite();
      return -1;
    }
    clock_->AdvanceTime(QuicTime::Delta::FromMilliseconds(timeout));
    return QuicPollEventLoop::PollSyscall(fds, nfds, timeout);
  }

  void TriggerEintrAfter(QuicTime::Delta time) { eintr_after_ = time; }

  const std::vector<int>& timeouts() const { return timeouts_; }

 private:
  MockClock* clock_;
  QuicTime::Delta eintr_after_ = QuicTime::Delta::Infinite();
  std::vector<int> timeouts_;
};

class QuicPollEventLoopTest : public QuicTest {
 public:
  QuicPollEventLoopTest()
      : loop_(&clock_), factory_(loop_.CreateAlarmFactory()) {
    int fds[2];
    int result = ::pipe(fds);
    QUICHE_CHECK(result >= 0) << "Failed to create a pipe, errno: " << errno;
    read_fd_ = fds[0];
    write_fd_ = fds[1];

    QUICHE_CHECK(::fcntl(read_fd_, F_SETFL,
                         ::fcntl(read_fd_, F_GETFL) | O_NONBLOCK) == 0)
        << "Failed to mark pipe FD non-blocking, errno: " << errno;
    QUICHE_CHECK(::fcntl(write_fd_, F_SETFL,
                         ::fcntl(write_fd_, F_GETFL) | O_NONBLOCK) == 0)
        << "Failed to mark pipe FD non-blocking, errno: " << errno;

    clock_.AdvanceTime(10 * kDefaultTimeout);
  }

  ~QuicPollEventLoopTest() {
    close(read_fd_);
    close(write_fd_);
  }

  QuicTime::Delta ComputePollTimeout() {
    return QuicPollEventLoopPeer::ComputePollTimeout(loop_, clock_.Now(),
                                                     kDefaultTimeout);
  }

  std::pair<std::unique_ptr<QuicAlarm>, MockDelegate*> CreateAlarm() {
    auto delegate = std::make_unique<testing::StrictMock<MockDelegate>>();
    MockDelegate* delegate_unowned = delegate.get();
    auto alarm = absl::WrapUnique(factory_->CreateAlarm(delegate.release()));
    return std::make_pair(std::move(alarm), delegate_unowned);
  }

 protected:
  MockClock clock_;
  QuicPollEventLoopForTest loop_;
  std::unique_ptr<QuicAlarmFactory> factory_;
  int read_fd_;
  int write_fd_;
};

TEST_F(QuicPollEventLoopTest, NothingHappens) {
  testing::StrictMock<MockQuicSocketEventListener> listener;
  ASSERT_TRUE(loop_.RegisterSocket(read_fd_, kAllEvents, &listener));
  ASSERT_TRUE(loop_.RegisterSocket(write_fd_, kAllEvents, &listener));

  // Attempt double-registration.
  EXPECT_FALSE(loop_.RegisterSocket(write_fd_, kAllEvents, &listener));

  EXPECT_EQ(ComputePollTimeout(), kDefaultTimeout);

  EXPECT_CALL(listener, OnSocketEvent(_, write_fd_, kSocketEventWritable));
  loop_.RunEventLoopOnce(QuicTime::Delta::FromMilliseconds(4));
  // Expect no further calls.
  loop_.RunEventLoopOnce(QuicTime::Delta::FromMilliseconds(5));
  EXPECT_THAT(loop_.timeouts(), ElementsAre(4, 5));
}

TEST_F(QuicPollEventLoopTest, RearmWriter) {
  testing::StrictMock<MockQuicSocketEventListener> listener;
  ASSERT_TRUE(loop_.RegisterSocket(write_fd_, kAllEvents, &listener));

  EXPECT_CALL(listener, OnSocketEvent(_, write_fd_, kSocketEventWritable))
      .Times(2);
  loop_.RunEventLoopOnce(QuicTime::Delta::FromMilliseconds(1));
  ASSERT_TRUE(loop_.RearmSocket(write_fd_, kSocketEventWritable));
  loop_.RunEventLoopOnce(QuicTime::Delta::FromMilliseconds(1));
}

TEST_F(QuicPollEventLoopTest, Readable) {
  testing::StrictMock<MockQuicSocketEventListener> listener;
  ASSERT_TRUE(loop_.RegisterSocket(read_fd_, kAllEvents, &listener));

  ASSERT_EQ(4, write(write_fd_, "test", 4));
  EXPECT_CALL(listener, OnSocketEvent(_, read_fd_, kSocketEventReadable));
  loop_.RunEventLoopOnce(QuicTime::Delta::FromMilliseconds(1));
  // Expect no further calls.
  loop_.RunEventLoopOnce(QuicTime::Delta::FromMilliseconds(1));
}

TEST_F(QuicPollEventLoopTest, RearmReader) {
  testing::StrictMock<MockQuicSocketEventListener> listener;
  ASSERT_TRUE(loop_.RegisterSocket(read_fd_, kAllEvents, &listener));

  ASSERT_EQ(4, write(write_fd_, "test", 4));
  EXPECT_CALL(listener, OnSocketEvent(_, read_fd_, kSocketEventReadable));
  loop_.RunEventLoopOnce(QuicTime::Delta::FromMilliseconds(1));
  // Expect no further calls.
  loop_.RunEventLoopOnce(QuicTime::Delta::FromMilliseconds(1));
}

TEST_F(QuicPollEventLoopTest, WriterUnblocked) {
  testing::StrictMock<MockQuicSocketEventListener> listener;
  ASSERT_TRUE(loop_.RegisterSocket(write_fd_, kAllEvents, &listener));

  EXPECT_CALL(listener, OnSocketEvent(_, write_fd_, kSocketEventWritable));
  loop_.RunEventLoopOnce(QuicTime::Delta::FromMilliseconds(1));
  loop_.RunEventLoopOnce(QuicTime::Delta::FromMilliseconds(1));

  int io_result;
  std::string data(2048, 'a');
  do {
    io_result = write(write_fd_, data.data(), data.size());
  } while (io_result > 0);
  ASSERT_EQ(errno, EAGAIN);

  // Rearm and expect no immediate calls.
  ASSERT_TRUE(loop_.RearmSocket(write_fd_, kSocketEventWritable));
  loop_.RunEventLoopOnce(QuicTime::Delta::FromMilliseconds(1));

  EXPECT_CALL(listener, OnSocketEvent(_, write_fd_, kSocketEventWritable));
  do {
    io_result = read(read_fd_, data.data(), data.size());
  } while (io_result > 0);
  ASSERT_EQ(errno, EAGAIN);
  loop_.RunEventLoopOnce(QuicTime::Delta::FromMilliseconds(1));
}

TEST_F(QuicPollEventLoopTest, ArtificialEvent) {
  testing::StrictMock<MockQuicSocketEventListener> listener;
  ASSERT_TRUE(loop_.RegisterSocket(read_fd_, kAllEvents, &listener));
  ASSERT_TRUE(loop_.RegisterSocket(write_fd_, kAllEvents, &listener));

  EXPECT_EQ(ComputePollTimeout(), kDefaultTimeout);
  ASSERT_TRUE(loop_.ArtificiallyNotifyEvent(read_fd_, kSocketEventReadable));
  EXPECT_EQ(ComputePollTimeout(), QuicTime::Delta::Zero());

  {
    testing::InSequence s;
    EXPECT_CALL(listener, OnSocketEvent(_, read_fd_, kSocketEventReadable));
    EXPECT_CALL(listener, OnSocketEvent(_, write_fd_, kSocketEventWritable));
  }
  loop_.RunEventLoopOnce(QuicTime::Delta::FromMilliseconds(1));
  EXPECT_EQ(ComputePollTimeout(), kDefaultTimeout);
}

TEST_F(QuicPollEventLoopTest, Unregister) {
  testing::StrictMock<MockQuicSocketEventListener> listener;
  ASSERT_TRUE(loop_.RegisterSocket(write_fd_, kAllEvents, &listener));
  ASSERT_TRUE(loop_.UnregisterSocket(write_fd_));

  // Expect nothing to happen.
  loop_.RunEventLoopOnce(QuicTime::Delta::FromMilliseconds(1));

  EXPECT_FALSE(loop_.UnregisterSocket(write_fd_));
  EXPECT_FALSE(loop_.RearmSocket(write_fd_, kSocketEventWritable));
  EXPECT_FALSE(loop_.ArtificiallyNotifyEvent(write_fd_, kSocketEventWritable));
}

TEST_F(QuicPollEventLoopTest, UnregisterInsideEventHandler) {
  testing::StrictMock<MockQuicSocketEventListener> listener;
  ASSERT_TRUE(loop_.RegisterSocket(read_fd_, kAllEvents, &listener));
  ASSERT_TRUE(loop_.RegisterSocket(write_fd_, kAllEvents, &listener));

  EXPECT_CALL(listener, OnSocketEvent(_, read_fd_, kSocketEventReadable))
      .WillOnce([this]() { ASSERT_TRUE(loop_.UnregisterSocket(write_fd_)); });
  EXPECT_CALL(listener, OnSocketEvent(_, write_fd_, kSocketEventWritable))
      .Times(0);
  ASSERT_TRUE(loop_.ArtificiallyNotifyEvent(read_fd_, kSocketEventReadable));
  loop_.RunEventLoopOnce(QuicTime::Delta::FromMilliseconds(1));
}

TEST_F(QuicPollEventLoopTest, EintrHandler) {
  testing::StrictMock<MockQuicSocketEventListener> listener;
  ASSERT_TRUE(loop_.RegisterSocket(read_fd_, kAllEvents, &listener));

  loop_.TriggerEintrAfter(QuicTime::Delta::FromMilliseconds(25));
  loop_.RunEventLoopOnce(QuicTime::Delta::FromMilliseconds(100));
  EXPECT_THAT(loop_.timeouts(), ElementsAre(100, 75));
}

TEST_F(QuicPollEventLoopTest, AlarmInFuture) {
  EXPECT_EQ(ComputePollTimeout(), kDefaultTimeout);

  constexpr auto kAlarmTimeout = QuicTime::Delta::FromMilliseconds(5);
  auto [alarm, delegate] = CreateAlarm();
  EXPECT_EQ(ComputePollTimeout(), kDefaultTimeout);

  alarm->Set(clock_.Now() + kAlarmTimeout);
  EXPECT_EQ(ComputePollTimeout(), kAlarmTimeout);

  EXPECT_CALL(*delegate, OnAlarm());
  loop_.RunEventLoopOnce(QuicTime::Delta::FromMilliseconds(100));
  EXPECT_EQ(ComputePollTimeout(), kDefaultTimeout);
}

TEST_F(QuicPollEventLoopTest, AlarmsInPast) {
  EXPECT_EQ(ComputePollTimeout(), kDefaultTimeout);

  constexpr auto kAlarmTimeout = QuicTime::Delta::FromMilliseconds(5);
  auto [alarm1, delegate1] = CreateAlarm();
  auto [alarm2, delegate2] = CreateAlarm();

  alarm1->Set(clock_.Now() - 2 * kAlarmTimeout);
  alarm2->Set(clock_.Now() - kAlarmTimeout);

  {
    testing::InSequence s;
    EXPECT_CALL(*delegate1, OnAlarm());
    EXPECT_CALL(*delegate2, OnAlarm());
  }
  loop_.RunEventLoopOnce(QuicTime::Delta::FromMilliseconds(100));
}

TEST_F(QuicPollEventLoopTest, AlarmCancelled) {
  EXPECT_EQ(ComputePollTimeout(), kDefaultTimeout);

  constexpr auto kAlarmTimeout = QuicTime::Delta::FromMilliseconds(5);
  auto [alarm, delegate] = CreateAlarm();
  EXPECT_EQ(ComputePollTimeout(), kDefaultTimeout);

  alarm->Set(clock_.Now() + kAlarmTimeout);
  alarm->Cancel();
  alarm->Set(clock_.Now() + 2 * kAlarmTimeout);
  EXPECT_EQ(ComputePollTimeout(), kAlarmTimeout);

  EXPECT_CALL(*delegate, OnAlarm());
  loop_.RunEventLoopOnce(QuicTime::Delta::FromMilliseconds(100));
  EXPECT_THAT(loop_.timeouts(), ElementsAre(10));
  EXPECT_EQ(ComputePollTimeout(), kDefaultTimeout);
}

TEST_F(QuicPollEventLoopTest, AlarmCancelsAnotherAlarm) {
  EXPECT_EQ(ComputePollTimeout(), kDefaultTimeout);

  constexpr auto kAlarmTimeout = QuicTime::Delta::FromMilliseconds(5);
  auto [alarm1_ptr, delegate1] = CreateAlarm();
  auto [alarm2_ptr, delegate2] = CreateAlarm();

  QuicAlarm& alarm1 = *alarm1_ptr;
  QuicAlarm& alarm2 = *alarm2_ptr;
  alarm1.Set(clock_.Now() - kAlarmTimeout);
  alarm2.Set(clock_.Now() - kAlarmTimeout);

  int alarms_called = 0;
  // Since the order in which alarms are cancelled is not well-determined, make
  // each one cancel another.
  EXPECT_CALL(*delegate1, OnAlarm()).Times(AtMost(1)).WillOnce([&]() {
    alarm2.Cancel();
    ++alarms_called;
  });
  EXPECT_CALL(*delegate2, OnAlarm()).Times(AtMost(1)).WillOnce([&]() {
    alarm1.Cancel();
    ++alarms_called;
  });
  loop_.RunEventLoopOnce(QuicTime::Delta::FromMilliseconds(100));
  EXPECT_EQ(alarms_called, 1);
  EXPECT_EQ(ComputePollTimeout(), kDefaultTimeout);
}

}  // namespace
}  // namespace quic::test
