Send and parse SCTP max-message-size in SDP

This also changes the default when no max-message-size is set
to the protocol defined value of 64K, and prevents messages
from being sent when they are too large to send.

Bug: webrtc:10358
Change-Id: Iacc1dd774d1554d9f27315378fbea6351300b5cc
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/135948
Commit-Queue: Harald Alvestrand <hta@webrtc.org>
Reviewed-by: Steve Anton <steveanton@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#27945}
diff --git a/media/sctp/sctp_transport.cc b/media/sctp/sctp_transport.cc
index 8bcec41..b99a55b 100644
--- a/media/sctp/sctp_transport.cc
+++ b/media/sctp/sctp_transport.cc
@@ -46,9 +46,6 @@
 // take off 80 bytes for DTLS/TURN/TCP/IP overhead.
 static constexpr size_t kSctpMtu = 1200;
 
-// The size of the SCTP association send buffer. 256kB, the usrsctp default.
-static constexpr int kSendBufferSize = 256 * 1024;
-
 // Set the initial value of the static SCTP Data Engines reference count.
 int g_usrsctp_usage_count = 0;
 rtc::GlobalLockPod g_usrsctp_lock_;
@@ -185,7 +182,7 @@
     // This is harmless, but we should find out when the library default
     // changes.
     int send_size = usrsctp_sysctl_get_sctp_sendspace();
-    if (send_size != kSendBufferSize) {
+    if (send_size != kSctpSendBufferSize) {
       RTC_LOG(LS_ERROR) << "Got different send size than expected: "
                         << send_size;
     }
@@ -327,7 +324,7 @@
       // callback. Larger messages (originating from other implementations) will
       // still be delivered in chunks.
       if (!(flags & MSG_EOR) &&
-          (transport->partial_message_.size() < kSendBufferSize)) {
+          (transport->partial_message_.size() < kSctpSendBufferSize)) {
         return 1;
       }
 
@@ -410,7 +407,9 @@
   }
 }
 
-bool SctpTransport::Start(int local_sctp_port, int remote_sctp_port) {
+bool SctpTransport::Start(int local_sctp_port,
+                          int remote_sctp_port,
+                          int max_message_size) {
   RTC_DCHECK_RUN_ON(network_thread_);
   if (local_sctp_port == -1) {
     local_sctp_port = kSctpDefaultPort;
@@ -418,6 +417,20 @@
   if (remote_sctp_port == -1) {
     remote_sctp_port = kSctpDefaultPort;
   }
+  if (max_message_size > kSctpSendBufferSize) {
+    RTC_LOG(LS_ERROR) << "Max message size of " << max_message_size
+                      << " is larger than send bufffer size "
+                      << kSctpSendBufferSize;
+    return false;
+  }
+  if (max_message_size < 1) {
+    RTC_LOG(LS_ERROR) << "Max message size of " << max_message_size
+                      << " is too small";
+    return false;
+  }
+  // We allow changing max_message_size with a second Start() call,
+  // but not changing the port numbers.
+  max_message_size_ = max_message_size;
   if (started_) {
     if (local_sctp_port != local_port_ || remote_sctp_port != remote_port_) {
       RTC_LOG(LS_ERROR)
@@ -537,6 +550,11 @@
     }
   }
 
+  if (payload.size() > static_cast<size_t>(max_message_size_)) {
+    RTC_LOG(LS_ERROR) << "Attempting to send message of size " << payload.size()
+                      << " which is larger than limit " << max_message_size_;
+    return false;
+  }
   // We don't fragment.
   send_res = usrsctp_sendv(
       sock_, payload.data(), static_cast<size_t>(payload.size()), NULL, 0, &spa,
@@ -657,9 +675,9 @@
 
   UsrSctpWrapper::IncrementUsrSctpUsageCount();
 
-  // If kSendBufferSize isn't reflective of reality, we log an error, but we
-  // still have to do something reasonable here.  Look up what the buffer's
-  // real size is and set our threshold to something reasonable.
+  // If kSctpSendBufferSize isn't reflective of reality, we log an error, but we
+  // still have to do something reasonable here.  Look up what the buffer's real
+  // size is and set our threshold to something reasonable.
   static const int kSendThreshold = usrsctp_sysctl_get_sctp_sendspace() / 2;
 
   sock_ = usrsctp_socket(
diff --git a/media/sctp/sctp_transport.h b/media/sctp/sctp_transport.h
index dccecd8..554407b 100644
--- a/media/sctp/sctp_transport.h
+++ b/media/sctp/sctp_transport.h
@@ -72,13 +72,14 @@
 
   // SctpTransportInternal overrides (see sctptransportinternal.h for comments).
   void SetDtlsTransport(rtc::PacketTransportInternal* transport) override;
-  bool Start(int local_port, int remote_port) override;
+  bool Start(int local_port, int remote_port, int max_message_size) override;
   bool OpenStream(int sid) override;
   bool ResetStream(int sid) override;
   bool SendData(const SendDataParams& params,
                 const rtc::CopyOnWriteBuffer& payload,
                 SendDataResult* result = nullptr) override;
   bool ReadyToSendData() override;
+  int max_message_size() const override { return max_message_size_; }
   void set_debug_name_for_testing(const char* debug_name) override {
     debug_name_ = debug_name;
   }
@@ -151,6 +152,7 @@
   bool was_ever_writable_ = false;
   int local_port_ = kSctpDefaultPort;
   int remote_port_ = kSctpDefaultPort;
+  int max_message_size_ = kSctpSendBufferSize;
   struct socket* sock_ = nullptr;  // The socket created by usrsctp_socket(...).
 
   // Has Start been called? Don't create SCTP socket until it has.
diff --git a/media/sctp/sctp_transport_internal.h b/media/sctp/sctp_transport_internal.h
index 12f1782..c08c414 100644
--- a/media/sctp/sctp_transport_internal.h
+++ b/media/sctp/sctp_transport_internal.h
@@ -28,6 +28,10 @@
 
 namespace cricket {
 
+// Constants that are important to API users
+// The size of the SCTP association send buffer. 256kB, the usrsctp default.
+constexpr int kSctpSendBufferSize = 256 * 1024;
+
 // The number of outgoing streams that we'll negotiate. Since stream IDs (SIDs)
 // are 0-based, the highest usable SID is 1023.
 //
@@ -67,13 +71,16 @@
   // listener and connector must be using the same port. They are not related
   // to the ports at the IP level. If set to -1, we default to
   // kSctpDefaultPort.
+  // |max_message_size_| sets the max message size on the connection.
+  // It must be smaller than or equal to kSctpSendBufferSize.
+  // It can be changed by a secons Start() call.
   //
-  // TODO(deadbeef): Add remote max message size as parameter to Start, once we
-  // start supporting it.
   // TODO(deadbeef): Support calling Start with different local/remote ports
   // and create a new association? Not clear if this is something we need to
   // support though. See: https://github.com/w3c/webrtc-pc/issues/979
-  virtual bool Start(int local_sctp_port, int remote_sctp_port) = 0;
+  virtual bool Start(int local_sctp_port,
+                     int remote_sctp_port,
+                     int max_message_size) = 0;
 
   // NOTE: Initially there was a "Stop" method here, but it was never used, so
   // it was removed.
@@ -105,6 +112,8 @@
   // ICE channels may be unwritable while ReadyToSendData is true, because data
   // can still be queued in usrsctp.
   virtual bool ReadyToSendData() = 0;
+  // Returns the current max message size, set with Start().
+  virtual int max_message_size() const = 0;
 
   sigslot::signal0<> SignalReadyToSendData;
   // ReceiveDataParams includes SID, seq num, timestamp, etc. CopyOnWriteBuffer
diff --git a/media/sctp/sctp_transport_unittest.cc b/media/sctp/sctp_transport_unittest.cc
index 86cb416..d7ccd58 100644
--- a/media/sctp/sctp_transport_unittest.cc
+++ b/media/sctp/sctp_transport_unittest.cc
@@ -151,8 +151,8 @@
         << "Connect the transports -----------------------------";
     // Both transports need to have started (with matching ports) for an
     // association to be formed.
-    transport1_->Start(port1, port2);
-    transport2_->Start(port2, port1);
+    transport1_->Start(port1, port2, kSctpSendBufferSize);
+    transport2_->Start(port2, port1, kSctpSendBufferSize);
   }
 
   bool AddStream(int sid) {
@@ -251,8 +251,8 @@
   transport2->OpenStream(1);
 
   // Tell them both to start (though transport1_ is connected to black_hole).
-  transport1->Start(kTransport1Port, kTransport2Port);
-  transport2->Start(kTransport2Port, kTransport1Port);
+  transport1->Start(kTransport1Port, kTransport2Port, kSctpSendBufferSize);
+  transport2->Start(kTransport2Port, kTransport1Port, kSctpSendBufferSize);
 
   // Switch transport1_ to the normal fake_dtls1_ transport.
   transport1->SetDtlsTransport(&fake_dtls1);
@@ -276,7 +276,8 @@
 // Calling Start twice shouldn't do anything bad, if with the same parameters.
 TEST_F(SctpTransportTest, DuplicateStartCallsIgnored) {
   SetupConnectedTransportsWithTwoStreams();
-  EXPECT_TRUE(transport1()->Start(kTransport1Port, kTransport2Port));
+  EXPECT_TRUE(transport1()->Start(kTransport1Port, kTransport2Port,
+                                  kSctpSendBufferSize));
 
   // Make sure we can still send/recv data.
   SendDataResult result;
@@ -289,8 +290,8 @@
 // Calling Start a second time with a different port should fail.
 TEST_F(SctpTransportTest, CallingStartWithDifferentPortFails) {
   SetupConnectedTransportsWithTwoStreams();
-  EXPECT_FALSE(transport1()->Start(kTransport1Port, 1234));
-  EXPECT_FALSE(transport1()->Start(1234, kTransport2Port));
+  EXPECT_FALSE(transport1()->Start(kTransport1Port, 1234, kSctpSendBufferSize));
+  EXPECT_FALSE(transport1()->Start(1234, kTransport2Port, kSctpSendBufferSize));
 }
 
 // A value of -1 for the local/remote port should be treated as the default
@@ -311,8 +312,8 @@
 
   // Tell them both to start, giving one transport the default port and the
   // other transport -1.
-  transport1->Start(kSctpDefaultPort, kSctpDefaultPort);
-  transport2->Start(-1, -1);
+  transport1->Start(kSctpDefaultPort, kSctpDefaultPort, kSctpSendBufferSize);
+  transport2->Start(-1, -1, kSctpSendBufferSize);
 
   // Connect the two fake DTLS transports.
   bool asymmetric = false;
@@ -351,7 +352,7 @@
   std::unique_ptr<SctpTransport> transport(CreateTransport(&fake_dtls, &recv));
   SctpTransportObserver observer(transport.get());
 
-  transport->Start(kSctpDefaultPort, kSctpDefaultPort);
+  transport->Start(kSctpDefaultPort, kSctpDefaultPort, kSctpSendBufferSize);
   fake_dtls.SetWritable(true);
   EXPECT_TRUE_WAIT(observer.ReadyToSend(), kDefaultTimeout);
 }
@@ -564,4 +565,31 @@
   EXPECT_EQ_WAIT(2, transport2_observer.StreamCloseCount(1), kDefaultTimeout);
 }
 
+TEST_F(SctpTransportTest, RejectsTooLargeMessageSize) {
+  FakeDtlsTransport fake_dtls("fake dtls", 0);
+  SctpFakeDataReceiver recv;
+  std::unique_ptr<SctpTransport> transport(CreateTransport(&fake_dtls, &recv));
+
+  EXPECT_FALSE(transport->Start(kSctpDefaultPort, kSctpDefaultPort,
+                                kSctpSendBufferSize + 1));
+}
+
+TEST_F(SctpTransportTest, RejectsTooSmallMessageSize) {
+  FakeDtlsTransport fake_dtls("fake dtls", 0);
+  SctpFakeDataReceiver recv;
+  std::unique_ptr<SctpTransport> transport(CreateTransport(&fake_dtls, &recv));
+
+  EXPECT_FALSE(transport->Start(kSctpDefaultPort, kSctpDefaultPort, 0));
+}
+
+TEST_F(SctpTransportTest, RejectsSendTooLargeMessages) {
+  SetupConnectedTransportsWithTwoStreams();
+  // Use "Start" to reduce the max message size
+  transport1()->Start(kTransport1Port, kTransport2Port, 10);
+  EXPECT_EQ(10, transport1()->max_message_size());
+  const char eleven_characters[] = "12345678901";
+  SendDataResult result;
+  EXPECT_FALSE(SendData(transport1(), 1, eleven_characters, &result));
+}
+
 }  // namespace cricket
diff --git a/pc/media_session.cc b/pc/media_session.cc
index 69ebb84..0924e6d 100644
--- a/pc/media_session.cc
+++ b/pc/media_session.cc
@@ -25,6 +25,7 @@
 #include "api/crypto_params.h"
 #include "media/base/h264_profile_level_id.h"
 #include "media/base/media_constants.h"
+#include "media/sctp/sctp_transport_internal.h"
 #include "p2p/base/p2p_constants.h"
 #include "pc/channel_manager.h"
 #include "pc/media_protocol_names.h"
@@ -2238,6 +2239,8 @@
   data->set_protocol(secure_transport ? kMediaProtocolUdpDtlsSctp
                                       : kMediaProtocolSctp);
   data->set_use_sctpmap(session_options.use_obsolete_sctp_sdp);
+  data->set_max_message_size(kSctpSendBufferSize);
+
   if (!CreateContentOffer(media_description_options, session_options,
                           sdes_policy, GetCryptos(current_content),
                           crypto_suites, RtpHeaderExtensions(), ssrc_generator_,
@@ -2579,6 +2582,10 @@
         offer_content->media_description()->as_sctp();
     // Respond with the offerer's proto, whatever it is.
     data_answer->as_sctp()->set_protocol(offer_data_description->protocol());
+    // Respond with our max message size or the remote max messsage size,
+    // whichever is smaller.
+    data_answer->as_sctp()->set_max_message_size(std::min(
+        offer_data_description->max_message_size(), kSctpSendBufferSize));
     if (!CreateMediaContentAnswer(
             offer_data_description, media_description_options, session_options,
             sdes_policy, GetCryptos(current_content), RtpHeaderExtensions(),
diff --git a/pc/peer_connection.cc b/pc/peer_connection.cc
index fcd1a2a..e1cc6ef 100644
--- a/pc/peer_connection.cc
+++ b/pc/peer_connection.cc
@@ -556,18 +556,6 @@
   return true;
 }
 
-// Get the SCTP port out of a SessionDescription.
-// Return -1 if not found.
-int GetSctpPort(const SessionDescription* session_description) {
-  const cricket::SctpDataContentDescription* data_desc =
-      GetFirstSctpDataContentDescription(session_description);
-  RTC_DCHECK(data_desc);
-  if (!data_desc) {
-    return -1;
-  }
-  return data_desc->port();
-}
-
 // Returns true if |new_desc| requests an ICE restart (i.e., new ufrag/pwd).
 bool CheckForRemoteIceRestart(const SessionDescriptionInterface* old_desc,
                               const SessionDescriptionInterface* new_desc,
@@ -5663,17 +5651,24 @@
 
   // Need complete offer/answer with an SCTP m= section before starting SCTP,
   // according to https://tools.ietf.org/html/draft-ietf-mmusic-sctp-sdp-19
-  if (sctp_transport_ && local_description() && remote_description() &&
-      cricket::GetFirstDataContent(local_description()->description()) &&
-      cricket::GetFirstDataContent(remote_description()->description())) {
-    bool success = network_thread()->Invoke<bool>(
-        RTC_FROM_HERE,
-        rtc::Bind(&PeerConnection::PushdownSctpParameters_n, this, source,
-                  GetSctpPort(local_description()->description()),
-                  GetSctpPort(remote_description()->description())));
-    if (!success) {
-      LOG_AND_RETURN_ERROR(RTCErrorType::INTERNAL_ERROR,
-                           "Failed to push down SCTP parameters.");
+  if (sctp_transport_ && local_description() && remote_description()) {
+    auto local_sctp_description = cricket::GetFirstSctpDataContentDescription(
+        local_description()->description());
+    auto remote_sctp_description = cricket::GetFirstSctpDataContentDescription(
+        remote_description()->description());
+    if (local_sctp_description && remote_sctp_description) {
+      int max_message_size =
+          std::min(local_sctp_description->max_message_size(),
+                   remote_sctp_description->max_message_size());
+      bool success = network_thread()->Invoke<bool>(
+          RTC_FROM_HERE,
+          rtc::Bind(&PeerConnection::PushdownSctpParameters_n, this, source,
+                    local_sctp_description->port(),
+                    remote_sctp_description->port(), max_message_size));
+      if (!success) {
+        LOG_AND_RETURN_ERROR(RTCErrorType::INTERNAL_ERROR,
+                             "Failed to push down SCTP parameters.");
+      }
     }
   }
 
@@ -5682,11 +5677,13 @@
 
 bool PeerConnection::PushdownSctpParameters_n(cricket::ContentSource source,
                                               int local_sctp_port,
-                                              int remote_sctp_port) {
+                                              int remote_sctp_port,
+                                              int max_message_size) {
   RTC_DCHECK_RUN_ON(network_thread());
   // Apply the SCTP port (which is hidden inside a DataCodec structure...)
   // When we support "max-message-size", that would also be pushed down here.
-  return cricket_sctp_transport()->Start(local_sctp_port, remote_sctp_port);
+  return cricket_sctp_transport()->Start(local_sctp_port, remote_sctp_port,
+                                         max_message_size);
 }
 
 RTCError PeerConnection::PushdownTransportDescription(
diff --git a/pc/peer_connection.h b/pc/peer_connection.h
index be46980..e83ef1e 100644
--- a/pc/peer_connection.h
+++ b/pc/peer_connection.h
@@ -879,7 +879,8 @@
       RTC_RUN_ON(signaling_thread());
   bool PushdownSctpParameters_n(cricket::ContentSource source,
                                 int local_sctp_port,
-                                int remote_sctp_port);
+                                int remote_sctp_port,
+                                int max_message_size);
 
   RTCError PushdownTransportDescription(cricket::ContentSource source,
                                         SdpType type);
diff --git a/pc/sctp_transport.cc b/pc/sctp_transport.cc
index 1db149f..d23d619 100644
--- a/pc/sctp_transport.cc
+++ b/pc/sctp_transport.cc
@@ -105,8 +105,14 @@
     must_send_update = (state != info_.state());
     // TODO(https://bugs.webrtc.org/10358): Update max message size and
     // max channels from internal SCTP transport when available.
-    info_ = SctpTransportInformation(
-        state, dtls_transport_, info_.MaxMessageSize(), info_.MaxChannels());
+    if (internal_sctp_transport_) {
+      info_ = SctpTransportInformation(
+          state, dtls_transport_, internal_sctp_transport_->max_message_size(),
+          info_.MaxChannels());
+    } else {
+      info_ = SctpTransportInformation(
+          state, dtls_transport_, info_.MaxMessageSize(), info_.MaxChannels());
+    }
     if (observer_ && must_send_update) {
       info_copy = info_;
     }
diff --git a/pc/sctp_transport_unittest.cc b/pc/sctp_transport_unittest.cc
index 3771b2c..3438b17 100644
--- a/pc/sctp_transport_unittest.cc
+++ b/pc/sctp_transport_unittest.cc
@@ -32,7 +32,9 @@
 class FakeCricketSctpTransport : public cricket::SctpTransportInternal {
  public:
   void SetDtlsTransport(rtc::PacketTransportInternal* transport) override {}
-  bool Start(int local_port, int remote_port) override { return true; }
+  bool Start(int local_port, int remote_port, int max_message_size) override {
+    return true;
+  }
   bool OpenStream(int sid) override { return true; }
   bool ResetStream(int sid) override { return true; }
   bool SendData(const cricket::SendDataParams& params,
@@ -42,6 +44,7 @@
   }
   bool ReadyToSendData() override { return true; }
   void set_debug_name_for_testing(const char* debug_name) override {}
+  int max_message_size() const override { return 0; }
   // Methods exposed for testing
   void SendSignalReadyToSendData() { SignalReadyToSendData(); }
 
diff --git a/pc/session_description.h b/pc/session_description.h
index 27b781f..fe08e3a 100644
--- a/pc/session_description.h
+++ b/pc/session_description.h
@@ -498,7 +498,8 @@
   bool use_sctpmap_ = true;  // Note: "true" is no longer conformant.
   // Defaults should be constants imported from SCTP. Quick hack.
   int port_ = 5000;
-  int max_message_size_ = 256 * 1024;
+  // draft-ietf-mmusic-sdp-sctp-23: Max message size default is 64K
+  int max_message_size_ = 64 * 1024;
   std::unique_ptr<DataContentDescription> shim_;
 };
 
diff --git a/pc/test/fake_sctp_transport.h b/pc/test/fake_sctp_transport.h
index b2c5c1a..b3eeee0 100644
--- a/pc/test/fake_sctp_transport.h
+++ b/pc/test/fake_sctp_transport.h
@@ -21,9 +21,10 @@
 class FakeSctpTransport : public cricket::SctpTransportInternal {
  public:
   void SetDtlsTransport(rtc::PacketTransportInternal* transport) override {}
-  bool Start(int local_port, int remote_port) override {
+  bool Start(int local_port, int remote_port, int max_message_size) override {
     local_port_.emplace(local_port);
     remote_port_.emplace(remote_port);
+    max_message_size_ = max_message_size;
     return true;
   }
   bool OpenStream(int sid) override { return true; }
@@ -36,12 +37,14 @@
   bool ReadyToSendData() override { return true; }
   void set_debug_name_for_testing(const char* debug_name) override {}
 
+  int max_message_size() const { return max_message_size_; }
   int local_port() const { return *local_port_; }
   int remote_port() const { return *remote_port_; }
 
  private:
   absl::optional<int> local_port_;
   absl::optional<int> remote_port_;
+  int max_message_size_;
 };
 
 class FakeSctpTransportFactory : public cricket::SctpTransportInternalFactory {
diff --git a/pc/webrtc_sdp.cc b/pc/webrtc_sdp.cc
index c6968b1..0c09fa2 100644
--- a/pc/webrtc_sdp.cc
+++ b/pc/webrtc_sdp.cc
@@ -172,6 +172,7 @@
 // a=sctp-port, a=max-message-size
 static const char kAttributeSctpPort[] = "sctp-port";
 static const char kAttributeMaxMessageSize[] = "max-message-size";
+static const int kDefaultSctpMaxMessageSize = 65536;
 // draft-ietf-mmusic-sdp-simulcast-13
 // a=simulcast
 static const char kAttributeSimulcast[] = "simulcast";
@@ -272,9 +273,6 @@
                                   const std::vector<Candidate>& candidates,
                                   int msid_signaling,
                                   std::string* message);
-static void BuildSctpContentAttributes(std::string* message,
-                                       int sctp_port,
-                                       bool use_sctpmap);
 static void BuildRtpContentAttributes(const MediaContentDescription* media_desc,
                                       const cricket::MediaType media_type,
                                       int msid_signaling,
@@ -1323,6 +1321,33 @@
   return true;
 }
 
+static void BuildSctpContentAttributes(
+    std::string* message,
+    const cricket::SctpDataContentDescription* data_desc) {
+  rtc::StringBuilder os;
+  if (data_desc->use_sctpmap()) {
+    // draft-ietf-mmusic-sctp-sdp-04
+    // a=sctpmap:sctpmap-number  protocol  [streams]
+    rtc::StringBuilder os;
+    InitAttrLine(kAttributeSctpmap, &os);
+    os << kSdpDelimiterColon << data_desc->port() << kSdpDelimiterSpace
+       << kDefaultSctpmapProtocol << kSdpDelimiterSpace
+       << cricket::kMaxSctpStreams;
+    AddLine(os.str(), message);
+  } else {
+    // draft-ietf-mmusic-sctp-sdp-23
+    // a=sctp-port:<port>
+    InitAttrLine(kAttributeSctpPort, &os);
+    os << kSdpDelimiterColon << data_desc->port();
+    AddLine(os.str(), message);
+    if (data_desc->max_message_size() != kDefaultSctpMaxMessageSize) {
+      InitAttrLine(kAttributeMaxMessageSize, &os);
+      os << kSdpDelimiterColon << data_desc->max_message_size();
+      AddLine(os.str(), message);
+    }
+  }
+}
+
 void BuildMediaDescription(const ContentInfo* content_info,
                            const TransportInfo* transport_info,
                            const cricket::MediaType media_type,
@@ -1518,34 +1543,12 @@
   if (cricket::IsDtlsSctp(media_desc->protocol())) {
     const cricket::SctpDataContentDescription* data_desc =
         media_desc->as_sctp();
-    bool use_sctpmap = data_desc->use_sctpmap();
-    BuildSctpContentAttributes(message, data_desc->port(), use_sctpmap);
+    BuildSctpContentAttributes(message, data_desc);
   } else if (cricket::IsRtpProtocol(media_desc->protocol())) {
     BuildRtpContentAttributes(media_desc, media_type, msid_signaling, message);
   }
 }
 
-void BuildSctpContentAttributes(std::string* message,
-                                int sctp_port,
-                                bool use_sctpmap) {
-  rtc::StringBuilder os;
-  if (use_sctpmap) {
-    // draft-ietf-mmusic-sctp-sdp-04
-    // a=sctpmap:sctpmap-number  protocol  [streams]
-    InitAttrLine(kAttributeSctpmap, &os);
-    os << kSdpDelimiterColon << sctp_port << kSdpDelimiterSpace
-       << kDefaultSctpmapProtocol << kSdpDelimiterSpace
-       << cricket::kMaxSctpStreams;
-  } else {
-    // draft-ietf-mmusic-sctp-sdp-23
-    // a=sctp-port:<port>
-    InitAttrLine(kAttributeSctpPort, &os);
-    os << kSdpDelimiterColon << sctp_port;
-    // TODO(zstein): emit max-message-size here
-  }
-  AddLine(os.str(), message);
-}
-
 void BuildRtpContentAttributes(const MediaContentDescription* media_desc,
                                const cricket::MediaType media_type,
                                int msid_signaling,
@@ -2706,6 +2709,9 @@
         // m=application <port> UDP/DTLS/SCTP webrtc-datachannel
         // use_sctpmap should be false.
         auto data_desc = absl::make_unique<SctpDataContentDescription>();
+        // Default max message size is 64K
+        // according to draft-ietf-mmusic-sctp-sdp-26
+        data_desc->set_max_message_size(kDefaultSctpMaxMessageSize);
         int p;
         if (rtc::FromString(fields[3], &p)) {
           data_desc->set_port(p);
diff --git a/pc/webrtc_sdp_unittest.cc b/pc/webrtc_sdp_unittest.cc
index 1d3e6a3..a3a8749 100644
--- a/pc/webrtc_sdp_unittest.cc
+++ b/pc/webrtc_sdp_unittest.cc
@@ -2891,6 +2891,34 @@
   EXPECT_TRUE(CompareSessionDescription(jdesc, jdesc_output));
 }
 
+TEST_F(WebRtcSdpTest, SerializeSdpWithSctpDataChannelWithMaxMessageSize) {
+  bool use_sctpmap = false;
+  AddSctpDataChannel(use_sctpmap);
+  JsepSessionDescription jdesc(kDummyType);
+  MutateJsepSctpMaxMessageSize(desc_, 12345, &jdesc);
+  std::string message = webrtc::SdpSerialize(jdesc);
+  EXPECT_NE(std::string::npos,
+            message.find("\r\na=max-message-size:12345\r\n"));
+  JsepSessionDescription jdesc_output(kDummyType);
+  EXPECT_TRUE(SdpDeserialize(message, &jdesc_output));
+  EXPECT_TRUE(CompareSessionDescription(jdesc, jdesc_output));
+}
+
+TEST_F(WebRtcSdpTest,
+       SerializeSdpWithSctpDataChannelWithDefaultMaxMessageSize) {
+  // https://tools.ietf.org/html/draft-ietf-mmusic-sctp-sdp-26#section-6
+  // The default max message size is 64K.
+  bool use_sctpmap = false;
+  AddSctpDataChannel(use_sctpmap);
+  JsepSessionDescription jdesc(kDummyType);
+  MutateJsepSctpMaxMessageSize(desc_, 65536, &jdesc);
+  std::string message = webrtc::SdpSerialize(jdesc);
+  EXPECT_EQ(std::string::npos, message.find("\r\na=max-message-size:"));
+  JsepSessionDescription jdesc_output(kDummyType);
+  EXPECT_TRUE(SdpDeserialize(message, &jdesc_output));
+  EXPECT_TRUE(CompareSessionDescription(jdesc, jdesc_output));
+}
+
 // Test to check the behaviour if sctp-port is specified
 // on the m= line and in a=sctp-port.
 TEST_F(WebRtcSdpTest, DeserializeSdpWithMultiSctpPort) {