Sets the SCTP port codec in the native SessionDescription.
Previously it's only set when a SDP string is parsed into SessionDescription, causing failuring for native client.

BUG=3141
R=juberti@webrtc.org, wu@webrtc.org

Review URL: https://webrtc-codereview.appspot.com/11999004

git-svn-id: http://webrtc.googlecode.com/svn/trunk@6036 4adac7df-926f-26a2-2b94-8c16560cd09d
diff --git a/talk/app/webrtc/peerconnection_unittest.cc b/talk/app/webrtc/peerconnection_unittest.cc
index bfee48c..f6003f6 100644
--- a/talk/app/webrtc/peerconnection_unittest.cc
+++ b/talk/app/webrtc/peerconnection_unittest.cc
@@ -1345,6 +1345,20 @@
                    kMaxWaitMs);
 }
 
+// This test sets up a Jsep call with SCTP DataChannel and verifies the
+// negotiation is completed without error.
+#ifdef HAVE_SCTP
+TEST_F(JsepPeerConnectionP2PTestClient, CreateOfferWithSctpDataChannel) {
+  MAYBE_SKIP_TEST(talk_base::SSLStreamAdapter::HaveDtlsSrtp);
+  FakeConstraints constraints;
+  constraints.SetMandatory(
+      MediaConstraintsInterface::kEnableDtlsSrtp, true);
+  ASSERT_TRUE(CreateTestClients(&constraints, &constraints));
+  initializing_client()->CreateDataChannel();
+  initializing_client()->Negotiate(false, false);
+}
+#endif
+
 // This test sets up a call between two parties with audio, and video.
 // During the call, the initializing side restart ice and the test verifies that
 // new ice candidates are generated and audio and video still can flow.
@@ -1406,6 +1420,4 @@
   EnableVideoDecoderFactory();
   LocalP2PTest();
 }
-
 #endif // if !defined(THREAD_SANITIZER)
-
diff --git a/talk/app/webrtc/webrtcsdp.cc b/talk/app/webrtc/webrtcsdp.cc
index 2a773b6..e726dab 100644
--- a/talk/app/webrtc/webrtcsdp.cc
+++ b/talk/app/webrtc/webrtcsdp.cc
@@ -207,7 +207,6 @@
 static const int kIsacWbDefaultRate = 32000;  // From acm_common_defs.h
 static const int kIsacSwbDefaultRate = 56000;  // From acm_common_defs.h
 
-static const int kDefaultSctpFmt = 5000;
 static const char kDefaultSctpmapProtocol[] = "webrtc-datachannel";
 
 struct SsrcInfo {
@@ -240,7 +239,7 @@
                                   const TransportInfo* transport_info,
                                   const MediaType media_type,
                                   std::string* message);
-static void BuildSctpContentAttributes(std::string* message);
+static void BuildSctpContentAttributes(std::string* message, int sctp_port);
 static void BuildRtpContentAttributes(
     const MediaContentDescription* media_desc,
     const MediaType media_type,
@@ -1166,6 +1165,7 @@
   ASSERT(media_desc != NULL);
 
   bool is_sctp = (media_desc->protocol() == cricket::kMediaProtocolDtlsSctp);
+  int sctp_port = cricket::kSctpDefaultPort;
 
   // RFC 4566
   // m=<media> <port> <proto> <fmt>
@@ -1200,14 +1200,22 @@
       fmt.append(talk_base::ToString<int>(it->id));
     }
   } else if (media_type == cricket::MEDIA_TYPE_DATA) {
+    const DataContentDescription* data_desc =
+          static_cast<const DataContentDescription*>(media_desc);
     if (is_sctp) {
       fmt.append(" ");
-      // TODO(jiayl): Replace the hard-coded string with the fmt read out of the
-      // ContentDescription.
-      fmt.append(talk_base::ToString<int>(kDefaultSctpFmt));
+
+      for (std::vector<cricket::DataCodec>::const_iterator it =
+           data_desc->codecs().begin();
+           it != data_desc->codecs().end(); ++it) {
+        if (it->id == cricket::kGoogleSctpDataCodecId &&
+            it->GetParam(cricket::kCodecParamPort, &sctp_port)) {
+          break;
+        }
+      }
+
+      fmt.append(talk_base::ToString<int>(sctp_port));
     } else {
-      const DataContentDescription* data_desc =
-          static_cast<const DataContentDescription*>(media_desc);
       for (std::vector<cricket::DataCodec>::const_iterator it =
            data_desc->codecs().begin();
            it != data_desc->codecs().end(); ++it) {
@@ -1289,18 +1297,18 @@
   AddLine(os.str(), message);
 
   if (is_sctp) {
-    BuildSctpContentAttributes(message);
+    BuildSctpContentAttributes(message, sctp_port);
   } else {
     BuildRtpContentAttributes(media_desc, media_type, message);
   }
 }
 
-void BuildSctpContentAttributes(std::string* message) {
+void BuildSctpContentAttributes(std::string* message, int sctp_port) {
   // draft-ietf-mmusic-sctp-sdp-04
   // a=sctpmap:sctpmap-number  protocol  [streams]
   std::ostringstream os;
   InitAttrLine(kAttributeSctpmap, &os);
-  os << kSdpDelimiterColon << kDefaultSctpFmt << kSdpDelimiterSpace
+  os << kSdpDelimiterColon << sctp_port << kSdpDelimiterSpace
      << kDefaultSctpmapProtocol << kSdpDelimiterSpace
      << (cricket::kMaxSctpSid + 1);
   AddLine(os.str(), message);
@@ -2166,6 +2174,7 @@
         codec_port.SetParam(cricket::kCodecParamPort, fields[3]);
         LOG(INFO) << "ParseMediaDescription: Got SCTP Port Number "
                   << fields[3];
+        ASSERT(!desc->HasCodec(cricket::kGoogleSctpDataCodecId));
         desc->AddCodec(codec_port);
       }
 
diff --git a/talk/app/webrtc/webrtcsdp_unittest.cc b/talk/app/webrtc/webrtcsdp_unittest.cc
index aec1447..e02b11f 100644
--- a/talk/app/webrtc/webrtcsdp_unittest.cc
+++ b/talk/app/webrtc/webrtcsdp_unittest.cc
@@ -299,7 +299,7 @@
     "a=mid:data_content_name\r\n"
     "a=sctpmap:5000 webrtc-datachannel 1024\r\n";
 
-    static const char kSdpConferenceString[] =
+static const char kSdpConferenceString[] =
     "v=0\r\n"
     "o=- 18446744069414584320 18446462598732840960 IN IP4 127.0.0.1\r\n"
     "s=-\r\n"
@@ -1466,6 +1466,37 @@
   EXPECT_EQ(message, expected_sdp);
 }
 
+TEST_F(WebRtcSdpTest, SerializeWithSctpDataChannelAndNewPort) {
+  AddSctpDataChannel();
+  JsepSessionDescription jsep_desc(kDummyString);
+
+  ASSERT_TRUE(jsep_desc.Initialize(desc_.Copy(), kSessionId, kSessionVersion));
+  DataContentDescription* dcdesc = static_cast<DataContentDescription*>(
+      jsep_desc.description()->GetContentDescriptionByName(kDataContentName));
+
+  const int kNewPort = 1234;
+  cricket::DataCodec codec(
+        cricket::kGoogleSctpDataCodecId, cricket::kGoogleSctpDataCodecName, 0);
+  codec.SetParam(cricket::kCodecParamPort, kNewPort);
+  dcdesc->AddOrReplaceCodec(codec);
+
+  std::string message = webrtc::SdpSerialize(jsep_desc);
+
+  std::string expected_sdp = kSdpString;
+  expected_sdp.append(kSdpSctpDataChannelString);
+
+  char default_portstr[16];
+  char new_portstr[16];
+  talk_base::sprintfn(default_portstr, sizeof(default_portstr), "%d",
+                      kDefaultSctpPort);
+  talk_base::sprintfn(new_portstr, sizeof(new_portstr), "%d", kNewPort);
+  talk_base::replace_substrs(default_portstr, strlen(default_portstr),
+                             new_portstr, strlen(new_portstr),
+                             &expected_sdp);
+
+  EXPECT_EQ(expected_sdp, message);
+}
+
 TEST_F(WebRtcSdpTest, SerializeSessionDescriptionWithDataChannelAndBandwidth) {
   AddRtpDataChannel();
   data_desc_->set_bandwidth(100*1000);
@@ -1898,6 +1929,7 @@
   talk_base::sprintfn(unusual_portstr, sizeof(unusual_portstr), "%d",
                       kUnusualSctpPort);
 
+  // First setup the expected JsepSessionDescription.
   JsepSessionDescription jdesc(kDummyString);
   // take our pre-built session description and change the SCTP port.
   cricket::SessionDescription* mutant = desc_.Copy();
@@ -1907,11 +1939,13 @@
   EXPECT_EQ(codecs.size(), 1UL);
   EXPECT_EQ(codecs[0].id, cricket::kGoogleSctpDataCodecId);
   codecs[0].SetParam(cricket::kCodecParamPort, kUnusualSctpPort);
+  dcdesc->set_codecs(codecs);
 
   // note: mutant's owned by jdesc now.
   ASSERT_TRUE(jdesc.Initialize(mutant, kSessionId, kSessionVersion));
   mutant = NULL;
 
+  // Then get the deserialized JsepSessionDescription.
   std::string sdp_with_data = kSdpString;
   sdp_with_data.append(kSdpSctpDataChannelString);
   talk_base::replace_substrs(default_portstr, strlen(default_portstr),
diff --git a/talk/media/base/codec.h b/talk/media/base/codec.h
index 56fc975..120c17b 100644
--- a/talk/media/base/codec.h
+++ b/talk/media/base/codec.h
@@ -120,6 +120,8 @@
     name = c.name;
     clockrate = c.clockrate;
     preference = c.preference;
+    params = c.params;
+    feedback_params = c.feedback_params;
     return *this;
   }
 
@@ -127,7 +129,9 @@
     return this->id == c.id &&  // id is reserved in objective-c
         name == c.name &&
         clockrate == c.clockrate &&
-        preference == c.preference;
+        preference == c.preference &&
+        params == c.params &&
+        feedback_params == c.feedback_params;
   }
 
   bool operator!=(const Codec& c) const {
diff --git a/talk/media/base/codec_unittest.cc b/talk/media/base/codec_unittest.cc
index f0ffd8f..f2bf4c7 100644
--- a/talk/media/base/codec_unittest.cc
+++ b/talk/media/base/codec_unittest.cc
@@ -40,6 +40,43 @@
   CodecTest() {}
 };
 
+TEST_F(CodecTest, TestCodecOperators) {
+  Codec c0(96, "D", 1000, 0);
+  c0.SetParam("a", 1);
+
+  Codec c1 = c0;
+  EXPECT_TRUE(c1 == c0);
+
+  int param_value0;
+  int param_value1;
+  EXPECT_TRUE(c0.GetParam("a", &param_value0));
+  EXPECT_TRUE(c1.GetParam("a", &param_value1));
+  EXPECT_EQ(param_value0, param_value1);
+
+  c1.id = 86;
+  EXPECT_TRUE(c0 != c1);
+
+  c1 = c0;
+  c1.name = "x";
+  EXPECT_TRUE(c0 != c1);
+
+  c1 = c0;
+  c1.clockrate = 2000;
+  EXPECT_TRUE(c0 != c1);
+
+  c1 = c0;
+  c1.preference = 1;
+  EXPECT_TRUE(c0 != c1);
+
+  c1 = c0;
+  c1.SetParam("a", 2);
+  EXPECT_TRUE(c0 != c1);
+
+  Codec c5;
+  Codec c6(0, "", 0, 0);
+  EXPECT_TRUE(c5 == c6);
+}
+
 TEST_F(CodecTest, TestAudioCodecOperators) {
   AudioCodec c0(96, "A", 44100, 20000, 2, 3);
   AudioCodec c1(95, "A", 44100, 20000, 2, 3);
@@ -238,23 +275,6 @@
   EXPECT_FALSE(c1.Matches(DataCodec(95, "D", 0)));
 }
 
-TEST_F(CodecTest, TestDataCodecOperators) {
-  DataCodec c0(96, "D", 3);
-  DataCodec c1(95, "D", 3);
-  DataCodec c2(96, "x", 3);
-  DataCodec c3(96, "D", 1);
-  EXPECT_TRUE(c0 != c1);
-  EXPECT_TRUE(c0 != c2);
-  EXPECT_TRUE(c0 != c3);
-
-  DataCodec c4;
-  DataCodec c5(0, "", 0);
-  DataCodec c6 = c0;
-  EXPECT_TRUE(c5 == c4);
-  EXPECT_TRUE(c6 != c4);
-  EXPECT_TRUE(c6 == c0);
-}
-
 TEST_F(CodecTest, TestSetParamAndGetParam) {
   AudioCodec codec;
   codec.SetParam("a", "1");
diff --git a/talk/media/sctp/sctpdataengine.cc b/talk/media/sctp/sctpdataengine.cc
index 59e252a..75c2a4c 100644
--- a/talk/media/sctp/sctpdataengine.cc
+++ b/talk/media/sctp/sctpdataengine.cc
@@ -105,12 +105,6 @@
 typedef talk_base::ScopedMessageData<SctpInboundPacket> InboundPacketMessage;
 typedef talk_base::ScopedMessageData<talk_base::Buffer> OutboundPacketMessage;
 
-// This is the SCTP port to use. It is passed along the wire and the listener
-// and connector must be using the same port. It is not related to the ports at
-// the IP level. (Corresponds to: sockaddr_conn.sconn_port in usrsctp.h)
-//
-// TODO(ldixon): Allow port to be set from higher level code.
-static const int kSctpDefaultPort = 5001;
 // TODO(ldixon): Find where this is defined, and also check is Sctp really
 // respects this.
 static const size_t kSctpMtu = 1280;
@@ -277,10 +271,9 @@
   }
   usrsctp_engines_count++;
 
-  // We don't put in a codec because we don't want one offered when we
-  // use the hybrid data engine.
-  // codecs_.push_back(cricket::DataCodec( kGoogleSctpDataCodecId,
-  // kGoogleSctpDataCodecName, 0));
+  cricket::DataCodec codec(kGoogleSctpDataCodecId, kGoogleSctpDataCodecName, 0);
+  codec.SetParam(kCodecParamPort, kSctpDefaultPort);
+  codecs_.push_back(codec);
 }
 
 SctpDataEngine::~SctpDataEngine() {
@@ -308,8 +301,8 @@
 
 SctpDataMediaChannel::SctpDataMediaChannel(talk_base::Thread* thread)
     : worker_thread_(thread),
-      local_port_(-1),
-      remote_port_(-1),
+      local_port_(kSctpDefaultPort),
+      remote_port_(kSctpDefaultPort),
       sock_(NULL),
       sending_(false),
       receiving_(false),
@@ -423,12 +416,6 @@
 
 bool SctpDataMediaChannel::Connect() {
   LOG(LS_VERBOSE) << debug_name_ << "->Connect().";
-  if (remote_port_ < 0) {
-    remote_port_ = kSctpDefaultPort;
-  }
-  if (local_port_ < 0) {
-    local_port_ = kSctpDefaultPort;
-  }
 
   // If we already have a socket connection, just return.
   if (sock_) {
diff --git a/talk/media/sctp/sctpdataengine.h b/talk/media/sctp/sctpdataengine.h
index f2322ab..7561977 100644
--- a/talk/media/sctp/sctpdataengine.h
+++ b/talk/media/sctp/sctpdataengine.h
@@ -58,6 +58,12 @@
 // tell SCTP we're going to use.
 const uint32 kMaxSctpSid = 1023;
 
+// This is the default SCTP port to use. It is passed along the wire and the
+// connectee and connector must be using the same port. It is not related to the
+// ports at the IP level. (Corresponds to: sockaddr_conn.sconn_port in
+// usrsctp.h)
+const int kSctpDefaultPort = 5000;
+
 // A DataEngine that interacts with usrsctp.
 //
 // From channel calls, data flows like this:
diff --git a/talk/session/media/mediasession.cc b/talk/session/media/mediasession.cc
index 17f7a1a..8219ec8 100644
--- a/talk/session/media/mediasession.cc
+++ b/talk/session/media/mediasession.cc
@@ -308,6 +308,20 @@
   }
 }
 
+// Filters the data codecs for the data channel type.
+void FilterDataCodecs(std::vector<DataCodec>* codecs, bool sctp) {
+  // Filter RTP codec for SCTP and vice versa.
+  int codec_id = sctp ? kGoogleRtpDataCodecId : kGoogleSctpDataCodecId;
+  for (std::vector<DataCodec>::iterator iter = codecs->begin();
+       iter != codecs->end();) {
+    if (iter->id == codec_id) {
+      iter = codecs->erase(iter);
+    } else {
+      ++iter;
+    }
+  }
+}
+
 template <typename IdStruct>
 class UsedIds {
  public:
@@ -1204,6 +1218,8 @@
     scoped_ptr<DataContentDescription> data(new DataContentDescription());
     bool is_sctp = (options.data_channel_type == DCT_SCTP);
 
+    FilterDataCodecs(&data_codecs, is_sctp);
+
     cricket::SecurePolicy sdes_policy =
         IsDtlsActive(CN_DATA, current_description) ?
             cricket::SEC_DISABLED : secure();
@@ -1397,6 +1413,10 @@
     if (!data_transport) {
       return NULL;
     }
+    bool is_sctp = (options.data_channel_type == DCT_SCTP);
+    std::vector<DataCodec> data_codecs(data_codecs_);
+    FilterDataCodecs(&data_codecs, is_sctp);
+
     scoped_ptr<DataContentDescription> data_answer(
         new DataContentDescription());
     // Do not require or create SDES cryptos if DTLS is used.
diff --git a/talk/session/media/mediasession.h b/talk/session/media/mediasession.h
index dff254f..a130b15 100644
--- a/talk/session/media/mediasession.h
+++ b/talk/session/media/mediasession.h
@@ -318,6 +318,16 @@
   void AddCodec(const C& codec) {
     codecs_.push_back(codec);
   }
+  void AddOrReplaceCodec(const C& codec) {
+    for (typename std::vector<C>::iterator iter = codecs_.begin();
+         iter != codecs_.end(); ++iter) {
+      if (iter->id == codec.id) {
+        *iter = codec;
+        return;
+      }
+    }
+    AddCodec(codec);
+  }
   void AddCodecs(const std::vector<C>& codecs) {
     typename std::vector<C>::const_iterator codec;
     for (codec = codecs.begin(); codec != codecs.end(); ++codec) {