// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef NET_SOCKET_TRANSPORT_CLIENT_SOCKET_POOL_H_
#define NET_SOCKET_TRANSPORT_CLIENT_SOCKET_POOL_H_

#include <stddef.h>
#include <stdint.h>

#include <list>
#include <map>
#include <memory>
#include <optional>
#include <set>
#include <string>
#include <utility>
#include <vector>

#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "net/base/address_list.h"
#include "net/base/completion_once_callback.h"
#include "net/base/load_states.h"
#include "net/base/load_timing_info.h"
#include "net/base/net_errors.h"
#include "net/base/net_export.h"
#include "net/base/network_change_notifier.h"
#include "net/base/priority_queue.h"
#include "net/base/proxy_chain.h"
#include "net/base/request_priority.h"
#include "net/log/net_log_with_source.h"
#include "net/socket/client_socket_handle.h"
#include "net/socket/client_socket_pool.h"
#include "net/socket/connect_job.h"
#include "net/socket/connection_attempts.h"
#include "net/socket/socket_tag.h"
#include "net/socket/ssl_client_socket.h"
#include "net/socket/stream_socket.h"

namespace net {

struct CommonConnectJobParams;
class ConnectJobFactory;
struct NetLogSource;
struct NetworkTrafficAnnotationTag;

// TransportClientSocketPool establishes network connections through using
// ConnectJobs, and maintains a list of idle persistent sockets available for
// reuse. It restricts the number of sockets open at a time, both globally, and
// for each unique GroupId, which roughly corresponds to origin and privacy mode
// setting. TransportClientSocketPool is designed to work with HTTP reuse
// semantics, handling each request serially, before reusable sockets are
// returned to the socket pool.
//
// In order to manage connection limits on a per-Proxy basis, separate
// TransportClientSocketPools are created for each proxy, and another for
// connections that have no proxy.
// TransportClientSocketPool is an internal class that implements almost all
// the functionality from ClientSocketPool.
class NET_EXPORT_PRIVATE TransportClientSocketPool
    : public ClientSocketPool,
      public NetworkChangeNotifier::IPAddressObserver,
      public SSLClientContext::Observer {
 public:
  // Reasons for closing sockets. Exposed here for testing.
  static const char kCertDatabaseChanged[];
  static const char kCertVerifierChanged[];
  static const char kClosedConnectionReturnedToPool[];
  static const char kDataReceivedUnexpectedly[];
  static const char kIdleTimeLimitExpired[];
  static const char kNetworkChanged[];
  static const char kRemoteSideClosedConnection[];
  static const char kSocketGenerationOutOfDate[];
  static const char kSocketPoolDestroyed[];
  static const char kSslConfigChanged[];

  using Flags = uint32_t;

  // Used to specify specific behavior for the ClientSocketPool.
  enum Flag {
    NORMAL = 0,             // Normal behavior.
    NO_IDLE_SOCKETS = 0x1,  // Do not return an idle socket. Create a new one.
  };

  class NET_EXPORT_PRIVATE Request {
   public:
    // If |proxy_auth_callback| is null, proxy auth challenges will
    // result in an error.
    Request(
        ClientSocketHandle* handle,
        CompletionOnceCallback callback,
        const ProxyAuthCallback& proxy_auth_callback,
        RequestPriority priority,
        const SocketTag& socket_tag,
        RespectLimits respect_limits,
        Flags flags,
        scoped_refptr<SocketParams> socket_params,
        const std::optional<NetworkTrafficAnnotationTag>& proxy_annotation_tag,
        const NetLogWithSource& net_log);

    Request(const Request&) = delete;
    Request& operator=(const Request&) = delete;

    ~Request();

    ClientSocketHandle* handle() const { return handle_; }
    CompletionOnceCallback release_callback() { return std::move(callback_); }
    const ProxyAuthCallback& proxy_auth_callback() const {
      return proxy_auth_callback_;
    }
    RequestPriority priority() const { return priority_; }
    void set_priority(RequestPriority priority) { priority_ = priority; }
    RespectLimits respect_limits() const { return respect_limits_; }
    Flags flags() const { return flags_; }
    SocketParams* socket_params() const { return socket_params_.get(); }
    const std::optional<NetworkTrafficAnnotationTag>& proxy_annotation_tag()
        const {
      return proxy_annotation_tag_;
    }
    const NetLogWithSource& net_log() const { return net_log_; }
    const SocketTag& socket_tag() const { return socket_tag_; }
    ConnectJob* job() const { return job_; }

    // Associates a ConnectJob with the request. Must be called on a request
    // that does not already have a job.
    void AssignJob(ConnectJob* job);

    // Unassigns the request's |job_| and returns it. Must be called on a
    // request with a job.
    ConnectJob* ReleaseJob();

   private:
    const raw_ptr<ClientSocketHandle> handle_;
    CompletionOnceCallback callback_;
    const ProxyAuthCallback proxy_auth_callback_;
    RequestPriority priority_;
    const RespectLimits respect_limits_;
    const Flags flags_;
    const scoped_refptr<SocketParams> socket_params_;
    const std::optional<NetworkTrafficAnnotationTag> proxy_annotation_tag_;
    const NetLogWithSource net_log_;
    const SocketTag socket_tag_;
    raw_ptr<ConnectJob> job_ = nullptr;
  };

  TransportClientSocketPool(
      int max_sockets,
      int max_sockets_per_group,
      base::TimeDelta unused_idle_socket_timeout,
      const ProxyChain& proxy_chain,
      bool is_for_websockets,
      const CommonConnectJobParams* common_connect_job_params,
      bool cleanup_on_ip_address_change = true);

  TransportClientSocketPool(const TransportClientSocketPool&) = delete;
  TransportClientSocketPool& operator=(const TransportClientSocketPool&) =
      delete;

  // Creates a socket pool with an alternative ConnectJobFactory, for use in
  // testing.
  //
  // |connect_backup_jobs_enabled| can be set to false to disable backup connect
  // jobs (Which are normally enabled).
  static std::unique_ptr<TransportClientSocketPool> CreateForTesting(
      int max_sockets,
      int max_sockets_per_group,
      base::TimeDelta unused_idle_socket_timeout,
      base::TimeDelta used_idle_socket_timeout,
      const ProxyChain& proxy_chain_,
      bool is_for_websockets,
      const CommonConnectJobParams* common_connect_job_params,
      std::unique_ptr<ConnectJobFactory> connect_job_factory,
      SSLClientContext* ssl_client_context,
      bool connect_backup_jobs_enabled);

  ~TransportClientSocketPool() override;

  // See LowerLayeredPool::IsStalled for documentation on this function.
  bool IsStalled() const override;

  // See LowerLayeredPool for documentation on these functions. It is expected
  // in the destructor that no higher layer pools remain.
  void AddHigherLayeredPool(HigherLayeredPool* higher_pool) override;
  void RemoveHigherLayeredPool(HigherLayeredPool* higher_pool) override;

  // ClientSocketPool implementation:
  int RequestSocket(
      const GroupId& group_id,
      scoped_refptr<SocketParams> params,
      const std::optional<NetworkTrafficAnnotationTag>& proxy_annotation_tag,
      RequestPriority priority,
      const SocketTag& socket_tag,
      RespectLimits respect_limits,
      ClientSocketHandle* handle,
      CompletionOnceCallback callback,
      const ProxyAuthCallback& proxy_auth_callback,
      const NetLogWithSource& net_log) override;
  int RequestSockets(
      const GroupId& group_id,
      scoped_refptr<SocketParams> params,
      const std::optional<NetworkTrafficAnnotationTag>& proxy_annotation_tag,
      int num_sockets,
      CompletionOnceCallback callback,
      const NetLogWithSource& net_log) override;
  void SetPriority(const GroupId& group_id,
                   ClientSocketHandle* handle,
                   RequestPriority priority) override;
  void CancelRequest(const GroupId& group_id,
                     ClientSocketHandle* handle,
                     bool cancel_connect_job) override;
  void ReleaseSocket(const GroupId& group_id,
                     std::unique_ptr<StreamSocket> socket,
                     int64_t group_generation) override;
  void FlushWithError(int error, const char* net_log_reason_utf8) override;
  void CloseIdleSockets(const char* net_log_reason_utf8) override;
  void CloseIdleSocketsInGroup(const GroupId& group_id,
                               const char* net_log_reason_utf8) override;
  int IdleSocketCount() const override;
  size_t IdleSocketCountInGroup(const GroupId& group_id) const override;
  LoadState GetLoadState(const GroupId& group_id,
                         const ClientSocketHandle* handle) const override;
  base::Value GetInfoAsValue(const std::string& name,
                             const std::string& type) const override;
  bool HasActiveSocket(const GroupId& group_id) const override;

  bool RequestInGroupWithHandleHasJobForTesting(
      const GroupId& group_id,
      const ClientSocketHandle* handle) const {
    return group_map_.find(group_id)->second->RequestWithHandleHasJobForTesting(
        handle);
  }

  size_t NumNeverAssignedConnectJobsInGroupForTesting(
      const GroupId& group_id) const {
    return NumNeverAssignedConnectJobsInGroup(group_id);
  }

  size_t NumUnassignedConnectJobsInGroupForTesting(
      const GroupId& group_id) const {
    return NumUnassignedConnectJobsInGroup(group_id);
  }

  size_t NumConnectJobsInGroupForTesting(const GroupId& group_id) const {
    return NumConnectJobsInGroup(group_id);
  }

  int NumActiveSocketsInGroupForTesting(const GroupId& group_id) const {
    return NumActiveSocketsInGroup(group_id);
  }

  bool HasGroupForTesting(const GroupId& group_id) const {
    return HasGroup(group_id);
  }

  static bool connect_backup_jobs_enabled();
  static bool set_connect_backup_jobs_enabled(bool enabled);

  // NetworkChangeNotifier::IPAddressObserver methods:
  void OnIPAddressChanged() override;

  // SSLClientContext::Observer methods.
  void OnSSLConfigChanged(
      SSLClientContext::SSLConfigChangeType change_type) override;
  void OnSSLConfigForServersChanged(
      const base::flat_set<HostPortPair>& servers) override;

 private:
  // Entry for a persistent socket which became idle at time |start_time|.
  struct IdleSocket;

  using RequestQueue = PriorityQueue<std::unique_ptr<Request>>;

  // A Group is allocated per GroupId when there are idle sockets, unbound
  // request, or bound requests. Otherwise, the Group object is removed from the
  // map.
  //
  // A request is "bound" to a ConnectJob when an unbound ConnectJob encounters
  // a proxy HTTP auth challenge, and the auth challenge is presented to that
  // request. Once a request and ConnectJob are bound together:
  // * All auth challenges the ConnectJob sees will be sent to that request.
  // * Cancelling the request will cancel the ConnectJob.
  // * The final result of the ConnectJob, and any returned socket, will only be
  //   sent to that bound request, though if the returned socket is returned to
  //   the socket pool, it can then be used to service any request.
  //
  // "assigned" jobs are unbound ConnectJobs that have a corresponding Request.
  // If there are 5 Jobs and 10 Requests, the 5 highest priority requests are
  // each assigned a Job. If there are 10 Jobs and 5 Requests, the first 5 Jobs
  // are each assigned to a request. Assignment is determined by order in their
  // corresponding arrays. The assignment concept is used to deal with
  // reprioritizing Jobs, and computing a Request's LoadState.
  //
  // |active_socket_count| tracks the number of sockets held by clients.
  // SanityCheck() will always be true, except during the invocation of a
  // method.  So all public methods expect the Group to pass SanityCheck() when
  // invoked.
  class NET_EXPORT_PRIVATE Group : public ConnectJob::Delegate {
   public:
    using JobList = std::list<std::unique_ptr<ConnectJob>>;

    struct BoundRequest {
      BoundRequest();
      BoundRequest(std::unique_ptr<ConnectJob> connect_job,
                   std::unique_ptr<Request> request,
                   int64_t generation);
      BoundRequest(BoundRequest&& other);
      BoundRequest& operator=(BoundRequest&& other);
      ~BoundRequest();

      std::unique_ptr<ConnectJob> connect_job;
      std::unique_ptr<Request> request;

      // Generation of |connect_job|. If it doesn't match the current
      // generation, ConnectJob will be destroyed, and a new one created on
      // completion.
      int64_t generation;

      // It's not safe to fail a request in a |CancelAllRequestsWithError| call
      // while it's waiting on user input, as the request may have raw pointers
      // to objects owned by |connect_job| that it could racily write to after
      // |connect_job| is destroyed. Instead, just track an error in that case,
      // and fail the request once the ConnectJob completes.
      int pending_error;
    };

    Group(const GroupId& group_id,
          TransportClientSocketPool* client_socket_pool);
    ~Group() override;

    // ConnectJob::Delegate methods:
    void OnConnectJobComplete(int result, ConnectJob* job) override;
    void OnNeedsProxyAuth(const HttpResponseInfo& response,
                          HttpAuthController* auth_controller,
                          base::OnceClosure restart_with_auth_callback,
                          ConnectJob* job) override;

    bool IsEmpty() const {
      return active_socket_count_ == 0 && idle_sockets_.empty() &&
             jobs_.empty() && unbound_requests_.empty() &&
             bound_requests_.empty();
    }

    bool HasAvailableSocketSlot(int max_sockets_per_group) const {
      return NumActiveSocketSlots() < max_sockets_per_group;
    }

    int NumActiveSocketSlots() const {
      return active_socket_count_ + static_cast<int>(jobs_.size()) +
             static_cast<int>(idle_sockets_.size()) +
             static_cast<int>(bound_requests_.size());
    }

    // Returns true if the group could make use of an additional socket slot, if
    // it were given one.
    bool CanUseAdditionalSocketSlot(int max_sockets_per_group) const {
      return HasAvailableSocketSlot(max_sockets_per_group) &&
             unbound_requests_.size() > jobs_.size();
    }

    // Returns the priority of the top of the unbound request queue
    // (which may be less than the maximum priority over the entire
    // queue, due to how we prioritize requests with |respect_limits|
    // DISABLED over others).
    RequestPriority TopPendingPriority() const {
      // NOTE: FirstMax().value()->priority() is not the same as
      // FirstMax().priority()!
      return unbound_requests_.FirstMax().value()->priority();
    }

    // Set a timer to create a backup job if it takes too long to
    // create one and if a timer isn't already running.
    void StartBackupJobTimer(const GroupId& group_id);

    bool BackupJobTimerIsRunning() const;

    // If there's a ConnectJob that's never been assigned to Request,
    // decrements |never_assigned_job_count_| and returns true.
    // Otherwise, returns false.
    bool TryToUseNeverAssignedConnectJob();

    void AddJob(std::unique_ptr<ConnectJob> job, bool is_preconnect);
    // Remove |job| from this group, which must already own |job|. Returns the
    // removed ConnectJob.
    std::unique_ptr<ConnectJob> RemoveUnboundJob(ConnectJob* job);
    void RemoveAllUnboundJobs();

    bool has_unbound_requests() const { return !unbound_requests_.empty(); }

    size_t unbound_request_count() const { return unbound_requests_.size(); }

    size_t ConnectJobCount() const;

    // Returns the connect job correspding to |handle|. In particular, if
    // |handle| is bound to a ConnectJob, returns that job. If |handle| is
    // "assigned" a ConnectJob, return that job. Otherwise, returns nullptr.
    ConnectJob* GetConnectJobForHandle(const ClientSocketHandle* handle) const;

    // Inserts the request into the queue based on priority
    // order. Older requests are prioritized over requests of equal
    // priority.
    void InsertUnboundRequest(std::unique_ptr<Request> request);

    // Gets (but does not remove) the next unbound request. Returns
    // NULL if there are no unbound requests.
    const Request* GetNextUnboundRequest() const;

    // Gets and removes the next unbound request. Returns NULL if
    // there are no unbound requests.
    std::unique_ptr<Request> PopNextUnboundRequest();

    // Finds the unbound request for |handle| and removes it. Returns
    // the removed unbound request, or NULL if there was none.
    std::unique_ptr<Request> FindAndRemoveUnboundRequest(
        ClientSocketHandle* handle);

    // Sets a pending error for all bound requests. Bound requests may be in the
    // middle of a callback, so can't be failed at arbitrary points in time.
    void SetPendingErrorForAllBoundRequests(int pending_error);

    // Attempts to bind the highest priority unbound request to |connect_job|,
    // and returns the bound request. If the request has previously been bound
    // to |connect_job|, returns the previously bound request. If there are no
    // requests, or the highest priority request doesn't have a proxy auth
    // callback, returns nullptr.
    const Request* BindRequestToConnectJob(ConnectJob* connect_job);

    // Finds the request, if any, bound to |connect_job|, and returns the
    // BoundRequest or std::nullopt if there was none.
    std::optional<BoundRequest> FindAndRemoveBoundRequestForConnectJob(
        ConnectJob* connect_job);

    // Finds the bound request, if any, corresponding to |client_socket_handle|
    // and returns it. Destroys the ConnectJob bound to the request, if there
    // was one.
    std::unique_ptr<Request> FindAndRemoveBoundRequest(
        ClientSocketHandle* client_socket_handle);

    // Change the priority of the request named by |*handle|.  |*handle|
    // must refer to a request currently present in the group.  If |priority|
    // is the same as the current priority of the request, this is a no-op.
    void SetPriority(ClientSocketHandle* handle, RequestPriority priority);

    void IncrementActiveSocketCount() { active_socket_count_++; }
    void DecrementActiveSocketCount() { active_socket_count_--; }

    void IncrementGeneration() { generation_++; }

    // Whether the request in |unbound_requests_| with a given handle has a job.
    bool RequestWithHandleHasJobForTesting(
        const ClientSocketHandle* handle) const;

    const GroupId& group_id() { return group_id_; }
    size_t unassigned_job_count() const { return unassigned_jobs_.size(); }
    const JobList& jobs() const { return jobs_; }
    const std::list<IdleSocket>& idle_sockets() const { return idle_sockets_; }
    int active_socket_count() const { return active_socket_count_; }
    std::list<IdleSocket>* mutable_idle_sockets() { return &idle_sockets_; }
    size_t never_assigned_job_count() const {
      return never_assigned_job_count_;
    }
    int64_t generation() const { return generation_; }

   private:
    // Returns the iterator's unbound request after removing it from
    // the queue. Expects the Group to pass SanityCheck() when called.
    std::unique_ptr<Request> RemoveUnboundRequest(
        const RequestQueue::Pointer& pointer);

    // Finds the Request which is associated with the given ConnectJob.
    // Returns nullptr if none is found. Expects the Group to pass SanityCheck()
    // when called.
    RequestQueue::Pointer FindUnboundRequestWithJob(
        const ConnectJob* job) const;

    // Finds the Request in |unbound_requests_| which is the first request
    // without a job. Returns a null pointer if all requests have jobs. Does not
    // expect the Group to pass SanityCheck() when called, but does expect all
    // jobs to either be assigned to a request or in |unassigned_jobs_|. Expects
    // that no requests with jobs come after any requests without a job.
    RequestQueue::Pointer GetFirstRequestWithoutJob() const;

    // Tries to assign an unassigned |job| to a request. If no requests need a
    // job, |job| is added to |unassigned_jobs_|.
    // When called, does not expect the Group to pass SanityCheck(), but does
    // expect it to have passed SanityCheck() before the given ConnectJob was
    // either created or had the request it was assigned to removed.
    void TryToAssignUnassignedJob(ConnectJob* job);

    // Tries to assign a job to the given request. If any unassigned jobs are
    // available, the first unassigned job is assigned to the request.
    // Otherwise, if the request is ahead of the last request with a job, the
    // job is stolen from the last request with a job.
    // When called, does not expect the Group to pass SanityCheck(), but does
    // expect that:
    //  - the request associated with |request_pointer| must not have
    //    an assigned ConnectJob,
    //  - the first min( jobs_.size(), unbound_requests_.size() - 1 ) Requests
    //    other than the given request must have ConnectJobs, i.e. the group
    //    must have passed SanityCheck() before the passed in Request was either
    //    added or had its job unassigned.
    void TryToAssignJobToRequest(RequestQueue::Pointer request_pointer);

    // Transfers the associated ConnectJob from one Request to another. Expects
    // the source request to have a job, and the destination request to not have
    // a job. Does not expect the Group to pass SanityCheck() when called.
    void TransferJobBetweenRequests(Request* source, Request* dest);

    // Called when the backup socket timer fires.
    void OnBackupJobTimerFired(const GroupId& group_id);

    // Checks that:
    //  - |unassigned_jobs_| is empty iff there are at least as many requests
    //    as jobs.
    //  - Exactly the first |jobs_.size() - unassigned_jobs_.size()| requests
    //    have ConnectJobs.
    //  - No requests are assigned a ConnectJob in |unassigned_jobs_|.
    //  - No requests are assigned a ConnectJob not in |jobs_|.
    //  - No two requests are assigned the same ConnectJob.
    //  - All entries in |unassigned_jobs_| are also in |jobs_|.
    //  - There are no duplicate entries in |unassigned_jobs_|.
    void SanityCheck() const;

    const GroupId group_id_;
    const raw_ptr<TransportClientSocketPool> client_socket_pool_;

    // Total number of ConnectJobs that have never been assigned to a Request.
    // Since jobs use late binding to requests, which ConnectJobs have or have
    // not been assigned to a request are not tracked.  This is incremented on
    // preconnect and decremented when a preconnect is assigned, or when there
    // are fewer than |never_assigned_job_count_| ConnectJobs.  Not incremented
    // when a request is cancelled.
    size_t never_assigned_job_count_ = 0;

    std::list<IdleSocket> idle_sockets_;
    JobList jobs_;  // For bookkeeping purposes, there is a copy of the raw
                    // pointer of each element of |jobs_| stored either in
                    // |unassigned_jobs_|, or as the associated |job_| of an
                    // element of |unbound_requests_|.
    std::list<raw_ptr<ConnectJob, CtnExperimental>> unassigned_jobs_;
    RequestQueue unbound_requests_;
    int active_socket_count_ = 0;  // number of active sockets used by clients
    // A timer for when to start the backup job.
    base::OneShotTimer backup_job_timer_;

    // List of Requests bound to ConnectJobs currently undergoing proxy auth.
    // The Requests and ConnectJobs in this list do not appear in
    // |unbound_requests_| or |jobs_|.
    std::vector<BoundRequest> bound_requests_;

    // An id for the group.  It gets incremented every time we FlushWithError()
    // the socket pool, or refresh the group.  This is so that when sockets get
    // released back to the group, we can make sure that they are discarded
    // rather than reused. Destroying a group will reset the generation number,
    // but as that only happens once there are no outstanding sockets or
    // requests associated with the group, that's harmless.
    int64_t generation_ = 0;
  };

  using GroupMap = std::map<GroupId, Group*>;

  struct CallbackResultPair {
    CallbackResultPair();
    CallbackResultPair(CompletionOnceCallback callback_in, int result_in);
    CallbackResultPair(CallbackResultPair&& other);
    CallbackResultPair& operator=(CallbackResultPair&& other);
    ~CallbackResultPair();

    CompletionOnceCallback callback;
    int result;
  };

  using PendingCallbackMap =
      std::map<const ClientSocketHandle*, CallbackResultPair>;

  TransportClientSocketPool(
      int max_sockets,
      int max_sockets_per_group,
      base::TimeDelta unused_idle_socket_timeout,
      base::TimeDelta used_idle_socket_timeout,
      const ProxyChain& proxy_chain,
      bool is_for_websockets,
      const CommonConnectJobParams* common_connect_job_params,
      bool cleanup_on_ip_address_change,
      std::unique_ptr<ConnectJobFactory> connect_job_factory,
      SSLClientContext* ssl_client_context,
      bool connect_backup_jobs_enabled);

  base::TimeDelta ConnectRetryInterval() const {
    // TODO(mbelshe): Make this tuned dynamically based on measured RTT.
    //                For now, just use the max retry interval.
    return base::Milliseconds(kMaxConnectRetryIntervalMs);
  }

  // TODO(mmenke): de-inline these.
  size_t NumNeverAssignedConnectJobsInGroup(const GroupId& group_id) const {
    return group_map_.find(group_id)->second->never_assigned_job_count();
  }

  size_t NumUnassignedConnectJobsInGroup(const GroupId& group_id) const {
    return group_map_.find(group_id)->second->unassigned_job_count();
  }

  size_t NumConnectJobsInGroup(const GroupId& group_id) const {
    return group_map_.find(group_id)->second->ConnectJobCount();
  }

  int NumActiveSocketsInGroup(const GroupId& group_id) const {
    return group_map_.find(group_id)->second->active_socket_count();
  }

  bool HasGroup(const GroupId& group_id) const;

  // Closes all idle sockets if |force| is true.  Else, only closes idle
  // sockets that timed out or can't be reused.  Made public for testing.
  // |reason| must be non-empty when |force| is true.
  void CleanupIdleSockets(bool force, const char* net_log_reason_utf8);

  // Closes one idle socket.  Picks the first one encountered.
  // TODO(willchan): Consider a better algorithm for doing this.  Perhaps we
  // should keep an ordered list of idle sockets, and close them in order.
  // Requires maintaining more state.  It's not clear if it's worth it since
  // I'm not sure if we hit this situation often.
  bool CloseOneIdleSocket();

  // Checks higher layered pools to see if they can close an idle connection.
  bool CloseOneIdleConnectionInHigherLayeredPool();

  // Closes all idle sockets in |group| if |force| is true.  Else, only closes
  // idle sockets in |group| that timed out with respect to |now| or can't be
  // reused.
  void CleanupIdleSocketsInGroup(bool force,
                                 Group* group,
                                 const base::TimeTicks& now,
                                 const char* net_log_reason_utf8);

  Group* GetOrCreateGroup(const GroupId& group_id);
  void RemoveGroup(const GroupId& group_id);
  GroupMap::iterator RemoveGroup(GroupMap::iterator it);

  // Called when the number of idle sockets changes.
  void IncrementIdleCount();
  void DecrementIdleCount();

  // Scans the group map for groups which have an available socket slot and
  // at least one pending request. Returns true if any groups are stalled, and
  // if so (and if both |group| and |group_id| are not NULL), fills |group|
  // and |group_id| with data of the stalled group having highest priority.
  bool FindTopStalledGroup(Group** group, GroupId* group_id) const;

  // Removes |job| from |group|, which must already own |job|.
  void RemoveConnectJob(ConnectJob* job, Group* group);

  // Tries to see if we can handle any more requests for |group|.
  void OnAvailableSocketSlot(const GroupId& group_id, Group* group);

  // Process a pending socket request for a group.
  void ProcessPendingRequest(const GroupId& group_id, Group* group);

  // Assigns |socket| to |handle| and updates |group|'s counters appropriately.
  void HandOutSocket(std::unique_ptr<StreamSocket> socket,
                     ClientSocketHandle::SocketReuseType reuse_type,
                     const LoadTimingInfo::ConnectTiming& connect_timing,
                     ClientSocketHandle* handle,
                     base::TimeDelta time_idle,
                     Group* group,
                     const NetLogWithSource& net_log);

  // Adds |socket| to the list of idle sockets for |group|.
  void AddIdleSocket(std::unique_ptr<StreamSocket> socket, Group* group);

  // Iterates through |group_map_|, canceling all ConnectJobs and deleting
  // groups if they are no longer needed.
  void CancelAllConnectJobs();

  // Iterates through |group_map_|, posting |error| callbacks for all
  // requests, and then deleting groups if they are no longer needed.
  void CancelAllRequestsWithError(int error);

  // Returns true if we can't create any more sockets due to the total limit.
  bool ReachedMaxSocketsLimit() const;

  // This is the internal implementation of RequestSocket().  It differs in that
  // it does not handle logging into NetLog of the queueing status of
  // |request|.
  // |preconnect_done_closure| is used only for preconnect requests. For
  // preconnect requests, this method returns ERR_IO_PENDING only if a connect
  // job is created and the connect job didn't finish synchronously. In such
  // case, |preconnect_done_closure| will be called when the created connect job
  // will be deleted.
  // For normal non-preconnect requests, |preconnect_done_closure| must be null.
  // And this method returns ERR_IO_PENDING when the number of sockets has
  // reached the limit or the created connect job didn't finish synchronously.
  // In such a case, the Request with a ClientSocketHandle must be registered to
  // |group_map_| to receive the completion callback.
  int RequestSocketInternal(const GroupId& group_id,
                            const Request& request,
                            base::OnceClosure preconnect_done_closure);

  // Assigns an idle socket for the group to the request.
  // Returns |true| if an idle socket is available, false otherwise.
  bool AssignIdleSocketToRequest(const Request& request, Group* group);

  static void LogBoundConnectJobToRequest(
      const NetLogSource& connect_job_source,
      const Request& request);

  // Same as CloseOneIdleSocket() except it won't close an idle socket in
  // |group|.  If |group| is NULL, it is ignored.  Returns true if it closed a
  // socket.
  bool CloseOneIdleSocketExceptInGroup(const Group* group);

  // Checks if there are stalled socket groups that should be notified
  // for possible wakeup.
  void CheckForStalledSocketGroups();

  // Posts a task to call InvokeUserCallback() on the next iteration through the
  // current message loop.  Inserts |callback| into |pending_callback_map_|,
  // keyed by |handle|. Apply |socket_tag| to the socket if socket successfully
  // created.
  void InvokeUserCallbackLater(ClientSocketHandle* handle,
                               CompletionOnceCallback callback,
                               int rv,
                               const SocketTag& socket_tag);

  // These correspond to ConnectJob::Delegate methods, and are invoked by the
  // Group a ConnectJob belongs to.
  void OnConnectJobComplete(Group* group, int result, ConnectJob* job);
  void OnNeedsProxyAuth(Group* group,
                        const HttpResponseInfo& response,
                        HttpAuthController* auth_controller,
                        base::OnceClosure restart_with_auth_callback,
                        ConnectJob* job);

  // Invokes the user callback for |handle|.  By the time this task has run,
  // it's possible that the request has been cancelled, so |handle| may not
  // exist in |pending_callback_map_|.  We look up the callback and result code
  // in |pending_callback_map_|.
  void InvokeUserCallback(MayBeDangling<ClientSocketHandle> handle);

  // Tries to close idle sockets in a higher level socket pool as long as this
  // this pool is stalled.
  void TryToCloseSocketsInLayeredPools();

  // Closes all idle sockets and cancels all unbound ConnectJobs associated with
  // |it->second|. Also increments the group's generation number, ensuring any
  // currently existing handed out socket will be silently closed when it is
  // returned to the socket pool. Bound ConnectJobs will only be destroyed on
  // once they complete, as they may be waiting on user input. No request
  // (including bound ones) will be failed as a result of this call - instead,
  // new ConnectJobs will be created.
  //
  // The group may be removed if this leaves the group empty. The caller must
  // call CheckForStalledSocketGroups() after all applicable groups have been
  // refreshed.
  GroupMap::iterator RefreshGroup(GroupMap::iterator it,
                                  const base::TimeTicks& now,
                                  const char* net_log_reason_utf8);

  GroupMap group_map_;

  // Map of the ClientSocketHandles for which we have a pending Task to invoke a
  // callback.  This is necessary since, before we invoke said callback, it's
  // possible that the request is cancelled.
  PendingCallbackMap pending_callback_map_;

  // The total number of idle sockets in the system.
  int idle_socket_count_ = 0;

  // Number of connecting sockets across all groups.
  int connecting_socket_count_ = 0;

  // Number of connected sockets we handed out across all groups.
  int handed_out_socket_count_ = 0;

  // The maximum total number of sockets. See ReachedMaxSocketsLimit.
  const int max_sockets_;

  // The maximum number of sockets kept per group.
  const int max_sockets_per_group_;

  // The time to wait until closing idle sockets.
  const base::TimeDelta unused_idle_socket_timeout_;
  const base::TimeDelta used_idle_socket_timeout_;

  const ProxyChain proxy_chain_;

  const bool cleanup_on_ip_address_change_;

  // TODO(vandebo) Remove when backup jobs move to TransportClientSocketPool
  bool connect_backup_jobs_enabled_;

  // Pools that create connections through |this|.  |this| will try to close
  // their idle sockets when it stalls.  Must be empty on destruction.
  std::set<raw_ptr<HigherLayeredPool, SetExperimental>> higher_pools_;

  const raw_ptr<SSLClientContext> ssl_client_context_;

#if DCHECK_IS_ON()
  // Reentrancy guard for RequestSocketInternal().
  bool request_in_process_ = false;
#endif  // DCHECK_IS_ON()

  base::WeakPtrFactory<TransportClientSocketPool> weak_factory_{this};
};

}  // namespace net

#endif  // NET_SOCKET_TRANSPORT_CLIENT_SOCKET_POOL_H_
