// Copyright 2020 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/trusted_vault/trusted_vault_connection_impl.h"

#include <algorithm>
#include <string>
#include <utility>
#include <variant>

#include "base/base64url.h"
#include "base/containers/span.h"
#include "base/files/important_file_writer.h"
#include "base/functional/bind.h"
#include "base/metrics/histogram_functions.h"
#include "base/time/time.h"
#include "components/signin/public/identity_manager/account_info.h"
#include "components/trusted_vault/download_keys_response_handler.h"
#include "components/trusted_vault/features.h"
#include "components/trusted_vault/proto/vault.pb.h"
#include "components/trusted_vault/proto_string_bytes_conversion.h"
#include "components/trusted_vault/securebox.h"
#include "components/trusted_vault/standalone_trusted_vault_server_constants.h"
#include "components/trusted_vault/trusted_vault_access_token_fetcher.h"
#include "components/trusted_vault/trusted_vault_connection.h"
#include "components/trusted_vault/trusted_vault_crypto.h"
#include "components/trusted_vault/trusted_vault_histograms.h"
#include "components/trusted_vault/trusted_vault_request.h"
#include "components/trusted_vault/trusted_vault_server_constants.h"
#include "net/base/url_util.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "third_party/abseil-cpp/absl/functional/overload.h"

namespace trusted_vault {

namespace {

TrustedVaultRequest::RecordFetchStatusCallback MakeFetchStatusCallback(
    SecurityDomainId security_domain_id,
    TrustedVaultURLFetchReasonForUMA reason) {
  return base::BindRepeating(&RecordTrustedVaultURLFetchResponse,
                             security_domain_id, reason);
}

// Returns security domain epoch if valid (>0) and nullopt otherwise.
std::optional<int> GetLastKeyVersionFromJoinSecurityDomainsResponse(
    const trusted_vault_pb::JoinSecurityDomainsResponse response) {
  if (response.security_domain().current_epoch() > 0) {
    return response.security_domain().current_epoch();
  }
  return std::nullopt;
}

// Returns security domain epoch if input is a valid response for already exists
// error case and nullopt otherwise.
std::optional<int> GetLastKeyVersionFromAlreadyExistsResponse(
    const std::string& response_body) {
  trusted_vault_pb::RPCStatus rpc_status;
  rpc_status.ParseFromString(response_body);
  for (const trusted_vault_pb::Proto3Any& status_detail :
       rpc_status.details()) {
    if (status_detail.type_url() != kJoinSecurityDomainsErrorDetailTypeURL) {
      continue;
    }
    trusted_vault_pb::JoinSecurityDomainsErrorDetail error_detail;
    error_detail.ParseFromString(status_detail.value());
    return GetLastKeyVersionFromJoinSecurityDomainsResponse(
        error_detail.already_exists_response());
  }
  return std::nullopt;
}

trusted_vault_pb::SharedMemberKey CreateSharedMemberKey(
    const TrustedVaultKeyAndVersion& trusted_vault_key_and_version,
    const SecureBoxPublicKey& public_key) {
  trusted_vault_pb::SharedMemberKey shared_member_key;
  shared_member_key.set_epoch(trusted_vault_key_and_version.version);

  const std::vector<uint8_t>& trusted_vault_key =
      trusted_vault_key_and_version.key;
  AssignBytesToProtoString(
      ComputeTrustedVaultWrappedKey(public_key, trusted_vault_key),
      shared_member_key.mutable_wrapped_key());
  AssignBytesToProtoString(ComputeMemberProof(public_key, trusted_vault_key),
                           shared_member_key.mutable_member_proof());
  return shared_member_key;
}

trusted_vault_pb::SharedMemberKey CreateSharedMemberKey(
    const MemberKeys& precomputed) {
  trusted_vault_pb::SharedMemberKey shared_member_key;
  shared_member_key.set_epoch(precomputed.version);

  AssignBytesToProtoString(precomputed.wrapped_key,
                           shared_member_key.mutable_wrapped_key());
  AssignBytesToProtoString(precomputed.proof,
                           shared_member_key.mutable_member_proof());
  return shared_member_key;
}

trusted_vault_pb::PhysicalDeviceMetadata::DeviceType
GetLocalPhysicalDeviceType() {
  // Note that some of the below are unreachable in practice as this code isn't
  // currently used or even built on all platforms.
#if BUILDFLAG(IS_CHROMEOS)
  return trusted_vault_pb::PhysicalDeviceMetadata::DEVICE_TYPE_CHROMEOS;
#elif BUILDFLAG(IS_LINUX)
  return trusted_vault_pb::PhysicalDeviceMetadata::DEVICE_TYPE_LINUX;
#elif BUILDFLAG(IS_ANDROID)
  return trusted_vault_pb::PhysicalDeviceMetadata::DEVICE_TYPE_ANDROID;
#elif BUILDFLAG(IS_IOS)
  return trusted_vault_pb::PhysicalDeviceMetadata::DEVICE_TYPE_IOS;
#elif BUILDFLAG(IS_MAC)
  return trusted_vault_pb::PhysicalDeviceMetadata::DEVICE_TYPE_MAC_OS;
#elif BUILDFLAG(IS_WIN)
  return trusted_vault_pb::PhysicalDeviceMetadata::DEVICE_TYPE_WINDOWS;
#elif BUILDFLAG(IS_FUCHSIA)
  // Not used in Fuchsia.
  return trusted_vault_pb::PhysicalDeviceMetadata::DEVICE_TYPE_UNKNOWN;
#else
#error Please handle your new device OS here.
#endif
}

trusted_vault_pb::SecurityDomainMember CreateSecurityDomainMember(
    const SecureBoxPublicKey& public_key,
    AuthenticationFactorTypeAndRegistrationParams
        authentication_factor_type_and_registration_params) {
  trusted_vault_pb::SecurityDomainMember member;
  std::string public_key_string;
  AssignBytesToProtoString(public_key.ExportToBytes(), &public_key_string);

  std::string encoded_public_key;
  base::Base64UrlEncode(public_key_string,
                        base::Base64UrlEncodePolicy::OMIT_PADDING,
                        &encoded_public_key);

  member.set_name(kSecurityDomainMemberNamePrefix + encoded_public_key);
  // Note: |public_key_string| using here is intentional, encoding is required
  // only to compute member name.
  member.set_public_key(public_key_string);

  std::visit(
      absl::Overload{
          [&member](const LocalPhysicalDevice&) {
            member.set_member_type(trusted_vault_pb::SecurityDomainMember::
                                       MEMBER_TYPE_PHYSICAL_DEVICE);
            auto* member_metadata = member.mutable_member_metadata();
            auto* physical_device_metadata =
                member_metadata->mutable_physical_device_metadata();
            physical_device_metadata->set_device_type(
                GetLocalPhysicalDeviceType());
          },
          [&member](const LockScreenKnowledgeFactor&) {
            member.set_member_type(trusted_vault_pb::SecurityDomainMember::
                                       MEMBER_TYPE_LOCKSCREEN_KNOWLEDGE_FACTOR);
          },
          [&member](const UnspecifiedAuthenticationFactorType&) {
            member.set_member_type(trusted_vault_pb::SecurityDomainMember::
                                       MEMBER_TYPE_UNSPECIFIED);
            // The type hint field is in the request protobuf, not the
            // security domain member, and so is set in
            // `CreateJoinSecurityDomainsRequest`.
          },
          [&member](const GpmPinMetadata& gpm_pin_metadata) {
            member.set_member_type(trusted_vault_pb::SecurityDomainMember::
                                       MEMBER_TYPE_GOOGLE_PASSWORD_MANAGER_PIN);
            auto* member_metadata = member.mutable_member_metadata();
            auto* pin_metadata =
                member_metadata->mutable_google_password_manager_pin_metadata();
            pin_metadata->set_encrypted_pin_hash(
                gpm_pin_metadata.usable_pin_metadata->wrapped_pin);
          },
          [&member](const ICloudKeychain&) {
            member.set_member_type(trusted_vault_pb::SecurityDomainMember::
                                       MEMBER_TYPE_ICLOUD_KEYCHAIN);
          }},
      authentication_factor_type_and_registration_params);
  return member;
}

void AddSharedMemberKeysFromSource(
    trusted_vault_pb::JoinSecurityDomainsRequest* request,
    const SecureBoxPublicKey& public_key,
    const MemberKeysSource& member_keys_source) {
  std::visit(
      absl::Overload{
          [request, &public_key](
              const std::vector<TrustedVaultKeyAndVersion>& key_and_versions) {
            for (const TrustedVaultKeyAndVersion&
                     trusted_vault_key_and_version : key_and_versions) {
              *request->add_shared_member_key() = CreateSharedMemberKey(
                  trusted_vault_key_and_version, public_key);
            }
          },
          [request](const MemberKeys& precomputed) {
            *request->add_shared_member_key() =
                CreateSharedMemberKey(precomputed);
          }},
      member_keys_source);
}

trusted_vault_pb::JoinSecurityDomainsRequest CreateJoinSecurityDomainsRequest(
    SecurityDomainId security_domain,
    const MemberKeysSource& member_keys_source,
    const SecureBoxPublicKey& public_key,
    AuthenticationFactorTypeAndRegistrationParams
        authentication_factor_type_and_registration_params) {
  trusted_vault_pb::JoinSecurityDomainsRequest request;
  request.mutable_security_domain()->set_name(
      GetSecurityDomainPath(security_domain));
  *request.mutable_security_domain_member() = CreateSecurityDomainMember(
      public_key, authentication_factor_type_and_registration_params);
  AddSharedMemberKeysFromSource(&request, public_key, member_keys_source);
  if (auto* unspecified_type = std::get_if<UnspecifiedAuthenticationFactorType>(
          &authentication_factor_type_and_registration_params)) {
    request.set_member_type_hint(unspecified_type->value());
  } else if (auto* gpm_pin_metadata = std::get_if<GpmPinMetadata>(
                 &authentication_factor_type_and_registration_params)) {
    if (gpm_pin_metadata->public_key) {
      request.set_current_public_key_to_replace(*gpm_pin_metadata->public_key);
    }
  }
  return request;
}

void RunRegisterAuthenticationFactorCallback(
    TrustedVaultConnection::RegisterAuthenticationFactorCallback callback,
    TrustedVaultRegistrationStatus status,
    int last_key_version) {
  std::move(callback).Run(status, last_key_version);
}

void ProcessJoinSecurityDomainsResponse(
    TrustedVaultConnectionImpl::JoinSecurityDomainsCallback callback,
    TrustedVaultRequest::HttpStatus http_status,
    const std::string& response_body) {
  switch (http_status) {
    case TrustedVaultRequest::HttpStatus::kSuccess:
    case TrustedVaultRequest::HttpStatus::kConflict:
      break;
    case TrustedVaultRequest::HttpStatus::kNetworkError:
      std::move(callback).Run(TrustedVaultRegistrationStatus::kNetworkError,
                              /*last_key_version=*/0);
      return;
    case TrustedVaultRequest::HttpStatus::kOtherError:
      std::move(callback).Run(TrustedVaultRegistrationStatus::kOtherError,
                              /*last_key_version=*/0);
      return;
    case TrustedVaultRequest::HttpStatus::kTransientAccessTokenFetchError:
      std::move(callback).Run(
          TrustedVaultRegistrationStatus::kTransientAccessTokenFetchError,
          /*last_key_version=*/0);
      return;
    case TrustedVaultRequest::HttpStatus::kPersistentAccessTokenFetchError:
      std::move(callback).Run(
          TrustedVaultRegistrationStatus::kPersistentAccessTokenFetchError,
          /*last_key_version=*/0);
      return;
    case TrustedVaultRequest::HttpStatus::
        kPrimaryAccountChangeAccessTokenFetchError:
      std::move(callback).Run(TrustedVaultRegistrationStatus::
                                  kPrimaryAccountChangeAccessTokenFetchError,
                              /*last_key_version=*/0);
      return;
    case TrustedVaultRequest::HttpStatus::kNotFound:
    case TrustedVaultRequest::HttpStatus::kBadRequest:
      // Local trusted vault keys are outdated.
      std::move(callback).Run(
          TrustedVaultRegistrationStatus::kLocalDataObsolete,
          /*last_key_version=*/0);
      return;
  }

  std::optional<int> last_key_version;
  if (http_status == TrustedVaultRequest::HttpStatus::kConflict) {
    last_key_version =
        GetLastKeyVersionFromAlreadyExistsResponse(response_body);
  } else {
    trusted_vault_pb::JoinSecurityDomainsResponse response;
    response.ParseFromString(response_body);
    last_key_version =
        GetLastKeyVersionFromJoinSecurityDomainsResponse(response);
  }

  if (!last_key_version.has_value()) {
    std::move(callback).Run(TrustedVaultRegistrationStatus::kOtherError,
                            /*last_key_version=*/0);
    return;
  }
  std::move(callback).Run(
      http_status == TrustedVaultRequest::HttpStatus::kConflict
          ? TrustedVaultRegistrationStatus::kAlreadyRegistered
          : TrustedVaultRegistrationStatus::kSuccess,
      *last_key_version);
}

base::Time ToTime(const trusted_vault_pb::Timestamp& proto) {
  return base::Time::UnixEpoch() + base::Seconds(proto.seconds()) +
         base::Nanoseconds(proto.nanos());
}

void ProcessDownloadKeysResponse(
    std::unique_ptr<DownloadKeysResponseHandler> response_handler,
    TrustedVaultConnection::DownloadNewKeysCallback callback,
    TrustedVaultRequest::HttpStatus http_status,
    const std::string& response_body) {
  DownloadKeysResponseHandler::ProcessedResponse processed_response =
      response_handler->ProcessResponse(http_status, response_body);
  std::move(callback).Run(processed_response.status,
                          processed_response.downloaded_keys,
                          processed_response.last_key_version);
}

void ProcessDownloadIsRecoverabilityDegradedResponse(
    TrustedVaultConnection::IsRecoverabilityDegradedCallback callback,
    TrustedVaultRequest::HttpStatus http_status,
    const std::string& response_body) {
  // TODO(crbug.com/40178774): consider special handling when security domain
  // doesn't exist.
  switch (http_status) {
    case TrustedVaultRequest::HttpStatus::kSuccess:
      break;
    case TrustedVaultRequest::HttpStatus::kNetworkError:
    case TrustedVaultRequest::HttpStatus::kOtherError:
    case TrustedVaultRequest::HttpStatus::kNotFound:
    case TrustedVaultRequest::HttpStatus::kBadRequest:
    case TrustedVaultRequest::HttpStatus::kConflict:
    case TrustedVaultRequest::HttpStatus::kTransientAccessTokenFetchError:
    case TrustedVaultRequest::HttpStatus::kPersistentAccessTokenFetchError:
    case TrustedVaultRequest::HttpStatus::
        kPrimaryAccountChangeAccessTokenFetchError:
      std::move(callback).Run(TrustedVaultRecoverabilityStatus::kError);
      return;
  }
  trusted_vault_pb::SecurityDomain security_domain;
  if (!security_domain.ParseFromString(response_body) ||
      !security_domain.security_domain_details().has_sync_details()) {
    std::move(callback).Run(TrustedVaultRecoverabilityStatus::kError);
    return;
  }
  TrustedVaultRecoverabilityStatus status =
      TrustedVaultRecoverabilityStatus::kNotDegraded;
  if (security_domain.security_domain_details()
          .sync_details()
          .degraded_recoverability()) {
    status = TrustedVaultRecoverabilityStatus::kDegraded;
  }
  std::move(callback).Run(status);
}

class DownloadAuthenticationFactorsRegistrationStateRequest
    : public TrustedVaultConnection::Request {
 public:
  DownloadAuthenticationFactorsRegistrationStateRequest(
      SecurityDomainId security_domain,
      const GURL& request_url,
      const CoreAccountId& account_id,
      scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
      std::unique_ptr<TrustedVaultAccessTokenFetcher> access_token_fetcher,
      TrustedVaultConnection::
          DownloadAuthenticationFactorsRegistrationStateCallback callback,
      base::RepeatingClosure keep_alive_callback)
      : security_domain_(security_domain),
        base_url_(request_url),
        account_id_(account_id),
        url_loader_factory_(std::move(url_loader_factory)),
        access_token_fetcher_(std::move(access_token_fetcher)),
        callback_(std::move(callback)),
        keep_alive_callback_(std::move(keep_alive_callback)) {
    result_.state =
        DownloadAuthenticationFactorsRegistrationStateResult::State::kEmpty;
    StartOrContinueRequest();
  }

 private:
  void StartOrContinueRequest(const std::string* next_page_token = nullptr) {
    request_ = std::make_unique<TrustedVaultRequest>(
        security_domain_, account_id_, TrustedVaultRequest::HttpMethod::kGet,
        next_page_token ? net::AppendQueryParameter(base_url_, "page_token",
                                                    *next_page_token)
                        : base_url_,
        /*serialized_request_proto=*/std::nullopt,
        /*max_retry_duration=*/base::Seconds(0), url_loader_factory_,
        access_token_fetcher_->Clone(),
        MakeFetchStatusCallback(
            security_domain_,
            TrustedVaultURLFetchReasonForUMA::
                kDownloadAuthenticationFactorsRegistrationState));

    // Unretained: this object owns `request_`. When `request_` is deleted, so
    // is the `SimpleURLLoader` and thus any callback is canceled.
    request_->FetchAccessTokenAndSendRequest(base::BindOnce(
        &DownloadAuthenticationFactorsRegistrationStateRequest::ProcessResponse,
        base::Unretained(this)));
  }

  void ProcessResponse(TrustedVaultRequest::HttpStatus http_status,
                       const std::string& response_body) {
    if (http_status != TrustedVaultRequest::HttpStatus::kSuccess) {
      result_.state =
          DownloadAuthenticationFactorsRegistrationStateResult::State::kError;
      FinishWithResultAndMaybeDestroySelf();
      return;
    }

    trusted_vault_pb::ListSecurityDomainMembersResponse response;
    if (!response.ParseFromString(response_body)) {
      result_.state =
          DownloadAuthenticationFactorsRegistrationStateResult::State::kError;
      FinishWithResultAndMaybeDestroySelf();
      return;
    }

    const std::string security_domain_name =
        GetSecurityDomainPath(security_domain_);
    for (const auto& member : response.security_domain_members()) {
      bool is_member_of_domain = false;
      for (const auto& membership : member.memberships()) {
        if (membership.security_domain() == security_domain_name) {
          is_member_of_domain = true;
          for (const auto& key : membership.keys()) {
            const int key_version = key.epoch();
            if (key_version != 0 &&
                (!result_.key_version.has_value() ||
                 result_.key_version.value() < key_version)) {
              result_.key_version = key_version;
            }
          }
          break;
        }
      }
      if (!is_member_of_domain) {
        continue;
      }

      if (member.has_member_metadata() &&
          member.member_metadata().usable_for_retrieval()) {
        result_.state = DownloadAuthenticationFactorsRegistrationStateResult::
            State::kRecoverable;
      } else if (result_.state ==
                 DownloadAuthenticationFactorsRegistrationStateResult::State::
                     kEmpty) {
        result_.state = DownloadAuthenticationFactorsRegistrationStateResult::
            State::kIrrecoverable;
      }

      if (member.member_type() == trusted_vault_pb::SecurityDomainMember::
                                      MEMBER_TYPE_GOOGLE_PASSWORD_MANAGER_PIN) {
        if (member.member_metadata()
                .has_google_password_manager_pin_metadata()) {
          pin_status_ = TrustedVaultListSecurityDomainMembersPinStatus::
              kPinPresentAndUsableForRecovery;
          const auto& pin_metadata =
              member.member_metadata().google_password_manager_pin_metadata();
          result_.gpm_pin_metadata = GpmPinMetadata(
              member.public_key(), UsableRecoveryPinMetadata(
                                       pin_metadata.encrypted_pin_hash(),
                                       ToTime(pin_metadata.expiration_time())));
        } else {
          result_.gpm_pin_metadata = GpmPinMetadata(
              member.public_key(), /*pin_metadata=*/std::nullopt);
          pin_status_ = TrustedVaultListSecurityDomainMembersPinStatus::
              kPinPresentButUnusableForRecovery;
        }
      } else if (member.member_type() ==
                 trusted_vault_pb::SecurityDomainMember::
                     MEMBER_TYPE_ICLOUD_KEYCHAIN) {
        std::unique_ptr<SecureBoxPublicKey> public_key =
            SecureBoxPublicKey::CreateByImport(
                ProtoStringToBytes(member.public_key()));
        if (!public_key) {
          continue;
        }
        const auto& membership = std::ranges::find_if(
            member.memberships(),
            [&security_domain_name](const auto& membership) {
              return membership.security_domain() == security_domain_name;
            });
        CHECK(membership != member.memberships().end())
            << "iCloud member should have been tested for membership above";
        std::vector<MemberKeys> member_keys;
        std::ranges::transform(
            membership->keys(), std::back_inserter(member_keys),
            [](const auto& key) {
              return MemberKeys(key.epoch(),
                                ProtoStringToBytes(key.wrapped_key()),
                                ProtoStringToBytes(key.member_proof()));
            });
        result_.icloud_keys.emplace_back(std::move(public_key),
                                         std::move(member_keys));
      } else if (member.member_type() ==
                     trusted_vault_pb::SecurityDomainMember::
                         MEMBER_TYPE_LOCKSCREEN_KNOWLEDGE_FACTOR &&
                 member.member_metadata().has_lskf_metadata()) {
        const auto& metadata = member.member_metadata().lskf_metadata();
        result_.lskf_expiries.push_back(ToTime(metadata.expiration_time()));
      }
    }

    if (!response.next_page_token().empty()) {
      StartOrContinueRequest(&response.next_page_token());
      if (keep_alive_callback_) {
        keep_alive_callback_.Run();
      }
      return;
    }

    FinishWithResultAndMaybeDestroySelf();
  }

  void FinishWithResultAndMaybeDestroySelf() {
    base::UmaHistogramEnumeration(
        "TrustedVault.DownloadAuthenticationFactorsRegistrationState." +
            GetSecurityDomainNameForUma(security_domain_),
        result_.state);
    RecordTrustedVaultListSecurityDomainMembersPinStatus(security_domain_,
                                                         pin_status_);
    std::move(callback_).Run(std::move(result_));
  }

  const SecurityDomainId security_domain_;
  const GURL base_url_;
  const CoreAccountId account_id_;
  const scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory_;
  const std::unique_ptr<TrustedVaultAccessTokenFetcher> access_token_fetcher_;
  TrustedVaultConnection::DownloadAuthenticationFactorsRegistrationStateCallback
      callback_;
  base::RepeatingClosure keep_alive_callback_;
  std::unique_ptr<TrustedVaultRequest> request_;
  DownloadAuthenticationFactorsRegistrationStateResult result_;
  TrustedVaultListSecurityDomainMembersPinStatus pin_status_ =
      TrustedVaultListSecurityDomainMembersPinStatus::kNoPinPresent;
};

TrustedVaultURLFetchReasonForUMA
GetURLFetchReasonForUMAForJoinSecurityDomainsRequest(
    AuthenticationFactorTypeAndRegistrationParams
        authentication_factor_type_and_registration_params) {
  return std::visit(
      absl::Overload{
          [](const LocalPhysicalDevice&) {
            return TrustedVaultURLFetchReasonForUMA::kRegisterDevice;
          },
          [](const LockScreenKnowledgeFactor&) {
            return TrustedVaultURLFetchReasonForUMA::
                kRegisterLockScreenKnowledgeFactor;
          },
          [](const UnspecifiedAuthenticationFactorType&) {
            return TrustedVaultURLFetchReasonForUMA::
                kRegisterUnspecifiedAuthenticationFactor;
          },
          [](const GpmPinMetadata&) {
            return TrustedVaultURLFetchReasonForUMA::kRegisterGpmPin;
          },
          [](const ICloudKeychain&) {
            return TrustedVaultURLFetchReasonForUMA::kRegisterICloudKeychain;
          }},
      authentication_factor_type_and_registration_params);
}

std::vector<TrustedVaultKeyAndVersion> ConstantKeySource() {
  return {{GetConstantTrustedVaultKey(),
           /*version=*/kUnknownConstantKeyVersion}};
}

void ProcessDownloadGaiaPasswordPublicKeyResponse(
    TrustedVaultConnection::DownloadGaiaPasswordPublicKeyCallback callback,
    TrustedVaultRequest::HttpStatus http_status,
    const std::string& response_body) {
  switch (http_status) {
    case TrustedVaultRequest::HttpStatus::kSuccess:
      break;
    case TrustedVaultRequest::HttpStatus::kTransientAccessTokenFetchError:
      std::move(callback).Run(TrustedVaultDownloadPasswordPublicKeyStatus::
                                  kTransientAccessTokenFetchError,
                              std::vector<uint8_t>());
      return;
    case TrustedVaultRequest::HttpStatus::kPersistentAccessTokenFetchError:
      std::move(callback).Run(TrustedVaultDownloadPasswordPublicKeyStatus::
                                  kPersistentAccessTokenFetchError,
                              std::vector<uint8_t>());
      return;
    case TrustedVaultRequest::HttpStatus::
        kPrimaryAccountChangeAccessTokenFetchError:
      std::move(callback).Run(TrustedVaultDownloadPasswordPublicKeyStatus::
                                  kPrimaryAccountChangeAccessTokenFetchError,
                              std::vector<uint8_t>());
      return;
    case TrustedVaultRequest::HttpStatus::kNetworkError:
      std::move(callback).Run(
          TrustedVaultDownloadPasswordPublicKeyStatus::kNetworkError,
          std::vector<uint8_t>());
      return;
    case TrustedVaultRequest::HttpStatus::kBadRequest:
    case TrustedVaultRequest::HttpStatus::kNotFound:
    case TrustedVaultRequest::HttpStatus::kConflict:
    case TrustedVaultRequest::HttpStatus::kOtherError:
      std::move(callback).Run(
          TrustedVaultDownloadPasswordPublicKeyStatus::kOtherError,
          std::vector<uint8_t>());
      return;
  }

  trusted_vault_pb::GetCurrentGaiaPasswordEncryptionKeyDataResponse response;
  if (!response.ParseFromString(response_body)) {
    std::move(callback).Run(
        TrustedVaultDownloadPasswordPublicKeyStatus::kOtherError,
        std::vector<uint8_t>());
    return;
  }

  const std::string& key_str =
      response.current_gaia_password_encryption_key_data().public_key();
  if (key_str.empty()) {
    std::move(callback).Run(
        TrustedVaultDownloadPasswordPublicKeyStatus::kOtherError,
        std::vector<uint8_t>());
    return;
  }

  std::move(callback).Run(TrustedVaultDownloadPasswordPublicKeyStatus::kSuccess,
                          ProtoStringToBytes(key_str));
}

void ProcessRotateSharedKeyResponse(
    TrustedVaultConnection::RotateSharedKeyCallback callback,
    TrustedVaultRequest::HttpStatus http_status,
    const std::string& response_body) {
  switch (http_status) {
    case TrustedVaultRequest::HttpStatus::kSuccess:
      break;
    case TrustedVaultRequest::HttpStatus::kNetworkError:
      std::move(callback).Run(TrustedVaultKeyRotationStatus::kNetworkError, 0);
      return;
    case TrustedVaultRequest::HttpStatus::kOtherError:
      std::move(callback).Run(TrustedVaultKeyRotationStatus::kOtherError, 0);
      return;
    case TrustedVaultRequest::HttpStatus::kTransientAccessTokenFetchError:
      std::move(callback).Run(
          TrustedVaultKeyRotationStatus::kTransientAccessTokenFetchError, 0);
      return;
    case TrustedVaultRequest::HttpStatus::kPersistentAccessTokenFetchError:
      std::move(callback).Run(
          TrustedVaultKeyRotationStatus::kPersistentAccessTokenFetchError, 0);
      return;
    case TrustedVaultRequest::HttpStatus::
        kPrimaryAccountChangeAccessTokenFetchError:
      std::move(callback).Run(TrustedVaultKeyRotationStatus::
                                  kPrimaryAccountChangeAccessTokenFetchError,
                              0);
      return;
    case TrustedVaultRequest::HttpStatus::kNotFound:
      std::move(callback).Run(
          TrustedVaultKeyRotationStatus::kEmptySecurityDomain, 0);
      return;
    case TrustedVaultRequest::HttpStatus::kBadRequest:
      std::move(callback).Run(
          TrustedVaultKeyRotationStatus::kMembershipMismatch, 0);
      return;
    case TrustedVaultRequest::HttpStatus::kConflict:
      std::move(callback).Run(TrustedVaultKeyRotationStatus::kLocalDataObsolete,
                              0);
      return;
  }

  trusted_vault_pb::RotateSharedKeyResponse response;
  if (!response.ParseFromString(response_body)) {
    std::move(callback).Run(TrustedVaultKeyRotationStatus::kOtherError, 0);
    return;
  }
  std::move(callback).Run(TrustedVaultKeyRotationStatus::kSuccess,
                          response.new_epoch());
}

}  // namespace

std::vector<TrustedVaultKeyAndVersion> GetTrustedVaultKeysWithVersions(
    const std::vector<std::vector<uint8_t>>& trusted_vault_keys,
    int last_key_version) {
  const int first_key_version =
      last_key_version - static_cast<int>(trusted_vault_keys.size()) + 1;
  std::vector<TrustedVaultKeyAndVersion> result;
  for (size_t i = 0; i < trusted_vault_keys.size(); ++i) {
    result.emplace_back(trusted_vault_keys[i], first_key_version + i);
  }
  return result;
}

TrustedVaultConnectionImpl::TrustedVaultConnectionImpl(
    SecurityDomainId security_domain,
    const GURL& trusted_vault_service_url,
    std::unique_ptr<network::PendingSharedURLLoaderFactory>
        pending_url_loader_factory,
    std::unique_ptr<TrustedVaultAccessTokenFetcher> access_token_fetcher)
    : security_domain_(security_domain),
      pending_url_loader_factory_(std::move(pending_url_loader_factory)),
      access_token_fetcher_(std::move(access_token_fetcher)),
      trusted_vault_service_url_(trusted_vault_service_url) {
  DCHECK(trusted_vault_service_url_.is_valid());
}

TrustedVaultConnectionImpl::~TrustedVaultConnectionImpl() = default;

std::unique_ptr<TrustedVaultConnection::Request>
TrustedVaultConnectionImpl::RegisterAuthenticationFactor(
    const CoreAccountInfo& account_info,
    const MemberKeysSource& member_keys_source,
    const SecureBoxPublicKey& authentication_factor_public_key,
    AuthenticationFactorTypeAndRegistrationParams
        authentication_factor_type_and_registration_params,
    RegisterAuthenticationFactorCallback callback) {
  return SendJoinSecurityDomainsRequest(
      account_info, member_keys_source, authentication_factor_public_key,
      authentication_factor_type_and_registration_params,
      base::BindOnce(&RunRegisterAuthenticationFactorCallback,
                     std::move(callback)));
}

std::unique_ptr<TrustedVaultConnection::Request>
TrustedVaultConnectionImpl::RegisterLocalDeviceWithoutKeys(
    const CoreAccountInfo& account_info,
    const SecureBoxPublicKey& device_public_key,
    RegisterAuthenticationFactorCallback callback) {
  return SendJoinSecurityDomainsRequest(
      account_info, ConstantKeySource(), device_public_key,
      LocalPhysicalDevice(),
      base::BindOnce(&RunRegisterAuthenticationFactorCallback,
                     std::move(callback)));
}

std::unique_ptr<TrustedVaultConnection::Request>
TrustedVaultConnectionImpl::DownloadNewKeys(
    const CoreAccountInfo& account_info,
    const TrustedVaultKeyAndVersion& last_trusted_vault_key_and_version,
    std::unique_ptr<SecureBoxKeyPair> device_key_pair,
    DownloadNewKeysCallback callback) {
  // TODO(crbug.com/40255601): consider retries for keys downloading after
  // initial failure returned to the upper layers.
  auto request = std::make_unique<TrustedVaultRequest>(
      security_domain_, account_info.account_id,
      TrustedVaultRequest::HttpMethod::kGet,
      GetGetSecurityDomainMemberURL(
          trusted_vault_service_url_,
          device_key_pair->public_key().ExportToBytes()),
      /*serialized_request_proto=*/std::nullopt,
      /*max_retry_duration=*/base::Seconds(0), GetOrCreateURLLoaderFactory(),
      access_token_fetcher_->Clone(),
      MakeFetchStatusCallback(security_domain_,
                              TrustedVaultURLFetchReasonForUMA::kDownloadKeys));

  request->FetchAccessTokenAndSendRequest(
      base::BindOnce(&ProcessDownloadKeysResponse,
                     /*response_processor=*/
                     std::make_unique<DownloadKeysResponseHandler>(
                         security_domain_, last_trusted_vault_key_and_version,
                         std::move(device_key_pair)),
                     std::move(callback)));

  return request;
}

std::unique_ptr<TrustedVaultConnection::Request>
TrustedVaultConnectionImpl::DownloadIsRecoverabilityDegraded(
    const CoreAccountInfo& account_info,
    IsRecoverabilityDegradedCallback callback) {
  auto request = std::make_unique<TrustedVaultRequest>(
      security_domain_, account_info.account_id,
      TrustedVaultRequest::HttpMethod::kGet,
      GetGetSecurityDomainURL(trusted_vault_service_url_, security_domain_),
      /*serialized_request_proto=*/std::nullopt,
      /*max_retry_duration=*/base::Seconds(0), GetOrCreateURLLoaderFactory(),
      access_token_fetcher_->Clone(),
      MakeFetchStatusCallback(
          security_domain_,
          TrustedVaultURLFetchReasonForUMA::kDownloadIsRecoverabilityDegraded));

  request->FetchAccessTokenAndSendRequest(base::BindOnce(
      &ProcessDownloadIsRecoverabilityDegradedResponse, std::move(callback)));

  return request;
}

std::unique_ptr<TrustedVaultConnection::Request>
TrustedVaultConnectionImpl::DownloadGaiaPasswordPublicKey(
    const CoreAccountInfo& account_info,
    DownloadGaiaPasswordPublicKeyCallback callback) {
  GURL url = GetDownloadGaiaPasswordPublicKeyURL(trusted_vault_service_url_);
  auto request = std::make_unique<TrustedVaultRequest>(
      security_domain_, account_info.account_id,
      TrustedVaultRequest::HttpMethod::kGet, url,
      /*serialized_request_proto=*/std::nullopt,
      /*max_retry_duration=*/base::Seconds(0), GetOrCreateURLLoaderFactory(),
      access_token_fetcher_->Clone(),
      MakeFetchStatusCallback(
          security_domain_,
          TrustedVaultURLFetchReasonForUMA::kDownloadGaiaPasswordPublicKey));

  request->FetchAccessTokenAndSendRequest(base::BindOnce(
      &ProcessDownloadGaiaPasswordPublicKeyResponse, std::move(callback)));

  return request;
}

std::unique_ptr<TrustedVaultConnection::Request>
TrustedVaultConnectionImpl::RotateSharedKey(
    const CoreAccountInfo& account_info,
    const trusted_vault_pb::RotateSharedKeyRequest& request,
    RotateSharedKeyCallback callback) {
  auto http_request = std::make_unique<TrustedVaultRequest>(
      security_domain_, account_info.account_id,
      TrustedVaultRequest::HttpMethod::kPost,
      GetRotateSharedKeyURL(trusted_vault_service_url_, security_domain_),
      /*serialized_request_proto=*/request.SerializeAsString(),
      /*max_retry_duration=*/kMaxKeyRotationRetryDuration,
      GetOrCreateURLLoaderFactory(), access_token_fetcher_->Clone(),
      MakeFetchStatusCallback(
          security_domain_,
          TrustedVaultURLFetchReasonForUMA::kRotateSharedKey));

  http_request->FetchAccessTokenAndSendRequest(
      base::BindOnce(&ProcessRotateSharedKeyResponse, std::move(callback)));

  return http_request;
}

std::unique_ptr<TrustedVaultConnection::Request>
TrustedVaultConnectionImpl::DownloadAuthenticationFactorsRegistrationState(
    const CoreAccountInfo& account_info,
    DownloadAuthenticationFactorsRegistrationStateCallback callback,
    base::RepeatingClosure keep_alive_callback) {
  return DownloadAuthenticationFactorsRegistrationState(
      account_info, {}, std::move(callback), std::move(keep_alive_callback));
}

std::unique_ptr<TrustedVaultConnection::Request>
TrustedVaultConnectionImpl::DownloadAuthenticationFactorsRegistrationState(
    const CoreAccountInfo& account_info,
    std::set<trusted_vault_pb::SecurityDomainMember_MemberType>
        recovery_factor_filter,
    DownloadAuthenticationFactorsRegistrationStateCallback callback,
    base::RepeatingClosure keep_alive_callback) {
  GURL request_url = GetGetSecurityDomainMembersURL(
      trusted_vault_service_url_, {security_domain_}, recovery_factor_filter);

  return std::make_unique<
      DownloadAuthenticationFactorsRegistrationStateRequest>(
      security_domain_, request_url, account_info.account_id,
      GetOrCreateURLLoaderFactory(), access_token_fetcher_->Clone(),
      std::move(callback), std::move(keep_alive_callback));
}

std::unique_ptr<TrustedVaultConnection::Request>
TrustedVaultConnectionImpl::SendJoinSecurityDomainsRequest(
    const CoreAccountInfo& account_info,
    const MemberKeysSource& member_keys_source,
    const SecureBoxPublicKey& authentication_factor_public_key,
    AuthenticationFactorTypeAndRegistrationParams
        authentication_factor_type_and_registration_params,
    JoinSecurityDomainsCallback callback) {
  auto request = std::make_unique<TrustedVaultRequest>(
      security_domain_, account_info.account_id,
      TrustedVaultRequest::HttpMethod::kPost,
      GetJoinSecurityDomainURL(trusted_vault_service_url_, security_domain_),
      /*serialized_request_proto=*/
      CreateJoinSecurityDomainsRequest(
          security_domain_, member_keys_source,
          authentication_factor_public_key,
          authentication_factor_type_and_registration_params)
          .SerializeAsString(),
      kMaxJoinSecurityDomainRetryDuration, GetOrCreateURLLoaderFactory(),
      access_token_fetcher_->Clone(),
      MakeFetchStatusCallback(
          security_domain_,
          GetURLFetchReasonForUMAForJoinSecurityDomainsRequest(
              authentication_factor_type_and_registration_params)));

  request->FetchAccessTokenAndSendRequest(
      base::BindOnce(&ProcessJoinSecurityDomainsResponse, std::move(callback)));
  return request;
}

scoped_refptr<network::SharedURLLoaderFactory>
TrustedVaultConnectionImpl::GetOrCreateURLLoaderFactory() {
  if (!url_loader_factory_) {
    url_loader_factory_ = network::SharedURLLoaderFactory::Create(
        std::move(pending_url_loader_factory_));
  }
  return url_loader_factory_;
}

}  // namespace trusted_vault
