// Copyright 2022 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_DNS_HOST_RESOLVER_SYSTEM_TASK_H_
#define NET_DNS_HOST_RESOLVER_SYSTEM_TASK_H_

#include <optional>
#include <string>

#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/task/task_runner.h"
#include "net/base/address_list.h"
#include "net/base/net_export.h"
#include "net/base/network_handle.h"
#include "net/dns/host_resolver_proc.h"
#include "net/log/net_log_with_source.h"

namespace net {

using SystemDnsResultsCallback = base::OnceCallback<
    void(const AddressList& addr_list, int os_error, int net_error)>;

// Calls SystemHostResolverCall() (or in some tests, HostResolverProc::Resolve)
// in ThreadPool. So EnsureSystemHostResolverCallReady() must be called before
// using this class.
//
// Performs retries if specified by HostResolverSystemTask::Params.
//
// Whenever we try to resolve the host, we post a delayed task to check if host
// resolution (OnLookupComplete) is completed or not. If the original attempt
// hasn't completed, then we start another attempt for host resolution. We take
// the results from the first attempt that finishes and ignore the results from
// all other attempts.
//
// This class is designed to be used not just by HostResolverManager, but by
// general consumers.
//
// It should only be used on the main thread to ensure that hooks (see
// SetSystemHostResolverOverride()) only ever run on the main thread.
class NET_EXPORT HostResolverSystemTask {
 public:
  // Parameters for customizing HostResolverSystemTask behavior.
  //
  // |resolver_proc| is used to override resolution in tests; it must be
  // thread-safe since it may be run from multiple worker threads. If
  // |resolver_proc| is NULL then the default host resolver procedure is
  // to call SystemHostResolverCall().
  //
  // For each attempt, we could start another attempt if host is not resolved
  // within |unresponsive_delay| time. We keep attempting to resolve the host
  // for |max_retry_attempts|. For every retry attempt, we grow the
  // |unresponsive_delay| by the |retry_factor| amount (that is retry interval
  // is multiplied by the retry factor each time). Once we have retried
  // |max_retry_attempts|, we give up on additional attempts.
  struct NET_EXPORT_PRIVATE Params {
    // Default delay between calls to the system resolver for the same hostname.
    // (Can be overridden by field trial.)
    static constexpr base::TimeDelta kDnsDefaultUnresponsiveDelay =
        base::Seconds(6);

    // Set |max_system_retry_attempts| to this to select a default retry value.
    static constexpr size_t kDefaultRetryAttempts = -1;

    // Sets up defaults.
    Params(scoped_refptr<HostResolverProc> resolver_proc,
           size_t max_retry_attempts);

    Params(const Params& other);

    ~Params();

    // The procedure to use for resolving host names. This will be NULL, except
    // in the case of some-tests which inject custom host resolving behaviors.
    scoped_refptr<HostResolverProc> resolver_proc;

    // Maximum number retry attempts to resolve the hostname.
    // Pass HostResolver::Options::kDefaultRetryAttempts to choose a default
    // value.
    size_t max_retry_attempts;

    // This is the limit after which we make another attempt to resolve the host
    // if the worker thread has not responded yet.
    base::TimeDelta unresponsive_delay = kDnsDefaultUnresponsiveDelay;

    // Factor to grow |unresponsive_delay| when we re-re-try.
    uint32_t retry_factor = 2;
  };

  static std::unique_ptr<HostResolverSystemTask> Create(
      std::string hostname,
      AddressFamily address_family,
      HostResolverFlags flags,
      const Params& params = Params(nullptr, 0),
      const NetLogWithSource& job_net_log = NetLogWithSource(),
      handles::NetworkHandle network = handles::kInvalidNetworkHandle);

  // Same as above but resolves the result of GetHostName() (the machine's own
  // hostname).
  static std::unique_ptr<HostResolverSystemTask> CreateForOwnHostname(
      AddressFamily address_family,
      HostResolverFlags flags,
      const Params& params = Params(nullptr, 0),
      const NetLogWithSource& job_net_log = NetLogWithSource(),
      handles::NetworkHandle network = handles::kInvalidNetworkHandle);

  // If `hostname` is std::nullopt, resolves the result of GetHostName().
  // Prefer using the above 2 static functions for constructing a
  // HostResolverSystemTask.
  HostResolverSystemTask(
      std::optional<std::string> hostname,
      AddressFamily address_family,
      HostResolverFlags flags,
      const Params& params = Params(nullptr, 0),
      const NetLogWithSource& job_net_log = NetLogWithSource(),
      handles::NetworkHandle network = handles::kInvalidNetworkHandle);

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

  // Cancels this HostResolverSystemTask. Any outstanding resolve attempts
  // cannot be cancelled, but they will post back to the current thread before
  // checking their WeakPtrs to find that this task is cancelled.
  ~HostResolverSystemTask();

  // Starts the resolution task. This can only be called once per
  // HostResolverSystemTask. `results_cb` will not be invoked synchronously and
  // can own `this`.
  void Start(SystemDnsResultsCallback results_cb);

  bool was_completed() const {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
    return results_cb_.is_null();
  }

 private:
  void StartLookupAttempt();

  // Callback for when DoLookup() completes.
  void OnLookupComplete(const uint32_t attempt_number,
                        const AddressList& results,
                        const int os_error,
                        int error);

  // If `hostname_` is std::nullopt, this class should resolve the result of
  // net::GetHostName() (the machine's own hostname).
  const std::optional<std::string> hostname_;
  const AddressFamily address_family_;
  const HostResolverFlags flags_;

  // Holds an owning reference to the HostResolverProc that we are going to use.
  // This may not be the current resolver procedure by the time we call
  // ResolveAddrInfo, but that's OK... we'll use it anyways, and the owning
  // reference ensures that it remains valid until we are done.
  Params params_;

  // The listener to the results of this HostResolverSystemTask.
  SystemDnsResultsCallback results_cb_;

  // Keeps track of the number of attempts we have made so far to resolve the
  // host. Whenever we start an attempt to resolve the host, we increase this
  // number.
  uint32_t attempt_number_ = 0;

  NetLogWithSource net_log_;

  // Network to perform DNS lookups for.
  const handles::NetworkHandle network_;

  SEQUENCE_CHECKER(sequence_checker_);

  // Used to loop back from the blocking lookup attempt tasks as well as from
  // delayed retry tasks. Invalidate WeakPtrs on completion and cancellation to
  // cancel handling of such posted tasks.
  base::WeakPtrFactory<HostResolverSystemTask> weak_ptr_factory_{this};
};

// Ensures any necessary initialization occurs such that
// SystemHostResolverCall() can be called on other threads.
NET_EXPORT void EnsureSystemHostResolverCallReady();

// Resolves `host` to an address list, using the system's default host resolver.
// (i.e. this calls out to getaddrinfo()). If successful returns OK and fills
// `addrlist` with a list of socket addresses. Otherwise returns a
// network error code, and fills `os_error` with a more specific error if it
// was non-NULL.
// `network` is an optional parameter, when specified (!=
// handles::kInvalidNetworkHandle) the lookup will be performed specifically for
// `network`.
//
// This should NOT be called in a sandboxed process.
NET_EXPORT_PRIVATE int SystemHostResolverCall(
    const std::string& host,
    AddressFamily address_family,
    HostResolverFlags host_resolver_flags,
    AddressList* addrlist,
    int* os_error,
    handles::NetworkHandle network = handles::kInvalidNetworkHandle);

// Sets the task runner that system DNS resolution will run on, which is mostly
// useful for tests and fuzzers that need reproducibilty of failures.
NET_EXPORT_PRIVATE void SetSystemDnsResolutionTaskRunnerForTesting(
    scoped_refptr<base::TaskRunner> task_runner);

// The following will be used to override the behavior of
// HostResolverSystemTask. This override will be called instead of posting
// SystemHostResolverCall() to a worker thread. The override will only be
// invoked on the main thread.
// The override should never invoke `results_cb` synchronously.
NET_EXPORT void SetSystemDnsResolverOverride(
    base::RepeatingCallback<void(const std::optional<std::string>& host,
                                 AddressFamily address_family,
                                 HostResolverFlags host_resolver_flags,
                                 SystemDnsResultsCallback results_cb,
                                 handles::NetworkHandle network)> dns_override);

}  // namespace net

#endif  // NET_DNS_HOST_RESOLVER_SYSTEM_TASK_H_
