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

#include "net/dns/host_resolver_manager_job.h"

#include <algorithm>
#include <deque>
#include <memory>
#include <optional>
#include <set>
#include <variant>
#include <vector>

#include "base/containers/linked_list.h"
#include "base/feature_list.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/safe_ref.h"
#include "base/memory/weak_ptr.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/strcat.h"
#include "base/task/sequenced_task_runner.h"
#include "base/time/time.h"
#include "net/base/address_family.h"
#include "net/base/features.h"
#include "net/base/network_anonymization_key.h"
#include "net/base/network_handle.h"
#include "net/base/prioritized_dispatcher.h"
#include "net/base/url_util.h"
#include "net/dns/dns_client.h"
#include "net/dns/dns_task_results_manager.h"
#include "net/dns/dns_transaction.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_internal_result.h"
#include "net/dns/host_resolver_manager.h"
#include "net/dns/host_resolver_manager_request_impl.h"
#include "net/dns/host_resolver_manager_service_endpoint_request_impl.h"
#include "net/dns/host_resolver_mdns_task.h"
#include "net/dns/host_resolver_nat64_task.h"
#include "net/dns/host_resolver_system_task.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"
#include "url/url_constants.h"

namespace net {

namespace {

// Default TTL for successful resolutions with HostResolverSystemTask.
const unsigned kCacheEntryTTLSeconds = 60;

// Default TTL for unsuccessful resolutions with HostResolverSystemTask.
const unsigned kNegativeCacheEntryTTLSeconds = 0;

// Minimum TTL for successful resolutions with HostResolverDnsTask.
const unsigned kMinimumTTLSeconds = kCacheEntryTTLSeconds;

// ICANN uses this localhost address to indicate a name collision.
//
// The policy in Chromium is to fail host resolving if it resolves to
// this special address.
//
// Not however that IP literals are exempt from this policy, so it is still
// possible to navigate to http://127.0.53.53/ directly.
//
// For more details: https://www.icann.org/news/announcement-2-2014-08-01-en
const uint8_t kIcanNameCollisionIp[] = {127, 0, 53, 53};

bool ContainsIcannNameCollisionIp(const std::vector<IPEndPoint>& endpoints) {
  for (const auto& endpoint : endpoints) {
    const IPAddress& addr = endpoint.address();
    if (addr.IsIPv4() && IPAddressStartsWith(addr, kIcanNameCollisionIp)) {
      return true;
    }
  }
  return false;
}

// Creates NetLog parameters for HOST_RESOLVER_MANAGER_JOB_ATTACH/DETACH events.
base::DictValue NetLogJobAttachParams(const NetLogSource& source,
                                      RequestPriority priority) {
  base::DictValue dict;
  source.AddToEventParameters(dict);
  dict.Set("priority", RequestPriorityToString(priority));
  return dict;
}

bool IsAttemptModeSecure(DnsTransactionFactory::AttemptMode attempt_mode) {
  switch (attempt_mode) {
    case DnsTransactionFactory::AttemptMode::kHttp:
      return true;
    case DnsTransactionFactory::AttemptMode::kClassic:
    case DnsTransactionFactory::AttemptMode::kPlatform:
      return false;
  }
}

}  // namespace

HostResolverManager::JobKey::JobKey(HostResolver::Host host,
                                    handles::NetworkHandle target_network,
                                    ResolveContext* resolve_context)
    : host(std::move(host)),
      resolve_context(resolve_context->GetWeakPtr()),
      target_network(target_network) {}

HostResolverManager::JobKey::~JobKey() = default;

HostResolverManager::JobKey::JobKey(const JobKey& other) = default;
HostResolverManager::JobKey& HostResolverManager::JobKey::operator=(
    const JobKey& other) = default;

bool HostResolverManager::JobKey::operator<(const JobKey& other) const {
  // Use `GetTargetNetwork()` instead of `target_network` to ensure a consistent
  // view of a JobKey's target network. This makes sure every piece of code has
  // a consistent view of a JobKey's target network. See the comments in
  // `GetTargetNetwork()` for more details.
  return std::forward_as_tuple(query_types.ToEnumBitmask(), flags, source,
                               secure_dns_mode, &*resolve_context, host,
                               network_anonymization_key, GetTargetNetwork()) <
         std::forward_as_tuple(
             other.query_types.ToEnumBitmask(), other.flags, other.source,
             other.secure_dns_mode, &*other.resolve_context, other.host,
             other.network_anonymization_key, other.GetTargetNetwork());
}

bool HostResolverManager::JobKey::operator==(const JobKey& other) const {
  return !(*this < other || other < *this);
}

HostCache::Key HostResolverManager::JobKey::ToCacheKey(bool secure) const {
  if (query_types.size() != 1) {
    // This function will produce identical cache keys for `JobKey` structs
    // that differ only in their (non-singleton) `query_types` fields. When we
    // enable new query types, this behavior could lead to subtle bugs. That
    // is why the following DCHECK restricts the allowable query types.
    DCHECK(Difference(query_types, {DnsQueryType::A, DnsQueryType::AAAA,
                                    DnsQueryType::HTTPS})
               .empty());
  }
  const DnsQueryType query_type_for_key = query_types.size() == 1
                                              ? *query_types.begin()
                                              : DnsQueryType::UNSPECIFIED;
  std::variant<url::SchemeHostPort, std::string> host_for_cache;
  if (host.HasScheme()) {
    host_for_cache = host.AsSchemeHostPort();
  } else {
    host_for_cache = std::string(host.GetHostnameWithoutBrackets());
  }
  HostCache::Key key(std::move(host_for_cache), query_type_for_key, flags,
                     source, network_anonymization_key, GetTargetNetwork());
  key.secure = secure;
  return key;
}

handles::NetworkHandle HostResolverManager::JobKey::GetTargetNetwork() const {
  // Multi-network support for Cronet and CCT was originally implemented by
  // creating multiple URLRequestContexts/ResolveContexts. Until this historical
  // artifact is removed, we need to maintain this compat layer between:
  // 1) The old way of doing multi-networking, piggybacking on ResolveContext
  // 2) The new way of doing multi-networking, using the target_network field
  //    in the JobKey.
  // TODO(crbug.com/495684670): Clean this up once multi-network Cronet and CCT
  // no longer depend on network-bound URLRequestContexts.

  // If there is a ResolveContext, and it has a target network, we are in the
  // "old way of doing multi-networking" scenario. In this case, there should
  // never be a non-default target_network set in the JobKey.
  if (resolve_context &&
      resolve_context->GetTargetNetwork() != handles::kInvalidNetworkHandle) {
    CHECK_EQ(target_network, handles::kInvalidNetworkHandle);
    return resolve_context->GetTargetNetwork();
  }

  // Otherwise, we are in the "new way of doing multi-networking" scenario. We
  // can just rely on the target_network field in the JobKey.
  return target_network;
}

// static
// 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.
std::optional<HostResolverManager::Job::ResolveFallbackPath>
HostResolverManager::Job::CalculateResolvePath(TaskType task_type,
                                               bool secure_dns_failed,
                                               bool classic_dns_failed,
                                               bool platform_dns_failed) {
  switch (task_type) {
    case TaskType::SECURE_DNS:
      return ResolveFallbackPath::kSecureSuccess;
    case TaskType::DNS:
      return secure_dns_failed
                 ? ResolveFallbackPath::kSecureFallbackToClassicSuccess
                 : ResolveFallbackPath::kClassicSuccess;
    case TaskType::DNS_PLATFORM:
      return secure_dns_failed
                 ? ResolveFallbackPath::kSecureFallbackToPlatformSuccess
                 : ResolveFallbackPath::kPlatformSuccess;
    case TaskType::SYSTEM:
      if (secure_dns_failed) {
        if (classic_dns_failed) {
          return ResolveFallbackPath::
              kSecureFallbackToClassicFallbackToSystemSuccess;
        }
        if (platform_dns_failed) {
          return ResolveFallbackPath::
              kSecureFallbackToPlatformFallbackToSystemSuccess;
        }
        return ResolveFallbackPath::kSecureFallbackToSystemSuccess;
      }
      if (classic_dns_failed) {
        return ResolveFallbackPath::kClassicFallbackToSystemSuccess;
      }
      if (platform_dns_failed) {
        return ResolveFallbackPath::kPlatformFallbackToSystemSuccess;
      }
      return ResolveFallbackPath::kSystemSuccess;
    default:
      return std::nullopt;
  }
}

HostResolverManager::Job::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)
    : resolver_(resolver),
      key_(std::move(key)),
      cache_usage_(cache_usage),
      host_cache_(host_cache),
      tasks_(tasks),
      priority_tracker_(priority),
      tick_clock_(tick_clock),
      https_svcb_options_(https_svcb_options),
      net_log_(
          NetLogWithSource::Make(source_net_log.net_log(),
                                 NetLogSourceType::HOST_RESOLVER_IMPL_JOB)) {
  source_net_log.AddEvent(NetLogEventType::HOST_RESOLVER_MANAGER_CREATE_JOB);

  net_log_.BeginEvent(NetLogEventType::HOST_RESOLVER_MANAGER_JOB, [&] {
    return NetLogJobCreationParams(source_net_log.source());
  });
}

HostResolverManager::Job::~Job() {
  bool was_queued = is_queued();
  bool was_running = is_running();
  // Clean up now for nice NetLog.
  Finish();
  if (was_running) {
    // This Job was destroyed while still in flight.
    net_log_.EndEventWithNetErrorCode(
        NetLogEventType::HOST_RESOLVER_MANAGER_JOB, ERR_ABORTED);
  } else if (was_queued) {
    // Job was cancelled before it could run.
    // TODO(szym): is there any benefit in having this distinction?
    net_log_.AddEvent(NetLogEventType::CANCELLED);
    net_log_.EndEvent(NetLogEventType::HOST_RESOLVER_MANAGER_JOB);
  }
  // else CompleteRequests logged EndEvent.
  while (!requests_.empty()) {
    // Log any remaining Requests as cancelled.
    RequestImpl* req = requests_.head()->value();
    req->RemoveFromList();
    CHECK(key_ == req->GetJobKey());
    req->OnJobCancelled(key_);
  }

  while (!service_endpoint_requests_.empty()) {
    ServiceEndpointRequestImpl* request =
        service_endpoint_requests_.head()->value();
    request->RemoveFromList();
    request->OnJobCancelled();
  }
}

HostResolverManager::TaskType HostResolverManager::Job::AttemptModeToTaskType(
    DnsTransactionFactory::AttemptMode attempt_mode) {
  switch (attempt_mode) {
    case DnsTransactionFactory::AttemptMode::kHttp:
      return HostResolverManager::TaskType::SECURE_DNS;
    case DnsTransactionFactory::AttemptMode::kClassic:
      return HostResolverManager::TaskType::DNS;
    case DnsTransactionFactory::AttemptMode::kPlatform:
      return HostResolverManager::TaskType::DNS_PLATFORM;
  }
}

void HostResolverManager::Job::Schedule(bool at_head) {
  DCHECK(!is_queued());
  PrioritizedDispatcher::Handle handle;
  DCHECK(dispatched_);
  if (!at_head) {
    handle = resolver_->dispatcher_->Add(this, priority());
  } else {
    handle = resolver_->dispatcher_->AddAtHead(this, priority());
  }
  // The dispatcher could have started |this| in the above call to Add, which
  // could have called Schedule again. In that case |handle| will be null,
  // but |handle_| may have been set by the other nested call to Schedule.
  if (!handle.is_null()) {
    DCHECK(handle_.is_null());
    handle_ = handle;
  }
}

void HostResolverManager::Job::AddRequest(RequestImpl* request) {
  // Job currently assumes a 1:1 correspondence between ResolveContext and
  // HostCache. Since the ResolveContext is part of the JobKey, any request
  // added to any existing Job should share the same HostCache.
  DCHECK_EQ(host_cache_, request->host_cache());
  // TODO(crbug.com/40181080): Check equality of whole host once Jobs are
  // separated by scheme/port.
  DCHECK_EQ(key_.host.GetHostnameWithoutBrackets(),
            request->request_host().GetHostnameWithoutBrackets());

  request->AssignJob(weak_ptr_factory_.GetSafeRef());

  AddRequestCommon(request->priority(), request->source_net_log(),
                   request->parameters().is_speculative);

  requests_.Append(request);

  UpdatePriority();
}

void HostResolverManager::Job::ChangeRequestPriority(RequestImpl* req,
                                                     RequestPriority priority) {
  DCHECK_EQ(key_.host, req->request_host());

  priority_tracker_.Remove(req->priority());
  req->set_priority(priority);
  priority_tracker_.Add(req->priority());
  UpdatePriority();
}

void HostResolverManager::Job::CancelRequest(RequestImpl* request) {
  DCHECK_EQ(key_.host, request->request_host());
  DCHECK(!requests_.empty());

  CancelRequestCommon(request->priority(), request->source_net_log());

  if (num_active_requests() > 0) {
    UpdatePriority();
    request->RemoveFromList();
  } else {
    // If we were called from a Request's callback within CompleteRequests,
    // that Request could not have been cancelled, so num_active_requests()
    // could not be 0. Therefore, we are not in CompleteRequests().
    CompleteRequestsWithError(ERR_DNS_REQUEST_CANCELLED,
                              /*task_type=*/std::nullopt);
  }
}

void HostResolverManager::Job::AddServiceEndpointRequest(
    ServiceEndpointRequestImpl* request) {
  CHECK_EQ(host_cache_, request->host_cache());

  request->AssignJob(weak_ptr_factory_.GetSafeRef());

  AddRequestCommon(request->priority(), request->net_log(),
                   request->parameters().is_speculative);

  service_endpoint_requests_.Append(request);

  UpdatePriority();
}

void HostResolverManager::Job::CancelServiceEndpointRequest(
    ServiceEndpointRequestImpl* request) {
  CHECK(!service_endpoint_requests_.empty());
  CancelRequestCommon(request->priority(), request->net_log());

  if (num_active_requests() > 0) {
    UpdatePriority();
    request->RemoveFromList();
  } else {
    // See comments in CancelRequest().
    CompleteRequestsWithError(ERR_DNS_REQUEST_CANCELLED,
                              /*task_type=*/std::nullopt);
  }
}

void HostResolverManager::Job::ChangeServiceEndpointRequestPriority(
    ServiceEndpointRequestImpl* request,
    RequestPriority priority) {
  priority_tracker_.Remove(request->priority());
  request->set_priority(priority);
  priority_tracker_.Add(request->priority());
  UpdatePriority();
}

void HostResolverManager::Job::Abort() {
  CompleteRequestsWithError(ERR_NETWORK_CHANGED, /*task_type=*/std::nullopt);
}

base::OnceClosure HostResolverManager::Job::GetAbortInsecureDnsTaskClosure(
    int error,
    bool fallback_only) {
  return base::BindOnce(&Job::AbortInsecureDnsTask,
                        weak_ptr_factory_.GetWeakPtr(), error, fallback_only);
}

void HostResolverManager::Job::AbortInsecureDnsTask(int error,
                                                    bool fallback_only) {
  bool has_system_fallback = std::ranges::contains(tasks_, TaskType::SYSTEM);
  if (has_system_fallback) {
    for (auto it = tasks_.begin(); it != tasks_.end();) {
      switch (*it) {
        case TaskType::DNS:
        case TaskType::DNS_PLATFORM:
          it = tasks_.erase(it);
          break;
        case TaskType::CACHE_LOOKUP:
        case TaskType::CONFIG_PRESET:
        case TaskType::HOSTS:
        case TaskType::INSECURE_CACHE_LOOKUP:
        case TaskType::MDNS:
        case TaskType::NAT64:
        case TaskType::SECURE_CACHE_LOOKUP:
        case TaskType::SECURE_DNS:
        case TaskType::SYSTEM:
          ++it;
          break;
      }
    }
  }

  if (dns_task_ && !dns_task_->secure()) {
    if (has_system_fallback) {
      KillDnsTask();
      dns_task_error_ = OK;
      RunNextTask();
    } else if (!fallback_only) {
      CompleteRequestsWithError(error, /*task_type=*/std::nullopt);
    }
  }
}

void HostResolverManager::Job::OnEvicted() {
  DCHECK(!is_running());
  DCHECK(is_queued());
  handle_.Reset();

  net_log_.AddEvent(NetLogEventType::HOST_RESOLVER_MANAGER_JOB_EVICTED);

  // This signals to CompleteRequests that parts of this job never ran.
  // Job must be saved in |resolver_| to be completed asynchronously.
  // Otherwise the job will be destroyed with requests silently cancelled
  // before completion runs.
  DCHECK(self_iterator_);
  base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(&Job::CompleteRequestsWithError,
                                weak_ptr_factory_.GetWeakPtr(),
                                ERR_HOST_RESOLVER_QUEUE_TOO_LARGE,
                                /*task_type=*/std::nullopt));
}

bool HostResolverManager::Job::ServeFromHosts() {
  DCHECK_GT(num_active_requests(), 0u);
  std::set<std::unique_ptr<HostResolverInternalResult>> results =
      resolver_->ServeFromHosts(
          key_.host.GetHostnameWithoutBrackets(), key_.query_types,
          key_.flags & HOST_RESOLVER_DEFAULT_FAMILY_SET_DUE_TO_NO_IPV6, tasks_);
  if (!results.empty() && std::ranges::any_of(results, [](const auto& result) {
        return result->type() == HostResolverInternalResult::Type::kData;
      })) {
    HostCache::Entry legacy_results(results, base::Time::Now(),
                                    tick_clock_->NowTicks(),
                                    HostCache::Entry::SOURCE_HOSTS);
    // This will destroy the Job.
    CompleteRequests(legacy_results, base::TimeDelta(), true /* allow_cache */,
                     true /* secure */, TaskType::HOSTS);
    return true;
  }
  return false;
}

void HostResolverManager::Job::OnAddedToJobMap(JobMap::iterator iterator) {
  CHECK(!self_iterator_);
  CHECK(iterator != resolver_->jobs_.end());
  self_iterator_ = iterator;
}

void HostResolverManager::Job::OnRemovedFromJobMap() {
  CHECK(self_iterator_);
  self_iterator_ = std::nullopt;
}

void HostResolverManager::Job::RunNextTask() {
  // If there are no tasks left to try, cache any stored results and complete
  // the request with the last stored result. All stored results should be
  // errors.
  if (tasks_.empty()) {
    // If there are no stored results, complete with an error.
    if (completion_results_.size() == 0) {
      CompleteRequestsWithError(ERR_NAME_NOT_RESOLVED,
                                /*task_type=*/std::nullopt);
      return;
    }

    // Cache all but the last result here. The last result will be cached
    // as part of CompleteRequests.
    for (size_t i = 0; i < completion_results_.size() - 1; ++i) {
      const auto& result = completion_results_[i];
      DCHECK_NE(OK, result.entry.error());
      MaybeCacheResult(result.entry, result.ttl,
                       IsAttemptModeSecure(result.attempt_mode));
    }
    const auto& last_result = completion_results_.back();
    DCHECK_NE(OK, last_result.entry.error());
    CompleteRequests(last_result.entry, last_result.ttl, true /* allow_cache */,
                     IsAttemptModeSecure(last_result.attempt_mode),
                     AttemptModeToTaskType(last_result.attempt_mode));
    return;
  }

  TaskType next_task = tasks_.front();

  bool do_dispatch = false;
  switch (next_task) {
    // Schedule insecure DnsTasks and HostResolverSystemTasks with the
    // dispatcher.
    case TaskType::DNS:
    case TaskType::DNS_PLATFORM:
    case TaskType::SYSTEM:
    case TaskType::MDNS:
      do_dispatch = true;
      break;
    case TaskType::CACHE_LOOKUP:
    case TaskType::CONFIG_PRESET:
    case TaskType::HOSTS:
    case TaskType::INSECURE_CACHE_LOOKUP:
    case TaskType::NAT64:
    case TaskType::SECURE_CACHE_LOOKUP:
    case TaskType::SECURE_DNS:
      break;
  }
  if (!dispatched_ && do_dispatch) {
    dispatched_ = true;
    job_running_ = false;
    Schedule(false);
    DCHECK(is_running() || is_queued());

    // Check for queue overflow.
    PrioritizedDispatcher& dispatcher = *resolver_->dispatcher_;
    if (dispatcher.num_queued_jobs() > resolver_->max_queued_jobs_) {
      Job* evicted = static_cast<Job*>(dispatcher.EvictOldestLowest());
      DCHECK(evicted);
      evicted->OnEvicted();
    }
    return;
  }

  if (start_time_ == base::TimeTicks()) {
    net_log_.AddEvent(NetLogEventType::HOST_RESOLVER_MANAGER_JOB_STARTED);
    start_time_ = tick_clock_->NowTicks();
  }
  tasks_.pop_front();
  job_running_ = true;

  switch (next_task) {
    case TaskType::SYSTEM:
      StartSystemTask();
      break;
    case TaskType::DNS:
      StartDnsTask(DnsTransactionFactory::AttemptMode::kClassic);
      break;
    case TaskType::DNS_PLATFORM:
      StartDnsTask(DnsTransactionFactory::AttemptMode::kPlatform);
      break;
    case TaskType::SECURE_DNS:
      StartDnsTask(DnsTransactionFactory::AttemptMode::kHttp);
      break;
    case TaskType::MDNS:
      StartMdnsTask();
      break;
    case TaskType::INSECURE_CACHE_LOOKUP:
      InsecureCacheLookup();
      break;
    case TaskType::NAT64:
      StartNat64Task();
      break;
    case TaskType::SECURE_CACHE_LOOKUP:
    case TaskType::CACHE_LOOKUP:
    case TaskType::CONFIG_PRESET:
    case TaskType::HOSTS:
      // These task types should have been handled synchronously in
      // ResolveLocally() prior to Job creation.
      NOTREACHED();
  }
}

base::DictValue HostResolverManager::Job::NetLogJobCreationParams(
    const NetLogSource& source) {
  base::DictValue dict;
  source.AddToEventParameters(dict);
  dict.Set("host", key_.host.ToString());
  base::ListValue query_types_list;
  for (DnsQueryType query_type : key_.query_types) {
    query_types_list.Append(kDnsQueryTypes.at(query_type));
  }
  dict.Set("dns_query_types", std::move(query_types_list));
  base::ListValue tasks_list;
  for (TaskType task : tasks_) {
    tasks_list.Append(static_cast<int>(task));
  }
  dict.Set("tasks", std::move(tasks_list));
  dict.Set("secure_dns_mode", base::strict_cast<int>(key_.secure_dns_mode));
  dict.Set("network_anonymization_key",
           key_.network_anonymization_key.ToDebugString());
  return dict;
}

void HostResolverManager::Job::Finish() {
  if (is_running()) {
    // Clean up but don't run any callbacks.
    system_task_ = nullptr;
    KillDnsTask();
    mdns_task_ = nullptr;
    job_running_ = false;

    if (dispatched_) {
      // Job should only ever occupy one slot after any tasks that may have
      // required additional slots, e.g. DnsTask, have been killed, and
      // additional slots are expected to be vacated as part of killing the
      // task.
      DCHECK_EQ(1, num_occupied_job_slots_);
      if (resolver_) {
        resolver_->dispatcher_->OnJobFinished();
      }
      num_occupied_job_slots_ = 0;
    }
  } else if (is_queued()) {
    DCHECK(dispatched_);
    if (resolver_) {
      resolver_->dispatcher_->Cancel(handle_);
    }
    handle_.Reset();
  }
}

void HostResolverManager::Job::KillDnsTask() {
  if (dns_task_) {
    if (dispatched_) {
      while (num_occupied_job_slots_ > 1 || is_queued()) {
        ReduceByOneJobSlot();
      }
    }
    dns_task_.reset();
  }
  dns_task_results_manager_.reset();
}

void HostResolverManager::Job::ReduceByOneJobSlot() {
  DCHECK_GE(num_occupied_job_slots_, 1);
  DCHECK(dispatched_);
  if (is_queued()) {
    if (resolver_) {
      resolver_->dispatcher_->Cancel(handle_);
    }
    handle_.Reset();
  } else if (num_occupied_job_slots_ > 1) {
    if (resolver_) {
      resolver_->dispatcher_->OnJobFinished();
    }
    --num_occupied_job_slots_;
  } else {
    NOTREACHED();
  }
}

void HostResolverManager::Job::AddRequestCommon(
    RequestPriority request_priority,
    const NetLogWithSource& request_net_log,
    bool is_speculative) {
  priority_tracker_.Add(request_priority);
  request_net_log.AddEventReferencingSource(
      NetLogEventType::HOST_RESOLVER_MANAGER_JOB_ATTACH, net_log_.source());
  net_log_.AddEvent(
      NetLogEventType::HOST_RESOLVER_MANAGER_JOB_REQUEST_ATTACH, [&] {
        return NetLogJobAttachParams(request_net_log.source(), priority());
      });
  if (!is_speculative) {
    had_non_speculative_request_ = true;
  }
}

void HostResolverManager::Job::CancelRequestCommon(
    RequestPriority request_priority,
    const NetLogWithSource& request_net_log) {
  priority_tracker_.Remove(request_priority);
  net_log_.AddEvent(
      NetLogEventType::HOST_RESOLVER_MANAGER_JOB_REQUEST_DETACH, [&] {
        return NetLogJobAttachParams(request_net_log.source(), priority());
      });
}

void HostResolverManager::Job::UpdatePriority() {
  if (is_queued()) {
    handle_ = resolver_->dispatcher_->ChangePriority(handle_, priority());
  }
}

void HostResolverManager::Job::Start() {
  handle_.Reset();
  ++num_occupied_job_slots_;

  if (num_occupied_job_slots_ >= 2) {
    if (!dns_task_) {
      resolver_->dispatcher_->OnJobFinished();
      return;
    }
    StartNextDnsTransaction();
    DCHECK_EQ(num_occupied_job_slots_,
              dns_task_->num_transactions_in_progress());
    if (dns_task_->num_additional_transactions_needed() >= 1) {
      Schedule(true);
    }
    return;
  }

  DCHECK(!is_running());
  DCHECK(!tasks_.empty());
  RunNextTask();
  // Caution: Job::Start must not complete synchronously.
}

void HostResolverManager::Job::StartSystemTask() {
  DCHECK(dispatched_);
  DCHECK_EQ(1, num_occupied_job_slots_);
  DCHECK(HasAddressType(key_.query_types));

  std::optional<HostResolverSystemTask::CacheParams> cache_params;
  if (key_.resolve_context->host_resolver_cache()) {
    cache_params.emplace(*key_.resolve_context->host_resolver_cache(),
                         key_.network_anonymization_key,
                         key_.GetTargetNetwork());
  }

  system_task_ = HostResolverSystemTask::Create(
      std::string(key_.host.GetHostnameWithoutBrackets()),
      HostResolver::DnsQueryTypeSetToAddressFamily(key_.query_types),
      key_.flags, priority(), resolver_->host_resolver_system_params_, net_log_,
      key_.GetTargetNetwork(), std::move(cache_params));

  // Start() could be called from within Resolve(), hence it must NOT directly
  // call OnSystemTaskComplete, for example, on synchronous failure.
  system_task_->Start(base::BindOnce(&Job::OnSystemTaskComplete,
                                     base::Unretained(this),
                                     tick_clock_->NowTicks()));
}

void HostResolverManager::Job::OnSystemTaskComplete(
    base::TimeTicks start_time,
    const AddressList& addr_list,
    int /*os_error*/,
    int net_error) {
  DCHECK(system_task_);

  base::TimeDelta duration = tick_clock_->NowTicks() - start_time;
  if (net_error == OK) {
    UMA_HISTOGRAM_LONG_TIMES_100("Net.DNS.SystemTask.SuccessTime", duration);
  } else {
    UMA_HISTOGRAM_LONG_TIMES_100("Net.DNS.SystemTask.FailureTime", duration);
  }

  if (dns_task_error_ != OK && net_error == OK) {
    // This HostResolverSystemTask was a fallback resolution after a failed
    // insecure DnsTask.
    resolver_->OnFallbackResolve(dns_task_error_);
    fallback_to_system_task_after_dns_task_ = true;
  }

  if (ContainsIcannNameCollisionIp(addr_list.endpoints())) {
    net_error = ERR_ICANN_NAME_COLLISION;
  }

  base::TimeDelta ttl = base::Seconds(kNegativeCacheEntryTTLSeconds);
  if (net_error == OK) {
    ttl = base::Seconds(kCacheEntryTTLSeconds);
  }

  auto aliases = std::set<std::string>(addr_list.dns_aliases().begin(),
                                       addr_list.dns_aliases().end());

  // Source unknown because the system resolver could have gotten it from a
  // hosts file, its own cache, a DNS lookup or somewhere else.
  // Don't store the |ttl| in cache since it's not obtained from the server.
  CompleteRequests(
      HostCache::Entry(
          net_error,
          net_error == OK ? addr_list.endpoints() : std::vector<IPEndPoint>(),
          std::move(aliases), HostCache::Entry::SOURCE_UNKNOWN),
      ttl, /*allow_cache=*/true, /*secure=*/false, TaskType::SYSTEM, duration);
}

void HostResolverManager::Job::InsecureCacheLookup() {
  // Insecure cache lookups for requests allowing stale results should have
  // occurred prior to Job creation.
  DCHECK(cache_usage_ != ResolveHostParameters::CacheUsage::STALE_ALLOWED);
  std::optional<HostCache::EntryStaleness> stale_info;
  std::optional<HostCache::Entry> resolved = resolver_->MaybeServeFromCache(
      host_cache_, key_.ToCacheKey(/*secure=*/false), cache_usage_,
      false /* ignore_secure */, net_log_, &stale_info);

  if (resolved) {
    DCHECK(stale_info);
    DCHECK(!stale_info.value().is_stale());
    CompleteRequestsWithoutCache(resolved.value(), std::move(stale_info),
                                 TaskType::INSECURE_CACHE_LOOKUP);
  } else {
    RunNextTask();
  }
}

void HostResolverManager::Job::StartDnsTask(
    DnsTransactionFactory::AttemptMode attempt_mode) {
  const bool secure = IsAttemptModeSecure(attempt_mode);
  DCHECK_EQ(secure, !dispatched_);
  DCHECK_EQ(dispatched_ ? 1 : 0, num_occupied_job_slots_);
  DCHECK(!resolver_->ShouldForceSystemResolverDueToTestOverride());

  // Need to create the task even if we're going to post a failure instead of
  // running it, as a "started" job needs a task to be properly cleaned up.
  dns_task_ = std::make_unique<HostResolverDnsTask>(
      resolver_->dns_client_.get(), key_.host, key_.network_anonymization_key,
      key_.query_types, &*key_.resolve_context, attempt_mode,
      key_.secure_dns_mode, key_.GetTargetNetwork(), this, net_log_,
      tick_clock_, !tasks_.empty() /* fallback_available */,
      https_svcb_options_);
  dns_task_executed_ = true;
  if (secure) {
    secure_dns_attempted_ = true;
  }
  if (base::FeatureList::IsEnabled(features::kEnableIntermediateDnsResults) ||
      base::FeatureList::IsEnabled(features::kAsyncDnsQuicJob) ||
      resolver_->IsHappyEyeballsV3Enabled()) {
    dns_task_results_manager_ = std::make_unique<DnsTaskResultsManager>(
        this, key_.host, key_.query_types, net_log_);
  }
  dns_task_->StartNextTransaction();
  // Schedule a second transaction, if needed. DoH queries can bypass the
  // dispatcher and start all of their transactions immediately.
  if (secure) {
    while (dns_task_->num_additional_transactions_needed() >= 1) {
      dns_task_->StartNextTransaction();
    }
    DCHECK_EQ(dns_task_->num_additional_transactions_needed(), 0);
  } else if (dns_task_->num_additional_transactions_needed() >= 1) {
    Schedule(true);
  }
}

void HostResolverManager::Job::StartNextDnsTransaction() {
  DCHECK(dns_task_);
  DCHECK_EQ(dns_task_->secure(), !dispatched_);
  DCHECK(!dispatched_ || num_occupied_job_slots_ ==
                             dns_task_->num_transactions_in_progress() + 1);
  DCHECK_GE(dns_task_->num_additional_transactions_needed(), 1);
  dns_task_->StartNextTransaction();
}

void HostResolverManager::Job::OnDnsTaskFailure(
    const base::WeakPtr<HostResolverDnsTask>& dns_task,
    base::TimeDelta duration,
    bool allow_fallback,
    const HostCache::Entry& failure_results,
    DnsTransactionFactory::AttemptMode attempt_mode) {
  DCHECK_NE(OK, failure_results.error());

  base::UmaHistogramLongTimes100(
      base::StrCat({"Net.DNS.DnsTask.",
                    IsAttemptModeSecure(attempt_mode) ? "Secure" : "Insecure",
                    ".FailureTime"}),
      duration);

  if (!dns_task) {
    return;
  }

  base::UmaHistogramLongTimes100("Net.DNS.JobQueueTime.Failure",
                                 total_transaction_time_queued_);

  // If one of the fallback tasks doesn't complete the request, store a result
  // to use during request completion.
  base::TimeDelta ttl =
      failure_results.has_ttl() ? failure_results.ttl() : base::Seconds(0);
  completion_results_.emplace_back(failure_results, ttl, attempt_mode);

  dns_task_error_ = failure_results.error();
  KillDnsTask();

  if (!allow_fallback) {
    tasks_.clear();
  }

  RunNextTask();
}

void HostResolverManager::Job::OnDnsTaskComplete(
    base::TimeTicks start_time,
    bool allow_fallback,
    HostResolverDnsTask::Results results,
    DnsTransactionFactory::AttemptMode attempt_mode) {
  DCHECK(dns_task_);

  HostCache::Entry legacy_results(results, base::Time::Now(),
                                  tick_clock_->NowTicks(),
                                  HostCache::Entry::SOURCE_DNS);

  // Tasks containing address queries are only considered successful overall
  // if they find address results. However, DnsTask may claim success if any
  // transaction, e.g. a supplemental HTTPS transaction, finds results.
  DCHECK(!key_.query_types.Has(DnsQueryType::UNSPECIFIED));
  if (HasAddressType(key_.query_types) && legacy_results.error() == OK &&
      legacy_results.ip_endpoints().empty()) {
    legacy_results.set_error(ERR_NAME_NOT_RESOLVED);
  }

  base::TimeDelta duration = tick_clock_->NowTicks() - start_time;
  if (legacy_results.error() != OK) {
    OnDnsTaskFailure(dns_task_->AsWeakPtr(), duration, allow_fallback,
                     legacy_results, attempt_mode);
    return;
  }

  dns_task_https_disabled_ = dns_task_->https_disabled();

  const bool secure = IsAttemptModeSecure(attempt_mode);
  base::UmaHistogramLongTimes100(
      base::StrCat(
          {"Net.DNS.DnsTask.", secure ? "Secure" : "Insecure", ".SuccessTime"}),
      duration);

  base::UmaHistogramLongTimes100("Net.DNS.JobQueueTime.Success",
                                 total_transaction_time_queued_);

  // Reset the insecure DNS failure counter if an insecure DnsTask completed
  // successfully.
  if (!secure) {
    resolver_->dns_client_->ClearInsecureFallbackFailures();
  }

  base::TimeDelta bounded_ttl =
      std::max(legacy_results.ttl(), base::Seconds(kMinimumTTLSeconds));

  if (ContainsIcannNameCollisionIp(legacy_results.ip_endpoints())) {
    CompleteRequestsWithError(ERR_ICANN_NAME_COLLISION,
                              AttemptModeToTaskType(attempt_mode));
    return;
  }

  CompleteRequests(legacy_results, bounded_ttl, true /* allow_cache */, secure,
                   AttemptModeToTaskType(attempt_mode), duration,
                   dns_task_->GetDohResolutionDetails());
}

void HostResolverManager::Job::OnIntermediateTransactionsComplete(
    std::optional<HostResolverDnsTask::SingleTransactionResults>
        single_transaction_results) {
  if (dispatched_) {
    DCHECK_GE(num_occupied_job_slots_,
              dns_task_->num_transactions_in_progress());
    int unused_slots =
        num_occupied_job_slots_ - dns_task_->num_transactions_in_progress();

    // Reuse vacated slots for any remaining transactions.
    while (unused_slots > 0 &&
           dns_task_->num_additional_transactions_needed() > 0) {
      dns_task_->StartNextTransaction();
      --unused_slots;
    }

    // If all remaining transactions found a slot, no more needed from the
    // dispatcher.
    if (is_queued() && dns_task_->num_additional_transactions_needed() == 0) {
      resolver_->dispatcher_->Cancel(handle_);
      handle_.Reset();
    }

    // Relinquish any remaining extra slots.
    while (unused_slots > 0) {
      ReduceByOneJobSlot();
      --unused_slots;
    }
  } else if (dns_task_->num_additional_transactions_needed() >= 1) {
    dns_task_->StartNextTransaction();
  }

  if (dns_task_results_manager_ && single_transaction_results.has_value()) {
    dns_task_results_manager_->ProcessDnsTransactionResults(
        single_transaction_results->query_type,
        std::move(single_transaction_results->results));
    // `this` may be deleted. Do not add code below.
  }
}

bool HostResolverManager::Job::IsHappyEyeballsV3Enabled() const {
  return resolver_->IsHappyEyeballsV3Enabled();
}

void HostResolverManager::Job::AddTransactionTimeQueued(
    base::TimeDelta time_queued) {
  total_transaction_time_queued_ += time_queued;
}

void HostResolverManager::Job::OnServiceEndpointsUpdated() {
  // Requests could be destroyed while executing callbacks. Post tasks
  // instead of calling callbacks synchronously to prevent requests from being
  // destroyed in the following for loop.
  for (auto* request = service_endpoint_requests_.head();
       request != service_endpoint_requests_.end(); request = request->next()) {
    base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE,
        base::BindOnce(&ServiceEndpointRequestImpl::OnServiceEndpointsChanged,
                       request->value()->GetWeakPtr()));
  }
}

void HostResolverManager::Job::StartMdnsTask() {
  // No flags are supported for MDNS except
  // HOST_RESOLVER_DEFAULT_FAMILY_SET_DUE_TO_NO_IPV6 (which is not actually an
  // input flag).
  DCHECK_EQ(0, key_.flags & ~HOST_RESOLVER_DEFAULT_FAMILY_SET_DUE_TO_NO_IPV6);

  MDnsClient* client = nullptr;
  int rv = resolver_->GetOrCreateMdnsClient(&client);
  mdns_task_ = std::make_unique<HostResolverMdnsTask>(
      client, std::string(key_.host.GetHostnameWithoutBrackets()),
      key_.query_types, priority());

  if (rv == OK) {
    mdns_task_->Start(base::BindOnce(&Job::OnMdnsTaskComplete,
                                     base::Unretained(this),
                                     tick_clock_->NowTicks()));
  } else {
    // Could not create an mDNS client. Since we cannot complete synchronously
    // from here, post a failure without starting the task.
    base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, base::BindOnce(&Job::OnMdnsImmediateFailure,
                                  weak_ptr_factory_.GetWeakPtr(), rv));
  }
}

void HostResolverManager::Job::OnMdnsTaskComplete(base::TimeTicks start_time) {
  DCHECK(mdns_task_);
  // TODO(crbug.com/40577881): Consider adding MDNS-specific logging.

  std::set<std::unique_ptr<HostResolverInternalResult>> results =
      mdns_task_->GetResults();
  HostCache::Entry legacy_results(results, base::Time::Now(),
                                  tick_clock_->NowTicks(),
                                  HostCache::Entry::SOURCE_UNKNOWN);

  if (ContainsIcannNameCollisionIp(legacy_results.ip_endpoints())) {
    CompleteRequestsWithError(ERR_ICANN_NAME_COLLISION, TaskType::MDNS);
    return;
  }

  // MDNS uses a separate cache, so skip saving result to cache.
  // TODO(crbug.com/40611558): Consider merging caches.
  CompleteRequestsWithoutCache(legacy_results, /*stale_info=*/std::nullopt,
                               TaskType::MDNS,
                               tick_clock_->NowTicks() - start_time);
}

void HostResolverManager::Job::OnMdnsImmediateFailure(int rv) {
  DCHECK(mdns_task_);
  DCHECK_NE(OK, rv);

  CompleteRequestsWithError(rv, TaskType::MDNS);
}

void HostResolverManager::Job::StartNat64Task() {
  DCHECK(!nat64_task_);
  nat64_task_ = std::make_unique<HostResolverNat64Task>(
      key_.host.GetHostnameWithoutBrackets(), key_.network_anonymization_key,
      key_.GetTargetNetwork(), net_log_, &*key_.resolve_context, resolver_,
      priority());
  nat64_task_->Start(base::BindOnce(&Job::OnNat64TaskComplete,
                                    weak_ptr_factory_.GetWeakPtr(),
                                    tick_clock_->NowTicks()));
}

void HostResolverManager::Job::OnNat64TaskComplete(
    base::TimeTicks start_time,
    std::unique_ptr<HostResolverInternalResult> result) {
  CHECK(nat64_task_);
  CHECK(result);

  std::set<std::unique_ptr<HostResolverInternalResult>> results;
  results.insert(std::move(result));
  HostCache::Entry legacy_results(results, base::Time::Now(),
                                  tick_clock_->NowTicks(),
                                  HostCache::Entry::SOURCE_UNKNOWN);

  CompleteRequestsWithoutCache(legacy_results, /*stale_info=*/std::nullopt,
                               TaskType::NAT64,
                               tick_clock_->NowTicks() - start_time);
}

void HostResolverManager::Job::RecordJobHistograms(
    const HostCache::Entry& results,
    std::optional<TaskType> task_type) {
  int error = results.error();
  // Used in UMA_HISTOGRAM_ENUMERATION. Do not renumber entries or reuse
  // deprecated values.
  enum Category {
    RESOLVE_SUCCESS = 0,
    RESOLVE_FAIL = 1,
    RESOLVE_SPECULATIVE_SUCCESS = 2,
    RESOLVE_SPECULATIVE_FAIL = 3,
    RESOLVE_ABORT = 4,
    RESOLVE_SPECULATIVE_ABORT = 5,
    RESOLVE_MAX,  // Bounding value.
  };
  Category category = RESOLVE_MAX;  // Illegal value for later DCHECK only.

  base::TimeDelta duration = tick_clock_->NowTicks() - start_time_;
  if (error == OK) {
    if (had_non_speculative_request_) {
      category = RESOLVE_SUCCESS;
      UMA_HISTOGRAM_LONG_TIMES_100("Net.DNS.ResolveSuccessTime", duration);
    } else {
      category = RESOLVE_SPECULATIVE_SUCCESS;
    }
  } else if (error == ERR_NETWORK_CHANGED ||
             error == ERR_HOST_RESOLVER_QUEUE_TOO_LARGE) {
    category = had_non_speculative_request_ ? RESOLVE_ABORT
                                            : RESOLVE_SPECULATIVE_ABORT;
  } else {
    if (had_non_speculative_request_) {
      category = RESOLVE_FAIL;
      UMA_HISTOGRAM_LONG_TIMES_100("Net.DNS.ResolveFailureTime", duration);
    } else {
      category = RESOLVE_SPECULATIVE_FAIL;
    }
  }
  DCHECK_LT(static_cast<int>(category),
            static_cast<int>(RESOLVE_MAX));  // Be sure it was set.
  UMA_HISTOGRAM_ENUMERATION("Net.DNS.ResolveCategory", category, RESOLVE_MAX);

  if (category == RESOLVE_FAIL ||
      (start_time_ != base::TimeTicks() && category == RESOLVE_ABORT)) {
    if (duration < base::Milliseconds(10)) {
      base::UmaHistogramSparse("Net.DNS.ResolveError.Fast", std::abs(error));
    } else {
      base::UmaHistogramSparse("Net.DNS.ResolveError.Slow", std::abs(error));
    }
  }

  // Record the complete fallback path taken to achieve a successful
  // resolution.
  if (category == RESOLVE_SUCCESS && task_type.has_value()) {
    bool secure_dns_failed = false;
    bool classic_dns_failed = false;
    bool platform_dns_failed = false;

    for (const auto& result : completion_results_) {
      if (IsAttemptModeSecure(result.attempt_mode)) {
        secure_dns_failed = true;
      } else if (result.attempt_mode ==
                 DnsTransactionFactory::AttemptMode::kClassic) {
        classic_dns_failed = true;
      } else if (result.attempt_mode ==
                 DnsTransactionFactory::AttemptMode::kPlatform) {
        platform_dns_failed = true;
      }
    }

    std::optional<ResolveFallbackPath> path =
        CalculateResolvePath(task_type.value(), secure_dns_failed,
                             classic_dns_failed, platform_dns_failed);
    if (path.has_value()) {
      base::UmaHistogramEnumeration("Net.DNS.ResolveFallbackPath",
                                    path.value());
    }
  }
}

void HostResolverManager::Job::RecordJobHttpsHistograms() {
  const bool https_attempted = key_.query_types.Has(DnsQueryType::HTTPS) &&
                               dns_task_executed_ && !dns_task_https_disabled_;
  base::UmaHistogramBoolean("Net.DNS.JobAttemptedHttps", https_attempted);

  if (!https_attempted) {
    auto calculate_reason = [&]() {
      if (!base::FeatureList::IsEnabled(features::kUseDnsHttpsSvcb)) {
        return HttpsNotAttemptedReason::kQueryingHttpsDisabled;
      }
      if (!base::FeatureList::IsEnabled(features::kAsyncDns)) {
        return HttpsNotAttemptedReason::kBuiltInResolverDisabled;
      }
      if (!resolver_->dns_client_) {
        return HttpsNotAttemptedReason::kNoDnsClient;
      }
      if (resolver_->dns_client_->FallbackFromInsecureTransactionPreferred()) {
        return HttpsNotAttemptedReason::
            kFallbackFromInsecureTransactionPreferred;
      }
      if (dns_task_https_disabled_) {
        return HttpsNotAttemptedReason::kInsecureDnsTaskDisabled;
      }
      if (fallback_to_system_task_after_dns_task_) {
        return HttpsNotAttemptedReason::kFallbackToSystemTaskAfterDnsTask;
      }
      return HttpsNotAttemptedReason::kUnknown;
    };

    base::UmaHistogramEnumeration("Net.DNS.JobHttpsNotAttemptedReason",
                                  calculate_reason());
  }
}

void HostResolverManager::Job::MaybeCacheResult(const HostCache::Entry& results,
                                                base::TimeDelta ttl,
                                                bool secure) {
  // If the request did not complete, don't cache it.
  if (!results.did_complete()) {
    return;
  }
  resolver_->CacheResult(host_cache_, key_.ToCacheKey(secure), results, ttl);
}

void HostResolverManager::Job::CompleteRequests(
    const HostCache::Entry& results,
    base::TimeDelta ttl,
    bool allow_cache,
    bool secure,
    std::optional<TaskType> task_type,
    std::optional<base::TimeDelta> task_completion_delay,
    std::optional<DohResolutionDetails> doh_details) {
  CHECK(resolver_.get());

  ResolutionDetails resolution_details;
  constexpr auto to_resolution_source = [](TaskType type) {
    switch (type) {
      case TaskType::SECURE_DNS:
        return ResolutionSource::kSecure;
      case TaskType::DNS:
        return ResolutionSource::kInsecure;
      case TaskType::SYSTEM:
        return ResolutionSource::kSystem;
      case TaskType::DNS_PLATFORM:
        return ResolutionSource::kPlatform;
      case TaskType::MDNS:
        return ResolutionSource::kMdns;
      case TaskType::NAT64:
        return ResolutionSource::kNat64;
      case TaskType::INSECURE_CACHE_LOOKUP:
        return ResolutionSource::kCache;
      case TaskType::SECURE_CACHE_LOOKUP:
      case TaskType::CACHE_LOOKUP:
      case TaskType::CONFIG_PRESET:
      case TaskType::HOSTS:
        // These task types should have been handled synchronously in
        // ResolveLocally() prior to Job creation.
        NOTREACHED() << "type=" << static_cast<int>(type);
    }
  };

  if (task_type) {
    resolution_details.source = to_resolution_source(*task_type);
  }
  if (results.error() == OK) {
    resolution_details.task_completion_delay = task_completion_delay;
  }
  resolution_details.secure_dns_attempted = secure_dns_attempted_;
  resolution_details.doh_details = std::move(doh_details);

  // This job must be removed from resolver's |jobs_| now to make room for a
  // new job with the same key in case one of the OnComplete callbacks decides
  // to spawn one. Consequently, if the job was owned by |jobs_|, the job
  // deletes itself when CompleteRequests is done.
  std::unique_ptr<Job> self_deleter;
  if (self_iterator_) {
    self_deleter = resolver_->RemoveJob(self_iterator_.value());
  }

  Finish();

  if (results.error() == ERR_DNS_REQUEST_CANCELLED) {
    CancelRequests();
    // `this` may be deleted. Do not access `this` after this point.
    return;
  }

  net_log_.EndEventWithNetErrorCode(NetLogEventType::HOST_RESOLVER_MANAGER_JOB,
                                    results.error());

  // Handle all caching before completing requests as completing requests may
  // start new requests that rely on cached results.
  if (allow_cache) {
    MaybeCacheResult(results, ttl, secure);
  }

  RecordJobHistograms(results, task_type);
  if (results.error() == OK && had_non_speculative_request_ &&
      key_.source == HostResolverSource::ANY) {
    RecordJobHttpsHistograms();
  }

  // Complete all of the requests that were attached to the job and
  // detach them.
  while (!requests_.empty()) {
    RequestImpl* req = requests_.head()->value();
    req->RemoveFromList();
    CHECK(key_ == req->GetJobKey());

    if (results.error() == OK && !req->parameters().is_speculative) {
      req->SetResults(
          results.CopyWithDefaultPort(req->request_host().GetPort()),
          resolution_details);
    }
    req->OnJobCompleted(
        key_, results.error(),
        /*is_secure_network_error=*/secure && results.error() != OK);

    // Check if the resolver was destroyed as a result of running the
    // callback. If it was, we could continue, but we choose to bail.
    if (!resolver_.get()) {
      return;
    }
  }

  while (!service_endpoint_requests_.empty()) {
    ServiceEndpointRequestImpl* request =
        service_endpoint_requests_.head()->value();
    request->RemoveFromList();
    request->OnJobCompleted(results, secure, resolution_details);
    if (!resolver_.get()) {
      return;
    }
  }

  // TODO(crbug.com/40178456): Call StartBootstrapFollowup() if any of the
  // requests have the Bootstrap policy.  Note: A naive implementation could
  // cause an infinite loop if the bootstrap result has TTL=0.
}

void HostResolverManager::Job::CompleteRequestsWithoutCache(
    const HostCache::Entry& results,
    std::optional<HostCache::EntryStaleness> stale_info,
    TaskType task_type,
    std::optional<base::TimeDelta> task_completion_delay) {
  // Record the stale_info for all non-speculative requests, if it exists.
  if (stale_info) {
    for (auto* node = requests_.head(); node != requests_.end();
         node = node->next()) {
      if (!node->value()->parameters().is_speculative) {
        node->value()->set_stale_info(stale_info.value());
      }
    }
  }
  CompleteRequests(results, base::TimeDelta(), false /* allow_cache */,
                   false /* secure */, task_type, task_completion_delay);
}

void HostResolverManager::Job::CompleteRequestsWithError(
    int net_error,
    std::optional<TaskType> task_type) {
  DCHECK_NE(OK, net_error);
  CompleteRequests(
      HostCache::Entry(net_error, HostCache::Entry::SOURCE_UNKNOWN),
      base::TimeDelta(), true /* allow_cache */, false /* secure */, task_type);
}

void HostResolverManager::Job::CancelRequests() {
  net_log_.AddEvent(NetLogEventType::CANCELLED);
  net_log_.EndEventWithNetErrorCode(NetLogEventType::HOST_RESOLVER_MANAGER_JOB,
                                    OK);

  // In the following while loops, we check if the resolver was destroyed as a
  // result of running the callback. If it was, we could continue, but we choose
  // to bail.

  while (!requests_.empty()) {
    RequestImpl* req = requests_.head()->value();
    req->RemoveFromList();
    req->OnJobCancelled(key_);
    if (!resolver_.get()) {
      return;
    }
  }

  while (!service_endpoint_requests_.empty()) {
    ServiceEndpointRequestImpl* request =
        service_endpoint_requests_.head()->value();
    request->RemoveFromList();
    request->OnJobCancelled();
    if (!resolver_.get()) {
      return;
    }
  }
}

RequestPriority HostResolverManager::Job::priority() const {
  return priority_tracker_.highest_priority();
}

}  // namespace net
