// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef SERVICES_NETWORK_PROXY_AUTO_CONFIG_LIBRARY_H_
#define SERVICES_NETWORK_PROXY_AUTO_CONFIG_LIBRARY_H_

#include <list>
#include <set>

#include "base/component_export.h"
#include "base/containers/queue.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/task/sequenced_task_runner.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/remote_set.h"
#include "net/base/completion_once_callback.h"
#include "net/base/ip_address.h"
#include "net/dns/host_resolver_proc.h"
#include "net/dns/host_resolver_system_task.h"
#include "services/proxy_resolver/public/mojom/proxy_resolver.mojom.h"

namespace net {
class ClientSocketFactory;
class AddressList;
}  // namespace net

namespace network {

// myIpAddress() is a broken API available to PAC scripts. Do not use these
// outside of PAC as they are broken APIs.
// It has the problematic definition of:
// "Returns the IP address of the host machine."
//
// This has ambiguity on what should happen for multi-homed hosts which may have
// multiple IP addresses to choose from. To be unambiguous we would need to
// know which hosts is going to be connected to, in order to use the outgoing
// IP for that request.
//
// However at this point that is not known, as the proxy still hasn't been
// decided.
//
// The strategy used here is to prioritize the IP address that would be used
// for connecting to the public internet by testing which interface is used for
// connecting to 8.8.8.8 and 2001:4860:4860::8888 (public IPs).
//
// If that fails, we will try resolving the machine's hostname, and also probing
// for routes in the private IP space.
//
// Link-local IP addresses are not generally returned, however may be if no
// other IP was found by the probes.
//
// This class supports being deleted at any time.
class COMPONENT_EXPORT(NETWORK_SERVICE) MyIpAddressImpl {
 public:
  enum class Mode {
    kMyIpAddress,
    kMyIpAddressEx,
  };

  explicit MyIpAddressImpl(Mode mode);
  ~MyIpAddressImpl();

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

  // Adds `my_ip_address_client` to the list of clients waiting for a list of
  // IP candidates. If no search for candidates is currently ongoing, a search
  // according to `mode_` is started which will run asynchronously on the
  // current thread.
  void AddRequest(
      mojo::PendingRemote<proxy_resolver::mojom::HostResolverRequestClient>
          my_ip_address_client);

  // Used for mocking the socket dependency.
  void SetSocketFactoryForTest(net::ClientSocketFactory* socket_factory);

  // Used for mocking the DNS dependency.
  void SetHostResolverProcForTest(
      scoped_refptr<net::HostResolverProc> host_resolver_proc);

 private:
  // State-machine states.
  enum class State {
    kNone,
    kConnectSocketsPublicInternetRoutes,
    kTestResolvingHostname,
    kConnectSocketsPrivateIpRoutes,
    kSendResultsAndReset,
  };

  // Sockets that are created and connected for the purpose of probing their
  // source addresses.
  struct SocketConnectionResult;

  // Try to make progress gathering IP candidates.
  void DoLoop();

  int DoConnectSocketsPublicInternetRoutes();
  int DoTestResolvingHostname();
  int DoConnectSocketsPrivateIPRoutes();
  int DoSendResultsAndReset();

  // Resets back to the initial state. Clears all candidate IPs, clears the
  // RemoteSet, and invalidates any weak pointers to cancel any pending
  // callbacks.
  void Reset();

  // Callback for a disconnected Remote in `my_ip_address_clients_`. If no
  // clients are left, calls Reset() to cancel the current candidate search.
  void ClientDisconnected(mojo::RemoteSetElementId id);

  // DoConnectSocketsPublicInternetRoutes() and
  // DoConnectSocketsPrivateIPRoutes() both test what source IP addresses would
  // be used for sending a UDP packet to a set of destination IPs. This function
  // connects sockets stored in `socket_results_` to the given
  // `destination_ips`. The sockets are stored in the same order as
  // `destination_ips` so sockets_results_[0] is connected to
  // destination_ips[0], etc.
  // Then returns the result of DoProcessConnectedSockets().
  int DoConnectSockets(std::vector<net::IPAddress> destination_ips);

  // Loops through the connected sockets in `socket_results_` (in order) and
  // gets the local address associated with the socket. The local address is
  // Add()ed and if the search is `done_`, returns immediately with net::OK.
  // If the loop encounters a socket that has not yet connected, this function
  // will return with net::ERR_IO_PENDING. This function can then be called
  // again later and will not reconsider already-processed sockets.
  // If all sockets in `socket_results_` are processed, calls
  // MarkAsDoneIfHaveCandidates().
  int DoProcessConnectedSockets();

  // Called whenever a socket is connect asynchronously. `socket_result` should
  // always be in `socket_results_` unless the WeakPtr associated with this
  // callback is invalidated.
  // Stores `result` in `socket_result` and calls DoProcessConnectedSockets().
  // If the result is not ERR_IO_PENDING, re-enters DoLoop().
  void OnConnectedSocket(SocketConnectionResult& socket_result, int result);

  // A callback for SystemHostResolverCallAsync(). Add()s all the IPs in
  // `addr_list` if `net_error` is net::OK, and then returns to the state
  // machine by calling DoLoop().
  void ReceiveDnsResults(
      std::unique_ptr<net::HostResolverSystemTask> system_dns_resolution_task,
      const net::AddressList& addr_list,
      int os_error,
      int net_error);

  // Adds `address` to the results if appropriate. Can add the IP to either
  // `candidate_ips_` or `link_local_ips_`.
  void Add(const net::IPAddress& address);

  // If candidate IPs have been found, marks the current search as done.
  // If `done_` is true then also sets `next_state_` to kSendResultsAndReset.
  //
  // This is used to stop exploring for IPs if any of the high-level tests find
  // a match (i.e. either the public internet route test, or hostname test, or
  // private route test found something).
  //
  // In the case of myIpAddressEx() this means it will be conservative in which
  // IPs it returns and not enumerate the full set. See http://crbug.com/905366
  // before expanding that policy.
  void MarkAsDoneIfHaveCandidates();

  net::IPAddressList GetResultForMyIpAddress() const;
  net::IPAddressList GetResultForMyIpAddressEx() const;

  static net::IPAddressList GetSingleResultFavoringIPv4(
      const net::IPAddressList& ips);

  // The operation being carried out.
  const Mode mode_;

  // A set of clients waiting for the search to conclude.
  mojo::RemoteSet<proxy_resolver::mojom::HostResolverRequestClient>
      my_ip_address_clients_;

  // Tracks current state of the state machine.
  State next_state_ = State::kNone;

  // Tracks all IPs seen so far so that duplicates don't end up in the returned
  // results.
  std::set<net::IPAddress> seen_ips_;

  // Sockets used to probe public and private routes for the machine's IP
  // address. These are considered in order.
  std::list<SocketConnectionResult> socket_results_;

  // The preferred ordered candidate IPs so far.
  net::IPAddressList candidate_ips_;

  // The link-local IP addresses seen so far (not part of `candidate_ips_`).
  net::IPAddressList link_local_ips_;

  // Whether the search for results has completed.
  //
  // Once "done", calling Add() will not change the final result. This is used
  // to short-circuit early.
  bool done_ = false;

  raw_ptr<net::ClientSocketFactory> override_socket_factory_ = nullptr;
  scoped_refptr<net::HostResolverProc> host_resolver_proc_ = nullptr;

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

}  // namespace network

#endif  // SERVICES_NETWORK_PROXY_AUTO_CONFIG_LIBRARY_H_