// 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 #include #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& alternative_services, const HttpServerProperties::ServerInfoMapKey& server_info_key, base::Time now, const HttpServerPropertiesManager::GetCannonicalSuffix& get_canonical_suffix, std::set>* 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 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 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* server_info_map, IPAddress* last_local_address_when_quic_worked, std::unique_ptr* quic_server_info_map, std::unique_ptr* broken_alternative_service_list, std::unique_ptr* 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 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(); *quic_server_info_map = std::make_unique( 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(); *recently_broken_alternative_services = std::make_unique( 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_|. 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 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(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 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(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 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> 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 http_server_properties_dict.Clone(); }); 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(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 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(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 server_info_map; IPAddress last_local_address_when_quic_worked; std::unique_ptr quic_server_info_map; std::unique_ptr broken_alternative_service_list; std::unique_ptr 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