// Copyright (c) 2011 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 "chrome/browser/chromeos/cros/cryptohome_library.h"

#include "base/command_line.h"
#include "base/hash_tables.h"
#include "base/message_loop.h"
#include "chrome/browser/chromeos/cros/cros_library.h"
#include "chrome/common/chrome_switches.h"
#include "content/browser/browser_thread.h"

namespace chromeos {

// This class handles the interaction with the ChromeOS cryptohome library APIs.
class CryptohomeLibraryImpl : public CryptohomeLibrary {
 public:
  CryptohomeLibraryImpl() {
    if (CrosLibrary::Get()->EnsureLoaded())
      Init();
  }
  virtual ~CryptohomeLibraryImpl() {}

  bool CheckKey(const std::string& user_email, const std::string& passhash) {
    return chromeos::CryptohomeCheckKey(user_email.c_str(), passhash.c_str());
  }

  bool AsyncCheckKey(const std::string& user_email,
                     const std::string& passhash,
                     Delegate* d) {
    return CacheCallback(
        chromeos::CryptohomeAsyncCheckKey(user_email.c_str(), passhash.c_str()),
        d,
        "Couldn't initiate async check of user's key.");
  }

  bool MigrateKey(const std::string& user_email,
                  const std::string& old_hash,
                  const std::string& new_hash) {
    return chromeos::CryptohomeMigrateKey(user_email.c_str(),
                                          old_hash.c_str(),
                                          new_hash.c_str());
  }

  bool AsyncMigrateKey(const std::string& user_email,
                       const std::string& old_hash,
                       const std::string& new_hash,
                       Delegate* d) {
    return CacheCallback(
        chromeos::CryptohomeAsyncMigrateKey(user_email.c_str(),
                                            old_hash.c_str(),
                                            new_hash.c_str()),
        d,
        "Couldn't initiate aync migration of user's key");
  }

  bool Mount(const std::string& user_email,
             const std::string& passhash,
             int* error_code) {
    return chromeos::CryptohomeMountAllowFail(user_email.c_str(),
                                              passhash.c_str(),
                                              error_code);
  }

  bool AsyncMount(const std::string& user_email,
                  const std::string& passhash,
                  const bool create_if_missing,
                  Delegate* d) {
    return CacheCallback(
        chromeos::CryptohomeAsyncMountSafe(user_email.c_str(),
                                           passhash.c_str(),
                                           create_if_missing,
                                           false,
                                           NULL),
        d,
        "Couldn't initiate async mount of cryptohome.");
  }

  bool MountForBwsi(int* error_code) {
    return chromeos::CryptohomeMountGuest(error_code);
  }

  bool AsyncMountForBwsi(Delegate* d) {
    return CacheCallback(chromeos::CryptohomeAsyncMountGuest(),
                         d,
                         "Couldn't initiate async mount of cryptohome.");
  }

  bool Unmount() {
    return chromeos::CryptohomeUnmount();
  }

  bool Remove(const std::string& user_email) {
    return chromeos::CryptohomeRemove(user_email.c_str());
  }

  bool AsyncRemove(const std::string& user_email, Delegate* d) {
    return CacheCallback(
        chromeos::CryptohomeAsyncRemove(user_email.c_str()),
        d,
        "Couldn't initiate async removal of cryptohome.");
  }

  bool IsMounted() {
    return chromeos::CryptohomeIsMounted();
  }

  CryptohomeBlob GetSystemSalt() {
    CryptohomeBlob system_salt;
    char* salt_buf;
    int salt_len;
    bool result = chromeos::CryptohomeGetSystemSaltSafe(&salt_buf, &salt_len);
    if (result) {
      system_salt.resize(salt_len);
      if ((int)system_salt.size() == salt_len) {
        memcpy(&system_salt[0], static_cast<const void*>(salt_buf),
               salt_len);
      } else {
        system_salt.clear();
      }
    }
    return system_salt;
  }

  bool AsyncDoAutomaticFreeDiskSpaceControl(Delegate* d) {
    return CacheCallback(
        chromeos::CryptohomeAsyncDoAutomaticFreeDiskSpaceControl(),
        d,
        "Couldn't do automatic free disk space control.");
  }

  bool TpmIsReady() {
    return chromeos::CryptohomeTpmIsReady();
  }

  bool TpmIsEnabled() {
    return chromeos::CryptohomeTpmIsEnabled();
  }

  bool TpmIsOwned() {
    return chromeos::CryptohomeTpmIsOwned();
  }

  bool TpmIsBeingOwned() {
    return chromeos::CryptohomeTpmIsBeingOwned();
  }

  bool TpmGetPassword(std::string* password) {
    char *password_buf;
    bool result = chromeos::CryptohomeTpmGetPasswordSafe(&password_buf);
    *password = password_buf;
    chromeos::CryptohomeFreeString(password_buf);
    return result;
  }

  void TpmCanAttemptOwnership() {
    chromeos::CryptohomeTpmCanAttemptOwnership();
  }

  void TpmClearStoredPassword() {
    chromeos::CryptohomeTpmClearStoredPassword();
  }

  bool InstallAttributesGet(const std::string& name, std::string* value) {
    char* local_value;
    bool done =
        chromeos::CryptohomeInstallAttributesGet(name.c_str(), &local_value);
    if (done) {
      *value = local_value;
      chromeos::CryptohomeFreeString(local_value);
    }
    return done;
  }

  bool InstallAttributesSet(const std::string& name, const std::string& value) {
    return chromeos::CryptohomeInstallAttributesSet(name.c_str(),
                                                    value.c_str());
  }

  int InstallAttributesCount() {
    return chromeos::CryptohomeInstallAttributesCount();
  }

  bool InstallAttributesFinalize() {
    return chromeos::CryptohomeInstallAttributesFinalize();
  }

  bool InstallAttributesIsReady() {
    return chromeos::CryptohomeInstallAttributesIsReady();
  }

  bool InstallAttributesIsSecure() {
    return chromeos::CryptohomeInstallAttributesIsSecure();
  }

  bool InstallAttributesIsInvalid() {
    return chromeos::CryptohomeInstallAttributesIsInvalid();
  }

  bool InstallAttributesIsFirstInstall() {
    return chromeos::CryptohomeInstallAttributesIsFirstInstall();
  }

  void Pkcs11GetTpmTokenInfo(std::string* label, std::string* user_pin) {
    chromeos::CryptohomePkcs11GetTpmTokenInfo(label, user_pin);
  }

  bool Pkcs11IsTpmTokenReady() {
    return chromeos::CryptohomePkcs11IsTpmTokenReady();
  }

 private:
  static void Handler(const chromeos::CryptohomeAsyncCallStatus& event,
                      void* cryptohome_library) {
    CryptohomeLibraryImpl* library =
        reinterpret_cast<CryptohomeLibraryImpl*>(cryptohome_library);
    library->Dispatch(event);
  }

  void Init() {
    cryptohome_connection_ = chromeos::CryptohomeMonitorSession(&Handler, this);
  }

  void Dispatch(const chromeos::CryptohomeAsyncCallStatus& event) {
    const CallbackMap::iterator callback = callback_map_.find(event.async_id);
    if (callback == callback_map_.end()) {
      LOG(ERROR) << "Received signal for unknown async_id " << event.async_id;
      return;
    }
    if (callback->second)
      callback->second->OnComplete(event.return_status, event.return_code);
    callback_map_.erase(callback);
  }

  bool CacheCallback(int async_id, Delegate* d, const char* error) {
    if (async_id == 0) {
      LOG(ERROR) << error;
      return false;
    }
    VLOG(1) << "Adding handler for " << async_id;
    callback_map_[async_id] = d;
    return true;
  }

  typedef base::hash_map<int, Delegate*> CallbackMap;
  mutable CallbackMap callback_map_;

  void* cryptohome_connection_;

  DISALLOW_COPY_AND_ASSIGN(CryptohomeLibraryImpl);
};

class CryptohomeLibraryStubImpl : public CryptohomeLibrary {
 public:
  CryptohomeLibraryStubImpl()
    : locked_(false) {}
  virtual ~CryptohomeLibraryStubImpl() {}

  bool CheckKey(const std::string& user_email, const std::string& passhash) {
    return true;
  }

  bool AsyncCheckKey(const std::string& user_email,
                     const std::string& passhash,
                     Delegate* callback) {
    BrowserThread::PostTask(
        BrowserThread::UI, FROM_HERE,
        NewRunnableFunction(&DoStubCallback, callback));
    return true;
  }

  bool MigrateKey(const std::string& user_email,
                  const std::string& old_hash,
                  const std::string& new_hash) {
    return true;
  }

  bool AsyncMigrateKey(const std::string& user_email,
                       const std::string& old_hash,
                       const std::string& new_hash,
                       Delegate* callback) {
    BrowserThread::PostTask(
        BrowserThread::UI, FROM_HERE,
        NewRunnableFunction(&DoStubCallback, callback));
    return true;
  }

  bool Mount(const std::string& user_email,
             const std::string& passhash,
             int* error_code) {
    // For testing password change.
    if (user_email ==
        CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
            switches::kLoginUserWithNewPassword)) {
      *error_code = kCryptohomeMountErrorKeyFailure;
      return false;
    }

    return true;
  }

  bool AsyncMount(const std::string& user_email,
                  const std::string& passhash,
                  const bool create_if_missing,
                  Delegate* callback) {
    BrowserThread::PostTask(
        BrowserThread::UI, FROM_HERE,
        NewRunnableFunction(&DoStubCallback, callback));
    return true;
  }

  bool MountForBwsi(int* error_code) {
    return true;
  }

  bool AsyncMountForBwsi(Delegate* callback) {
    BrowserThread::PostTask(
        BrowserThread::UI, FROM_HERE,
        NewRunnableFunction(&DoStubCallback, callback));
    return true;
  }

  bool Unmount() {
    return true;
  }

  bool Remove(const std::string& user_email) {
    return true;
  }

  bool AsyncRemove(const std::string& user_email, Delegate* callback) {
    BrowserThread::PostTask(
        BrowserThread::UI, FROM_HERE,
        NewRunnableFunction(&DoStubCallback, callback));
    return true;
  }

  bool IsMounted() {
    return true;
  }

  CryptohomeBlob GetSystemSalt() {
    CryptohomeBlob salt = CryptohomeBlob();
    salt.push_back(0);
    salt.push_back(0);
    return salt;
  }

  bool AsyncDoAutomaticFreeDiskSpaceControl(Delegate* callback) {
    BrowserThread::PostTask(
        BrowserThread::UI, FROM_HERE,
        NewRunnableFunction(&DoStubCallback, callback));
    return true;
  }

  // Tpm begin ready after 20-th call.
  bool TpmIsReady() {
    static int counter = 0;
    return ++counter > 20;
  }

  bool TpmIsEnabled() {
    return true;
  }

  bool TpmIsOwned() {
    return true;
  }

  bool TpmIsBeingOwned() {
    return true;
  }

  bool TpmGetPassword(std::string* password) {
    *password = "Stub-TPM-password";
    return true;
  }

  void TpmCanAttemptOwnership() {}

  void TpmClearStoredPassword() {}

  bool InstallAttributesGet(const std::string& name, std::string* value) {
    if (install_attrs_.find(name) != install_attrs_.end()) {
      *value = install_attrs_[name];
      return true;
    }
    return false;
  }

  bool InstallAttributesSet(const std::string& name, const std::string& value) {
    install_attrs_[name] = value;
    return true;
  }

  int InstallAttributesCount() {
    return install_attrs_.size();
  }

  bool InstallAttributesFinalize() {
    locked_ = true;
    return true;
  }

  bool InstallAttributesIsReady() {
    return true;
  }

  bool InstallAttributesIsSecure() {
    return false;
  }

  bool InstallAttributesIsInvalid() {
    return false;
  }

  bool InstallAttributesIsFirstInstall() {
    return !locked_;
  }

  void Pkcs11GetTpmTokenInfo(std::string* label,
                             std::string* user_pin) {
    *label = "Stub TPM Token";
    *user_pin = "012345";
  }

  bool Pkcs11IsTpmTokenReady() { return true; }

 private:
  static void DoStubCallback(Delegate* callback) {
    if (callback)
      callback->OnComplete(true, kCryptohomeMountErrorNone);
  }

  std::map<std::string, std::string> install_attrs_;
  bool locked_;
  DISALLOW_COPY_AND_ASSIGN(CryptohomeLibraryStubImpl);
};

CryptohomeLibrary::CryptohomeLibrary() {}
CryptohomeLibrary::~CryptohomeLibrary() {}

// static
CryptohomeLibrary* CryptohomeLibrary::GetImpl(bool stub) {
  if (stub)
    return new CryptohomeLibraryStubImpl();
  else
    return new CryptohomeLibraryImpl();
}

} // namespace chromeos