// Copyright 2014 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/http/http_server_properties_manager.h"

#include <algorithm>
#include <utility>

#include "base/containers/adapters.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/string_number_conversions.h"
#include "base/time/tick_clock.h"
#include "base/time/time.h"
#include "base/values.h"
#include "net/base/features.h"
#include "net/base/host_port_pair.h"
#include "net/base/ip_address.h"
#include "net/base/port_util.h"
#include "net/base/privacy_mode.h"
#include "net/http/http_server_properties.h"
#include "net/third_party/quiche/src/quiche/quic/platform/api/quic_hostname_utils.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "url/gurl.h"
#include "url/scheme_host_port.h"

namespace net {

namespace {

// "version" 0 indicates, http_server_properties doesn't have "version"
// property.
const int kMissingVersion = 0;

// The version number of persisted http_server_properties.
const int kVersionNumber = 5;

// Persist at most 200 currently-broken alternative services to disk.
const int kMaxBrokenAlternativeServicesToPersist = 200;

const char kServerKey[] = "server";
const char kQuicServerIdKey[] = "server_id";
const char kNetworkAnonymizationKey[] = "anonymization";
const char kVersionKey[] = "version";
const char kServersKey[] = "servers";
const char kSupportsSpdyKey[] = "supports_spdy";
const char kSupportsQuicKey[] = "supports_quic";
const char kQuicServers[] = "quic_servers";
const char kServerInfoKey[] = "server_info";
const char kUsedQuicKey[] = "used_quic";
const char kAddressKey[] = "address";
const char kAlternativeServiceKey[] = "alternative_service";
const char kProtocolKey[] = "protocol_str";
const char kHostKey[] = "host";
const char kPortKey[] = "port";
const char kExpirationKey[] = "expiration";
const char kAdvertisedAlpnsKey[] = "advertised_alpns";
const char kNetworkStatsKey[] = "network_stats";
const char kSrttKey[] = "srtt";
const char kBrokenAlternativeServicesKey[] = "broken_alternative_services";
const char kBrokenUntilKey[] = "broken_until";
const char kBrokenCountKey[] = "broken_count";

// Utility method to return only those AlternativeServiceInfos that should be
// persisted to disk. In particular, removes expired and invalid alternative
// services. Also checks if an alternative service for the same canonical suffix
// has already been saved, and if so, returns an empty list.
AlternativeServiceInfoVector GetAlternativeServiceToPersist(
    const absl::optional<AlternativeServiceInfoVector>& alternative_services,
    const HttpServerProperties::ServerInfoMapKey& server_info_key,
    base::Time now,
    const HttpServerPropertiesManager::GetCannonicalSuffix&
        get_canonical_suffix,
    std::set<std::pair<std::string, NetworkAnonymizationKey>>*
        persisted_canonical_suffix_set) {
  if (!alternative_services)
    return AlternativeServiceInfoVector();
  // Separate out valid, non-expired AlternativeServiceInfo entries.
  AlternativeServiceInfoVector notbroken_alternative_service_info_vector;
  for (const auto& alternative_service_info : alternative_services.value()) {
    if (alternative_service_info.expiration() < now ||
        !IsAlternateProtocolValid(
            alternative_service_info.alternative_service().protocol)) {
      continue;
    }
    notbroken_alternative_service_info_vector.push_back(
        alternative_service_info);
  }
  if (notbroken_alternative_service_info_vector.empty())
    return notbroken_alternative_service_info_vector;
  const std::string* canonical_suffix =
      get_canonical_suffix.Run(server_info_key.server.host());
  if (canonical_suffix) {
    // Don't save if have already saved information associated with the same
    // canonical suffix.
    std::pair<std::string, NetworkAnonymizationKey> index(
        *canonical_suffix, server_info_key.network_anonymization_key);
    if (persisted_canonical_suffix_set->find(index) !=
        persisted_canonical_suffix_set->end()) {
      return AlternativeServiceInfoVector();
    }
    persisted_canonical_suffix_set->emplace(std::move(index));
  }
  return notbroken_alternative_service_info_vector;
}

void AddAlternativeServiceFieldsToDictionaryValue(
    const AlternativeService& alternative_service,
    base::Value::Dict& dict) {
  dict.Set(kPortKey, alternative_service.port);
  if (!alternative_service.host.empty()) {
    dict.Set(kHostKey, alternative_service.host);
  }
  dict.Set(kProtocolKey, NextProtoToString(alternative_service.protocol));
}

// Fails in the case of NetworkAnonymizationKeys that can't be persisted to
// disk, like unique origins.
bool TryAddBrokenAlternativeServiceFieldsToDictionaryValue(
    const BrokenAlternativeService& broken_alt_service,
    base::Value::Dict& dict) {
  base::Value network_anonymization_key_value;
  if (!broken_alt_service.network_anonymization_key.ToValue(
          &network_anonymization_key_value)) {
    return false;
  }

  dict.Set(kNetworkAnonymizationKey,
           std::move(network_anonymization_key_value));
  AddAlternativeServiceFieldsToDictionaryValue(
      broken_alt_service.alternative_service, dict);
  return true;
}

quic::QuicServerId QuicServerIdFromString(const std::string& str) {
  GURL url(str);
  if (!url.is_valid()) {
    return quic::QuicServerId();
  }
  HostPortPair host_port_pair = HostPortPair::FromURL(url);
  return quic::QuicServerId(host_port_pair.host(), host_port_pair.port(),
                            url.path_piece() == "/private"
                                ? PRIVACY_MODE_ENABLED
                                : PRIVACY_MODE_DISABLED);
}

std::string QuicServerIdToString(const quic::QuicServerId& server_id) {
  HostPortPair host_port_pair(server_id.host(), server_id.port());
  return "https://" + host_port_pair.ToString() +
         (server_id.privacy_mode_enabled() ? "/private" : "");
}

// Takes in a base::Value::Dict, and whether NetworkIsolationKeys are enabled
// for HttpServerProperties, and extracts the NetworkAnonymizationKey stored
// with the |kNetworkAnonymizationKey| in the dictionary, and writes it to
// |out_network_anonymization_key|. Returns false if unable to load a
// NetworkAnonymizationKey, or the NetworkAnonymizationKey is non-empty, but
// |use_network_anonymization_key| is false.
bool GetNetworkIsolationKeyFromDict(
    const base::Value::Dict& dict,
    bool use_network_anonymization_key,
    NetworkAnonymizationKey* out_network_anonymization_key) {
  const base::Value* network_anonymization_key_value =
      dict.Find(kNetworkAnonymizationKey);
  NetworkAnonymizationKey network_anonymization_key;
  if (!network_anonymization_key_value ||
      !NetworkAnonymizationKey::FromValue(*network_anonymization_key_value,
                                          &network_anonymization_key)) {
    return false;
  }

  // Fail if NetworkIsolationKeys are disabled, but the entry has a non-empty
  // NetworkAnonymizationKey.
  if (!use_network_anonymization_key && !network_anonymization_key.IsEmpty())
    return false;

  *out_network_anonymization_key = std::move(network_anonymization_key);
  return true;
}

}  // namespace

////////////////////////////////////////////////////////////////////////////////
//  HttpServerPropertiesManager

HttpServerPropertiesManager::HttpServerPropertiesManager(
    std::unique_ptr<HttpServerProperties::PrefDelegate> pref_delegate,
    OnPrefsLoadedCallback on_prefs_loaded_callback,
    size_t max_server_configs_stored_in_properties,
    NetLog* net_log,
    const base::TickClock* clock)
    : pref_delegate_(std::move(pref_delegate)),
      on_prefs_loaded_callback_(std::move(on_prefs_loaded_callback)),
      max_server_configs_stored_in_properties_(
          max_server_configs_stored_in_properties),
      clock_(clock),
      net_log_(
          NetLogWithSource::Make(net_log,
                                 NetLogSourceType::HTTP_SERVER_PROPERTIES)) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK(pref_delegate_);
  DCHECK(on_prefs_loaded_callback_);
  DCHECK(clock_);

  pref_delegate_->WaitForPrefLoad(
      base::BindOnce(&HttpServerPropertiesManager::OnHttpServerPropertiesLoaded,
                     pref_load_weak_ptr_factory_.GetWeakPtr()));
  net_log_.BeginEvent(NetLogEventType::HTTP_SERVER_PROPERTIES_INITIALIZATION);
}

HttpServerPropertiesManager::~HttpServerPropertiesManager() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}

void HttpServerPropertiesManager::ReadPrefs(
    std::unique_ptr<HttpServerProperties::ServerInfoMap>* server_info_map,
    IPAddress* last_local_address_when_quic_worked,
    std::unique_ptr<HttpServerProperties::QuicServerInfoMap>*
        quic_server_info_map,
    std::unique_ptr<BrokenAlternativeServiceList>*
        broken_alternative_service_list,
    std::unique_ptr<RecentlyBrokenAlternativeServices>*
        recently_broken_alternative_services) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  net_log_.EndEvent(NetLogEventType::HTTP_SERVER_PROPERTIES_INITIALIZATION);

  const base::Value::Dict& http_server_properties_dict =
      pref_delegate_->GetServerProperties();

  net_log_.AddEvent(NetLogEventType::HTTP_SERVER_PROPERTIES_UPDATE_CACHE,
                    [&] { return http_server_properties_dict.Clone(); });
  absl::optional<int> maybe_version_number =
      http_server_properties_dict.FindInt(kVersionKey);
  if (!maybe_version_number.has_value() ||
      *maybe_version_number != kVersionNumber) {
    DVLOG(1) << "Missing or unsupported. Clearing all properties. "
             << maybe_version_number.value_or(kMissingVersion);
    return;
  }

  // For Version 5, data is stored in the following format.
  // `servers` are saved in LRU order (least-recently-used item is in the
  // front). `servers` are in the format flattened representation of
  // (scheme/host/port) where port might be ignored if is default with scheme.
  //
  // "http_server_properties": {
  //      "servers": [
  //          {"https://yt3.ggpht.com" : {...}},
  //          {"http://0.client-channel.google.com:443" : {...}},
  //          {"http://0-edge-chat.facebook.com" : {...}},
  //          ...
  //      ], ...
  // },
  const base::Value::List* servers_list =
      http_server_properties_dict.FindList(kServersKey);
  if (!servers_list) {
    DVLOG(1) << "Malformed http_server_properties for servers list.";
    return;
  }

  ReadLastLocalAddressWhenQuicWorked(http_server_properties_dict,
                                     last_local_address_when_quic_worked);

  *server_info_map = std::make_unique<HttpServerProperties::ServerInfoMap>();
  *quic_server_info_map =
      std::make_unique<HttpServerProperties::QuicServerInfoMap>(
          max_server_configs_stored_in_properties_);

  bool use_network_anonymization_key =
      NetworkAnonymizationKey::IsPartitioningEnabled();

  // Iterate `servers_list` (least-recently-used item is in the front) so that
  // entries are inserted into `server_info_map` from oldest to newest.
  for (const auto& server_dict_value : *servers_list) {
    if (!server_dict_value.is_dict()) {
      DVLOG(1) << "Malformed http_server_properties for servers dictionary.";
      continue;
    }
    AddServerData(server_dict_value.GetDict(), server_info_map->get(),
                  use_network_anonymization_key);
  }

  AddToQuicServerInfoMap(http_server_properties_dict,
                         use_network_anonymization_key,
                         quic_server_info_map->get());

  // Read list containing broken and recently-broken alternative services, if
  // it exists.
  const base::Value::List* broken_alt_svc_list =
      http_server_properties_dict.FindList(kBrokenAlternativeServicesKey);
  if (broken_alt_svc_list) {
    *broken_alternative_service_list =
        std::make_unique<BrokenAlternativeServiceList>();
    *recently_broken_alternative_services =
        std::make_unique<RecentlyBrokenAlternativeServices>(
            kMaxRecentlyBrokenAlternativeServiceEntries);

    // Iterate `broken_alt_svc_list` (least-recently-used item is in the front)
    // so that entries are inserted into `recently_broken_alternative_services`
    // from oldest to newest.
    for (const auto& broken_alt_svc_entry_dict_value : *broken_alt_svc_list) {
      if (!broken_alt_svc_entry_dict_value.is_dict()) {
        DVLOG(1) << "Malformed broken alterantive service entry.";
        continue;
      }
      AddToBrokenAlternativeServices(
          broken_alt_svc_entry_dict_value.GetDict(),
          use_network_anonymization_key, broken_alternative_service_list->get(),
          recently_broken_alternative_services->get());
    }
  }

  // Set the properties loaded from prefs on |http_server_properties_impl_|.

  // TODO(mmenke): Rename this once more information is stored in this map.
  UMA_HISTOGRAM_COUNTS_1M("Net.HttpServerProperties.CountOfServers",
                          (*server_info_map)->size());

  UMA_HISTOGRAM_COUNTS_1000("Net.CountOfQuicServerInfos",
                            (*quic_server_info_map)->size());

  if (*recently_broken_alternative_services) {
    DCHECK(*broken_alternative_service_list);

    UMA_HISTOGRAM_COUNTS_1000("Net.CountOfBrokenAlternativeServices",
                              (*broken_alternative_service_list)->size());
    UMA_HISTOGRAM_COUNTS_1000("Net.CountOfRecentlyBrokenAlternativeServices",
                              (*recently_broken_alternative_services)->size());
  }
}

void HttpServerPropertiesManager::AddToBrokenAlternativeServices(
    const base::Value::Dict& broken_alt_svc_entry_dict,
    bool use_network_anonymization_key,
    BrokenAlternativeServiceList* broken_alternative_service_list,
    RecentlyBrokenAlternativeServices* recently_broken_alternative_services) {
  AlternativeService alt_service;
  if (!ParseAlternativeServiceDict(broken_alt_svc_entry_dict, false,
                                   "broken alternative services",
                                   &alt_service)) {
    return;
  }

  NetworkAnonymizationKey network_anonymization_key;
  if (!GetNetworkIsolationKeyFromDict(broken_alt_svc_entry_dict,
                                      use_network_anonymization_key,
                                      &network_anonymization_key)) {
    return;
  }

  // Each entry must contain either broken-count and/or broken-until fields.
  bool contains_broken_count_or_broken_until = false;

  // Read broken-count and add an entry for |alt_service| into
  // |recently_broken_alternative_services|.
  if (broken_alt_svc_entry_dict.Find(kBrokenCountKey)) {
    absl::optional<int> broken_count =
        broken_alt_svc_entry_dict.FindInt(kBrokenCountKey);
    if (!broken_count.has_value()) {
      DVLOG(1) << "Recently broken alternative service has malformed "
               << "broken-count.";
      return;
    }
    if (broken_count.value() < 0) {
      DVLOG(1) << "Broken alternative service has negative broken-count.";
      return;
    }
    recently_broken_alternative_services->Put(
        BrokenAlternativeService(alt_service, network_anonymization_key,
                                 use_network_anonymization_key),
        broken_count.value());
    contains_broken_count_or_broken_until = true;
  }

  // Read broken-until and add an entry for |alt_service| in
  // |broken_alternative_service_list|.
  if (broken_alt_svc_entry_dict.Find(kBrokenUntilKey)) {
    const std::string* expiration_string =
        broken_alt_svc_entry_dict.FindString(kBrokenUntilKey);
    int64_t expiration_int64;
    if (!expiration_string ||
        !base::StringToInt64(*expiration_string, &expiration_int64)) {
      DVLOG(1) << "Broken alternative service has malformed broken-until "
               << "string.";
      return;
    }

    time_t expiration_time_t = static_cast<time_t>(expiration_int64);
    // Convert expiration from time_t to Time to TimeTicks
    base::TimeTicks expiration_time_ticks =
        clock_->NowTicks() +
        (base::Time::FromTimeT(expiration_time_t) - base::Time::Now());
    broken_alternative_service_list->push_back(std::make_pair(
        BrokenAlternativeService(alt_service, network_anonymization_key,
                                 use_network_anonymization_key),
        expiration_time_ticks));
    contains_broken_count_or_broken_until = true;
  }

  if (!contains_broken_count_or_broken_until) {
    DVLOG(1) << "Broken alternative service has neither broken-count nor "
             << "broken-until specified.";
  }
}

void HttpServerPropertiesManager::AddServerData(
    const base::Value::Dict& server_dict,
    HttpServerProperties::ServerInfoMap* server_info_map,
    bool use_network_anonymization_key) {
  // Get server's scheme/host/pair.
  const std::string* server_str = server_dict.FindString(kServerKey);
  NetworkAnonymizationKey network_anonymization_key;
  // Can't load entry if server name missing, or if the network isolation key is
  // missing or invalid.
  if (!server_str || !GetNetworkIsolationKeyFromDict(
                         server_dict, use_network_anonymization_key,
                         &network_anonymization_key)) {
    return;
  }

  url::SchemeHostPort spdy_server((GURL(*server_str)));
  if (spdy_server.host().empty()) {
    DVLOG(1) << "Malformed http_server_properties for server: " << server_str;
    return;
  }

  HttpServerProperties::ServerInfo server_info;

  server_info.supports_spdy = server_dict.FindBool(kSupportsSpdyKey);

  if (ParseAlternativeServiceInfo(spdy_server, server_dict, &server_info))
    ParseNetworkStats(spdy_server, server_dict, &server_info);

  if (!server_info.empty()) {
    server_info_map->Put(HttpServerProperties::ServerInfoMapKey(
                             std::move(spdy_server), network_anonymization_key,
                             use_network_anonymization_key),
                         std::move(server_info));
  }
}

bool HttpServerPropertiesManager::ParseAlternativeServiceDict(
    const base::Value::Dict& dict,
    bool host_optional,
    const std::string& parsing_under,
    AlternativeService* alternative_service) {
  // Protocol is mandatory.
  const std::string* protocol_str = dict.FindString(kProtocolKey);
  if (!protocol_str) {
    DVLOG(1) << "Malformed alternative service protocol string under: "
             << parsing_under;
    return false;
  }
  NextProto protocol = NextProtoFromString(*protocol_str);
  if (!IsAlternateProtocolValid(protocol)) {
    DVLOG(1) << "Invalid alternative service protocol string \"" << protocol_str
             << "\" under: " << parsing_under;
    return false;
  }
  alternative_service->protocol = protocol;

  // If host is optional, it defaults to "".
  std::string host = "";
  const std::string* hostp = nullptr;
  if (dict.Find(kHostKey)) {
    hostp = dict.FindString(kHostKey);
    if (!hostp) {
      DVLOG(1) << "Malformed alternative service host string under: "
               << parsing_under;
      return false;
    }
    host = *hostp;
  } else if (!host_optional) {
    DVLOG(1) << "alternative service missing host string under: "
             << parsing_under;
    return false;
  }
  alternative_service->host = host;

  // Port is mandatory.
  absl::optional<int> maybe_port = dict.FindInt(kPortKey);
  if (!maybe_port.has_value() || !IsPortValid(maybe_port.value())) {
    DVLOG(1) << "Malformed alternative service port under: " << parsing_under;
    return false;
  }
  alternative_service->port = static_cast<uint32_t>(maybe_port.value());

  return true;
}

bool HttpServerPropertiesManager::ParseAlternativeServiceInfoDictOfServer(
    const base::Value::Dict& dict,
    const std::string& server_str,
    AlternativeServiceInfo* alternative_service_info) {
  AlternativeService alternative_service;
  if (!ParseAlternativeServiceDict(dict, true, "server " + server_str,
                                   &alternative_service)) {
    return false;
  }
  alternative_service_info->set_alternative_service(alternative_service);

  // Expiration is optional, defaults to one day.
  if (!dict.Find(kExpirationKey)) {
    alternative_service_info->set_expiration(base::Time::Now() + base::Days(1));
  } else {
    const std::string* expiration_string = dict.FindString(kExpirationKey);
    if (expiration_string) {
      int64_t expiration_int64 = 0;
      if (!base::StringToInt64(*expiration_string, &expiration_int64)) {
        DVLOG(1) << "Malformed alternative service expiration for server: "
                 << server_str;
        return false;
      }
      alternative_service_info->set_expiration(
          base::Time::FromInternalValue(expiration_int64));
    } else {
      DVLOG(1) << "Malformed alternative service expiration for server: "
               << server_str;
      return false;
    }
  }

  // Advertised versions list is optional.
  if (dict.Find(kAdvertisedAlpnsKey)) {
    const base::Value::List* versions_list = dict.FindList(kAdvertisedAlpnsKey);
    if (!versions_list) {
      DVLOG(1) << "Malformed alternative service advertised versions list for "
               << "server: " << server_str;
      return false;
    }
    quic::ParsedQuicVersionVector advertised_versions;
    for (const auto& value : *versions_list) {
      const std::string* version_string = value.GetIfString();
      if (!version_string) {
        DVLOG(1) << "Malformed alternative service version for server: "
                 << server_str;
        return false;
      }
      quic::ParsedQuicVersion version =
          quic::ParseQuicVersionString(*version_string);
      if (version != quic::ParsedQuicVersion::Unsupported()) {
        advertised_versions.push_back(version);
      }
    }
    alternative_service_info->set_advertised_versions(advertised_versions);
  }

  return true;
}

bool HttpServerPropertiesManager::ParseAlternativeServiceInfo(
    const url::SchemeHostPort& server,
    const base::Value::Dict& server_pref_dict,
    HttpServerProperties::ServerInfo* server_info) {
  DCHECK(!server_info->alternative_services.has_value());
  const base::Value::List* alternative_service_list =
      server_pref_dict.FindList(kAlternativeServiceKey);
  if (!alternative_service_list) {
    return true;
  }
  if (server.scheme() != "https") {
    return false;
  }

  AlternativeServiceInfoVector alternative_service_info_vector;
  for (const auto& alternative_service_list_item : *alternative_service_list) {
    if (!alternative_service_list_item.is_dict())
      return false;
    AlternativeServiceInfo alternative_service_info;
    if (!ParseAlternativeServiceInfoDictOfServer(
            alternative_service_list_item.GetDict(), server.Serialize(),
            &alternative_service_info)) {
      return false;
    }
    if (base::Time::Now() < alternative_service_info.expiration()) {
      alternative_service_info_vector.push_back(alternative_service_info);
    }
  }

  if (alternative_service_info_vector.empty()) {
    return false;
  }

  server_info->alternative_services = alternative_service_info_vector;
  return true;
}

void HttpServerPropertiesManager::ReadLastLocalAddressWhenQuicWorked(
    const base::Value::Dict& http_server_properties_dict,
    IPAddress* last_local_address_when_quic_worked) {
  const base::Value::Dict* supports_quic_dict =
      http_server_properties_dict.FindDict(kSupportsQuicKey);
  if (!supports_quic_dict) {
    return;
  }
  const base::Value* used_quic = supports_quic_dict->Find(kUsedQuicKey);
  if (!used_quic || !used_quic->is_bool()) {
    DVLOG(1) << "Malformed SupportsQuic";
    return;
  }
  if (!used_quic->GetBool())
    return;

  const std::string* address = supports_quic_dict->FindString(kAddressKey);
  if (!address ||
      !last_local_address_when_quic_worked->AssignFromIPLiteral(*address)) {
    DVLOG(1) << "Malformed SupportsQuic";
  }
}

void HttpServerPropertiesManager::ParseNetworkStats(
    const url::SchemeHostPort& server,
    const base::Value::Dict& server_pref_dict,
    HttpServerProperties::ServerInfo* server_info) {
  DCHECK(!server_info->server_network_stats.has_value());
  const base::Value::Dict* server_network_stats_dict =
      server_pref_dict.FindDict(kNetworkStatsKey);
  if (!server_network_stats_dict) {
    return;
  }
  absl::optional<int> maybe_srtt = server_network_stats_dict->FindInt(kSrttKey);
  if (!maybe_srtt.has_value()) {
    DVLOG(1) << "Malformed ServerNetworkStats for server: "
             << server.Serialize();
    return;
  }
  ServerNetworkStats server_network_stats;
  server_network_stats.srtt = base::Microseconds(maybe_srtt.value());
  // TODO(rtenneti): When QUIC starts using bandwidth_estimate, then persist
  // bandwidth_estimate.
  server_info->server_network_stats = server_network_stats;
}

void HttpServerPropertiesManager::AddToQuicServerInfoMap(
    const base::Value::Dict& http_server_properties_dict,
    bool use_network_anonymization_key,
    HttpServerProperties::QuicServerInfoMap* quic_server_info_map) {
  const base::Value::List* quic_server_info_list =
      http_server_properties_dict.FindList(kQuicServers);
  if (!quic_server_info_list) {
    DVLOG(1) << "Malformed http_server_properties for quic_servers.";
    return;
  }

  for (const auto& quic_server_info_value : *quic_server_info_list) {
    const base::Value::Dict* quic_server_info_dict =
        quic_server_info_value.GetIfDict();
    if (!quic_server_info_dict)
      continue;

    const std::string* quic_server_id_str =
        quic_server_info_dict->FindString(kQuicServerIdKey);
    if (!quic_server_id_str || quic_server_id_str->empty())
      continue;

    quic::QuicServerId quic_server_id =
        QuicServerIdFromString(*quic_server_id_str);
    if (quic_server_id.host().empty()) {
      DVLOG(1) << "Malformed http_server_properties for quic server: "
               << quic_server_id_str;
      continue;
    }

    NetworkAnonymizationKey network_anonymization_key;
    if (!GetNetworkIsolationKeyFromDict(*quic_server_info_dict,
                                        use_network_anonymization_key,
                                        &network_anonymization_key)) {
      DVLOG(1) << "Malformed http_server_properties quic server dict: "
               << *quic_server_id_str;
      continue;
    }

    const std::string* quic_server_info =
        quic_server_info_dict->FindString(kServerInfoKey);
    if (!quic_server_info) {
      DVLOG(1) << "Malformed http_server_properties quic server info: "
               << *quic_server_id_str;
      continue;
    }
    quic_server_info_map->Put(HttpServerProperties::QuicServerInfoMapKey(
                                  quic_server_id, network_anonymization_key,
                                  use_network_anonymization_key),
                              *quic_server_info);
  }
}

void HttpServerPropertiesManager::WriteToPrefs(
    const HttpServerProperties::ServerInfoMap& server_info_map,
    const GetCannonicalSuffix& get_canonical_suffix,
    const IPAddress& last_local_address_when_quic_worked,
    const HttpServerProperties::QuicServerInfoMap& quic_server_info_map,
    const BrokenAlternativeServiceList& broken_alternative_service_list,
    const RecentlyBrokenAlternativeServices&
        recently_broken_alternative_services,
    base::OnceClosure callback) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  // If loading prefs hasn't completed, don't call it, since this will overwrite
  // existing prefs.
  on_prefs_loaded_callback_.Reset();

  std::set<std::pair<std::string, NetworkAnonymizationKey>>
      persisted_canonical_suffix_set;
  const base::Time now = base::Time::Now();
  base::Value::Dict http_server_properties_dict;

  // Convert |server_info_map| to a list Value and add it to
  // |http_server_properties_dict|.
  base::Value::List servers_list;
  for (const auto& [key, server_info] : server_info_map) {
    // If can't convert the NetworkAnonymizationKey to a value, don't save to
    // disk. Generally happens because the key is for a unique origin.
    base::Value network_anonymization_key_value;
    if (!key.network_anonymization_key.ToValue(
            &network_anonymization_key_value)) {
      continue;
    }

    base::Value::Dict server_dict;

    bool supports_spdy = server_info.supports_spdy.value_or(false);
    if (supports_spdy)
      server_dict.Set(kSupportsSpdyKey, supports_spdy);

    AlternativeServiceInfoVector alternative_services =
        GetAlternativeServiceToPersist(server_info.alternative_services, key,
                                       now, get_canonical_suffix,
                                       &persisted_canonical_suffix_set);
    if (!alternative_services.empty())
      SaveAlternativeServiceToServerPrefs(alternative_services, server_dict);

    if (server_info.server_network_stats) {
      SaveNetworkStatsToServerPrefs(*server_info.server_network_stats,
                                    server_dict);
    }

    // Don't add empty entries. This can happen if, for example, all alternative
    // services are empty, or |supports_spdy| is set to false, and all other
    // fields are not set.
    if (server_dict.empty())
      continue;
    server_dict.Set(kServerKey, key.server.Serialize());
    server_dict.Set(kNetworkAnonymizationKey,
                    std::move(network_anonymization_key_value));
    servers_list.Append(std::move(server_dict));
  }
  // Reverse `servers_list`. The least recently used item will be in the front.
  std::reverse(servers_list.begin(), servers_list.end());

  http_server_properties_dict.Set(kServersKey, std::move(servers_list));

  http_server_properties_dict.Set(kVersionKey, kVersionNumber);

  SaveLastLocalAddressWhenQuicWorkedToPrefs(last_local_address_when_quic_worked,
                                            http_server_properties_dict);

  SaveQuicServerInfoMapToServerPrefs(quic_server_info_map,
                                     http_server_properties_dict);

  SaveBrokenAlternativeServicesToPrefs(
      broken_alternative_service_list, kMaxBrokenAlternativeServicesToPersist,
      recently_broken_alternative_services, http_server_properties_dict);

  net_log_.AddEvent(NetLogEventType::HTTP_SERVER_PROPERTIES_UPDATE_PREFS,
                    [&] { return std::move(http_server_properties_dict); });

  pref_delegate_->SetServerProperties(std::move(http_server_properties_dict),
                                      std::move(callback));
}

void HttpServerPropertiesManager::SaveAlternativeServiceToServerPrefs(
    const AlternativeServiceInfoVector& alternative_service_info_vector,
    base::Value::Dict& server_pref_dict) {
  if (alternative_service_info_vector.empty()) {
    return;
  }
  base::Value::List alternative_service_list;
  for (const AlternativeServiceInfo& alternative_service_info :
       alternative_service_info_vector) {
    const AlternativeService& alternative_service =
        alternative_service_info.alternative_service();
    DCHECK(IsAlternateProtocolValid(alternative_service.protocol));
    base::Value::Dict alternative_service_dict;
    AddAlternativeServiceFieldsToDictionaryValue(alternative_service,
                                                 alternative_service_dict);
    // JSON cannot store int64_t, so expiration is converted to a string.
    alternative_service_dict.Set(
        kExpirationKey,
        base::NumberToString(
            alternative_service_info.expiration().ToInternalValue()));
    base::Value::List advertised_versions_list;
    for (const auto& version : alternative_service_info.advertised_versions()) {
      advertised_versions_list.Append(quic::AlpnForVersion(version));
    }
    alternative_service_dict.Set(kAdvertisedAlpnsKey,
                                 std::move(advertised_versions_list));
    alternative_service_list.Append(std::move(alternative_service_dict));
  }
  if (alternative_service_list.size() == 0)
    return;
  server_pref_dict.Set(kAlternativeServiceKey,
                       std::move(alternative_service_list));
}

void HttpServerPropertiesManager::SaveLastLocalAddressWhenQuicWorkedToPrefs(
    const IPAddress& last_local_address_when_quic_worked,
    base::Value::Dict& http_server_properties_dict) {
  if (!last_local_address_when_quic_worked.IsValid())
    return;

  base::Value::Dict supports_quic_dict;
  supports_quic_dict.Set(kUsedQuicKey, true);
  supports_quic_dict.Set(kAddressKey,
                         last_local_address_when_quic_worked.ToString());
  http_server_properties_dict.Set(kSupportsQuicKey,
                                  std::move(supports_quic_dict));
}

void HttpServerPropertiesManager::SaveNetworkStatsToServerPrefs(
    const ServerNetworkStats& server_network_stats,
    base::Value::Dict& server_pref_dict) {
  base::Value::Dict server_network_stats_dict;
  // Because JSON doesn't support int64_t, persist int64_t as a string.
  server_network_stats_dict.Set(
      kSrttKey, static_cast<int>(server_network_stats.srtt.InMicroseconds()));
  // TODO(rtenneti): When QUIC starts using bandwidth_estimate, then persist
  // bandwidth_estimate.
  server_pref_dict.Set(kNetworkStatsKey, std::move(server_network_stats_dict));
}

void HttpServerPropertiesManager::SaveQuicServerInfoMapToServerPrefs(
    const HttpServerProperties::QuicServerInfoMap& quic_server_info_map,
    base::Value::Dict& http_server_properties_dict) {
  if (quic_server_info_map.empty())
    return;
  base::Value::List quic_servers_list;
  for (const auto& [key, server_info] : base::Reversed(quic_server_info_map)) {
    base::Value network_anonymization_key_value;
    // Don't save entries with ephemeral NIKs.
    if (!key.network_anonymization_key.ToValue(
            &network_anonymization_key_value)) {
      continue;
    }

    base::Value::Dict quic_server_pref_dict;
    quic_server_pref_dict.Set(kQuicServerIdKey,
                              QuicServerIdToString(key.server_id));
    quic_server_pref_dict.Set(kNetworkAnonymizationKey,
                              std::move(network_anonymization_key_value));
    quic_server_pref_dict.Set(kServerInfoKey, server_info);

    quic_servers_list.Append(std::move(quic_server_pref_dict));
  }
  http_server_properties_dict.Set(kQuicServers, std::move(quic_servers_list));
}

void HttpServerPropertiesManager::SaveBrokenAlternativeServicesToPrefs(
    const BrokenAlternativeServiceList& broken_alternative_service_list,
    size_t max_broken_alternative_services,
    const RecentlyBrokenAlternativeServices&
        recently_broken_alternative_services,
    base::Value::Dict& http_server_properties_dict) {
  if (broken_alternative_service_list.empty() &&
      recently_broken_alternative_services.empty()) {
    return;
  }

  // JSON list will be in LRU order (least-recently-used item is in the front)
  // according to `recently_broken_alternative_services`.
  base::Value::List json_list;

  // Maps recently-broken alternative services to the index where it's stored
  // in |json_list|.
  std::map<BrokenAlternativeService, size_t> json_list_index_map;

  if (!recently_broken_alternative_services.empty()) {
    for (const auto& [broken_alt_service, broken_count] :
         base::Reversed(recently_broken_alternative_services)) {
      base::Value::Dict entry_dict;
      if (!TryAddBrokenAlternativeServiceFieldsToDictionaryValue(
              broken_alt_service, entry_dict)) {
        continue;
      }
      entry_dict.Set(kBrokenCountKey, broken_count);
      json_list_index_map[broken_alt_service] = json_list.size();
      json_list.Append(std::move(entry_dict));
    }
  }

  if (!broken_alternative_service_list.empty()) {
    // Add expiration time info from |broken_alternative_service_list| to
    // the JSON list.
    size_t count = 0;
    for (auto it = broken_alternative_service_list.begin();
         it != broken_alternative_service_list.end() &&
         count < max_broken_alternative_services;
         ++it, ++count) {
      const BrokenAlternativeService& broken_alt_service = it->first;
      base::TimeTicks expiration_time_ticks = it->second;
      // Convert expiration from TimeTicks to Time to time_t
      time_t expiration_time_t =
          (base::Time::Now() + (expiration_time_ticks - clock_->NowTicks()))
              .ToTimeT();
      int64_t expiration_int64 = static_cast<int64_t>(expiration_time_t);

      auto index_map_it = json_list_index_map.find(broken_alt_service);
      if (index_map_it != json_list_index_map.end()) {
        size_t json_list_index = index_map_it->second;
        base::Value& entry_dict = json_list[json_list_index];
        DCHECK(entry_dict.is_dict());
        DCHECK(!entry_dict.GetDict().Find(kBrokenUntilKey));
        entry_dict.GetDict().Set(kBrokenUntilKey,
                                 base::NumberToString(expiration_int64));
      } else {
        base::Value::Dict entry_dict;
        if (!TryAddBrokenAlternativeServiceFieldsToDictionaryValue(
                broken_alt_service, entry_dict)) {
          continue;
        }
        entry_dict.Set(kBrokenUntilKey, base::NumberToString(expiration_int64));
        json_list.Append(std::move(entry_dict));
      }
    }
  }

  // This can happen if all the entries are for NetworkAnonymizationKeys for
  // opaque origins, which isn't exactly common, but can theoretically happen.
  if (json_list.empty())
    return;

  http_server_properties_dict.Set(kBrokenAlternativeServicesKey,
                                  std::move(json_list));
}

void HttpServerPropertiesManager::OnHttpServerPropertiesLoaded() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  // If prefs have already been written, nothing to do.
  if (!on_prefs_loaded_callback_)
    return;

  std::unique_ptr<HttpServerProperties::ServerInfoMap> server_info_map;
  IPAddress last_local_address_when_quic_worked;
  std::unique_ptr<HttpServerProperties::QuicServerInfoMap> quic_server_info_map;
  std::unique_ptr<BrokenAlternativeServiceList> broken_alternative_service_list;
  std::unique_ptr<RecentlyBrokenAlternativeServices>
      recently_broken_alternative_services;

  ReadPrefs(&server_info_map, &last_local_address_when_quic_worked,
            &quic_server_info_map, &broken_alternative_service_list,
            &recently_broken_alternative_services);

  std::move(on_prefs_loaded_callback_)
      .Run(std::move(server_info_map), last_local_address_when_quic_worked,
           std::move(quic_server_info_map),
           std::move(broken_alternative_service_list),
           std::move(recently_broken_alternative_services));
}

}  // namespace net
