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

#include "net/cert/coalescing_cert_verifier.h"

#include <memory>

#include "base/functional/bind.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "net/base/net_errors.h"
#include "net/base/test_completion_callback.h"
#include "net/cert/mock_cert_verifier.h"
#include "net/cert/x509_certificate.h"
#include "net/log/net_log_with_source.h"
#include "net/test/cert_test_util.h"
#include "net/test/gtest_util.h"
#include "net/test/test_data_directory.h"
#include "net/test/test_with_task_environment.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

using net::test::IsError;
using net::test::IsOk;

namespace net {

using CoalescingCertVerifierTest = TestWithTaskEnvironment;

// Tests that synchronous completion does not cause any issues.
TEST_F(CoalescingCertVerifierTest, SyncCompletion) {
  scoped_refptr<X509Certificate> test_cert(
      ImportCertFromFile(GetTestCertsDirectory(), "ok_cert.pem"));
  ASSERT_TRUE(test_cert);

  CertVerifyResult fake_result;
  fake_result.verified_cert = test_cert;

  std::unique_ptr<MockCertVerifier> mock_verifier_owner =
      std::make_unique<MockCertVerifier>();
  MockCertVerifier* mock_verifier = mock_verifier_owner.get();
  mock_verifier->set_async(false);  // Force sync completion.
  mock_verifier->AddResultForCert(test_cert, fake_result, OK);

  CoalescingCertVerifier verifier(std::move(mock_verifier_owner));

  CertVerifier::RequestParams request_params(test_cert, "www.example.com", 0,
                                             /*ocsp_response=*/std::string(),
                                             /*sct_list=*/std::string());

  CertVerifyResult result1, result2;
  TestCompletionCallback callback1, callback2;
  std::unique_ptr<CertVerifier::Request> request1, request2;

  // Start an (asynchronous) initial request.
  int error = verifier.Verify(request_params, &result1, callback1.callback(),
                              &request1, NetLogWithSource());
  ASSERT_THAT(error, IsOk());
  ASSERT_FALSE(request1);
  ASSERT_TRUE(result1.verified_cert);
}

// Test that requests with identical parameters only result in a single
// underlying verification; that is, the second Request is joined to the
// in-progress first Request.
TEST_F(CoalescingCertVerifierTest, InflightJoin) {
  scoped_refptr<X509Certificate> test_cert(
      ImportCertFromFile(GetTestCertsDirectory(), "ok_cert.pem"));
  ASSERT_TRUE(test_cert);

  base::HistogramTester histograms;

  CertVerifyResult fake_result;
  fake_result.verified_cert = test_cert;

  std::unique_ptr<MockCertVerifier> mock_verifier_owner =
      std::make_unique<MockCertVerifier>();
  MockCertVerifier* mock_verifier = mock_verifier_owner.get();
  mock_verifier->set_async(true);  // Always complete via PostTask
  mock_verifier->AddResultForCert(test_cert, fake_result, OK);

  CoalescingCertVerifier verifier(std::move(mock_verifier_owner));

  CertVerifier::RequestParams request_params(test_cert, "www.example.com", 0,
                                             /*ocsp_response=*/std::string(),
                                             /*sct_list=*/std::string());

  CertVerifyResult result1, result2;
  TestCompletionCallback callback1, callback2;
  std::unique_ptr<CertVerifier::Request> request1, request2;

  // Start an (asynchronous) initial request.
  int error = verifier.Verify(request_params, &result1, callback1.callback(),
                              &request1, NetLogWithSource());
  ASSERT_THAT(error, IsError(ERR_IO_PENDING));
  EXPECT_TRUE(request1);

  // Simulate the underlying verifier returning different results if another
  // verification is done.
  mock_verifier->ClearRules();
  mock_verifier->AddResultForCert(test_cert, fake_result, ERR_CERT_REVOKED);

  // Start a second request; this should join the first request.
  error = verifier.Verify(request_params, &result2, callback2.callback(),
                          &request2, NetLogWithSource());
  ASSERT_THAT(error, IsError(ERR_IO_PENDING));
  EXPECT_TRUE(request2);

  // Ensure only one request was ever started.
  EXPECT_EQ(2u, verifier.requests_for_testing());
  EXPECT_EQ(1u, verifier.inflight_joins_for_testing());

  // Make sure both results completed.
  EXPECT_THAT(callback1.WaitForResult(), IsOk());
  EXPECT_THAT(callback2.WaitForResult(), IsOk());

  // There should only have been one Job started.
  histograms.ExpectTotalCount("Net.CertVerifier_Job_Latency", 1);
  histograms.ExpectTotalCount("Net.CertVerifier_First_Job_Latency", 1);
}

// Test that changing configurations between Requests prevents the second
// Request from being attached to the first Request. There should be two
// Requests to the underlying CertVerifier, and the correct results should be
// received by each.
TEST_F(CoalescingCertVerifierTest, DoesNotJoinAfterConfigChange) {
  scoped_refptr<X509Certificate> test_cert(
      ImportCertFromFile(GetTestCertsDirectory(), "ok_cert.pem"));
  ASSERT_TRUE(test_cert);

  base::HistogramTester histograms;

  CertVerifyResult fake_result;
  fake_result.verified_cert = test_cert;

  std::unique_ptr<MockCertVerifier> mock_verifier_owner =
      std::make_unique<MockCertVerifier>();
  MockCertVerifier* mock_verifier = mock_verifier_owner.get();
  mock_verifier->set_async(true);  // Always complete via PostTask
  mock_verifier->AddResultForCert(test_cert, fake_result, OK);

  CoalescingCertVerifier verifier(std::move(mock_verifier_owner));

  CertVerifier::Config config1;
  verifier.SetConfig(config1);

  CertVerifier::RequestParams request_params(test_cert, "www.example.com", 0,
                                             /*ocsp_response=*/std::string(),
                                             /*sct_list=*/std::string());

  CertVerifyResult result1, result2;
  TestCompletionCallback callback1, callback2;
  std::unique_ptr<CertVerifier::Request> request1, request2;

  // Start an (asynchronous) initial request.
  int error = verifier.Verify(request_params, &result1, callback1.callback(),
                              &request1, NetLogWithSource());
  ASSERT_THAT(error, IsError(ERR_IO_PENDING));
  EXPECT_TRUE(request1);

  // Change the configuration, and change the result to to simulate the
  // configuration change affecting behavior.
  CertVerifier::Config config2;
  config2.enable_rev_checking = !config1.enable_rev_checking;
  verifier.SetConfig(config2);
  mock_verifier->ClearRules();
  mock_verifier->AddResultForCert(test_cert, fake_result, ERR_CERT_REVOKED);

  // Start a second request; this should not join the first request, as the
  // config is different.
  error = verifier.Verify(request_params, &result2, callback2.callback(),
                          &request2, NetLogWithSource());
  ASSERT_THAT(error, IsError(ERR_IO_PENDING));
  EXPECT_TRUE(request2);

  // Ensure a total of two requests were started, and neither were joined.
  EXPECT_EQ(2u, verifier.requests_for_testing());
  EXPECT_EQ(0u, verifier.inflight_joins_for_testing());

  // Make sure both results completed.
  EXPECT_THAT(callback1.WaitForResult(), IsOk());
  EXPECT_THAT(callback2.WaitForResult(), IsError(ERR_CERT_REVOKED));

  // There should have been two separate Jobs.
  histograms.ExpectTotalCount("Net.CertVerifier_Job_Latency", 2);
  histograms.ExpectTotalCount("Net.CertVerifier_First_Job_Latency", 1);
}

// Test that the underlying CertVerifier changing configurations and triggering
// an OnCertVerifierChanged notification between Requests prevents the second
// Request from being attached to the first Request. There should be two
// Requests to the underlying CertVerifier, and the correct results should be
// received by each.
TEST_F(CoalescingCertVerifierTest, DoesNotJoinAfterUnderlyingVerifierChange) {
  scoped_refptr<X509Certificate> test_cert(
      ImportCertFromFile(GetTestCertsDirectory(), "ok_cert.pem"));
  ASSERT_TRUE(test_cert);

  base::HistogramTester histograms;

  CertVerifyResult fake_result;
  fake_result.verified_cert = test_cert;

  std::unique_ptr<MockCertVerifier> mock_verifier_owner =
      std::make_unique<MockCertVerifier>();
  MockCertVerifier* mock_verifier = mock_verifier_owner.get();
  mock_verifier->set_async(true);  // Always complete via PostTask
  mock_verifier->AddResultForCert(test_cert, fake_result, OK);

  CoalescingCertVerifier verifier(std::move(mock_verifier_owner));

  mock_verifier->SimulateOnCertVerifierChanged();

  CertVerifier::RequestParams request_params(test_cert, "www.example.com", 0,
                                             /*ocsp_response=*/std::string(),
                                             /*sct_list=*/std::string());

  CertVerifyResult result1, result2;
  TestCompletionCallback callback1, callback2;
  std::unique_ptr<CertVerifier::Request> request1, request2;

  // Start an (asynchronous) initial request.
  int error = verifier.Verify(request_params, &result1, callback1.callback(),
                              &request1, NetLogWithSource());
  ASSERT_THAT(error, IsError(ERR_IO_PENDING));
  EXPECT_TRUE(request1);

  // Change the configuration, and change the result to to simulate the
  // configuration change affecting behavior.
  mock_verifier->SimulateOnCertVerifierChanged();
  mock_verifier->ClearRules();
  mock_verifier->AddResultForCert(test_cert, fake_result, ERR_CERT_REVOKED);

  // Start a second request; this should not join the first request, as the
  // config is different.
  error = verifier.Verify(request_params, &result2, callback2.callback(),
                          &request2, NetLogWithSource());
  ASSERT_THAT(error, IsError(ERR_IO_PENDING));
  EXPECT_TRUE(request2);

  // Ensure a total of two requests were started, and neither were joined.
  EXPECT_EQ(2u, verifier.requests_for_testing());
  EXPECT_EQ(0u, verifier.inflight_joins_for_testing());

  // Make sure both results completed.
  EXPECT_THAT(callback1.WaitForResult(), IsOk());
  EXPECT_THAT(callback2.WaitForResult(), IsError(ERR_CERT_REVOKED));

  // There should have been two separate Jobs.
  histograms.ExpectTotalCount("Net.CertVerifier_Job_Latency", 2);
  histograms.ExpectTotalCount("Net.CertVerifier_First_Job_Latency", 1);
}

TEST_F(CoalescingCertVerifierTest, ObserverIsForwarded) {
  auto mock_cert_verifier_owner = std::make_unique<MockCertVerifier>();
  MockCertVerifier* mock_cert_verifier = mock_cert_verifier_owner.get();
  CoalescingCertVerifier verifier(std::move(mock_cert_verifier_owner));

  CertVerifierObserverCounter observer_(&verifier);
  EXPECT_EQ(observer_.change_count(), 0u);
  // A CertVerifierChanged event on the wrapped verifier should be forwarded to
  // observers registered on CoalescingCertVerifier.
  mock_cert_verifier->SimulateOnCertVerifierChanged();
  EXPECT_EQ(observer_.change_count(), 1u);
}

// Test that when two Requests are attached to the same Job, it's safe to
// delete the second Request while processing the response to the first. The
// second Request should not cause the second callback to be called.
TEST_F(CoalescingCertVerifierTest, DeleteSecondRequestDuringFirstCompletion) {
  scoped_refptr<X509Certificate> test_cert(
      ImportCertFromFile(GetTestCertsDirectory(), "ok_cert.pem"));
  ASSERT_TRUE(test_cert);

  CertVerifyResult fake_result;
  fake_result.verified_cert = test_cert;

  std::unique_ptr<MockCertVerifier> mock_verifier_owner =
      std::make_unique<MockCertVerifier>();
  MockCertVerifier* mock_verifier = mock_verifier_owner.get();
  mock_verifier->set_async(true);  // Always complete via PostTask
  mock_verifier->AddResultForCert(test_cert, fake_result, OK);

  CoalescingCertVerifier verifier(std::move(mock_verifier_owner));

  CertVerifier::RequestParams request_params(test_cert, "www.example.com", 0,
                                             /*ocsp_response=*/std::string(),
                                             /*sct_list=*/std::string());

  CertVerifyResult result1, result2;
  TestCompletionCallback callback1, callback2;
  std::unique_ptr<CertVerifier::Request> request1, request2;

  // Start an (asynchronous) initial request. When this request is completed,
  // it will delete (reset) |request2|, which should prevent it from being
  // called.
  int error = verifier.Verify(
      request_params, &result1,
      base::BindLambdaForTesting([&callback1, &request2](int result) {
        request2.reset();
        callback1.callback().Run(result);
      }),
      &request1, NetLogWithSource());
  ASSERT_THAT(error, IsError(ERR_IO_PENDING));
  EXPECT_TRUE(request1);

  // Start a second request; this should join the first request.
  error = verifier.Verify(request_params, &result2, callback2.callback(),
                          &request2, NetLogWithSource());
  ASSERT_THAT(error, IsError(ERR_IO_PENDING));
  EXPECT_TRUE(request2);

  // Ensure only one underlying verification was started.
  ASSERT_EQ(2u, verifier.requests_for_testing());
  ASSERT_EQ(1u, verifier.inflight_joins_for_testing());

  // Make sure that only the first callback is invoked; because the second
  // CertVerifier::Request was deleted during processing the first's callback,
  // the second callback should not be invoked.
  EXPECT_THAT(callback1.WaitForResult(), IsOk());
  ASSERT_FALSE(callback2.have_result());
  ASSERT_FALSE(request2);

  // While CoalescingCertVerifier doesn't use PostTask, make sure to flush the
  // tasks as well, in case the implementation changes in the future.
  RunUntilIdle();
  ASSERT_FALSE(callback2.have_result());
  ASSERT_FALSE(request2);
}

// Test that it's safe to delete the CoalescingCertVerifier during completion,
// even when there are outstanding Requests to be processed. The additional
// Requests should not invoke the user callback once the
// CoalescingCertVerifier is deleted.
TEST_F(CoalescingCertVerifierTest, DeleteVerifierDuringCompletion) {
  scoped_refptr<X509Certificate> test_cert(
      ImportCertFromFile(GetTestCertsDirectory(), "ok_cert.pem"));
  ASSERT_TRUE(test_cert);

  CertVerifyResult fake_result;
  fake_result.verified_cert = test_cert;

  std::unique_ptr<MockCertVerifier> mock_verifier_owner =
      std::make_unique<MockCertVerifier>();
  MockCertVerifier* mock_verifier = mock_verifier_owner.get();
  mock_verifier->set_async(true);  // Always complete via PostTask
  mock_verifier->AddResultForCert(test_cert, fake_result, OK);

  auto verifier =
      std::make_unique<CoalescingCertVerifier>(std::move(mock_verifier_owner));

  CertVerifier::RequestParams request_params(test_cert, "www.example.com", 0,
                                             /*ocsp_response=*/std::string(),
                                             /*sct_list=*/std::string());

  CertVerifyResult result1, result2;
  TestCompletionCallback callback1, callback2;
  std::unique_ptr<CertVerifier::Request> request1, request2;

  // Start an (asynchronous) initial request. When this request is completed,
  // it will delete (reset) |request2|, which should prevent it from being
  // called.
  int error = verifier->Verify(
      request_params, &result1,
      base::BindLambdaForTesting([&callback1, &verifier](int result) {
        verifier.reset();
        callback1.callback().Run(result);
      }),
      &request1, NetLogWithSource());
  ASSERT_THAT(error, IsError(ERR_IO_PENDING));
  EXPECT_TRUE(request1);

  // Start a second request; this should join the first request.
  error = verifier->Verify(request_params, &result2, callback2.callback(),
                           &request2, NetLogWithSource());
  ASSERT_THAT(error, IsError(ERR_IO_PENDING));
  EXPECT_TRUE(request2);

  // Ensure only one underlying verification was started.
  ASSERT_EQ(2u, verifier->requests_for_testing());
  ASSERT_EQ(1u, verifier->inflight_joins_for_testing());

  // Make sure that only the first callback is invoked. This will delete the
  // underlying CoalescingCertVerifier, which should prevent the second
  // request's callback from being invoked.
  EXPECT_THAT(callback1.WaitForResult(), IsOk());
  ASSERT_FALSE(callback2.have_result());
  ASSERT_TRUE(request2);

  // While CoalescingCertVerifier doesn't use PostTask, make sure to flush the
  // tasks as well, in case the implementation changes in the future.
  RunUntilIdle();
  ASSERT_FALSE(callback2.have_result());
  ASSERT_TRUE(request2);
}

// Test that it's safe to delete a Request before the underlying verifier has
// completed. This is a guard against memory safety (e.g. when this Request
// is the last/only Request remaining).
TEST_F(CoalescingCertVerifierTest, DeleteRequestBeforeCompletion) {
  scoped_refptr<X509Certificate> test_cert(
      ImportCertFromFile(GetTestCertsDirectory(), "ok_cert.pem"));
  ASSERT_TRUE(test_cert);

  CertVerifyResult fake_result;
  fake_result.verified_cert = test_cert;

  std::unique_ptr<MockCertVerifier> mock_verifier_owner =
      std::make_unique<MockCertVerifier>();
  MockCertVerifier* mock_verifier = mock_verifier_owner.get();
  mock_verifier->set_async(true);  // Always complete via PostTask
  mock_verifier->AddResultForCert(test_cert, fake_result, OK);

  CoalescingCertVerifier verifier(std::move(mock_verifier_owner));

  CertVerifier::RequestParams request_params(test_cert, "www.example.com", 0,
                                             /*ocsp_response=*/std::string(),
                                             /*sct_list=*/std::string());

  CertVerifyResult result1;
  TestCompletionCallback callback1;
  std::unique_ptr<CertVerifier::Request> request1;

  // Start an (asynchronous) initial request.
  int error = verifier.Verify(request_params, &result1, callback1.callback(),
                              &request1, NetLogWithSource());
  ASSERT_THAT(error, IsError(ERR_IO_PENDING));
  EXPECT_TRUE(request1);

  // Abandon the request before it's completed.
  request1.reset();
  EXPECT_FALSE(callback1.have_result());

  // Make sure the request never completes / the callback is never invoked.
  RunUntilIdle();
  EXPECT_FALSE(callback1.have_result());
}

// Test that it's safe to delete a Request before the underlying verifier has
// completed. This is a correctness test, to ensure that other Requests are
// still notified.
TEST_F(CoalescingCertVerifierTest,
       DeleteFirstRequestBeforeCompletionStillCompletesSecondRequest) {
  scoped_refptr<X509Certificate> test_cert(
      ImportCertFromFile(GetTestCertsDirectory(), "ok_cert.pem"));
  ASSERT_TRUE(test_cert);

  CertVerifyResult fake_result;
  fake_result.verified_cert = test_cert;

  std::unique_ptr<MockCertVerifier> mock_verifier_owner =
      std::make_unique<MockCertVerifier>();
  MockCertVerifier* mock_verifier = mock_verifier_owner.get();
  mock_verifier->set_async(true);  // Always complete via PostTask
  mock_verifier->AddResultForCert(test_cert, fake_result, OK);

  CoalescingCertVerifier verifier(std::move(mock_verifier_owner));

  CertVerifier::RequestParams request_params(test_cert, "www.example.com", 0,
                                             /*ocsp_response=*/std::string(),
                                             /*sct_list=*/std::string());

  CertVerifyResult result1, result2;
  TestCompletionCallback callback1, callback2;
  std::unique_ptr<CertVerifier::Request> request1, request2;

  // Start an (asynchronous) initial request.
  int error = verifier.Verify(request_params, &result1, callback1.callback(),
                              &request1, NetLogWithSource());
  ASSERT_THAT(error, IsError(ERR_IO_PENDING));
  EXPECT_TRUE(request1);

  // Start a second request; this should join the first request.
  error = verifier.Verify(request_params, &result2, callback2.callback(),
                          &request2, NetLogWithSource());
  ASSERT_THAT(error, IsError(ERR_IO_PENDING));
  EXPECT_TRUE(request2);

  // Ensure only one underlying verification was started.
  ASSERT_EQ(2u, verifier.requests_for_testing());
  ASSERT_EQ(1u, verifier.inflight_joins_for_testing());

  // Abandon the first request before it's completed.
  request1.reset();

  // Make sure the first request never completes / the callback is never
  // invoked, while the second request completes normally.
  EXPECT_THAT(callback2.WaitForResult(), IsOk());
  EXPECT_FALSE(callback1.have_result());

  // Simulate the second request going away during processing.
  request2.reset();

  // Flush any events, although there should not be any.
  RunUntilIdle();
  EXPECT_FALSE(callback1.have_result());
}

TEST_F(CoalescingCertVerifierTest, DeleteRequestDuringCompletion) {
  scoped_refptr<X509Certificate> test_cert(
      ImportCertFromFile(GetTestCertsDirectory(), "ok_cert.pem"));
  ASSERT_TRUE(test_cert);

  CertVerifyResult fake_result;
  fake_result.verified_cert = test_cert;

  std::unique_ptr<MockCertVerifier> mock_verifier_owner =
      std::make_unique<MockCertVerifier>();
  MockCertVerifier* mock_verifier = mock_verifier_owner.get();
  mock_verifier->set_async(true);  // Always complete via PostTask
  mock_verifier->AddResultForCert(test_cert, fake_result, OK);

  CoalescingCertVerifier verifier(std::move(mock_verifier_owner));

  CertVerifier::RequestParams request_params(test_cert, "www.example.com", 0,
                                             /*ocsp_response=*/std::string(),
                                             /*sct_list=*/std::string());

  CertVerifyResult result1;
  TestCompletionCallback callback1;
  std::unique_ptr<CertVerifier::Request> request1;

  // Start an (asynchronous) initial request.
  int error = verifier.Verify(
      request_params, &result1,
      base::BindLambdaForTesting([&callback1, &request1](int result) {
        // Delete the Request during the completion callback. This should be
        // perfectly safe, and not cause any memory trouble, because the
        // Request was already detached from the Job prior to being invoked.
        request1.reset();
        callback1.callback().Run(result);
      }),
      &request1, NetLogWithSource());
  ASSERT_THAT(error, IsError(ERR_IO_PENDING));
  EXPECT_TRUE(request1);

  // The result should be available, even though the request is deleted
  // during the result processing. This should not cause any memory errors.
  EXPECT_THAT(callback1.WaitForResult(), IsOk());
}

TEST_F(CoalescingCertVerifierTest, DeleteVerifierBeforeRequest) {
  scoped_refptr<X509Certificate> test_cert(
      ImportCertFromFile(GetTestCertsDirectory(), "ok_cert.pem"));
  ASSERT_TRUE(test_cert);

  base::HistogramTester histograms;

  CertVerifyResult fake_result;
  fake_result.verified_cert = test_cert;

  std::unique_ptr<MockCertVerifier> mock_verifier_owner =
      std::make_unique<MockCertVerifier>();
  MockCertVerifier* mock_verifier = mock_verifier_owner.get();
  mock_verifier->set_async(true);  // Always complete via PostTask
  mock_verifier->AddResultForCert(test_cert, fake_result, OK);

  auto verifier =
      std::make_unique<CoalescingCertVerifier>(std::move(mock_verifier_owner));

  CertVerifier::RequestParams request_params(test_cert, "www.example.com", 0,
                                             /*ocsp_response=*/std::string(),
                                             /*sct_list=*/std::string());

  CertVerifyResult result1;
  TestCompletionCallback callback1;
  std::unique_ptr<CertVerifier::Request> request1;

  // Start an (asynchronous) initial request.
  int error = verifier->Verify(request_params, &result1, callback1.callback(),
                               &request1, NetLogWithSource());
  ASSERT_THAT(error, IsError(ERR_IO_PENDING));
  EXPECT_TRUE(request1);

  // Delete the CoalescingCertVerifier first. This should orphan all
  // outstanding Requests and delete all associated Jobs.
  verifier.reset();

  // Flush any pending tasks; there should not be any, at this point, but use
  // it in case the implementation changes.
  RunUntilIdle();

  // Make sure the callback was never called.
  EXPECT_FALSE(callback1.have_result());

  // Delete the Request. This should be a no-op as the Request was orphaned
  // when the CoalescingCertVerifier was deleted.
  request1.reset();

  // There should not have been any histograms logged.
  histograms.ExpectTotalCount("Net.CertVerifier_Job_Latency", 0);
  histograms.ExpectTotalCount("Net.CertVerifier_First_Job_Latency", 0);
}

}  // namespace net
