/*
 *  Copyright 2016 The WebRTC Project Authors. All rights reserved.
 *
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the LICENSE file in the root of the source
 *  tree. An additional intellectual property rights grant can be found
 *  in the file PATENTS.  All contributing project authors may
 *  be found in the AUTHORS file in the root of the source tree.
 */

#ifndef P2P_BASE_ICE_TRANSPORT_INTERNAL_H_
#define P2P_BASE_ICE_TRANSPORT_INTERNAL_H_

#include <cstdint>
#include <functional>
#include <optional>
#include <span>
#include <string>
#include <utility>
#include <vector>

#include "absl/functional/any_invocable.h"
#include "api/candidate.h"
#include "api/peer_connection_interface.h"
#include "api/rtc_error.h"
#include "api/task_queue/task_queue_base.h"
#include "api/transport/enums.h"
#include "api/units/time_delta.h"
#include "p2p/base/candidate_pair_interface.h"
#include "p2p/base/connection_info.h"
#include "p2p/base/packet_transport_internal.h"
#include "p2p/base/port.h"
#include "p2p/base/stun_dictionary.h"
#include "p2p/base/transport_description.h"
#include "p2p/dtls/dtls_stun_piggyback_callbacks.h"
#include "rtc_base/callback_list.h"
#include "rtc_base/checks.h"
#include "rtc_base/network_constants.h"
#include "rtc_base/system/rtc_export.h"

namespace webrtc {

// TODO(zhihuang): Replace this with
// PeerConnectionInterface::IceConnectionState.
enum class IceTransportStateInternal {
  STATE_INIT,
  STATE_CONNECTING,  // Will enter this state once a connection is created
  STATE_COMPLETED,
  STATE_FAILED
};

enum IceConnectionState {
  kIceConnectionConnecting = 0,
  kIceConnectionFailed,
  kIceConnectionConnected,  // Writable, but still checking one or more
                            // connections
  kIceConnectionCompleted,
};

struct IceTransportStats {
  CandidateStatsList candidate_stats_list;
  ConnectionInfos connection_infos;
  // Number of times the selected candidate pair has changed
  // Initially 0 and 1 once the first candidate pair has been selected.
  // The counter is increase also when "unselecting" a connection.
  uint32_t selected_candidate_pair_changes = 0;

  // Bytes/packets sent/received.
  // note: Is not the same as sum(connection_infos.bytes_sent)
  // as connections are created and destroyed while the ICE transport
  // is alive.
  uint64_t bytes_sent = 0;
  uint64_t bytes_received = 0;
  uint64_t packets_sent = 0;
  uint64_t packets_received = 0;

  IceRole ice_role = ICEROLE_UNKNOWN;
  std::string ice_local_username_fragment;
  IceTransportState ice_state = IceTransportState::kNew;
};

typedef std::vector<Candidate> Candidates;

// TODO(deadbeef): Unify with PeerConnectionInterface::IceConnectionState
// once /talk/ and /webrtc/ are combined, and also switch to ENUM_NAME naming
// style.
enum IceGatheringState {
  kIceGatheringNew = 0,
  kIceGatheringGathering,
  kIceGatheringComplete,
};

enum ContinualGatheringPolicy {
  // All port allocator sessions will stop after a writable connection is found.
  GATHER_ONCE = 0,
  // The most recent port allocator session will keep on running.
  GATHER_CONTINUALLY,
};

// ICE Nomination mode.
enum class NominationMode {
  REGULAR,         // Nominate once per ICE restart (Not implemented yet).
  AGGRESSIVE,      // Nominate every connection except that it will behave as if
                   // REGULAR when the remote is an ICE-LITE endpoint.
  SEMI_AGGRESSIVE  // Our current implementation of the nomination algorithm.
                   // The details are described in P2PTransportChannel.
};

// Utility method that checks if various required Candidate fields are filled in
// and contain valid values. If conditions are not met, an RTCError with the
// appropriated error number and description is returned. If the configuration
// is valid RTCError::OK() is returned.
RTCError VerifyCandidate(const Candidate& cand);

// Runs through a list of Candidate instances and calls VerifyCandidate
// for each one, stopping on the first error encounted and returning that error
// value if so. On success returns RTCError::OK().
RTCError VerifyCandidates(const Candidates& candidates);

// Information about ICE configuration.
// TODO(bugs.webrtc.org/15609): Define a public API for this.
struct RTC_EXPORT IceConfig {
  // The ICE connection receiving timeout value.
  std::optional<TimeDelta> receiving_timeout;
  // Time interval to ping a backup connection when the ICE channel is strongly
  // connected.
  std::optional<TimeDelta> backup_connection_ping_interval;

  ContinualGatheringPolicy continual_gathering_policy = GATHER_ONCE;

  bool gather_continually() const {
    return continual_gathering_policy == GATHER_CONTINUALLY;
  }

  // Whether we should prioritize Relay/Relay candidate when nothing
  // is writable yet.
  bool prioritize_most_likely_candidate_pairs = false;

  // Writable connections are pinged at a slower rate once stablized.
  std::optional<TimeDelta> stable_writable_connection_ping_interval;

  // If set to true, this means the ICE transport should presume TURN-to-TURN
  // candidate pairs will succeed, even before a binding response is received.
  bool presume_writable_when_fully_relayed = false;

  // If true, after the ICE transport type (as the candidate filter used by the
  // port allocator) is changed such that new types of ICE candidates are
  // allowed by the new filter, e.g. from CF_RELAY to CF_ALL, candidates that
  // have been gathered by the ICE transport but filtered out and not signaled
  // to the upper layers, will be surfaced.
  bool surface_ice_candidates_on_ice_transport_type_changed = false;

  // Interval to check on all networks and to perform ICE regathering on any
  // active network having no connection on it.
  std::optional<TimeDelta> regather_on_failed_networks_interval;

  // The time period in which we will not switch the selected connection
  // when a new connection becomes receiving but the selected connection is not
  // in case that the selected connection may become receiving soon.
  std::optional<TimeDelta> receiving_switching_delay;

  // TODO(honghaiz): Change the default to regular nomination.
  // Default nomination mode if the remote does not support renomination.
  NominationMode default_nomination_mode = NominationMode::SEMI_AGGRESSIVE;

  // The interval at which ICE checks (STUN pings) will be sent for a candidate
  // pair when it is both writable and receiving (strong connectivity). This
  // parameter overrides the default value given by `kStrongPingInterval` in
  // p2ptransport.h if set.
  std::optional<TimeDelta> ice_check_interval_strong_connectivity;
  // The interval \at which ICE checks (STUN pings) will be sent for a candidate
  // pair when it is either not writable or not receiving (weak connectivity).
  // This parameter overrides the default value given by `kWeakPingInterval` in
  // p2ptransport.h if set.
  std::optional<TimeDelta> ice_check_interval_weak_connectivity;
  // ICE checks (STUN pings) will not be sent at higher rate (lower interval)
  // than this, no matter what other settings there are.
  //
  // Note that this parameter overrides both the above check intervals for
  // candidate pairs with strong or weak connectivity, if either of the above
  // interval is shorter than the min interval.
  std::optional<TimeDelta> ice_check_min_interval;
  // The min time period for which a candidate pair must wait for response to
  // connectivity checks before it becomes unwritable. This parameter
  // overrides the default value given by `kConnectionWriteConnectTimeout`
  // in port.h if set, when determining the writability of a candidate pair.
  std::optional<TimeDelta> ice_unwritable_timeout;

  // The min number of connectivity checks that a candidate pair must sent
  // without receiving response before it becomes unwritable. This parameter
  // overrides the default value given by `kConnectionWriteConnectTimeout` in
  // port.h if set, when determining the writability of a candidate pair.
  std::optional<int> ice_unwritable_min_checks;

  // The min time period for which a candidate pair must wait for response to
  // connectivity checks it becomes inactive. This parameter overrides the
  // default value given by `kConnectionWriteTimeout` in port.h if set, when
  // determining the writability of a candidate pair.
  std::optional<TimeDelta> ice_inactive_timeout;

  // The interval at which STUN candidates will resend STUN binding requests to
  // keep NAT bindings open.
  std::optional<TimeDelta> stun_keepalive_interval;

  std::optional<AdapterType> network_preference;

  VpnPreference vpn_preference = VpnPreference::kDefault;

  // Experimental feature to transport the DTLS handshake in STUN packets.
  bool dtls_handshake_in_stun = false;

  IceConfig();
  IceConfig(TimeDelta receiving_timeout,
            TimeDelta backup_connection_ping_interval,
            ContinualGatheringPolicy gathering_policy,
            bool prioritize_most_likely_candidate_pairs,
            TimeDelta stable_writable_connection_ping_interval,
            bool presume_writable_when_fully_relayed,
            TimeDelta regather_on_failed_networks_interval,
            TimeDelta receiving_switching_delay);
  // Construct an IceConfig object from an RTCConfiguration object.
  // This will check the `config` settings and set the associated IceConfig
  // member properties.
  explicit IceConfig(const PeerConnectionInterface::RTCConfiguration& config);
  ~IceConfig();

  // Checks if the current configuration values are consistent.
  RTCError IsValid() const;

  // Helper getters for parameters with implementation-specific default value.
  // By convention, parameters with default value are represented by
  // std::optional and setting a parameter to null restores its default value.
  TimeDelta receiving_timeout_or_default() const;
  TimeDelta backup_connection_ping_interval_or_default() const;
  TimeDelta stable_writable_connection_ping_interval_or_default() const;
  TimeDelta regather_on_failed_networks_interval_or_default() const;
  TimeDelta receiving_switching_delay_or_default() const;
  TimeDelta ice_check_interval_strong_connectivity_or_default() const;
  TimeDelta ice_check_interval_weak_connectivity_or_default() const;
  TimeDelta ice_check_min_interval_or_default() const;
  TimeDelta ice_unwritable_timeout_or_default() const;
  int ice_unwritable_min_checks_or_default() const;
  TimeDelta ice_inactive_timeout_or_default() const;
  TimeDelta stun_keepalive_interval_or_default() const;
};

// IceTransportInternal is an internal abstract class that does ICE.
// Once the public interface is supported,
// (https://www.w3.org/TR/webrtc/#rtcicetransport)
// the IceTransportInterface will be split from this class.
//
// TODO(bugs.webrtc.org/15609): Define a public API for this.
class RTC_EXPORT IceTransportInternal : public PacketTransportInternal {
 public:
  explicit IceTransportInternal(TaskQueueBase* attached_queue = nullptr);
  ~IceTransportInternal() override;

  // This class is uncopyable and immovable.
  IceTransportInternal(const IceTransportInternal&) = delete;
  IceTransportInternal& operator=(const IceTransportInternal&) = delete;
  IceTransportInternal(IceTransportInternal&&) = delete;
  IceTransportInternal& operator=(IceTransportInternal&&) = delete;

  // TODO(bugs.webrtc.org/9308): Remove GetState once all uses have been
  // migrated to GetIceTransportState.
  virtual IceTransportStateInternal GetState() const = 0;
  virtual IceTransportState GetIceTransportState() const = 0;

  virtual int component() const = 0;

  virtual IceRole GetIceRole() const = 0;

  virtual void SetIceRole(IceRole role) = 0;

  // The ufrag and pwd in `ice_params` must be set
  // before candidate gathering can start.
  virtual void SetIceParameters(const IceParameters& ice_params) = 0;

  virtual void SetRemoteIceParameters(const IceParameters& ice_params) = 0;

  virtual void SetRemoteIceMode(IceMode mode) = 0;

  virtual void SetIceConfig(const IceConfig& config) = 0;
  // Default implementation in order to allow downstream usage deletion.
  // TODO: bugs.webrtc.org/367395350 - Make virtual when all downstream
  // overrides are gone.
  virtual const IceConfig& config() const { RTC_CHECK_NOTREACHED(); }

  // Start gathering candidates if not already started, or if an ICE restart
  // occurred.
  virtual void MaybeStartGathering() = 0;

  virtual void AddRemoteCandidate(const Candidate& candidate) = 0;

  virtual void RemoveRemoteCandidate(const Candidate& candidate) = 0;

  virtual void RemoveAllRemoteCandidates() = 0;

  virtual IceGatheringState gathering_state() const = 0;

  // Returns the current stats for this connection.
  virtual bool GetStats(IceTransportStats* ice_transport_stats) = 0;

  // Returns RTT estimate over the currently active connection, or an empty
  // std::optional if there is none.
  virtual std::optional<int> GetRttEstimate() = 0;

  // Returns the selected candidate pair, or an empty std::optional if there is
  // none.
  virtual std::optional<const CandidatePair> GetSelectedCandidatePair()
      const = 0;

  virtual std::optional<std::reference_wrapper<StunDictionaryWriter>>
  GetDictionaryWriter() {
    return std::nullopt;
  }

  void AddGatheringStateCallback(
      const void* removal_tag,
      absl::AnyInvocable<void(IceTransportInternal*)> callback);
  void RemoveGatheringStateCallback(const void* removal_tag);

  // Handles sending and receiving of candidates.
  void NotifyCandidateGathered(IceTransportInternal* transport,
                               const Candidate& candidate) {
    candidate_gathered_callbacks_.Send(transport, candidate);
  }
  [[deprecated("Use SubscribeCandidateGathered(void* tag, ...)")]]
  void SubscribeCandidateGathered(
      absl::AnyInvocable<void(IceTransportInternal*, const Candidate&)>
          callback);

  void SubscribeCandidateGathered(
      void* tag,
      absl::AnyInvocable<void(IceTransportInternal*, const Candidate&)>
          callback);

  void SetCandidateErrorCallback(
      absl::AnyInvocable<void(IceTransportInternal*,
                              const IceCandidateErrorEvent&)> callback) {
    RTC_DCHECK(!candidate_error_callback_);
    candidate_error_callback_ = std::move(callback);
  }

  void SetCandidatesRemovedCallback(
      absl::AnyInvocable<void(IceTransportInternal*, const Candidates&)>
          callback) {
    RTC_DCHECK(!candidates_removed_callback_);
    candidates_removed_callback_ = std::move(callback);
  }

  void SetCandidatePairChangeCallback(
      absl::AnyInvocable<void(const CandidatePairChangeEvent&)> callback) {
    RTC_DCHECK(!candidate_pair_change_callback_);
    candidate_pair_change_callback_ = std::move(callback);
  }

  // Invoked when there is conflict in the ICE role between local and remote
  // agents.
  void NotifyRoleConflict(IceTransportInternal* transport) {
    role_conflict_callbacks_.Send(transport);
  }
  [[deprecated("Use SubscribeRoleConflict(void* tag, ...)")]]
  void SubscribeRoleConflict(
      absl::AnyInvocable<void(IceTransportInternal*)> callback);
  void SubscribeRoleConflict(
      void* tag,
      absl::AnyInvocable<void(IceTransportInternal*)> callback);

  // Emitted whenever the new standards-compliant transport state changed.
  void NotifyIceTransportStateChanged(IceTransportInternal* transport) {
    ice_transport_state_changed_callbacks_.Send(transport);
  }
  [[deprecated("Use SubscribeIceTransportStateChanged(void* tag, ...)")]]
  void SubscribeIceTransportStateChanged(
      absl::AnyInvocable<void(IceTransportInternal*)> callback);

  void SubscribeIceTransportStateChanged(
      void* tag,
      absl::AnyInvocable<void(IceTransportInternal*)> callback);

  // Invoked when remote dictionary has been updated,
  // i.e. modifications to attributes from remote ice agent has
  // reflected in our StunDictionaryView.
  template <typename F>
  void AddDictionaryViewUpdatedCallback(const void* tag, F&& callback) {
    dictionary_view_updated_callback_list_.AddReceiver(
        tag, std::forward<F>(callback));
  }
  void RemoveDictionaryViewUpdatedCallback(const void* tag) {
    dictionary_view_updated_callback_list_.RemoveReceivers(tag);
  }

  // Invoked when local dictionary has been synchronized,
  // i.e. remote ice agent has reported acknowledged updates from us.
  template <typename F>
  void AddDictionaryWriterSyncedCallback(const void* tag, F&& callback) {
    dictionary_writer_synced_callback_list_.AddReceiver(
        tag, std::forward<F>(callback));
  }
  void RemoveDictionaryWriterSyncedCallback(const void* tag) {
    dictionary_writer_synced_callback_list_.RemoveReceivers(tag);
  }

  virtual void ResetDtlsStunPiggybackCallbacks() {}
  virtual void SetDtlsStunPiggybackCallbacks(
      DtlsStunPiggybackCallbacks&& callbacks) {}

 protected:
  void SendGatheringStateEvent() { gathering_state_callback_list_.Send(this); }

  CallbackList<IceTransportInternal*,
               const StunDictionaryView&,
               std::span<uint16_t>>
      dictionary_view_updated_callback_list_;
  CallbackList<IceTransportInternal*, const StunDictionaryWriter&>
      dictionary_writer_synced_callback_list_;

  CallbackList<IceTransportInternal*> gathering_state_callback_list_;

  absl::AnyInvocable<void(IceTransportInternal*, const IceCandidateErrorEvent&)>
      candidate_error_callback_;

  absl::AnyInvocable<void(IceTransportInternal*, const Candidates&)>
      candidates_removed_callback_;

  absl::AnyInvocable<void(const CandidatePairChangeEvent&)>
      candidate_pair_change_callback_;

 private:
  CallbackList<IceTransportInternal*, const Candidate&>
      candidate_gathered_callbacks_;
  CallbackList<IceTransportInternal*> role_conflict_callbacks_;
  CallbackList<IceTransportInternal*> ice_transport_state_changed_callbacks_;
};

}  //  namespace webrtc


#endif  // P2P_BASE_ICE_TRANSPORT_INTERNAL_H_
