Report an error when trying to set complex Plan B SDP on Unified Plan

This changes the PeerConnection when in Unified Plan mode to reject
SDP applied with SetLocalDescription or SetRemoteDescription if the
SDP has multiple "Plan B tracks" (a=ssrc lines) in a media section.
The error is to inform developers that the given SDP will not be
interpreted as they might expect.

Bug: None
Change-Id: I7a0e11282fbf63dac06038cd22a66683517a87d0
Reviewed-on: https://webrtc-review.googlesource.com/68764
Commit-Queue: Steve Anton <steveanton@webrtc.org>
Reviewed-by: Taylor Brandstetter <deadbeef@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#22829}
diff --git a/pc/peerconnection.cc b/pc/peerconnection.cc
index 1f63874..cce9021 100644
--- a/pc/peerconnection.cc
+++ b/pc/peerconnection.cc
@@ -2194,6 +2194,11 @@
     return;
   }
 
+  if (desc->GetType() == SdpType::kOffer) {
+    // Report to UMA the format of the received offer.
+    ReportSdpFormatReceived(*desc);
+  }
+
   RTCError error = ValidateSessionDescription(desc.get(), cricket::CS_REMOTE);
   if (!error.ok()) {
     std::string error_message = GetSetDescriptionErrorMessage(
@@ -2225,11 +2230,6 @@
   }
   RTC_DCHECK(remote_description());
 
-  if (type == SdpType::kOffer) {
-    // Report to UMA the format of the received offer.
-    ReportSdpFormatReceived(*remote_description());
-  }
-
   if (type == SdpType::kAnswer) {
     // TODO(deadbeef): We already had to hop to the network thread for
     // MaybeStartGathering...
@@ -5751,6 +5751,25 @@
     }
   }
 
+  if (IsUnifiedPlan()) {
+    // Ensure that each audio and video media section has at most one
+    // "StreamParams". This will return an error if receiving a session
+    // description from a "Plan B" endpoint which adds multiple tracks of the
+    // same type. With Unified Plan, there can only be at most one track per
+    // media section.
+    for (const ContentInfo& content : sdesc->description()->contents()) {
+      const MediaContentDescription& desc = *content.description;
+      if ((desc.type() == cricket::MEDIA_TYPE_AUDIO ||
+           desc.type() == cricket::MEDIA_TYPE_VIDEO) &&
+          desc.streams().size() > 1u) {
+        LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_PARAMETER,
+                             "Media section has more than one track specified "
+                             "with a=ssrc lines which is not supported with "
+                             "Unified Plan.");
+      }
+    }
+  }
+
   return RTCError::OK();
 }
 
diff --git a/pc/peerconnection_integrationtest.cc b/pc/peerconnection_integrationtest.cc
index f7ac830..b9a0227 100644
--- a/pc/peerconnection_integrationtest.cc
+++ b/pc/peerconnection_integrationtest.cc
@@ -4258,10 +4258,22 @@
   ASSERT_TRUE(ExpectNewFrames(media_expectations));
 }
 
-// Test that if one side offers two video tracks then the other side will only
-// see the first one and ignore the second.
-TEST_P(PeerConnectionIntegrationInteropTest, TwoVideoLocalToNoMediaRemote) {
-  ASSERT_TRUE(CreatePeerConnectionWrappersWithSemantics());
+INSTANTIATE_TEST_CASE_P(
+    PeerConnectionIntegrationTest,
+    PeerConnectionIntegrationInteropTest,
+    Values(std::make_tuple(SdpSemantics::kPlanB, SdpSemantics::kUnifiedPlan),
+           std::make_tuple(SdpSemantics::kUnifiedPlan, SdpSemantics::kPlanB)));
+
+// Test that if the Unified Plan side offers two video tracks then the Plan B
+// side will only see the first one and ignore the second.
+TEST_F(PeerConnectionIntegrationTestPlanB, TwoVideoUnifiedPlanToNoMediaPlanB) {
+  RTCConfiguration caller_config;
+  caller_config.sdp_semantics = SdpSemantics::kUnifiedPlan;
+  RTCConfiguration callee_config;
+  callee_config.sdp_semantics = SdpSemantics::kPlanB;
+  ASSERT_TRUE(
+      CreatePeerConnectionWrappersWithConfig(caller_config, callee_config));
+
   ConnectFakeSignaling();
   auto first_sender = caller()->AddVideoTrack();
   caller()->AddVideoTrack();
@@ -4281,66 +4293,6 @@
   ASSERT_TRUE(ExpectNewFrames(media_expectations));
 }
 
-// Test that in the multi-track case each endpoint only looks at the first track
-// and ignores the second one.
-TEST_P(PeerConnectionIntegrationInteropTest, TwoVideoLocalToTwoVideoRemote) {
-  ASSERT_TRUE(CreatePeerConnectionWrappersWithSemantics());
-  ConnectFakeSignaling();
-  caller()->AddVideoTrack();
-  caller()->AddVideoTrack();
-  callee()->AddVideoTrack();
-  callee()->AddVideoTrack();
-
-  caller()->CreateAndSetAndSignalOffer();
-  ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
-
-  PeerConnectionWrapper* plan_b =
-      (caller_semantics_ == SdpSemantics::kPlanB ? caller() : callee());
-  PeerConnectionWrapper* unified_plan =
-      (caller_semantics_ == SdpSemantics::kUnifiedPlan ? caller() : callee());
-
-  // Should have two senders each, one for each track.
-  EXPECT_EQ(2u, plan_b->pc()->GetSenders().size());
-  EXPECT_EQ(2u, unified_plan->pc()->GetSenders().size());
-
-  // Plan B will have one receiver since it only looks at the first video
-  // section. The receiver should have the same track ID as the sender's first
-  // track.
-  ASSERT_EQ(1u, plan_b->pc()->GetReceivers().size());
-  EXPECT_EQ(unified_plan->pc()->GetSenders()[0]->track()->id(),
-            plan_b->pc()->GetReceivers()[0]->track()->id());
-
-  // Unified Plan will have two receivers since they were created with the
-  // transceivers when the tracks were added.
-  ASSERT_EQ(2u, unified_plan->pc()->GetReceivers().size());
-
-  if (unified_plan == caller()) {
-    // If the Unified Plan endpoint was the caller, then the Plan B endpoint
-    // would have rejected the second video media section so we would expect the
-    // transceiver to be stopped.
-    EXPECT_FALSE(unified_plan->pc()->GetTransceivers()[0]->stopped());
-    EXPECT_TRUE(unified_plan->pc()->GetTransceivers()[1]->stopped());
-  } else {
-    // If the Unified Plan endpoint was the callee, then the Plan B endpoint
-    // would have offered only one video section so we would expect the first
-    // transceiver to map to the first track and the second transceiver to be
-    // missing a mid.
-    EXPECT_TRUE(unified_plan->pc()->GetTransceivers()[0]->mid());
-    EXPECT_FALSE(unified_plan->pc()->GetTransceivers()[1]->mid());
-  }
-
-  // Should be exchanging video frames for the first tracks on each endpoint.
-  MediaExpectations media_expectations;
-  media_expectations.ExpectBidirectionalVideo();
-  ASSERT_TRUE(ExpectNewFrames(media_expectations));
-}
-
-INSTANTIATE_TEST_CASE_P(
-    PeerConnectionIntegrationTest,
-    PeerConnectionIntegrationInteropTest,
-    Values(std::make_tuple(SdpSemantics::kPlanB, SdpSemantics::kUnifiedPlan),
-           std::make_tuple(SdpSemantics::kUnifiedPlan, SdpSemantics::kPlanB)));
-
 }  // namespace
 
 #endif  // if !defined(THREAD_SANITIZER)
diff --git a/pc/peerconnection_jsep_unittest.cc b/pc/peerconnection_jsep_unittest.cc
index caa0ea0..635591c 100644
--- a/pc/peerconnection_jsep_unittest.cc
+++ b/pc/peerconnection_jsep_unittest.cc
@@ -1263,18 +1263,51 @@
   EXPECT_FALSE(transceiver->current_direction());
 }
 
-// Tests that you can't set an answer on a PeerConnection before setting
-// the offer.
+// Test that you can't set an answer on a PeerConnection before setting the
+// offer.
 TEST_F(PeerConnectionJsepTest, AnswerBeforeOfferFails) {
   auto caller = CreatePeerConnection();
   auto callee = CreatePeerConnection();
-  RTCError error_out;
   caller->AddAudioTrack("audio");
-  auto offer = caller->CreateOffer();
-  callee->SetRemoteDescription(std::move(offer), &error_out);
-  auto answer = callee->CreateAnswer();
-  EXPECT_FALSE(caller->SetRemoteDescription(std::move(answer), &error_out));
-  EXPECT_EQ(RTCErrorType::INVALID_STATE, error_out.type());
+
+  ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOffer()));
+
+  RTCError error;
+  ASSERT_FALSE(caller->SetRemoteDescription(callee->CreateAnswer(), &error));
+  EXPECT_EQ(RTCErrorType::INVALID_STATE, error.type());
+}
+
+// Test that a Unified Plan PeerConnection fails to set a Plan B offer if it has
+// two video tracks.
+TEST_F(PeerConnectionJsepTest, TwoVideoPlanBToUnifiedPlanFails) {
+  RTCConfiguration config_planb;
+  config_planb.sdp_semantics = SdpSemantics::kPlanB;
+  auto caller = CreatePeerConnection(config_planb);
+  auto callee = CreatePeerConnection();
+  caller->AddVideoTrack("video1");
+  caller->AddVideoTrack("video2");
+
+  RTCError error;
+  ASSERT_FALSE(callee->SetRemoteDescription(caller->CreateOffer(), &error));
+  EXPECT_EQ(RTCErrorType::INVALID_PARAMETER, error.type());
+}
+
+// Test that a Unified Plan PeerConnection fails to set a Plan B answer if it
+// has two video tracks.
+TEST_F(PeerConnectionJsepTest, OneVideoUnifiedPlanToTwoVideoPlanBFails) {
+  auto caller = CreatePeerConnection();
+  RTCConfiguration config_planb;
+  config_planb.sdp_semantics = SdpSemantics::kPlanB;
+  auto callee = CreatePeerConnection(config_planb);
+  caller->AddVideoTrack("video");
+  callee->AddVideoTrack("video1");
+  callee->AddVideoTrack("video2");
+
+  ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
+
+  RTCError error;
+  ASSERT_FALSE(caller->SetRemoteDescription(caller->CreateAnswer(), &error));
+  EXPECT_EQ(RTCErrorType::INVALID_PARAMETER, error.type());
 }
 
 }  // namespace webrtc
diff --git a/pc/peerconnection_rtp_unittest.cc b/pc/peerconnection_rtp_unittest.cc
index 5f4f34e..942bd46 100644
--- a/pc/peerconnection_rtp_unittest.cc
+++ b/pc/peerconnection_rtp_unittest.cc
@@ -1294,7 +1294,10 @@
   auto callee = CreatePeerConnectionWithUnifiedPlan();
   auto callee_metrics = callee->RegisterFakeMetricsObserver();
 
-  ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOffer()));
+  // This fails since Unified Plan cannot set a session description with
+  // multiple "Plan B tracks" in the same media section. But we still expect the
+  // SDP Format to be recorded.
+  ASSERT_FALSE(callee->SetRemoteDescription(caller->CreateOffer()));
 
   EXPECT_TRUE(callee_metrics->ExpectOnlySingleEnumCount(
       kEnumCounterSdpFormatReceived, kSdpFormatReceivedComplexPlanB));