mirror of
https://github.com/mollyim/webrtc.git
synced 2025-05-13 05:40:42 +01:00
Add an active ICE controller that wraps a legacy controller (#7/n)
The wrapping ICE controller will allow existing ICE controller implementations to migrate to the active interface, and eventually deprecate the legacy interface. Follow-up CL has unit tests for P2PTransportChannel using the new wrapping controller. Bug: webrtc:14367, webrtc:14131 Change-Id: I6c517449ff1e503e8268a7ef91afda793723fdeb Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/275302 Reviewed-by: Per Kjellander <perkj@webrtc.org> Reviewed-by: Jonas Oreland <jonaso@webrtc.org> Commit-Queue: Sameer Vijaykar <samvi@google.com> Cr-Commit-Position: refs/heads/main@{#38130}
This commit is contained in:
parent
56b96ffe6a
commit
6326c9c201
7 changed files with 729 additions and 6 deletions
|
@ -99,8 +99,10 @@ struct IceTransportInit final {
|
|||
// constructed and used.
|
||||
//
|
||||
// 2. If the field trial is enabled
|
||||
// - then an active ICE controller factory must be supplied and is used.
|
||||
// - the legacy ICE controller factory is not used in this case.
|
||||
// a. If an active ICE controller factory is supplied, it is used and
|
||||
// the legacy ICE controller factory is not used.
|
||||
// b. If not, a default active ICE controller is used, wrapping over the
|
||||
// supplied or the default legacy ICE controller.
|
||||
void set_active_ice_controller_factory(
|
||||
cricket::ActiveIceControllerFactoryInterface*
|
||||
active_ice_controller_factory) {
|
||||
|
|
|
@ -81,6 +81,8 @@ rtc_library("rtc_p2p") {
|
|||
"base/turn_port.cc",
|
||||
"base/turn_port.h",
|
||||
"base/udp_port.h",
|
||||
"base/wrapping_active_ice_controller.cc",
|
||||
"base/wrapping_active_ice_controller.h",
|
||||
"client/basic_port_allocator.cc",
|
||||
"client/basic_port_allocator.h",
|
||||
"client/relay_port_factory_interface.h",
|
||||
|
@ -201,6 +203,7 @@ if (rtc_include_tests) {
|
|||
"base/fake_packet_transport.h",
|
||||
"base/mock_active_ice_controller.h",
|
||||
"base/mock_async_resolver.h",
|
||||
"base/mock_ice_agent.h",
|
||||
"base/mock_ice_controller.h",
|
||||
"base/mock_ice_transport.h",
|
||||
"base/test_stun_server.cc",
|
||||
|
@ -260,6 +263,7 @@ if (rtc_include_tests) {
|
|||
"base/transport_description_unittest.cc",
|
||||
"base/turn_port_unittest.cc",
|
||||
"base/turn_server_unittest.cc",
|
||||
"base/wrapping_active_ice_controller_unittest.cc",
|
||||
"client/basic_port_allocator_unittest.cc",
|
||||
]
|
||||
deps = [
|
||||
|
|
50
p2p/base/mock_ice_agent.h
Normal file
50
p2p/base/mock_ice_agent.h
Normal file
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* 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_MOCK_ICE_AGENT_H_
|
||||
#define P2P_BASE_MOCK_ICE_AGENT_H_
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "p2p/base/connection.h"
|
||||
#include "p2p/base/ice_agent_interface.h"
|
||||
#include "p2p/base/ice_switch_reason.h"
|
||||
#include "p2p/base/transport_description.h"
|
||||
#include "test/gmock.h"
|
||||
|
||||
namespace cricket {
|
||||
|
||||
class MockIceAgent : public IceAgentInterface {
|
||||
public:
|
||||
~MockIceAgent() override = default;
|
||||
|
||||
MOCK_METHOD(int64_t, GetLastPingSentMs, (), (override, const));
|
||||
MOCK_METHOD(IceRole, GetIceRole, (), (override, const));
|
||||
MOCK_METHOD(void, OnStartedPinging, (), (override));
|
||||
MOCK_METHOD(void, UpdateConnectionStates, (), (override));
|
||||
MOCK_METHOD(void, UpdateState, (), (override));
|
||||
MOCK_METHOD(void,
|
||||
ForgetLearnedStateForConnections,
|
||||
(std::vector<const Connection*>),
|
||||
(override));
|
||||
MOCK_METHOD(void, SendPingRequest, (const Connection*), (override));
|
||||
MOCK_METHOD(void,
|
||||
SwitchSelectedConnection,
|
||||
(const Connection*, IceSwitchReason),
|
||||
(override));
|
||||
MOCK_METHOD(bool,
|
||||
PruneConnections,
|
||||
(std::vector<const Connection*>),
|
||||
(override));
|
||||
};
|
||||
|
||||
} // namespace cricket
|
||||
|
||||
#endif // P2P_BASE_MOCK_ICE_AGENT_H_
|
|
@ -33,6 +33,7 @@
|
|||
#include "p2p/base/connection.h"
|
||||
#include "p2p/base/connection_info.h"
|
||||
#include "p2p/base/port.h"
|
||||
#include "p2p/base/wrapping_active_ice_controller.h"
|
||||
#include "rtc_base/checks.h"
|
||||
#include "rtc_base/crc32.h"
|
||||
#include "rtc_base/experiments/struct_parameters_parser.h"
|
||||
|
@ -2472,10 +2473,15 @@ P2PTransportChannel::IceControllerAdapter::IceControllerAdapter(
|
|||
P2PTransportChannel* transport)
|
||||
: transport_(transport) {
|
||||
if (UseActiveIceControllerFieldTrialEnabled(field_trials)) {
|
||||
RTC_DCHECK(active_ice_controller_factory);
|
||||
ActiveIceControllerFactoryArgs active_args{args,
|
||||
/* ice_agent= */ transport};
|
||||
active_ice_controller_ = active_ice_controller_factory->Create(active_args);
|
||||
if (active_ice_controller_factory) {
|
||||
ActiveIceControllerFactoryArgs active_args{args,
|
||||
/* ice_agent= */ transport};
|
||||
active_ice_controller_ =
|
||||
active_ice_controller_factory->Create(active_args);
|
||||
} else {
|
||||
active_ice_controller_ = std::make_unique<WrappingActiveIceController>(
|
||||
/* ice_agent= */ transport, ice_controller_factory, args);
|
||||
}
|
||||
} else {
|
||||
if (ice_controller_factory != nullptr) {
|
||||
legacy_ice_controller_ = ice_controller_factory->Create(args);
|
||||
|
|
253
p2p/base/wrapping_active_ice_controller.cc
Normal file
253
p2p/base/wrapping_active_ice_controller.cc
Normal file
|
@ -0,0 +1,253 @@
|
|||
/*
|
||||
* Copyright 2022 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/wrapping_active_ice_controller.h"
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "api/sequence_checker.h"
|
||||
#include "api/task_queue/pending_task_safety_flag.h"
|
||||
#include "api/units/time_delta.h"
|
||||
#include "p2p/base/basic_ice_controller.h"
|
||||
#include "p2p/base/connection.h"
|
||||
#include "p2p/base/ice_agent_interface.h"
|
||||
#include "p2p/base/ice_controller_interface.h"
|
||||
#include "p2p/base/ice_switch_reason.h"
|
||||
#include "p2p/base/ice_transport_internal.h"
|
||||
#include "p2p/base/transport_description.h"
|
||||
#include "rtc_base/logging.h"
|
||||
#include "rtc_base/thread.h"
|
||||
#include "rtc_base/time_utils.h"
|
||||
|
||||
namespace {
|
||||
using ::webrtc::SafeTask;
|
||||
using ::webrtc::TimeDelta;
|
||||
} // unnamed namespace
|
||||
|
||||
namespace cricket {
|
||||
|
||||
WrappingActiveIceController::WrappingActiveIceController(
|
||||
IceAgentInterface* ice_agent,
|
||||
std::unique_ptr<IceControllerInterface> wrapped)
|
||||
: network_thread_(rtc::Thread::Current()),
|
||||
wrapped_(std::move(wrapped)),
|
||||
agent_(*ice_agent) {
|
||||
RTC_DCHECK(ice_agent != nullptr);
|
||||
}
|
||||
|
||||
WrappingActiveIceController::WrappingActiveIceController(
|
||||
IceAgentInterface* ice_agent,
|
||||
IceControllerFactoryInterface* wrapped_factory,
|
||||
const IceControllerFactoryArgs& wrapped_factory_args)
|
||||
: network_thread_(rtc::Thread::Current()), agent_(*ice_agent) {
|
||||
RTC_DCHECK(ice_agent != nullptr);
|
||||
if (wrapped_factory) {
|
||||
wrapped_ = wrapped_factory->Create(wrapped_factory_args);
|
||||
} else {
|
||||
wrapped_ = std::make_unique<BasicIceController>(wrapped_factory_args);
|
||||
}
|
||||
}
|
||||
|
||||
WrappingActiveIceController::~WrappingActiveIceController() {}
|
||||
|
||||
void WrappingActiveIceController::SetIceConfig(const IceConfig& config) {
|
||||
RTC_DCHECK_RUN_ON(network_thread_);
|
||||
wrapped_->SetIceConfig(config);
|
||||
}
|
||||
|
||||
bool WrappingActiveIceController::GetUseCandidateAttribute(
|
||||
const Connection* connection,
|
||||
NominationMode mode,
|
||||
IceMode remote_ice_mode) const {
|
||||
RTC_DCHECK_RUN_ON(network_thread_);
|
||||
return wrapped_->GetUseCandidateAttr(connection, mode, remote_ice_mode);
|
||||
}
|
||||
|
||||
void WrappingActiveIceController::OnConnectionAdded(
|
||||
const Connection* connection) {
|
||||
RTC_DCHECK_RUN_ON(network_thread_);
|
||||
wrapped_->AddConnection(connection);
|
||||
}
|
||||
|
||||
void WrappingActiveIceController::OnConnectionPinged(
|
||||
const Connection* connection) {
|
||||
RTC_DCHECK_RUN_ON(network_thread_);
|
||||
wrapped_->MarkConnectionPinged(connection);
|
||||
}
|
||||
|
||||
void WrappingActiveIceController::OnConnectionUpdated(
|
||||
const Connection* connection) {
|
||||
RTC_LOG(LS_VERBOSE) << "Connection report for " << connection->ToString();
|
||||
// Do nothing. Native ICE controllers have direct access to Connection, so no
|
||||
// need to update connection state separately.
|
||||
}
|
||||
|
||||
void WrappingActiveIceController::OnConnectionSwitched(
|
||||
const Connection* connection) {
|
||||
RTC_DCHECK_RUN_ON(network_thread_);
|
||||
selected_connection_ = connection;
|
||||
wrapped_->SetSelectedConnection(connection);
|
||||
}
|
||||
|
||||
void WrappingActiveIceController::OnConnectionDestroyed(
|
||||
const Connection* connection) {
|
||||
RTC_DCHECK_RUN_ON(network_thread_);
|
||||
wrapped_->OnConnectionDestroyed(connection);
|
||||
}
|
||||
|
||||
void WrappingActiveIceController::MaybeStartPinging() {
|
||||
RTC_DCHECK_RUN_ON(network_thread_);
|
||||
if (started_pinging_) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (wrapped_->HasPingableConnection()) {
|
||||
network_thread_->PostTask(
|
||||
SafeTask(task_safety_.flag(), [this]() { SelectAndPingConnection(); }));
|
||||
agent_.OnStartedPinging();
|
||||
started_pinging_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
void WrappingActiveIceController::SelectAndPingConnection() {
|
||||
RTC_DCHECK_RUN_ON(network_thread_);
|
||||
agent_.UpdateConnectionStates();
|
||||
|
||||
IceControllerInterface::PingResult result =
|
||||
wrapped_->SelectConnectionToPing(agent_.GetLastPingSentMs());
|
||||
HandlePingResult(result);
|
||||
}
|
||||
|
||||
void WrappingActiveIceController::HandlePingResult(
|
||||
IceControllerInterface::PingResult result) {
|
||||
RTC_DCHECK_RUN_ON(network_thread_);
|
||||
|
||||
if (result.connection.has_value()) {
|
||||
agent_.SendPingRequest(result.connection.value());
|
||||
}
|
||||
|
||||
network_thread_->PostDelayedTask(
|
||||
SafeTask(task_safety_.flag(), [this]() { SelectAndPingConnection(); }),
|
||||
TimeDelta::Millis(result.recheck_delay_ms));
|
||||
}
|
||||
|
||||
void WrappingActiveIceController::OnSortAndSwitchRequest(
|
||||
IceSwitchReason reason) {
|
||||
RTC_DCHECK_RUN_ON(network_thread_);
|
||||
if (!sort_pending_) {
|
||||
network_thread_->PostTask(SafeTask(task_safety_.flag(), [this, reason]() {
|
||||
SortAndSwitchToBestConnection(reason);
|
||||
}));
|
||||
sort_pending_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
void WrappingActiveIceController::OnImmediateSortAndSwitchRequest(
|
||||
IceSwitchReason reason) {
|
||||
RTC_DCHECK_RUN_ON(network_thread_);
|
||||
SortAndSwitchToBestConnection(reason);
|
||||
}
|
||||
|
||||
void WrappingActiveIceController::SortAndSwitchToBestConnection(
|
||||
IceSwitchReason reason) {
|
||||
RTC_DCHECK_RUN_ON(network_thread_);
|
||||
|
||||
// Make sure the connection states are up-to-date since this affects how they
|
||||
// will be sorted.
|
||||
agent_.UpdateConnectionStates();
|
||||
|
||||
// Any changes after this point will require a re-sort.
|
||||
sort_pending_ = false;
|
||||
|
||||
IceControllerInterface::SwitchResult result =
|
||||
wrapped_->SortAndSwitchConnection(reason);
|
||||
HandleSwitchResult(reason, result);
|
||||
UpdateStateOnConnectionsResorted();
|
||||
}
|
||||
|
||||
bool WrappingActiveIceController::OnImmediateSwitchRequest(
|
||||
IceSwitchReason reason,
|
||||
const Connection* selected) {
|
||||
RTC_DCHECK_RUN_ON(network_thread_);
|
||||
IceControllerInterface::SwitchResult result =
|
||||
wrapped_->ShouldSwitchConnection(reason, selected);
|
||||
HandleSwitchResult(reason, result);
|
||||
return result.connection.has_value();
|
||||
}
|
||||
|
||||
void WrappingActiveIceController::HandleSwitchResult(
|
||||
IceSwitchReason reason_for_switch,
|
||||
IceControllerInterface::SwitchResult result) {
|
||||
RTC_DCHECK_RUN_ON(network_thread_);
|
||||
if (result.connection.has_value()) {
|
||||
RTC_LOG(LS_INFO) << "Switching selected connection due to: "
|
||||
<< IceSwitchReasonToString(reason_for_switch);
|
||||
agent_.SwitchSelectedConnection(result.connection.value(),
|
||||
reason_for_switch);
|
||||
}
|
||||
|
||||
if (result.recheck_event.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.
|
||||
network_thread_->PostDelayedTask(
|
||||
SafeTask(task_safety_.flag(),
|
||||
[this, recheck_reason = result.recheck_event->reason]() {
|
||||
SortAndSwitchToBestConnection(recheck_reason);
|
||||
}),
|
||||
TimeDelta::Millis(result.recheck_event->recheck_delay_ms));
|
||||
}
|
||||
|
||||
agent_.ForgetLearnedStateForConnections(
|
||||
result.connections_to_forget_state_on);
|
||||
}
|
||||
|
||||
void WrappingActiveIceController::UpdateStateOnConnectionsResorted() {
|
||||
RTC_DCHECK_RUN_ON(network_thread_);
|
||||
PruneConnections();
|
||||
|
||||
// Update the internal state of the ICE agentl.
|
||||
agent_.UpdateState();
|
||||
|
||||
// Also possibly start pinging.
|
||||
// We could start pinging if:
|
||||
// * The first connection was created.
|
||||
// * ICE credentials were provided.
|
||||
// * A TCP connection became connected.
|
||||
MaybeStartPinging();
|
||||
}
|
||||
|
||||
void WrappingActiveIceController::PruneConnections() {
|
||||
RTC_DCHECK_RUN_ON(network_thread_);
|
||||
|
||||
// The controlled side can prune only if the selected connection has been
|
||||
// nominated because otherwise it may prune the connection that will be
|
||||
// selected by the controlling side.
|
||||
// TODO(honghaiz): This is not enough to prevent a connection from being
|
||||
// pruned too early because with aggressive nomination, the controlling side
|
||||
// will nominate every connection until it becomes writable.
|
||||
if (agent_.GetIceRole() == ICEROLE_CONTROLLING ||
|
||||
(selected_connection_ && selected_connection_->nominated())) {
|
||||
std::vector<const Connection*> connections_to_prune =
|
||||
wrapped_->PruneConnections();
|
||||
agent_.PruneConnections(connections_to_prune);
|
||||
}
|
||||
}
|
||||
|
||||
// Only for unit tests
|
||||
const Connection* WrappingActiveIceController::FindNextPingableConnection() {
|
||||
RTC_DCHECK_RUN_ON(network_thread_);
|
||||
return wrapped_->FindNextPingableConnection();
|
||||
}
|
||||
|
||||
} // namespace cricket
|
97
p2p/base/wrapping_active_ice_controller.h
Normal file
97
p2p/base/wrapping_active_ice_controller.h
Normal file
|
@ -0,0 +1,97 @@
|
|||
/*
|
||||
* Copyright 2022 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_WRAPPING_ACTIVE_ICE_CONTROLLER_H_
|
||||
#define P2P_BASE_WRAPPING_ACTIVE_ICE_CONTROLLER_H_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "absl/types/optional.h"
|
||||
#include "api/task_queue/pending_task_safety_flag.h"
|
||||
#include "p2p/base/active_ice_controller_interface.h"
|
||||
#include "p2p/base/connection.h"
|
||||
#include "p2p/base/ice_agent_interface.h"
|
||||
#include "p2p/base/ice_controller_factory_interface.h"
|
||||
#include "p2p/base/ice_controller_interface.h"
|
||||
#include "p2p/base/ice_switch_reason.h"
|
||||
#include "p2p/base/ice_transport_internal.h"
|
||||
#include "p2p/base/transport_description.h"
|
||||
#include "rtc_base/thread.h"
|
||||
#include "rtc_base/thread_annotations.h"
|
||||
|
||||
namespace cricket {
|
||||
|
||||
// WrappingActiveIceController provides the functionality of a legacy passive
|
||||
// ICE controller but packaged as an active ICE Controller.
|
||||
class WrappingActiveIceController : public ActiveIceControllerInterface {
|
||||
public:
|
||||
// Constructs an active ICE controller wrapping an already constructed legacy
|
||||
// ICE controller. Does not take ownership of the ICE agent, which must
|
||||
// already exist and outlive the ICE controller.
|
||||
WrappingActiveIceController(IceAgentInterface* ice_agent,
|
||||
std::unique_ptr<IceControllerInterface> wrapped);
|
||||
// Constructs an active ICE controller that wraps over a legacy ICE
|
||||
// controller. The legacy ICE controller is constructed through a factory, if
|
||||
// one is supplied. If not, a default BasicIceController is wrapped instead.
|
||||
// Does not take ownership of the ICE agent, which must already exist and
|
||||
// outlive the ICE controller.
|
||||
WrappingActiveIceController(
|
||||
IceAgentInterface* ice_agent,
|
||||
IceControllerFactoryInterface* wrapped_factory,
|
||||
const IceControllerFactoryArgs& wrapped_factory_args);
|
||||
virtual ~WrappingActiveIceController();
|
||||
|
||||
void SetIceConfig(const IceConfig& config) override;
|
||||
bool GetUseCandidateAttribute(const Connection* connection,
|
||||
NominationMode mode,
|
||||
IceMode remote_ice_mode) const override;
|
||||
|
||||
void OnConnectionAdded(const Connection* connection) override;
|
||||
void OnConnectionPinged(const Connection* connection) override;
|
||||
void OnConnectionUpdated(const Connection* connection) override;
|
||||
void OnConnectionSwitched(const Connection* connection) override;
|
||||
void OnConnectionDestroyed(const Connection* connection) override;
|
||||
|
||||
void OnSortAndSwitchRequest(IceSwitchReason reason) override;
|
||||
void OnImmediateSortAndSwitchRequest(IceSwitchReason reason) override;
|
||||
bool OnImmediateSwitchRequest(IceSwitchReason reason,
|
||||
const Connection* selected) override;
|
||||
|
||||
// Only for unit tests
|
||||
const Connection* FindNextPingableConnection() override;
|
||||
|
||||
private:
|
||||
void MaybeStartPinging();
|
||||
void SelectAndPingConnection();
|
||||
void HandlePingResult(IceControllerInterface::PingResult result);
|
||||
|
||||
void SortAndSwitchToBestConnection(IceSwitchReason reason);
|
||||
void HandleSwitchResult(IceSwitchReason reason_for_switch,
|
||||
IceControllerInterface::SwitchResult result);
|
||||
void UpdateStateOnConnectionsResorted();
|
||||
|
||||
void PruneConnections();
|
||||
|
||||
rtc::Thread* const network_thread_;
|
||||
webrtc::ScopedTaskSafety task_safety_;
|
||||
|
||||
bool started_pinging_ RTC_GUARDED_BY(network_thread_) = false;
|
||||
bool sort_pending_ RTC_GUARDED_BY(network_thread_) = false;
|
||||
const Connection* selected_connection_ RTC_GUARDED_BY(network_thread_) =
|
||||
nullptr;
|
||||
|
||||
std::unique_ptr<IceControllerInterface> wrapped_
|
||||
RTC_GUARDED_BY(network_thread_);
|
||||
IceAgentInterface& agent_ RTC_GUARDED_BY(network_thread_);
|
||||
};
|
||||
|
||||
} // namespace cricket
|
||||
|
||||
#endif // P2P_BASE_WRAPPING_ACTIVE_ICE_CONTROLLER_H_
|
311
p2p/base/wrapping_active_ice_controller_unittest.cc
Normal file
311
p2p/base/wrapping_active_ice_controller_unittest.cc
Normal file
|
@ -0,0 +1,311 @@
|
|||
/*
|
||||
* Copyright 2009 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/wrapping_active_ice_controller.h"
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "p2p/base/connection.h"
|
||||
#include "p2p/base/mock_ice_agent.h"
|
||||
#include "p2p/base/mock_ice_controller.h"
|
||||
#include "rtc_base/fake_clock.h"
|
||||
#include "rtc_base/gunit.h"
|
||||
#include "rtc_base/thread.h"
|
||||
|
||||
namespace {
|
||||
|
||||
using ::cricket::Connection;
|
||||
using ::cricket::IceConfig;
|
||||
using ::cricket::IceControllerFactoryArgs;
|
||||
using ::cricket::IceControllerInterface;
|
||||
using ::cricket::IceMode;
|
||||
using ::cricket::IceRecheckEvent;
|
||||
using ::cricket::IceSwitchReason;
|
||||
using ::cricket::MockIceAgent;
|
||||
using ::cricket::MockIceController;
|
||||
using ::cricket::MockIceControllerFactory;
|
||||
using ::cricket::NominationMode;
|
||||
using ::cricket::WrappingActiveIceController;
|
||||
|
||||
using ::testing::_;
|
||||
using ::testing::NiceMock;
|
||||
using ::testing::Ref;
|
||||
using ::testing::Return;
|
||||
using ::testing::Sequence;
|
||||
|
||||
using ::rtc::AutoThread;
|
||||
using ::rtc::Event;
|
||||
using ::rtc::ScopedFakeClock;
|
||||
using ::webrtc::TimeDelta;
|
||||
|
||||
using NiceMockIceController = NiceMock<MockIceController>;
|
||||
|
||||
static const Connection* kConnection =
|
||||
reinterpret_cast<const Connection*>(0xabcd);
|
||||
static const Connection* kConnectionTwo =
|
||||
reinterpret_cast<const Connection*>(0xbcde);
|
||||
static const Connection* kConnectionThree =
|
||||
reinterpret_cast<const Connection*>(0xcdef);
|
||||
|
||||
static const std::vector<const Connection*> kEmptyConnsList =
|
||||
std::vector<const Connection*>();
|
||||
|
||||
static const TimeDelta kTick = TimeDelta::Millis(1);
|
||||
|
||||
TEST(WrappingActiveIceControllerTest, CreateLegacyIceControllerFromFactory) {
|
||||
MockIceAgent agent;
|
||||
IceControllerFactoryArgs args;
|
||||
MockIceControllerFactory legacy_controller_factory;
|
||||
EXPECT_CALL(legacy_controller_factory, RecordIceControllerCreated()).Times(1);
|
||||
WrappingActiveIceController controller(&agent, &legacy_controller_factory,
|
||||
args);
|
||||
}
|
||||
|
||||
TEST(WrappingActiveIceControllerTest, PassthroughIceControllerInterface) {
|
||||
MockIceAgent agent;
|
||||
std::unique_ptr<MockIceController> will_move =
|
||||
std::make_unique<MockIceController>(IceControllerFactoryArgs{});
|
||||
MockIceController* wrapped = will_move.get();
|
||||
WrappingActiveIceController controller(&agent, std::move(will_move));
|
||||
|
||||
IceConfig config{};
|
||||
EXPECT_CALL(*wrapped, SetIceConfig(Ref(config)));
|
||||
controller.SetIceConfig(config);
|
||||
|
||||
EXPECT_CALL(*wrapped,
|
||||
GetUseCandidateAttr(kConnection, NominationMode::AGGRESSIVE,
|
||||
IceMode::ICEMODE_LITE))
|
||||
.WillOnce(Return(true));
|
||||
EXPECT_TRUE(controller.GetUseCandidateAttribute(
|
||||
kConnection, NominationMode::AGGRESSIVE, IceMode::ICEMODE_LITE));
|
||||
|
||||
EXPECT_CALL(*wrapped, AddConnection(kConnection));
|
||||
controller.OnConnectionAdded(kConnection);
|
||||
|
||||
EXPECT_CALL(*wrapped, OnConnectionDestroyed(kConnection));
|
||||
controller.OnConnectionDestroyed(kConnection);
|
||||
|
||||
EXPECT_CALL(*wrapped, SetSelectedConnection(kConnection));
|
||||
controller.OnConnectionSwitched(kConnection);
|
||||
|
||||
EXPECT_CALL(*wrapped, MarkConnectionPinged(kConnection));
|
||||
controller.OnConnectionPinged(kConnection);
|
||||
|
||||
EXPECT_CALL(*wrapped, FindNextPingableConnection())
|
||||
.WillOnce(Return(kConnection));
|
||||
EXPECT_EQ(controller.FindNextPingableConnection(), kConnection);
|
||||
}
|
||||
|
||||
TEST(WrappingActiveIceControllerTest, HandlesImmediateSwitchRequest) {
|
||||
AutoThread main;
|
||||
ScopedFakeClock clock;
|
||||
NiceMock<MockIceAgent> agent;
|
||||
std::unique_ptr<NiceMockIceController> will_move =
|
||||
std::make_unique<NiceMockIceController>(IceControllerFactoryArgs{});
|
||||
NiceMockIceController* wrapped = will_move.get();
|
||||
WrappingActiveIceController controller(&agent, std::move(will_move));
|
||||
|
||||
IceSwitchReason reason = IceSwitchReason::NOMINATION_ON_CONTROLLED_SIDE;
|
||||
std::vector<const Connection*> conns_to_forget{kConnectionTwo};
|
||||
int recheck_delay_ms = 10;
|
||||
IceControllerInterface::SwitchResult switch_result{
|
||||
kConnection,
|
||||
IceRecheckEvent(IceSwitchReason::ICE_CONTROLLER_RECHECK,
|
||||
recheck_delay_ms),
|
||||
conns_to_forget};
|
||||
|
||||
// ICE controller should switch to given connection immediately.
|
||||
Sequence check_then_switch;
|
||||
EXPECT_CALL(*wrapped, ShouldSwitchConnection(reason, kConnection))
|
||||
.InSequence(check_then_switch)
|
||||
.WillOnce(Return(switch_result));
|
||||
EXPECT_CALL(agent, SwitchSelectedConnection(kConnection, reason))
|
||||
.InSequence(check_then_switch);
|
||||
EXPECT_CALL(agent, ForgetLearnedStateForConnections(conns_to_forget));
|
||||
|
||||
EXPECT_TRUE(controller.OnImmediateSwitchRequest(reason, kConnection));
|
||||
|
||||
// No rechecks before recheck delay.
|
||||
clock.AdvanceTime(TimeDelta::Millis(recheck_delay_ms - 1));
|
||||
|
||||
// ICE controller should recheck for best connection after the recheck delay.
|
||||
Sequence recheck_sort;
|
||||
EXPECT_CALL(agent, UpdateConnectionStates()).InSequence(recheck_sort);
|
||||
EXPECT_CALL(*wrapped,
|
||||
SortAndSwitchConnection(IceSwitchReason::ICE_CONTROLLER_RECHECK))
|
||||
.InSequence(recheck_sort)
|
||||
.WillOnce(Return(IceControllerInterface::SwitchResult{}));
|
||||
EXPECT_CALL(agent, ForgetLearnedStateForConnections(kEmptyConnsList));
|
||||
|
||||
clock.AdvanceTime(kTick);
|
||||
}
|
||||
|
||||
TEST(WrappingActiveIceControllerTest, HandlesImmediateSortAndSwitchRequest) {
|
||||
AutoThread main;
|
||||
ScopedFakeClock clock;
|
||||
NiceMock<MockIceAgent> agent;
|
||||
std::unique_ptr<NiceMockIceController> will_move =
|
||||
std::make_unique<NiceMockIceController>(IceControllerFactoryArgs{});
|
||||
NiceMockIceController* wrapped = will_move.get();
|
||||
WrappingActiveIceController controller(&agent, std::move(will_move));
|
||||
|
||||
IceSwitchReason reason = IceSwitchReason::NEW_CONNECTION_FROM_LOCAL_CANDIDATE;
|
||||
std::vector<const Connection*> conns_to_forget{kConnectionTwo};
|
||||
std::vector<const Connection*> conns_to_prune{kConnectionThree};
|
||||
int recheck_delay_ms = 10;
|
||||
IceControllerInterface::SwitchResult switch_result{
|
||||
kConnection,
|
||||
IceRecheckEvent(IceSwitchReason::ICE_CONTROLLER_RECHECK,
|
||||
recheck_delay_ms),
|
||||
conns_to_forget};
|
||||
|
||||
Sequence sort_and_switch;
|
||||
EXPECT_CALL(agent, UpdateConnectionStates()).InSequence(sort_and_switch);
|
||||
EXPECT_CALL(*wrapped, SortAndSwitchConnection(reason))
|
||||
.InSequence(sort_and_switch)
|
||||
.WillOnce(Return(switch_result));
|
||||
EXPECT_CALL(agent, SwitchSelectedConnection(kConnection, reason))
|
||||
.InSequence(sort_and_switch);
|
||||
EXPECT_CALL(*wrapped, PruneConnections())
|
||||
.InSequence(sort_and_switch)
|
||||
.WillOnce(Return(conns_to_prune));
|
||||
EXPECT_CALL(agent, PruneConnections(conns_to_prune))
|
||||
.InSequence(sort_and_switch);
|
||||
|
||||
controller.OnImmediateSortAndSwitchRequest(reason);
|
||||
|
||||
// No rechecks before recheck delay.
|
||||
clock.AdvanceTime(TimeDelta::Millis(recheck_delay_ms - 1));
|
||||
|
||||
// ICE controller should recheck for best connection after the recheck delay.
|
||||
Sequence recheck_sort;
|
||||
EXPECT_CALL(agent, UpdateConnectionStates()).InSequence(recheck_sort);
|
||||
EXPECT_CALL(*wrapped,
|
||||
SortAndSwitchConnection(IceSwitchReason::ICE_CONTROLLER_RECHECK))
|
||||
.InSequence(recheck_sort)
|
||||
.WillOnce(Return(IceControllerInterface::SwitchResult{}));
|
||||
EXPECT_CALL(*wrapped, PruneConnections())
|
||||
.InSequence(recheck_sort)
|
||||
.WillOnce(Return(kEmptyConnsList));
|
||||
EXPECT_CALL(agent, PruneConnections(kEmptyConnsList))
|
||||
.InSequence(recheck_sort);
|
||||
|
||||
clock.AdvanceTime(kTick);
|
||||
}
|
||||
|
||||
TEST(WrappingActiveIceControllerTest, HandlesSortAndSwitchRequest) {
|
||||
AutoThread main;
|
||||
ScopedFakeClock clock;
|
||||
|
||||
// Block the main task queue until ready.
|
||||
Event init;
|
||||
TimeDelta init_delay = TimeDelta::Millis(10);
|
||||
main.PostTask([&init, &init_delay] { init.Wait(init_delay); });
|
||||
|
||||
NiceMock<MockIceAgent> agent;
|
||||
std::unique_ptr<NiceMockIceController> will_move =
|
||||
std::make_unique<NiceMockIceController>(IceControllerFactoryArgs{});
|
||||
NiceMockIceController* wrapped = will_move.get();
|
||||
WrappingActiveIceController controller(&agent, std::move(will_move));
|
||||
|
||||
IceSwitchReason reason = IceSwitchReason::NETWORK_PREFERENCE_CHANGE;
|
||||
|
||||
// No action should occur immediately
|
||||
EXPECT_CALL(agent, UpdateConnectionStates()).Times(0);
|
||||
EXPECT_CALL(*wrapped, SortAndSwitchConnection(_)).Times(0);
|
||||
EXPECT_CALL(agent, SwitchSelectedConnection(_, _)).Times(0);
|
||||
|
||||
controller.OnSortAndSwitchRequest(reason);
|
||||
|
||||
std::vector<const Connection*> conns_to_forget{kConnectionTwo};
|
||||
int recheck_delay_ms = 10;
|
||||
IceControllerInterface::SwitchResult switch_result{
|
||||
kConnection,
|
||||
IceRecheckEvent(IceSwitchReason::ICE_CONTROLLER_RECHECK,
|
||||
recheck_delay_ms),
|
||||
conns_to_forget};
|
||||
|
||||
// Sort and switch should take place as the subsequent task.
|
||||
Sequence sort_and_switch;
|
||||
EXPECT_CALL(agent, UpdateConnectionStates()).InSequence(sort_and_switch);
|
||||
EXPECT_CALL(*wrapped, SortAndSwitchConnection(reason))
|
||||
.InSequence(sort_and_switch)
|
||||
.WillOnce(Return(switch_result));
|
||||
EXPECT_CALL(agent, SwitchSelectedConnection(kConnection, reason))
|
||||
.InSequence(sort_and_switch);
|
||||
|
||||
// Unblock the init task.
|
||||
clock.AdvanceTime(init_delay);
|
||||
}
|
||||
|
||||
TEST(WrappingActiveIceControllerTest, StartPingingAfterSortAndSwitch) {
|
||||
AutoThread main;
|
||||
ScopedFakeClock clock;
|
||||
|
||||
// Block the main task queue until ready.
|
||||
Event init;
|
||||
TimeDelta init_delay = TimeDelta::Millis(10);
|
||||
main.PostTask([&init, &init_delay] { init.Wait(init_delay); });
|
||||
|
||||
NiceMock<MockIceAgent> agent;
|
||||
std::unique_ptr<NiceMockIceController> will_move =
|
||||
std::make_unique<NiceMockIceController>(IceControllerFactoryArgs{});
|
||||
NiceMockIceController* wrapped = will_move.get();
|
||||
WrappingActiveIceController controller(&agent, std::move(will_move));
|
||||
|
||||
// Pinging does not start automatically, unless triggered through a sort.
|
||||
EXPECT_CALL(*wrapped, HasPingableConnection()).Times(0);
|
||||
EXPECT_CALL(*wrapped, SelectConnectionToPing(_)).Times(0);
|
||||
EXPECT_CALL(agent, OnStartedPinging()).Times(0);
|
||||
|
||||
controller.OnSortAndSwitchRequest(IceSwitchReason::DATA_RECEIVED);
|
||||
|
||||
// Pinging does not start if no pingable connection.
|
||||
EXPECT_CALL(*wrapped, HasPingableConnection()).WillOnce(Return(false));
|
||||
EXPECT_CALL(*wrapped, SelectConnectionToPing(_)).Times(0);
|
||||
EXPECT_CALL(agent, OnStartedPinging()).Times(0);
|
||||
|
||||
// Unblock the init task.
|
||||
clock.AdvanceTime(init_delay);
|
||||
|
||||
int recheck_delay_ms = 10;
|
||||
IceControllerInterface::PingResult ping_result(kConnection, recheck_delay_ms);
|
||||
|
||||
// Pinging starts when there is a pingable connection.
|
||||
Sequence start_pinging;
|
||||
EXPECT_CALL(*wrapped, HasPingableConnection())
|
||||
.InSequence(start_pinging)
|
||||
.WillOnce(Return(true));
|
||||
EXPECT_CALL(agent, OnStartedPinging()).InSequence(start_pinging);
|
||||
EXPECT_CALL(agent, GetLastPingSentMs())
|
||||
.InSequence(start_pinging)
|
||||
.WillOnce(Return(123));
|
||||
EXPECT_CALL(*wrapped, SelectConnectionToPing(123))
|
||||
.InSequence(start_pinging)
|
||||
.WillOnce(Return(ping_result));
|
||||
EXPECT_CALL(agent, SendPingRequest(kConnection)).InSequence(start_pinging);
|
||||
|
||||
controller.OnSortAndSwitchRequest(IceSwitchReason::DATA_RECEIVED);
|
||||
clock.AdvanceTime(kTick);
|
||||
|
||||
// ICE controller should recheck and ping after the recheck delay.
|
||||
// No ping should be sent if no connection selected to ping.
|
||||
EXPECT_CALL(agent, GetLastPingSentMs()).WillOnce(Return(456));
|
||||
EXPECT_CALL(*wrapped, SelectConnectionToPing(456))
|
||||
.WillOnce(Return(IceControllerInterface::PingResult(
|
||||
/* connection= */ nullptr, recheck_delay_ms)));
|
||||
EXPECT_CALL(agent, SendPingRequest(kConnection)).Times(0);
|
||||
|
||||
clock.AdvanceTime(TimeDelta::Millis(recheck_delay_ms));
|
||||
}
|
||||
|
||||
} // namespace
|
Loading…
Reference in a new issue