| // Copyright 2025 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_stream_pool_ip_endpoint_state_tracker.h" | 
 |  | 
 | #include <map> | 
 | #include <optional> | 
 |  | 
 | #include "base/memory/raw_ptr.h" | 
 | #include "base/values.h" | 
 | #include "net/base/ip_endpoint.h" | 
 | #include "net/base/net_export.h" | 
 | #include "net/dns/host_resolver.h" | 
 | #include "net/dns/public/host_resolver_results.h" | 
 | #include "net/http/http_stream_pool.h" | 
 |  | 
 | namespace net { | 
 |  | 
 | HttpStreamPool::IPEndPointStateTracker::IPEndPointStateTracker( | 
 |     Delegate* delegate) | 
 |     : delegate_(delegate) { | 
 |   CHECK(delegate_); | 
 | } | 
 |  | 
 | HttpStreamPool::IPEndPointStateTracker::~IPEndPointStateTracker() = default; | 
 |  | 
 | std::optional<HttpStreamPool::IPEndPointStateTracker::IPEndPointState> | 
 | HttpStreamPool::IPEndPointStateTracker::GetState( | 
 |     const IPEndPoint& ip_endpoint) const { | 
 |   auto it = ip_endpoint_states_.find(ip_endpoint); | 
 |   if (it == ip_endpoint_states_.end()) { | 
 |     return std::nullopt; | 
 |   } else { | 
 |     return it->second; | 
 |   } | 
 | } | 
 |  | 
 | void HttpStreamPool::IPEndPointStateTracker::OnEndpointSlow( | 
 |     const IPEndPoint& ip_endpoint) { | 
 |   // This will not overwrite the previous value, if it's already tagged as | 
 |   // kSlowSucceeded (Nor will it overwrite other values). | 
 |   ip_endpoint_states_.emplace(ip_endpoint, IPEndPointState::kSlowAttempting); | 
 |   prefer_ipv6_ = !ip_endpoint.address().IsIPv6(); | 
 | } | 
 |  | 
 | void HttpStreamPool::IPEndPointStateTracker::OnEndpointSlowSucceeded( | 
 |     const IPEndPoint& ip_endpoint) { | 
 |   auto it = ip_endpoint_states_.find(ip_endpoint); | 
 |   CHECK(it != ip_endpoint_states_.end()); | 
 |   it->second = IPEndPointState::kSlowSucceeded; | 
 | } | 
 |  | 
 | void HttpStreamPool::IPEndPointStateTracker::OnEndpointFailed( | 
 |     const IPEndPoint& ip_endpoint) { | 
 |   ip_endpoint_states_.insert_or_assign(ip_endpoint, IPEndPointState::kFailed); | 
 | } | 
 |  | 
 | void HttpStreamPool::IPEndPointStateTracker::RemoveSlowAttemptingEndpoint() { | 
 |   std::erase_if(ip_endpoint_states_, [](const auto& it) { | 
 |     return it.second == IPEndPointState::kSlowAttempting; | 
 |   }); | 
 | } | 
 |  | 
 | std::optional<IPEndPoint> | 
 | HttpStreamPool::IPEndPointStateTracker::GetIPEndPointToAttemptTcpBased( | 
 |     std::optional<IPEndPoint> exclude_ip_endpoint) { | 
 |   // TODO(crbug.com/383824591): Add a trace event to see if this method is | 
 |   // time consuming. | 
 |  | 
 |   HostResolver::ServiceEndpointRequest* service_endpoint_request = | 
 |       delegate_->GetServiceEndpointRequest(); | 
 |   if (!service_endpoint_request || | 
 |       service_endpoint_request->GetEndpointResults().empty()) { | 
 |     return std::nullopt; | 
 |   } | 
 |  | 
 |   const bool svcb_optional = delegate_->IsSvcbOptional(); | 
 |   std::optional<IPEndPoint> current_endpoint; | 
 |   std::optional<IPEndPointState> current_state; | 
 |  | 
 |   for (bool ip_v6 : {prefer_ipv6_, !prefer_ipv6_}) { | 
 |     for (const auto& service_endpoint : | 
 |          service_endpoint_request->GetEndpointResults()) { | 
 |       if (!delegate_->IsEndpointUsableForTcpBasedAttempt(service_endpoint, | 
 |                                                          svcb_optional)) { | 
 |         continue; | 
 |       } | 
 |  | 
 |       const std::vector<IPEndPoint>& ip_endpoints = | 
 |           ip_v6 ? service_endpoint.ipv6_endpoints | 
 |                 : service_endpoint.ipv4_endpoints; | 
 |       FindBetterIPEndPoint(ip_endpoints, exclude_ip_endpoint, current_state, | 
 |                            current_endpoint); | 
 |       if (current_endpoint.has_value() && !current_state.has_value()) { | 
 |         // This endpoint is fast or no connection attempt has been made to | 
 |         // it yet. | 
 |         return current_endpoint; | 
 |       } | 
 |     } | 
 |   } | 
 |  | 
 |   // No available IP endpoint, or `current_endpoint` is slow. | 
 |   return current_endpoint; | 
 | } | 
 |  | 
 | void HttpStreamPool::IPEndPointStateTracker::FindBetterIPEndPoint( | 
 |     const std::vector<IPEndPoint>& ip_endpoints, | 
 |     std::optional<IPEndPoint> exclude_ip_endpoint, | 
 |     std::optional<IPEndPointState>& current_state, | 
 |     std::optional<IPEndPoint>& current_endpoint) { | 
 |   for (const auto& ip_endpoint : ip_endpoints) { | 
 |     if (exclude_ip_endpoint.has_value() && | 
 |         ip_endpoint == *exclude_ip_endpoint) { | 
 |       continue; | 
 |     } | 
 |  | 
 |     auto it = ip_endpoint_states_.find(ip_endpoint); | 
 |     if (it == ip_endpoint_states_.end()) { | 
 |       // If there is no state for the IP endpoint it means that we haven't tried | 
 |       // the endpoint yet or previous attempt to the endpoint was fast. Just use | 
 |       // it. | 
 |       current_endpoint = ip_endpoint; | 
 |       current_state = std::nullopt; | 
 |       return; | 
 |     } | 
 |  | 
 |     switch (it->second) { | 
 |       case IPEndPointState::kFailed: | 
 |         continue; | 
 |       case IPEndPointState::kSlowAttempting: | 
 |         if (!current_endpoint.has_value() && | 
 |             !delegate_->HasEnoughTcpBasedAttemptsForSlowIPEndPoint( | 
 |                 ip_endpoint)) { | 
 |           current_endpoint = ip_endpoint; | 
 |           current_state = it->second; | 
 |         } | 
 |         continue; | 
 |       case IPEndPointState::kSlowSucceeded: | 
 |         const bool prefer_slow_succeeded = | 
 |             !current_state.has_value() || | 
 |             *current_state == IPEndPointState::kSlowAttempting; | 
 |         if (prefer_slow_succeeded && | 
 |             !delegate_->HasEnoughTcpBasedAttemptsForSlowIPEndPoint( | 
 |                 ip_endpoint)) { | 
 |           current_endpoint = ip_endpoint; | 
 |           current_state = it->second; | 
 |         } | 
 |         continue; | 
 |     } | 
 |   } | 
 | } | 
 |  | 
 | base::Value::List HttpStreamPool::IPEndPointStateTracker::GetInfoAsValue() | 
 |     const { | 
 |   base::Value::List list; | 
 |   for (const auto& [ip_endpoint, state] : ip_endpoint_states_) { | 
 |     list.Append(base::Value::Dict() | 
 |                     .Set("ip_endpoint", ip_endpoint.ToString()) | 
 |                     .Set("state", static_cast<int>(state))); | 
 |   } | 
 |   return list; | 
 | } | 
 |  | 
 | }  // namespace net |