// Copyright 2012 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_PROXY_RESOLUTION_WIN_DHCP_PAC_FILE_FETCHER_WIN_H_
#define NET_PROXY_RESOLUTION_WIN_DHCP_PAC_FILE_FETCHER_WIN_H_

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

#include "base/memory/raw_ptr.h"
#include "base/memory/ref_counted.h"
#include "base/threading/thread_checker.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "net/base/completion_once_callback.h"
#include "net/base/net_export.h"
#include "net/log/net_log_with_source.h"
#include "net/proxy_resolution/dhcp_pac_file_fetcher.h"
#include "net/traffic_annotation/network_traffic_annotation.h"

namespace base {
class TaskRunner;
}

namespace net {

struct DhcpAdapterNamesLoggingInfo;
class DhcpPacFileAdapterFetcher;
class URLRequestContext;

// Windows-specific implementation.
class NET_EXPORT_PRIVATE DhcpPacFileFetcherWin
    : public DhcpPacFileFetcher,
      public base::SupportsWeakPtr<DhcpPacFileFetcherWin> {
 public:
  DhcpPacFileFetcherWin() = delete;

  // Creates a DhcpPacFileFetcherWin that issues requests through
  // |url_request_context|. |url_request_context| must remain valid for
  // the lifetime of DhcpPacFileFetcherWin.
  explicit DhcpPacFileFetcherWin(URLRequestContext* url_request_context);

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

  ~DhcpPacFileFetcherWin() override;

  // DhcpPacFileFetcher implementation.
  int Fetch(std::u16string* utf16_text,
            CompletionOnceCallback callback,
            const NetLogWithSource& net_log,
            const NetworkTrafficAnnotationTag traffic_annotation) override;
  void Cancel() override;
  void OnShutdown() override;
  const GURL& GetPacURL() const override;
  std::string GetFetcherName() const override;

  // Sets |adapter_names| to contain the name of each network adapter on
  // this machine that has DHCP enabled and is not a loop-back adapter. May
  // optionally update |info| (if non-null) with information for logging.
  // Returns false on error.
  static bool GetCandidateAdapterNames(std::set<std::string>* adapter_names,
                                       DhcpAdapterNamesLoggingInfo* info);

 protected:
  int num_pending_fetchers() const;

  URLRequestContext* url_request_context() const;

  scoped_refptr<base::TaskRunner> GetTaskRunner();

  // This inner class encapsulate work done on a worker pool thread.
  // The class calls GetCandidateAdapterNames, which can take a couple of
  // hundred milliseconds.
  class NET_EXPORT_PRIVATE AdapterQuery
      : public base::RefCountedThreadSafe<AdapterQuery> {
   public:
    AdapterQuery();

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

    // This is the method that runs on the worker pool thread.
    void GetCandidateAdapterNames();

    // This set is valid after GetCandidateAdapterNames has
    // been run. Its lifetime is scoped by this object.
    const std::set<std::string>& adapter_names() const;

    DhcpAdapterNamesLoggingInfo* logging_info() { return logging_info_.get(); }

   protected:
    // Virtual method introduced to allow unit testing.
    virtual bool ImplGetCandidateAdapterNames(
        std::set<std::string>* adapter_names,
        DhcpAdapterNamesLoggingInfo* info);

    friend class base::RefCountedThreadSafe<AdapterQuery>;
    virtual ~AdapterQuery();

   private:
    // These are constructed on the originating thread, then used on the
    // worker thread, then used again on the originating thread only when
    // the task has completed on the worker thread. No locking required.
    std::set<std::string> adapter_names_;
    std::unique_ptr<DhcpAdapterNamesLoggingInfo> logging_info_;
  };

  // Virtual methods introduced to allow unit testing.
  virtual std::unique_ptr<DhcpPacFileAdapterFetcher> ImplCreateAdapterFetcher();
  virtual scoped_refptr<AdapterQuery> ImplCreateAdapterQuery();
  virtual base::TimeDelta ImplGetMaxWait();
  virtual void ImplOnGetCandidateAdapterNamesDone() {}

 private:
  // Event/state transition handlers
  void CancelImpl();
  void OnGetCandidateAdapterNamesDone(
      scoped_refptr<AdapterQuery> query,
      const NetworkTrafficAnnotationTag traffic_annotation);
  void OnFetcherDone(size_t fetcher_i, int result);
  void OnWaitTimer();
  void TransitionToDone();

  // This is the outer state machine for fetching PAC configuration from
  // DHCP.  It relies for sub-states on the state machine of the
  // DhcpPacFileAdapterFetcher class.
  //
  // The goal of the implementation is to the following work in parallel
  // for all network adapters that are using DHCP:
  // a) Try to get the PAC URL configured in DHCP;
  // b) If one is configured, try to fetch the PAC URL.
  // c) Once this is done for all adapters, or a timeout has passed after
  //    it has completed for the fastest adapter, return the PAC file
  //    available for the most preferred network adapter, if any.
  //
  // The state machine goes from START->WAIT_ADAPTERS when it starts a
  // worker thread to get the list of adapters with DHCP enabled.
  // It then goes from WAIT_ADAPTERS->NO_RESULTS when it creates
  // and starts an DhcpPacFileAdapterFetcher for each adapter.  It goes
  // from NO_RESULTS->SOME_RESULTS when it gets the first result; at this
  // point a wait timer is started.  It goes from SOME_RESULTS->DONE in
  // two cases: All results are known, or the wait timer expired.  A call
  // to Cancel() will also go straight to DONE from any state.  Any
  // way the DONE state is entered, we will at that point cancel any
  // outstanding work and return the best known PAC script or the empty
  // string.
  //
  // The state machine is reset for each Fetch(), a call to which is
  // only valid in states START and DONE, as only one Fetch() is
  // allowed to be outstanding at any given time.
  enum State {
    STATE_START,
    STATE_WAIT_ADAPTERS,
    STATE_NO_RESULTS,
    STATE_SOME_RESULTS,
    STATE_DONE,
  };

  // Vector, in Windows' network adapter preference order, of
  // DhcpPacFileAdapterFetcher objects that are or were attempting
  // to fetch a PAC file based on DHCP configuration.
  using FetcherVector = std::vector<std::unique_ptr<DhcpPacFileAdapterFetcher>>;
  FetcherVector fetchers_;

  // Current state of this state machine.
  State state_ = STATE_START;

  // The following members are associated with the latest call to Fetch().

  // Number of fetchers we are waiting for.
  int num_pending_fetchers_ = 0;

  // Lets our client know we're done. Not valid in states START or DONE.
  CompletionOnceCallback callback_;

  // The NetLog to use for the current Fetch().
  NetLogWithSource net_log_;

  // Pointer to string we will write results to. Not valid in states
  // START and DONE.
  raw_ptr<std::u16string, DanglingUntriaged> destination_string_ = nullptr;

  // PAC URL retrieved from DHCP, if any. Valid only in state STATE_DONE.
  GURL pac_url_;

  base::OneShotTimer wait_timer_;

  // Set to nullptr on cancellation.
  raw_ptr<URLRequestContext> url_request_context_;

  // NULL or the AdapterQuery currently in flight.
  scoped_refptr<AdapterQuery> last_query_;

  // TaskRunner used for all DHCP lookup tasks.
  const scoped_refptr<base::TaskRunner> task_runner_;

  THREAD_CHECKER(thread_checker_);
};

}  // namespace net

#endif  // NET_PROXY_RESOLUTION_WIN_DHCP_PAC_FILE_FETCHER_WIN_H_
