Handle the corner cases for BUNDLE.

Reject the local/remote description trying to change the pre-negotiated
BUNDLE tag.

Reject an answer containing a BUNDLE group that's not a subset of the offered group.

Reject an offer/answer with a BUNDLE group containing a MID that no m= section has.

Reject an answer removes an m= section from an established BUNDLE group without
rejecting it.

Bug: chromium:827917
Change-Id: If334eefb00b1c1c1e24f9afba0cb00b5867f5590
Reviewed-on: https://webrtc-review.googlesource.com/67190
Commit-Queue: Zhi Huang <zhihuang@webrtc.org>
Reviewed-by: Taylor Brandstetter <deadbeef@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#22813}
This commit is contained in:
Zhi Huang 2018-04-10 14:41:03 -07:00 committed by Commit Bot
parent 3ef3bfc2aa
commit d2248f82d3
6 changed files with 388 additions and 63 deletions

View File

@ -140,7 +140,7 @@ class SctpTransport : public SctpTransportInternal,
// Helps pass inbound/outbound packets asynchronously to the network thread.
rtc::AsyncInvoker invoker_;
// Underlying DTLS channel.
rtc::PacketTransportInternal* transport_;
rtc::PacketTransportInternal* transport_ = nullptr;
bool was_ever_writable_ = false;
int local_port_ = kSctpDefaultPort;
int remote_port_ = kSctpDefaultPort;

View File

@ -543,17 +543,8 @@ RTCError JsepTransportController::ApplyDescription_n(
remote_desc_ = description;
}
if (ShouldUpdateBundleGroup(type, description)) {
if (!description->HasGroup(cricket::GROUP_TYPE_BUNDLE)) {
return RTCError(RTCErrorType::INVALID_PARAMETER,
"max-bundle is used but no bundle group found.");
} else {
bundle_group_ = *description->GetGroupByName(cricket::GROUP_TYPE_BUNDLE);
}
}
RTCError error;
error = ValidateBundleGroup(description);
error = ValidateAndMaybeUpdateBundleGroup(local, type, description);
if (!error.ok()) {
return error;
}
@ -570,7 +561,7 @@ RTCError JsepTransportController::ApplyDescription_n(
(IsBundled(content_info.name) && content_info.name != *bundled_mid())) {
continue;
}
error = MaybeCreateJsepTransport(content_info.name, content_info);
error = MaybeCreateJsepTransport(content_info);
if (!error.ok()) {
return error;
}
@ -583,7 +574,7 @@ RTCError JsepTransportController::ApplyDescription_n(
const cricket::TransportInfo& transport_info =
description->transport_infos()[i];
if (content_info.rejected) {
HandleRejectedContent(content_info);
HandleRejectedContent(content_info, description);
continue;
}
@ -633,9 +624,75 @@ RTCError JsepTransportController::ApplyDescription_n(
return RTCError::OK();
}
RTCError JsepTransportController::ValidateBundleGroup(
RTCError JsepTransportController::ValidateAndMaybeUpdateBundleGroup(
bool local,
SdpType type,
const cricket::SessionDescription* description) {
RTC_DCHECK(description);
const cricket::ContentGroup* new_bundle_group =
description->GetGroupByName(cricket::GROUP_TYPE_BUNDLE);
// The BUNDLE group containing a MID that no m= section has is invalid.
if (new_bundle_group) {
for (auto content_name : new_bundle_group->content_names()) {
if (!description->GetContentByName(content_name)) {
return RTCError(RTCErrorType::INVALID_PARAMETER,
"The BUNDLE group contains MID:" + content_name +
" matching no m= section.");
}
}
}
if (type == SdpType::kAnswer) {
const cricket::ContentGroup* offered_bundle_group =
local ? remote_desc_->GetGroupByName(cricket::GROUP_TYPE_BUNDLE)
: local_desc_->GetGroupByName(cricket::GROUP_TYPE_BUNDLE);
if (new_bundle_group) {
// The BUNDLE group in answer should be a subset of offered group.
for (auto content_name : new_bundle_group->content_names()) {
if (!offered_bundle_group ||
!offered_bundle_group->HasContentName(content_name)) {
return RTCError(RTCErrorType::INVALID_PARAMETER,
"The BUNDLE group in answer contains a MID that was "
"not in the offered group.");
}
}
}
if (bundle_group_) {
for (auto content_name : bundle_group_->content_names()) {
// An answer that removes m= sections from pre-negotiated BUNDLE group
// without rejecting it, is invalid.
if (!new_bundle_group ||
!new_bundle_group->HasContentName(content_name)) {
auto* content_info = description->GetContentByName(content_name);
if (!content_info || !content_info->rejected) {
return RTCError(RTCErrorType::INVALID_PARAMETER,
"Answer cannot remove m= section " + content_name +
" from already-established BUNDLE group.");
}
}
}
}
}
if (config_.bundle_policy ==
PeerConnectionInterface::kBundlePolicyMaxBundle &&
!description->HasGroup(cricket::GROUP_TYPE_BUNDLE)) {
return RTCError(RTCErrorType::INVALID_PARAMETER,
"max-bundle is used but no bundle group found.");
}
if (ShouldUpdateBundleGroup(type, description)) {
std::string new_bundled_mid = *(new_bundle_group->FirstContentName());
if (bundled_mid() && *bundled_mid() != new_bundled_mid) {
return RTCError(RTCErrorType::UNSUPPORTED_OPERATION,
"Changing the negotiated BUNDLE-tag is not supported.");
}
bundle_group_ = *new_bundle_group;
}
if (!bundled_mid()) {
return RTCError::OK();
@ -679,24 +736,20 @@ RTCError JsepTransportController::ValidateContent(
}
void JsepTransportController::HandleRejectedContent(
const cricket::ContentInfo& content_info) {
const cricket::ContentInfo& content_info,
const cricket::SessionDescription* description) {
// If the content is rejected, let the
// BaseChannel/SctpTransport change the RtpTransport/DtlsTransport first,
// then destroy the cricket::JsepTransport2.
if (content_info.type == cricket::MediaProtocolType::kRtp) {
SignalRtpTransportChanged(content_info.name, nullptr);
} else {
SignalDtlsTransportChanged(content_info.name, nullptr);
}
RemoveTransportForMid(content_info.name, content_info.type);
// If the answerer rejects the first content, which other contents are bundled
// on, all the other contents in the bundle group will be rejected.
if (content_info.name == bundled_mid()) {
for (auto content_name : bundle_group_->content_names()) {
if (content_info.type == cricket::MediaProtocolType::kRtp) {
SignalRtpTransportChanged(content_name, nullptr);
} else {
SignalDtlsTransportChanged(content_name, nullptr);
}
const cricket::ContentInfo* content_in_group =
description->GetContentByName(content_name);
RTC_DCHECK(content_in_group);
RemoveTransportForMid(content_name, content_in_group->type);
}
bundle_group_.reset();
} else if (IsBundled(content_info.name)) {
@ -712,21 +765,42 @@ void JsepTransportController::HandleRejectedContent(
void JsepTransportController::HandleBundledContent(
const cricket::ContentInfo& content_info) {
auto jsep_transport = GetJsepTransportByName(*bundled_mid());
RTC_DCHECK(jsep_transport);
// If the content is bundled, let the
// BaseChannel/SctpTransport change the RtpTransport/DtlsTransport first,
// then destroy the cricket::JsepTransport2.
if (content_info.type == cricket::MediaProtocolType::kRtp) {
auto rtp_transport =
jsep_transports_by_name_[*bundled_mid()]->rtp_transport();
SignalRtpTransportChanged(content_info.name, rtp_transport);
} else {
auto dtls_transport =
jsep_transports_by_name_[*bundled_mid()]->rtp_dtls_transport();
SignalDtlsTransportChanged(content_info.name, dtls_transport);
}
SetTransportForMid(content_info.name, jsep_transport, content_info.type);
MaybeDestroyJsepTransport(content_info.name);
}
void JsepTransportController::SetTransportForMid(
const std::string& mid,
cricket::JsepTransport2* jsep_transport,
cricket::MediaProtocolType protocol_type) {
if (mid_to_transport_[mid] == jsep_transport) {
return;
}
mid_to_transport_[mid] = jsep_transport;
if (protocol_type == cricket::MediaProtocolType::kRtp) {
SignalRtpTransportChanged(mid, jsep_transport->rtp_transport());
} else {
SignalDtlsTransportChanged(mid, jsep_transport->rtp_dtls_transport());
}
}
void JsepTransportController::RemoveTransportForMid(
const std::string& mid,
cricket::MediaProtocolType protocol_type) {
if (protocol_type == cricket::MediaProtocolType::kRtp) {
SignalRtpTransportChanged(mid, nullptr);
} else {
SignalDtlsTransportChanged(mid, nullptr);
}
mid_to_transport_.erase(mid);
}
cricket::JsepTransportDescription
JsepTransportController::CreateJsepTransportDescription(
cricket::ContentInfo content_info,
@ -832,22 +906,14 @@ int JsepTransportController::GetRtpAbsSendTimeHeaderExtensionId(
const cricket::JsepTransport2* JsepTransportController::GetJsepTransportForMid(
const std::string& mid) const {
auto target_mid = mid;
if (IsBundled(mid)) {
target_mid = *bundled_mid();
}
auto it = jsep_transports_by_name_.find(target_mid);
return (it == jsep_transports_by_name_.end()) ? nullptr : it->second.get();
auto it = mid_to_transport_.find(mid);
return it == mid_to_transport_.end() ? nullptr : it->second;
}
cricket::JsepTransport2* JsepTransportController::GetJsepTransportForMid(
const std::string& mid) {
auto target_mid = mid;
if (IsBundled(mid)) {
target_mid = *bundled_mid();
}
auto it = jsep_transports_by_name_.find(target_mid);
return (it == jsep_transports_by_name_.end()) ? nullptr : it->second.get();
auto it = mid_to_transport_.find(mid);
return it == mid_to_transport_.end() ? nullptr : it->second;
}
const cricket::JsepTransport2* JsepTransportController::GetJsepTransportByName(
@ -863,10 +929,10 @@ cricket::JsepTransport2* JsepTransportController::GetJsepTransportByName(
}
RTCError JsepTransportController::MaybeCreateJsepTransport(
const std::string& mid,
const cricket::ContentInfo& content_info) {
RTC_DCHECK(network_thread_->IsCurrent());
cricket::JsepTransport2* transport = GetJsepTransportForMid(mid);
cricket::JsepTransport2* transport =
GetJsepTransportByName(content_info.name);
if (transport) {
return RTCError::OK();
}
@ -880,12 +946,13 @@ RTCError JsepTransportController::MaybeCreateJsepTransport(
}
std::unique_ptr<cricket::DtlsTransportInternal> rtp_dtls_transport =
CreateDtlsTransport(mid, /*rtcp =*/false);
CreateDtlsTransport(content_info.name, /*rtcp =*/false);
std::unique_ptr<cricket::DtlsTransportInternal> rtcp_dtls_transport;
if (config_.rtcp_mux_policy !=
PeerConnectionInterface::kRtcpMuxPolicyRequire &&
content_info.type == cricket::MediaProtocolType::kRtp) {
rtcp_dtls_transport = CreateDtlsTransport(mid, /*rtcp =*/true);
rtcp_dtls_transport =
CreateDtlsTransport(content_info.name, /*rtcp =*/true);
}
std::unique_ptr<RtpTransport> unencrypted_rtp_transport;
@ -893,30 +960,44 @@ RTCError JsepTransportController::MaybeCreateJsepTransport(
std::unique_ptr<DtlsSrtpTransport> dtls_srtp_transport;
if (config_.disable_encryption) {
unencrypted_rtp_transport = CreateUnencryptedRtpTransport(
mid, rtp_dtls_transport.get(), rtcp_dtls_transport.get());
content_info.name, rtp_dtls_transport.get(), rtcp_dtls_transport.get());
} else if (!content_desc->cryptos().empty()) {
sdes_transport = CreateSdesTransport(mid, rtp_dtls_transport.get(),
rtcp_dtls_transport.get());
sdes_transport = CreateSdesTransport(
content_info.name, rtp_dtls_transport.get(), rtcp_dtls_transport.get());
} else {
dtls_srtp_transport = CreateDtlsSrtpTransport(mid, rtp_dtls_transport.get(),
rtcp_dtls_transport.get());
dtls_srtp_transport = CreateDtlsSrtpTransport(
content_info.name, rtp_dtls_transport.get(), rtcp_dtls_transport.get());
}
std::unique_ptr<cricket::JsepTransport2> jsep_transport =
rtc::MakeUnique<cricket::JsepTransport2>(
mid, certificate_, std::move(unencrypted_rtp_transport),
content_info.name, certificate_, std::move(unencrypted_rtp_transport),
std::move(sdes_transport), std::move(dtls_srtp_transport),
std::move(rtp_dtls_transport), std::move(rtcp_dtls_transport));
jsep_transport->SignalRtcpMuxActive.connect(
this, &JsepTransportController::UpdateAggregateStates_n);
jsep_transports_by_name_[mid] = std::move(jsep_transport);
UpdateAggregateStates_n();
SetTransportForMid(content_info.name, jsep_transport.get(),
content_info.type);
jsep_transports_by_name_[content_info.name] = std::move(jsep_transport);
UpdateAggregateStates_n();
return RTCError::OK();
}
void JsepTransportController::MaybeDestroyJsepTransport(
const std::string& mid) {
auto jsep_transport = GetJsepTransportByName(mid);
if (!jsep_transport) {
return;
}
// Don't destroy the JsepTransport if there are still media sections referring
// to it.
for (const auto& kv : mid_to_transport_) {
if (kv.second == jsep_transport) {
return;
}
}
jsep_transports_by_name_.erase(mid);
UpdateAggregateStates_n();
}

View File

@ -177,12 +177,22 @@ class JsepTransportController : public sigslot::has_slots<>,
RTCError ApplyDescription_n(bool local,
SdpType type,
const cricket::SessionDescription* description);
RTCError ValidateBundleGroup(const cricket::SessionDescription* description);
RTCError ValidateAndMaybeUpdateBundleGroup(
bool local,
SdpType type,
const cricket::SessionDescription* description);
RTCError ValidateContent(const cricket::ContentInfo& content_info);
void HandleRejectedContent(const cricket::ContentInfo& content_info);
void HandleRejectedContent(const cricket::ContentInfo& content_info,
const cricket::SessionDescription* description);
void HandleBundledContent(const cricket::ContentInfo& content_info);
void SetTransportForMid(const std::string& mid,
cricket::JsepTransport2* jsep_transport,
cricket::MediaProtocolType protocol_type);
void RemoveTransportForMid(const std::string& mid,
cricket::MediaProtocolType protocol_type);
cricket::JsepTransportDescription CreateJsepTransportDescription(
cricket::ContentInfo content_info,
cricket::TransportInfo transport_info,
@ -227,8 +237,7 @@ class JsepTransportController : public sigslot::has_slots<>,
cricket::JsepTransport2* GetJsepTransportByName(
const std::string& transport_name);
RTCError MaybeCreateJsepTransport(const std::string& mid,
const cricket::ContentInfo& content_info);
RTCError MaybeCreateJsepTransport(const cricket::ContentInfo& content_info);
void MaybeDestroyJsepTransport(const std::string& mid);
void DestroyAllJsepTransports_n();
@ -284,6 +293,9 @@ class JsepTransportController : public sigslot::has_slots<>,
std::map<std::string, std::unique_ptr<cricket::JsepTransport2>>
jsep_transports_by_name_;
// This keeps track of the mapping between media section
// (BaseChannel/SctpTransport) and the JsepTransport2 underneath.
std::map<std::string, cricket::JsepTransport2*> mid_to_transport_;
// Aggregate state for Transports.
cricket::IceConnectionState ice_connection_state_ =

View File

@ -1053,7 +1053,7 @@ TEST_F(JsepTransportControllerTest, BundleSubsetOfMediaSections) {
ASSERT_TRUE(it != changed_rtp_transport_by_mid_.end());
EXPECT_EQ(transport1, it->second);
it = changed_rtp_transport_by_mid_.find(kAudioMid2);
EXPECT_TRUE(it == changed_rtp_transport_by_mid_.end());
EXPECT_TRUE(transport2 == it->second);
}
// Tests that the initial offer/answer only have data section and audio/video
@ -1325,4 +1325,88 @@ TEST_F(JsepTransportControllerTest, ApplyNonRtcpMuxAnswerWhenMuxingRequired) {
.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());
}
} // namespace webrtc

View File

@ -659,6 +659,146 @@ TEST_P(PeerConnectionBundleTest, BundleOnFirstMidInAnswer) {
EXPECT_EQ(caller->voice_rtp_transport(), caller->video_rtp_transport());
}
// This tests that changing the pre-negotiated BUNDLE tag is not supported.
TEST_P(PeerConnectionBundleTest, RejectDescriptionChangingBundleTag) {
RTCConfiguration config;
config.bundle_policy = BundlePolicy::kBundlePolicyMaxBundle;
auto caller = CreatePeerConnectionWithAudioVideo(config);
auto callee = CreatePeerConnectionWithAudioVideo(config);
RTCOfferAnswerOptions options;
options.use_rtp_mux = true;
auto offer = caller->CreateOfferAndSetAsLocal(options);
// Create a new bundle-group with different bundled_mid.
auto* old_bundle_group =
offer->description()->GetGroupByName(cricket::GROUP_TYPE_BUNDLE);
std::string first_mid = old_bundle_group->content_names()[0];
std::string second_mid = old_bundle_group->content_names()[1];
cricket::ContentGroup new_bundle_group(cricket::GROUP_TYPE_BUNDLE);
new_bundle_group.AddContentName(second_mid);
auto re_offer = CloneSessionDescription(offer.get());
callee->SetRemoteDescription(std::move(offer));
auto answer = callee->CreateAnswer(options);
// Reject the first MID.
answer->description()->contents()[0].rejected = true;
// Remove the first MID from the bundle group.
answer->description()->RemoveGroupByName(cricket::GROUP_TYPE_BUNDLE);
answer->description()->AddGroup(new_bundle_group);
// The answer is expected to be rejected.
EXPECT_FALSE(caller->SetRemoteDescription(std::move(answer)));
// Do the same thing for re-offer.
re_offer->description()->contents()[0].rejected = true;
re_offer->description()->RemoveGroupByName(cricket::GROUP_TYPE_BUNDLE);
re_offer->description()->AddGroup(new_bundle_group);
// The re-offer is expected to be rejected.
EXPECT_FALSE(caller->SetLocalDescription(std::move(re_offer)));
}
// This tests that removing contents from BUNDLE group and reject the whole
// BUNDLE group could work. This is a regression test for
// (https://bugs.chromium.org/p/chromium/issues/detail?id=827917)
TEST_P(PeerConnectionBundleTest, RemovingContentAndRejectBundleGroup) {
RTCConfiguration config;
#ifndef HAVE_SCTP
config.enable_rtp_data_channel = true;
#endif
config.bundle_policy = BundlePolicy::kBundlePolicyMaxBundle;
auto caller = CreatePeerConnectionWithAudioVideo(config);
caller->CreateDataChannel("dc");
auto offer = caller->CreateOfferAndSetAsLocal();
auto re_offer = CloneSessionDescription(offer.get());
// Removing the second MID from the BUNDLE group.
auto* old_bundle_group =
offer->description()->GetGroupByName(cricket::GROUP_TYPE_BUNDLE);
std::string first_mid = old_bundle_group->content_names()[0];
std::string third_mid = old_bundle_group->content_names()[2];
cricket::ContentGroup new_bundle_group(cricket::GROUP_TYPE_BUNDLE);
new_bundle_group.AddContentName(first_mid);
new_bundle_group.AddContentName(third_mid);
// Reject the entire new bundle group.
re_offer->description()->contents()[0].rejected = true;
re_offer->description()->contents()[2].rejected = true;
re_offer->description()->RemoveGroupByName(cricket::GROUP_TYPE_BUNDLE);
re_offer->description()->AddGroup(new_bundle_group);
EXPECT_TRUE(caller->SetLocalDescription(std::move(re_offer)));
}
// This tests that the BUNDLE group in answer should be a subset of the offered
// group.
TEST_P(PeerConnectionBundleTest, AddContentToBundleGroupInAnswerNotSupported) {
auto caller = CreatePeerConnectionWithAudioVideo();
auto callee = CreatePeerConnectionWithAudioVideo();
auto offer = caller->CreateOffer();
std::string first_mid = offer->description()->contents()[0].name;
std::string second_mid = offer->description()->contents()[1].name;
cricket::ContentGroup bundle_group(cricket::GROUP_TYPE_BUNDLE);
bundle_group.AddContentName(first_mid);
offer->description()->RemoveGroupByName(cricket::GROUP_TYPE_BUNDLE);
offer->description()->AddGroup(bundle_group);
EXPECT_TRUE(
caller->SetLocalDescription(CloneSessionDescription(offer.get())));
EXPECT_TRUE(callee->SetRemoteDescription(std::move(offer)));
auto answer = callee->CreateAnswer();
bundle_group.AddContentName(second_mid);
answer->description()->RemoveGroupByName(cricket::GROUP_TYPE_BUNDLE);
answer->description()->AddGroup(bundle_group);
// The answer is expected to be rejected because second mid is not in the
// offered BUNDLE group.
EXPECT_FALSE(callee->SetLocalDescription(std::move(answer)));
}
// This tests that the BUNDLE group with non-existing MID should be rejectd.
TEST_P(PeerConnectionBundleTest, RejectBundleGroupWithNonExistingMid) {
auto caller = CreatePeerConnectionWithAudioVideo();
auto callee = CreatePeerConnectionWithAudioVideo();
auto offer = caller->CreateOffer();
auto invalid_bundle_group =
*offer->description()->GetGroupByName(cricket::GROUP_TYPE_BUNDLE);
invalid_bundle_group.AddContentName("non-existing-MID");
offer->description()->RemoveGroupByName(cricket::GROUP_TYPE_BUNDLE);
offer->description()->AddGroup(invalid_bundle_group);
EXPECT_FALSE(
caller->SetLocalDescription(CloneSessionDescription(offer.get())));
EXPECT_FALSE(callee->SetRemoteDescription(std::move(offer)));
}
// This tests that an answer shouldn't be able to remove an m= section from an
// established group without rejecting it.
TEST_P(PeerConnectionBundleTest, RemoveContentFromBundleGroup) {
auto caller = CreatePeerConnectionWithAudioVideo();
auto callee = CreatePeerConnectionWithAudioVideo();
EXPECT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
EXPECT_TRUE(
caller->SetRemoteDescription(callee->CreateAnswerAndSetAsLocal()));
EXPECT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
auto answer = callee->CreateAnswer();
std::string second_mid = answer->description()->contents()[1].name;
auto invalid_bundle_group =
*answer->description()->GetGroupByName(cricket::GROUP_TYPE_BUNDLE);
invalid_bundle_group.RemoveContentName(second_mid);
answer->description()->RemoveGroupByName(cricket::GROUP_TYPE_BUNDLE);
answer->description()->AddGroup(invalid_bundle_group);
EXPECT_FALSE(
callee->SetLocalDescription(CloneSessionDescription(answer.get())));
}
INSTANTIATE_TEST_CASE_P(PeerConnectionBundleTest,
PeerConnectionBundleTest,
Values(SdpSemantics::kPlanB,

View File

@ -955,6 +955,14 @@ void RenameContent(cricket::SessionDescription* desc,
auto* transport = desc->GetTransportInfoByName(old_name);
RTC_DCHECK(transport);
transport->content_name = new_name;
// Rename the content name in the BUNDLE group.
cricket::ContentGroup new_bundle_group =
*desc->GetGroupByName(cricket::GROUP_TYPE_BUNDLE);
new_bundle_group.RemoveContentName(old_name);
new_bundle_group.AddContentName(new_name);
desc->RemoveGroupByName(cricket::GROUP_TYPE_BUNDLE);
desc->AddGroup(new_bundle_group);
}
// Tests that an answer responds with the same MIDs as the offer.