// Copyright 2016 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 "components/certificate_transparency/tree_state_tracker.h"

#include <memory>
#include <string>
#include <utility>

#include "base/run_loop.h"
#include "base/strings/string_number_conversions.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/scoped_task_environment.h"
#include "components/certificate_transparency/features.h"
#include "net/base/net_errors.h"
#include "net/cert/ct_log_verifier.h"
#include "net/cert/ct_serialization.h"
#include "net/cert/merkle_tree_leaf.h"
#include "net/cert/signed_certificate_timestamp.h"
#include "net/cert/signed_tree_head.h"
#include "net/cert/x509_certificate.h"
#include "net/dns/mock_host_resolver.h"
#include "net/log/net_log.h"
#include "net/log/test_net_log.h"
#include "net/test/ct_test_util.h"
#include "testing/gtest/include/gtest/gtest.h"

using net::ct::SignedCertificateTimestamp;
using net::ct::SignedTreeHead;
using net::ct::GetSampleSignedTreeHead;
using net::ct::GetTestPublicKeyId;
using net::ct::GetTestPublicKey;
using net::ct::kSthRootHashLength;
using net::ct::GetX509CertSCT;

constexpr char kHostname[] = "example.test";
constexpr base::TimeDelta kZeroTTL;

namespace certificate_transparency {

class TreeStateTrackerTest : public ::testing::Test {
  void SetUp() override {
    log_ = net::CTLogVerifier::Create(GetTestPublicKey(), "testlog",
                                      "unresolvable.invalid");

    ASSERT_TRUE(log_);
    ASSERT_EQ(log_->key_id(), GetTestPublicKeyId());

    const std::string der_test_cert(net::ct::GetDerEncodedX509Cert());
    chain_ = net::X509Certificate::CreateFromBytes(der_test_cert.data(),
                                                   der_test_cert.length());
    ASSERT_TRUE(chain_.get());
    GetX509CertSCT(&cert_sct_);
    cert_sct_->origin = SignedCertificateTimestamp::SCT_FROM_OCSP_RESPONSE;
  }

 protected:
  base::test::ScopedTaskEnvironment task_environment_{
      base::test::ScopedTaskEnvironment::MainThreadType::IO};
  scoped_refptr<const net::CTLogVerifier> log_;
  net::MockCachingHostResolver host_resolver_;
  std::unique_ptr<TreeStateTracker> tree_tracker_;
  scoped_refptr<net::X509Certificate> chain_;
  scoped_refptr<SignedCertificateTimestamp> cert_sct_;
  net::TestNetLog net_log_;
};

// Test that a new STH & SCT are delegated correctly to a
// SingleTreeTracker instance created by the TreeStateTracker.
// This is verified by looking for a single event on the net_log_
// passed into the TreeStateTracker c'tor.
TEST_F(TreeStateTrackerTest, TestDelegatesCorrectly) {
  std::vector<scoped_refptr<const net::CTLogVerifier>> verifiers;
  verifiers.push_back(log_);

  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndEnableFeature(kCTLogAuditing);

  tree_tracker_ =
      std::make_unique<TreeStateTracker>(verifiers, &host_resolver_, &net_log_);

  // Add a cache entry for kHostname that indicates it was looked up over DNS.
  // SingleTreeTracker requires this before it will request an inclusion proof,
  // as otherwise it would reveal to the DNS resolver which server a user
  // visited. If the server was already looked up via DNS though, that that
  // information is already known to the DNS resolver so there is then no harm.
  host_resolver_.GetHostCache()->Set(
      net::HostCache::Key(kHostname, net::ADDRESS_FAMILY_UNSPECIFIED, 0),
      net::HostCache::Entry(net::OK, net::AddressList(),
                            net::HostCache::Entry::SOURCE_DNS),
      base::TimeTicks::Now(), kZeroTTL);

  SignedTreeHead sth;
  GetSampleSignedTreeHead(&sth);
  ASSERT_EQ(log_->key_id(), sth.log_id);
  tree_tracker_->NewSTHObserved(sth);

  ASSERT_EQ(log_->key_id(), cert_sct_->log_id);
  tree_tracker_->OnSCTVerified(kHostname, chain_.get(), cert_sct_.get());
  base::RunLoop().RunUntilIdle();

  net::ct::MerkleTreeLeaf leaf;
  ASSERT_TRUE(GetMerkleTreeLeaf(chain_.get(), cert_sct_.get(), &leaf));

  std::string leaf_hash;
  ASSERT_TRUE(HashMerkleTreeLeaf(leaf, &leaf_hash));
  // There should be one NetLog event.
  EXPECT_EQ(1u, net_log_.GetSize());
}

}  // namespace certificate_transparency
