// Copyright 2016 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_HTTP_HTTP_STREAM_FACTORY_JOB_CONTROLLER_H_
#define NET_HTTP_HTTP_STREAM_FACTORY_JOB_CONTROLLER_H_

#include <memory>
#include <string>
#include <vector>

#include "base/cancelable_callback.h"
#include "base/memory/raw_ptr.h"
#include "base/time/time.h"
#include "net/base/host_port_pair.h"
#include "net/base/net_errors.h"
#include "net/http/http_stream_factory_job.h"
#include "net/http/http_stream_request.h"
#include "net/spdy/spdy_session_pool.h"
#include "net/ssl/ssl_config.h"

namespace net {

class ProxyResolutionRequest;

namespace test {

class JobControllerPeer;

}  // namespace test

// HttpStreamFactory::JobController manages Request and Job(s).
class HttpStreamFactory::JobController
    : public HttpStreamFactory::Job::Delegate,
      public HttpStreamRequest::Helper {
 public:
  JobController(HttpStreamFactory* factory,
                HttpStreamRequest::Delegate* delegate,
                HttpNetworkSession* session,
                JobFactory* job_factory,
                const HttpRequestInfo& http_request_info,
                bool is_preconnect,
                bool is_websocket,
                bool enable_ip_based_pooling,
                bool enable_alternative_services,
                bool delay_main_job_with_available_spdy_session,
                const std::vector<SSLConfig::CertAndStatus>& allowed_bad_certs);

  ~JobController() override;

  // Used in tests only for verification purpose.
  const Job* main_job() const { return main_job_.get(); }
  const Job* alternative_job() const { return alternative_job_.get(); }
  const Job* dns_alpn_h3_job() const { return dns_alpn_h3_job_.get(); }

  // Modifies `url` in-place, applying any applicable HostMappingRules of
  // `session_` to it.
  void RewriteUrlWithHostMappingRules(GURL& url) const;

  // Same as RewriteUrlWithHostMappingRules(), but duplicates `url` instead of
  // modifying it.
  GURL DuplicateUrlWithHostMappingRules(const GURL& url) const;

  // Methods below are called by HttpStreamFactory only.
  // Creates request and hands out to HttpStreamFactory, this will also create
  // Job(s) and start serving the created request.
  std::unique_ptr<HttpStreamRequest> Start(
      HttpStreamRequest::Delegate* delegate,
      WebSocketHandshakeStreamBase::CreateHelper*
          websocket_handshake_stream_create_helper,
      const NetLogWithSource& source_net_log,
      HttpStreamRequest::StreamType stream_type,
      RequestPriority priority);

  void Preconnect(int num_streams);

  // From HttpStreamRequest::Helper.
  // Returns the LoadState for Request.
  LoadState GetLoadState() const override;

  // Called when Request is destructed. Job(s) associated with but not bound to
  // |request_| will be deleted. |request_| and |bound_job_| will be nulled if
  // ever set.
  void OnRequestComplete() override;

  // Called to resume the HttpStream creation process when necessary
  // Proxy authentication credentials are collected.
  int RestartTunnelWithProxyAuth() override;

  // Called when the priority of transaction changes.
  void SetPriority(RequestPriority priority) override;

  // From HttpStreamFactory::Job::Delegate.
  // Invoked when |job| has an HttpStream ready.
  void OnStreamReady(Job* job) override;

  // Invoked when |job| has a BidirectionalStream ready.
  void OnBidirectionalStreamImplReady(
      Job* job,
      const ProxyInfo& used_proxy_info) override;

  // Invoked when |job| has a WebSocketHandshakeStream ready.
  void OnWebSocketHandshakeStreamReady(
      Job* job,
      const ProxyInfo& used_proxy_info,
      std::unique_ptr<WebSocketHandshakeStreamBase> stream) override;

  // Invoked when |job| fails to create a stream.
  void OnStreamFailed(Job* job, int status) override;

  // Invoked when |job| fails on the default network.
  void OnFailedOnDefaultNetwork(Job* job) override;

  // Invoked when |job| has a certificate error for the Request.
  void OnCertificateError(Job* job,
                          int status,
                          const SSLInfo& ssl_info) override;

  // Invoked when |job| raises failure for SSL Client Auth.
  void OnNeedsClientAuth(Job* job, SSLCertRequestInfo* cert_info) override;

  // Invoked when |job| needs proxy authentication.
  void OnNeedsProxyAuth(Job* job,
                        const HttpResponseInfo& proxy_response,
                        const ProxyInfo& used_proxy_info,
                        HttpAuthController* auth_controller) override;

  // Invoked when the |job| finishes pre-connecting sockets.
  void OnPreconnectsComplete(Job* job, int result) override;

  // Invoked to record connection attempts made by the socket layer to
  // Request if |job| is associated with Request.
  void AddConnectionAttemptsToRequest(
      Job* job,
      const ConnectionAttempts& attempts) override;

  // Invoked when |job| finishes initiating a connection.
  // Resume the other job if there's an error raised.
  void OnConnectionInitialized(Job* job, int rv) override;

  // Return false if |job| can advance to the next state. Otherwise, |job|
  // will wait for Job::Resume() to be called before advancing.
  bool ShouldWait(Job* job) override;

  const NetLogWithSource* GetNetLog() const override;

  void MaybeSetWaitTimeForMainJob(const base::TimeDelta& delay) override;

  WebSocketHandshakeStreamBase::CreateHelper*
  websocket_handshake_stream_create_helper() override;

  bool is_preconnect() const { return is_preconnect_; }

  // Returns true if |this| has a pending request that is not completed.
  bool HasPendingRequest() const { return request_ != nullptr; }

  // Returns true if |this| has a pending main job that is not completed.
  bool HasPendingMainJob() const;

  // Returns true if |this| has a pending alternative job that is not completed.
  bool HasPendingAltJob() const;

  base::TimeDelta get_main_job_wait_time_for_tests() {
    return main_job_wait_time_;
  }

 private:
  friend class test::JobControllerPeer;

  enum State {
    STATE_RESOLVE_PROXY,
    STATE_RESOLVE_PROXY_COMPLETE,
    STATE_CREATE_JOBS,
    STATE_NONE
  };

  void OnIOComplete(int result);
  void OnResolveProxyError(int error);
  void RunLoop(int result);
  int DoLoop(int result);
  int DoResolveProxy();
  int DoResolveProxyComplete(int result);
  // Creates Job(s) for |request_info_|. Job(s) will be owned by |this|.
  int DoCreateJobs();

  // Called to bind |job| to the |request_| and orphan all other jobs that are
  // still associated with |request_|.
  void BindJob(Job* job);

  // Called after BindJob() to notify the unbound job that its result should be
  // ignored by JobController. The unbound job can be canceled or continue until
  // completion.
  void OrphanUnboundJob();

  // Invoked when the orphaned |job| finishes.
  void OnOrphanedJobComplete(const Job* job);

  // Called when a Job succeeds.
  void OnJobSucceeded(Job* job);

  // Clears inappropriate jobs before starting them.
  void ClearInappropriateJobs();

  // Marks completion of the |request_|.
  void MarkRequestComplete(Job* job);

  // Called when all Jobs complete. Reports alternative service brokenness to
  // HttpServerProperties if apply and resets net errors afterwards:
  // - report broken if the main job has no error and the alternative job has an
  //   error;
  // - report broken until default network change if the main job has no error,
  //   the alternative job has no error, but the alternative job failed on the
  //   default network.
  void MaybeReportBrokenAlternativeService(
      const AlternativeService& alt_service,
      int alt_job_net_error,
      bool alt_job_failed_on_default_network,
      const std::string& histogram_name_for_failure);

  void MaybeNotifyFactoryOfCompletion();

  void NotifyRequestFailed(int rv);

  // Called to resume the main job with delay. Main job is resumed only when
  // |alternative_job_| has failed or |main_job_wait_time_| elapsed.
  void MaybeResumeMainJob(Job* job, const base::TimeDelta& delay);

  // Posts a task to resume the main job after |delay|.
  void ResumeMainJobLater(const base::TimeDelta& delay);

  // Resumes the main job immediately.
  void ResumeMainJob();

  // Reset error status to default value for Jobs:
  // - reset |main_job_net_error_| and |alternative_job_net_error_| and
  //   |dns_alpn_h3_job_net_error_| to OK;
  // - reset |alternative_job_failed_on_default_network_| and
  //   |dns_alpn_h3_job_failed_on_default_network_| to false.
  void ResetErrorStatusForJobs();

  AlternativeServiceInfo GetAlternativeServiceInfoFor(
      const GURL& http_request_info_url,
      const StreamRequestInfo& request_info,
      HttpStreamRequest::Delegate* delegate,
      HttpStreamRequest::StreamType stream_type);

  AlternativeServiceInfo GetAlternativeServiceInfoInternal(
      const GURL& http_request_info_url,
      const StreamRequestInfo& request_info,
      HttpStreamRequest::Delegate* delegate,
      HttpStreamRequest::StreamType stream_type);

  // Returns the first quic::ParsedQuicVersion that has been advertised in
  // |advertised_versions| and is supported, following the order of
  // |advertised_versions|.  If no mutually supported version is found,
  // quic::ParsedQuicVersion::Unsupported() will be returned.
  quic::ParsedQuicVersion SelectQuicVersion(
      const quic::ParsedQuicVersionVector& advertised_versions);

  // Records histogram metrics for the usage of alternative protocol. Must be
  // called when |job| has succeeded and the other job will be orphaned.
  void ReportAlternateProtocolUsage(
      AlternateProtocolUsage alternate_protocol_usage,
      bool is_google_host) const;

  // Returns whether |job| is an orphaned job.
  bool IsJobOrphaned(Job* job) const;

  // Calculates why Chrome uses a specific transport protocol for HTTP semantics
  // and returns it as an enum.
  // This returns ALTERNATE_PROTOCOL_USAGE_UNSPECIFIED_REASON as a default value
  // when the reason is unknown.
  AlternateProtocolUsage CalculateAlternateProtocolUsage(Job* job) const;

  // Called when a Job encountered a network error that could be resolved by
  // trying a new proxy configuration. If there is another proxy configuration
  // to try then this method sets |next_state_| appropriately and returns either
  // OK or ERR_IO_PENDING depending on whether or not the new proxy
  // configuration is available synchronously or asynchronously.  Otherwise, the
  // given error code is simply returned.
  int ReconsiderProxyAfterError(Job* job, int error);

  // Returns true if QUIC is allowed for |host|.
  bool IsQuicAllowedForHost(const std::string& host);

  int GetJobCount() const {
    return (main_job_ ? 1 : 0) + (alternative_job_ ? 1 : 0) +
           (dns_alpn_h3_job_ ? 1 : 0);
  }

  raw_ptr<HttpStreamFactory> factory_;
  raw_ptr<HttpNetworkSession> session_;
  raw_ptr<JobFactory> job_factory_;

  // Request will be handed out to factory once created. This just keeps an
  // reference and is safe as |request_| will notify |this| JobController
  // when it's destructed by calling OnRequestComplete(), which nulls
  // |request_|.
  raw_ptr<HttpStreamRequest> request_ = nullptr;

  raw_ptr<HttpStreamRequest::Delegate> delegate_;

  // True if this JobController is used to preconnect streams.
  const bool is_preconnect_;

  // True if request is for Websocket.
  const bool is_websocket_;

  // Enable pooling to a SpdySession with matching IP and certificate even if
  // the SpdySessionKey is different.
  const bool enable_ip_based_pooling_;

  // Enable using alternative services for the request. If false, the
  // JobController will only create a |main_job_|.
  const bool enable_alternative_services_;

  // For normal (non-preconnect) job, |main_job_| is a job waiting to see if
  // |alternative_job_| or |dns_alpn_h3_job_| can reuse a connection. If both
  // |alternative_job_| and |dns_alpn_h3_job_| are unable to do so, |this| will
  // notify |main_job_| to proceed and then race the two jobs.
  // For preconnect job, |main_job_| is started first, and if it fails with
  // ERR_DNS_NO_MATCHING_SUPPORTED_ALPN, |preconnect_backup_job_| will be
  // started.
  std::unique_ptr<Job> main_job_;
  std::unique_ptr<Job> alternative_job_;
  std::unique_ptr<Job> dns_alpn_h3_job_;

  std::unique_ptr<Job> preconnect_backup_job_;

  // The alternative service used by |alternative_job_|
  // (or by |main_job_| if |is_preconnect_|.)
  AlternativeServiceInfo alternative_service_info_;

  // Error status used for alternative service brokenness reporting.
  // Net error code of the main job. Set to OK by default.
  int main_job_net_error_ = OK;
  // Net error code of the alternative job. Set to OK by default.
  int alternative_job_net_error_ = OK;
  // Set to true if the alternative job failed on the default network.
  bool alternative_job_failed_on_default_network_ = false;
  // Net error code of the DNS HTTPS ALPN job. Set to OK by default.
  int dns_alpn_h3_job_net_error_ = OK;
  // Set to true if the DNS HTTPS ALPN job failed on the default network.
  bool dns_alpn_h3_job_failed_on_default_network_ = false;

  // True if a Job has ever been bound to the |request_|.
  bool job_bound_ = false;

  // True if the main job has to wait for the alternative job: i.e., the main
  // job must not create a connection until it is resumed.
  bool main_job_is_blocked_ = false;

  // Handle for cancelling any posted delayed ResumeMainJob() task.
  base::CancelableOnceClosure resume_main_job_callback_;
  // True if the main job was blocked and has been resumed in ResumeMainJob().
  bool main_job_is_resumed_ = false;

  // If true, delay main job even the request can be sent immediately on an
  // available SPDY session.
  bool delay_main_job_with_available_spdy_session_;

  // Waiting time for the main job before it is resumed.
  base::TimeDelta main_job_wait_time_;

  // At the point where a Job is irrevocably tied to |request_|, we set this.
  // It will be nulled when the |request_| is finished.
  raw_ptr<Job> bound_job_ = nullptr;

  State next_state_ = STATE_RESOLVE_PROXY;
  std::unique_ptr<ProxyResolutionRequest> proxy_resolve_request_;
  // The URL from the input `http_request_info`.
  // TODO(https://crbug.com/332724851): Remove this, and update code to use
  // `origin_url_`.
  const GURL http_request_info_url_;
  // The same as `request_info_url_`, but with any applicable rules in
  // HostMappingRules applied to it.
  // TODO: Make this use SchemeHostPort instead, and rename it.
  const GURL origin_url_;
  const StreamRequestInfo request_info_;
  ProxyInfo proxy_info_;
  const std::vector<SSLConfig::CertAndStatus> allowed_bad_certs_;
  int num_streams_ = 0;
  HttpStreamRequest::StreamType stream_type_;
  RequestPriority priority_ = IDLE;
  const NetLogWithSource net_log_;

  base::WeakPtrFactory<JobController> ptr_factory_{this};
};

}  // namespace net

#endif  // NET_HTTP_HTTP_STREAM_FACTORY_JOB_CONTROLLER_H_
