// Copyright (c) 2020 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/quic_path_validator.h"

#include <memory>

#include "quiche/quic/core/frames/quic_path_challenge_frame.h"
#include "quiche/quic/core/quic_constants.h"
#include "quiche/quic/core/quic_types.h"
#include "quiche/quic/platform/api/quic_ip_address.h"
#include "quiche/quic/platform/api/quic_socket_address.h"
#include "quiche/quic/platform/api/quic_test.h"
#include "quiche/quic/test_tools/mock_clock.h"
#include "quiche/quic/test_tools/mock_random.h"
#include "quiche/quic/test_tools/quic_path_validator_peer.h"
#include "quiche/quic/test_tools/quic_test_utils.h"

using testing::_;
using testing::Invoke;
using testing::Return;

namespace quic {
namespace test {

class MockSendDelegate : public QuicPathValidator::SendDelegate {
 public:
  // Send a PATH_CHALLENGE frame using given path information and populate
  // |data_buffer| with the frame payload. Return true if the validator should
  // move forward in validation, i.e. arm the retry timer.
  MOCK_METHOD(bool, SendPathChallenge,
              (const QuicPathFrameBuffer&, const QuicSocketAddress&,
               const QuicSocketAddress&, const QuicSocketAddress&,
               QuicPacketWriter*),
              (override));

  MOCK_METHOD(QuicTime, GetRetryTimeout,
              (const QuicSocketAddress&, QuicPacketWriter*), (const, override));
};

class QuicPathValidatorTest : public QuicTest {
 public:
  QuicPathValidatorTest()
      : path_validator_(&alarm_factory_, &arena_, &send_delegate_, &random_,
                        &clock_,
                        /*context=*/nullptr),
        context_(new MockQuicPathValidationContext(
            self_address_, peer_address_, effective_peer_address_, &writer_)),
        result_delegate_(
            new testing::StrictMock<MockQuicPathValidationResultDelegate>()) {
    clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(1));
    ON_CALL(send_delegate_, GetRetryTimeout(_, _))
        .WillByDefault(
            Return(clock_.ApproximateNow() +
                   3 * QuicTime::Delta::FromMilliseconds(kInitialRttMs)));
  }

 protected:
  quic::test::MockAlarmFactory alarm_factory_;
  MockSendDelegate send_delegate_;
  MockRandom random_;
  MockClock clock_;
  QuicConnectionArena arena_;
  QuicPathValidator path_validator_;
  QuicSocketAddress self_address_{QuicIpAddress::Any4(), 443};
  QuicSocketAddress peer_address_{QuicIpAddress::Loopback4(), 443};
  QuicSocketAddress effective_peer_address_{QuicIpAddress::Loopback4(), 12345};
  MockPacketWriter writer_;
  MockQuicPathValidationContext* context_;
  MockQuicPathValidationResultDelegate* result_delegate_;
};

TEST_F(QuicPathValidatorTest, PathValidationSuccessOnFirstRound) {
  QuicPathFrameBuffer challenge_data;
  EXPECT_CALL(send_delegate_,
              SendPathChallenge(_, self_address_, peer_address_,
                                effective_peer_address_, &writer_))
      .WillOnce(Invoke([&](const QuicPathFrameBuffer& payload,
                           const QuicSocketAddress&, const QuicSocketAddress&,
                           const QuicSocketAddress&, QuicPacketWriter*) {
        memcpy(challenge_data.data(), payload.data(), payload.size());
        return true;
      }));
  EXPECT_CALL(send_delegate_, GetRetryTimeout(peer_address_, &writer_));
  const QuicTime expected_start_time = clock_.Now();
  path_validator_.StartPathValidation(
      std::unique_ptr<QuicPathValidationContext>(context_),
      std::unique_ptr<MockQuicPathValidationResultDelegate>(result_delegate_),
      PathValidationReason::kMultiPort);
  EXPECT_TRUE(path_validator_.HasPendingPathValidation());
  EXPECT_EQ(PathValidationReason::kMultiPort,
            path_validator_.GetPathValidationReason());
  EXPECT_TRUE(path_validator_.IsValidatingPeerAddress(effective_peer_address_));
  EXPECT_CALL(*result_delegate_, OnPathValidationSuccess(_, _))
      .WillOnce(Invoke([=](std::unique_ptr<QuicPathValidationContext> context,
                           QuicTime start_time) {
        EXPECT_EQ(context.get(), context_);
        EXPECT_EQ(start_time, expected_start_time);
      }));
  clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(kInitialRttMs));
  path_validator_.OnPathResponse(challenge_data, self_address_);
  EXPECT_FALSE(path_validator_.HasPendingPathValidation());
  EXPECT_EQ(PathValidationReason::kReasonUnknown,
            path_validator_.GetPathValidationReason());
}

TEST_F(QuicPathValidatorTest, RespondWithDifferentSelfAddress) {
  QuicPathFrameBuffer challenge_data;
  EXPECT_CALL(send_delegate_,
              SendPathChallenge(_, self_address_, peer_address_,
                                effective_peer_address_, &writer_))
      .WillOnce(Invoke([&](const QuicPathFrameBuffer payload,
                           const QuicSocketAddress&, const QuicSocketAddress&,
                           const QuicSocketAddress&, QuicPacketWriter*) {
        memcpy(challenge_data.data(), payload.data(), payload.size());
        return true;
      }));
  EXPECT_CALL(send_delegate_, GetRetryTimeout(peer_address_, &writer_));
  const QuicTime expected_start_time = clock_.Now();
  path_validator_.StartPathValidation(
      std::unique_ptr<QuicPathValidationContext>(context_),
      std::unique_ptr<MockQuicPathValidationResultDelegate>(result_delegate_),
      PathValidationReason::kMultiPort);

  // Reception of a PATH_RESPONSE on a different self address should be ignored.
  const QuicSocketAddress kAlternativeSelfAddress(QuicIpAddress::Any6(), 54321);
  EXPECT_NE(kAlternativeSelfAddress, self_address_);
  clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(kInitialRttMs));
  path_validator_.OnPathResponse(challenge_data, kAlternativeSelfAddress);

  EXPECT_CALL(*result_delegate_, OnPathValidationSuccess(_, _))
      .WillOnce(Invoke([=](std::unique_ptr<QuicPathValidationContext> context,
                           QuicTime start_time) {
        EXPECT_EQ(context->self_address(), self_address_);
        EXPECT_EQ(start_time, expected_start_time);
      }));
  clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(kInitialRttMs));
  path_validator_.OnPathResponse(challenge_data, self_address_);
  EXPECT_EQ(PathValidationReason::kReasonUnknown,
            path_validator_.GetPathValidationReason());
}

TEST_F(QuicPathValidatorTest, RespondAfter1stRetry) {
  QuicPathFrameBuffer challenge_data;
  EXPECT_CALL(send_delegate_,
              SendPathChallenge(_, self_address_, peer_address_,
                                effective_peer_address_, &writer_))
      .WillOnce(Invoke([&](const QuicPathFrameBuffer& payload,
                           const QuicSocketAddress&, const QuicSocketAddress&,
                           const QuicSocketAddress&, QuicPacketWriter*) {
        // Store up the 1st PATH_CHALLANGE payload.
        memcpy(challenge_data.data(), payload.data(), payload.size());
        return true;
      }))
      .WillOnce(Invoke([&](const QuicPathFrameBuffer& payload,
                           const QuicSocketAddress&, const QuicSocketAddress&,
                           const QuicSocketAddress&, QuicPacketWriter*) {
        EXPECT_NE(payload, challenge_data);
        return true;
      }));
  EXPECT_CALL(send_delegate_, GetRetryTimeout(peer_address_, &writer_))
      .Times(2u);
  const QuicTime start_time = clock_.Now();
  path_validator_.StartPathValidation(
      std::unique_ptr<QuicPathValidationContext>(context_),
      std::unique_ptr<MockQuicPathValidationResultDelegate>(result_delegate_),
      PathValidationReason::kMultiPort);

  clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(3 * kInitialRttMs));
  random_.ChangeValue();
  alarm_factory_.FireAlarm(
      QuicPathValidatorPeer::retry_timer(&path_validator_));

  EXPECT_CALL(*result_delegate_, OnPathValidationSuccess(_, start_time));
  // Respond to the 1st PATH_CHALLENGE should complete the validation.
  path_validator_.OnPathResponse(challenge_data, self_address_);
  EXPECT_FALSE(path_validator_.HasPendingPathValidation());
}

TEST_F(QuicPathValidatorTest, RespondToRetryChallenge) {
  QuicPathFrameBuffer challenge_data;
  EXPECT_CALL(send_delegate_,
              SendPathChallenge(_, self_address_, peer_address_,
                                effective_peer_address_, &writer_))
      .WillOnce(Invoke([&](const QuicPathFrameBuffer& payload,
                           const QuicSocketAddress&, const QuicSocketAddress&,
                           const QuicSocketAddress&, QuicPacketWriter*) {
        memcpy(challenge_data.data(), payload.data(), payload.size());
        return true;
      }))
      .WillOnce(Invoke([&](const QuicPathFrameBuffer& payload,
                           const QuicSocketAddress&, const QuicSocketAddress&,
                           const QuicSocketAddress&, QuicPacketWriter*) {
        EXPECT_NE(challenge_data, payload);
        memcpy(challenge_data.data(), payload.data(), payload.size());
        return true;
      }));
  EXPECT_CALL(send_delegate_, GetRetryTimeout(peer_address_, &writer_))
      .Times(2u);
  path_validator_.StartPathValidation(
      std::unique_ptr<QuicPathValidationContext>(context_),
      std::unique_ptr<MockQuicPathValidationResultDelegate>(result_delegate_),
      PathValidationReason::kMultiPort);

  clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(3 * kInitialRttMs));
  const QuicTime start_time = clock_.Now();
  random_.ChangeValue();
  alarm_factory_.FireAlarm(
      QuicPathValidatorPeer::retry_timer(&path_validator_));

  // Respond to the 2nd PATH_CHALLENGE should complete the validation.
  EXPECT_CALL(*result_delegate_, OnPathValidationSuccess(_, start_time));
  path_validator_.OnPathResponse(challenge_data, self_address_);
  EXPECT_FALSE(path_validator_.HasPendingPathValidation());
}

TEST_F(QuicPathValidatorTest, ValidationTimeOut) {
  EXPECT_CALL(send_delegate_,
              SendPathChallenge(_, self_address_, peer_address_,
                                effective_peer_address_, &writer_))
      .Times(3u)
      .WillRepeatedly(Return(true));
  EXPECT_CALL(send_delegate_, GetRetryTimeout(peer_address_, &writer_))
      .Times(3u);
  path_validator_.StartPathValidation(
      std::unique_ptr<QuicPathValidationContext>(context_),
      std::unique_ptr<MockQuicPathValidationResultDelegate>(result_delegate_),
      PathValidationReason::kMultiPort);

  QuicPathFrameBuffer challenge_data;
  memset(challenge_data.data(), 'a', challenge_data.size());
  // Reception of a PATH_RESPONSE with different payload should be ignored.
  path_validator_.OnPathResponse(challenge_data, self_address_);

  // Retry 3 times. The 3rd time should fail the validation.
  EXPECT_CALL(*result_delegate_, OnPathValidationFailure(_))
      .WillOnce(Invoke([=](std::unique_ptr<QuicPathValidationContext> context) {
        EXPECT_EQ(context_, context.get());
      }));
  for (size_t i = 0; i <= QuicPathValidator::kMaxRetryTimes; ++i) {
    clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(3 * kInitialRttMs));
    alarm_factory_.FireAlarm(
        QuicPathValidatorPeer::retry_timer(&path_validator_));
  }
  EXPECT_EQ(PathValidationReason::kReasonUnknown,
            path_validator_.GetPathValidationReason());
}

TEST_F(QuicPathValidatorTest, SendPathChallengeError) {
  EXPECT_CALL(send_delegate_,
              SendPathChallenge(_, self_address_, peer_address_,
                                effective_peer_address_, &writer_))
      .WillOnce(Invoke([&](const QuicPathFrameBuffer&, const QuicSocketAddress&,
                           const QuicSocketAddress&, const QuicSocketAddress&,
                           QuicPacketWriter*) {
        // Abandon this validation in the call stack shouldn't cause crash and
        // should cancel the alarm.
        path_validator_.CancelPathValidation();
        return false;
      }));
  EXPECT_CALL(send_delegate_, GetRetryTimeout(peer_address_, &writer_))
      .Times(0u);
  EXPECT_CALL(*result_delegate_, OnPathValidationFailure(_));
  path_validator_.StartPathValidation(
      std::unique_ptr<QuicPathValidationContext>(context_),
      std::unique_ptr<MockQuicPathValidationResultDelegate>(result_delegate_),
      PathValidationReason::kMultiPort);
  EXPECT_FALSE(path_validator_.HasPendingPathValidation());
  EXPECT_FALSE(QuicPathValidatorPeer::retry_timer(&path_validator_)->IsSet());
  EXPECT_EQ(PathValidationReason::kReasonUnknown,
            path_validator_.GetPathValidationReason());
}

}  // namespace test
}  // namespace quic
