diff --git a/p2p/BUILD.gn b/p2p/BUILD.gn index 769bd4b209..07d1f711b6 100644 --- a/p2p/BUILD.gn +++ b/p2p/BUILD.gn @@ -48,6 +48,8 @@ rtc_static_library("rtc_p2p") { "base/portinterface.h", "base/pseudotcp.cc", "base/pseudotcp.h", + "base/regatheringcontroller.cc", + "base/regatheringcontroller.h", "base/relayport.cc", "base/relayport.h", "base/stun.cc", @@ -149,6 +151,7 @@ if (rtc_include_tests) { "base/port_unittest.cc", "base/portallocator_unittest.cc", "base/pseudotcp_unittest.cc", + "base/regatheringcontroller_unittest.cc", "base/relayport_unittest.cc", "base/relayserver_unittest.cc", "base/stun_unittest.cc", diff --git a/p2p/base/fakeportallocator.h b/p2p/base/fakeportallocator.h index 514340bd5b..fc5f23722f 100644 --- a/p2p/base/fakeportallocator.h +++ b/p2p/base/fakeportallocator.h @@ -140,7 +140,16 @@ class FakePortAllocatorSession : public PortAllocatorSession { void StopGettingPorts() override { running_ = false; } bool IsGettingPorts() override { return running_; } - void ClearGettingPorts() override {} + void ClearGettingPorts() override { is_cleared = true; } + bool IsCleared() const override { return is_cleared; } + + void RegatherOnAllNetworks() override { + SignalIceRegathering(this, IceRegatheringReason::OCCASIONAL_REFRESH); + } + + void RegatherOnFailedNetworks() override { + SignalIceRegathering(this, IceRegatheringReason::NETWORK_FAILURE); + } std::vector ReadyPorts() const override { return ready_ports_; @@ -205,6 +214,7 @@ class FakePortAllocatorSession : public PortAllocatorSession { std::vector candidates_; std::vector ready_ports_; bool allocation_done_ = false; + bool is_cleared = false; ServerAddresses stun_servers_; std::vector turn_servers_; uint32_t candidate_filter_ = CF_ALL; diff --git a/p2p/base/p2ptransportchannel.cc b/p2p/base/p2ptransportchannel.cc index 0b6beb9b3f..d1c58c2162 100644 --- a/p2p/base/p2ptransportchannel.cc +++ b/p2p/base/p2ptransportchannel.cc @@ -125,7 +125,6 @@ P2PTransportChannel::P2PTransportChannel(const std::string& transport_name, ice_role_(ICEROLE_UNKNOWN), tiebreaker_(0), gathering_state_(kIceGatheringNew), - rand_(rtc::SystemTimeNanos()), config_(RECEIVING_TIMEOUT, BACKUP_CONNECTION_PING_INTERVAL, GATHER_ONCE /* continual_gathering_policy */, @@ -138,6 +137,11 @@ P2PTransportChannel::P2PTransportChannel(const std::string& transport_name, // Validate IceConfig even for mostly built-in constant default values in case // we change them. RTC_DCHECK(ValidateIceConfig(config_).ok()); + webrtc::BasicRegatheringController::Config regathering_config( + config_.regather_all_networks_interval_range, + config_.regather_on_failed_networks_interval_or_default()); + regathering_controller_.reset(new webrtc::BasicRegatheringController( + regathering_config, this, network_thread_)); ice_event_log_.set_event_log(event_log); } @@ -164,6 +168,7 @@ void P2PTransportChannel::AddAllocatorSession( allocator_session()->PruneAllPorts(); } allocator_sessions_.push_back(std::move(session)); + regathering_controller_->set_allocator_session(allocator_session()); // We now only want to apply new candidates that we receive to the ports // created by this new session because these are replacing those of the @@ -571,6 +576,11 @@ void P2PTransportChannel::SetIceConfig(const IceConfig& config) { << config.stun_keepalive_interval_or_default(); } + webrtc::BasicRegatheringController::Config regathering_config( + config_.regather_all_networks_interval_range, + config_.regather_on_failed_networks_interval_or_default()); + regathering_controller_->SetConfig(regathering_config); + RTC_DCHECK(ValidateIceConfig(config_).ok()); } @@ -578,6 +588,8 @@ const IceConfig& P2PTransportChannel::config() const { return config_; } +// TODO(qingsi): Add tests for the config validation starting from +// PeerConnection::SetConfiguration. RTCError P2PTransportChannel::ValidateIceConfig(const IceConfig& config) { if (config.regather_all_networks_interval_range && config.continual_gathering_policy == GATHER_ONCE) { @@ -625,6 +637,13 @@ RTCError P2PTransportChannel::ValidateIceConfig(const IceConfig& config) { "UNRELIABLE is longer than that to become TIMEOUT."); } + if (config.regather_all_networks_interval_range && + config.regather_all_networks_interval_range.value().min() < 0) { + return RTCError( + RTCErrorType::INVALID_RANGE, + "The minimum regathering interval for all networks is negative."); + } + return RTCError::OK(); } @@ -1292,16 +1311,7 @@ void P2PTransportChannel::MaybeStartPinging() { invoker_.AsyncInvoke( RTC_FROM_HERE, thread(), rtc::Bind(&P2PTransportChannel::CheckAndPing, this)); - invoker_.AsyncInvokeDelayed( - RTC_FROM_HERE, thread(), - rtc::Bind(&P2PTransportChannel::RegatherOnFailedNetworks, this), - config_.regather_on_failed_networks_interval_or_default()); - if (config_.regather_all_networks_interval_range) { - invoker_.AsyncInvokeDelayed( - RTC_FROM_HERE, thread(), - rtc::Bind(&P2PTransportChannel::RegatherOnAllNetworks, this), - SampleRegatherAllNetworksInterval()); - } + regathering_controller_->Start(); started_pinging_ = true; } } @@ -2185,31 +2195,6 @@ void P2PTransportChannel::OnCandidatesRemoved( SignalCandidatesRemoved(this, candidates_to_remove); } -void P2PTransportChannel::RegatherOnFailedNetworks() { - // Only re-gather when the current session is in the CLEARED state (i.e., not - // running or stopped). It is only possible to enter this state when we gather - // continually, so there is an implicit check on continual gathering here. - if (!allocator_sessions_.empty() && allocator_session()->IsCleared()) { - allocator_session()->RegatherOnFailedNetworks(); - } - - invoker_.AsyncInvokeDelayed( - RTC_FROM_HERE, thread(), - rtc::Bind(&P2PTransportChannel::RegatherOnFailedNetworks, this), - config_.regather_on_failed_networks_interval_or_default()); -} - -void P2PTransportChannel::RegatherOnAllNetworks() { - if (!allocator_sessions_.empty() && allocator_session()->IsCleared()) { - allocator_session()->RegatherOnAllNetworks(); - } - - invoker_.AsyncInvokeDelayed( - RTC_FROM_HERE, thread(), - rtc::Bind(&P2PTransportChannel::RegatherOnAllNetworks, this), - SampleRegatherAllNetworksInterval()); -} - void P2PTransportChannel::PruneAllPorts() { pruned_ports_.insert(pruned_ports_.end(), ports_.begin(), ports_.end()); ports_.clear(); @@ -2363,12 +2348,6 @@ void P2PTransportChannel::set_receiving(bool receiving) { SignalReceivingState(this); } -int P2PTransportChannel::SampleRegatherAllNetworksInterval() { - auto interval = config_.regather_all_networks_interval_range; - RTC_DCHECK(interval); - return rand_.Rand(interval->min(), interval->max()); -} - void P2PTransportChannel::LogCandidatePairConfig( Connection* conn, webrtc::IceCandidatePairConfigType type) { diff --git a/p2p/base/p2ptransportchannel.h b/p2p/base/p2ptransportchannel.h index c061431900..8e3b06ce3e 100644 --- a/p2p/base/p2ptransportchannel.h +++ b/p2p/base/p2ptransportchannel.h @@ -36,10 +36,10 @@ #include "p2p/base/p2pconstants.h" #include "p2p/base/portallocator.h" #include "p2p/base/portinterface.h" +#include "p2p/base/regatheringcontroller.h" #include "rtc_base/asyncinvoker.h" #include "rtc_base/asyncpacketsocket.h" #include "rtc_base/constructormagic.h" -#include "rtc_base/random.h" #include "rtc_base/sigslot.h" namespace webrtc { @@ -151,7 +151,10 @@ class P2PTransportChannel : public IceTransportInternal { const std::vector& connections() const { return connections_; } // Public for unit tests. - PortAllocatorSession* allocator_session() { + PortAllocatorSession* allocator_session() const { + if (allocator_sessions_.empty()) { + return nullptr; + } return allocator_sessions_.back().get(); } @@ -171,6 +174,7 @@ class P2PTransportChannel : public IceTransportInternal { private: rtc::Thread* thread() const { return network_thread_; } + bool IsGettingPorts() { return allocator_session()->IsGettingPorts(); } // A transport channel is weak if the current best connection is either @@ -289,8 +293,6 @@ class P2PTransportChannel : public IceTransportInternal { void OnNominated(Connection* conn); void CheckAndPing(); - void RegatherOnFailedNetworks(); - void RegatherOnAllNetworks(); void LogCandidatePairConfig(Connection* conn, webrtc::IceCandidatePairConfigType type); @@ -342,10 +344,6 @@ class P2PTransportChannel : public IceTransportInternal { : static_cast(remote_ice_parameters_.size() - 1); } - // Samples a delay from the uniform distribution defined by the - // regather_on_all_networks_interval ICE configuration pair. - int SampleRegatherAllNetworksInterval(); - // Indicates if the given local port has been pruned. bool IsPortPruned(const Port* port) const; @@ -395,10 +393,7 @@ class P2PTransportChannel : public IceTransportInternal { IceRole ice_role_; uint64_t tiebreaker_; IceGatheringState gathering_state_; - - // Used to generate random intervals for regather_all_networks_interval_range. - webrtc::Random rand_; - + std::unique_ptr regathering_controller_; int64_t last_ping_sent_ms_ = 0; int weak_ping_interval_ = WEAK_PING_INTERVAL; IceTransportState state_ = IceTransportState::STATE_INIT; diff --git a/p2p/base/regatheringcontroller.cc b/p2p/base/regatheringcontroller.cc new file mode 100644 index 0000000000..c358e6ab19 --- /dev/null +++ b/p2p/base/regatheringcontroller.cc @@ -0,0 +1,157 @@ +/* + * Copyright 2018 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/regatheringcontroller.h" + +namespace webrtc { + +using Config = BasicRegatheringController::Config; + +Config::Config(const rtc::Optional& + regather_on_all_networks_interval_range, + int regather_on_failed_networks_interval) + : regather_on_all_networks_interval_range( + regather_on_all_networks_interval_range), + regather_on_failed_networks_interval( + regather_on_failed_networks_interval) {} + +Config::Config(const Config& other) = default; + +Config::~Config() = default; +Config& Config::operator=(const Config& other) = default; + +BasicRegatheringController::BasicRegatheringController( + const Config& config, + cricket::IceTransportInternal* ice_transport, + rtc::Thread* thread) + : config_(config), + ice_transport_(ice_transport), + thread_(thread), + rand_(rtc::SystemTimeNanos()) { + RTC_DCHECK(ice_transport_); + RTC_DCHECK(thread_); + ice_transport_->SignalStateChanged.connect( + this, &BasicRegatheringController::OnIceTransportStateChanged); + ice_transport->SignalWritableState.connect( + this, &BasicRegatheringController::OnIceTransportWritableState); + ice_transport->SignalReceivingState.connect( + this, &BasicRegatheringController::OnIceTransportReceivingState); + ice_transport->SignalNetworkRouteChanged.connect( + this, &BasicRegatheringController::OnIceTransportNetworkRouteChanged); +} + +BasicRegatheringController::~BasicRegatheringController() = default; + +void BasicRegatheringController::Start() { + ScheduleRecurringRegatheringOnFailedNetworks(); + if (config_.regather_on_all_networks_interval_range) { + ScheduleRecurringRegatheringOnAllNetworks(); + } +} + +void BasicRegatheringController::SetConfig(const Config& config) { + bool need_cancel_on_all_networks = + has_recurring_schedule_on_all_networks_ && + (config_.regather_on_all_networks_interval_range != + config.regather_on_all_networks_interval_range); + bool need_reschedule_on_all_networks = + config.regather_on_all_networks_interval_range && + (config_.regather_on_all_networks_interval_range != + config.regather_on_all_networks_interval_range); + bool need_cancel_and_reschedule_on_failed_networks = + has_recurring_schedule_on_failed_networks_ && + (config_.regather_on_failed_networks_interval != + config.regather_on_failed_networks_interval); + config_ = config; + if (need_cancel_on_all_networks) { + CancelScheduledRecurringRegatheringOnAllNetworks(); + } + if (need_reschedule_on_all_networks) { + ScheduleRecurringRegatheringOnAllNetworks(); + } + if (need_cancel_and_reschedule_on_failed_networks) { + CancelScheduledRecurringRegatheringOnFailedNetworks(); + ScheduleRecurringRegatheringOnFailedNetworks(); + } +} + +void BasicRegatheringController::ScheduleRecurringRegatheringOnAllNetworks() { + RTC_DCHECK(config_.regather_on_all_networks_interval_range && + config_.regather_on_all_networks_interval_range.value().min() >= + 0); + int delay_ms = SampleRegatherAllNetworksInterval( + config_.regather_on_all_networks_interval_range.value()); + CancelScheduledRecurringRegatheringOnAllNetworks(); + has_recurring_schedule_on_all_networks_ = true; + invoker_for_all_networks_.AsyncInvokeDelayed( + RTC_FROM_HERE, thread(), + rtc::Bind( + &BasicRegatheringController::RegatherOnAllNetworksIfDoneGathering, + this, true), + delay_ms); +} + +void BasicRegatheringController::RegatherOnAllNetworksIfDoneGathering( + bool repeated) { + // Only regather when the current session is in the CLEARED state (i.e., not + // running or stopped). It is only possible to enter this state when we gather + // continually, so there is an implicit check on continual gathering here. + if (allocator_session_ && allocator_session_->IsCleared()) { + allocator_session_->RegatherOnAllNetworks(); + } + if (repeated) { + ScheduleRecurringRegatheringOnAllNetworks(); + } +} + +void BasicRegatheringController:: + ScheduleRecurringRegatheringOnFailedNetworks() { + RTC_DCHECK(config_.regather_on_failed_networks_interval >= 0); + CancelScheduledRecurringRegatheringOnFailedNetworks(); + has_recurring_schedule_on_failed_networks_ = true; + invoker_for_failed_networks_.AsyncInvokeDelayed( + RTC_FROM_HERE, thread(), + rtc::Bind( + &BasicRegatheringController::RegatherOnFailedNetworksIfDoneGathering, + this, true), + config_.regather_on_failed_networks_interval); +} + +void BasicRegatheringController::RegatherOnFailedNetworksIfDoneGathering( + bool repeated) { + // Only regather when the current session is in the CLEARED state (i.e., not + // running or stopped). It is only possible to enter this state when we gather + // continually, so there is an implicit check on continual gathering here. + if (allocator_session_ && allocator_session_->IsCleared()) { + allocator_session_->RegatherOnFailedNetworks(); + } + if (repeated) { + ScheduleRecurringRegatheringOnFailedNetworks(); + } +} + +void BasicRegatheringController:: + CancelScheduledRecurringRegatheringOnAllNetworks() { + invoker_for_all_networks_.Clear(); + has_recurring_schedule_on_all_networks_ = false; +} + +void BasicRegatheringController:: + CancelScheduledRecurringRegatheringOnFailedNetworks() { + invoker_for_failed_networks_.Clear(); + has_recurring_schedule_on_failed_networks_ = false; +} + +int BasicRegatheringController::SampleRegatherAllNetworksInterval( + const rtc::IntervalRange& range) { + return rand_.Rand(range.min(), range.max()); +} + +} // namespace webrtc diff --git a/p2p/base/regatheringcontroller.h b/p2p/base/regatheringcontroller.h new file mode 100644 index 0000000000..e030ff097c --- /dev/null +++ b/p2p/base/regatheringcontroller.h @@ -0,0 +1,124 @@ +/* + * Copyright 2018 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_REGATHERINGCONTROLLER_H_ +#define P2P_BASE_REGATHERINGCONTROLLER_H_ + +#include "p2p/base/icetransportinternal.h" +#include "p2p/base/portallocator.h" +#include "rtc_base/asyncinvoker.h" +#include "rtc_base/random.h" +#include "rtc_base/thread.h" + +namespace webrtc { + +// Controls regathering of candidates for the ICE transport passed into it, +// reacting to signals like SignalWritableState, SignalNetworkRouteChange, etc., +// using methods like GetStats to get additional information, and calling +// methods like RegatherOnAllNetworks on the PortAllocatorSession when +// regathering is desired. +// +// TODO(qingsi): Add the description of behavior when autonomous regathering is +// implemented. +// +// "Regathering" is defined as gathering additional candidates within a single +// ICE generation (or in other words, PortAllocatorSession), and is possible +// when "continual gathering" is enabled. This may allow connectivity to be +// maintained and/or restored without a full ICE restart. +// +// Regathering will only begin after PortAllocationSession is set via +// set_allocator_session. This should be called any time the "active" +// PortAllocatorSession is changed (in other words, when an ICE restart occurs), +// so that candidates are gathered for the "current" ICE generation. +// +// All methods of BasicRegatheringController should be called on the same +// thread as the one passed to the constructor, and this thread should be the +// same one where PortAllocatorSession runs, which is also identical to the +// network thread of the ICE transport, as given by +// P2PTransportChannel::thread(). +class BasicRegatheringController : public sigslot::has_slots<> { + public: + struct Config { + Config(const rtc::Optional& + regather_on_all_networks_interval_range, + int regather_on_failed_networks_interval); + Config(const Config& other); + ~Config(); + Config& operator=(const Config& other); + rtc::Optional regather_on_all_networks_interval_range; + int regather_on_failed_networks_interval; + }; + + BasicRegatheringController() = delete; + BasicRegatheringController(const Config& config, + cricket::IceTransportInternal* ice_transport, + rtc::Thread* thread); + ~BasicRegatheringController() override; + // TODO(qingsi): Remove this method after implementing a new signal in + // P2PTransportChannel and reacting to that signal for the initial schedules + // of regathering. + void Start(); + void set_allocator_session(cricket::PortAllocatorSession* allocator_session) { + allocator_session_ = allocator_session; + } + // Setting a different config of the regathering interval range on all + // networks cancels and reschedules the recurring schedules, if any, of + // regathering on all networks. The same applies to the change of the + // regathering interval on the failed networks. This rescheduling behavior is + // seperately defined for the two config parameters. + void SetConfig(const Config& config); + + private: + // TODO(qingsi): Implement the following methods and use methods from the ICE + // transport like GetStats to get additional information for the decision + // making in regathering. + void OnIceTransportStateChanged(cricket::IceTransportInternal*) {} + void OnIceTransportWritableState(rtc::PacketTransportInternal*) {} + void OnIceTransportReceivingState(rtc::PacketTransportInternal*) {} + void OnIceTransportNetworkRouteChanged(rtc::Optional) {} + // Schedules delayed and repeated regathering of local candidates on all + // networks, where the delay in milliseconds is randomly sampled from the + // range in the config. The delay of each repetition is independently sampled + // from the same range. When scheduled, all previous schedules are canceled. + void ScheduleRecurringRegatheringOnAllNetworks(); + // Schedules delayed and repeated regathering of local candidates on failed + // networks, where the delay in milliseconds is given by the config. Each + // repetition is separated by the same delay. When scheduled, all previous + // schedules are canceled. + void ScheduleRecurringRegatheringOnFailedNetworks(); + // Cancels regathering scheduled by ScheduleRecurringRegatheringOnAllNetworks. + void CancelScheduledRecurringRegatheringOnAllNetworks(); + // Cancels regathering scheduled by + // ScheduleRecurringRegatheringOnFailedNetworks. + void CancelScheduledRecurringRegatheringOnFailedNetworks(); + + rtc::Thread* thread() const { return thread_; } + // The following two methods perform the actual regathering, if the recent + // port allocator session has done the initial gathering. + void RegatherOnAllNetworksIfDoneGathering(bool repeated); + void RegatherOnFailedNetworksIfDoneGathering(bool repeated); + // Samples a delay from the uniform distribution in the given range. + int SampleRegatherAllNetworksInterval(const rtc::IntervalRange& range); + + Config config_; + cricket::IceTransportInternal* ice_transport_; + cricket::PortAllocatorSession* allocator_session_ = nullptr; + bool has_recurring_schedule_on_all_networks_ = false; + bool has_recurring_schedule_on_failed_networks_ = false; + rtc::Thread* thread_; + rtc::AsyncInvoker invoker_for_all_networks_; + rtc::AsyncInvoker invoker_for_failed_networks_; + // Used to generate random intervals for regather_all_networks_interval_range. + Random rand_; +}; + +} // namespace webrtc + +#endif // P2P_BASE_REGATHERINGCONTROLLER_H_ diff --git a/p2p/base/regatheringcontroller_unittest.cc b/p2p/base/regatheringcontroller_unittest.cc new file mode 100644 index 0000000000..798611b1ed --- /dev/null +++ b/p2p/base/regatheringcontroller_unittest.cc @@ -0,0 +1,308 @@ +/* + * Copyright 2018 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 +#include +#include +#include + +#include "api/fakemetricsobserver.h" +#include "p2p/base/fakeportallocator.h" +#include "p2p/base/mockicetransport.h" +#include "p2p/base/p2pconstants.h" +#include "p2p/base/port.h" +#include "p2p/base/regatheringcontroller.h" +#include "p2p/base/stunserver.h" +#include "rtc_base/gunit.h" +#include "rtc_base/refcountedobject.h" +#include "rtc_base/scoped_ref_ptr.h" +#include "rtc_base/socketaddress.h" +#include "rtc_base/thread.h" +#include "rtc_base/virtualsocketserver.h" + +namespace { + +const int kOnlyLocalPorts = cricket::PORTALLOCATOR_DISABLE_STUN | + cricket::PORTALLOCATOR_DISABLE_RELAY | + cricket::PORTALLOCATOR_DISABLE_TCP; +// The address of the public STUN server. +const rtc::SocketAddress kStunAddr("99.99.99.1", cricket::STUN_SERVER_PORT); +// The addresses for the public TURN server. +const rtc::SocketAddress kTurnUdpIntAddr("99.99.99.3", + cricket::STUN_SERVER_PORT); +const cricket::RelayCredentials kRelayCredentials("test", "test"); +const char kIceUfrag[] = "UF00"; +const char kIcePwd[] = "TESTICEPWD00000000000000"; + +} // namespace + +namespace webrtc { + +class RegatheringControllerTest : public testing::Test, + public sigslot::has_slots<> { + public: + RegatheringControllerTest() + : vss_(new rtc::VirtualSocketServer()), + thread_(vss_.get()), + ice_transport_(new cricket::MockIceTransport()), + allocator_( + new cricket::FakePortAllocator(rtc::Thread::Current(), nullptr)) { + BasicRegatheringController::Config regathering_config(rtc::nullopt, 0); + regathering_controller_.reset(new BasicRegatheringController( + regathering_config, ice_transport_.get(), rtc::Thread::Current())); + } + + // Initializes the allocator and gathers candidates once by StartGettingPorts. + void InitializeAndGatherOnce() { + cricket::ServerAddresses stun_servers; + stun_servers.insert(kStunAddr); + cricket::RelayServerConfig turn_server(cricket::RELAY_TURN); + turn_server.credentials = kRelayCredentials; + turn_server.ports.push_back( + cricket::ProtocolAddress(kTurnUdpIntAddr, cricket::PROTO_UDP)); + std::vector turn_servers(1, turn_server); + allocator_->set_flags(kOnlyLocalPorts); + allocator_->SetConfiguration(stun_servers, turn_servers, 0 /* pool size */, + false /* prune turn ports */); + allocator_session_ = allocator_->CreateSession( + "test", cricket::ICE_CANDIDATE_COMPONENT_RTP, kIceUfrag, kIcePwd); + // The gathering will take place on the current thread and the following + // call of StartGettingPorts is blocking. We will not ClearGettingPorts + // prematurely. + allocator_session_->StartGettingPorts(); + allocator_session_->SignalIceRegathering.connect( + this, &RegatheringControllerTest::OnIceRegathering); + regathering_controller_->set_allocator_session(allocator_session_.get()); + } + + // The regathering controller is initialized with the allocator session + // cleared. Only after clearing the session, we would be able to regather. See + // the comments for BasicRegatheringController in regatheringcontroller.h. + void InitializeAndGatherOnceWithSessionCleared() { + InitializeAndGatherOnce(); + allocator_session_->ClearGettingPorts(); + } + + void OnIceRegathering(cricket::PortAllocatorSession* allocator_session, + cricket::IceRegatheringReason reason) { + ++count_[reason]; + } + + int GetRegatheringReasonCount(cricket::IceRegatheringReason reason) { + return count_[reason]; + } + + BasicRegatheringController* regathering_controller() { + return regathering_controller_.get(); + } + + private: + std::unique_ptr vss_; + rtc::AutoSocketServerThread thread_; + std::unique_ptr ice_transport_; + std::unique_ptr regathering_controller_; + std::unique_ptr allocator_; + std::unique_ptr allocator_session_; + std::map count_; +}; + +// Tests that ICE regathering occurs only if the port allocator session is +// cleared. A port allocation session is not cleared if the initial gathering is +// still in progress or the continual gathering is not enabled. +TEST_F(RegatheringControllerTest, + IceRegatheringDoesNotOccurIfSessionNotCleared) { + rtc::ScopedFakeClock clock; + InitializeAndGatherOnce(); // Session not cleared. + + rtc::IntervalRange regather_all_networks_interval_range(2000, 2000); + BasicRegatheringController::Config config( + regather_all_networks_interval_range, 2000); + regathering_controller()->SetConfig(config); + regathering_controller()->Start(); + SIMULATED_WAIT(false, 10000, clock); + // Expect no regathering in the last 10s. + EXPECT_EQ(0, GetRegatheringReasonCount( + cricket::IceRegatheringReason::OCCASIONAL_REFRESH)); + EXPECT_EQ(0, GetRegatheringReasonCount( + cricket::IceRegatheringReason::NETWORK_FAILURE)); +} + +TEST_F(RegatheringControllerTest, IceRegatheringRepeatsAsScheduled) { + rtc::ScopedFakeClock clock; + InitializeAndGatherOnceWithSessionCleared(); + + rtc::IntervalRange regather_all_networks_interval_range(2000, 2000); + BasicRegatheringController::Config config( + regather_all_networks_interval_range, 2000); + regathering_controller()->SetConfig(config); + regathering_controller()->Start(); + SIMULATED_WAIT(false, 2000 - 1, clock); + // Expect no regathering. + EXPECT_EQ(0, GetRegatheringReasonCount( + cricket::IceRegatheringReason::OCCASIONAL_REFRESH)); + EXPECT_EQ(0, GetRegatheringReasonCount( + cricket::IceRegatheringReason::NETWORK_FAILURE)); + SIMULATED_WAIT(false, 2, clock); + // Expect regathering on all networks and on failed networks to happen once + // respectively in that last 2s with 2s interval. + EXPECT_EQ(1, GetRegatheringReasonCount( + cricket::IceRegatheringReason::OCCASIONAL_REFRESH)); + EXPECT_EQ(1, GetRegatheringReasonCount( + cricket::IceRegatheringReason::NETWORK_FAILURE)); + SIMULATED_WAIT(false, 11000, clock); + // Expect regathering to happen for another 5 times in 11s with 2s interval. + EXPECT_EQ(6, GetRegatheringReasonCount( + cricket::IceRegatheringReason::OCCASIONAL_REFRESH)); + EXPECT_EQ(6, GetRegatheringReasonCount( + cricket::IceRegatheringReason::NETWORK_FAILURE)); +} + +// Tests that the schedule of ICE regathering on all networks can be started +// when not scheduled initially. +TEST_F(RegatheringControllerTest, + IceRegatheringOnAllNetworksCanBeScheduledAfterStart) { + rtc::ScopedFakeClock clock; + InitializeAndGatherOnceWithSessionCleared(); + + BasicRegatheringController::Config config(rtc::nullopt, 2000); + regathering_controller()->SetConfig(config); + regathering_controller()->Start(); + SIMULATED_WAIT(false, 3000, clock); + // Expect no regathering on all networks. + EXPECT_EQ(0, GetRegatheringReasonCount( + cricket::IceRegatheringReason::OCCASIONAL_REFRESH)); + config.regather_on_all_networks_interval_range = + rtc::IntervalRange(2000, 2000); + regathering_controller()->SetConfig(config); + SIMULATED_WAIT(false, 11000, clock); + // Expect regathering to happen for 5 times on all networks in the last 11s + // with 2s interval. + EXPECT_EQ(5, GetRegatheringReasonCount( + cricket::IceRegatheringReason::OCCASIONAL_REFRESH)); +} + +// Tests that ICE regathering on all networks can be canceled by changing the +// config. +TEST_F(RegatheringControllerTest, IceRegatheringOnAllNetworksCanBeCanceled) { + rtc::ScopedFakeClock clock; + InitializeAndGatherOnceWithSessionCleared(); + + rtc::IntervalRange regather_all_networks_interval_range(2000, 2000); + BasicRegatheringController::Config config( + regather_all_networks_interval_range, 2000); + regathering_controller()->SetConfig(config); + regathering_controller()->Start(); + config.regather_on_all_networks_interval_range.reset(); + // Set the regathering interval range on all networks to nullopt should cancel + // the schedule on all networks. + regathering_controller()->SetConfig(config); + SIMULATED_WAIT(false, 10000, clock); + // Expect no regathering on all networks happened in the last 10s. + EXPECT_EQ(0, GetRegatheringReasonCount( + cricket::IceRegatheringReason::OCCASIONAL_REFRESH)); +} + +// Tests that canceling the regathering on all networks does not cancel the +// schedule on failed networks. +TEST_F(RegatheringControllerTest, + CancelingRegatheringOnAllNetworksDoesNotCancelOnFailedNetworks) { + rtc::ScopedFakeClock clock; + InitializeAndGatherOnceWithSessionCleared(); + + rtc::IntervalRange regather_all_networks_interval_range(2000, 2000); + BasicRegatheringController::Config config( + regather_all_networks_interval_range, 2000); + regathering_controller()->SetConfig(config); + regathering_controller()->Start(); + config.regather_on_all_networks_interval_range = + rtc::IntervalRange(20000, 20000); + // Canceling and rescheduling the regathering on all networks should not + // impact the schedule for failed networks. + regathering_controller()->SetConfig(config); + SIMULATED_WAIT(false, 11000, clock); + // Expect regathering to happen for 5 times for failed networks in the last + // 11s with 2s interval. + EXPECT_EQ(5, GetRegatheringReasonCount( + cricket::IceRegatheringReason::NETWORK_FAILURE)); +} + +// Tests that canceling the regathering on failed networks does not cancel the +// schedule on all networks. +TEST_F(RegatheringControllerTest, + CancelingRegatheringOnFailedNetworksDoesNotCancelOnAllNetworks) { + rtc::ScopedFakeClock clock; + InitializeAndGatherOnceWithSessionCleared(); + + rtc::IntervalRange regather_all_networks_interval_range(2000, 2000); + BasicRegatheringController::Config config( + regather_all_networks_interval_range, 2000); + regathering_controller()->SetConfig(config); + regathering_controller()->Start(); + config.regather_on_failed_networks_interval = 20000; + // Canceling and rescheduling the regathering on failed networks should not + // impact the schedule for all networks. + regathering_controller()->SetConfig(config); + SIMULATED_WAIT(false, 11000, clock); + // Expect regathering to happen for 5 times for all networks in the last 11s + // with 2s interval. + EXPECT_EQ(5, GetRegatheringReasonCount( + cricket::IceRegatheringReason::OCCASIONAL_REFRESH)); +} + +// Tests that the schedule of ICE regathering on all networks can be canceled +// and replaced by a new recurring schedule. +TEST_F(RegatheringControllerTest, + ScheduleOfIceRegatheringOnAllNetworksCanBeReplaced) { + rtc::ScopedFakeClock clock; + InitializeAndGatherOnceWithSessionCleared(); + + rtc::IntervalRange regather_all_networks_interval_range(2000, 2000); + BasicRegatheringController::Config config( + regather_all_networks_interval_range, 2000); + regathering_controller()->SetConfig(config); + regathering_controller()->Start(); + config.regather_on_all_networks_interval_range = + rtc::IntervalRange(5000, 5000); + regathering_controller()->SetConfig(config); + SIMULATED_WAIT(false, 3000, clock); + // Expect no regathering from the previous schedule. + EXPECT_EQ(0, GetRegatheringReasonCount( + cricket::IceRegatheringReason::OCCASIONAL_REFRESH)); + SIMULATED_WAIT(false, 11000 - 3000, clock); + // Expect regathering to happen twice in the last 11s with 5s interval. + EXPECT_EQ(2, GetRegatheringReasonCount( + cricket::IceRegatheringReason::OCCASIONAL_REFRESH)); +} + +// Tests that the schedule of ICE regathering on failed networks can be canceled +// and replaced by a new recurring schedule. +TEST_F(RegatheringControllerTest, + ScheduleOfIceRegatheringOnFailedNetworksCanBeReplaced) { + rtc::ScopedFakeClock clock; + InitializeAndGatherOnceWithSessionCleared(); + + rtc::IntervalRange regather_all_networks_interval_range(2000, 2000); + BasicRegatheringController::Config config( + regather_all_networks_interval_range, 2000); + regathering_controller()->SetConfig(config); + regathering_controller()->Start(); + config.regather_on_failed_networks_interval = 5000; + regathering_controller()->SetConfig(config); + SIMULATED_WAIT(false, 3000, clock); + // Expect no regathering from the previous schedule. + EXPECT_EQ(0, GetRegatheringReasonCount( + cricket::IceRegatheringReason::NETWORK_FAILURE)); + SIMULATED_WAIT(false, 11000 - 3000, clock); + // Expect regathering to happen twice in the last 11s with 5s interval. + EXPECT_EQ(2, GetRegatheringReasonCount( + cricket::IceRegatheringReason::NETWORK_FAILURE)); +} + +} // namespace webrtc