webrtc/pc/jsep_transport_controller_unittest.cc
Per K e1e94ad4c8 Ensure Call is notified of un demuxable packets
With this cl, packets that are discarded in RtpTransport now notifies Call, so that
they can be part of BWE even if they are dropped.
These packets have been recevied on the transport, and has bin decrypted
and parsed and thus can be accounted for.

The un demuxable packets are forwarded to Call similarly how RTCP packets are forwarded.

Bug: webrtc:14928
Change-Id: Ia53349c7b316c4442a3c7aac085a85ec4f4ab9ae
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/299262
Reviewed-by: Henrik Boström <hbos@webrtc.org>
Commit-Queue: Per Kjellander <perkj@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#39727}
2023-03-31 10:33:09 +00:00

2704 lines
127 KiB
C++

/*
* 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 "pc/jsep_transport_controller.h"
#include <map>
#include <string>
#include <utility>
#include "api/dtls_transport_interface.h"
#include "api/transport/enums.h"
#include "p2p/base/candidate_pair_interface.h"
#include "p2p/base/dtls_transport_factory.h"
#include "p2p/base/fake_dtls_transport.h"
#include "p2p/base/fake_ice_transport.h"
#include "p2p/base/p2p_constants.h"
#include "p2p/base/transport_info.h"
#include "rtc_base/fake_ssl_identity.h"
#include "rtc_base/gunit.h"
#include "rtc_base/logging.h"
#include "rtc_base/net_helper.h"
#include "rtc_base/socket_address.h"
#include "rtc_base/ssl_fingerprint.h"
#include "rtc_base/ssl_identity.h"
#include "rtc_base/task_queue_for_test.h"
#include "rtc_base/thread.h"
#include "test/gtest.h"
#include "test/scoped_key_value_config.h"
using cricket::Candidate;
using cricket::Candidates;
using cricket::FakeDtlsTransport;
using webrtc::SdpType;
static const int kTimeout = 100;
static const char kIceUfrag1[] = "u0001";
static const char kIcePwd1[] = "TESTICEPWD00000000000001";
static const char kIceUfrag2[] = "u0002";
static const char kIcePwd2[] = "TESTICEPWD00000000000002";
static const char kIceUfrag3[] = "u0003";
static const char kIcePwd3[] = "TESTICEPWD00000000000003";
static const char kIceUfrag4[] = "u0004";
static const char kIcePwd4[] = "TESTICEPWD00000000000004";
static const char kAudioMid1[] = "audio1";
static const char kAudioMid2[] = "audio2";
static const char kVideoMid1[] = "video1";
static const char kVideoMid2[] = "video2";
static const char kDataMid1[] = "data1";
namespace webrtc {
class FakeIceTransportFactory : public webrtc::IceTransportFactory {
public:
~FakeIceTransportFactory() override = default;
rtc::scoped_refptr<IceTransportInterface> CreateIceTransport(
const std::string& transport_name,
int component,
IceTransportInit init) override {
return rtc::make_ref_counted<cricket::FakeIceTransportWrapper>(
std::make_unique<cricket::FakeIceTransport>(transport_name, component));
}
};
class FakeDtlsTransportFactory : public cricket::DtlsTransportFactory {
public:
std::unique_ptr<cricket::DtlsTransportInternal> CreateDtlsTransport(
cricket::IceTransportInternal* ice,
const webrtc::CryptoOptions& crypto_options,
rtc::SSLProtocolVersion max_version) override {
return std::make_unique<FakeDtlsTransport>(
static_cast<cricket::FakeIceTransport*>(ice));
}
};
class JsepTransportControllerTest : public JsepTransportController::Observer,
public ::testing::Test,
public sigslot::has_slots<> {
public:
JsepTransportControllerTest() : signaling_thread_(rtc::Thread::Current()) {
fake_ice_transport_factory_ = std::make_unique<FakeIceTransportFactory>();
fake_dtls_transport_factory_ = std::make_unique<FakeDtlsTransportFactory>();
}
void CreateJsepTransportController(
JsepTransportController::Config config,
rtc::Thread* network_thread = rtc::Thread::Current(),
cricket::PortAllocator* port_allocator = nullptr) {
config.transport_observer = this;
config.rtcp_handler = [](const rtc::CopyOnWriteBuffer& packet,
int64_t packet_time_us) {
RTC_DCHECK_NOTREACHED();
};
config.ice_transport_factory = fake_ice_transport_factory_.get();
config.dtls_transport_factory = fake_dtls_transport_factory_.get();
config.on_dtls_handshake_error_ = [](rtc::SSLHandshakeError s) {};
config.field_trials = &field_trials_;
transport_controller_ = std::make_unique<JsepTransportController>(
network_thread, port_allocator, nullptr /* async_resolver_factory */,
std::move(config));
SendTask(network_thread, [&] { ConnectTransportControllerSignals(); });
}
void ConnectTransportControllerSignals() {
transport_controller_->SubscribeIceConnectionState(
[this](cricket::IceConnectionState s) {
JsepTransportControllerTest::OnConnectionState(s);
});
transport_controller_->SubscribeConnectionState(
[this](PeerConnectionInterface::PeerConnectionState s) {
JsepTransportControllerTest::OnCombinedConnectionState(s);
});
transport_controller_->SubscribeStandardizedIceConnectionState(
[this](PeerConnectionInterface::IceConnectionState s) {
JsepTransportControllerTest::OnStandardizedIceConnectionState(s);
});
transport_controller_->SubscribeIceGatheringState(
[this](cricket::IceGatheringState s) {
JsepTransportControllerTest::OnGatheringState(s);
});
transport_controller_->SubscribeIceCandidateGathered(
[this](const std::string& transport,
const std::vector<cricket::Candidate>& candidates) {
JsepTransportControllerTest::OnCandidatesGathered(transport,
candidates);
});
}
std::unique_ptr<cricket::SessionDescription>
CreateSessionDescriptionWithoutBundle() {
auto description = std::make_unique<cricket::SessionDescription>();
AddAudioSection(description.get(), kAudioMid1, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddVideoSection(description.get(), kVideoMid1, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
return description;
}
std::unique_ptr<cricket::SessionDescription>
CreateSessionDescriptionWithBundleGroup() {
auto description = CreateSessionDescriptionWithoutBundle();
cricket::ContentGroup bundle_group(cricket::GROUP_TYPE_BUNDLE);
bundle_group.AddContentName(kAudioMid1);
bundle_group.AddContentName(kVideoMid1);
description->AddGroup(bundle_group);
return description;
}
std::unique_ptr<cricket::SessionDescription>
CreateSessionDescriptionWithBundledData() {
auto description = CreateSessionDescriptionWithoutBundle();
AddDataSection(description.get(), kDataMid1,
cricket::MediaProtocolType::kSctp, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
cricket::ContentGroup bundle_group(cricket::GROUP_TYPE_BUNDLE);
bundle_group.AddContentName(kAudioMid1);
bundle_group.AddContentName(kVideoMid1);
bundle_group.AddContentName(kDataMid1);
description->AddGroup(bundle_group);
return description;
}
void AddAudioSection(cricket::SessionDescription* description,
const std::string& mid,
const std::string& ufrag,
const std::string& pwd,
cricket::IceMode ice_mode,
cricket::ConnectionRole conn_role,
rtc::scoped_refptr<rtc::RTCCertificate> cert) {
std::unique_ptr<cricket::AudioContentDescription> audio(
new cricket::AudioContentDescription());
// Set RTCP-mux to be true because the default policy is "mux required".
audio->set_rtcp_mux(true);
description->AddContent(mid, cricket::MediaProtocolType::kRtp,
/*rejected=*/false, std::move(audio));
AddTransportInfo(description, mid, ufrag, pwd, ice_mode, conn_role, cert);
}
void AddVideoSection(cricket::SessionDescription* description,
const std::string& mid,
const std::string& ufrag,
const std::string& pwd,
cricket::IceMode ice_mode,
cricket::ConnectionRole conn_role,
rtc::scoped_refptr<rtc::RTCCertificate> cert) {
std::unique_ptr<cricket::VideoContentDescription> video(
new cricket::VideoContentDescription());
// Set RTCP-mux to be true because the default policy is "mux required".
video->set_rtcp_mux(true);
description->AddContent(mid, cricket::MediaProtocolType::kRtp,
/*rejected=*/false, std::move(video));
AddTransportInfo(description, mid, ufrag, pwd, ice_mode, conn_role, cert);
}
void AddDataSection(cricket::SessionDescription* description,
const std::string& mid,
cricket::MediaProtocolType protocol_type,
const std::string& ufrag,
const std::string& pwd,
cricket::IceMode ice_mode,
cricket::ConnectionRole conn_role,
rtc::scoped_refptr<rtc::RTCCertificate> cert) {
RTC_CHECK(protocol_type == cricket::MediaProtocolType::kSctp);
std::unique_ptr<cricket::SctpDataContentDescription> data(
new cricket::SctpDataContentDescription());
data->set_rtcp_mux(true);
description->AddContent(mid, protocol_type,
/*rejected=*/false, std::move(data));
AddTransportInfo(description, mid, ufrag, pwd, ice_mode, conn_role, cert);
}
void AddTransportInfo(cricket::SessionDescription* description,
const std::string& mid,
const std::string& ufrag,
const std::string& pwd,
cricket::IceMode ice_mode,
cricket::ConnectionRole conn_role,
rtc::scoped_refptr<rtc::RTCCertificate> cert) {
std::unique_ptr<rtc::SSLFingerprint> fingerprint;
if (cert) {
fingerprint = rtc::SSLFingerprint::CreateFromCertificate(*cert);
}
cricket::TransportDescription transport_desc(std::vector<std::string>(),
ufrag, pwd, ice_mode,
conn_role, fingerprint.get());
description->AddTransportInfo(cricket::TransportInfo(mid, transport_desc));
}
cricket::IceConfig CreateIceConfig(
int receiving_timeout,
cricket::ContinualGatheringPolicy continual_gathering_policy) {
cricket::IceConfig config;
config.receiving_timeout = receiving_timeout;
config.continual_gathering_policy = continual_gathering_policy;
return config;
}
Candidate CreateCandidate(const std::string& transport_name, int component) {
Candidate c;
c.set_transport_name(transport_name);
c.set_address(rtc::SocketAddress("192.168.1.1", 8000));
c.set_component(component);
c.set_protocol(cricket::UDP_PROTOCOL_NAME);
c.set_priority(1);
return c;
}
void CreateLocalDescriptionAndCompleteConnectionOnNetworkThread() {
if (!network_thread_->IsCurrent()) {
SendTask(network_thread_.get(), [&] {
CreateLocalDescriptionAndCompleteConnectionOnNetworkThread();
});
return;
}
auto description = CreateSessionDescriptionWithBundleGroup();
EXPECT_TRUE(transport_controller_
->SetLocalDescription(SdpType::kOffer, description.get())
.ok());
transport_controller_->MaybeStartGathering();
auto fake_audio_dtls = static_cast<FakeDtlsTransport*>(
transport_controller_->GetDtlsTransport(kAudioMid1));
auto fake_video_dtls = static_cast<FakeDtlsTransport*>(
transport_controller_->GetDtlsTransport(kVideoMid1));
fake_audio_dtls->fake_ice_transport()->SignalCandidateGathered(
fake_audio_dtls->fake_ice_transport(),
CreateCandidate(kAudioMid1, /*component=*/1));
fake_video_dtls->fake_ice_transport()->SignalCandidateGathered(
fake_video_dtls->fake_ice_transport(),
CreateCandidate(kVideoMid1, /*component=*/1));
fake_audio_dtls->fake_ice_transport()->SetCandidatesGatheringComplete();
fake_video_dtls->fake_ice_transport()->SetCandidatesGatheringComplete();
fake_audio_dtls->fake_ice_transport()->SetConnectionCount(2);
fake_video_dtls->fake_ice_transport()->SetConnectionCount(2);
fake_audio_dtls->SetReceiving(true);
fake_video_dtls->SetReceiving(true);
fake_audio_dtls->SetWritable(true);
fake_video_dtls->SetWritable(true);
fake_audio_dtls->fake_ice_transport()->SetConnectionCount(1);
fake_video_dtls->fake_ice_transport()->SetConnectionCount(1);
}
protected:
void OnConnectionState(cricket::IceConnectionState state) {
ice_signaled_on_thread_ = rtc::Thread::Current();
connection_state_ = state;
++connection_state_signal_count_;
}
void OnStandardizedIceConnectionState(
PeerConnectionInterface::IceConnectionState state) {
ice_signaled_on_thread_ = rtc::Thread::Current();
ice_connection_state_ = state;
++ice_connection_state_signal_count_;
}
void OnCombinedConnectionState(
PeerConnectionInterface::PeerConnectionState state) {
RTC_LOG(LS_INFO) << "OnCombinedConnectionState: "
<< static_cast<int>(state);
ice_signaled_on_thread_ = rtc::Thread::Current();
combined_connection_state_ = state;
++combined_connection_state_signal_count_;
}
void OnGatheringState(cricket::IceGatheringState state) {
ice_signaled_on_thread_ = rtc::Thread::Current();
gathering_state_ = state;
++gathering_state_signal_count_;
}
void OnCandidatesGathered(const std::string& transport_name,
const Candidates& candidates) {
ice_signaled_on_thread_ = rtc::Thread::Current();
candidates_[transport_name].insert(candidates_[transport_name].end(),
candidates.begin(), candidates.end());
++candidates_signal_count_;
}
// JsepTransportController::Observer overrides.
bool OnTransportChanged(
const std::string& mid,
RtpTransportInternal* rtp_transport,
rtc::scoped_refptr<DtlsTransport> dtls_transport,
DataChannelTransportInterface* data_channel_transport) override {
changed_rtp_transport_by_mid_[mid] = rtp_transport;
if (dtls_transport) {
changed_dtls_transport_by_mid_[mid] = dtls_transport->internal();
} else {
changed_dtls_transport_by_mid_[mid] = nullptr;
}
return true;
}
rtc::AutoThread main_thread_;
// Information received from signals from transport controller.
cricket::IceConnectionState connection_state_ =
cricket::kIceConnectionConnecting;
PeerConnectionInterface::IceConnectionState ice_connection_state_ =
PeerConnectionInterface::kIceConnectionNew;
PeerConnectionInterface::PeerConnectionState combined_connection_state_ =
PeerConnectionInterface::PeerConnectionState::kNew;
bool receiving_ = false;
cricket::IceGatheringState gathering_state_ = cricket::kIceGatheringNew;
// transport_name => candidates
std::map<std::string, Candidates> candidates_;
// Counts of each signal emitted.
int connection_state_signal_count_ = 0;
int ice_connection_state_signal_count_ = 0;
int combined_connection_state_signal_count_ = 0;
int receiving_signal_count_ = 0;
int gathering_state_signal_count_ = 0;
int candidates_signal_count_ = 0;
// `network_thread_` should be destroyed after `transport_controller_`
std::unique_ptr<rtc::Thread> network_thread_;
std::unique_ptr<FakeIceTransportFactory> fake_ice_transport_factory_;
std::unique_ptr<FakeDtlsTransportFactory> fake_dtls_transport_factory_;
rtc::Thread* const signaling_thread_ = nullptr;
rtc::Thread* ice_signaled_on_thread_ = nullptr;
// Used to verify the SignalRtpTransportChanged/SignalDtlsTransportChanged are
// signaled correctly.
std::map<std::string, RtpTransportInternal*> changed_rtp_transport_by_mid_;
std::map<std::string, cricket::DtlsTransportInternal*>
changed_dtls_transport_by_mid_;
// Transport controller needs to be destroyed first, because it may issue
// callbacks that modify the changed_*_by_mid in the destructor.
std::unique_ptr<JsepTransportController> transport_controller_;
webrtc::test::ScopedKeyValueConfig field_trials_;
};
TEST_F(JsepTransportControllerTest, GetRtpTransport) {
CreateJsepTransportController(JsepTransportController::Config());
auto description = CreateSessionDescriptionWithoutBundle();
EXPECT_TRUE(transport_controller_
->SetLocalDescription(SdpType::kOffer, description.get())
.ok());
auto audio_rtp_transport = transport_controller_->GetRtpTransport(kAudioMid1);
auto video_rtp_transport = transport_controller_->GetRtpTransport(kVideoMid1);
EXPECT_NE(nullptr, audio_rtp_transport);
EXPECT_NE(nullptr, video_rtp_transport);
EXPECT_NE(audio_rtp_transport, video_rtp_transport);
// Return nullptr for non-existing ones.
EXPECT_EQ(nullptr, transport_controller_->GetRtpTransport(kAudioMid2));
}
TEST_F(JsepTransportControllerTest, GetDtlsTransport) {
JsepTransportController::Config config;
config.rtcp_mux_policy = PeerConnectionInterface::kRtcpMuxPolicyNegotiate;
CreateJsepTransportController(std::move(config));
auto description = CreateSessionDescriptionWithoutBundle();
EXPECT_TRUE(transport_controller_
->SetLocalDescription(SdpType::kOffer, description.get())
.ok());
EXPECT_NE(nullptr, transport_controller_->GetDtlsTransport(kAudioMid1));
EXPECT_NE(nullptr, transport_controller_->GetRtcpDtlsTransport(kAudioMid1));
EXPECT_NE(nullptr,
transport_controller_->LookupDtlsTransportByMid(kAudioMid1));
EXPECT_NE(nullptr, transport_controller_->GetDtlsTransport(kVideoMid1));
EXPECT_NE(nullptr, transport_controller_->GetRtcpDtlsTransport(kVideoMid1));
EXPECT_NE(nullptr,
transport_controller_->LookupDtlsTransportByMid(kVideoMid1));
// Lookup for all MIDs should return different transports (no bundle)
EXPECT_NE(transport_controller_->LookupDtlsTransportByMid(kAudioMid1),
transport_controller_->LookupDtlsTransportByMid(kVideoMid1));
// Return nullptr for non-existing ones.
EXPECT_EQ(nullptr, transport_controller_->GetDtlsTransport(kVideoMid2));
EXPECT_EQ(nullptr, transport_controller_->GetRtcpDtlsTransport(kVideoMid2));
EXPECT_EQ(nullptr,
transport_controller_->LookupDtlsTransportByMid(kVideoMid2));
// Take a pointer to a transport, shut down the transport controller,
// and verify that the resulting container is empty.
auto dtls_transport =
transport_controller_->LookupDtlsTransportByMid(kVideoMid1);
webrtc::DtlsTransport* my_transport =
static_cast<DtlsTransport*>(dtls_transport.get());
EXPECT_NE(nullptr, my_transport->internal());
transport_controller_.reset();
EXPECT_EQ(nullptr, my_transport->internal());
}
TEST_F(JsepTransportControllerTest, GetDtlsTransportWithRtcpMux) {
JsepTransportController::Config config;
config.rtcp_mux_policy = PeerConnectionInterface::kRtcpMuxPolicyRequire;
CreateJsepTransportController(std::move(config));
auto description = CreateSessionDescriptionWithoutBundle();
EXPECT_TRUE(transport_controller_
->SetLocalDescription(SdpType::kOffer, description.get())
.ok());
EXPECT_NE(nullptr, transport_controller_->GetDtlsTransport(kAudioMid1));
EXPECT_EQ(nullptr, transport_controller_->GetRtcpDtlsTransport(kAudioMid1));
EXPECT_NE(nullptr, transport_controller_->GetDtlsTransport(kVideoMid1));
EXPECT_EQ(nullptr, transport_controller_->GetRtcpDtlsTransport(kVideoMid1));
}
TEST_F(JsepTransportControllerTest, SetIceConfig) {
CreateJsepTransportController(JsepTransportController::Config());
auto description = CreateSessionDescriptionWithoutBundle();
EXPECT_TRUE(transport_controller_
->SetLocalDescription(SdpType::kOffer, description.get())
.ok());
transport_controller_->SetIceConfig(
CreateIceConfig(kTimeout, cricket::GATHER_CONTINUALLY));
FakeDtlsTransport* fake_audio_dtls = static_cast<FakeDtlsTransport*>(
transport_controller_->GetDtlsTransport(kAudioMid1));
ASSERT_NE(nullptr, fake_audio_dtls);
EXPECT_EQ(kTimeout,
fake_audio_dtls->fake_ice_transport()->receiving_timeout());
EXPECT_TRUE(fake_audio_dtls->fake_ice_transport()->gather_continually());
// Test that value stored in controller is applied to new transports.
AddAudioSection(description.get(), kAudioMid2, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
EXPECT_TRUE(transport_controller_
->SetLocalDescription(SdpType::kOffer, description.get())
.ok());
fake_audio_dtls = static_cast<FakeDtlsTransport*>(
transport_controller_->GetDtlsTransport(kAudioMid2));
ASSERT_NE(nullptr, fake_audio_dtls);
EXPECT_EQ(kTimeout,
fake_audio_dtls->fake_ice_transport()->receiving_timeout());
EXPECT_TRUE(fake_audio_dtls->fake_ice_transport()->gather_continually());
}
// Tests the getter and setter of the ICE restart flag.
TEST_F(JsepTransportControllerTest, NeedIceRestart) {
CreateJsepTransportController(JsepTransportController::Config());
auto description = CreateSessionDescriptionWithoutBundle();
EXPECT_TRUE(transport_controller_
->SetLocalDescription(SdpType::kOffer, description.get())
.ok());
EXPECT_TRUE(transport_controller_
->SetRemoteDescription(SdpType::kAnswer, description.get())
.ok());
// Initially NeedsIceRestart should return false.
EXPECT_FALSE(transport_controller_->NeedsIceRestart(kAudioMid1));
EXPECT_FALSE(transport_controller_->NeedsIceRestart(kVideoMid1));
// Set the needs-ice-restart flag and verify NeedsIceRestart starts returning
// true.
transport_controller_->SetNeedsIceRestartFlag();
EXPECT_TRUE(transport_controller_->NeedsIceRestart(kAudioMid1));
EXPECT_TRUE(transport_controller_->NeedsIceRestart(kVideoMid1));
// For a nonexistent transport, false should be returned.
EXPECT_FALSE(transport_controller_->NeedsIceRestart(kVideoMid2));
// Reset the ice_ufrag/ice_pwd for audio.
auto audio_transport_info = description->GetTransportInfoByName(kAudioMid1);
audio_transport_info->description.ice_ufrag = kIceUfrag2;
audio_transport_info->description.ice_pwd = kIcePwd2;
EXPECT_TRUE(transport_controller_
->SetLocalDescription(SdpType::kOffer, description.get())
.ok());
// Because the ICE is only restarted for audio, NeedsIceRestart is expected to
// return false for audio and true for video.
EXPECT_FALSE(transport_controller_->NeedsIceRestart(kAudioMid1));
EXPECT_TRUE(transport_controller_->NeedsIceRestart(kVideoMid1));
}
TEST_F(JsepTransportControllerTest, MaybeStartGathering) {
CreateJsepTransportController(JsepTransportController::Config());
auto description = CreateSessionDescriptionWithBundleGroup();
EXPECT_TRUE(transport_controller_
->SetLocalDescription(SdpType::kOffer, description.get())
.ok());
// After setting the local description, we should be able to start gathering
// candidates.
transport_controller_->MaybeStartGathering();
EXPECT_EQ_WAIT(cricket::kIceGatheringGathering, gathering_state_, kTimeout);
EXPECT_EQ(1, gathering_state_signal_count_);
}
TEST_F(JsepTransportControllerTest, AddRemoveRemoteCandidates) {
CreateJsepTransportController(JsepTransportController::Config());
auto description = CreateSessionDescriptionWithoutBundle();
transport_controller_->SetLocalDescription(SdpType::kOffer,
description.get());
transport_controller_->SetRemoteDescription(SdpType::kAnswer,
description.get());
auto fake_audio_dtls = static_cast<FakeDtlsTransport*>(
transport_controller_->GetDtlsTransport(kAudioMid1));
ASSERT_NE(nullptr, fake_audio_dtls);
Candidates candidates;
candidates.push_back(
CreateCandidate(kAudioMid1, cricket::ICE_CANDIDATE_COMPONENT_RTP));
EXPECT_TRUE(
transport_controller_->AddRemoteCandidates(kAudioMid1, candidates).ok());
EXPECT_EQ(1U,
fake_audio_dtls->fake_ice_transport()->remote_candidates().size());
EXPECT_TRUE(transport_controller_->RemoveRemoteCandidates(candidates).ok());
EXPECT_EQ(0U,
fake_audio_dtls->fake_ice_transport()->remote_candidates().size());
}
TEST_F(JsepTransportControllerTest, SetAndGetLocalCertificate) {
CreateJsepTransportController(JsepTransportController::Config());
rtc::scoped_refptr<rtc::RTCCertificate> certificate1 =
rtc::RTCCertificate::Create(
rtc::SSLIdentity::Create("session1", rtc::KT_DEFAULT));
rtc::scoped_refptr<rtc::RTCCertificate> returned_certificate;
auto description = std::make_unique<cricket::SessionDescription>();
AddAudioSection(description.get(), kAudioMid1, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
certificate1);
// Apply the local certificate.
EXPECT_TRUE(transport_controller_->SetLocalCertificate(certificate1));
// Apply the local description.
EXPECT_TRUE(transport_controller_
->SetLocalDescription(SdpType::kOffer, description.get())
.ok());
returned_certificate = transport_controller_->GetLocalCertificate(kAudioMid1);
EXPECT_TRUE(returned_certificate);
EXPECT_EQ(certificate1->identity()->certificate().ToPEMString(),
returned_certificate->identity()->certificate().ToPEMString());
// Should fail if called for a nonexistant transport.
EXPECT_EQ(nullptr, transport_controller_->GetLocalCertificate(kVideoMid1));
// Shouldn't be able to change the identity once set.
rtc::scoped_refptr<rtc::RTCCertificate> certificate2 =
rtc::RTCCertificate::Create(
rtc::SSLIdentity::Create("session2", rtc::KT_DEFAULT));
EXPECT_FALSE(transport_controller_->SetLocalCertificate(certificate2));
}
TEST_F(JsepTransportControllerTest, GetRemoteSSLCertChain) {
CreateJsepTransportController(JsepTransportController::Config());
auto description = CreateSessionDescriptionWithBundleGroup();
EXPECT_TRUE(transport_controller_
->SetLocalDescription(SdpType::kOffer, description.get())
.ok());
rtc::FakeSSLCertificate fake_certificate("fake_data");
auto fake_audio_dtls = static_cast<FakeDtlsTransport*>(
transport_controller_->GetDtlsTransport(kAudioMid1));
fake_audio_dtls->SetRemoteSSLCertificate(&fake_certificate);
std::unique_ptr<rtc::SSLCertChain> returned_cert_chain =
transport_controller_->GetRemoteSSLCertChain(kAudioMid1);
ASSERT_TRUE(returned_cert_chain);
ASSERT_EQ(1u, returned_cert_chain->GetSize());
EXPECT_EQ(fake_certificate.ToPEMString(),
returned_cert_chain->Get(0).ToPEMString());
// Should fail if called for a nonexistant transport.
EXPECT_FALSE(transport_controller_->GetRemoteSSLCertChain(kAudioMid2));
}
TEST_F(JsepTransportControllerTest, GetDtlsRole) {
CreateJsepTransportController(JsepTransportController::Config());
auto offer_certificate = rtc::RTCCertificate::Create(
rtc::SSLIdentity::Create("offer", rtc::KT_DEFAULT));
auto answer_certificate = rtc::RTCCertificate::Create(
rtc::SSLIdentity::Create("answer", rtc::KT_DEFAULT));
transport_controller_->SetLocalCertificate(offer_certificate);
auto offer_desc = std::make_unique<cricket::SessionDescription>();
AddAudioSection(offer_desc.get(), kAudioMid1, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
offer_certificate);
auto answer_desc = std::make_unique<cricket::SessionDescription>();
AddAudioSection(answer_desc.get(), kAudioMid1, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_PASSIVE,
answer_certificate);
EXPECT_TRUE(transport_controller_
->SetLocalDescription(SdpType::kOffer, offer_desc.get())
.ok());
absl::optional<rtc::SSLRole> role =
transport_controller_->GetDtlsRole(kAudioMid1);
// The DTLS role is not decided yet.
EXPECT_FALSE(role);
EXPECT_TRUE(transport_controller_
->SetRemoteDescription(SdpType::kAnswer, answer_desc.get())
.ok());
role = transport_controller_->GetDtlsRole(kAudioMid1);
ASSERT_TRUE(role);
EXPECT_EQ(rtc::SSL_CLIENT, *role);
}
TEST_F(JsepTransportControllerTest, GetStats) {
CreateJsepTransportController(JsepTransportController::Config());
auto description = CreateSessionDescriptionWithBundleGroup();
EXPECT_TRUE(transport_controller_
->SetLocalDescription(SdpType::kOffer, description.get())
.ok());
cricket::TransportStats stats;
EXPECT_TRUE(transport_controller_->GetStats(kAudioMid1, &stats));
EXPECT_EQ(kAudioMid1, stats.transport_name);
EXPECT_EQ(1u, stats.channel_stats.size());
// Return false for non-existing transport.
EXPECT_FALSE(transport_controller_->GetStats(kAudioMid2, &stats));
}
TEST_F(JsepTransportControllerTest, SignalConnectionStateFailed) {
CreateJsepTransportController(JsepTransportController::Config());
auto description = CreateSessionDescriptionWithoutBundle();
EXPECT_TRUE(transport_controller_
->SetLocalDescription(SdpType::kOffer, description.get())
.ok());
auto fake_ice = static_cast<cricket::FakeIceTransport*>(
transport_controller_->GetDtlsTransport(kAudioMid1)->ice_transport());
fake_ice->SetCandidatesGatheringComplete();
fake_ice->SetConnectionCount(1);
// The connection stats will be failed if there is no active connection.
fake_ice->SetConnectionCount(0);
EXPECT_EQ_WAIT(cricket::kIceConnectionFailed, connection_state_, kTimeout);
EXPECT_EQ(1, connection_state_signal_count_);
EXPECT_EQ_WAIT(PeerConnectionInterface::kIceConnectionFailed,
ice_connection_state_, kTimeout);
EXPECT_EQ(1, ice_connection_state_signal_count_);
EXPECT_EQ_WAIT(PeerConnectionInterface::PeerConnectionState::kFailed,
combined_connection_state_, kTimeout);
EXPECT_EQ(1, combined_connection_state_signal_count_);
}
TEST_F(JsepTransportControllerTest,
SignalConnectionStateConnectedNoMediaTransport) {
CreateJsepTransportController(JsepTransportController::Config());
auto description = CreateSessionDescriptionWithoutBundle();
EXPECT_TRUE(transport_controller_
->SetLocalDescription(SdpType::kOffer, description.get())
.ok());
auto fake_audio_dtls = static_cast<FakeDtlsTransport*>(
transport_controller_->GetDtlsTransport(kAudioMid1));
auto fake_video_dtls = static_cast<FakeDtlsTransport*>(
transport_controller_->GetDtlsTransport(kVideoMid1));
// First, have one transport connect, and another fail, to ensure that
// the first transport connecting didn't trigger a "connected" state signal.
// We should only get a signal when all are connected.
fake_audio_dtls->fake_ice_transport()->SetConnectionCount(1);
fake_audio_dtls->SetWritable(true);
fake_audio_dtls->fake_ice_transport()->SetCandidatesGatheringComplete();
// Decrease the number of the connection to trigger the signal.
fake_video_dtls->fake_ice_transport()->SetConnectionCount(1);
fake_video_dtls->fake_ice_transport()->SetConnectionCount(0);
fake_video_dtls->fake_ice_transport()->SetCandidatesGatheringComplete();
EXPECT_EQ_WAIT(cricket::kIceConnectionFailed, connection_state_, kTimeout);
EXPECT_EQ(1, connection_state_signal_count_);
EXPECT_EQ_WAIT(PeerConnectionInterface::kIceConnectionFailed,
ice_connection_state_, kTimeout);
EXPECT_EQ(2, ice_connection_state_signal_count_);
EXPECT_EQ_WAIT(PeerConnectionInterface::PeerConnectionState::kFailed,
combined_connection_state_, kTimeout);
EXPECT_EQ(2, combined_connection_state_signal_count_);
fake_audio_dtls->SetDtlsState(DtlsTransportState::kConnected);
fake_video_dtls->SetDtlsState(DtlsTransportState::kConnected);
// Set the connection count to be 2 and the cricket::FakeIceTransport will set
// the transport state to be STATE_CONNECTING.
fake_video_dtls->fake_ice_transport()->SetConnectionCount(2);
fake_video_dtls->SetWritable(true);
EXPECT_EQ_WAIT(cricket::kIceConnectionConnected, connection_state_, kTimeout);
EXPECT_EQ(2, connection_state_signal_count_);
EXPECT_EQ_WAIT(PeerConnectionInterface::kIceConnectionConnected,
ice_connection_state_, kTimeout);
EXPECT_EQ(3, ice_connection_state_signal_count_);
EXPECT_EQ_WAIT(PeerConnectionInterface::PeerConnectionState::kConnected,
combined_connection_state_, kTimeout);
EXPECT_EQ(3, combined_connection_state_signal_count_);
}
TEST_F(JsepTransportControllerTest, SignalConnectionStateComplete) {
CreateJsepTransportController(JsepTransportController::Config());
auto description = CreateSessionDescriptionWithoutBundle();
EXPECT_TRUE(transport_controller_
->SetLocalDescription(SdpType::kOffer, description.get())
.ok());
auto fake_audio_dtls = static_cast<FakeDtlsTransport*>(
transport_controller_->GetDtlsTransport(kAudioMid1));
auto fake_video_dtls = static_cast<FakeDtlsTransport*>(
transport_controller_->GetDtlsTransport(kVideoMid1));
// First, have one transport connect, and another fail, to ensure that
// the first transport connecting didn't trigger a "connected" state signal.
// We should only get a signal when all are connected.
fake_audio_dtls->fake_ice_transport()->SetTransportState(
IceTransportState::kCompleted,
cricket::IceTransportState::STATE_COMPLETED);
fake_audio_dtls->SetWritable(true);
fake_audio_dtls->fake_ice_transport()->SetCandidatesGatheringComplete();
EXPECT_EQ_WAIT(PeerConnectionInterface::kIceConnectionChecking,
ice_connection_state_, kTimeout);
EXPECT_EQ(1, ice_connection_state_signal_count_);
EXPECT_EQ_WAIT(PeerConnectionInterface::PeerConnectionState::kConnecting,
combined_connection_state_, kTimeout);
EXPECT_EQ(1, combined_connection_state_signal_count_);
fake_video_dtls->fake_ice_transport()->SetTransportState(
IceTransportState::kFailed, cricket::IceTransportState::STATE_FAILED);
fake_video_dtls->fake_ice_transport()->SetCandidatesGatheringComplete();
EXPECT_EQ_WAIT(cricket::kIceConnectionFailed, connection_state_, kTimeout);
EXPECT_EQ(1, connection_state_signal_count_);
EXPECT_EQ_WAIT(PeerConnectionInterface::kIceConnectionFailed,
ice_connection_state_, kTimeout);
EXPECT_EQ(2, ice_connection_state_signal_count_);
EXPECT_EQ_WAIT(PeerConnectionInterface::PeerConnectionState::kFailed,
combined_connection_state_, kTimeout);
EXPECT_EQ(2, combined_connection_state_signal_count_);
fake_audio_dtls->SetDtlsState(DtlsTransportState::kConnected);
fake_video_dtls->SetDtlsState(DtlsTransportState::kConnected);
// Set the connection count to be 1 and the cricket::FakeIceTransport will set
// the transport state to be STATE_COMPLETED.
fake_video_dtls->fake_ice_transport()->SetTransportState(
IceTransportState::kCompleted,
cricket::IceTransportState::STATE_COMPLETED);
fake_video_dtls->SetWritable(true);
EXPECT_EQ_WAIT(cricket::kIceConnectionCompleted, connection_state_, kTimeout);
EXPECT_EQ(3, connection_state_signal_count_);
EXPECT_EQ_WAIT(PeerConnectionInterface::kIceConnectionCompleted,
ice_connection_state_, kTimeout);
EXPECT_EQ(3, ice_connection_state_signal_count_);
EXPECT_EQ_WAIT(PeerConnectionInterface::PeerConnectionState::kConnected,
combined_connection_state_, kTimeout);
EXPECT_EQ(3, combined_connection_state_signal_count_);
}
TEST_F(JsepTransportControllerTest, SignalIceGatheringStateGathering) {
CreateJsepTransportController(JsepTransportController::Config());
auto description = CreateSessionDescriptionWithoutBundle();
EXPECT_TRUE(transport_controller_
->SetLocalDescription(SdpType::kOffer, description.get())
.ok());
auto fake_audio_dtls = static_cast<FakeDtlsTransport*>(
transport_controller_->GetDtlsTransport(kAudioMid1));
fake_audio_dtls->fake_ice_transport()->MaybeStartGathering();
// Should be in the gathering state as soon as any transport starts gathering.
EXPECT_EQ_WAIT(cricket::kIceGatheringGathering, gathering_state_, kTimeout);
EXPECT_EQ(1, gathering_state_signal_count_);
}
TEST_F(JsepTransportControllerTest, SignalIceGatheringStateComplete) {
CreateJsepTransportController(JsepTransportController::Config());
auto description = CreateSessionDescriptionWithoutBundle();
EXPECT_TRUE(transport_controller_
->SetLocalDescription(SdpType::kOffer, description.get())
.ok());
auto fake_audio_dtls = static_cast<FakeDtlsTransport*>(
transport_controller_->GetDtlsTransport(kAudioMid1));
auto fake_video_dtls = static_cast<FakeDtlsTransport*>(
transport_controller_->GetDtlsTransport(kVideoMid1));
fake_audio_dtls->fake_ice_transport()->MaybeStartGathering();
EXPECT_EQ_WAIT(cricket::kIceGatheringGathering, gathering_state_, kTimeout);
EXPECT_EQ(1, gathering_state_signal_count_);
// Have one transport finish gathering, to make sure gathering
// completion wasn't signalled if only one transport finished gathering.
fake_audio_dtls->fake_ice_transport()->SetCandidatesGatheringComplete();
EXPECT_EQ(1, gathering_state_signal_count_);
fake_video_dtls->fake_ice_transport()->MaybeStartGathering();
EXPECT_EQ_WAIT(cricket::kIceGatheringGathering, gathering_state_, kTimeout);
EXPECT_EQ(1, gathering_state_signal_count_);
fake_video_dtls->fake_ice_transport()->SetCandidatesGatheringComplete();
EXPECT_EQ_WAIT(cricket::kIceGatheringComplete, gathering_state_, kTimeout);
EXPECT_EQ(2, gathering_state_signal_count_);
}
// Test that when the last transport that hasn't finished connecting and/or
// gathering is destroyed, the aggregate state jumps to "completed". This can
// happen if, for example, we have an audio and video transport, the audio
// transport completes, then we start bundling video on the audio transport.
TEST_F(JsepTransportControllerTest,
SignalingWhenLastIncompleteTransportDestroyed) {
CreateJsepTransportController(JsepTransportController::Config());
auto description = CreateSessionDescriptionWithBundleGroup();
EXPECT_TRUE(transport_controller_
->SetLocalDescription(SdpType::kOffer, description.get())
.ok());
auto fake_audio_dtls = static_cast<FakeDtlsTransport*>(
transport_controller_->GetDtlsTransport(kAudioMid1));
auto fake_video_dtls = static_cast<FakeDtlsTransport*>(
transport_controller_->GetDtlsTransport(kVideoMid1));
EXPECT_NE(fake_audio_dtls, fake_video_dtls);
fake_audio_dtls->fake_ice_transport()->MaybeStartGathering();
EXPECT_EQ_WAIT(cricket::kIceGatheringGathering, gathering_state_, kTimeout);
EXPECT_EQ(1, gathering_state_signal_count_);
// Let the audio transport complete.
fake_audio_dtls->SetWritable(true);
fake_audio_dtls->fake_ice_transport()->SetCandidatesGatheringComplete();
fake_audio_dtls->fake_ice_transport()->SetConnectionCount(1);
fake_audio_dtls->SetDtlsState(DtlsTransportState::kConnected);
EXPECT_EQ(1, gathering_state_signal_count_);
// Set the remote description and enable the bundle.
EXPECT_TRUE(transport_controller_
->SetRemoteDescription(SdpType::kAnswer, description.get())
.ok());
// The BUNDLE should be enabled, the incomplete video transport should be
// deleted and the states shoud be updated.
fake_video_dtls = static_cast<FakeDtlsTransport*>(
transport_controller_->GetDtlsTransport(kVideoMid1));
EXPECT_EQ(fake_audio_dtls, fake_video_dtls);
EXPECT_EQ_WAIT(cricket::kIceConnectionCompleted, connection_state_, kTimeout);
EXPECT_EQ(PeerConnectionInterface::kIceConnectionCompleted,
ice_connection_state_);
EXPECT_EQ(PeerConnectionInterface::PeerConnectionState::kConnected,
combined_connection_state_);
EXPECT_EQ_WAIT(cricket::kIceGatheringComplete, gathering_state_, kTimeout);
EXPECT_EQ(2, gathering_state_signal_count_);
}
// Test that states immediately return to "new" if all transports are
// discarded. This should happen at offer time, even though the transport
// controller may keep the transport alive in case of rollback.
TEST_F(JsepTransportControllerTest,
IceStatesReturnToNewWhenTransportsDiscarded) {
CreateJsepTransportController(JsepTransportController::Config());
auto description = std::make_unique<cricket::SessionDescription>();
AddAudioSection(description.get(), kAudioMid1, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
EXPECT_TRUE(transport_controller_
->SetLocalDescription(SdpType::kOffer, description.get())
.ok());
EXPECT_TRUE(transport_controller_
->SetRemoteDescription(SdpType::kAnswer, description.get())
.ok());
// Trigger and verify initial non-new states.
auto fake_audio_dtls = static_cast<FakeDtlsTransport*>(
transport_controller_->GetDtlsTransport(kAudioMid1));
fake_audio_dtls->fake_ice_transport()->MaybeStartGathering();
fake_audio_dtls->fake_ice_transport()->SetTransportState(
webrtc::IceTransportState::kChecking,
cricket::IceTransportState::STATE_CONNECTING);
EXPECT_EQ_WAIT(PeerConnectionInterface::kIceConnectionChecking,
ice_connection_state_, kTimeout);
EXPECT_EQ(1, ice_connection_state_signal_count_);
EXPECT_EQ_WAIT(PeerConnectionInterface::PeerConnectionState::kConnecting,
combined_connection_state_, kTimeout);
EXPECT_EQ(1, combined_connection_state_signal_count_);
EXPECT_EQ_WAIT(cricket::kIceGatheringGathering, gathering_state_, kTimeout);
EXPECT_EQ(1, gathering_state_signal_count_);
// Reject m= section which should disconnect the transport and return states
// to "new".
description->contents()[0].rejected = true;
EXPECT_TRUE(transport_controller_
->SetRemoteDescription(SdpType::kOffer, description.get())
.ok());
EXPECT_EQ_WAIT(PeerConnectionInterface::kIceConnectionNew,
ice_connection_state_, kTimeout);
EXPECT_EQ(2, ice_connection_state_signal_count_);
EXPECT_EQ_WAIT(PeerConnectionInterface::PeerConnectionState::kNew,
combined_connection_state_, kTimeout);
EXPECT_EQ(2, combined_connection_state_signal_count_);
EXPECT_EQ_WAIT(cricket::kIceGatheringNew, gathering_state_, kTimeout);
EXPECT_EQ(2, gathering_state_signal_count_);
// For good measure, rollback the offer and verify that states return to
// their previous values.
EXPECT_TRUE(transport_controller_->RollbackTransports().ok());
EXPECT_EQ_WAIT(PeerConnectionInterface::kIceConnectionChecking,
ice_connection_state_, kTimeout);
EXPECT_EQ(3, ice_connection_state_signal_count_);
EXPECT_EQ_WAIT(PeerConnectionInterface::PeerConnectionState::kConnecting,
combined_connection_state_, kTimeout);
EXPECT_EQ(3, combined_connection_state_signal_count_);
EXPECT_EQ_WAIT(cricket::kIceGatheringGathering, gathering_state_, kTimeout);
EXPECT_EQ(3, gathering_state_signal_count_);
}
TEST_F(JsepTransportControllerTest, SignalCandidatesGathered) {
CreateJsepTransportController(JsepTransportController::Config());
auto description = CreateSessionDescriptionWithBundleGroup();
EXPECT_TRUE(transport_controller_
->SetLocalDescription(SdpType::kOffer, description.get())
.ok());
transport_controller_->MaybeStartGathering();
auto fake_audio_dtls = static_cast<FakeDtlsTransport*>(
transport_controller_->GetDtlsTransport(kAudioMid1));
fake_audio_dtls->fake_ice_transport()->SignalCandidateGathered(
fake_audio_dtls->fake_ice_transport(), CreateCandidate(kAudioMid1, 1));
EXPECT_EQ_WAIT(1, candidates_signal_count_, kTimeout);
EXPECT_EQ(1u, candidates_[kAudioMid1].size());
}
TEST_F(JsepTransportControllerTest, IceSignalingOccursOnNetworkThread) {
network_thread_ = rtc::Thread::CreateWithSocketServer();
network_thread_->Start();
EXPECT_EQ(ice_signaled_on_thread_, nullptr);
CreateJsepTransportController(JsepTransportController::Config(),
network_thread_.get(),
/*port_allocator=*/nullptr);
CreateLocalDescriptionAndCompleteConnectionOnNetworkThread();
// connecting --> connected --> completed
EXPECT_EQ_WAIT(cricket::kIceConnectionCompleted, connection_state_, kTimeout);
EXPECT_EQ(2, connection_state_signal_count_);
// new --> gathering --> complete
EXPECT_EQ_WAIT(cricket::kIceGatheringComplete, gathering_state_, kTimeout);
EXPECT_EQ(2, gathering_state_signal_count_);
EXPECT_EQ_WAIT(1u, candidates_[kAudioMid1].size(), kTimeout);
EXPECT_EQ_WAIT(1u, candidates_[kVideoMid1].size(), kTimeout);
EXPECT_EQ(2, candidates_signal_count_);
EXPECT_EQ(ice_signaled_on_thread_, network_thread_.get());
SendTask(network_thread_.get(), [&] { transport_controller_.reset(); });
}
// Test that if the TransportController was created with the
// `redetermine_role_on_ice_restart` parameter set to false, the role is *not*
// redetermined on an ICE restart.
TEST_F(JsepTransportControllerTest, IceRoleNotRedetermined) {
JsepTransportController::Config config;
config.redetermine_role_on_ice_restart = false;
CreateJsepTransportController(std::move(config));
// Let the `transport_controller_` be the controlled side initially.
auto remote_offer = std::make_unique<cricket::SessionDescription>();
AddAudioSection(remote_offer.get(), kAudioMid1, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
auto local_answer = std::make_unique<cricket::SessionDescription>();
AddAudioSection(local_answer.get(), kAudioMid1, kIceUfrag2, kIcePwd2,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_PASSIVE,
nullptr);
EXPECT_TRUE(transport_controller_
->SetRemoteDescription(SdpType::kOffer, remote_offer.get())
.ok());
EXPECT_TRUE(transport_controller_
->SetLocalDescription(SdpType::kAnswer, local_answer.get())
.ok());
auto fake_dtls = static_cast<FakeDtlsTransport*>(
transport_controller_->GetDtlsTransport(kAudioMid1));
EXPECT_EQ(cricket::ICEROLE_CONTROLLED,
fake_dtls->fake_ice_transport()->GetIceRole());
// New offer will trigger the ICE restart.
auto restart_local_offer = std::make_unique<cricket::SessionDescription>();
AddAudioSection(restart_local_offer.get(), kAudioMid1, kIceUfrag3, kIcePwd3,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
EXPECT_TRUE(
transport_controller_
->SetLocalDescription(SdpType::kOffer, restart_local_offer.get())
.ok());
EXPECT_EQ(cricket::ICEROLE_CONTROLLED,
fake_dtls->fake_ice_transport()->GetIceRole());
}
// Tests ICE-Lite mode in remote answer.
TEST_F(JsepTransportControllerTest, SetIceRoleWhenIceLiteInRemoteAnswer) {
CreateJsepTransportController(JsepTransportController::Config());
auto local_offer = std::make_unique<cricket::SessionDescription>();
AddAudioSection(local_offer.get(), kAudioMid1, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
EXPECT_TRUE(transport_controller_
->SetLocalDescription(SdpType::kOffer, local_offer.get())
.ok());
auto fake_dtls = static_cast<FakeDtlsTransport*>(
transport_controller_->GetDtlsTransport(kAudioMid1));
EXPECT_EQ(cricket::ICEROLE_CONTROLLING,
fake_dtls->fake_ice_transport()->GetIceRole());
EXPECT_EQ(cricket::ICEMODE_FULL,
fake_dtls->fake_ice_transport()->remote_ice_mode());
auto remote_answer = std::make_unique<cricket::SessionDescription>();
AddAudioSection(remote_answer.get(), kAudioMid1, kIceUfrag2, kIcePwd2,
cricket::ICEMODE_LITE, cricket::CONNECTIONROLE_PASSIVE,
nullptr);
EXPECT_TRUE(transport_controller_
->SetRemoteDescription(SdpType::kAnswer, remote_answer.get())
.ok());
EXPECT_EQ(cricket::ICEROLE_CONTROLLING,
fake_dtls->fake_ice_transport()->GetIceRole());
EXPECT_EQ(cricket::ICEMODE_LITE,
fake_dtls->fake_ice_transport()->remote_ice_mode());
}
// Tests that the ICE role remains "controlling" if a subsequent offer that
// does an ICE restart is received from an ICE lite endpoint. Regression test
// for: https://crbug.com/710760
TEST_F(JsepTransportControllerTest,
IceRoleIsControllingAfterIceRestartFromIceLiteEndpoint) {
CreateJsepTransportController(JsepTransportController::Config());
auto remote_offer = std::make_unique<cricket::SessionDescription>();
AddAudioSection(remote_offer.get(), kAudioMid1, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_LITE, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
auto local_answer = std::make_unique<cricket::SessionDescription>();
AddAudioSection(local_answer.get(), kAudioMid1, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_PASSIVE,
nullptr);
// Initial Offer/Answer exchange. If the remote offerer is ICE-Lite, then the
// local side is the controlling.
EXPECT_TRUE(transport_controller_
->SetRemoteDescription(SdpType::kOffer, remote_offer.get())
.ok());
EXPECT_TRUE(transport_controller_
->SetLocalDescription(SdpType::kAnswer, local_answer.get())
.ok());
auto fake_dtls = static_cast<FakeDtlsTransport*>(
transport_controller_->GetDtlsTransport(kAudioMid1));
EXPECT_EQ(cricket::ICEROLE_CONTROLLING,
fake_dtls->fake_ice_transport()->GetIceRole());
// In the subsequence remote offer triggers an ICE restart.
auto remote_offer2 = std::make_unique<cricket::SessionDescription>();
AddAudioSection(remote_offer2.get(), kAudioMid1, kIceUfrag2, kIcePwd2,
cricket::ICEMODE_LITE, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
auto local_answer2 = std::make_unique<cricket::SessionDescription>();
AddAudioSection(local_answer2.get(), kAudioMid1, kIceUfrag2, kIcePwd2,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_PASSIVE,
nullptr);
EXPECT_TRUE(transport_controller_
->SetRemoteDescription(SdpType::kOffer, remote_offer2.get())
.ok());
EXPECT_TRUE(transport_controller_
->SetLocalDescription(SdpType::kAnswer, local_answer2.get())
.ok());
fake_dtls = static_cast<FakeDtlsTransport*>(
transport_controller_->GetDtlsTransport(kAudioMid1));
// The local side is still the controlling role since the remote side is using
// ICE-Lite.
EXPECT_EQ(cricket::ICEROLE_CONTROLLING,
fake_dtls->fake_ice_transport()->GetIceRole());
}
// Tests that the SDP has more than one audio/video m= sections.
TEST_F(JsepTransportControllerTest, MultipleMediaSectionsOfSameTypeWithBundle) {
CreateJsepTransportController(JsepTransportController::Config());
cricket::ContentGroup bundle_group(cricket::GROUP_TYPE_BUNDLE);
bundle_group.AddContentName(kAudioMid1);
bundle_group.AddContentName(kAudioMid2);
bundle_group.AddContentName(kVideoMid1);
bundle_group.AddContentName(kDataMid1);
auto local_offer = std::make_unique<cricket::SessionDescription>();
AddAudioSection(local_offer.get(), kAudioMid1, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddAudioSection(local_offer.get(), kAudioMid2, kIceUfrag2, kIcePwd2,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddVideoSection(local_offer.get(), kVideoMid1, kIceUfrag3, kIcePwd3,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddDataSection(local_offer.get(), kDataMid1,
cricket::MediaProtocolType::kSctp, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
auto remote_answer = std::make_unique<cricket::SessionDescription>();
AddAudioSection(remote_answer.get(), kAudioMid1, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_PASSIVE,
nullptr);
AddAudioSection(remote_answer.get(), kAudioMid2, kIceUfrag2, kIcePwd2,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_PASSIVE,
nullptr);
AddVideoSection(remote_answer.get(), kVideoMid1, kIceUfrag3, kIcePwd3,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_PASSIVE,
nullptr);
AddDataSection(remote_answer.get(), kDataMid1,
cricket::MediaProtocolType::kSctp, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_PASSIVE,
nullptr);
local_offer->AddGroup(bundle_group);
remote_answer->AddGroup(bundle_group);
EXPECT_TRUE(transport_controller_
->SetLocalDescription(SdpType::kOffer, local_offer.get())
.ok());
EXPECT_TRUE(transport_controller_
->SetRemoteDescription(SdpType::kAnswer, remote_answer.get())
.ok());
// Verify that all the sections are bundled on kAudio1.
auto transport1 = transport_controller_->GetRtpTransport(kAudioMid1);
auto transport2 = transport_controller_->GetRtpTransport(kAudioMid2);
auto transport3 = transport_controller_->GetRtpTransport(kVideoMid1);
auto transport4 = transport_controller_->GetRtpTransport(kDataMid1);
EXPECT_EQ(transport1, transport2);
EXPECT_EQ(transport1, transport3);
EXPECT_EQ(transport1, transport4);
EXPECT_EQ(transport_controller_->LookupDtlsTransportByMid(kAudioMid1),
transport_controller_->LookupDtlsTransportByMid(kVideoMid1));
// Verify the OnRtpTransport/DtlsTransportChanged signals are fired correctly.
auto it = changed_rtp_transport_by_mid_.find(kAudioMid2);
ASSERT_TRUE(it != changed_rtp_transport_by_mid_.end());
EXPECT_EQ(transport1, it->second);
it = changed_rtp_transport_by_mid_.find(kAudioMid2);
ASSERT_TRUE(it != changed_rtp_transport_by_mid_.end());
EXPECT_EQ(transport1, it->second);
it = changed_rtp_transport_by_mid_.find(kVideoMid1);
ASSERT_TRUE(it != changed_rtp_transport_by_mid_.end());
EXPECT_EQ(transport1, it->second);
// Verify the DtlsTransport for the SCTP data channel is reset correctly.
auto it2 = changed_dtls_transport_by_mid_.find(kDataMid1);
ASSERT_TRUE(it2 != changed_dtls_transport_by_mid_.end());
}
TEST_F(JsepTransportControllerTest, MultipleBundleGroups) {
static const char kMid1Audio[] = "1_audio";
static const char kMid2Video[] = "2_video";
static const char kMid3Audio[] = "3_audio";
static const char kMid4Video[] = "4_video";
CreateJsepTransportController(JsepTransportController::Config());
cricket::ContentGroup bundle_group1(cricket::GROUP_TYPE_BUNDLE);
bundle_group1.AddContentName(kMid1Audio);
bundle_group1.AddContentName(kMid2Video);
cricket::ContentGroup bundle_group2(cricket::GROUP_TYPE_BUNDLE);
bundle_group2.AddContentName(kMid3Audio);
bundle_group2.AddContentName(kMid4Video);
auto local_offer = std::make_unique<cricket::SessionDescription>();
AddAudioSection(local_offer.get(), kMid1Audio, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddVideoSection(local_offer.get(), kMid2Video, kIceUfrag2, kIcePwd2,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddAudioSection(local_offer.get(), kMid3Audio, kIceUfrag3, kIcePwd3,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddVideoSection(local_offer.get(), kMid4Video, kIceUfrag4, kIcePwd4,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
local_offer->AddGroup(bundle_group1);
local_offer->AddGroup(bundle_group2);
auto remote_answer = std::make_unique<cricket::SessionDescription>();
AddAudioSection(remote_answer.get(), kMid1Audio, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddVideoSection(remote_answer.get(), kMid2Video, kIceUfrag2, kIcePwd2,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddAudioSection(remote_answer.get(), kMid3Audio, kIceUfrag3, kIcePwd3,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddVideoSection(remote_answer.get(), kMid4Video, kIceUfrag4, kIcePwd4,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
remote_answer->AddGroup(bundle_group1);
remote_answer->AddGroup(bundle_group2);
EXPECT_TRUE(transport_controller_
->SetLocalDescription(SdpType::kOffer, local_offer.get())
.ok());
EXPECT_TRUE(transport_controller_
->SetRemoteDescription(SdpType::kAnswer, remote_answer.get())
.ok());
// Verify that (kMid1Audio,kMid2Video) and (kMid3Audio,kMid4Video) form two
// distinct bundled groups.
auto mid1_transport = transport_controller_->GetRtpTransport(kMid1Audio);
auto mid2_transport = transport_controller_->GetRtpTransport(kMid2Video);
auto mid3_transport = transport_controller_->GetRtpTransport(kMid3Audio);
auto mid4_transport = transport_controller_->GetRtpTransport(kMid4Video);
EXPECT_EQ(mid1_transport, mid2_transport);
EXPECT_EQ(mid3_transport, mid4_transport);
EXPECT_NE(mid1_transport, mid3_transport);
auto it = changed_rtp_transport_by_mid_.find(kMid1Audio);
ASSERT_TRUE(it != changed_rtp_transport_by_mid_.end());
EXPECT_EQ(it->second, mid1_transport);
it = changed_rtp_transport_by_mid_.find(kMid2Video);
ASSERT_TRUE(it != changed_rtp_transport_by_mid_.end());
EXPECT_EQ(it->second, mid2_transport);
it = changed_rtp_transport_by_mid_.find(kMid3Audio);
ASSERT_TRUE(it != changed_rtp_transport_by_mid_.end());
EXPECT_EQ(it->second, mid3_transport);
it = changed_rtp_transport_by_mid_.find(kMid4Video);
ASSERT_TRUE(it != changed_rtp_transport_by_mid_.end());
EXPECT_EQ(it->second, mid4_transport);
}
TEST_F(JsepTransportControllerTest,
MultipleBundleGroupsInOfferButOnlyASingleGroupInAnswer) {
static const char kMid1Audio[] = "1_audio";
static const char kMid2Video[] = "2_video";
static const char kMid3Audio[] = "3_audio";
static const char kMid4Video[] = "4_video";
CreateJsepTransportController(JsepTransportController::Config());
cricket::ContentGroup bundle_group1(cricket::GROUP_TYPE_BUNDLE);
bundle_group1.AddContentName(kMid1Audio);
bundle_group1.AddContentName(kMid2Video);
cricket::ContentGroup bundle_group2(cricket::GROUP_TYPE_BUNDLE);
bundle_group2.AddContentName(kMid3Audio);
bundle_group2.AddContentName(kMid4Video);
auto local_offer = std::make_unique<cricket::SessionDescription>();
AddAudioSection(local_offer.get(), kMid1Audio, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddVideoSection(local_offer.get(), kMid2Video, kIceUfrag2, kIcePwd2,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddAudioSection(local_offer.get(), kMid3Audio, kIceUfrag3, kIcePwd3,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddVideoSection(local_offer.get(), kMid4Video, kIceUfrag4, kIcePwd4,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
// The offer has both groups.
local_offer->AddGroup(bundle_group1);
local_offer->AddGroup(bundle_group2);
auto remote_answer = std::make_unique<cricket::SessionDescription>();
AddAudioSection(remote_answer.get(), kMid1Audio, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddVideoSection(remote_answer.get(), kMid2Video, kIceUfrag2, kIcePwd2,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddAudioSection(remote_answer.get(), kMid3Audio, kIceUfrag3, kIcePwd3,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddVideoSection(remote_answer.get(), kMid4Video, kIceUfrag4, kIcePwd4,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
// The answer only has a single group! This is what happens when talking to an
// endpoint that does not have support for multiple BUNDLE groups.
remote_answer->AddGroup(bundle_group1);
EXPECT_TRUE(transport_controller_
->SetLocalDescription(SdpType::kOffer, local_offer.get())
.ok());
EXPECT_TRUE(transport_controller_
->SetRemoteDescription(SdpType::kAnswer, remote_answer.get())
.ok());
// Verify that (kMid1Audio,kMid2Video) form a bundle group, but that
// kMid3Audio and kMid4Video are unbundled.
auto mid1_transport = transport_controller_->GetRtpTransport(kMid1Audio);
auto mid2_transport = transport_controller_->GetRtpTransport(kMid2Video);
auto mid3_transport = transport_controller_->GetRtpTransport(kMid3Audio);
auto mid4_transport = transport_controller_->GetRtpTransport(kMid4Video);
EXPECT_EQ(mid1_transport, mid2_transport);
EXPECT_NE(mid3_transport, mid4_transport);
EXPECT_NE(mid1_transport, mid3_transport);
EXPECT_NE(mid1_transport, mid4_transport);
}
TEST_F(JsepTransportControllerTest, MultipleBundleGroupsIllegallyChangeGroup) {
static const char kMid1Audio[] = "1_audio";
static const char kMid2Video[] = "2_video";
static const char kMid3Audio[] = "3_audio";
static const char kMid4Video[] = "4_video";
CreateJsepTransportController(JsepTransportController::Config());
// Offer groups (kMid1Audio,kMid2Video) and (kMid3Audio,kMid4Video).
cricket::ContentGroup offer_bundle_group1(cricket::GROUP_TYPE_BUNDLE);
offer_bundle_group1.AddContentName(kMid1Audio);
offer_bundle_group1.AddContentName(kMid2Video);
cricket::ContentGroup offer_bundle_group2(cricket::GROUP_TYPE_BUNDLE);
offer_bundle_group2.AddContentName(kMid3Audio);
offer_bundle_group2.AddContentName(kMid4Video);
// Answer groups (kMid1Audio,kMid4Video) and (kMid3Audio,kMid2Video), i.e. the
// second group members have switched places. This should get rejected.
cricket::ContentGroup answer_bundle_group1(cricket::GROUP_TYPE_BUNDLE);
answer_bundle_group1.AddContentName(kMid1Audio);
answer_bundle_group1.AddContentName(kMid4Video);
cricket::ContentGroup answer_bundle_group2(cricket::GROUP_TYPE_BUNDLE);
answer_bundle_group2.AddContentName(kMid3Audio);
answer_bundle_group2.AddContentName(kMid2Video);
auto local_offer = std::make_unique<cricket::SessionDescription>();
AddAudioSection(local_offer.get(), kMid1Audio, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddVideoSection(local_offer.get(), kMid2Video, kIceUfrag2, kIcePwd2,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddAudioSection(local_offer.get(), kMid3Audio, kIceUfrag3, kIcePwd3,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddVideoSection(local_offer.get(), kMid4Video, kIceUfrag4, kIcePwd4,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
local_offer->AddGroup(offer_bundle_group1);
local_offer->AddGroup(offer_bundle_group2);
auto remote_answer = std::make_unique<cricket::SessionDescription>();
AddAudioSection(remote_answer.get(), kMid1Audio, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddVideoSection(remote_answer.get(), kMid2Video, kIceUfrag2, kIcePwd2,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddAudioSection(remote_answer.get(), kMid3Audio, kIceUfrag3, kIcePwd3,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddVideoSection(remote_answer.get(), kMid4Video, kIceUfrag4, kIcePwd4,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
remote_answer->AddGroup(answer_bundle_group1);
remote_answer->AddGroup(answer_bundle_group2);
// Accept offer.
EXPECT_TRUE(transport_controller_
->SetLocalDescription(SdpType::kOffer, local_offer.get())
.ok());
// Reject answer!
EXPECT_FALSE(transport_controller_
->SetRemoteDescription(SdpType::kAnswer, remote_answer.get())
.ok());
}
TEST_F(JsepTransportControllerTest, MultipleBundleGroupsInvalidSubsets) {
static const char kMid1Audio[] = "1_audio";
static const char kMid2Video[] = "2_video";
static const char kMid3Audio[] = "3_audio";
static const char kMid4Video[] = "4_video";
CreateJsepTransportController(JsepTransportController::Config());
// Offer groups (kMid1Audio,kMid2Video) and (kMid3Audio,kMid4Video).
cricket::ContentGroup offer_bundle_group1(cricket::GROUP_TYPE_BUNDLE);
offer_bundle_group1.AddContentName(kMid1Audio);
offer_bundle_group1.AddContentName(kMid2Video);
cricket::ContentGroup offer_bundle_group2(cricket::GROUP_TYPE_BUNDLE);
offer_bundle_group2.AddContentName(kMid3Audio);
offer_bundle_group2.AddContentName(kMid4Video);
// Answer groups (kMid1Audio) and (kMid2Video), i.e. the second group was
// moved from the first group. This should get rejected.
cricket::ContentGroup answer_bundle_group1(cricket::GROUP_TYPE_BUNDLE);
answer_bundle_group1.AddContentName(kMid1Audio);
cricket::ContentGroup answer_bundle_group2(cricket::GROUP_TYPE_BUNDLE);
answer_bundle_group2.AddContentName(kMid2Video);
auto local_offer = std::make_unique<cricket::SessionDescription>();
AddAudioSection(local_offer.get(), kMid1Audio, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddVideoSection(local_offer.get(), kMid2Video, kIceUfrag2, kIcePwd2,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddAudioSection(local_offer.get(), kMid3Audio, kIceUfrag3, kIcePwd3,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddVideoSection(local_offer.get(), kMid4Video, kIceUfrag4, kIcePwd4,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
local_offer->AddGroup(offer_bundle_group1);
local_offer->AddGroup(offer_bundle_group2);
auto remote_answer = std::make_unique<cricket::SessionDescription>();
AddAudioSection(remote_answer.get(), kMid1Audio, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddVideoSection(remote_answer.get(), kMid2Video, kIceUfrag2, kIcePwd2,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddAudioSection(remote_answer.get(), kMid3Audio, kIceUfrag3, kIcePwd3,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddVideoSection(remote_answer.get(), kMid4Video, kIceUfrag4, kIcePwd4,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
remote_answer->AddGroup(answer_bundle_group1);
remote_answer->AddGroup(answer_bundle_group2);
// Accept offer.
EXPECT_TRUE(transport_controller_
->SetLocalDescription(SdpType::kOffer, local_offer.get())
.ok());
// Reject answer!
EXPECT_FALSE(transport_controller_
->SetRemoteDescription(SdpType::kAnswer, remote_answer.get())
.ok());
}
TEST_F(JsepTransportControllerTest, MultipleBundleGroupsInvalidOverlap) {
static const char kMid1Audio[] = "1_audio";
static const char kMid2Video[] = "2_video";
static const char kMid3Audio[] = "3_audio";
CreateJsepTransportController(JsepTransportController::Config());
// Offer groups (kMid1Audio,kMid3Audio) and (kMid2Video,kMid3Audio), i.e.
// kMid3Audio is in both groups - this is illegal.
cricket::ContentGroup offer_bundle_group1(cricket::GROUP_TYPE_BUNDLE);
offer_bundle_group1.AddContentName(kMid1Audio);
offer_bundle_group1.AddContentName(kMid3Audio);
cricket::ContentGroup offer_bundle_group2(cricket::GROUP_TYPE_BUNDLE);
offer_bundle_group2.AddContentName(kMid2Video);
offer_bundle_group2.AddContentName(kMid3Audio);
auto offer = std::make_unique<cricket::SessionDescription>();
AddAudioSection(offer.get(), kMid1Audio, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddVideoSection(offer.get(), kMid2Video, kIceUfrag2, kIcePwd2,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddAudioSection(offer.get(), kMid3Audio, kIceUfrag3, kIcePwd3,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
offer->AddGroup(offer_bundle_group1);
offer->AddGroup(offer_bundle_group2);
// Reject offer, both if set as local or remote.
EXPECT_FALSE(
transport_controller_->SetLocalDescription(SdpType::kOffer, offer.get())
.ok());
EXPECT_FALSE(
transport_controller_->SetRemoteDescription(SdpType::kOffer, offer.get())
.ok());
}
TEST_F(JsepTransportControllerTest, MultipleBundleGroupsUnbundleFirstMid) {
static const char kMid1Audio[] = "1_audio";
static const char kMid2Audio[] = "2_audio";
static const char kMid3Audio[] = "3_audio";
static const char kMid4Video[] = "4_video";
static const char kMid5Video[] = "5_video";
static const char kMid6Video[] = "6_video";
CreateJsepTransportController(JsepTransportController::Config());
// Offer groups (kMid1Audio,kMid2Audio,kMid3Audio) and
// (kMid4Video,kMid5Video,kMid6Video).
cricket::ContentGroup offer_bundle_group1(cricket::GROUP_TYPE_BUNDLE);
offer_bundle_group1.AddContentName(kMid1Audio);
offer_bundle_group1.AddContentName(kMid2Audio);
offer_bundle_group1.AddContentName(kMid3Audio);
cricket::ContentGroup offer_bundle_group2(cricket::GROUP_TYPE_BUNDLE);
offer_bundle_group2.AddContentName(kMid4Video);
offer_bundle_group2.AddContentName(kMid5Video);
offer_bundle_group2.AddContentName(kMid6Video);
// Answer groups (kMid2Audio,kMid3Audio) and (kMid5Video,kMid6Video), i.e.
// we've moved the first MIDs out of the groups.
cricket::ContentGroup answer_bundle_group1(cricket::GROUP_TYPE_BUNDLE);
answer_bundle_group1.AddContentName(kMid2Audio);
answer_bundle_group1.AddContentName(kMid3Audio);
cricket::ContentGroup answer_bundle_group2(cricket::GROUP_TYPE_BUNDLE);
answer_bundle_group2.AddContentName(kMid5Video);
answer_bundle_group2.AddContentName(kMid6Video);
auto local_offer = std::make_unique<cricket::SessionDescription>();
AddAudioSection(local_offer.get(), kMid1Audio, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddAudioSection(local_offer.get(), kMid2Audio, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddAudioSection(local_offer.get(), kMid3Audio, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddVideoSection(local_offer.get(), kMid4Video, kIceUfrag2, kIcePwd2,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddVideoSection(local_offer.get(), kMid5Video, kIceUfrag2, kIcePwd2,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddVideoSection(local_offer.get(), kMid6Video, kIceUfrag2, kIcePwd2,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
local_offer->AddGroup(offer_bundle_group1);
local_offer->AddGroup(offer_bundle_group2);
auto remote_answer = std::make_unique<cricket::SessionDescription>();
AddAudioSection(remote_answer.get(), kMid1Audio, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddAudioSection(remote_answer.get(), kMid2Audio, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddAudioSection(remote_answer.get(), kMid3Audio, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddVideoSection(remote_answer.get(), kMid4Video, kIceUfrag2, kIcePwd2,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddVideoSection(remote_answer.get(), kMid5Video, kIceUfrag2, kIcePwd2,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddVideoSection(remote_answer.get(), kMid6Video, kIceUfrag2, kIcePwd2,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
remote_answer->AddGroup(answer_bundle_group1);
remote_answer->AddGroup(answer_bundle_group2);
EXPECT_TRUE(transport_controller_
->SetLocalDescription(SdpType::kOffer, local_offer.get())
.ok());
EXPECT_TRUE(transport_controller_
->SetRemoteDescription(SdpType::kAnswer, remote_answer.get())
.ok());
auto mid1_transport = transport_controller_->GetRtpTransport(kMid1Audio);
auto mid2_transport = transport_controller_->GetRtpTransport(kMid2Audio);
auto mid3_transport = transport_controller_->GetRtpTransport(kMid3Audio);
auto mid4_transport = transport_controller_->GetRtpTransport(kMid4Video);
auto mid5_transport = transport_controller_->GetRtpTransport(kMid5Video);
auto mid6_transport = transport_controller_->GetRtpTransport(kMid6Video);
EXPECT_NE(mid1_transport, mid2_transport);
EXPECT_EQ(mid2_transport, mid3_transport);
EXPECT_NE(mid4_transport, mid5_transport);
EXPECT_EQ(mid5_transport, mid6_transport);
EXPECT_NE(mid1_transport, mid4_transport);
EXPECT_NE(mid2_transport, mid5_transport);
}
TEST_F(JsepTransportControllerTest, MultipleBundleGroupsChangeFirstMid) {
static const char kMid1Audio[] = "1_audio";
static const char kMid2Audio[] = "2_audio";
static const char kMid3Audio[] = "3_audio";
static const char kMid4Video[] = "4_video";
static const char kMid5Video[] = "5_video";
static const char kMid6Video[] = "6_video";
CreateJsepTransportController(JsepTransportController::Config());
// Offer groups (kMid1Audio,kMid2Audio,kMid3Audio) and
// (kMid4Video,kMid5Video,kMid6Video).
cricket::ContentGroup offer_bundle_group1(cricket::GROUP_TYPE_BUNDLE);
offer_bundle_group1.AddContentName(kMid1Audio);
offer_bundle_group1.AddContentName(kMid2Audio);
offer_bundle_group1.AddContentName(kMid3Audio);
cricket::ContentGroup offer_bundle_group2(cricket::GROUP_TYPE_BUNDLE);
offer_bundle_group2.AddContentName(kMid4Video);
offer_bundle_group2.AddContentName(kMid5Video);
offer_bundle_group2.AddContentName(kMid6Video);
// Answer groups (kMid2Audio,kMid1Audio,kMid3Audio) and
// (kMid5Video,kMid6Video,kMid4Video), i.e. we've changed which MID is first
// but accept the whole group.
cricket::ContentGroup answer_bundle_group1(cricket::GROUP_TYPE_BUNDLE);
answer_bundle_group1.AddContentName(kMid2Audio);
answer_bundle_group1.AddContentName(kMid1Audio);
answer_bundle_group1.AddContentName(kMid3Audio);
cricket::ContentGroup answer_bundle_group2(cricket::GROUP_TYPE_BUNDLE);
answer_bundle_group2.AddContentName(kMid5Video);
answer_bundle_group2.AddContentName(kMid6Video);
answer_bundle_group2.AddContentName(kMid4Video);
auto local_offer = std::make_unique<cricket::SessionDescription>();
AddAudioSection(local_offer.get(), kMid1Audio, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddAudioSection(local_offer.get(), kMid2Audio, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddAudioSection(local_offer.get(), kMid3Audio, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddVideoSection(local_offer.get(), kMid4Video, kIceUfrag2, kIcePwd2,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddVideoSection(local_offer.get(), kMid5Video, kIceUfrag2, kIcePwd2,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddVideoSection(local_offer.get(), kMid6Video, kIceUfrag2, kIcePwd2,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
local_offer->AddGroup(offer_bundle_group1);
local_offer->AddGroup(offer_bundle_group2);
auto remote_answer = std::make_unique<cricket::SessionDescription>();
AddAudioSection(remote_answer.get(), kMid1Audio, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddAudioSection(remote_answer.get(), kMid2Audio, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddAudioSection(remote_answer.get(), kMid3Audio, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddVideoSection(remote_answer.get(), kMid4Video, kIceUfrag2, kIcePwd2,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddVideoSection(remote_answer.get(), kMid5Video, kIceUfrag2, kIcePwd2,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddVideoSection(remote_answer.get(), kMid6Video, kIceUfrag2, kIcePwd2,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
remote_answer->AddGroup(answer_bundle_group1);
remote_answer->AddGroup(answer_bundle_group2);
EXPECT_TRUE(transport_controller_
->SetLocalDescription(SdpType::kOffer, local_offer.get())
.ok());
// The fact that we accept this answer is actually a bug. If we accept the
// first MID to be in the group, we should also accept that it is the tagged
// one.
// TODO(https://crbug.com/webrtc/12699): When this issue is fixed, change this
// to EXPECT_FALSE and remove the below expectations about transports.
EXPECT_TRUE(transport_controller_
->SetRemoteDescription(SdpType::kAnswer, remote_answer.get())
.ok());
auto mid1_transport = transport_controller_->GetRtpTransport(kMid1Audio);
auto mid2_transport = transport_controller_->GetRtpTransport(kMid2Audio);
auto mid3_transport = transport_controller_->GetRtpTransport(kMid3Audio);
auto mid4_transport = transport_controller_->GetRtpTransport(kMid4Video);
auto mid5_transport = transport_controller_->GetRtpTransport(kMid5Video);
auto mid6_transport = transport_controller_->GetRtpTransport(kMid6Video);
EXPECT_NE(mid1_transport, mid4_transport);
EXPECT_EQ(mid1_transport, mid2_transport);
EXPECT_EQ(mid2_transport, mid3_transport);
EXPECT_EQ(mid4_transport, mid5_transport);
EXPECT_EQ(mid5_transport, mid6_transport);
}
TEST_F(JsepTransportControllerTest,
MultipleBundleGroupsSectionsAddedInSubsequentOffer) {
static const char kMid1Audio[] = "1_audio";
static const char kMid2Audio[] = "2_audio";
static const char kMid3Audio[] = "3_audio";
static const char kMid4Video[] = "4_video";
static const char kMid5Video[] = "5_video";
static const char kMid6Video[] = "6_video";
CreateJsepTransportController(JsepTransportController::Config());
// Start by grouping (kMid1Audio,kMid2Audio) and (kMid4Video,kMid4f5Video).
cricket::ContentGroup bundle_group1(cricket::GROUP_TYPE_BUNDLE);
bundle_group1.AddContentName(kMid1Audio);
bundle_group1.AddContentName(kMid2Audio);
cricket::ContentGroup bundle_group2(cricket::GROUP_TYPE_BUNDLE);
bundle_group2.AddContentName(kMid4Video);
bundle_group2.AddContentName(kMid5Video);
auto local_offer = std::make_unique<cricket::SessionDescription>();
AddAudioSection(local_offer.get(), kMid1Audio, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddAudioSection(local_offer.get(), kMid2Audio, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddVideoSection(local_offer.get(), kMid4Video, kIceUfrag2, kIcePwd2,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddVideoSection(local_offer.get(), kMid5Video, kIceUfrag2, kIcePwd2,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
local_offer->AddGroup(bundle_group1);
local_offer->AddGroup(bundle_group2);
auto remote_answer = std::make_unique<cricket::SessionDescription>();
AddAudioSection(remote_answer.get(), kMid1Audio, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddAudioSection(remote_answer.get(), kMid2Audio, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddVideoSection(remote_answer.get(), kMid4Video, kIceUfrag2, kIcePwd2,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddVideoSection(remote_answer.get(), kMid5Video, kIceUfrag2, kIcePwd2,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
remote_answer->AddGroup(bundle_group1);
remote_answer->AddGroup(bundle_group2);
EXPECT_TRUE(transport_controller_
->SetLocalDescription(SdpType::kOffer, local_offer.get())
.ok());
EXPECT_TRUE(transport_controller_
->SetRemoteDescription(SdpType::kAnswer, remote_answer.get())
.ok());
// Add kMid3Audio and kMid6Video to the respective audio/video bundle groups.
cricket::ContentGroup new_bundle_group1(cricket::GROUP_TYPE_BUNDLE);
bundle_group1.AddContentName(kMid3Audio);
cricket::ContentGroup new_bundle_group2(cricket::GROUP_TYPE_BUNDLE);
bundle_group2.AddContentName(kMid6Video);
auto subsequent_offer = std::make_unique<cricket::SessionDescription>();
AddAudioSection(subsequent_offer.get(), kMid1Audio, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddAudioSection(subsequent_offer.get(), kMid2Audio, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddAudioSection(subsequent_offer.get(), kMid3Audio, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddVideoSection(subsequent_offer.get(), kMid4Video, kIceUfrag2, kIcePwd2,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddVideoSection(subsequent_offer.get(), kMid5Video, kIceUfrag2, kIcePwd2,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddVideoSection(subsequent_offer.get(), kMid6Video, kIceUfrag2, kIcePwd2,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
subsequent_offer->AddGroup(bundle_group1);
subsequent_offer->AddGroup(bundle_group2);
EXPECT_TRUE(transport_controller_
->SetLocalDescription(SdpType::kOffer, subsequent_offer.get())
.ok());
auto mid1_transport = transport_controller_->GetRtpTransport(kMid1Audio);
auto mid2_transport = transport_controller_->GetRtpTransport(kMid2Audio);
auto mid3_transport = transport_controller_->GetRtpTransport(kMid3Audio);
auto mid4_transport = transport_controller_->GetRtpTransport(kMid4Video);
auto mid5_transport = transport_controller_->GetRtpTransport(kMid5Video);
auto mid6_transport = transport_controller_->GetRtpTransport(kMid6Video);
EXPECT_NE(mid1_transport, mid4_transport);
EXPECT_EQ(mid1_transport, mid2_transport);
EXPECT_EQ(mid2_transport, mid3_transport);
EXPECT_EQ(mid4_transport, mid5_transport);
EXPECT_EQ(mid5_transport, mid6_transport);
}
TEST_F(JsepTransportControllerTest,
MultipleBundleGroupsCombinedInSubsequentOffer) {
static const char kMid1Audio[] = "1_audio";
static const char kMid2Audio[] = "2_audio";
static const char kMid3Video[] = "3_video";
static const char kMid4Video[] = "4_video";
CreateJsepTransportController(JsepTransportController::Config());
// Start by grouping (kMid1Audio,kMid2Audio) and (kMid3Video,kMid4Video).
cricket::ContentGroup bundle_group1(cricket::GROUP_TYPE_BUNDLE);
bundle_group1.AddContentName(kMid1Audio);
bundle_group1.AddContentName(kMid2Audio);
cricket::ContentGroup bundle_group2(cricket::GROUP_TYPE_BUNDLE);
bundle_group2.AddContentName(kMid3Video);
bundle_group2.AddContentName(kMid4Video);
auto local_offer = std::make_unique<cricket::SessionDescription>();
AddAudioSection(local_offer.get(), kMid1Audio, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddAudioSection(local_offer.get(), kMid2Audio, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddVideoSection(local_offer.get(), kMid3Video, kIceUfrag2, kIcePwd2,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddVideoSection(local_offer.get(), kMid4Video, kIceUfrag2, kIcePwd2,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
local_offer->AddGroup(bundle_group1);
local_offer->AddGroup(bundle_group2);
auto remote_answer = std::make_unique<cricket::SessionDescription>();
AddAudioSection(remote_answer.get(), kMid1Audio, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddAudioSection(remote_answer.get(), kMid2Audio, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddVideoSection(remote_answer.get(), kMid3Video, kIceUfrag2, kIcePwd2,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddVideoSection(remote_answer.get(), kMid4Video, kIceUfrag2, kIcePwd2,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
remote_answer->AddGroup(bundle_group1);
remote_answer->AddGroup(bundle_group2);
EXPECT_TRUE(transport_controller_
->SetLocalDescription(SdpType::kOffer, local_offer.get())
.ok());
EXPECT_TRUE(transport_controller_
->SetRemoteDescription(SdpType::kAnswer, remote_answer.get())
.ok());
// Switch to grouping (kMid1Audio,kMid2Audio,kMid3Video,kMid4Video).
// This is a illegal without first removing m= sections from their groups.
cricket::ContentGroup new_bundle_group(cricket::GROUP_TYPE_BUNDLE);
new_bundle_group.AddContentName(kMid1Audio);
new_bundle_group.AddContentName(kMid2Audio);
new_bundle_group.AddContentName(kMid3Video);
new_bundle_group.AddContentName(kMid4Video);
auto subsequent_offer = std::make_unique<cricket::SessionDescription>();
AddAudioSection(subsequent_offer.get(), kMid1Audio, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddAudioSection(subsequent_offer.get(), kMid2Audio, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddVideoSection(subsequent_offer.get(), kMid3Video, kIceUfrag2, kIcePwd2,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddVideoSection(subsequent_offer.get(), kMid4Video, kIceUfrag2, kIcePwd2,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
subsequent_offer->AddGroup(new_bundle_group);
EXPECT_FALSE(
transport_controller_
->SetLocalDescription(SdpType::kOffer, subsequent_offer.get())
.ok());
}
TEST_F(JsepTransportControllerTest,
MultipleBundleGroupsSplitInSubsequentOffer) {
static const char kMid1Audio[] = "1_audio";
static const char kMid2Audio[] = "2_audio";
static const char kMid3Video[] = "3_video";
static const char kMid4Video[] = "4_video";
CreateJsepTransportController(JsepTransportController::Config());
// Start by grouping (kMid1Audio,kMid2Audio,kMid3Video,kMid4Video).
cricket::ContentGroup bundle_group(cricket::GROUP_TYPE_BUNDLE);
bundle_group.AddContentName(kMid1Audio);
bundle_group.AddContentName(kMid2Audio);
bundle_group.AddContentName(kMid3Video);
bundle_group.AddContentName(kMid4Video);
auto local_offer = std::make_unique<cricket::SessionDescription>();
AddAudioSection(local_offer.get(), kMid1Audio, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddAudioSection(local_offer.get(), kMid2Audio, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddVideoSection(local_offer.get(), kMid3Video, kIceUfrag2, kIcePwd2,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddVideoSection(local_offer.get(), kMid4Video, kIceUfrag2, kIcePwd2,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
local_offer->AddGroup(bundle_group);
auto remote_answer = std::make_unique<cricket::SessionDescription>();
AddAudioSection(remote_answer.get(), kMid1Audio, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddAudioSection(remote_answer.get(), kMid2Audio, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddVideoSection(remote_answer.get(), kMid3Video, kIceUfrag2, kIcePwd2,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddVideoSection(remote_answer.get(), kMid4Video, kIceUfrag2, kIcePwd2,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
remote_answer->AddGroup(bundle_group);
EXPECT_TRUE(transport_controller_
->SetLocalDescription(SdpType::kOffer, local_offer.get())
.ok());
EXPECT_TRUE(transport_controller_
->SetRemoteDescription(SdpType::kAnswer, remote_answer.get())
.ok());
// Switch to grouping (kMid1Audio,kMid2Audio) and (kMid3Video,kMid4Video).
// This is a illegal without first removing m= sections from their groups.
cricket::ContentGroup new_bundle_group1(cricket::GROUP_TYPE_BUNDLE);
new_bundle_group1.AddContentName(kMid1Audio);
new_bundle_group1.AddContentName(kMid2Audio);
cricket::ContentGroup new_bundle_group2(cricket::GROUP_TYPE_BUNDLE);
new_bundle_group2.AddContentName(kMid3Video);
new_bundle_group2.AddContentName(kMid4Video);
auto subsequent_offer = std::make_unique<cricket::SessionDescription>();
AddAudioSection(subsequent_offer.get(), kMid1Audio, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddAudioSection(subsequent_offer.get(), kMid2Audio, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddVideoSection(subsequent_offer.get(), kMid3Video, kIceUfrag2, kIcePwd2,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddVideoSection(subsequent_offer.get(), kMid4Video, kIceUfrag2, kIcePwd2,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
subsequent_offer->AddGroup(new_bundle_group1);
subsequent_offer->AddGroup(new_bundle_group2);
EXPECT_FALSE(
transport_controller_
->SetLocalDescription(SdpType::kOffer, subsequent_offer.get())
.ok());
}
TEST_F(JsepTransportControllerTest,
MultipleBundleGroupsShuffledInSubsequentOffer) {
static const char kMid1Audio[] = "1_audio";
static const char kMid2Audio[] = "2_audio";
static const char kMid3Video[] = "3_video";
static const char kMid4Video[] = "4_video";
CreateJsepTransportController(JsepTransportController::Config());
// Start by grouping (kMid1Audio,kMid2Audio) and (kMid3Video,kMid4Video).
cricket::ContentGroup bundle_group1(cricket::GROUP_TYPE_BUNDLE);
bundle_group1.AddContentName(kMid1Audio);
bundle_group1.AddContentName(kMid2Audio);
cricket::ContentGroup bundle_group2(cricket::GROUP_TYPE_BUNDLE);
bundle_group2.AddContentName(kMid3Video);
bundle_group2.AddContentName(kMid4Video);
auto local_offer = std::make_unique<cricket::SessionDescription>();
AddAudioSection(local_offer.get(), kMid1Audio, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddAudioSection(local_offer.get(), kMid2Audio, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddVideoSection(local_offer.get(), kMid3Video, kIceUfrag2, kIcePwd2,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddVideoSection(local_offer.get(), kMid4Video, kIceUfrag2, kIcePwd2,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
local_offer->AddGroup(bundle_group1);
local_offer->AddGroup(bundle_group2);
auto remote_answer = std::make_unique<cricket::SessionDescription>();
AddAudioSection(remote_answer.get(), kMid1Audio, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddAudioSection(remote_answer.get(), kMid2Audio, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddVideoSection(remote_answer.get(), kMid3Video, kIceUfrag2, kIcePwd2,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddVideoSection(remote_answer.get(), kMid4Video, kIceUfrag2, kIcePwd2,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
remote_answer->AddGroup(bundle_group1);
remote_answer->AddGroup(bundle_group2);
EXPECT_TRUE(transport_controller_
->SetLocalDescription(SdpType::kOffer, local_offer.get())
.ok());
EXPECT_TRUE(transport_controller_
->SetRemoteDescription(SdpType::kAnswer, remote_answer.get())
.ok());
// Switch to grouping (kMid1Audio,kMid3Video) and (kMid2Audio,kMid3Video).
// This is a illegal without first removing m= sections from their groups.
cricket::ContentGroup new_bundle_group1(cricket::GROUP_TYPE_BUNDLE);
new_bundle_group1.AddContentName(kMid1Audio);
new_bundle_group1.AddContentName(kMid3Video);
cricket::ContentGroup new_bundle_group2(cricket::GROUP_TYPE_BUNDLE);
new_bundle_group2.AddContentName(kMid2Audio);
new_bundle_group2.AddContentName(kMid4Video);
auto subsequent_offer = std::make_unique<cricket::SessionDescription>();
AddAudioSection(subsequent_offer.get(), kMid1Audio, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddAudioSection(subsequent_offer.get(), kMid2Audio, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddVideoSection(subsequent_offer.get(), kMid3Video, kIceUfrag2, kIcePwd2,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddVideoSection(subsequent_offer.get(), kMid4Video, kIceUfrag2, kIcePwd2,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
subsequent_offer->AddGroup(new_bundle_group1);
subsequent_offer->AddGroup(new_bundle_group2);
EXPECT_FALSE(
transport_controller_
->SetLocalDescription(SdpType::kOffer, subsequent_offer.get())
.ok());
}
// Tests that only a subset of all the m= sections are bundled.
TEST_F(JsepTransportControllerTest, BundleSubsetOfMediaSections) {
CreateJsepTransportController(JsepTransportController::Config());
cricket::ContentGroup bundle_group(cricket::GROUP_TYPE_BUNDLE);
bundle_group.AddContentName(kAudioMid1);
bundle_group.AddContentName(kVideoMid1);
auto local_offer = std::make_unique<cricket::SessionDescription>();
AddAudioSection(local_offer.get(), kAudioMid1, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddAudioSection(local_offer.get(), kAudioMid2, kIceUfrag2, kIcePwd2,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddVideoSection(local_offer.get(), kVideoMid1, kIceUfrag3, kIcePwd3,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
auto remote_answer = std::make_unique<cricket::SessionDescription>();
AddAudioSection(remote_answer.get(), kAudioMid1, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_PASSIVE,
nullptr);
AddAudioSection(remote_answer.get(), kAudioMid2, kIceUfrag2, kIcePwd2,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_PASSIVE,
nullptr);
AddVideoSection(remote_answer.get(), kVideoMid1, kIceUfrag3, kIcePwd3,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_PASSIVE,
nullptr);
local_offer->AddGroup(bundle_group);
remote_answer->AddGroup(bundle_group);
EXPECT_TRUE(transport_controller_
->SetLocalDescription(SdpType::kOffer, local_offer.get())
.ok());
EXPECT_TRUE(transport_controller_
->SetRemoteDescription(SdpType::kAnswer, remote_answer.get())
.ok());
// Verifiy that only `kAudio1` and `kVideo1` are bundled.
auto transport1 = transport_controller_->GetRtpTransport(kAudioMid1);
auto transport2 = transport_controller_->GetRtpTransport(kAudioMid2);
auto transport3 = transport_controller_->GetRtpTransport(kVideoMid1);
EXPECT_NE(transport1, transport2);
EXPECT_EQ(transport1, transport3);
auto it = changed_rtp_transport_by_mid_.find(kVideoMid1);
ASSERT_TRUE(it != changed_rtp_transport_by_mid_.end());
EXPECT_EQ(transport1, it->second);
it = changed_rtp_transport_by_mid_.find(kAudioMid2);
EXPECT_TRUE(transport2 == it->second);
}
// Tests that the initial offer/answer only have data section and audio/video
// sections are added in the subsequent offer.
TEST_F(JsepTransportControllerTest, BundleOnDataSectionInSubsequentOffer) {
CreateJsepTransportController(JsepTransportController::Config());
cricket::ContentGroup bundle_group(cricket::GROUP_TYPE_BUNDLE);
bundle_group.AddContentName(kDataMid1);
auto local_offer = std::make_unique<cricket::SessionDescription>();
AddDataSection(local_offer.get(), kDataMid1,
cricket::MediaProtocolType::kSctp, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
auto remote_answer = std::make_unique<cricket::SessionDescription>();
AddDataSection(remote_answer.get(), kDataMid1,
cricket::MediaProtocolType::kSctp, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_PASSIVE,
nullptr);
local_offer->AddGroup(bundle_group);
remote_answer->AddGroup(bundle_group);
EXPECT_TRUE(transport_controller_
->SetLocalDescription(SdpType::kOffer, local_offer.get())
.ok());
EXPECT_TRUE(transport_controller_
->SetRemoteDescription(SdpType::kAnswer, remote_answer.get())
.ok());
auto data_transport = transport_controller_->GetRtpTransport(kDataMid1);
// Add audio/video sections in subsequent offer.
AddAudioSection(local_offer.get(), kAudioMid1, kIceUfrag2, kIcePwd2,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddVideoSection(local_offer.get(), kVideoMid1, kIceUfrag3, kIcePwd3,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddAudioSection(remote_answer.get(), kAudioMid1, kIceUfrag2, kIcePwd2,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_PASSIVE,
nullptr);
AddVideoSection(remote_answer.get(), kVideoMid1, kIceUfrag3, kIcePwd3,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_PASSIVE,
nullptr);
// Reset the bundle group and do another offer/answer exchange.
bundle_group.AddContentName(kAudioMid1);
bundle_group.AddContentName(kVideoMid1);
local_offer->RemoveGroupByName(cricket::GROUP_TYPE_BUNDLE);
remote_answer->RemoveGroupByName(cricket::GROUP_TYPE_BUNDLE);
local_offer->AddGroup(bundle_group);
remote_answer->AddGroup(bundle_group);
EXPECT_TRUE(transport_controller_
->SetLocalDescription(SdpType::kOffer, local_offer.get())
.ok());
EXPECT_TRUE(transport_controller_
->SetRemoteDescription(SdpType::kAnswer, remote_answer.get())
.ok());
auto audio_transport = transport_controller_->GetRtpTransport(kAudioMid1);
auto video_transport = transport_controller_->GetRtpTransport(kVideoMid1);
EXPECT_EQ(data_transport, audio_transport);
EXPECT_EQ(data_transport, video_transport);
}
TEST_F(JsepTransportControllerTest, VideoDataRejectedInAnswer) {
CreateJsepTransportController(JsepTransportController::Config());
cricket::ContentGroup bundle_group(cricket::GROUP_TYPE_BUNDLE);
bundle_group.AddContentName(kAudioMid1);
bundle_group.AddContentName(kVideoMid1);
bundle_group.AddContentName(kDataMid1);
auto local_offer = std::make_unique<cricket::SessionDescription>();
AddAudioSection(local_offer.get(), kAudioMid1, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddVideoSection(local_offer.get(), kVideoMid1, kIceUfrag2, kIcePwd2,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddDataSection(local_offer.get(), kDataMid1,
cricket::MediaProtocolType::kSctp, kIceUfrag3, kIcePwd3,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
auto remote_answer = std::make_unique<cricket::SessionDescription>();
AddAudioSection(remote_answer.get(), kAudioMid1, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_PASSIVE,
nullptr);
AddVideoSection(remote_answer.get(), kVideoMid1, kIceUfrag2, kIcePwd2,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_PASSIVE,
nullptr);
AddDataSection(remote_answer.get(), kDataMid1,
cricket::MediaProtocolType::kSctp, kIceUfrag3, kIcePwd3,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_PASSIVE,
nullptr);
// Reject video and data section.
remote_answer->contents()[1].rejected = true;
remote_answer->contents()[2].rejected = true;
local_offer->AddGroup(bundle_group);
remote_answer->AddGroup(bundle_group);
EXPECT_TRUE(transport_controller_
->SetLocalDescription(SdpType::kOffer, local_offer.get())
.ok());
EXPECT_TRUE(transport_controller_
->SetRemoteDescription(SdpType::kAnswer, remote_answer.get())
.ok());
// Verify the RtpTransport/DtlsTransport is destroyed correctly.
EXPECT_EQ(nullptr, transport_controller_->GetRtpTransport(kVideoMid1));
EXPECT_EQ(nullptr, transport_controller_->GetDtlsTransport(kDataMid1));
// Verify the signals are fired correctly.
auto it = changed_rtp_transport_by_mid_.find(kVideoMid1);
ASSERT_TRUE(it != changed_rtp_transport_by_mid_.end());
EXPECT_EQ(nullptr, it->second);
auto it2 = changed_dtls_transport_by_mid_.find(kDataMid1);
ASSERT_TRUE(it2 != changed_dtls_transport_by_mid_.end());
EXPECT_EQ(nullptr, it2->second);
}
// Tests that changing the bundled MID in subsequent offer/answer exchange is
// not supported.
// TODO(bugs.webrtc.org/6704): Change this test to expect success once issue is
// fixed
TEST_F(JsepTransportControllerTest, ChangeBundledMidNotSupported) {
CreateJsepTransportController(JsepTransportController::Config());
cricket::ContentGroup bundle_group(cricket::GROUP_TYPE_BUNDLE);
bundle_group.AddContentName(kAudioMid1);
bundle_group.AddContentName(kVideoMid1);
auto local_offer = std::make_unique<cricket::SessionDescription>();
AddAudioSection(local_offer.get(), kAudioMid1, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddVideoSection(local_offer.get(), kVideoMid1, kIceUfrag2, kIcePwd2,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
auto remote_answer = std::make_unique<cricket::SessionDescription>();
AddAudioSection(remote_answer.get(), kAudioMid1, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_PASSIVE,
nullptr);
AddVideoSection(remote_answer.get(), kVideoMid1, kIceUfrag2, kIcePwd2,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_PASSIVE,
nullptr);
local_offer->AddGroup(bundle_group);
remote_answer->AddGroup(bundle_group);
EXPECT_TRUE(transport_controller_
->SetLocalDescription(SdpType::kOffer, local_offer.get())
.ok());
EXPECT_TRUE(transport_controller_
->SetRemoteDescription(SdpType::kAnswer, remote_answer.get())
.ok());
EXPECT_EQ(transport_controller_->GetRtpTransport(kAudioMid1),
transport_controller_->GetRtpTransport(kVideoMid1));
// Reorder the bundle group.
EXPECT_TRUE(bundle_group.RemoveContentName(kAudioMid1));
bundle_group.AddContentName(kAudioMid1);
// The answerer uses the new bundle group and now the bundle mid is changed to
// `kVideo1`.
remote_answer->RemoveGroupByName(cricket::GROUP_TYPE_BUNDLE);
remote_answer->AddGroup(bundle_group);
EXPECT_TRUE(transport_controller_
->SetLocalDescription(SdpType::kOffer, local_offer.get())
.ok());
EXPECT_FALSE(transport_controller_
->SetRemoteDescription(SdpType::kAnswer, remote_answer.get())
.ok());
}
// Test that rejecting only the first m= section of a BUNDLE group is treated as
// an error, but rejecting all of them works as expected.
TEST_F(JsepTransportControllerTest, RejectFirstContentInBundleGroup) {
CreateJsepTransportController(JsepTransportController::Config());
cricket::ContentGroup bundle_group(cricket::GROUP_TYPE_BUNDLE);
bundle_group.AddContentName(kAudioMid1);
bundle_group.AddContentName(kVideoMid1);
bundle_group.AddContentName(kDataMid1);
auto local_offer = std::make_unique<cricket::SessionDescription>();
AddAudioSection(local_offer.get(), kAudioMid1, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddVideoSection(local_offer.get(), kVideoMid1, kIceUfrag2, kIcePwd2,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddDataSection(local_offer.get(), kDataMid1,
cricket::MediaProtocolType::kSctp, kIceUfrag3, kIcePwd3,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
auto remote_answer = std::make_unique<cricket::SessionDescription>();
AddAudioSection(remote_answer.get(), kAudioMid1, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_PASSIVE,
nullptr);
AddVideoSection(remote_answer.get(), kVideoMid1, kIceUfrag2, kIcePwd2,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_PASSIVE,
nullptr);
AddDataSection(remote_answer.get(), kDataMid1,
cricket::MediaProtocolType::kSctp, kIceUfrag3, kIcePwd3,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_PASSIVE,
nullptr);
// Reject audio content in answer.
remote_answer->contents()[0].rejected = true;
local_offer->AddGroup(bundle_group);
remote_answer->AddGroup(bundle_group);
EXPECT_TRUE(transport_controller_
->SetLocalDescription(SdpType::kOffer, local_offer.get())
.ok());
EXPECT_FALSE(transport_controller_
->SetRemoteDescription(SdpType::kAnswer, remote_answer.get())
.ok());
// Reject all the contents.
remote_answer->contents()[1].rejected = true;
remote_answer->contents()[2].rejected = true;
EXPECT_TRUE(transport_controller_
->SetRemoteDescription(SdpType::kAnswer, remote_answer.get())
.ok());
EXPECT_EQ(nullptr, transport_controller_->GetRtpTransport(kAudioMid1));
EXPECT_EQ(nullptr, transport_controller_->GetRtpTransport(kVideoMid1));
EXPECT_EQ(nullptr, transport_controller_->GetDtlsTransport(kDataMid1));
}
// Tests that applying non-RTCP-mux offer would fail when kRtcpMuxPolicyRequire
// is used.
TEST_F(JsepTransportControllerTest, ApplyNonRtcpMuxOfferWhenMuxingRequired) {
JsepTransportController::Config config;
config.rtcp_mux_policy = PeerConnectionInterface::kRtcpMuxPolicyRequire;
CreateJsepTransportController(std::move(config));
auto local_offer = std::make_unique<cricket::SessionDescription>();
AddAudioSection(local_offer.get(), kAudioMid1, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
local_offer->contents()[0].media_description()->set_rtcp_mux(false);
// Applying a non-RTCP-mux offer is expected to fail.
EXPECT_FALSE(transport_controller_
->SetLocalDescription(SdpType::kOffer, local_offer.get())
.ok());
}
// Tests that applying non-RTCP-mux answer would fail when kRtcpMuxPolicyRequire
// is used.
TEST_F(JsepTransportControllerTest, ApplyNonRtcpMuxAnswerWhenMuxingRequired) {
JsepTransportController::Config config;
config.rtcp_mux_policy = PeerConnectionInterface::kRtcpMuxPolicyRequire;
CreateJsepTransportController(std::move(config));
auto local_offer = std::make_unique<cricket::SessionDescription>();
AddAudioSection(local_offer.get(), kAudioMid1, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
EXPECT_TRUE(transport_controller_
->SetLocalDescription(SdpType::kOffer, local_offer.get())
.ok());
auto remote_answer = std::make_unique<cricket::SessionDescription>();
AddAudioSection(remote_answer.get(), kAudioMid1, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_PASSIVE,
nullptr);
// Applying a non-RTCP-mux answer is expected to fail.
remote_answer->contents()[0].media_description()->set_rtcp_mux(false);
EXPECT_FALSE(transport_controller_
->SetRemoteDescription(SdpType::kAnswer, remote_answer.get())
.ok());
}
// This tests that the BUNDLE group in answer should be a subset of the offered
// group.
TEST_F(JsepTransportControllerTest,
AddContentToBundleGroupInAnswerNotSupported) {
CreateJsepTransportController(JsepTransportController::Config());
auto local_offer = CreateSessionDescriptionWithoutBundle();
auto remote_answer = CreateSessionDescriptionWithoutBundle();
cricket::ContentGroup offer_bundle_group(cricket::GROUP_TYPE_BUNDLE);
offer_bundle_group.AddContentName(kAudioMid1);
local_offer->AddGroup(offer_bundle_group);
cricket::ContentGroup answer_bundle_group(cricket::GROUP_TYPE_BUNDLE);
answer_bundle_group.AddContentName(kAudioMid1);
answer_bundle_group.AddContentName(kVideoMid1);
remote_answer->AddGroup(answer_bundle_group);
EXPECT_TRUE(transport_controller_
->SetLocalDescription(SdpType::kOffer, local_offer.get())
.ok());
EXPECT_FALSE(transport_controller_
->SetRemoteDescription(SdpType::kAnswer, remote_answer.get())
.ok());
}
// This tests that the BUNDLE group with non-existing MID should be rejectd.
TEST_F(JsepTransportControllerTest, RejectBundleGroupWithNonExistingMid) {
CreateJsepTransportController(JsepTransportController::Config());
auto local_offer = CreateSessionDescriptionWithoutBundle();
auto remote_answer = CreateSessionDescriptionWithoutBundle();
cricket::ContentGroup invalid_bundle_group(cricket::GROUP_TYPE_BUNDLE);
// The BUNDLE group is invalid because there is no data section in the
// description.
invalid_bundle_group.AddContentName(kDataMid1);
local_offer->AddGroup(invalid_bundle_group);
remote_answer->AddGroup(invalid_bundle_group);
EXPECT_FALSE(transport_controller_
->SetLocalDescription(SdpType::kOffer, local_offer.get())
.ok());
EXPECT_FALSE(transport_controller_
->SetRemoteDescription(SdpType::kAnswer, remote_answer.get())
.ok());
}
// This tests that an answer shouldn't be able to remove an m= section from an
// established group without rejecting it.
TEST_F(JsepTransportControllerTest, RemoveContentFromBundleGroup) {
CreateJsepTransportController(JsepTransportController::Config());
auto local_offer = CreateSessionDescriptionWithBundleGroup();
auto remote_answer = CreateSessionDescriptionWithBundleGroup();
EXPECT_TRUE(transport_controller_
->SetLocalDescription(SdpType::kOffer, local_offer.get())
.ok());
EXPECT_TRUE(transport_controller_
->SetRemoteDescription(SdpType::kAnswer, remote_answer.get())
.ok());
// Do an re-offer/answer.
EXPECT_TRUE(transport_controller_
->SetLocalDescription(SdpType::kOffer, local_offer.get())
.ok());
auto new_answer = CreateSessionDescriptionWithoutBundle();
cricket::ContentGroup new_bundle_group(cricket::GROUP_TYPE_BUNDLE);
// The answer removes video from the BUNDLE group without rejecting it is
// invalid.
new_bundle_group.AddContentName(kAudioMid1);
new_answer->AddGroup(new_bundle_group);
// Applying invalid answer is expected to fail.
EXPECT_FALSE(transport_controller_
->SetRemoteDescription(SdpType::kAnswer, new_answer.get())
.ok());
// Rejected the video content.
auto video_content = new_answer->GetContentByName(kVideoMid1);
ASSERT_TRUE(video_content);
video_content->rejected = true;
EXPECT_TRUE(transport_controller_
->SetRemoteDescription(SdpType::kAnswer, new_answer.get())
.ok());
}
// Test that the JsepTransportController can process a new local and remote
// description that changes the tagged BUNDLE group with the max-bundle policy
// specified.
// This is a regression test for bugs.webrtc.org/9954
TEST_F(JsepTransportControllerTest, ChangeTaggedMediaSectionMaxBundle) {
CreateJsepTransportController(JsepTransportController::Config());
auto local_offer = std::make_unique<cricket::SessionDescription>();
AddAudioSection(local_offer.get(), kAudioMid1, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
cricket::ContentGroup bundle_group(cricket::GROUP_TYPE_BUNDLE);
bundle_group.AddContentName(kAudioMid1);
local_offer->AddGroup(bundle_group);
EXPECT_TRUE(transport_controller_
->SetLocalDescription(SdpType::kOffer, local_offer.get())
.ok());
std::unique_ptr<cricket::SessionDescription> remote_answer(
local_offer->Clone());
EXPECT_TRUE(transport_controller_
->SetRemoteDescription(SdpType::kAnswer, remote_answer.get())
.ok());
std::unique_ptr<cricket::SessionDescription> local_reoffer(
local_offer->Clone());
local_reoffer->contents()[0].rejected = true;
AddVideoSection(local_reoffer.get(), kVideoMid1, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
local_reoffer->RemoveGroupByName(cricket::GROUP_TYPE_BUNDLE);
cricket::ContentGroup new_bundle_group(cricket::GROUP_TYPE_BUNDLE);
new_bundle_group.AddContentName(kVideoMid1);
local_reoffer->AddGroup(new_bundle_group);
EXPECT_TRUE(transport_controller_
->SetLocalDescription(SdpType::kOffer, local_reoffer.get())
.ok());
std::unique_ptr<cricket::SessionDescription> remote_reanswer(
local_reoffer->Clone());
EXPECT_TRUE(
transport_controller_
->SetRemoteDescription(SdpType::kAnswer, remote_reanswer.get())
.ok());
}
TEST_F(JsepTransportControllerTest, RollbackRestoresRejectedTransport) {
static const char kMid1Audio[] = "1_audio";
// Perform initial offer/answer.
CreateJsepTransportController(JsepTransportController::Config());
auto local_offer = std::make_unique<cricket::SessionDescription>();
AddAudioSection(local_offer.get(), kMid1Audio, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
std::unique_ptr<cricket::SessionDescription> remote_answer(
local_offer->Clone());
EXPECT_TRUE(transport_controller_
->SetLocalDescription(SdpType::kOffer, local_offer.get())
.ok());
EXPECT_TRUE(transport_controller_
->SetRemoteDescription(SdpType::kAnswer, remote_answer.get())
.ok());
auto mid1_transport = transport_controller_->GetRtpTransport(kMid1Audio);
// Apply a reoffer which rejects the m= section, causing the transport to be
// set to null.
auto local_reoffer = std::make_unique<cricket::SessionDescription>();
AddAudioSection(local_reoffer.get(), kMid1Audio, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
local_reoffer->contents()[0].rejected = true;
EXPECT_TRUE(transport_controller_
->SetLocalDescription(SdpType::kOffer, local_reoffer.get())
.ok());
auto old_mid1_transport = mid1_transport;
mid1_transport = transport_controller_->GetRtpTransport(kMid1Audio);
EXPECT_EQ(nullptr, mid1_transport);
// Rolling back shouldn't just create a new transport for MID 1, it should
// restore the old transport.
EXPECT_TRUE(transport_controller_->RollbackTransports().ok());
mid1_transport = transport_controller_->GetRtpTransport(kMid1Audio);
EXPECT_EQ(old_mid1_transport, mid1_transport);
}
// If an offer with a modified BUNDLE group causes a MID->transport mapping to
// change, rollback should restore the previous mapping.
TEST_F(JsepTransportControllerTest, RollbackRestoresPreviousTransportMapping) {
static const char kMid1Audio[] = "1_audio";
static const char kMid2Audio[] = "2_audio";
static const char kMid3Audio[] = "3_audio";
// Perform an initial offer/answer to establish a (kMid1Audio,kMid2Audio)
// group.
CreateJsepTransportController(JsepTransportController::Config());
cricket::ContentGroup bundle_group(cricket::GROUP_TYPE_BUNDLE);
bundle_group.AddContentName(kMid1Audio);
bundle_group.AddContentName(kMid2Audio);
auto local_offer = std::make_unique<cricket::SessionDescription>();
AddAudioSection(local_offer.get(), kMid1Audio, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddVideoSection(local_offer.get(), kMid2Audio, kIceUfrag2, kIcePwd2,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddAudioSection(local_offer.get(), kMid3Audio, kIceUfrag3, kIcePwd3,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
local_offer->AddGroup(bundle_group);
std::unique_ptr<cricket::SessionDescription> remote_answer(
local_offer->Clone());
EXPECT_TRUE(transport_controller_
->SetLocalDescription(SdpType::kOffer, local_offer.get())
.ok());
EXPECT_TRUE(transport_controller_
->SetRemoteDescription(SdpType::kAnswer, remote_answer.get())
.ok());
auto mid1_transport = transport_controller_->GetRtpTransport(kMid1Audio);
auto mid2_transport = transport_controller_->GetRtpTransport(kMid2Audio);
auto mid3_transport = transport_controller_->GetRtpTransport(kMid3Audio);
EXPECT_EQ(mid1_transport, mid2_transport);
EXPECT_NE(mid1_transport, mid3_transport);
// Apply a reoffer adding kMid3Audio to the group; transport mapping should
// change, even without an answer, since this is an existing group.
bundle_group.AddContentName(kMid3Audio);
auto local_reoffer = std::make_unique<cricket::SessionDescription>();
AddAudioSection(local_reoffer.get(), kMid1Audio, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddVideoSection(local_reoffer.get(), kMid2Audio, kIceUfrag2, kIcePwd2,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddAudioSection(local_reoffer.get(), kMid3Audio, kIceUfrag3, kIcePwd3,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
local_reoffer->AddGroup(bundle_group);
EXPECT_TRUE(transport_controller_
->SetLocalDescription(SdpType::kOffer, local_reoffer.get())
.ok());
// Store the old transport pointer and verify that the offer actually changed
// transports.
auto old_mid3_transport = mid3_transport;
mid1_transport = transport_controller_->GetRtpTransport(kMid1Audio);
mid2_transport = transport_controller_->GetRtpTransport(kMid2Audio);
mid3_transport = transport_controller_->GetRtpTransport(kMid3Audio);
EXPECT_EQ(mid1_transport, mid2_transport);
EXPECT_EQ(mid1_transport, mid3_transport);
// Rolling back shouldn't just create a new transport for MID 3, it should
// restore the old transport.
EXPECT_TRUE(transport_controller_->RollbackTransports().ok());
mid3_transport = transport_controller_->GetRtpTransport(kMid3Audio);
EXPECT_EQ(old_mid3_transport, mid3_transport);
}
// Test that if an offer adds a MID to a specific BUNDLE group and is then
// rolled back, it can be added to a different BUNDLE group in a new offer.
// This is effectively testing that rollback resets the BundleManager state.
TEST_F(JsepTransportControllerTest, RollbackAndAddToDifferentBundleGroup) {
static const char kMid1Audio[] = "1_audio";
static const char kMid2Audio[] = "2_audio";
static const char kMid3Audio[] = "3_audio";
// Perform an initial offer/answer to establish two bundle groups, each with
// one MID.
CreateJsepTransportController(JsepTransportController::Config());
cricket::ContentGroup bundle_group1(cricket::GROUP_TYPE_BUNDLE);
bundle_group1.AddContentName(kMid1Audio);
cricket::ContentGroup bundle_group2(cricket::GROUP_TYPE_BUNDLE);
bundle_group2.AddContentName(kMid2Audio);
auto local_offer = std::make_unique<cricket::SessionDescription>();
AddAudioSection(local_offer.get(), kMid1Audio, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddVideoSection(local_offer.get(), kMid2Audio, kIceUfrag2, kIcePwd2,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
local_offer->AddGroup(bundle_group1);
local_offer->AddGroup(bundle_group2);
std::unique_ptr<cricket::SessionDescription> remote_answer(
local_offer->Clone());
EXPECT_TRUE(transport_controller_
->SetLocalDescription(SdpType::kOffer, local_offer.get())
.ok());
EXPECT_TRUE(transport_controller_
->SetRemoteDescription(SdpType::kAnswer, remote_answer.get())
.ok());
// Apply an offer that adds kMid3Audio to the first BUNDLE group.,
cricket::ContentGroup modified_bundle_group1(cricket::GROUP_TYPE_BUNDLE);
modified_bundle_group1.AddContentName(kMid1Audio);
modified_bundle_group1.AddContentName(kMid3Audio);
auto subsequent_offer_1 = std::make_unique<cricket::SessionDescription>();
AddAudioSection(subsequent_offer_1.get(), kMid1Audio, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddVideoSection(subsequent_offer_1.get(), kMid2Audio, kIceUfrag2, kIcePwd2,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddVideoSection(subsequent_offer_1.get(), kMid3Audio, kIceUfrag3, kIcePwd3,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
subsequent_offer_1->AddGroup(modified_bundle_group1);
subsequent_offer_1->AddGroup(bundle_group2);
EXPECT_TRUE(
transport_controller_
->SetLocalDescription(SdpType::kOffer, subsequent_offer_1.get())
.ok());
auto mid1_transport = transport_controller_->GetRtpTransport(kMid1Audio);
auto mid2_transport = transport_controller_->GetRtpTransport(kMid2Audio);
auto mid3_transport = transport_controller_->GetRtpTransport(kMid3Audio);
EXPECT_NE(mid1_transport, mid2_transport);
EXPECT_EQ(mid1_transport, mid3_transport);
// Rollback and expect the transport to be reset.
EXPECT_TRUE(transport_controller_->RollbackTransports().ok());
EXPECT_EQ(nullptr, transport_controller_->GetRtpTransport(kMid3Audio));
// Apply an offer that adds kMid3Audio to the second BUNDLE group.,
cricket::ContentGroup modified_bundle_group2(cricket::GROUP_TYPE_BUNDLE);
modified_bundle_group2.AddContentName(kMid2Audio);
modified_bundle_group2.AddContentName(kMid3Audio);
auto subsequent_offer_2 = std::make_unique<cricket::SessionDescription>();
AddAudioSection(subsequent_offer_2.get(), kMid1Audio, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddVideoSection(subsequent_offer_2.get(), kMid2Audio, kIceUfrag2, kIcePwd2,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddVideoSection(subsequent_offer_2.get(), kMid3Audio, kIceUfrag3, kIcePwd3,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
subsequent_offer_2->AddGroup(bundle_group1);
subsequent_offer_2->AddGroup(modified_bundle_group2);
EXPECT_TRUE(
transport_controller_
->SetLocalDescription(SdpType::kOffer, subsequent_offer_2.get())
.ok());
mid1_transport = transport_controller_->GetRtpTransport(kMid1Audio);
mid2_transport = transport_controller_->GetRtpTransport(kMid2Audio);
mid3_transport = transport_controller_->GetRtpTransport(kMid3Audio);
EXPECT_NE(mid1_transport, mid2_transport);
EXPECT_EQ(mid2_transport, mid3_transport);
}
} // namespace webrtc