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

#include <utility>

#include "absl/strings/string_view.h"
#include "quiche/quic/core/proto/crypto_server_config_proto.h"
#include "quiche/quic/platform/api/quic_test.h"
#include "quiche/quic/test_tools/quic_test_utils.h"

namespace quic::test {
namespace {

class TestProofHandler : public QuicCryptoClientStream::ProofHandler {
 public:
  ~TestProofHandler() override {}
  void OnProofValid(
      const QuicCryptoClientConfig::CachedState& /*cached*/) override {}
  void OnProofVerifyDetailsAvailable(
      const ProofVerifyDetails& /*verify_details*/) override {}
};

class InsecureProofVerifier : public ProofVerifier {
 public:
  InsecureProofVerifier() {}
  ~InsecureProofVerifier() override {}

  // ProofVerifier override.
  QuicAsyncStatus VerifyProof(
      const std::string& /*hostname*/, const uint16_t /*port*/,
      const std::string& /*server_config*/,
      QuicTransportVersion /*transport_version*/,
      absl::string_view /*chlo_hash*/,
      const std::vector<std::string>& /*certs*/,
      const std::string& /*cert_sct*/, const std::string& /*signature*/,
      const ProofVerifyContext* /*context*/, std::string* /*error_details*/,
      std::unique_ptr<ProofVerifyDetails>* /*verify_details*/,
      std::unique_ptr<ProofVerifierCallback> /*callback*/) override {
    return QUIC_SUCCESS;
  }

  QuicAsyncStatus VerifyCertChain(
      const std::string& /*hostname*/, const uint16_t /*port*/,
      const std::vector<std::string>& /*certs*/,
      const std::string& /*ocsp_response*/, const std::string& /*cert_sct*/,
      const ProofVerifyContext* /*context*/, std::string* /*error_details*/,
      std::unique_ptr<ProofVerifyDetails>* /*details*/, uint8_t* /*out_alert*/,
      std::unique_ptr<ProofVerifierCallback> /*callback*/) override {
    return QUIC_SUCCESS;
  }

  std::unique_ptr<ProofVerifyContext> CreateDefaultContext() override {
    return nullptr;
  }
};

class DummyProofSource : public ProofSource {
 public:
  DummyProofSource() {}
  ~DummyProofSource() override {}

  // ProofSource override.
  void GetProof(const QuicSocketAddress& server_address,
                const QuicSocketAddress& client_address,
                const std::string& hostname,
                const std::string& /*server_config*/,
                QuicTransportVersion /*transport_version*/,
                absl::string_view /*chlo_hash*/,
                std::unique_ptr<Callback> callback) override {
    bool cert_matched_sni;
    quiche::QuicheReferenceCountedPointer<ProofSource::Chain> chain =
        GetCertChain(server_address, client_address, hostname,
                     &cert_matched_sni);
    QuicCryptoProof proof;
    proof.signature = "Dummy signature";
    proof.leaf_cert_scts = "Dummy timestamp";
    proof.cert_matched_sni = cert_matched_sni;
    callback->Run(true, chain, proof, /*details=*/nullptr);
  }

  quiche::QuicheReferenceCountedPointer<Chain> GetCertChain(
      const QuicSocketAddress& /*server_address*/,
      const QuicSocketAddress& /*client_address*/,
      const std::string& /*hostname*/, bool* /*cert_matched_sni*/) override {
    std::vector<std::string> certs;
    certs.push_back("Dummy cert");
    return quiche::QuicheReferenceCountedPointer<ProofSource::Chain>(
        new ProofSource::Chain(certs));
  }

  void ComputeTlsSignature(
      const QuicSocketAddress& /*server_address*/,
      const QuicSocketAddress& /*client_address*/,
      const std::string& /*hostname*/, uint16_t /*signature_algorit*/,
      absl::string_view /*in*/,
      std::unique_ptr<SignatureCallback> callback) override {
    callback->Run(true, "Dummy signature", /*details=*/nullptr);
  }

  absl::InlinedVector<uint16_t, 8> SupportedTlsSignatureAlgorithms()
      const override {
    return {};
  }

  TicketCrypter* GetTicketCrypter() override { return nullptr; }
};

class Handshaker : public QuicCryptoClientHandshaker {
 public:
  Handshaker(const QuicServerId& server_id, QuicCryptoClientStream* stream,
             QuicSession* session,
             std::unique_ptr<ProofVerifyContext> verify_context,
             QuicCryptoClientConfig* crypto_config,
             QuicCryptoClientStream::ProofHandler* proof_handler)
      : QuicCryptoClientHandshaker(server_id, stream, session,
                                   std::move(verify_context), crypto_config,
                                   proof_handler) {}

  void DoSendCHLOTest(QuicCryptoClientConfig::CachedState* cached) {
    QuicCryptoClientHandshaker::DoSendCHLO(cached);
  }
};

class QuicCryptoClientHandshakerTest
    : public QuicTestWithParam<ParsedQuicVersion> {
 protected:
  QuicCryptoClientHandshakerTest()
      : version_(GetParam()),
        proof_handler_(),
        helper_(),
        alarm_factory_(),
        server_id_("host", 123),
        connection_(new test::MockQuicConnection(
            &helper_, &alarm_factory_, Perspective::IS_CLIENT, {version_})),
        session_(connection_, false),
        crypto_client_config_(std::make_unique<InsecureProofVerifier>()),
        client_stream_(
            new QuicCryptoClientStream(server_id_, &session_, nullptr,
                                       &crypto_client_config_, &proof_handler_,
                                       /*has_application_state = */ false)),
        handshaker_(server_id_, client_stream_, &session_, nullptr,
                    &crypto_client_config_, &proof_handler_),
        state_() {
    // Session takes the ownership of the client stream! (but handshaker also
    // takes a reference to it, but doesn't take the ownership).
    session_.SetCryptoStream(client_stream_);
    session_.Initialize();
  }

  void InitializeServerParametersToEnableFullHello() {
    QuicCryptoServerConfig::ConfigOptions options;
    QuicServerConfigProtobuf config = QuicCryptoServerConfig::GenerateConfig(
        helper_.GetRandomGenerator(), helper_.GetClock(), options);
    state_.Initialize(
        config.config(), "sourcetoken", std::vector<std::string>{"Dummy cert"},
        "", "chlo_hash", "signature", helper_.GetClock()->WallNow(),
        helper_.GetClock()->WallNow().Add(QuicTime::Delta::FromSeconds(30)));

    state_.SetProofValid();
  }

  ParsedQuicVersion version_;
  TestProofHandler proof_handler_;
  test::MockQuicConnectionHelper helper_;
  test::MockAlarmFactory alarm_factory_;
  QuicServerId server_id_;
  // Session takes the ownership of the connection.
  test::MockQuicConnection* connection_;
  test::MockQuicSession session_;
  QuicCryptoClientConfig crypto_client_config_;
  QuicCryptoClientStream* client_stream_;
  Handshaker handshaker_;
  QuicCryptoClientConfig::CachedState state_;
};

INSTANTIATE_TEST_SUITE_P(
    QuicCryptoClientHandshakerTests, QuicCryptoClientHandshakerTest,
    ::testing::ValuesIn(AllSupportedVersionsWithQuicCrypto()),
    ::testing::PrintToStringParamName());

TEST_P(QuicCryptoClientHandshakerTest, TestSendFullPaddingInInchoateHello) {
  handshaker_.DoSendCHLOTest(&state_);

  EXPECT_TRUE(connection_->fully_pad_during_crypto_handshake());
}

TEST_P(QuicCryptoClientHandshakerTest, TestDisabledPaddingInInchoateHello) {
  crypto_client_config_.set_pad_inchoate_hello(false);
  handshaker_.DoSendCHLOTest(&state_);
  EXPECT_FALSE(connection_->fully_pad_during_crypto_handshake());
}

TEST_P(QuicCryptoClientHandshakerTest,
       TestPaddingInFullHelloEvenIfInchoateDisabled) {
  // Disable inchoate, but full hello should still be padded.
  crypto_client_config_.set_pad_inchoate_hello(false);

  InitializeServerParametersToEnableFullHello();

  handshaker_.DoSendCHLOTest(&state_);
  EXPECT_TRUE(connection_->fully_pad_during_crypto_handshake());
}

TEST_P(QuicCryptoClientHandshakerTest, TestNoPaddingInFullHelloWhenDisabled) {
  crypto_client_config_.set_pad_full_hello(false);

  InitializeServerParametersToEnableFullHello();

  handshaker_.DoSendCHLOTest(&state_);
  EXPECT_FALSE(connection_->fully_pad_during_crypto_handshake());
}

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