// 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.

#ifndef COMPONENTS_CRONET_CRONET_URL_REQUEST_H_
#define COMPONENTS_CRONET_CRONET_URL_REQUEST_H_

#include <memory>
#include <string>

#include "base/functional/callback.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "base/time/time.h"
#include "net/base/idempotency.h"
#include "net/base/network_handle.h"
#include "net/base/request_priority.h"
#include "net/shared_dictionary/shared_dictionary.h"
#include "net/third_party/quiche/src/quiche/quic/core/quic_types.h"
#include "net/url_request/url_request.h"
#include "url/gurl.h"

namespace net {
class HttpRequestHeaders;
enum LoadState;
class SSLCertRequestInfo;
class SSLInfo;
class UploadDataStream;
}  // namespace net

namespace cronet {

class CronetContext;
class TestUtil;

// Wrapper around net::URLRequestContext.
// Created and configured from client thread. Start, ReadData, and Destroy are
// posted to network thread and all callbacks into the Callback() are
// done on the network thread. CronetUrlRequest client is expected to initiate
// the next step like FollowDeferredRedirect, ReadData or Destroy. Public
// methods can be called on any thread.
class CronetURLRequest {
 public:
  // Callback implemented by CronetURLRequest() caller and owned by
  // CronetURLRequest::NetworkTasks. All callback methods are invoked on network
  // thread.
  class Callback {
   public:
    virtual ~Callback() = default;

    // Invoked whenever a redirect is encountered. This will only be invoked
    // between the call to CronetURLRequest::Start() and
    // Callback::OnResponseStarted(). The body of the redirect response, if
    // it has one, will be ignored.
    //
    // The redirect will not be followed until
    // CronetURLRequest::FollowDeferredRedirect() method is called, either
    // synchronously or asynchronously.
    virtual void OnReceivedRedirect(const std::string& new_location,
                                    int http_status_code,
                                    const std::string& http_status_text,
                                    const net::HttpResponseHeaders* headers,
                                    bool was_cached,
                                    const std::string& negotiated_protocol,
                                    const std::string& proxy_server,
                                    int64_t received_byte_count) = 0;

    // Invoked when the final set of headers, after all redirects, is received.
    // Will only be invoked once for each request.
    //
    // With the exception of Callback::OnCanceled(),
    // no other Callback method will be invoked for the request,
    // including Callback::OnSucceeded() and Callback::OnFailed(), until
    // CronetUrlRequest::Read() is called to attempt to start reading the
    // response body.
    virtual void OnResponseStarted(int http_status_code,
                                   const std::string& http_status_text,
                                   const net::HttpResponseHeaders* headers,
                                   bool was_cached,
                                   const std::string& negotiated_protocol,
                                   const std::string& proxy_server,
                                   int64_t received_byte_count,
                                   bool is_proxied) = 0;

    // Invoked whenever part of the response body has been read. Only part of
    // the buffer may be populated, even if the entire response body has not yet
    // been consumed.
    //
    // With the exception of Callback::OnCanceled(),
    // no other Callback method will be invoked for the request,
    // including Callback::OnSucceeded() and Callback::OnFailed(), until
    // CronetUrlRequest::Read() is called to attempt to continue reading the
    // response body.
    virtual void OnReadCompleted(scoped_refptr<net::IOBuffer> buffer,
                                 int bytes_read,
                                 int64_t received_byte_count) = 0;

    // Invoked when request is completed successfully.
    virtual void OnSucceeded(int64_t received_byte_count) = 0;

    // Invoked if request failed for any reason after CronetURLRequest::Start().
    // |net_error| provides information about the failure. |quic_error| is only
    // valid if |net_error| is net::QUIC_PROTOCOL_ERROR.
    // |source| represents who asked to terminate the connection, it is only
    // valid if the |net_error| is net::QUIC_PROTOCOL_ERROR.
    virtual void OnError(int net_error,
                         int quic_error,
                         quic::ConnectionCloseSource source,
                         const std::string& error_string,
                         int64_t received_byte_count) = 0;

    // Invoked if request was canceled via CronetURLRequest::Destroy().
    virtual void OnCanceled() = 0;

    // Invoked when request is destroyed. Once invoked, no other Callback
    // methods will be invoked.
    virtual void OnDestroyed() = 0;

    // Reports metrics data about the request.
    // This is called immediately before the terminal state callback (i.e.
    // OnSucceeded()/OnError()/OnCanceled()).
    virtual void OnMetricsCollected(
        const base::Time& request_start_time,
        const base::TimeTicks& request_start,
        const base::TimeTicks& dns_start,
        const base::TimeTicks& dns_end,
        const base::TimeTicks& connect_start,
        const base::TimeTicks& connect_end,
        const base::TimeTicks& ssl_start,
        const base::TimeTicks& ssl_end,
        const base::TimeTicks& send_start,
        const base::TimeTicks& send_end,
        const base::TimeTicks& push_start,
        const base::TimeTicks& push_end,
        const base::TimeTicks& receive_headers_end,
        const base::TimeTicks& request_end,
        bool socket_reused,
        int64_t sent_bytes_count,
        int64_t received_bytes_count,
        bool quic_connection_migration_attempted,
        bool quic_connection_migration_successful) = 0;
  };
  // Invoked in response to CronetURLRequest::GetStatus() to allow multiple
  // overlapping calls. The load states correspond to the lengthy periods of
  // time that a request load may be blocked and unable to make progress.
  using OnStatusCallback = base::OnceCallback<void(net::LoadState)>;

  // Bypasses cache if |disable_cache| is true. If context is not set up to
  // use cache, |disable_cache| has no effect. |disable_connection_migration|
  // causes connection migration to be disabled for this request if true. If
  // global connection migration flag is not enabled,
  // |disable_connection_migration| has no effect.
  CronetURLRequest(CronetContext* context,
                   std::unique_ptr<Callback> callback,
                   const GURL& url,
                   net::RequestPriority priority,
                   bool disable_cache,
                   bool disable_connection_migration,
                   bool traffic_stats_tag_set,
                   int32_t traffic_stats_tag,
                   bool traffic_stats_uid_set,
                   int32_t traffic_stats_uid,
                   net::Idempotency idempotency,
                   scoped_refptr<net::SharedDictionary> shared_dictionary,
                   net::handles::NetworkHandle network =
                       net::handles::kInvalidNetworkHandle);

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

  // Methods called prior to Start are never called on network thread.

  // Sets the request method GET, POST etc.
  bool SetHttpMethod(const std::string& method);

  // Adds a header to the request before it starts.
  bool AddRequestHeader(const std::string& name, const std::string& value);

  // Adds a request body to the request before it starts.
  void SetUpload(std::unique_ptr<net::UploadDataStream> upload);

  // Starts the request.
  void Start();

  // GetStatus invokes |on_status_callback| on network thread to allow multiple
  // overlapping calls.
  void GetStatus(OnStatusCallback on_status_callback) const;

  // Follows redirect.
  void FollowDeferredRedirect();

  // Reads more data.
  bool ReadData(net::IOBuffer* buffer, int max_bytes);

  // Releases all resources for the request and deletes the object itself.
  // |send_on_canceled| indicates whether OnCanceled callback should be
  // issued to indicate when no more callbacks will be issued.
  void Destroy(bool send_on_canceled);

  // On the network thread, reports metrics to the registered
  // CronetURLRequest::Callback, and then runs |callback| on the network thread.
  //
  // Since metrics are only reported once, this can be used to ensure metrics
  // are reported to the registered CronetURLRequest::Callback before resources
  // used by the callback are deleted.
  void MaybeReportMetricsAndRunCallback(base::OnceClosure callback);

 private:
  friend class TestUtil;

  // Private destructor invoked fron NetworkTasks::Destroy() on network thread.
  ~CronetURLRequest();

  // NetworkTasks performs tasks on the network thread and owns objects that
  // live on the network thread.
  class NetworkTasks : public net::URLRequest::Delegate {
   public:
    // Invoked off the network thread.
    NetworkTasks(std::unique_ptr<Callback> callback,
                 const GURL& url,
                 net::RequestPriority priority,
                 int load_flags,
                 bool traffic_stats_tag_set,
                 int32_t traffic_stats_tag,
                 bool traffic_stats_uid_set,
                 int32_t traffic_stats_uid,
                 net::Idempotency idempotency,
                 scoped_refptr<net::SharedDictionary> shared_dictionary,
                 net::handles::NetworkHandle network);

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

    // Invoked on the network thread.
    ~NetworkTasks() override;

    // Starts the request.
    void Start(CronetContext* context,
               const std::string& method,
               std::unique_ptr<net::HttpRequestHeaders> request_headers,
               std::unique_ptr<net::UploadDataStream> upload);

    // Gets status of the requrest and invokes |on_status_callback| to allow
    // multiple overlapping calls.
    void GetStatus(OnStatusCallback on_status_callback) const;

    // Follows redirect.
    void FollowDeferredRedirect();

    // Reads more data.
    void ReadData(scoped_refptr<net::IOBuffer> read_buffer, int buffer_size);

    // Releases all resources for the request and deletes the |request|, which
    // owns |this|, so |this| is also deleted.
    // |send_on_canceled| indicates whether OnCanceled callback should be
    // issued to indicate when no more callbacks will be issued.
    void Destroy(CronetURLRequest* request, bool send_on_canceled);

    // Runs MaybeReportMetrics(), then runs |callback|.
    void MaybeReportMetricsAndRunCallback(base::OnceClosure callback);

   private:
    friend class TestUtil;

    // net::URLRequest::Delegate implementations:
    void OnReceivedRedirect(net::URLRequest* request,
                            const net::RedirectInfo& redirect_info,
                            bool* defer_redirect) override;
    void OnCertificateRequested(
        net::URLRequest* request,
        net::SSLCertRequestInfo* cert_request_info) override;
    void OnSSLCertificateError(net::URLRequest* request,
                               int net_error,
                               const net::SSLInfo& ssl_info,
                               bool fatal) override;
    void OnResponseStarted(net::URLRequest* request, int net_error) override;
    void OnReadCompleted(net::URLRequest* request, int bytes_read) override;

    // Report error and cancel request_adapter.
    void ReportError(net::URLRequest* request, int net_error);
    // Reports metrics collected.
    void MaybeReportMetrics();

    // Callback implemented by the client.
    std::unique_ptr<CronetURLRequest::Callback> callback_;

    const GURL initial_url_;
    const net::RequestPriority initial_priority_;
    const int initial_load_flags_;
    // Count of bytes received during redirect is added to received byte count.
    int64_t received_byte_count_from_redirects_;

    // Whether error has been already reported, for example from
    // OnSSLCertificateError().
    bool error_reported_;

    // Whether metrics have been reported.
    bool metrics_reported_;

    // Whether |traffic_stats_tag_| should be applied.
    const bool traffic_stats_tag_set_;
    // TrafficStats tag to apply to URLRequest.
    const int32_t traffic_stats_tag_;
    // Whether |traffic_stats_uid_| should be applied.
    const bool traffic_stats_uid_set_;
    // UID to be applied to URLRequest.
    const int32_t traffic_stats_uid_;
    // Idempotency of the request.
    const net::Idempotency idempotency_;
    // Optional compression dictionary for this request. != nullptr if present.
    scoped_refptr<net::SharedDictionary> shared_dictionary_;

    net::handles::NetworkHandle network_;

    scoped_refptr<net::IOBuffer> read_buffer_;
    std::unique_ptr<net::URLRequest> url_request_;

    THREAD_CHECKER(network_thread_checker_);
  };

  raw_ptr<CronetContext> context_;
  // |network_tasks_| is invoked on network thread.
  NetworkTasks network_tasks_;

  // Request parameters set off network thread before Start().
  std::string initial_method_;
  std::unique_ptr<net::HttpRequestHeaders> initial_request_headers_;
  std::unique_ptr<net::UploadDataStream> upload_;
};

}  // namespace cronet

#endif  // COMPONENTS_CRONET_CRONET_URL_REQUEST_H_
