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

#include "components/os_crypt/async/browser/os_crypt_async.h"

#include <memory>
#include <optional>
#include <vector>

#include "base/functional/bind.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/test_future.h"
#include "chrome/browser/browser_process.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "components/os_crypt/async/browser/test_utils.h"
#include "components/os_crypt/async/common/encryptor.h"
#include "content/public/browser/service_process_host.h"
#include "content/public/test/browser_test.h"
#include "services/test/echo/public/mojom/echo.mojom.h"

namespace os_crypt_async {

namespace {

scoped_refptr<Encryptor> GetInstanceSync(OSCryptAsync& factory) {
  base::test::TestFuture<scoped_refptr<Encryptor>> future;
  factory.GetInstance(future.GetCallback());
  return future.Take();
}

}  // namespace

class OSCryptAsyncBrowserTest : public InProcessBrowserTest {
 protected:
  base::HistogramTester histogram_tester_;
};

// Test the basic browser interface to Encrypt and Decrypt data.
IN_PROC_BROWSER_TEST_F(OSCryptAsyncBrowserTest, EncryptDecrypt) {
  auto encryptor = GetInstanceSync(*g_browser_process->os_crypt_async());
  // These histograms should always have been recorded by the time the
  // GetInstance callback above has happened, since the browser registers its
  // metrics callback before anything else gets a chance to.
  histogram_tester_.ExpectTotalCount("OSCrypt.AsyncInitialization.Time", 1u);

  auto ciphertext = encryptor->EncryptString("plaintext");
  ASSERT_TRUE(ciphertext);

  auto decrypted = encryptor->DecryptData(*ciphertext);
  ASSERT_TRUE(decrypted);

  EXPECT_EQ(*decrypted, "plaintext");
}

// This test verifies that an Encryptor works inside a fully sandboxed process.
IN_PROC_BROWSER_TEST_F(OSCryptAsyncBrowserTest, SandboxedEncryptionTest) {
  // Use a testing instance, otherwise fallback to os_crypt sync does not work
  // on all platforms when inside a sandbox without transferring the key over
  // manually.
  auto os_crypt_async = GetTestOSCryptAsyncForTesting();
  scoped_refptr<Encryptor> encryptor = GetInstanceSync(*os_crypt_async);

  constexpr char kTestData[] = "testdatatest";
  // First, encrypt the data.
  const auto encrypted_data = encryptor->EncryptString(kTestData);
  ASSERT_TRUE(encrypted_data.has_value());

  // Launch sandboxed echo service.
  auto echo_service =
      content::ServiceProcessHost::Launch<echo::mojom::EchoService>();

  base::test::TestFuture<std::optional<std::vector<uint8_t>>> future;

  // This performs a decrypt, then an encrypt.
  echo_service->DecryptEncrypt(
      std::move(encryptor), *encrypted_data,
      future.GetCallback<const std::optional<std::vector<uint8_t>>&>());
  auto& result = future.Get();
  ASSERT_TRUE(result.has_value());

  // Obtain a second encryptor, since the first one was consumed in the mojo
  // call above.
  scoped_refptr<Encryptor> encryptor2 = GetInstanceSync(*os_crypt_async);
  // Finally, decrypt the data again.
  const auto plaintext = encryptor2->DecryptData(*result);
  ASSERT_TRUE(plaintext.has_value());
  EXPECT_EQ(*plaintext, kTestData);
}


}  // namespace os_crypt_async
