Split P2PTransportChannel

This patch moves the logic for
- selection of connection to ping
- selection of connection to use
- selection of connection to prune

into own file and puts it behind a new interface called 'IceControllerInterface'.

BUG=webrtc:10647

Change-Id: I10228b3edd361d3200fa4a734d74a319560966c9
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/158205
Reviewed-by: Qingsi Wang <qingsi@webrtc.org>
Reviewed-by: Honghai Zhang <honghaiz@webrtc.org>
Commit-Queue: Jonas Oreland <jonaso@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#29850}
diff --git a/p2p/BUILD.gn b/p2p/BUILD.gn
index 6ed14a8..945c679 100644
--- a/p2p/BUILD.gn
+++ b/p2p/BUILD.gn
@@ -36,6 +36,8 @@
     "base/async_stun_tcp_socket.h",
     "base/basic_async_resolver_factory.cc",
     "base/basic_async_resolver_factory.h",
+    "base/basic_ice_controller.cc",
+    "base/basic_ice_controller.h",
     "base/basic_packet_socket_factory.cc",
     "base/basic_packet_socket_factory.h",
     "base/candidate_pair_interface.h",
@@ -50,6 +52,8 @@
     "base/dtls_transport_factory.h",
     "base/dtls_transport_internal.cc",
     "base/dtls_transport_internal.h",
+    "base/ice_controller_interface.cc",
+    "base/ice_controller_interface.h",
     "base/ice_credentials_iterator.cc",
     "base/ice_credentials_iterator.h",
     "base/ice_transport_internal.cc",
diff --git a/p2p/base/basic_ice_controller.cc b/p2p/base/basic_ice_controller.cc
new file mode 100644
index 0000000..d348ae9
--- /dev/null
+++ b/p2p/base/basic_ice_controller.cc
@@ -0,0 +1,829 @@
+/*
+ *  Copyright 2019 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.
+ */
+
+#include "p2p/base/basic_ice_controller.h"
+
+namespace {
+
+// The minimum improvement in RTT that justifies a switch.
+const int kMinImprovement = 10;
+
+bool IsRelayRelay(const cricket::Connection* conn) {
+  return conn->local_candidate().type() == cricket::RELAY_PORT_TYPE &&
+         conn->remote_candidate().type() == cricket::RELAY_PORT_TYPE;
+}
+
+bool IsUdp(const cricket::Connection* conn) {
+  return conn->local_candidate().relay_protocol() == cricket::UDP_PROTOCOL_NAME;
+}
+
+// TODO(qingsi) Use an enum to replace the following constants for all
+// comparision results.
+static constexpr int a_is_better = 1;
+static constexpr int b_is_better = -1;
+static constexpr int a_and_b_equal = 0;
+
+bool LocalCandidateUsesPreferredNetwork(
+    const cricket::Connection* conn,
+    absl::optional<rtc::AdapterType> network_preference) {
+  rtc::AdapterType network_type = conn->port()->Network()->type();
+  return network_preference.has_value() && (network_type == network_preference);
+}
+
+int CompareCandidatePairsByNetworkPreference(
+    const cricket::Connection* a,
+    const cricket::Connection* b,
+    absl::optional<rtc::AdapterType> network_preference) {
+  bool a_uses_preferred_network =
+      LocalCandidateUsesPreferredNetwork(a, network_preference);
+  bool b_uses_preferred_network =
+      LocalCandidateUsesPreferredNetwork(b, network_preference);
+  if (a_uses_preferred_network && !b_uses_preferred_network) {
+    return a_is_better;
+  } else if (!a_uses_preferred_network && b_uses_preferred_network) {
+    return b_is_better;
+  }
+  return a_and_b_equal;
+}
+
+}  // namespace
+
+namespace cricket {
+
+BasicIceController::BasicIceController(
+    std::function<IceTransportState()> ice_transport_state_func,
+    std::function<IceRole()> ice_role_func,
+    std::function<bool(const Connection*)> is_connection_pruned_func,
+    const IceFieldTrials* field_trials)
+    : ice_transport_state_func_(ice_transport_state_func),
+      ice_role_func_(ice_role_func),
+      is_connection_pruned_func_(is_connection_pruned_func),
+      field_trials_(field_trials) {}
+
+BasicIceController::~BasicIceController() {}
+
+void BasicIceController::SetIceConfig(const IceConfig& config) {
+  config_ = config;
+}
+
+void BasicIceController::SetSelectedConnection(
+    const Connection* selected_connection) {
+  selected_connection_ = selected_connection;
+}
+
+void BasicIceController::AddConnection(const Connection* connection) {
+  connections_.push_back(connection);
+  unpinged_connections_.insert(connection);
+}
+
+void BasicIceController::OnConnectionDestroyed(const Connection* connection) {
+  pinged_connections_.erase(connection);
+  unpinged_connections_.erase(connection);
+  connections_.erase(absl::c_find(connections_, connection));
+}
+
+bool BasicIceController::HasPingableConnection() const {
+  int64_t now = rtc::TimeMillis();
+  return absl::c_any_of(connections_, [this, now](const Connection* c) {
+    return IsPingable(c, now);
+  });
+}
+
+std::pair<Connection*, int> BasicIceController::SelectConnectionToPing(
+    int64_t last_ping_sent_ms) {
+  // When the selected connection is not receiving or not writable, or any
+  // active connection has not been pinged enough times, use the weak ping
+  // interval.
+  bool need_more_pings_at_weak_interval =
+      absl::c_any_of(connections_, [](const Connection* conn) {
+        return conn->active() &&
+               conn->num_pings_sent() < MIN_PINGS_AT_WEAK_PING_INTERVAL;
+      });
+  int ping_interval = (weak() || need_more_pings_at_weak_interval)
+                          ? weak_ping_interval()
+                          : strong_ping_interval();
+
+  const Connection* conn = nullptr;
+  if (rtc::TimeMillis() >= last_ping_sent_ms + ping_interval) {
+    conn = FindNextPingableConnection();
+  }
+  int delay = std::min(ping_interval, check_receiving_interval());
+  return std::make_pair(const_cast<Connection*>(conn), delay);
+}
+
+void BasicIceController::MarkConnectionPinged(const Connection* conn) {
+  if (conn && pinged_connections_.insert(conn).second) {
+    unpinged_connections_.erase(conn);
+  }
+}
+
+// Returns the next pingable connection to ping.
+const Connection* BasicIceController::FindNextPingableConnection() {
+  int64_t now = rtc::TimeMillis();
+
+  // Rule 1: Selected connection takes priority over non-selected ones.
+  if (selected_connection_ && selected_connection_->connected() &&
+      selected_connection_->writable() &&
+      WritableConnectionPastPingInterval(selected_connection_, now)) {
+    return selected_connection_;
+  }
+
+  // Rule 2: If the channel is weak, we need to find a new writable and
+  // receiving connection, probably on a different network. If there are lots of
+  // connections, it may take several seconds between two pings for every
+  // non-selected connection. This will cause the receiving state of those
+  // connections to be false, and thus they won't be selected. This is
+  // problematic for network fail-over. We want to make sure at least one
+  // connection per network is pinged frequently enough in order for it to be
+  // selectable. So we prioritize one connection per network.
+  // Rule 2.1: Among such connections, pick the one with the earliest
+  // last-ping-sent time.
+  if (weak()) {
+    std::vector<const Connection*> pingable_selectable_connections;
+    absl::c_copy_if(GetBestWritableConnectionPerNetwork(),
+                    std::back_inserter(pingable_selectable_connections),
+                    [this, now](const Connection* conn) {
+                      return WritableConnectionPastPingInterval(conn, now);
+                    });
+    auto iter = absl::c_min_element(
+        pingable_selectable_connections,
+        [](const Connection* conn1, const Connection* conn2) {
+          return conn1->last_ping_sent() < conn2->last_ping_sent();
+        });
+    if (iter != pingable_selectable_connections.end()) {
+      return *iter;
+    }
+  }
+
+  // Rule 3: Triggered checks have priority over non-triggered connections.
+  // Rule 3.1: Among triggered checks, oldest takes precedence.
+  const Connection* oldest_triggered_check =
+      FindOldestConnectionNeedingTriggeredCheck(now);
+  if (oldest_triggered_check) {
+    return oldest_triggered_check;
+  }
+
+  // Rule 4: Unpinged connections have priority over pinged ones.
+  RTC_CHECK(connections_.size() ==
+            pinged_connections_.size() + unpinged_connections_.size());
+  // If there are unpinged and pingable connections, only ping those.
+  // Otherwise, treat everything as unpinged.
+  // TODO(honghaiz): Instead of adding two separate vectors, we can add a state
+  // "pinged" to filter out unpinged connections.
+  if (absl::c_none_of(unpinged_connections_,
+                      [this, now](const Connection* conn) {
+                        return this->IsPingable(conn, now);
+                      })) {
+    unpinged_connections_.insert(pinged_connections_.begin(),
+                                 pinged_connections_.end());
+    pinged_connections_.clear();
+  }
+
+  // Among un-pinged pingable connections, "more pingable" takes precedence.
+  std::vector<const Connection*> pingable_connections;
+  absl::c_copy_if(
+      unpinged_connections_, std::back_inserter(pingable_connections),
+      [this, now](const Connection* conn) { return IsPingable(conn, now); });
+  auto iter = absl::c_max_element(
+      pingable_connections,
+      [this](const Connection* conn1, const Connection* conn2) {
+        // Some implementations of max_element
+        // compare an element with itself.
+        if (conn1 == conn2) {
+          return false;
+        }
+        return MorePingable(conn1, conn2) == conn2;
+      });
+  if (iter != pingable_connections.end()) {
+    return *iter;
+  }
+  return nullptr;
+}
+
+// Find "triggered checks".  We ping first those connections that have
+// received a ping but have not sent a ping since receiving it
+// (last_ping_received > last_ping_sent).  But we shouldn't do
+// triggered checks if the connection is already writable.
+const Connection* BasicIceController::FindOldestConnectionNeedingTriggeredCheck(
+    int64_t now) {
+  const Connection* oldest_needing_triggered_check = nullptr;
+  for (auto* conn : connections_) {
+    if (!IsPingable(conn, now)) {
+      continue;
+    }
+    bool needs_triggered_check =
+        (!conn->writable() &&
+         conn->last_ping_received() > conn->last_ping_sent());
+    if (needs_triggered_check &&
+        (!oldest_needing_triggered_check ||
+         (conn->last_ping_received() <
+          oldest_needing_triggered_check->last_ping_received()))) {
+      oldest_needing_triggered_check = conn;
+    }
+  }
+
+  if (oldest_needing_triggered_check) {
+    RTC_LOG(LS_INFO) << "Selecting connection for triggered check: "
+                     << oldest_needing_triggered_check->ToString();
+  }
+  return oldest_needing_triggered_check;
+}
+
+bool BasicIceController::WritableConnectionPastPingInterval(
+    const Connection* conn,
+    int64_t now) const {
+  int interval = CalculateActiveWritablePingInterval(conn, now);
+  return conn->last_ping_sent() + interval <= now;
+}
+
+int BasicIceController::CalculateActiveWritablePingInterval(
+    const Connection* conn,
+    int64_t now) const {
+  // Ping each connection at a higher rate at least
+  // MIN_PINGS_AT_WEAK_PING_INTERVAL times.
+  if (conn->num_pings_sent() < MIN_PINGS_AT_WEAK_PING_INTERVAL) {
+    return weak_ping_interval();
+  }
+
+  int stable_interval =
+      config_.stable_writable_connection_ping_interval_or_default();
+  int weak_or_stablizing_interval = std::min(
+      stable_interval, WEAK_OR_STABILIZING_WRITABLE_CONNECTION_PING_INTERVAL);
+  // If the channel is weak or the connection is not stable yet, use the
+  // weak_or_stablizing_interval.
+  return (!weak() && conn->stable(now)) ? stable_interval
+                                        : weak_or_stablizing_interval;
+}
+
+// Is the connection in a state for us to even consider pinging the other side?
+// We consider a connection pingable even if it's not connected because that's
+// how a TCP connection is kicked into reconnecting on the active side.
+bool BasicIceController::IsPingable(const Connection* conn, int64_t now) const {
+  const Candidate& remote = conn->remote_candidate();
+  // We should never get this far with an empty remote ufrag.
+  RTC_DCHECK(!remote.username().empty());
+  if (remote.username().empty() || remote.password().empty()) {
+    // If we don't have an ICE ufrag and pwd, there's no way we can ping.
+    return false;
+  }
+
+  // A failed connection will not be pinged.
+  if (conn->state() == IceCandidatePairState::FAILED) {
+    return false;
+  }
+
+  // An never connected connection cannot be written to at all, so pinging is
+  // out of the question. However, if it has become WRITABLE, it is in the
+  // reconnecting state so ping is needed.
+  if (!conn->connected() && !conn->writable()) {
+    return false;
+  }
+
+  // If we sent a number of pings wo/ reply, skip sending more
+  // until we get one.
+  if (conn->TooManyOutstandingPings(field_trials_->max_outstanding_pings)) {
+    return false;
+  }
+
+  // If the channel is weakly connected, ping all connections.
+  if (weak()) {
+    return true;
+  }
+
+  // Always ping active connections regardless whether the channel is completed
+  // or not, but backup connections are pinged at a slower rate.
+  if (IsBackupConnection(conn)) {
+    return conn->rtt_samples() == 0 ||
+           (now >= conn->last_ping_response_received() +
+                       config_.backup_connection_ping_interval_or_default());
+  }
+  // Don't ping inactive non-backup connections.
+  if (!conn->active()) {
+    return false;
+  }
+
+  // Do ping unwritable, active connections.
+  if (!conn->writable()) {
+    return true;
+  }
+
+  // Ping writable, active connections if it's been long enough since the last
+  // ping.
+  return WritableConnectionPastPingInterval(conn, now);
+}
+
+// A connection is considered a backup connection if the channel state
+// is completed, the connection is not the selected connection and it is active.
+bool BasicIceController::IsBackupConnection(const Connection* conn) const {
+  return ice_transport_state_func_() == IceTransportState::STATE_COMPLETED &&
+         conn != selected_connection_ && conn->active();
+}
+
+const Connection* BasicIceController::MorePingable(const Connection* conn1,
+                                                   const Connection* conn2) {
+  RTC_DCHECK(conn1 != conn2);
+  if (config_.prioritize_most_likely_candidate_pairs) {
+    const Connection* most_likely_to_work_conn = MostLikelyToWork(conn1, conn2);
+    if (most_likely_to_work_conn) {
+      return most_likely_to_work_conn;
+    }
+  }
+
+  const Connection* least_recently_pinged_conn =
+      LeastRecentlyPinged(conn1, conn2);
+  if (least_recently_pinged_conn) {
+    return least_recently_pinged_conn;
+  }
+
+  // During the initial state when nothing has been pinged yet, return the first
+  // one in the ordered |connections_|.
+  auto connections = connections_;
+  return *(std::find_if(connections.begin(), connections.end(),
+                        [conn1, conn2](const Connection* conn) {
+                          return conn == conn1 || conn == conn2;
+                        }));
+}
+
+const Connection* BasicIceController::MostLikelyToWork(
+    const Connection* conn1,
+    const Connection* conn2) {
+  bool rr1 = IsRelayRelay(conn1);
+  bool rr2 = IsRelayRelay(conn2);
+  if (rr1 && !rr2) {
+    return conn1;
+  } else if (rr2 && !rr1) {
+    return conn2;
+  } else if (rr1 && rr2) {
+    bool udp1 = IsUdp(conn1);
+    bool udp2 = IsUdp(conn2);
+    if (udp1 && !udp2) {
+      return conn1;
+    } else if (udp2 && udp1) {
+      return conn2;
+    }
+  }
+  return nullptr;
+}
+
+const Connection* BasicIceController::LeastRecentlyPinged(
+    const Connection* conn1,
+    const Connection* conn2) {
+  if (conn1->last_ping_sent() < conn2->last_ping_sent()) {
+    return conn1;
+  }
+  if (conn1->last_ping_sent() > conn2->last_ping_sent()) {
+    return conn2;
+  }
+  return nullptr;
+}
+
+std::map<rtc::Network*, const Connection*>
+BasicIceController::GetBestConnectionByNetwork() const {
+  // |connections_| has been sorted, so the first one in the list on a given
+  // network is the best connection on the network, except that the selected
+  // connection is always the best connection on the network.
+  std::map<rtc::Network*, const Connection*> best_connection_by_network;
+  if (selected_connection_) {
+    best_connection_by_network[selected_connection_->port()->Network()] =
+        selected_connection_;
+  }
+  // TODO(honghaiz): Need to update this if |connections_| are not sorted.
+  for (const Connection* conn : connections_) {
+    rtc::Network* network = conn->port()->Network();
+    // This only inserts when the network does not exist in the map.
+    best_connection_by_network.insert(std::make_pair(network, conn));
+  }
+  return best_connection_by_network;
+}
+
+std::vector<const Connection*>
+BasicIceController::GetBestWritableConnectionPerNetwork() const {
+  std::vector<const Connection*> connections;
+  for (auto kv : GetBestConnectionByNetwork()) {
+    const Connection* conn = kv.second;
+    if (conn->writable() && conn->connected()) {
+      connections.push_back(conn);
+    }
+  }
+  return connections;
+}
+
+IceControllerInterface::SwitchResult
+BasicIceController::HandleInitialSelectDampening(
+    IceControllerEvent reason,
+    const Connection* new_connection) {
+  if (!field_trials_->initial_select_dampening.has_value() &&
+      !field_trials_->initial_select_dampening_ping_received.has_value()) {
+    // experiment not enabled => select connection.
+    return {new_connection, absl::nullopt};
+  }
+
+  int64_t now = rtc::TimeMillis();
+  int64_t max_delay = 0;
+  if (new_connection->last_ping_received() > 0 &&
+      field_trials_->initial_select_dampening_ping_received.has_value()) {
+    max_delay = *field_trials_->initial_select_dampening_ping_received;
+  } else if (field_trials_->initial_select_dampening.has_value()) {
+    max_delay = *field_trials_->initial_select_dampening;
+  }
+
+  int64_t start_wait =
+      initial_select_timestamp_ms_ == 0 ? now : initial_select_timestamp_ms_;
+  int64_t max_wait_until = start_wait + max_delay;
+
+  if (now >= max_wait_until) {
+    RTC_LOG(LS_INFO) << "reset initial_select_timestamp_ = "
+                     << initial_select_timestamp_ms_
+                     << " selection delayed by: " << (now - start_wait) << "ms";
+    initial_select_timestamp_ms_ = 0;
+    return {new_connection, absl::nullopt};
+  }
+
+  // We are not yet ready to select first connection...
+  if (initial_select_timestamp_ms_ == 0) {
+    // Set timestamp on first time...
+    // but run the delayed invokation everytime to
+    // avoid possibility that we miss it.
+    initial_select_timestamp_ms_ = now;
+    RTC_LOG(LS_INFO) << "set initial_select_timestamp_ms_ = "
+                     << initial_select_timestamp_ms_;
+  }
+
+  int min_delay = max_delay;
+  if (field_trials_->initial_select_dampening.has_value()) {
+    min_delay = std::min(min_delay, *field_trials_->initial_select_dampening);
+  }
+  if (field_trials_->initial_select_dampening_ping_received.has_value()) {
+    min_delay = std::min(
+        min_delay, *field_trials_->initial_select_dampening_ping_received);
+  }
+
+  RTC_LOG(LS_INFO) << "delay initial selection up to " << min_delay << "ms";
+  return {absl::nullopt, min_delay};
+}
+
+IceControllerInterface::SwitchResult BasicIceController::ShouldSwitchConnection(
+    IceControllerEvent reason,
+    const Connection* new_connection) {
+  if (!ReadyToSend(new_connection) || selected_connection_ == new_connection) {
+    return {absl::nullopt, absl::nullopt};
+  }
+
+  if (selected_connection_ == nullptr) {
+    return HandleInitialSelectDampening(reason, new_connection);
+  }
+
+  // Do not switch to a connection that is not receiving if it is not on a
+  // preferred network or it has higher cost because it may be just spuriously
+  // better.
+  int compare_a_b_by_networks = CompareCandidatePairNetworks(
+      new_connection, selected_connection_, config_.network_preference);
+  if (compare_a_b_by_networks == b_is_better && !new_connection->receiving()) {
+    return {absl::nullopt, absl::nullopt};
+  }
+
+  bool missed_receiving_unchanged_threshold = false;
+  absl::optional<int64_t> receiving_unchanged_threshold(
+      rtc::TimeMillis() - config_.receiving_switching_delay_or_default());
+  int cmp = CompareConnections(selected_connection_, new_connection,
+                               receiving_unchanged_threshold,
+                               &missed_receiving_unchanged_threshold);
+
+  absl::optional<int> recheck_delay;
+  if (missed_receiving_unchanged_threshold &&
+      config_.receiving_switching_delay_or_default()) {
+    // If we do not switch to the connection because it missed the receiving
+    // threshold, the new connection is in a better receiving state than the
+    // currently selected connection. So we need to re-check whether it needs
+    // to be switched at a later time.
+    recheck_delay = config_.receiving_switching_delay_or_default();
+  }
+
+  if (cmp < 0) {
+    return {new_connection, absl::nullopt};
+  } else if (cmp > 0) {
+    return {absl::nullopt, recheck_delay};
+  }
+
+  // If everything else is the same, switch only if rtt has improved by
+  // a margin.
+  if (new_connection->rtt() <= selected_connection_->rtt() - kMinImprovement) {
+    return {new_connection, absl::nullopt};
+  }
+
+  return {absl::nullopt, recheck_delay};
+}
+
+IceControllerInterface::SwitchResult
+BasicIceController::SortAndSwitchConnection(IceControllerEvent reason) {
+  // Find the best alternative connection by sorting.  It is important to note
+  // that amongst equal preference, writable connections, this will choose the
+  // one whose estimated latency is lowest.  So it is the only one that we
+  // need to consider switching to.
+  // TODO(honghaiz): Don't sort;  Just use std::max_element in the right places.
+  absl::c_stable_sort(
+      connections_, [this](const Connection* a, const Connection* b) {
+        int cmp = CompareConnections(a, b, absl::nullopt, nullptr);
+        if (cmp != 0) {
+          return cmp > 0;
+        }
+        // Otherwise, sort based on latency estimate.
+        return a->rtt() < b->rtt();
+      });
+
+  RTC_LOG(LS_VERBOSE) << "Sorting " << connections_.size()
+                      << " available connections";
+  for (size_t i = 0; i < connections_.size(); ++i) {
+    RTC_LOG(LS_VERBOSE) << connections_[i]->ToString();
+  }
+
+  const Connection* top_connection =
+      (!connections_.empty()) ? connections_[0] : nullptr;
+
+  return ShouldSwitchConnection(reason, top_connection);
+}
+
+bool BasicIceController::ReadyToSend(const Connection* connection) const {
+  // Note that we allow sending on an unreliable connection, because it's
+  // possible that it became unreliable simply due to bad chance.
+  // So this shouldn't prevent attempting to send media.
+  return connection != nullptr &&
+         (connection->writable() ||
+          connection->write_state() == Connection::STATE_WRITE_UNRELIABLE ||
+          PresumedWritable(connection));
+}
+
+bool BasicIceController::PresumedWritable(const Connection* conn) const {
+  return (conn->write_state() == Connection::STATE_WRITE_INIT &&
+          config_.presume_writable_when_fully_relayed &&
+          conn->local_candidate().type() == RELAY_PORT_TYPE &&
+          (conn->remote_candidate().type() == RELAY_PORT_TYPE ||
+           conn->remote_candidate().type() == PRFLX_PORT_TYPE));
+}
+
+// Compare two connections based on their writing, receiving, and connected
+// states.
+int BasicIceController::CompareConnectionStates(
+    const Connection* a,
+    const Connection* b,
+    absl::optional<int64_t> receiving_unchanged_threshold,
+    bool* missed_receiving_unchanged_threshold) const {
+  // First, prefer a connection that's writable or presumed writable over
+  // one that's not writable.
+  bool a_writable = a->writable() || PresumedWritable(a);
+  bool b_writable = b->writable() || PresumedWritable(b);
+  if (a_writable && !b_writable) {
+    return a_is_better;
+  }
+  if (!a_writable && b_writable) {
+    return b_is_better;
+  }
+
+  // Sort based on write-state. Better states have lower values.
+  if (a->write_state() < b->write_state()) {
+    return a_is_better;
+  }
+  if (b->write_state() < a->write_state()) {
+    return b_is_better;
+  }
+
+  // We prefer a receiving connection to a non-receiving, higher-priority
+  // connection when sorting connections and choosing which connection to
+  // switch to.
+  if (a->receiving() && !b->receiving()) {
+    return a_is_better;
+  }
+  if (!a->receiving() && b->receiving()) {
+    if (!receiving_unchanged_threshold ||
+        (a->receiving_unchanged_since() <= *receiving_unchanged_threshold &&
+         b->receiving_unchanged_since() <= *receiving_unchanged_threshold)) {
+      return b_is_better;
+    }
+    *missed_receiving_unchanged_threshold = true;
+  }
+
+  // WARNING: Some complexity here about TCP reconnecting.
+  // When a TCP connection fails because of a TCP socket disconnecting, the
+  // active side of the connection will attempt to reconnect for 5 seconds while
+  // pretending to be writable (the connection is not set to the unwritable
+  // state).  On the passive side, the connection also remains writable even
+  // though it is disconnected, and a new connection is created when the active
+  // side connects.  At that point, there are two TCP connections on the passive
+  // side: 1. the old, disconnected one that is pretending to be writable, and
+  // 2.  the new, connected one that is maybe not yet writable.  For purposes of
+  // pruning, pinging, and selecting the selected connection, we want to treat
+  // the new connection as "better" than the old one. We could add a method
+  // called something like Connection::ImReallyBadEvenThoughImWritable, but that
+  // is equivalent to the existing Connection::connected(), which we already
+  // have. So, in code throughout this file, we'll check whether the connection
+  // is connected() or not, and if it is not, treat it as "worse" than a
+  // connected one, even though it's writable.  In the code below, we're doing
+  // so to make sure we treat a new writable connection as better than an old
+  // disconnected connection.
+
+  // In the case where we reconnect TCP connections, the original best
+  // connection is disconnected without changing to WRITE_TIMEOUT. In this case,
+  // the new connection, when it becomes writable, should have higher priority.
+  if (a->write_state() == Connection::STATE_WRITABLE &&
+      b->write_state() == Connection::STATE_WRITABLE) {
+    if (a->connected() && !b->connected()) {
+      return a_is_better;
+    }
+    if (!a->connected() && b->connected()) {
+      return b_is_better;
+    }
+  }
+
+  return 0;
+}
+
+// Compares two connections based only on the candidate and network information.
+// Returns positive if |a| is better than |b|.
+int BasicIceController::CompareConnectionCandidates(const Connection* a,
+                                                    const Connection* b) const {
+  int compare_a_b_by_networks =
+      CompareCandidatePairNetworks(a, b, config_.network_preference);
+  if (compare_a_b_by_networks != a_and_b_equal) {
+    return compare_a_b_by_networks;
+  }
+
+  // Compare connection priority. Lower values get sorted last.
+  if (a->priority() > b->priority()) {
+    return a_is_better;
+  }
+  if (a->priority() < b->priority()) {
+    return b_is_better;
+  }
+
+  // If we're still tied at this point, prefer a younger generation.
+  // (Younger generation means a larger generation number).
+  int cmp = (a->remote_candidate().generation() + a->port()->generation()) -
+            (b->remote_candidate().generation() + b->port()->generation());
+  if (cmp != 0) {
+    return cmp;
+  }
+
+  // A periodic regather (triggered by the regather_all_networks_interval_range)
+  // will produce candidates that appear the same but would use a new port. We
+  // want to use the new candidates and purge the old candidates as they come
+  // in, so use the fact that the old ports get pruned immediately to rank the
+  // candidates with an active port/remote candidate higher.
+  bool a_pruned = is_connection_pruned_func_(a);
+  bool b_pruned = is_connection_pruned_func_(b);
+  if (!a_pruned && b_pruned) {
+    return a_is_better;
+  }
+  if (a_pruned && !b_pruned) {
+    return b_is_better;
+  }
+
+  // Otherwise, must be equal
+  return 0;
+}
+
+int BasicIceController::CompareConnections(
+    const Connection* a,
+    const Connection* b,
+    absl::optional<int64_t> receiving_unchanged_threshold,
+    bool* missed_receiving_unchanged_threshold) const {
+  RTC_CHECK(a != nullptr);
+  RTC_CHECK(b != nullptr);
+
+  // We prefer to switch to a writable and receiving connection over a
+  // non-writable or non-receiving connection, even if the latter has
+  // been nominated by the controlling side.
+  int state_cmp = CompareConnectionStates(a, b, receiving_unchanged_threshold,
+                                          missed_receiving_unchanged_threshold);
+  if (state_cmp != 0) {
+    return state_cmp;
+  }
+
+  if (ice_role_func_() == ICEROLE_CONTROLLED) {
+    // Compare the connections based on the nomination states and the last data
+    // received time if this is on the controlled side.
+    if (a->remote_nomination() > b->remote_nomination()) {
+      return a_is_better;
+    }
+    if (a->remote_nomination() < b->remote_nomination()) {
+      return b_is_better;
+    }
+
+    if (a->last_data_received() > b->last_data_received()) {
+      return a_is_better;
+    }
+    if (a->last_data_received() < b->last_data_received()) {
+      return b_is_better;
+    }
+  }
+
+  // Compare the network cost and priority.
+  return CompareConnectionCandidates(a, b);
+}
+
+int BasicIceController::CompareCandidatePairNetworks(
+    const Connection* a,
+    const Connection* b,
+    absl::optional<rtc::AdapterType> network_preference) const {
+  int compare_a_b_by_network_preference =
+      CompareCandidatePairsByNetworkPreference(a, b,
+                                               config_.network_preference);
+  // The network preference has a higher precedence than the network cost.
+  if (compare_a_b_by_network_preference != a_and_b_equal) {
+    return compare_a_b_by_network_preference;
+  }
+
+  uint32_t a_cost = a->ComputeNetworkCost();
+  uint32_t b_cost = b->ComputeNetworkCost();
+  // Prefer lower network cost.
+  if (a_cost < b_cost) {
+    return a_is_better;
+  }
+  if (a_cost > b_cost) {
+    return b_is_better;
+  }
+  return a_and_b_equal;
+}
+
+std::vector<const Connection*> BasicIceController::PruneConnections() {
+  // We can prune any connection for which there is a connected, writable
+  // connection on the same network with better or equal priority.  We leave
+  // those with better priority just in case they become writable later (at
+  // which point, we would prune out the current selected connection).  We leave
+  // connections on other networks because they may not be using the same
+  // resources and they may represent very distinct paths over which we can
+  // switch. If |best_conn_on_network| is not connected, we may be reconnecting
+  // a TCP connection and should not prune connections in this network.
+  // See the big comment in CompareConnectionStates.
+  //
+  // An exception is made for connections on an "any address" network, meaning
+  // not bound to any specific network interface. We don't want to keep one of
+  // these alive as a backup, since it could be using the same network
+  // interface as the higher-priority, selected candidate pair.
+  std::vector<const Connection*> connections_to_prune;
+  auto best_connection_by_network = GetBestConnectionByNetwork();
+  for (const Connection* conn : connections_) {
+    const Connection* best_conn = selected_connection_;
+    if (!rtc::IPIsAny(conn->port()->Network()->ip())) {
+      // If the connection is bound to a specific network interface (not an
+      // "any address" network), compare it against the best connection for
+      // that network interface rather than the best connection overall. This
+      // ensures that at least one connection per network will be left
+      // unpruned.
+      best_conn = best_connection_by_network[conn->port()->Network()];
+    }
+    // Do not prune connections if the connection being compared against is
+    // weak. Otherwise, it may delete connections prematurely.
+    if (best_conn && conn != best_conn && !best_conn->weak() &&
+        CompareConnectionCandidates(best_conn, conn) >= 0) {
+      connections_to_prune.push_back(conn);
+    }
+  }
+  return connections_to_prune;
+}
+
+bool BasicIceController::GetUseCandidateAttr(const Connection* conn,
+                                             NominationMode mode,
+                                             IceMode remote_ice_mode) const {
+  switch (mode) {
+    case NominationMode::REGULAR:
+      // TODO(honghaiz): Implement regular nomination.
+      return false;
+    case NominationMode::AGGRESSIVE:
+      if (remote_ice_mode == ICEMODE_LITE) {
+        return GetUseCandidateAttr(conn, NominationMode::REGULAR,
+                                   remote_ice_mode);
+      }
+      return true;
+    case NominationMode::SEMI_AGGRESSIVE: {
+      // Nominate if
+      // a) Remote is in FULL ICE AND
+      //    a.1) |conn| is the selected connection OR
+      //    a.2) there is no selected connection OR
+      //    a.3) the selected connection is unwritable OR
+      //    a.4) |conn| has higher priority than selected_connection.
+      // b) Remote is in LITE ICE AND
+      //    b.1) |conn| is the selected_connection AND
+      //    b.2) |conn| is writable.
+      bool selected = conn == selected_connection_;
+      if (remote_ice_mode == ICEMODE_LITE) {
+        return selected && conn->writable();
+      }
+      bool better_than_selected =
+          !selected_connection_ || !selected_connection_->writable() ||
+          CompareConnectionCandidates(selected_connection_, conn) < 0;
+      return selected || better_than_selected;
+    }
+    default:
+      RTC_NOTREACHED();
+      return false;
+  }
+}
+
+}  // namespace cricket
diff --git a/p2p/base/basic_ice_controller.h b/p2p/base/basic_ice_controller.h
new file mode 100644
index 0000000..5335c00
--- /dev/null
+++ b/p2p/base/basic_ice_controller.h
@@ -0,0 +1,167 @@
+/*
+ *  Copyright 2019 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_BASIC_ICE_CONTROLLER_H_
+#define P2P_BASE_BASIC_ICE_CONTROLLER_H_
+
+#include <algorithm>
+#include <map>
+#include <set>
+#include <utility>
+#include <vector>
+
+#include "p2p/base/ice_controller_interface.h"
+#include "p2p/base/p2p_transport_channel.h"
+
+namespace cricket {
+
+class BasicIceController : public IceControllerInterface {
+ public:
+  BasicIceController(
+      std::function<IceTransportState()> ice_transport_state_func,
+      std::function<IceRole()> ice_role_func,
+      std::function<bool(const Connection*)> is_candidated_pruned_func,
+      const IceFieldTrials*);
+  virtual ~BasicIceController();
+
+  void SetIceConfig(const IceConfig& config) override;
+  void SetSelectedConnection(const Connection* selected_connection) override;
+  void AddConnection(const Connection* connection) override;
+  void OnConnectionDestroyed(const Connection* connection) override;
+  rtc::ArrayView<const Connection*> connections() const override {
+    return rtc::ArrayView<const Connection*>(
+        const_cast<const Connection**>(connections_.data()),
+        connections_.size());
+  }
+
+  bool HasPingableConnection() const override;
+
+  std::pair<Connection*, int> SelectConnectionToPing(
+      int64_t last_ping_sent_ms) override;
+  bool GetUseCandidateAttr(const Connection* conn,
+                           NominationMode mode,
+                           IceMode remote_ice_mode) const override;
+
+  SwitchResult ShouldSwitchConnection(IceControllerEvent reason,
+                                      const Connection* connection) override;
+  SwitchResult SortAndSwitchConnection(IceControllerEvent reason) override;
+
+  std::vector<const Connection*> PruneConnections() override;
+
+  // These methods are only for tests.
+  const Connection* FindNextPingableConnection() override;
+  void MarkConnectionPinged(const Connection* conn) override;
+
+ private:
+  // A transport channel is weak if the current best connection is either
+  // not receiving or not writable, or if there is no best connection at all.
+  bool weak() const {
+    return !selected_connection_ || selected_connection_->weak();
+  }
+
+  int weak_ping_interval() const {
+    return std::max(config_.ice_check_interval_weak_connectivity_or_default(),
+                    config_.ice_check_min_interval_or_default());
+  }
+
+  int strong_ping_interval() const {
+    return std::max(config_.ice_check_interval_strong_connectivity_or_default(),
+                    config_.ice_check_min_interval_or_default());
+  }
+
+  int check_receiving_interval() const {
+    return std::max(MIN_CHECK_RECEIVING_INTERVAL,
+                    config_.receiving_timeout_or_default() / 10);
+  }
+
+  const Connection* FindOldestConnectionNeedingTriggeredCheck(int64_t now);
+  // Between |conn1| and |conn2|, this function returns the one which should
+  // be pinged first.
+  const Connection* MorePingable(const Connection* conn1,
+                                 const Connection* conn2);
+  // Select the connection which is Relay/Relay. If both of them are,
+  // UDP relay protocol takes precedence.
+  const Connection* MostLikelyToWork(const Connection* conn1,
+                                     const Connection* conn2);
+  // Compare the last_ping_sent time and return the one least recently pinged.
+  const Connection* LeastRecentlyPinged(const Connection* conn1,
+                                        const Connection* conn2);
+
+  bool IsPingable(const Connection* conn, int64_t now) const;
+  bool IsBackupConnection(const Connection* conn) const;
+  // Whether a writable connection is past its ping interval and needs to be
+  // pinged again.
+  bool WritableConnectionPastPingInterval(const Connection* conn,
+                                          int64_t now) const;
+  int CalculateActiveWritablePingInterval(const Connection* conn,
+                                          int64_t now) const;
+
+  std::map<rtc::Network*, const Connection*> GetBestConnectionByNetwork() const;
+  std::vector<const Connection*> GetBestWritableConnectionPerNetwork() const;
+
+  bool ReadyToSend(const Connection* connection) const;
+  bool PresumedWritable(const Connection* conn) const;
+
+  int CompareCandidatePairNetworks(
+      const Connection* a,
+      const Connection* b,
+      absl::optional<rtc::AdapterType> network_preference) const;
+
+  // The methods below return a positive value if |a| is preferable to |b|,
+  // a negative value if |b| is preferable, and 0 if they're equally preferable.
+  // If |receiving_unchanged_threshold| is set, then when |b| is receiving and
+  // |a| is not, returns a negative value only if |b| has been in receiving
+  // state and |a| has been in not receiving state since
+  // |receiving_unchanged_threshold| and sets
+  // |missed_receiving_unchanged_threshold| to true otherwise.
+  int CompareConnectionStates(
+      const Connection* a,
+      const Connection* b,
+      absl::optional<int64_t> receiving_unchanged_threshold,
+      bool* missed_receiving_unchanged_threshold) const;
+  int CompareConnectionCandidates(const Connection* a,
+                                  const Connection* b) const;
+  // Compares two connections based on the connection states
+  // (writable/receiving/connected), nomination states, last data received time,
+  // and static preferences. Does not include latency. Used by both sorting
+  // and ShouldSwitchSelectedConnection().
+  // Returns a positive value if |a| is better than |b|.
+  int CompareConnections(const Connection* a,
+                         const Connection* b,
+                         absl::optional<int64_t> receiving_unchanged_threshold,
+                         bool* missed_receiving_unchanged_threshold) const;
+
+  SwitchResult HandleInitialSelectDampening(IceControllerEvent reason,
+                                            const Connection* new_connection);
+
+  std::function<IceTransportState()> ice_transport_state_func_;
+  std::function<IceRole()> ice_role_func_;
+  std::function<bool(const Connection*)> is_connection_pruned_func_;
+
+  IceConfig config_;
+  const IceFieldTrials* field_trials_;
+
+  // |connections_| is a sorted list with the first one always be the
+  // |selected_connection_| when it's not nullptr. The combination of
+  // |pinged_connections_| and |unpinged_connections_| has the same
+  // connections as |connections_|. These 2 sets maintain whether a
+  // connection should be pinged next or not.
+  const Connection* selected_connection_ = nullptr;
+  std::vector<const Connection*> connections_;
+  std::set<const Connection*> pinged_connections_;
+  std::set<const Connection*> unpinged_connections_;
+
+  // Timestamp for when we got the first selectable connection.
+  int64_t initial_select_timestamp_ms_ = 0;
+};
+
+}  // namespace cricket
+
+#endif  // P2P_BASE_BASIC_ICE_CONTROLLER_H_
diff --git a/p2p/base/ice_controller_interface.cc b/p2p/base/ice_controller_interface.cc
new file mode 100644
index 0000000..6c93012
--- /dev/null
+++ b/p2p/base/ice_controller_interface.cc
@@ -0,0 +1,55 @@
+/*
+ *  Copyright 2019 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.
+ */
+
+#include "p2p/base/ice_controller_interface.h"
+
+#include <string>
+
+namespace cricket {
+
+std::string IceControllerEvent::ToString() const {
+  std::string reason;
+  switch (type) {
+    case REMOTE_CANDIDATE_GENERATION_CHANGE:
+      reason = "remote candidate generation maybe changed";
+      break;
+    case NETWORK_PREFERENCE_CHANGE:
+      reason = "network preference changed";
+      break;
+    case NEW_CONNECTION_FROM_LOCAL_CANDIDATE:
+      reason = "new candidate pairs created from a new local candidate";
+      break;
+    case NEW_CONNECTION_FROM_REMOTE_CANDIDATE:
+      reason = "new candidate pairs created from a new remote candidate";
+      break;
+    case NEW_CONNECTION_FROM_UNKNOWN_REMOTE_ADDRESS:
+      reason = "a new candidate pair created from an unknown remote address";
+      break;
+    case NOMINATION_ON_CONTROLLED_SIDE:
+      reason = "nomination on the controlled side";
+      break;
+    case DATA_RECEIVED:
+      reason = "data received";
+      break;
+    case CONNECT_STATE_CHANGE:
+      reason = "candidate pair state changed";
+      break;
+    case SELECTED_CONNECTION_DESTROYED:
+      reason = "selected candidate pair destroyed";
+      break;
+  }
+  if (dampening_delay) {
+    reason += " (after switching dampening interval: " +
+              std::to_string(dampening_delay) + ")";
+  }
+  return reason;
+}
+
+}  // namespace cricket
diff --git a/p2p/base/ice_controller_interface.h b/p2p/base/ice_controller_interface.h
new file mode 100644
index 0000000..4f8dc72
--- /dev/null
+++ b/p2p/base/ice_controller_interface.h
@@ -0,0 +1,117 @@
+/*
+ *  Copyright 2019 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_CONTROLLER_INTERFACE_H_
+#define P2P_BASE_ICE_CONTROLLER_INTERFACE_H_
+
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "p2p/base/connection.h"
+#include "p2p/base/ice_transport_internal.h"
+
+namespace cricket {
+
+struct IceFieldTrials;  // Forward declaration to avoid circular dependency.
+
+struct IceControllerEvent {
+  enum Type {
+    REMOTE_CANDIDATE_GENERATION_CHANGE,
+    NETWORK_PREFERENCE_CHANGE,
+    NEW_CONNECTION_FROM_LOCAL_CANDIDATE,
+    NEW_CONNECTION_FROM_REMOTE_CANDIDATE,
+    NEW_CONNECTION_FROM_UNKNOWN_REMOTE_ADDRESS,
+    NOMINATION_ON_CONTROLLED_SIDE,
+    DATA_RECEIVED,
+    CONNECT_STATE_CHANGE,
+    SELECTED_CONNECTION_DESTROYED
+  };
+
+  IceControllerEvent(const Type& _type)  // NOLINT: runtime/explicit
+      : type(_type) {}
+  std::string ToString() const;
+
+  Type type;
+  int dampening_delay = 0;
+};
+
+// Defines the interface for a module that control
+// - which connection to ping
+// - which connection to use
+// - which connection to prune
+//
+// P2PTransportChannel creates a |Connection| and adds a const pointer
+// to the IceController using |AddConnection|, i.e the IceController
+// should not call any non-const methods on a Connection.
+//
+// The IceController shall keeps track of all connections added
+// (and not destroyed) and give them back using the connections()-function-
+//
+// When a Connection gets destroyed
+// - signals on Connection::SignalDestroyed
+// - P2PTransportChannel calls IceController::OnConnectionDestroyed
+class IceControllerInterface {
+ public:
+  // This represents the result of a switch call.
+  struct SwitchResult {
+    // Connection that we should (optionally) switch to.
+    absl::optional<const Connection*> connection;
+
+    // Delay in milliseconds when we should resort and try switching again.
+    absl::optional<int> recheck_delay_ms;
+  };
+
+  virtual ~IceControllerInterface() = default;
+
+  // These setters are called when the state of P2PTransportChannel is mutated.
+  virtual void SetIceConfig(const IceConfig& config) = 0;
+  virtual void SetSelectedConnection(const Connection* selected_connection) = 0;
+  virtual void AddConnection(const Connection* connection) = 0;
+  virtual void OnConnectionDestroyed(const Connection* connection) = 0;
+
+  // These are all connections that has been added and not destroyed.
+  virtual rtc::ArrayView<const Connection*> connections() const = 0;
+
+  // Is there a pingable connection ?
+  // This function is used to boot-strap pinging, after this returns true
+  // SelectConnectionToPing() will be called periodically.
+  virtual bool HasPingableConnection() const = 0;
+
+  // Select a connection to Ping, or nullptr if none.
+  virtual std::pair<Connection*, int> SelectConnectionToPing(
+      int64_t last_ping_sent_ms) = 0;
+
+  // Compute the "STUN_ATTR_USE_CANDIDATE" for |conn|.
+  virtual bool GetUseCandidateAttr(const Connection* conn,
+                                   NominationMode mode,
+                                   IceMode remote_ice_mode) const = 0;
+
+  // These methods is only added to not have to change all unit tests
+  // that simulate pinging by marking a connection pinged.
+  virtual const Connection* FindNextPingableConnection() = 0;
+  virtual void MarkConnectionPinged(const Connection* con) = 0;
+
+  // Check if we should switch to |connection|.
+  // This method is called for IceControllerEvent's that can switch directly
+  // i.e without resorting.
+  virtual SwitchResult ShouldSwitchConnection(IceControllerEvent reason,
+                                              const Connection* connection) = 0;
+
+  // Sort connections and check if we should switch.
+  virtual SwitchResult SortAndSwitchConnection(IceControllerEvent reason) = 0;
+
+  // Prune connections.
+  virtual std::vector<const Connection*> PruneConnections() = 0;
+};
+
+}  // namespace cricket
+
+#endif  // P2P_BASE_ICE_CONTROLLER_INTERFACE_H_
diff --git a/p2p/base/p2p_transport_channel.cc b/p2p/base/p2p_transport_channel.cc
index 7f2b37a..b5f57dc 100644
--- a/p2p/base/p2p_transport_channel.cc
+++ b/p2p/base/p2p_transport_channel.cc
@@ -19,6 +19,7 @@
 #include "absl/strings/match.h"
 #include "api/candidate.h"
 #include "logging/rtc_event_log/ice_logger.h"
+#include "p2p/base/basic_ice_controller.h"
 #include "p2p/base/candidate_pair_interface.h"
 #include "p2p/base/connection.h"
 #include "p2p/base/port.h"
@@ -35,18 +36,6 @@
 
 namespace {
 
-// The minimum improvement in RTT that justifies a switch.
-const int kMinImprovement = 10;
-
-bool IsRelayRelay(const cricket::Connection* conn) {
-  return conn->local_candidate().type() == cricket::RELAY_PORT_TYPE &&
-         conn->remote_candidate().type() == cricket::RELAY_PORT_TYPE;
-}
-
-bool IsUdp(cricket::Connection* conn) {
-  return conn->local_candidate().relay_protocol() == cricket::UDP_PROTOCOL_NAME;
-}
-
 cricket::PortInterface::CandidateOrigin GetOrigin(
     cricket::PortInterface* port,
     cricket::PortInterface* origin_port) {
@@ -58,35 +47,6 @@
     return cricket::PortInterface::ORIGIN_OTHER_PORT;
 }
 
-// TODO(qingsi) Use an enum to replace the following constants for all
-// comparision results.
-static constexpr int a_is_better = 1;
-static constexpr int b_is_better = -1;
-static constexpr int a_and_b_equal = 0;
-
-bool LocalCandidateUsesPreferredNetwork(
-    const cricket::Connection* conn,
-    absl::optional<rtc::AdapterType> network_preference) {
-  rtc::AdapterType network_type = conn->port()->Network()->type();
-  return network_preference.has_value() && (network_type == network_preference);
-}
-
-int CompareCandidatePairsByNetworkPreference(
-    const cricket::Connection* a,
-    const cricket::Connection* b,
-    absl::optional<rtc::AdapterType> network_preference) {
-  bool a_uses_preferred_network =
-      LocalCandidateUsesPreferredNetwork(a, network_preference);
-  bool b_uses_preferred_network =
-      LocalCandidateUsesPreferredNetwork(b, network_preference);
-  if (a_uses_preferred_network && !b_uses_preferred_network) {
-    return a_is_better;
-  } else if (!a_uses_preferred_network && b_uses_preferred_network) {
-    return b_is_better;
-  }
-  return a_and_b_equal;
-}
-
 uint32_t GetWeakPingIntervalInFieldTrial() {
   uint32_t weak_ping_interval = ::strtoul(
       webrtc::field_trial::FindFullName("WebRTC-StunInterPacketDelay").c_str(),
@@ -162,9 +122,21 @@
   allocator_->SignalCandidateFilterChanged.connect(
       this, &P2PTransportChannel::OnCandidateFilterChanged);
   ice_event_log_.set_event_log(event_log);
+
+  ice_controller_ = std::make_unique<BasicIceController>(
+      [this] { return GetState(); }, [this] { return GetIceRole(); },
+      [this](const Connection* connection) {
+        return IsPortPruned(connection->port()) ||
+               IsRemoteCandidatePruned(connection->remote_candidate());
+      },
+      &field_trials_);
 }
 
 P2PTransportChannel::~P2PTransportChannel() {
+  std::vector<Connection*> copy(connections().begin(), connections().end());
+  for (Connection* con : copy) {
+    con->Destroy();
+  }
   for (auto& p : resolvers_) {
     p.resolver_->Destroy(false);
   }
@@ -203,8 +175,6 @@
 
 void P2PTransportChannel::AddConnection(Connection* connection) {
   RTC_DCHECK_RUN_ON(network_thread_);
-  connections_.push_back(connection);
-  unpinged_connections_.insert(connection);
   connection->set_remote_ice_mode(remote_ice_mode_);
   connection->set_receiving_timeout(config_.receiving_timeout);
   connection->set_unwritable_timeout(config_.ice_unwritable_timeout);
@@ -225,155 +195,44 @@
   connection->set_ice_event_log(&ice_event_log_);
   LogCandidatePairConfig(connection,
                          webrtc::IceCandidatePairConfigType::kAdded);
-}
 
-// Determines whether we should switch the selected connection to
-// |new_connection| based the writable/receiving state, the nomination state,
-// and the last data received time. This prevents the controlled side from
-// switching the selected connection too frequently when the controlling side
-// is doing aggressive nominations. The precedence of the connection switching
-// criteria is as follows:
-// i) write/receiving/connected states
-// ii) For controlled side,
-//        a) nomination state,
-//        b) last data received time.
-// iii) Lower cost / higher priority.
-// iv) rtt.
-// To further prevent switching to high-cost networks, does not switch to
-// a high-cost connection if it is not receiving.
-// TODO(honghaiz): Stop the aggressive nomination on the controlling side and
-// implement the ice-renomination option.
-bool P2PTransportChannel::ShouldSwitchSelectedConnection(
-    Connection* new_connection,
-    bool* missed_receiving_unchanged_threshold) const {
-  RTC_DCHECK_RUN_ON(network_thread_);
-  if (!ReadyToSend(new_connection) || selected_connection_ == new_connection) {
-    return false;
-  }
-
-  if (selected_connection_ == nullptr) {
-    return true;
-  }
-
-  // Do not switch to a connection that is not receiving if it is not on a
-  // preferred network or it has higher cost because it may be just spuriously
-  // better.
-  int compare_a_b_by_networks = CompareCandidatePairNetworks(
-      new_connection, selected_connection_, config_.network_preference);
-  if (compare_a_b_by_networks == b_is_better && !new_connection->receiving()) {
-    return false;
-  }
-
-  absl::optional<int64_t> receiving_unchanged_threshold(
-      rtc::TimeMillis() - config_.receiving_switching_delay_or_default());
-  int cmp = CompareConnections(selected_connection_, new_connection,
-                               receiving_unchanged_threshold,
-                               missed_receiving_unchanged_threshold);
-  if (cmp != 0) {
-    return cmp < 0;
-  }
-
-  // If everything else is the same, switch only if rtt has improved by
-  // a margin.
-  return new_connection->rtt() <= selected_connection_->rtt() - kMinImprovement;
-}
-
-bool P2PTransportChannel::HandleInitialSelectDampening(
-    Connection* new_connection,
-    const std::string& reason) {
-  RTC_DCHECK_RUN_ON(network_thread_);
-  if (!field_trials_.initial_select_dampening.has_value() &&
-      !field_trials_.initial_select_dampening_ping_received.has_value()) {
-    // experiment not enabled.
-    return true;
-  }
-
-  int64_t now = rtc::TimeMillis();
-  int64_t max_delay = 0;
-  if (new_connection->last_ping_received() > 0 &&
-      field_trials_.initial_select_dampening_ping_received.has_value()) {
-    max_delay = *field_trials_.initial_select_dampening_ping_received;
-  } else if (field_trials_.initial_select_dampening.has_value()) {
-    max_delay = *field_trials_.initial_select_dampening;
-  }
-
-  int64_t start_wait =
-      initial_select_timestamp_ms_ == 0 ? now : initial_select_timestamp_ms_;
-  int64_t max_wait_until = start_wait + max_delay;
-
-  if (now >= max_wait_until) {
-    RTC_LOG(LS_INFO) << "reset initial_select_timestamp_ = "
-                     << initial_select_timestamp_ms_
-                     << " selection delayed by: " << (now - start_wait) << "ms";
-    initial_select_timestamp_ms_ = 0;
-    return true;
-  }
-
-  // We are not yet ready to select first connection...
-  if (initial_select_timestamp_ms_ == 0) {
-    // Set timestamp on first time...
-    // but run the delayed invokation everytime to
-    // avoid possibility that we miss it.
-    initial_select_timestamp_ms_ = now;
-    RTC_LOG(LS_INFO) << "set initial_select_timestamp_ms_ = "
-                     << initial_select_timestamp_ms_;
-  }
-
-  int min_delay = max_delay;
-  if (field_trials_.initial_select_dampening.has_value()) {
-    min_delay = std::min(min_delay, *field_trials_.initial_select_dampening);
-  }
-  if (field_trials_.initial_select_dampening_ping_received.has_value()) {
-    min_delay = std::min(min_delay,
-                         *field_trials_.initial_select_dampening_ping_received);
-  }
-
-  const std::string reason_to_sort =
-      reason + " (after initial select dampening interval: " +
-      std::to_string(max_delay) + ")";
-  invoker_.AsyncInvokeDelayed<void>(
-      RTC_FROM_HERE, thread(),
-      rtc::Bind(&P2PTransportChannel::SortConnectionsAndUpdateState, this,
-                reason_to_sort),
-      min_delay);
-  RTC_LOG(LS_INFO) << "delay initial selection up to " << min_delay << "ms";
-  return false;
+  ice_controller_->AddConnection(connection);
 }
 
 bool P2PTransportChannel::MaybeSwitchSelectedConnection(
     Connection* new_connection,
-    const std::string& reason) {
+    IceControllerEvent reason) {
   RTC_DCHECK_RUN_ON(network_thread_);
 
-  if (selected_connection_ == nullptr && ReadyToSend(new_connection)) {
-    if (!HandleInitialSelectDampening(new_connection, reason)) {
-      // Delay the initial selection a while waiting for a better connection.
-      return false;
-    }
+  return MaybeSwitchSelectedConnection(
+      reason, ice_controller_->ShouldSwitchConnection(reason, new_connection));
+}
+
+bool P2PTransportChannel::MaybeSwitchSelectedConnection(
+    IceControllerEvent reason,
+    IceControllerInterface::SwitchResult result) {
+  RTC_DCHECK_RUN_ON(network_thread_);
+  if (result.connection.has_value()) {
+    RTC_LOG(LS_INFO) << "Switching selected connection due to: "
+                     << reason.ToString();
+    SwitchSelectedConnection(const_cast<Connection*>(*result.connection),
+                             reason);
   }
 
-  bool missed_receiving_unchanged_threshold = false;
-  if (ShouldSwitchSelectedConnection(new_connection,
-                                     &missed_receiving_unchanged_threshold)) {
-    RTC_LOG(LS_INFO) << "Switching selected connection due to: " << reason;
-    SwitchSelectedConnection(new_connection, reason);
-    return true;
-  }
-  if (missed_receiving_unchanged_threshold &&
-      config_.receiving_switching_delay_or_default()) {
+  if (result.recheck_delay_ms.has_value()) {
     // If we do not switch to the connection because it missed the receiving
     // threshold, the new connection is in a better receiving state than the
     // currently selected connection. So we need to re-check whether it needs
     // to be switched at a later time.
-    const std::string reason_to_sort =
-        reason + " (after switching dampening interval)";
+    reason.dampening_delay = *result.recheck_delay_ms;
     invoker_.AsyncInvokeDelayed<void>(
         RTC_FROM_HERE, thread(),
         rtc::Bind(&P2PTransportChannel::SortConnectionsAndUpdateState, this,
-                  reason_to_sort),
-        config_.receiving_switching_delay_or_default());
+                  reason),
+        reason.dampening_delay);
   }
-  return false;
+
+  return result.connection.has_value();
 }
 
 void P2PTransportChannel::SetIceRole(IceRole ice_role) {
@@ -475,7 +334,7 @@
   }
 
   std::vector<Connection*> active_connections;
-  for (Connection* connection : connections_) {
+  for (Connection* connection : connections()) {
     if (connection->active()) {
       active_connections.push_back(connection);
     }
@@ -510,7 +369,7 @@
     const {
   RTC_DCHECK_RUN_ON(network_thread_);
   bool has_connection = false;
-  for (Connection* connection : connections_) {
+  for (Connection* connection : connections()) {
     if (connection->active()) {
       has_connection = true;
       break;
@@ -570,12 +429,13 @@
   }
   // We need to update the credentials and generation for any peer reflexive
   // candidates.
-  for (Connection* conn : connections_) {
+  for (Connection* conn : connections()) {
     conn->MaybeSetRemoteIceParametersAndGeneration(
         ice_params, static_cast<int>(remote_ice_parameters_.size() - 1));
   }
   // Updating the remote ICE candidate generation could change the sort order.
-  RequestSortAndStateUpdate("remote candidate generation maybe changed");
+  RequestSortAndStateUpdate(
+      IceControllerEvent::REMOTE_CANDIDATE_GENERATION_CHANGE);
 }
 
 void P2PTransportChannel::SetRemoteIceMode(IceMode mode) {
@@ -611,7 +471,7 @@
   }
   if (config_.receiving_timeout != config.receiving_timeout) {
     config_.receiving_timeout = config.receiving_timeout;
-    for (Connection* connection : connections_) {
+    for (Connection* connection : connections()) {
       connection->set_receiving_timeout(config_.receiving_timeout);
     }
     RTC_LOG(LS_INFO) << "Set ICE receiving timeout to "
@@ -635,7 +495,7 @@
 
   if (config_.presume_writable_when_fully_relayed !=
       config.presume_writable_when_fully_relayed) {
-    if (!connections_.empty()) {
+    if (!connections().empty()) {
       RTC_LOG(LS_ERROR) << "Trying to change 'presume writable' "
                            "while connections already exist!";
     } else {
@@ -714,7 +574,7 @@
 
   if (config_.ice_unwritable_timeout != config.ice_unwritable_timeout) {
     config_.ice_unwritable_timeout = config.ice_unwritable_timeout;
-    for (Connection* conn : connections_) {
+    for (Connection* conn : connections()) {
       conn->set_unwritable_timeout(config_.ice_unwritable_timeout);
     }
     RTC_LOG(LS_INFO) << "Set unwritable timeout to "
@@ -723,7 +583,7 @@
 
   if (config_.ice_unwritable_min_checks != config.ice_unwritable_min_checks) {
     config_.ice_unwritable_min_checks = config.ice_unwritable_min_checks;
-    for (Connection* conn : connections_) {
+    for (Connection* conn : connections()) {
       conn->set_unwritable_min_checks(config_.ice_unwritable_min_checks);
     }
     RTC_LOG(LS_INFO) << "Set unwritable min checks to "
@@ -732,7 +592,7 @@
 
   if (config_.ice_inactive_timeout != config.ice_inactive_timeout) {
     config_.ice_inactive_timeout = config.ice_inactive_timeout;
-    for (Connection* conn : connections_) {
+    for (Connection* conn : connections()) {
       conn->set_inactive_timeout(config_.ice_inactive_timeout);
     }
     RTC_LOG(LS_INFO) << "Set inactive timeout to "
@@ -741,7 +601,7 @@
 
   if (config_.network_preference != config.network_preference) {
     config_.network_preference = config.network_preference;
-    RequestSortAndStateUpdate("network preference changed");
+    RequestSortAndStateUpdate(IceControllerEvent::NETWORK_PREFERENCE_CHANGE);
     RTC_LOG(LS_INFO) << "Set network preference to "
                      << (config_.network_preference.has_value()
                              ? config_.network_preference.value()
@@ -799,6 +659,8 @@
       config_.regather_on_failed_networks_interval_or_default());
   regathering_controller_->SetConfig(regathering_config);
 
+  ice_controller_->SetIceConfig(config_);
+
   RTC_DCHECK(ValidateIceConfig(config_).ok());
 }
 
@@ -980,7 +842,7 @@
   }
 
   SortConnectionsAndUpdateState(
-      "new candidate pairs created from a new local candidate");
+      IceControllerEvent::NEW_CONNECTION_FROM_LOCAL_CANDIDATE);
 }
 
 // A new candidate is available, let listeners know
@@ -1157,7 +1019,7 @@
   // after sending the response since it could (in principle) delete the
   // connection in question.
   SortConnectionsAndUpdateState(
-      "a new candidate pair created from an unknown remote address");
+      IceControllerEvent::NEW_CONNECTION_FROM_UNKNOWN_REMOTE_ADDRESS);
 }
 
 void P2PTransportChannel::OnCandidateFilterChanged(uint32_t prev_filter,
@@ -1202,11 +1064,12 @@
 
   // TODO(qingsi): RequestSortAndStateUpdate will eventually call
   // MaybeSwitchSelectedConnection again. Rewrite this logic.
-  if (MaybeSwitchSelectedConnection(conn,
-                                    "nomination on the controlled side")) {
+  if (MaybeSwitchSelectedConnection(
+          conn, IceControllerEvent::NOMINATION_ON_CONTROLLED_SIDE)) {
     // Now that we have selected a connection, it is time to prune other
     // connections and update the read/write state of the channel.
-    RequestSortAndStateUpdate("nomination on the controlled side");
+    RequestSortAndStateUpdate(
+        IceControllerEvent::NOMINATION_ON_CONTROLLED_SIDE);
   } else {
     RTC_LOG(LS_INFO)
         << "Not switching the selected connection on controlled side yet: "
@@ -1337,7 +1200,7 @@
   RTC_DCHECK_RUN_ON(network_thread_);
   // If this candidate matches what was thought to be a peer reflexive
   // candidate, we need to update the candidate priority/etc.
-  for (Connection* conn : connections_) {
+  for (Connection* conn : connections()) {
     conn->MaybeUpdatePeerReflexiveCandidate(new_remote_candidate);
   }
 
@@ -1346,7 +1209,7 @@
 
   // Resort the connections list, which may have new elements.
   SortConnectionsAndUpdateState(
-      "new candidate pairs created from a new remote candidate");
+      IceControllerEvent::NEW_CONNECTION_FROM_REMOTE_CANDIDATE);
 }
 
 void P2PTransportChannel::RemoveRemoteCandidate(
@@ -1453,7 +1316,7 @@
     AddConnection(connection);
     RTC_LOG(LS_INFO) << ToString()
                      << ": Created connection with origin: " << origin
-                     << ", total: " << connections_.size();
+                     << ", total: " << connections().size();
     return true;
   }
 
@@ -1472,7 +1335,7 @@
 
 bool P2PTransportChannel::FindConnection(Connection* connection) const {
   RTC_DCHECK_RUN_ON(network_thread_);
-  return absl::c_linear_search(connections_, connection);
+  return absl::c_linear_search(connections(), connection);
 }
 
 uint32_t P2PTransportChannel::GetRemoteCandidateGeneration(
@@ -1618,7 +1481,7 @@
   }
 
   // TODO(qingsi): Remove naming inconsistency for candidate pair/connection.
-  for (Connection* connection : connections_) {
+  for (Connection* connection : connections()) {
     ConnectionInfo stats = connection->stats();
     stats.local_candidate = SanitizeLocalCandidate(stats.local_candidate);
     stats.remote_candidate = SanitizeRemoteCandidate(stats.remote_candidate);
@@ -1646,6 +1509,13 @@
   return static_cast<rtc::DiffServCodePoint>(it->second);
 }
 
+rtc::ArrayView<Connection*> P2PTransportChannel::connections() const {
+  RTC_DCHECK_RUN_ON(network_thread_);
+  rtc::ArrayView<const Connection*> res = ice_controller_->connections();
+  return rtc::ArrayView<Connection*>(const_cast<Connection**>(res.data()),
+                                     res.size());
+}
+
 // Monitor connection states.
 void P2PTransportChannel::UpdateConnectionStates() {
   RTC_DCHECK_RUN_ON(network_thread_);
@@ -1653,14 +1523,14 @@
 
   // We need to copy the list of connections since some may delete themselves
   // when we call UpdateState.
-  for (Connection* c : connections_) {
+  for (Connection* c : connections()) {
     c->UpdateState(now);
   }
 }
 
 // Prepare for best candidate sorting.
 void P2PTransportChannel::RequestSortAndStateUpdate(
-    const std::string& reason_to_sort) {
+    IceControllerEvent reason_to_sort) {
   RTC_DCHECK_RUN_ON(network_thread_);
   if (!sort_dirty_) {
     invoker_.AsyncInvoke<void>(
@@ -1677,10 +1547,7 @@
     return;
   }
 
-  int64_t now = rtc::TimeMillis();
-  if (absl::c_any_of(connections_, [this, now](const Connection* c) {
-        return IsPingable(c, now);
-      })) {
+  if (ice_controller_->HasPingableConnection()) {
     RTC_LOG(LS_INFO) << ToString()
                      << ": Have a pingable connection for the first time; "
                         "starting to ping.";
@@ -1692,156 +1559,6 @@
   }
 }
 
-int P2PTransportChannel::CompareCandidatePairNetworks(
-    const Connection* a,
-    const Connection* b,
-    absl::optional<rtc::AdapterType> network_preference) const {
-  RTC_DCHECK_RUN_ON(network_thread_);
-  int compare_a_b_by_network_preference =
-      CompareCandidatePairsByNetworkPreference(a, b,
-                                               config_.network_preference);
-  // The network preference has a higher precedence than the network cost.
-  if (compare_a_b_by_network_preference != a_and_b_equal) {
-    return compare_a_b_by_network_preference;
-  }
-
-  uint32_t a_cost = a->ComputeNetworkCost();
-  uint32_t b_cost = b->ComputeNetworkCost();
-  // Prefer lower network cost.
-  if (a_cost < b_cost) {
-    return a_is_better;
-  }
-  if (a_cost > b_cost) {
-    return b_is_better;
-  }
-  return a_and_b_equal;
-}
-
-// Compare two connections based on their writing, receiving, and connected
-// states.
-int P2PTransportChannel::CompareConnectionStates(
-    const Connection* a,
-    const Connection* b,
-    absl::optional<int64_t> receiving_unchanged_threshold,
-    bool* missed_receiving_unchanged_threshold) const {
-  RTC_DCHECK_RUN_ON(network_thread_);
-  // First, prefer a connection that's writable or presumed writable over
-  // one that's not writable.
-  bool a_writable = a->writable() || PresumedWritable(a);
-  bool b_writable = b->writable() || PresumedWritable(b);
-  if (a_writable && !b_writable) {
-    return a_is_better;
-  }
-  if (!a_writable && b_writable) {
-    return b_is_better;
-  }
-
-  // Sort based on write-state. Better states have lower values.
-  if (a->write_state() < b->write_state()) {
-    return a_is_better;
-  }
-  if (b->write_state() < a->write_state()) {
-    return b_is_better;
-  }
-
-  // We prefer a receiving connection to a non-receiving, higher-priority
-  // connection when sorting connections and choosing which connection to
-  // switch to.
-  if (a->receiving() && !b->receiving()) {
-    return a_is_better;
-  }
-  if (!a->receiving() && b->receiving()) {
-    if (!receiving_unchanged_threshold ||
-        (a->receiving_unchanged_since() <= *receiving_unchanged_threshold &&
-         b->receiving_unchanged_since() <= *receiving_unchanged_threshold)) {
-      return b_is_better;
-    }
-    *missed_receiving_unchanged_threshold = true;
-  }
-
-  // WARNING: Some complexity here about TCP reconnecting.
-  // When a TCP connection fails because of a TCP socket disconnecting, the
-  // active side of the connection will attempt to reconnect for 5 seconds while
-  // pretending to be writable (the connection is not set to the unwritable
-  // state).  On the passive side, the connection also remains writable even
-  // though it is disconnected, and a new connection is created when the active
-  // side connects.  At that point, there are two TCP connections on the passive
-  // side: 1. the old, disconnected one that is pretending to be writable, and
-  // 2.  the new, connected one that is maybe not yet writable.  For purposes of
-  // pruning, pinging, and selecting the selected connection, we want to treat
-  // the new connection as "better" than the old one. We could add a method
-  // called something like Connection::ImReallyBadEvenThoughImWritable, but that
-  // is equivalent to the existing Connection::connected(), which we already
-  // have. So, in code throughout this file, we'll check whether the connection
-  // is connected() or not, and if it is not, treat it as "worse" than a
-  // connected one, even though it's writable.  In the code below, we're doing
-  // so to make sure we treat a new writable connection as better than an old
-  // disconnected connection.
-
-  // In the case where we reconnect TCP connections, the original best
-  // connection is disconnected without changing to WRITE_TIMEOUT. In this case,
-  // the new connection, when it becomes writable, should have higher priority.
-  if (a->write_state() == Connection::STATE_WRITABLE &&
-      b->write_state() == Connection::STATE_WRITABLE) {
-    if (a->connected() && !b->connected()) {
-      return a_is_better;
-    }
-    if (!a->connected() && b->connected()) {
-      return b_is_better;
-    }
-  }
-
-  return 0;
-}
-
-// Compares two connections based only on the candidate and network information.
-// Returns positive if |a| is better than |b|.
-int P2PTransportChannel::CompareConnectionCandidates(
-    const Connection* a,
-    const Connection* b) const {
-  RTC_DCHECK_RUN_ON(network_thread_);
-  int compare_a_b_by_networks =
-      CompareCandidatePairNetworks(a, b, config_.network_preference);
-  if (compare_a_b_by_networks != a_and_b_equal) {
-    return compare_a_b_by_networks;
-  }
-
-  // Compare connection priority. Lower values get sorted last.
-  if (a->priority() > b->priority()) {
-    return a_is_better;
-  }
-  if (a->priority() < b->priority()) {
-    return b_is_better;
-  }
-
-  // If we're still tied at this point, prefer a younger generation.
-  // (Younger generation means a larger generation number).
-  int cmp = (a->remote_candidate().generation() + a->port()->generation()) -
-            (b->remote_candidate().generation() + b->port()->generation());
-  if (cmp != 0) {
-    return cmp;
-  }
-
-  // A periodic regather (triggered by the regather_all_networks_interval_range)
-  // will produce candidates that appear the same but would use a new port. We
-  // want to use the new candidates and purge the old candidates as they come
-  // in, so use the fact that the old ports get pruned immediately to rank the
-  // candidates with an active port/remote candidate higher.
-  bool a_pruned =
-      IsPortPruned(a->port()) || IsRemoteCandidatePruned(a->remote_candidate());
-  bool b_pruned =
-      IsPortPruned(b->port()) || IsRemoteCandidatePruned(b->remote_candidate());
-  if (!a_pruned && b_pruned) {
-    return a_is_better;
-  }
-  if (a_pruned && !b_pruned) {
-    return b_is_better;
-  }
-
-  // Otherwise, must be equal
-  return 0;
-}
-
 bool P2PTransportChannel::IsPortPruned(const Port* port) const {
   RTC_DCHECK_RUN_ON(network_thread_);
   return !absl::c_linear_search(ports_, port);
@@ -1852,46 +1569,6 @@
   return !absl::c_linear_search(remote_candidates_, cand);
 }
 
-int P2PTransportChannel::CompareConnections(
-    const Connection* a,
-    const Connection* b,
-    absl::optional<int64_t> receiving_unchanged_threshold,
-    bool* missed_receiving_unchanged_threshold) const {
-  RTC_DCHECK_RUN_ON(network_thread_);
-  RTC_CHECK(a != nullptr);
-  RTC_CHECK(b != nullptr);
-
-  // We prefer to switch to a writable and receiving connection over a
-  // non-writable or non-receiving connection, even if the latter has
-  // been nominated by the controlling side.
-  int state_cmp = CompareConnectionStates(a, b, receiving_unchanged_threshold,
-                                          missed_receiving_unchanged_threshold);
-  if (state_cmp != 0) {
-    return state_cmp;
-  }
-
-  if (ice_role_ == ICEROLE_CONTROLLED) {
-    // Compare the connections based on the nomination states and the last data
-    // received time if this is on the controlled side.
-    if (a->remote_nomination() > b->remote_nomination()) {
-      return a_is_better;
-    }
-    if (a->remote_nomination() < b->remote_nomination()) {
-      return b_is_better;
-    }
-
-    if (a->last_data_received() > b->last_data_received()) {
-      return a_is_better;
-    }
-    if (a->last_data_received() < b->last_data_received()) {
-      return b_is_better;
-    }
-  }
-
-  // Compare the network cost and priority.
-  return CompareConnectionCandidates(a, b);
-}
-
 bool P2PTransportChannel::PresumedWritable(const Connection* conn) const {
   RTC_DCHECK_RUN_ON(network_thread_);
   return (conn->write_state() == Connection::STATE_WRITE_INIT &&
@@ -1904,7 +1581,7 @@
 // Sort the available connections to find the best one.  We also monitor
 // the number of available connections and the current state.
 void P2PTransportChannel::SortConnectionsAndUpdateState(
-    const std::string& reason_to_sort) {
+    IceControllerEvent reason_to_sort) {
   RTC_DCHECK_RUN_ON(network_thread_);
 
   // Make sure the connection states are up-to-date since this affects how they
@@ -1914,34 +1591,11 @@
   // Any changes after this point will require a re-sort.
   sort_dirty_ = false;
 
-  // Find the best alternative connection by sorting.  It is important to note
-  // that amongst equal preference, writable connections, this will choose the
-  // one whose estimated latency is lowest.  So it is the only one that we
-  // need to consider switching to.
-  // TODO(honghaiz): Don't sort;  Just use std::max_element in the right places.
-  absl::c_stable_sort(
-      connections_, [this](const Connection* a, const Connection* b) {
-        int cmp = CompareConnections(a, b, absl::nullopt, nullptr);
-        if (cmp != 0) {
-          return cmp > 0;
-        }
-        // Otherwise, sort based on latency estimate.
-        return a->rtt() < b->rtt();
-      });
-
-  RTC_LOG(LS_VERBOSE) << "Sorting " << connections_.size()
-                      << " available connections";
-  for (size_t i = 0; i < connections_.size(); ++i) {
-    RTC_LOG(LS_VERBOSE) << connections_[i]->ToString();
-  }
-
-  Connection* top_connection =
-      (connections_.size() > 0) ? connections_[0] : nullptr;
-
   // If necessary, switch to the new choice. Note that |top_connection| doesn't
   // have to be writable to become the selected connection although it will
   // have higher priority if it is writable.
-  MaybeSwitchSelectedConnection(top_connection, reason_to_sort);
+  MaybeSwitchSelectedConnection(
+      reason_to_sort, ice_controller_->SortAndSwitchConnection(reason_to_sort));
 
   // The controlled side can prune only if the selected connection has been
   // nominated because otherwise it may prune the connection that will be
@@ -1956,8 +1610,8 @@
 
   // Check if all connections are timedout.
   bool all_connections_timedout = true;
-  for (size_t i = 0; i < connections_.size(); ++i) {
-    if (connections_[i]->write_state() != Connection::STATE_WRITE_TIMEOUT) {
+  for (const Connection* conn : connections()) {
+    if (conn->write_state() != Connection::STATE_WRITE_TIMEOUT) {
       all_connections_timedout = false;
       break;
     }
@@ -1980,77 +1634,18 @@
   MaybeStartPinging();
 }
 
-std::map<rtc::Network*, Connection*>
-P2PTransportChannel::GetBestConnectionByNetwork() const {
-  RTC_DCHECK_RUN_ON(network_thread_);
-  // |connections_| has been sorted, so the first one in the list on a given
-  // network is the best connection on the network, except that the selected
-  // connection is always the best connection on the network.
-  std::map<rtc::Network*, Connection*> best_connection_by_network;
-  if (selected_connection_) {
-    best_connection_by_network[selected_connection_->port()->Network()] =
-        selected_connection_;
-  }
-  // TODO(honghaiz): Need to update this if |connections_| are not sorted.
-  for (Connection* conn : connections_) {
-    rtc::Network* network = conn->port()->Network();
-    // This only inserts when the network does not exist in the map.
-    best_connection_by_network.insert(std::make_pair(network, conn));
-  }
-  return best_connection_by_network;
-}
-
-std::vector<Connection*>
-P2PTransportChannel::GetBestWritableConnectionPerNetwork() const {
-  std::vector<Connection*> connections;
-  for (auto kv : GetBestConnectionByNetwork()) {
-    Connection* conn = kv.second;
-    if (conn->writable() && conn->connected()) {
-      connections.push_back(conn);
-    }
-  }
-  return connections;
-}
-
 void P2PTransportChannel::PruneConnections() {
-  // We can prune any connection for which there is a connected, writable
-  // connection on the same network with better or equal priority.  We leave
-  // those with better priority just in case they become writable later (at
-  // which point, we would prune out the current selected connection).  We leave
-  // connections on other networks because they may not be using the same
-  // resources and they may represent very distinct paths over which we can
-  // switch. If |best_conn_on_network| is not connected, we may be reconnecting
-  // a TCP connection and should not prune connections in this network.
-  // See the big comment in CompareConnectionStates.
-  //
-  // An exception is made for connections on an "any address" network, meaning
-  // not bound to any specific network interface. We don't want to keep one of
-  // these alive as a backup, since it could be using the same network
-  // interface as the higher-priority, selected candidate pair.
   RTC_DCHECK_RUN_ON(network_thread_);
-  auto best_connection_by_network = GetBestConnectionByNetwork();
-  for (Connection* conn : connections_) {
-    Connection* best_conn = selected_connection_;
-    if (!rtc::IPIsAny(conn->port()->Network()->ip())) {
-      // If the connection is bound to a specific network interface (not an
-      // "any address" network), compare it against the best connection for
-      // that network interface rather than the best connection overall. This
-      // ensures that at least one connection per network will be left
-      // unpruned.
-      best_conn = best_connection_by_network[conn->port()->Network()];
-    }
-    // Do not prune connections if the connection being compared against is
-    // weak. Otherwise, it may delete connections prematurely.
-    if (best_conn && conn != best_conn && !best_conn->weak() &&
-        CompareConnectionCandidates(best_conn, conn) >= 0) {
-      conn->Prune();
-    }
+  std::vector<const Connection*> connections_to_prune =
+      ice_controller_->PruneConnections();
+  for (const Connection* conn : connections_to_prune) {
+    const_cast<Connection*>(conn)->Prune();
   }
 }
 
 // Change the selected connection, and let listeners know.
 void P2PTransportChannel::SwitchSelectedConnection(Connection* conn,
-                                                   const std::string& reason) {
+                                                   IceControllerEvent reason) {
   RTC_DCHECK_RUN_ON(network_thread_);
   // Note: if conn is NULL, the previous |selected_connection_| has been
   // destroyed, so don't use it.
@@ -2100,7 +1695,7 @@
   // Create event for candidate pair change.
   if (selected_connection_) {
     CandidatePairChangeEvent pair_change;
-    pair_change.reason = reason;
+    pair_change.reason = reason.ToString();
     pair_change.selected_candidate_pair = *GetSelectedCandidatePair();
     pair_change.last_data_received_ms =
         selected_connection_->last_data_received();
@@ -2108,6 +1703,8 @@
   }
 
   ++selected_candidate_pair_changes_;
+
+  ice_controller_->SetSelectedConnection(selected_connection_);
 }
 
 // Warning: UpdateState should eventually be called whenever a connection
@@ -2127,7 +1724,7 @@
   SetWritable(writable);
 
   bool receiving = false;
-  for (const Connection* connection : connections_) {
+  for (const Connection* connection : connections()) {
     if (connection->receiving()) {
       receiving = true;
       break;
@@ -2208,16 +1805,11 @@
 // If all connections timed out, delete them all.
 void P2PTransportChannel::HandleAllTimedOut() {
   RTC_DCHECK_RUN_ON(network_thread_);
-  for (Connection* connection : connections_) {
+  for (Connection* connection : connections()) {
     connection->Destroy();
   }
 }
 
-bool P2PTransportChannel::weak() const {
-  RTC_DCHECK_RUN_ON(network_thread_);
-  return !selected_connection_ || selected_connection_->weak();
-}
-
 bool P2PTransportChannel::ReadyToSend(Connection* connection) const {
   RTC_DCHECK_RUN_ON(network_thread_);
   // Note that we allow sending on an unreliable connection, because it's
@@ -2235,212 +1827,32 @@
   // Make sure the states of the connections are up-to-date (since this affects
   // which ones are pingable).
   UpdateConnectionStates();
-  // When the selected connection is not receiving or not writable, or any
-  // active connection has not been pinged enough times, use the weak ping
-  // interval.
-  bool need_more_pings_at_weak_interval =
-      absl::c_any_of(connections_, [](Connection* conn) {
-        return conn->active() &&
-               conn->num_pings_sent() < MIN_PINGS_AT_WEAK_PING_INTERVAL;
-      });
-  int ping_interval = (weak() || need_more_pings_at_weak_interval)
-                          ? weak_ping_interval()
-                          : strong_ping_interval();
-  if (rtc::TimeMillis() >= last_ping_sent_ms_ + ping_interval) {
-    Connection* conn = FindNextPingableConnection();
-    if (conn) {
-      PingConnection(conn);
-      MarkConnectionPinged(conn);
-    }
+
+  auto result = ice_controller_->SelectConnectionToPing(last_ping_sent_ms_);
+  Connection* conn = result.first;
+  int delay = result.second;
+
+  if (conn) {
+    PingConnection(conn);
+    MarkConnectionPinged(conn);
   }
-  int delay = std::min(ping_interval, check_receiving_interval());
+
   invoker_.AsyncInvokeDelayed<void>(
       RTC_FROM_HERE, thread(),
       rtc::Bind(&P2PTransportChannel::CheckAndPing, this), delay);
 }
 
-// A connection is considered a backup connection if the channel state
-// is completed, the connection is not the selected connection and it is active.
-bool P2PTransportChannel::IsBackupConnection(const Connection* conn) const {
-  RTC_DCHECK_RUN_ON(network_thread_);
-  return state_ == IceTransportState::STATE_COMPLETED &&
-         conn != selected_connection_ && conn->active();
-}
-
-// Is the connection in a state for us to even consider pinging the other side?
-// We consider a connection pingable even if it's not connected because that's
-// how a TCP connection is kicked into reconnecting on the active side.
-bool P2PTransportChannel::IsPingable(const Connection* conn,
-                                     int64_t now) const {
-  RTC_DCHECK_RUN_ON(network_thread_);
-  const Candidate& remote = conn->remote_candidate();
-  // We should never get this far with an empty remote ufrag.
-  RTC_DCHECK(!remote.username().empty());
-  if (remote.username().empty() || remote.password().empty()) {
-    // If we don't have an ICE ufrag and pwd, there's no way we can ping.
-    return false;
-  }
-
-  // A failed connection will not be pinged.
-  if (conn->state() == IceCandidatePairState::FAILED) {
-    return false;
-  }
-
-  // An never connected connection cannot be written to at all, so pinging is
-  // out of the question. However, if it has become WRITABLE, it is in the
-  // reconnecting state so ping is needed.
-  if (!conn->connected() && !conn->writable()) {
-    return false;
-  }
-
-  // If we sent a number of pings wo/ reply, skip sending more
-  // until we get one.
-  if (conn->TooManyOutstandingPings(field_trials_.max_outstanding_pings)) {
-    return false;
-  }
-
-  // If the channel is weakly connected, ping all connections.
-  if (weak()) {
-    return true;
-  }
-
-  // Always ping active connections regardless whether the channel is completed
-  // or not, but backup connections are pinged at a slower rate.
-  if (IsBackupConnection(conn)) {
-    return conn->rtt_samples() == 0 ||
-           (now >= conn->last_ping_response_received() +
-                       config_.backup_connection_ping_interval_or_default());
-  }
-  // Don't ping inactive non-backup connections.
-  if (!conn->active()) {
-    return false;
-  }
-
-  // Do ping unwritable, active connections.
-  if (!conn->writable()) {
-    return true;
-  }
-
-  // Ping writable, active connections if it's been long enough since the last
-  // ping.
-  return WritableConnectionPastPingInterval(conn, now);
-}
-
-bool P2PTransportChannel::WritableConnectionPastPingInterval(
-    const Connection* conn,
-    int64_t now) const {
-  RTC_DCHECK_RUN_ON(network_thread_);
-  int interval = CalculateActiveWritablePingInterval(conn, now);
-  return conn->last_ping_sent() + interval <= now;
-}
-
-int P2PTransportChannel::CalculateActiveWritablePingInterval(
-    const Connection* conn,
-    int64_t now) const {
-  RTC_DCHECK_RUN_ON(network_thread_);
-  // Ping each connection at a higher rate at least
-  // MIN_PINGS_AT_WEAK_PING_INTERVAL times.
-  if (conn->num_pings_sent() < MIN_PINGS_AT_WEAK_PING_INTERVAL) {
-    return weak_ping_interval();
-  }
-
-  int stable_interval =
-      config_.stable_writable_connection_ping_interval_or_default();
-  int weak_or_stablizing_interval = std::min(
-      stable_interval, WEAK_OR_STABILIZING_WRITABLE_CONNECTION_PING_INTERVAL);
-  // If the channel is weak or the connection is not stable yet, use the
-  // weak_or_stablizing_interval.
-  return (!weak() && conn->stable(now)) ? stable_interval
-                                        : weak_or_stablizing_interval;
-}
-
-// Returns the next pingable connection to ping.
+// This method is only for unit testing.
 Connection* P2PTransportChannel::FindNextPingableConnection() {
   RTC_DCHECK_RUN_ON(network_thread_);
-  int64_t now = rtc::TimeMillis();
-
-  // Rule 1: Selected connection takes priority over non-selected ones.
-  if (selected_connection_ && selected_connection_->connected() &&
-      selected_connection_->writable() &&
-      WritableConnectionPastPingInterval(selected_connection_, now)) {
-    return selected_connection_;
-  }
-
-  // Rule 2: If the channel is weak, we need to find a new writable and
-  // receiving connection, probably on a different network. If there are lots of
-  // connections, it may take several seconds between two pings for every
-  // non-selected connection. This will cause the receiving state of those
-  // connections to be false, and thus they won't be selected. This is
-  // problematic for network fail-over. We want to make sure at least one
-  // connection per network is pinged frequently enough in order for it to be
-  // selectable. So we prioritize one connection per network.
-  // Rule 2.1: Among such connections, pick the one with the earliest
-  // last-ping-sent time.
-  if (weak()) {
-    std::vector<Connection*> pingable_selectable_connections;
-    absl::c_copy_if(GetBestWritableConnectionPerNetwork(),
-                    std::back_inserter(pingable_selectable_connections),
-                    [this, now](Connection* conn) {
-                      return WritableConnectionPastPingInterval(conn, now);
-                    });
-    auto iter = absl::c_min_element(pingable_selectable_connections,
-                                    [](Connection* conn1, Connection* conn2) {
-                                      return conn1->last_ping_sent() <
-                                             conn2->last_ping_sent();
-                                    });
-    if (iter != pingable_selectable_connections.end()) {
-      return *iter;
-    }
-  }
-
-  // Rule 3: Triggered checks have priority over non-triggered connections.
-  // Rule 3.1: Among triggered checks, oldest takes precedence.
-  Connection* oldest_triggered_check =
-      FindOldestConnectionNeedingTriggeredCheck(now);
-  if (oldest_triggered_check) {
-    return oldest_triggered_check;
-  }
-
-  // Rule 4: Unpinged connections have priority over pinged ones.
-  RTC_CHECK(connections_.size() ==
-            pinged_connections_.size() + unpinged_connections_.size());
-  // If there are unpinged and pingable connections, only ping those.
-  // Otherwise, treat everything as unpinged.
-  // TODO(honghaiz): Instead of adding two separate vectors, we can add a state
-  // "pinged" to filter out unpinged connections.
-  if (absl::c_none_of(unpinged_connections_, [this, now](Connection* conn) {
-        return this->IsPingable(conn, now);
-      })) {
-    unpinged_connections_.insert(pinged_connections_.begin(),
-                                 pinged_connections_.end());
-    pinged_connections_.clear();
-  }
-
-  // Among un-pinged pingable connections, "more pingable" takes precedence.
-  std::vector<Connection*> pingable_connections;
-  absl::c_copy_if(
-      unpinged_connections_, std::back_inserter(pingable_connections),
-      [this, now](Connection* conn) { return IsPingable(conn, now); });
-  auto iter = absl::c_max_element(pingable_connections,
-                                  [this](Connection* conn1, Connection* conn2) {
-                                    // Some implementations of max_element
-                                    // compare an element with itself.
-                                    if (conn1 == conn2) {
-                                      return false;
-                                    }
-                                    return MorePingable(conn1, conn2) == conn2;
-                                  });
-  if (iter != pingable_connections.end()) {
-    return *iter;
-  }
-  return nullptr;
+  return const_cast<Connection*>(ice_controller_->FindNextPingableConnection());
 }
 
+// A connection is considered a backup connection if the channel state
+// is completed, the connection is not the selected connection and it is active.
 void P2PTransportChannel::MarkConnectionPinged(Connection* conn) {
   RTC_DCHECK_RUN_ON(network_thread_);
-  if (conn && pinged_connections_.insert(conn).second) {
-    unpinged_connections_.erase(conn);
-  }
+  ice_controller_->MarkConnectionPinged(conn);
 }
 
 // Apart from sending ping from |conn| this method also updates
@@ -2457,8 +1869,7 @@
     if (renomination_supported) {
       nomination = GetNominationAttr(conn);
     } else {
-      use_candidate_attr =
-          GetUseCandidateAttr(conn, config_.default_nomination_mode);
+      use_candidate_attr = GetUseCandidateAttr(conn);
     }
   }
   conn->set_nomination(nomination);
@@ -2473,41 +1884,10 @@
 }
 
 // Nominate a connection based on the NominationMode.
-bool P2PTransportChannel::GetUseCandidateAttr(Connection* conn,
-                                              NominationMode mode) const {
+bool P2PTransportChannel::GetUseCandidateAttr(Connection* conn) const {
   RTC_DCHECK_RUN_ON(network_thread_);
-  switch (mode) {
-    case NominationMode::REGULAR:
-      // TODO(honghaiz): Implement regular nomination.
-      return false;
-    case NominationMode::AGGRESSIVE:
-      if (remote_ice_mode_ == ICEMODE_LITE) {
-        return GetUseCandidateAttr(conn, NominationMode::REGULAR);
-      }
-      return true;
-    case NominationMode::SEMI_AGGRESSIVE: {
-      // Nominate if
-      // a) Remote is in FULL ICE AND
-      //    a.1) |conn| is the selected connection OR
-      //    a.2) there is no selected connection OR
-      //    a.3) the selected connection is unwritable OR
-      //    a.4) |conn| has higher priority than selected_connection.
-      // b) Remote is in LITE ICE AND
-      //    b.1) |conn| is the selected_connection AND
-      //    b.2) |conn| is writable.
-      bool selected = conn == selected_connection_;
-      if (remote_ice_mode_ == ICEMODE_LITE) {
-        return selected && conn->writable();
-      }
-      bool better_than_selected =
-          !selected_connection_ || !selected_connection_->writable() ||
-          CompareConnectionCandidates(selected_connection_, conn) < 0;
-      return selected || better_than_selected;
-    }
-    default:
-      RTC_NOTREACHED();
-      return false;
-  }
+  return ice_controller_->GetUseCandidateAttr(
+      conn, config_.default_nomination_mode, remote_ice_mode_);
 }
 
 // When a connection's state changes, we need to figure out who to use as
@@ -2529,7 +1909,9 @@
   }
   // We have to unroll the stack before doing this because we may be changing
   // the state of connections while sorting.
-  RequestSortAndStateUpdate("candidate pair state changed");
+  RequestSortAndStateUpdate(
+      IceControllerEvent::CONNECT_STATE_CHANGE);  // "candidate pair state
+                                                  // changed");
 }
 
 // When a connection is removed, edit it out, and then update our best
@@ -2541,14 +1923,10 @@
   // use it.
 
   // Remove this connection from the list.
-  auto iter = absl::c_find(connections_, connection);
-  RTC_DCHECK(iter != connections_.end());
-  pinged_connections_.erase(connection);
-  unpinged_connections_.erase(connection);
-  connections_.erase(iter);
+  ice_controller_->OnConnectionDestroyed(connection);
 
   RTC_LOG(LS_INFO) << ToString() << ": Removed connection " << connection
-                   << " (" << connections_.size() << " remaining)";
+                   << " (" << connections().size() << " remaining)";
 
   // If this is currently the selected connection, then we need to pick a new
   // one. The call to SortConnectionsAndUpdateState will pick a new one. It
@@ -2558,7 +1936,8 @@
   // there was no selected connection.
   if (selected_connection_ == connection) {
     RTC_LOG(LS_INFO) << "Selected connection destroyed. Will choose a new one.";
-    const std::string reason = "selected candidate pair destroyed";
+    IceControllerEvent reason =
+        IceControllerEvent::SELECTED_CONNECTION_DESTROYED;
     SwitchSelectedConnection(nullptr, reason);
     RequestSortAndStateUpdate(reason);
   } else {
@@ -2638,6 +2017,12 @@
                                        int64_t packet_time_us) {
   RTC_DCHECK_RUN_ON(network_thread_);
 
+  if (connection == selected_connection_) {
+    // Let the client know of an incoming packet
+    SignalReadPacket(this, data, len, packet_time_us, 0);
+    return;
+  }
+
   // Do not deliver, if packet doesn't belong to the correct transport channel.
   if (!FindConnection(connection))
     return;
@@ -2648,7 +2033,8 @@
   // May need to switch the sending connection based on the receiving media path
   // if this is the controlled side.
   if (ice_role_ == ICEROLE_CONTROLLED) {
-    MaybeSwitchSelectedConnection(connection, "data received");
+    MaybeSwitchSelectedConnection(connection,
+                                  IceControllerEvent::DATA_RECEIVED);
   }
 }
 
@@ -2665,92 +2051,6 @@
   }
 }
 
-// Find "triggered checks".  We ping first those connections that have
-// received a ping but have not sent a ping since receiving it
-// (last_ping_received > last_ping_sent).  But we shouldn't do
-// triggered checks if the connection is already writable.
-Connection* P2PTransportChannel::FindOldestConnectionNeedingTriggeredCheck(
-    int64_t now) {
-  RTC_DCHECK_RUN_ON(network_thread_);
-  Connection* oldest_needing_triggered_check = nullptr;
-  for (auto* conn : connections_) {
-    if (!IsPingable(conn, now)) {
-      continue;
-    }
-    bool needs_triggered_check =
-        (!conn->writable() &&
-         conn->last_ping_received() > conn->last_ping_sent());
-    if (needs_triggered_check &&
-        (!oldest_needing_triggered_check ||
-         (conn->last_ping_received() <
-          oldest_needing_triggered_check->last_ping_received()))) {
-      oldest_needing_triggered_check = conn;
-    }
-  }
-
-  if (oldest_needing_triggered_check) {
-    RTC_LOG(LS_INFO) << "Selecting connection for triggered check: "
-                     << oldest_needing_triggered_check->ToString();
-  }
-  return oldest_needing_triggered_check;
-}
-
-Connection* P2PTransportChannel::MostLikelyToWork(Connection* conn1,
-                                                  Connection* conn2) {
-  RTC_DCHECK_RUN_ON(network_thread_);
-  bool rr1 = IsRelayRelay(conn1);
-  bool rr2 = IsRelayRelay(conn2);
-  if (rr1 && !rr2) {
-    return conn1;
-  } else if (rr2 && !rr1) {
-    return conn2;
-  } else if (rr1 && rr2) {
-    bool udp1 = IsUdp(conn1);
-    bool udp2 = IsUdp(conn2);
-    if (udp1 && !udp2) {
-      return conn1;
-    } else if (udp2 && udp1) {
-      return conn2;
-    }
-  }
-  return nullptr;
-}
-
-Connection* P2PTransportChannel::LeastRecentlyPinged(Connection* conn1,
-                                                     Connection* conn2) {
-  RTC_DCHECK_RUN_ON(network_thread_);
-  if (conn1->last_ping_sent() < conn2->last_ping_sent()) {
-    return conn1;
-  }
-  if (conn1->last_ping_sent() > conn2->last_ping_sent()) {
-    return conn2;
-  }
-  return nullptr;
-}
-
-Connection* P2PTransportChannel::MorePingable(Connection* conn1,
-                                              Connection* conn2) {
-  RTC_DCHECK_RUN_ON(network_thread_);
-  RTC_DCHECK(conn1 != conn2);
-  if (config_.prioritize_most_likely_candidate_pairs) {
-    Connection* most_likely_to_work_conn = MostLikelyToWork(conn1, conn2);
-    if (most_likely_to_work_conn) {
-      return most_likely_to_work_conn;
-    }
-  }
-
-  Connection* least_recently_pinged_conn = LeastRecentlyPinged(conn1, conn2);
-  if (least_recently_pinged_conn) {
-    return least_recently_pinged_conn;
-  }
-
-  // During the initial state when nothing has been pinged yet, return the first
-  // one in the ordered |connections_|.
-  return *(absl::c_find_if(connections_, [conn1, conn2](Connection* conn) {
-    return conn == conn1 || conn == conn2;
-  }));
-}
-
 void P2PTransportChannel::SetWritable(bool writable) {
   RTC_DCHECK_RUN_ON(network_thread_);
   if (writable_ == writable) {
diff --git a/p2p/base/p2p_transport_channel.h b/p2p/base/p2p_transport_channel.h
index 7ce0651..ee0cca2 100644
--- a/p2p/base/p2p_transport_channel.h
+++ b/p2p/base/p2p_transport_channel.h
@@ -33,6 +33,7 @@
 #include "logging/rtc_event_log/events/rtc_event_ice_candidate_pair_config.h"
 #include "logging/rtc_event_log/ice_logger.h"
 #include "p2p/base/candidate_pair_interface.h"
+#include "p2p/base/ice_controller_interface.h"
 #include "p2p/base/ice_transport_internal.h"
 #include "p2p/base/p2p_constants.h"
 #include "p2p/base/port_allocator.h"
@@ -191,7 +192,7 @@
   void MarkConnectionPinged(Connection* conn);
 
   // Public for unit tests.
-  const std::vector<Connection*>& connections() const { return connections_; }
+  rtc::ArrayView<Connection*> connections() const;
 
   // Public for unit tests.
   PortAllocatorSession* allocator_session() const {
@@ -226,63 +227,19 @@
     return allocator_session()->IsGettingPorts();
   }
 
-  // A transport channel is weak if the current best connection is either
-  // not receiving or not writable, or if there is no best connection at all.
-  bool weak() const;
-
-  int weak_ping_interval() const {
-    RTC_DCHECK_RUN_ON(network_thread_);
-    return std::max(config_.ice_check_interval_weak_connectivity_or_default(),
-                    config_.ice_check_min_interval_or_default());
-  }
-
-  int strong_ping_interval() const {
-    RTC_DCHECK_RUN_ON(network_thread_);
-    return std::max(config_.ice_check_interval_strong_connectivity_or_default(),
-                    config_.ice_check_min_interval_or_default());
-  }
-
   // Returns true if it's possible to send packets on |connection|.
   bool ReadyToSend(Connection* connection) const;
+  bool PresumedWritable(const Connection* conn) const;
   void UpdateConnectionStates();
-  void RequestSortAndStateUpdate(const std::string& reason_to_sort);
+  void RequestSortAndStateUpdate(IceControllerEvent reason_to_sort);
   // Start pinging if we haven't already started, and we now have a connection
   // that's pingable.
   void MaybeStartPinging();
 
-  int CompareCandidatePairNetworks(
-      const Connection* a,
-      const Connection* b,
-      absl::optional<rtc::AdapterType> network_preference) const;
-
-  // The methods below return a positive value if |a| is preferable to |b|,
-  // a negative value if |b| is preferable, and 0 if they're equally preferable.
-  // If |receiving_unchanged_threshold| is set, then when |b| is receiving and
-  // |a| is not, returns a negative value only if |b| has been in receiving
-  // state and |a| has been in not receiving state since
-  // |receiving_unchanged_threshold| and sets
-  // |missed_receiving_unchanged_threshold| to true otherwise.
-  int CompareConnectionStates(
-      const cricket::Connection* a,
-      const cricket::Connection* b,
-      absl::optional<int64_t> receiving_unchanged_threshold,
-      bool* missed_receiving_unchanged_threshold) const;
-  int CompareConnectionCandidates(const cricket::Connection* a,
-                                  const cricket::Connection* b) const;
-  // Compares two connections based on the connection states
-  // (writable/receiving/connected), nomination states, last data received time,
-  // and static preferences. Does not include latency. Used by both sorting
-  // and ShouldSwitchSelectedConnection().
-  // Returns a positive value if |a| is better than |b|.
-  int CompareConnections(const cricket::Connection* a,
-                         const cricket::Connection* b,
-                         absl::optional<int64_t> receiving_unchanged_threshold,
-                         bool* missed_receiving_unchanged_threshold) const;
-
-  bool PresumedWritable(const cricket::Connection* conn) const;
-
-  void SortConnectionsAndUpdateState(const std::string& reason_to_sort);
-  void SwitchSelectedConnection(Connection* conn, const std::string& reason);
+  void SortConnectionsAndUpdateState(IceControllerEvent reason_to_sort);
+  void SortConnections();
+  void SortConnectionsIfNeeded();
+  void SwitchSelectedConnection(Connection* conn, IceControllerEvent reason);
   void UpdateState();
   void HandleAllTimedOut();
   void MaybeStopPortAllocatorSessions();
@@ -294,25 +251,17 @@
   IceTransportState ComputeState() const;
   webrtc::IceTransportState ComputeIceTransportState() const;
 
-  Connection* GetBestConnectionOnNetwork(rtc::Network* network) const;
   bool CreateConnections(const Candidate& remote_candidate,
                          PortInterface* origin_port);
   bool CreateConnection(PortInterface* port,
                         const Candidate& remote_candidate,
                         PortInterface* origin_port);
-  bool FindConnection(cricket::Connection* connection) const;
+  bool FindConnection(Connection* connection) const;
 
   uint32_t GetRemoteCandidateGeneration(const Candidate& candidate);
   bool IsDuplicateRemoteCandidate(const Candidate& candidate);
   void RememberRemoteCandidate(const Candidate& remote_candidate,
                                PortInterface* origin_port);
-  bool IsPingable(const Connection* conn, int64_t now) const;
-  // Whether a writable connection is past its ping interval and needs to be
-  // pinged again.
-  bool WritableConnectionPastPingInterval(const Connection* conn,
-                                          int64_t now) const;
-  int CalculateActiveWritablePingInterval(const Connection* conn,
-                                          int64_t now) const;
   void PingConnection(Connection* conn);
   void AddAllocatorSession(std::unique_ptr<PortAllocatorSession> session);
   void AddConnection(Connection* connection);
@@ -360,33 +309,15 @@
                               webrtc::IceCandidatePairConfigType type);
 
   uint32_t GetNominationAttr(Connection* conn) const;
-  bool GetUseCandidateAttr(Connection* conn, NominationMode mode) const;
+  bool GetUseCandidateAttr(Connection* conn) const;
 
-  // Returns true if we should switch to the new connection.
-  // sets |missed_receiving_unchanged_threshold| to true if either
-  // the selected connection or the new connection missed its
-  // receiving-unchanged-threshold.
-  bool ShouldSwitchSelectedConnection(
-      Connection* new_connection,
-      bool* missed_receiving_unchanged_threshold) const;
   // Returns true if the new_connection is selected for transmission.
   bool MaybeSwitchSelectedConnection(Connection* new_connection,
-                                     const std::string& reason);
-  // Gets the best connection for each network.
-  std::map<rtc::Network*, Connection*> GetBestConnectionByNetwork() const;
-  std::vector<Connection*> GetBestWritableConnectionPerNetwork() const;
+                                     IceControllerEvent reason);
+  bool MaybeSwitchSelectedConnection(
+      IceControllerEvent reason,
+      IceControllerInterface::SwitchResult result);
   void PruneConnections();
-  bool IsBackupConnection(const Connection* conn) const;
-
-  Connection* FindOldestConnectionNeedingTriggeredCheck(int64_t now);
-  // Between |conn1| and |conn2|, this function returns the one which should
-  // be pinged first.
-  Connection* MorePingable(Connection* conn1, Connection* conn2);
-  // Select the connection which is Relay/Relay. If both of them are,
-  // UDP relay protocol takes precedence.
-  Connection* MostLikelyToWork(Connection* conn1, Connection* conn2);
-  // Compare the last_ping_sent time and return the one least recently pinged.
-  Connection* LeastRecentlyPinged(Connection* conn1, Connection* conn2);
 
   // Returns the latest remote ICE parameters or nullptr if there are no remote
   // ICE parameters yet.
@@ -428,9 +359,6 @@
   // 2. Peer-reflexive remote candidates.
   Candidate SanitizeRemoteCandidate(const Candidate& c) const;
 
-  bool HandleInitialSelectDampening(Connection* new_connection,
-                                    const std::string& reason);
-
   std::string transport_name_ RTC_GUARDED_BY(network_thread_);
   int component_ RTC_GUARDED_BY(network_thread_);
   PortAllocator* allocator_ RTC_GUARDED_BY(network_thread_);
@@ -450,15 +378,6 @@
   // SignalUnknownAddress.
   std::vector<PortInterface*> pruned_ports_ RTC_GUARDED_BY(network_thread_);
 
-  // |connections_| is a sorted list with the first one always be the
-  // |selected_connection_| when it's not nullptr. The combination of
-  // |pinged_connections_| and |unpinged_connections_| has the same
-  // connections as |connections_|. These 2 sets maintain whether a
-  // connection should be pinged next or not.
-  std::vector<Connection*> connections_ RTC_GUARDED_BY(network_thread_);
-  std::set<Connection*> pinged_connections_ RTC_GUARDED_BY(network_thread_);
-  std::set<Connection*> unpinged_connections_ RTC_GUARDED_BY(network_thread_);
-
   Connection* selected_connection_ RTC_GUARDED_BY(network_thread_) = nullptr;
 
   std::vector<RemoteCandidate> remote_candidates_
@@ -503,6 +422,9 @@
       RTC_GUARDED_BY(network_thread_);
   webrtc::IceEventLog ice_event_log_ RTC_GUARDED_BY(network_thread_);
 
+  std::unique_ptr<IceControllerInterface> ice_controller_
+      RTC_GUARDED_BY(network_thread_);
+
   struct CandidateAndResolver final {
     CandidateAndResolver(const Candidate& candidate,
                          rtc::AsyncResolverInterface* resolver);
@@ -521,9 +443,6 @@
 
   IceFieldTrials field_trials_;
 
-  // Timestamp for when we got the first selectable connection.
-  int64_t initial_select_timestamp_ms_ = 0;
-
   RTC_DISALLOW_COPY_AND_ASSIGN(P2PTransportChannel);
 };