// Copyright 2024 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_MANAGER_JOB_H_
#define NET_DNS_HOST_RESOLVER_MANAGER_JOB_H_

#include <array>
#include <deque>
#include <memory>
#include <optional>
#include <variant>
#include <vector>

#include "base/containers/linked_list.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/safe_ref.h"
#include "base/memory/weak_ptr.h"
#include "base/time/time.h"
#include "net/base/address_family.h"
#include "net/base/network_anonymization_key.h"
#include "net/base/network_handle.h"
#include "net/base/prioritized_dispatcher.h"
#include "net/dns/dns_task_results_manager.h"
#include "net/dns/host_cache.h"
#include "net/dns/host_resolver.h"
#include "net/dns/host_resolver_dns_task.h"
#include "net/dns/host_resolver_manager.h"
#include "net/dns/public/dns_query_type.h"
#include "net/dns/public/secure_dns_mode.h"
#include "net/log/net_log_with_source.h"

namespace net {

class ResolveContext;
class HostResolverInternalResult;
class HostResolverMdnsTask;
class HostResolverNat64Task;

// Key used to identify a HostResolverManager::Job.
struct HostResolverManager::JobKey {
  JobKey(HostResolver::Host host,
         handles::NetworkHandle target_network,
         ResolveContext* resolve_context);
  ~JobKey();

  JobKey(const JobKey& other);
  JobKey& operator=(const JobKey& other);

  bool operator<(const JobKey& other) const;
  bool operator==(const JobKey& other) const;

  HostResolver::Host host;
  NetworkAnonymizationKey network_anonymization_key;
  DnsQueryTypeSet query_types;
  HostResolverFlags flags;
  HostResolverSource source;
  SecureDnsMode secure_dns_mode;
  base::WeakPtr<ResolveContext> resolve_context;

  HostCache::Key ToCacheKey(bool secure) const;
  // TODO(crbug.com/495684670): Remove this once the old way of doing
  // multi-networking has been removed. Currently, this is used as a compat
  // layer between the two.
  handles::NetworkHandle GetTargetNetwork() const;

 private:
  // TODO(crbug.com/495684670): Make this public once the old way of doing
  // multi-networking has been removed. Currently, this should always be
  // accessed via GetTargetNetwork(), which provides a compat layer between the
  // two.
  handles::NetworkHandle target_network = handles::kInvalidNetworkHandle;
};

// Aggregates all Requests for the same Key. Dispatched via
// PrioritizedDispatcher.
class HostResolverManager::Job : public PrioritizedDispatcher::Job,
                                 public HostResolverDnsTask::Delegate,
                                 public DnsTaskResultsManager::Delegate {
 public:
  // Describes the complete fallback path taken to successfully resolve a host.
  // Used to analyze the frequency and success rate of fallbacks between Secure
  // DNS, built-in Classic DNS (Do53), Platform DNS, and the System Resolver.
  //
  // These values are persisted to logs. Entries should not be renumbered and
  // numeric values should never be reused.
  //
  // LINT.IfChange(ResolveFallbackPath)
  enum class ResolveFallbackPath {
    // Successfully resolved via Secure DNS (DoH) without fallback.
    kSecureSuccess = 0,
    // Secure DNS failed, fallback to Classic DNS (built-in Do53) succeeded.
    kSecureFallbackToClassicSuccess = 1,
    // Secure DNS failed, fallback to Platform DNS succeeded.
    kSecureFallbackToPlatformSuccess = 2,
    // Secure DNS failed, fallback directly to System Resolver succeeded.
    kSecureFallbackToSystemSuccess = 3,
    // Secure DNS and Classic DNS both failed, fallback to System Resolver
    // succeeded.
    kSecureFallbackToClassicFallbackToSystemSuccess = 4,
    // Secure DNS and Platform DNS both failed, fallback to System Resolver
    // succeeded.
    kSecureFallbackToPlatformFallbackToSystemSuccess = 5,
    // Successfully resolved via Classic DNS (built-in Do53) without fallback.
    kClassicSuccess = 6,
    // Successfully resolved via Platform DNS without fallback.
    kPlatformSuccess = 7,
    // Classic DNS failed, fallback to System Resolver succeeded.
    kClassicFallbackToSystemSuccess = 8,
    // Platform DNS failed, fallback to System Resolver succeeded.
    kPlatformFallbackToSystemSuccess = 9,
    // Successfully resolved via System Resolver from the start (no built-in DNS
    // attempted).
    kSystemSuccess = 10,
    kMaxValue = kSystemSuccess,
  };
  // LINT.ThenChange(//tools/metrics/histograms/metadata/net/enums.xml:ResolveFallbackPath)

  // Helper method to calculate the ResolveFallbackPath based on the final
  // successful TaskType and the history of failed attempts. Returns
  // std::nullopt if the combination is not applicable for the metrics.
  //
  // WARNING: This method assumes the current task ordering configured by
  // HostResolverManager::ResolveLocally() and
  // HostResolverManager::CreateTaskSequence(). If you modify the task ordering
  // in those methods, update this method accordingly.
  static std::optional<ResolveFallbackPath> CalculateResolvePath(
      TaskType successful_task_type,
      bool secure_dns_failed,
      bool classic_dns_failed,
      bool platform_dns_failed);

  // Creates new job for |key| where |request_net_log| is bound to the
  // request that spawned it.
  Job(const base::WeakPtr<HostResolverManager>& resolver,
      JobKey key,
      ResolveHostParameters::CacheUsage cache_usage,
      HostCache* host_cache,
      std::deque<TaskType> tasks,
      RequestPriority priority,
      const NetLogWithSource& source_net_log,
      const base::TickClock* tick_clock,
      const HostResolver::HttpsSvcbOptions& https_svcb_options);
  ~Job() override;

  // Add this job to the dispatcher.  If "at_head" is true, adds at the front
  // of the queue.
  void Schedule(bool at_head);

  void AddRequest(RequestImpl* request);

  void ChangeRequestPriority(RequestImpl* req, RequestPriority priority);

  // Detach cancelled request. If it was the last active Request, also finishes
  // this Job.
  void CancelRequest(RequestImpl* request);

  void AddServiceEndpointRequest(ServiceEndpointRequestImpl* request);

  // Similar to CancelRequest(), if `request` was the last active one, finishes
  // this job.
  void CancelServiceEndpointRequest(ServiceEndpointRequestImpl* request);

  // Similar to ChangeRequestPriority(), but for a ServiceEndpointRequest.
  void ChangeServiceEndpointRequestPriority(ServiceEndpointRequestImpl* request,
                                            RequestPriority priority);

  // Called from AbortJobsWithoutTargetNetwork(). Completes all requests and
  // destroys the job. This currently assumes the abort is due to a network
  // change.
  // TODO This should not delete |this|.
  void Abort();

  // Gets a closure that will abort an insecure DnsTask (see
  // AbortInsecureDnsTask()) iff |this| is still valid. Useful if aborting a
  // list of Jobs as some may be cancelled while aborting others.
  base::OnceClosure GetAbortInsecureDnsTaskClosure(int error,
                                                   bool fallback_only);

  // Aborts or removes any current/future insecure DnsTasks if a
  // HostResolverSystemTask is available for fallback. If no fallback is
  // available and |fallback_only| is false, a job that is currently running an
  // insecure DnsTask will be completed with |error|.
  void AbortInsecureDnsTask(int error, bool fallback_only);

  // Called by HostResolverManager when this job is evicted due to queue
  // overflow. Completes all requests and destroys the job. The job could have
  // waiting requests that will receive completion callbacks, so cleanup
  // asynchronously to avoid reentrancy.
  void OnEvicted();

  // Attempts to serve the job from HOSTS. Returns true if succeeded and
  // this Job was destroyed.
  bool ServeFromHosts();

  void OnAddedToJobMap(JobMap::iterator iterator);

  void OnRemovedFromJobMap();

  void RunNextTask();

  const JobKey& key() const { return key_; }

  bool is_queued() const { return !handle_.is_null(); }

  bool is_running() const { return job_running_; }

  bool HasTargetNetwork() const {
    return key_.GetTargetNetwork() != handles::kInvalidNetworkHandle;
  }

  DnsTaskResultsManager* dns_task_results_manager() const {
    return dns_task_results_manager_.get();
  }

 private:
  // Explains why a Job didn't attempt HTTPS query.
  // These values are persisted to logs. Entries should not be renumbered and
  // numeric values should never be reused.
  //
  // LINT.IfChange(HttpsNotAttemptedReason)
  enum class HttpsNotAttemptedReason {
    // The reason is unknown. This is the last resort option and should be
    // avoided as much as possible.
    kUnknown = 0,
    // Querying HTTPS is disabled.
    kQueryingHttpsDisabled = 1,
    // Built-in DNS resolver is disabled.
    kBuiltInResolverDisabled = 2,
    // No DnsClient.
    kNoDnsClient = 3,
    // DnsTask wasn't scheduled because it hadn't been working for a while.
    kFallbackFromInsecureTransactionPreferred = 4,
    // Insecure DnsTask was not allowed to query HTTPS.
    kInsecureDnsTaskDisabled = 5,
    // DnsTask was executed but fell back to the system task.
    kFallbackToSystemTaskAfterDnsTask = 6,
    kMaxValue = kFallbackToSystemTaskAfterDnsTask,
  };
  // LINT.ThenChange(//tools/metrics/histograms/metadata/net/enums.xml:HttpsNotAttemptedReason)

  // Keeps track of the highest priority.
  class PriorityTracker {
   public:
    explicit PriorityTracker(RequestPriority initial_priority)
        : highest_priority_(initial_priority) {}

    RequestPriority highest_priority() const { return highest_priority_; }

    size_t total_count() const { return total_count_; }

    void Add(RequestPriority req_priority) {
      ++total_count_;
      ++counts_[req_priority];
      if (highest_priority_ < req_priority) {
        highest_priority_ = req_priority;
      }
    }

    void Remove(RequestPriority req_priority) {
      DCHECK_GT(total_count_, 0u);
      DCHECK_GT(counts_[req_priority], 0u);
      --total_count_;
      --counts_[req_priority];
      size_t i;
      for (i = highest_priority_; i > MINIMUM_PRIORITY && !counts_[i]; --i) {
      }
      highest_priority_ = static_cast<RequestPriority>(i);

      // In absence of requests, default to MINIMUM_PRIORITY.
      if (total_count_ == 0) {
        DCHECK_EQ(MINIMUM_PRIORITY, highest_priority_);
      }
    }

   private:
    RequestPriority highest_priority_;
    size_t total_count_ = 0;
    std::array<size_t, NUM_PRIORITIES> counts_ = {};
  };

  // This is private static, instead of being in an anonymous namespace, to have
  // access to HostResolverManager::TaskType. Otherwise,
  // HostResolverManager::TaskType would need to be made public.
  static TaskType AttemptModeToTaskType(
      DnsTransactionFactory::AttemptMode attempt_mode);

  base::DictValue NetLogJobCreationParams(const NetLogSource& source);

  void Finish();

  void KillDnsTask();

  // Reduce the number of job slots occupied and queued in the dispatcher by
  // one. If the next Job slot is queued in the dispatcher, cancels the queued
  // job. Otherwise, the next Job has been started by the PrioritizedDispatcher,
  // so signals it is complete.
  void ReduceByOneJobSlot();

  // Common helper methods for adding and canceling a request.
  void AddRequestCommon(RequestPriority request_priority,
                        const NetLogWithSource& request_net_log,
                        bool is_speculative);
  void CancelRequestCommon(RequestPriority request_priority,
                           const NetLogWithSource& request_net_log);

  void UpdatePriority();

  // PrioritizedDispatcher::Job:
  void Start() override;

  // TODO(szym): Since DnsTransaction does not consume threads, we can increase
  // the limits on |dispatcher_|. But in order to keep the number of
  // ThreadPool threads low, we will need to use an "inner"
  // PrioritizedDispatcher with tighter limits.
  void StartSystemTask();
  // Called by HostResolverSystemTask when it completes.
  void OnSystemTaskComplete(base::TimeTicks start_time,
                            const AddressList& addr_list,
                            int /*os_error*/,
                            int net_error);

  void InsecureCacheLookup();

  void StartDnsTask(DnsTransactionFactory::AttemptMode attempt_mode);
  void StartNextDnsTransaction();
  // Called if DnsTask fails. It is posted from StartDnsTask, so Job may be
  // deleted before this callback. In this case dns_task is deleted as well,
  // so we use it as indicator whether Job is still valid.
  void OnDnsTaskFailure(const base::WeakPtr<HostResolverDnsTask>& dns_task,
                        base::TimeDelta duration,
                        bool allow_fallback,
                        const HostCache::Entry& failure_results,
                        DnsTransactionFactory::AttemptMode attempt_mode);
  // HostResolverDnsTask::Delegate implementation:
  void OnDnsTaskComplete(
      base::TimeTicks start_time,
      bool allow_fallback,
      HostResolverDnsTask::Results results,
      DnsTransactionFactory::AttemptMode attempt_mode) override;
  void OnIntermediateTransactionsComplete(
      std::optional<HostResolverDnsTask::SingleTransactionResults>
          single_transaction_results) override;
  bool IsHappyEyeballsV3Enabled() const override;
  void AddTransactionTimeQueued(base::TimeDelta time_queued) override;

  // DnsTaskResultsManager::Delegate implementation:
  void OnServiceEndpointsUpdated() override;

  void StartMdnsTask();
  void OnMdnsTaskComplete(base::TimeTicks start_time);
  void OnMdnsImmediateFailure(int rv);

  void StartNat64Task();
  void OnNat64TaskComplete(base::TimeTicks start_time,
                           std::unique_ptr<HostResolverInternalResult> result);

  void RecordJobHistograms(const HostCache::Entry& results,
                           std::optional<TaskType> successful_task_type);

  void RecordJobHttpsHistograms();

  void MaybeCacheResult(const HostCache::Entry& results,
                        base::TimeDelta ttl,
                        bool secure);

  // Performs Job's last rites. Completes all Requests. Deletes this.
  //
  // If not |allow_cache|, result will not be stored in the host cache, even if
  // result would otherwise allow doing so. Update the key to reflect |secure|,
  // which indicates whether or not the result was obtained securely.
  void CompleteRequests(
      const HostCache::Entry& results,
      base::TimeDelta ttl,
      bool allow_cache,
      bool secure,
      std::optional<TaskType> successful_task_type,
      std::optional<base::TimeDelta> task_completion_delay = std::nullopt,
      std::optional<DohResolutionDetails> doh_details = std::nullopt);

  void CompleteRequestsWithoutCache(
      const HostCache::Entry& results,
      std::optional<HostCache::EntryStaleness> stale_info,
      TaskType successful_task_type,
      std::optional<base::TimeDelta> task_completion_delay = std::nullopt);

  // Convenience wrapper for CompleteRequests in case of failure.
  void CompleteRequestsWithError(int net_error,
                                 std::optional<TaskType> successful_task_type);

  // Notifies requests that the job was cancelled.
  void CancelRequests();

  RequestPriority priority() const override;

  // Number of non-canceled requests in |requests_|.
  size_t num_active_requests() const { return priority_tracker_.total_count(); }

  base::WeakPtr<HostResolverManager> resolver_;

  const JobKey key_;
  const ResolveHostParameters::CacheUsage cache_usage_;
  // TODO(crbug.com/41462480): Consider allowing requests within a single Job to
  // have different HostCaches.
  const raw_ptr<HostCache> host_cache_;

  struct CompletionResult {
    const HostCache::Entry entry;
    base::TimeDelta ttl;
    DnsTransactionFactory::AttemptMode attempt_mode;
  };

  // Results of failed tasks, used in last-ditch attempt to complete request or
  // to record fallback path metrics.
  std::vector<CompletionResult> completion_results_;

  // The sequence of tasks to run in this Job. Tasks may be aborted and removed
  // from the sequence, but otherwise the tasks will run in order until a
  // successful result is found.
  std::deque<TaskType> tasks_;

  // Whether the job is running.
  bool job_running_ = false;

  // Tracks the highest priority across |requests_|.
  PriorityTracker priority_tracker_;

  bool had_non_speculative_request_ = false;

  // Number of slots occupied by this Job in |dispatcher_|. Should be 0 when
  // the job is not registered with any dispatcher.
  int num_occupied_job_slots_ = 0;

  // True once this Job has been sent to `resolver_->dispatcher_`.
  bool dispatched_ = false;

  // Result of DnsTask.
  int dns_task_error_ = OK;

  // Set to true when this Job executed DnsTask.
  bool dns_task_executed_ = false;

  // Set to whether DnsTask disabled HTTPS query. Set only when DnsTask
  // succeeds.
  bool dns_task_https_disabled_ = false;

  raw_ptr<const base::TickClock> tick_clock_;
  base::TimeTicks start_time_;

  bool secure_dns_attempted_ = false;

  HostResolver::HttpsSvcbOptions https_svcb_options_;

  NetLogWithSource net_log_;

  // Resolves the host using the system DNS resolver, which can be overridden
  // for tests.
  std::unique_ptr<HostResolverSystemTask> system_task_;

  // Set to true when this Job falls back to the system DNS task after DNS task.
  bool fallback_to_system_task_after_dns_task_ = false;

  // Resolves the host using a DnsTransaction.
  std::unique_ptr<HostResolverDnsTask> dns_task_;

  // Resolves the host using MDnsClient.
  std::unique_ptr<HostResolverMdnsTask> mdns_task_;

  // Perform NAT64 address synthesis to a given IPv4 literal.
  std::unique_ptr<HostResolverNat64Task> nat64_task_;

  // All Requests waiting for the result of this Job. Some can be canceled.
  base::LinkedList<RequestImpl> requests_;

  // All ServiceEndpointRequests waiting for the result of this Job. Some can
  // be canceled.
  base::LinkedList<ServiceEndpointRequestImpl> service_endpoint_requests_;

  // Builds and updates intermediate service endpoints while executing
  // a DnsTransaction.
  std::unique_ptr<DnsTaskResultsManager> dns_task_results_manager_;

  // A handle used for |dispatcher_|.
  PrioritizedDispatcher::Handle handle_;

  // Iterator to |this| in the JobMap. |nullopt| if not owned by the JobMap.
  std::optional<JobMap::iterator> self_iterator_;

  base::TimeDelta total_transaction_time_queued_;

  base::WeakPtrFactory<Job> weak_ptr_factory_{this};
};

}  // namespace net

#endif  // NET_DNS_HOST_RESOLVER_MANAGER_JOB_H_
