// Copyright 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/tools/simple_ticket_crypter.h"

#include "quiche/quic/platform/api/quic_test.h"
#include "quiche/quic/test_tools/mock_clock.h"

namespace quic {
namespace test {

namespace {

constexpr QuicTime::Delta kOneDay = QuicTime::Delta::FromSeconds(60 * 60 * 24);

}  // namespace

class DecryptCallback : public quic::ProofSource::DecryptCallback {
 public:
  explicit DecryptCallback(std::vector<uint8_t>* out) : out_(out) {}

  void Run(std::vector<uint8_t> plaintext) override { *out_ = plaintext; }

 private:
  std::vector<uint8_t>* out_;
};

absl::string_view StringPiece(const std::vector<uint8_t>& in) {
  return absl::string_view(reinterpret_cast<const char*>(in.data()), in.size());
}

class SimpleTicketCrypterTest : public QuicTest {
 public:
  SimpleTicketCrypterTest() : ticket_crypter_(&mock_clock_) {}

 protected:
  MockClock mock_clock_;
  SimpleTicketCrypter ticket_crypter_;
};

TEST_F(SimpleTicketCrypterTest, EncryptDecrypt) {
  std::vector<uint8_t> plaintext = {1, 2, 3, 4, 5};
  std::vector<uint8_t> ciphertext =
      ticket_crypter_.Encrypt(StringPiece(plaintext), {});
  EXPECT_NE(plaintext, ciphertext);

  std::vector<uint8_t> out_plaintext;
  ticket_crypter_.Decrypt(StringPiece(ciphertext),
                          std::make_unique<DecryptCallback>(&out_plaintext));
  EXPECT_EQ(out_plaintext, plaintext);
}

TEST_F(SimpleTicketCrypterTest, CiphertextsDiffer) {
  std::vector<uint8_t> plaintext = {1, 2, 3, 4, 5};
  std::vector<uint8_t> ciphertext1 =
      ticket_crypter_.Encrypt(StringPiece(plaintext), {});
  std::vector<uint8_t> ciphertext2 =
      ticket_crypter_.Encrypt(StringPiece(plaintext), {});
  EXPECT_NE(ciphertext1, ciphertext2);
}

TEST_F(SimpleTicketCrypterTest, DecryptionFailureWithModifiedCiphertext) {
  std::vector<uint8_t> plaintext = {1, 2, 3, 4, 5};
  std::vector<uint8_t> ciphertext =
      ticket_crypter_.Encrypt(StringPiece(plaintext), {});
  EXPECT_NE(plaintext, ciphertext);

  // Check that a bit flip in any byte will cause a decryption failure.
  for (size_t i = 0; i < ciphertext.size(); i++) {
    SCOPED_TRACE(i);
    std::vector<uint8_t> munged_ciphertext = ciphertext;
    munged_ciphertext[i] ^= 1;
    std::vector<uint8_t> out_plaintext;
    ticket_crypter_.Decrypt(StringPiece(munged_ciphertext),
                            std::make_unique<DecryptCallback>(&out_plaintext));
    EXPECT_TRUE(out_plaintext.empty());
  }
}

TEST_F(SimpleTicketCrypterTest, DecryptionFailureWithEmptyCiphertext) {
  std::vector<uint8_t> out_plaintext;
  ticket_crypter_.Decrypt(absl::string_view(),
                          std::make_unique<DecryptCallback>(&out_plaintext));
  EXPECT_TRUE(out_plaintext.empty());
}

TEST_F(SimpleTicketCrypterTest, KeyRotation) {
  std::vector<uint8_t> plaintext = {1, 2, 3};
  std::vector<uint8_t> ciphertext =
      ticket_crypter_.Encrypt(StringPiece(plaintext), {});
  EXPECT_FALSE(ciphertext.empty());

  // Advance the clock 8 days, so the key used for |ciphertext| is now the
  // previous key. Check that decryption still works.
  mock_clock_.AdvanceTime(kOneDay * 8);
  std::vector<uint8_t> out_plaintext;
  ticket_crypter_.Decrypt(StringPiece(ciphertext),
                          std::make_unique<DecryptCallback>(&out_plaintext));
  EXPECT_EQ(out_plaintext, plaintext);

  // Advance the clock 8 more days. Now the original key should be expired and
  // decryption should fail.
  mock_clock_.AdvanceTime(kOneDay * 8);
  ticket_crypter_.Decrypt(StringPiece(ciphertext),
                          std::make_unique<DecryptCallback>(&out_plaintext));
  EXPECT_TRUE(out_plaintext.empty());
}

}  // namespace test
}  // namespace quic
