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

#include "components/media_router/common/providers/cast/channel/keep_alive_delegate.h"

#include <stdint.h>

#include <memory>

#include "base/json/json_writer.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/test/task_environment.h"
#include "base/test/test_mock_time_task_runner.h"
#include "base/time/tick_clock.h"
#include "base/timer/mock_timer.h"
#include "base/timer/timer.h"
#include "base/values.h"
#include "components/media_router/common/providers/cast/channel/cast_test_util.h"
#include "net/base/net_errors.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

using testing::_;
using testing::Sequence;

namespace cast_channel {
namespace {

const int64_t kTestPingTimeoutMillis = 1000;
const int64_t kTestLivenessTimeoutMillis = 10000;

CastMessage CreateNonKeepAliveMessage(const std::string& message_type) {
  CastMessage output;
  output.set_protocol_version(CastMessage::CASTV2_1_0);
  output.set_source_id("source");
  output.set_destination_id("receiver");
  output.set_namespace_("some.namespace");
  output.set_payload_type(
      CastMessage::PayloadType::CastMessage_PayloadType_STRING);

  base::DictValue type_dict;
  type_dict.Set("type", message_type);
  CHECK(base::JSONWriter::Write(type_dict, output.mutable_payload_utf8()));
  return output;
}

// Extends MockTimer with a mockable method ResetTriggered() which permits
// test code to set GMock expectations for Timer::Reset().
class MockTimerWithMonitoredReset : public base::MockRetainingOneShotTimer {
 public:
  MockTimerWithMonitoredReset() = default;
  ~MockTimerWithMonitoredReset() override = default;

  // Instrumentation point for determining how many times Reset() was called.
  MOCK_METHOD0(ResetTriggered, void(void));
  MOCK_METHOD0(StopTriggered, void(void));

  // Passes through the Reset call to the base MockTimer and visits the mock
  // ResetTriggered method.
  void Reset() override {
    base::MockRetainingOneShotTimer::Reset();
    ResetTriggered();
  }

  void Stop() override {
    base::MockRetainingOneShotTimer::Stop();
    StopTriggered();
  }
};

class KeepAliveDelegateTest : public testing::Test {
 public:
  using ChannelError = ::cast_channel::ChannelError;

  KeepAliveDelegateTest() = default;

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

  ~KeepAliveDelegateTest() override = default;

 protected:
  void SetUp() override {
    inner_delegate_ = new MockCastTransportDelegate;
    logger_ = new Logger();
    keep_alive_ = std::make_unique<KeepAliveDelegate>(
        &socket_, logger_, base::WrapUnique(inner_delegate_.get()),
        base::Milliseconds(kTestPingTimeoutMillis),
        base::Milliseconds(kTestLivenessTimeoutMillis));
    liveness_timer_ = new MockTimerWithMonitoredReset;
    ping_timer_ = new MockTimerWithMonitoredReset;
    EXPECT_CALL(*liveness_timer_, StopTriggered()).Times(0);
    EXPECT_CALL(*ping_timer_, StopTriggered()).Times(0);
    keep_alive_->SetTimersForTest(base::WrapUnique(ping_timer_.get()),
                                  base::WrapUnique(liveness_timer_.get()));
  }

  // Runs all pending tasks in the message loop.
  void RunPendingTasks() {
    base::RunLoop run_loop;
    run_loop.RunUntilIdle();
  }

  base::test::SingleThreadTaskEnvironment task_environment_;
  MockCastSocket socket_;
  std::unique_ptr<KeepAliveDelegate> keep_alive_;
  scoped_refptr<Logger> logger_;
  raw_ptr<MockCastTransportDelegate> inner_delegate_;
  raw_ptr<MockTimerWithMonitoredReset, DanglingUntriaged> liveness_timer_;
  raw_ptr<MockTimerWithMonitoredReset, DanglingUntriaged> ping_timer_;
};

TEST_F(KeepAliveDelegateTest, TestErrorHandledBeforeStarting) {
  EXPECT_CALL(*inner_delegate_, OnError(ChannelError::CONNECT_ERROR));
  keep_alive_->OnError(ChannelError::CONNECT_ERROR);
}

TEST_F(KeepAliveDelegateTest, TestPing) {
  EXPECT_CALL(*socket_.mock_transport(),
              SendMessage_(EqualsProto(CreateKeepAlivePingMessage()), _))
      .WillOnce(PostCompletionCallbackTask<1>(net::OK));
  EXPECT_CALL(*inner_delegate_, Start());
  EXPECT_CALL(*ping_timer_, ResetTriggered()).Times(2);
  EXPECT_CALL(*liveness_timer_, ResetTriggered()).Times(3);
  EXPECT_CALL(*ping_timer_, StopTriggered());

  keep_alive_->Start();
  ping_timer_->Fire();
  EXPECT_FALSE(ping_timer_->IsRunning());

  keep_alive_->OnMessage(CreateKeepAlivePongMessage());
  RunPendingTasks();
  EXPECT_TRUE(ping_timer_->IsRunning());
}

TEST_F(KeepAliveDelegateTest, TestPingFailed) {
  EXPECT_CALL(*socket_.mock_transport(),
              SendMessage_(EqualsProto(CreateKeepAlivePingMessage()), _))
      .WillOnce(PostCompletionCallbackTask<1>(net::ERR_CONNECTION_RESET));
  EXPECT_CALL(*inner_delegate_, Start());
  EXPECT_CALL(*inner_delegate_, OnError(ChannelError::CAST_SOCKET_ERROR));
  EXPECT_CALL(*ping_timer_, ResetTriggered()).Times(1);
  EXPECT_CALL(*liveness_timer_, ResetTriggered()).Times(1);
  EXPECT_CALL(*liveness_timer_, StopTriggered());
  EXPECT_CALL(*ping_timer_, StopTriggered()).Times(2);

  keep_alive_->Start();
  ping_timer_->Fire();
  RunPendingTasks();
  EXPECT_EQ(ChannelEvent::PING_WRITE_ERROR,
            logger_->GetLastError(socket_.id()).channel_event);
  EXPECT_EQ(net::ERR_CONNECTION_RESET,
            logger_->GetLastError(socket_.id()).net_return_value);
}

TEST_F(KeepAliveDelegateTest, TestPingAndLivenessTimeout) {
  EXPECT_CALL(*socket_.mock_transport(),
              SendMessage_(EqualsProto(CreateKeepAlivePingMessage()), _))
      .WillOnce(PostCompletionCallbackTask<1>(net::OK));
  EXPECT_CALL(*inner_delegate_, OnError(ChannelError::PING_TIMEOUT));
  EXPECT_CALL(*inner_delegate_, Start());
  EXPECT_CALL(*ping_timer_, ResetTriggered()).Times(1);
  EXPECT_CALL(*liveness_timer_, ResetTriggered()).Times(2);
  EXPECT_CALL(*liveness_timer_, StopTriggered()).Times(2);
  EXPECT_CALL(*ping_timer_, StopTriggered()).Times(2);

  keep_alive_->Start();
  ping_timer_->Fire();
  liveness_timer_->Fire();
  RunPendingTasks();
}

TEST_F(KeepAliveDelegateTest, TestResetTimersAndPassthroughAllOtherTraffic) {
  CastMessage other_message = CreateNonKeepAliveMessage("someMessageType");

  EXPECT_CALL(*inner_delegate_, OnMessage(EqualsProto(other_message)));
  EXPECT_CALL(*inner_delegate_, Start());
  EXPECT_CALL(*ping_timer_, ResetTriggered()).Times(2);
  EXPECT_CALL(*liveness_timer_, ResetTriggered()).Times(2);

  keep_alive_->Start();
  keep_alive_->OnMessage(other_message);
  RunPendingTasks();
}

TEST_F(KeepAliveDelegateTest, TestPassthroughMessagesAfterError) {
  CastMessage message = CreateNonKeepAliveMessage("someMessageType");
  CastMessage message_after_error =
      CreateNonKeepAliveMessage("someMessageType2");
  CastMessage late_ping_message = CreateKeepAlivePingMessage();

  EXPECT_CALL(*inner_delegate_, Start()).Times(1);
  EXPECT_CALL(*ping_timer_, ResetTriggered()).Times(2);
  EXPECT_CALL(*liveness_timer_, ResetTriggered()).Times(2);
  EXPECT_CALL(*liveness_timer_, StopTriggered()).Times(1);
  EXPECT_CALL(*ping_timer_, StopTriggered()).Times(1);

  Sequence message_and_error_sequence;
  EXPECT_CALL(*inner_delegate_, OnMessage(EqualsProto(message)))
      .Times(1)
      .InSequence(message_and_error_sequence)
      .RetiresOnSaturation();
  EXPECT_CALL(*inner_delegate_, OnError(ChannelError::INVALID_MESSAGE))
      .Times(1)
      .InSequence(message_and_error_sequence);
  EXPECT_CALL(*inner_delegate_, OnMessage(EqualsProto(message_after_error)))
      .Times(1)
      .InSequence(message_and_error_sequence)
      .RetiresOnSaturation();
  EXPECT_CALL(*inner_delegate_, OnMessage(EqualsProto(late_ping_message)))
      .Times(0)
      .InSequence(message_and_error_sequence)
      .RetiresOnSaturation();

  // Start, process one message, then error-out. KeepAliveDelegate will
  // automatically stop itself.
  keep_alive_->Start();
  keep_alive_->OnMessage(message);
  RunPendingTasks();
  keep_alive_->OnError(ChannelError::INVALID_MESSAGE);
  RunPendingTasks();

  // Process a non-PING/PONG message and expect it to pass through.
  keep_alive_->OnMessage(message_after_error);
  RunPendingTasks();

  // Process a late-arriving PING/PONG message, which should have no effect.
  keep_alive_->OnMessage(late_ping_message);
  RunPendingTasks();
}

TEST_F(KeepAliveDelegateTest, TestLivenessTimerResetAfterSendingMessage) {
  scoped_refptr<base::TestMockTimeTaskRunner> mock_time_task_runner(
      new base::TestMockTimeTaskRunner());

  // Drop unowned references before replacing owned references in
  // the KeepAliveDelegate.
  ping_timer_ = nullptr;
  liveness_timer_ = nullptr;

  auto liveness_timer = std::make_unique<base::RetainingOneShotTimer>(
      mock_time_task_runner->GetMockTickClock());
  auto ping_timer = std::make_unique<base::RetainingOneShotTimer>(
      mock_time_task_runner->GetMockTickClock());
  ping_timer->SetTaskRunner(mock_time_task_runner);
  liveness_timer->SetTaskRunner(mock_time_task_runner);
  keep_alive_->SetTimersForTest(std::move(ping_timer),
                                std::move(liveness_timer));

  // At time 0, start.
  EXPECT_CALL(*inner_delegate_, Start());
  keep_alive_->Start();

  EXPECT_CALL(*socket_.mock_transport(),
              SendMessage_(EqualsProto(CreateKeepAlivePingMessage()), _))
      .WillOnce(PostCompletionCallbackTask<1>(net::OK));
  // Forward 1s, at time 1, fire ping timer.
  mock_time_task_runner->FastForwardBy(
      base::Milliseconds(kTestPingTimeoutMillis));

  // Forward 9s, at Time 10, do not fire liveness timer.
  EXPECT_CALL(*inner_delegate_, OnError(_)).Times(0);
  mock_time_task_runner->FastForwardBy(
      base::Milliseconds(kTestLivenessTimeoutMillis - kTestPingTimeoutMillis));

  // Forward 1s, at time 11s, fire liveness timer.
  EXPECT_CALL(*inner_delegate_, OnError(_));
  mock_time_task_runner->FastForwardBy(
      base::Milliseconds(kTestPingTimeoutMillis));
}

}  // namespace
}  // namespace cast_channel
