Buffers non atomic message send with usrsctp lib.

Currently we set the EOR bit when sending a message through the sctp
library. This makes the send non atomic, meaning that message can be
partially accepted by the sctp socket. Currently we ignore the sent
amount result, but this change now checks that result and buffers the
remaining message to be sent later in the case that it was only
partially accepted by usrsctp.

Bug: webrtc:10922
Change-Id: I9ff563c40e2b7dbdeb19b40d07c43a15ff7c9b49
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/150562
Commit-Queue: Seth Hampson <shampson@webrtc.org>
Reviewed-by: Steve Anton <steveanton@webrtc.org>
Reviewed-by: Amit Hilbuch <amithi@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#29051}
diff --git a/media/sctp/sctp_transport_unittest.cc b/media/sctp/sctp_transport_unittest.cc
index 843fcc2..4c776ed 100644
--- a/media/sctp/sctp_transport_unittest.cc
+++ b/media/sctp/sctp_transport_unittest.cc
@@ -18,6 +18,7 @@
 #include <vector>
 
 #include "absl/algorithm/container.h"
+#include "media/sctp/sctp_transport_internal.h"
 #include "p2p/base/fake_dtls_transport.h"
 #include "rtc_base/copy_on_write_buffer.h"
 #include "rtc_base/gunit.h"
@@ -48,10 +49,12 @@
     received_ = false;
     last_data_ = "";
     last_params_ = ReceiveDataParams();
+    num_messages_received_ = 0;
   }
 
   void OnDataReceived(const ReceiveDataParams& params,
                       const rtc::CopyOnWriteBuffer& data) {
+    num_messages_received_++;
     received_ = true;
     last_data_ = std::string(data.data<char>(), data.size());
     last_params_ = params;
@@ -60,10 +63,12 @@
   bool received() const { return received_; }
   std::string last_data() const { return last_data_; }
   ReceiveDataParams last_params() const { return last_params_; }
+  size_t num_messages_received() const { return num_messages_received_; }
 
  private:
   bool received_;
   std::string last_data_;
+  size_t num_messages_received_ = 0;
   ReceiveDataParams last_params_;
 };
 
@@ -177,9 +182,11 @@
   bool SendData(SctpTransport* chan,
                 int sid,
                 const std::string& msg,
-                SendDataResult* result) {
+                SendDataResult* result,
+                bool ordered = false) {
     SendDataParams params;
     params.sid = sid;
+    params.ordered = ordered;
 
     return chan->SendData(params, rtc::CopyOnWriteBuffer(&msg[0], msg.length()),
                           result);
@@ -369,14 +376,14 @@
   // socket.
   fake_dtls1()->SetWritable(false);
   // Send messages until we get EWOULDBLOCK.
-  static const int kMaxMessages = 1024;
+  static const size_t kMaxMessages = 1024;
   SendDataParams params;
   params.sid = 1;
   rtc::CopyOnWriteBuffer buf(1024);
   memset(buf.data<uint8_t>(), 0, 1024);
   SendDataResult result;
-  int message_count;
-  for (message_count = 0; message_count < kMaxMessages; ++message_count) {
+  size_t message_count = 0;
+  for (; message_count < kMaxMessages; ++message_count) {
     if (!transport1()->SendData(params, buf, &result) && result == SDR_BLOCK) {
       break;
     }
@@ -389,6 +396,94 @@
   // some point.
   fake_dtls1()->SetWritable(true);
   EXPECT_EQ_WAIT(2, transport1_ready_to_send_count(), kDefaultTimeout);
+  EXPECT_EQ_WAIT(message_count, receiver2()->num_messages_received(),
+                 kDefaultTimeout);
+}
+
+// Tests that a small message gets buffered and later sent by the SctpTransport
+// when the sctp library only accepts the message partially.
+TEST_F(SctpTransportTest, SendSmallBufferedOutgoingMessage) {
+  SetupConnectedTransportsWithTwoStreams();
+  // Wait for initial SCTP association to be formed.
+  EXPECT_EQ_WAIT(1, transport1_ready_to_send_count(), kDefaultTimeout);
+  // Make the fake transport unwritable so that messages pile up for the SCTP
+  // socket.
+  fake_dtls1()->SetWritable(false);
+  SendDataResult result;
+  // TODO(bugs.webrtc.org/10939): We can't test this behavior unless we are
+  // sending in ordered mode becuase the sctp lib drops large buffered data in
+  // unordered mode.
+  bool ordered = true;
+
+  // Fill almost all of sctp library's send buffer.
+  ASSERT_TRUE(SendData(transport1(), /*sid=*/1,
+                       std::string(kSctpSendBufferSize - 1, 'a'), &result,
+                       ordered));
+
+  std::string buffered_message("hello hello");
+  // SctpTransport accepts this message by buffering part of it.
+  ASSERT_TRUE(
+      SendData(transport1(), /*sid=*/1, buffered_message, &result, ordered));
+  ASSERT_TRUE(transport1()->ReadyToSendData());
+
+  // Sending anything else should block now.
+  ASSERT_FALSE(
+      SendData(transport1(), /*sid=*/1, "hello again", &result, ordered));
+  ASSERT_EQ(SDR_BLOCK, result);
+  ASSERT_FALSE(transport1()->ReadyToSendData());
+
+  // Make sure the ready-to-send count hasn't changed.
+  EXPECT_EQ(1, transport1_ready_to_send_count());
+  // Make the transport writable again and expect a "SignalReadyToSendData" at
+  // some point after sending the buffered message.
+  fake_dtls1()->SetWritable(true);
+  EXPECT_EQ_WAIT(2, transport1_ready_to_send_count(), kDefaultTimeout);
+  EXPECT_TRUE_WAIT(ReceivedData(receiver2(), 1, buffered_message),
+                   kDefaultTimeout);
+  EXPECT_EQ(2u, receiver2()->num_messages_received());
+}
+
+// Tests that a large message gets buffered and later sent by the SctpTransport
+// when the sctp library only accepts the message partially.
+TEST_F(SctpTransportTest, SendLargeBufferedOutgoingMessage) {
+  SetupConnectedTransportsWithTwoStreams();
+  // Wait for initial SCTP association to be formed.
+  EXPECT_EQ_WAIT(1, transport1_ready_to_send_count(), kDefaultTimeout);
+  // Make the fake transport unwritable so that messages pile up for the SCTP
+  // socket.
+  fake_dtls1()->SetWritable(false);
+  SendDataResult result;
+  // TODO(bugs.webrtc.org/10939): We can't test this behavior unless we are
+  // sending in ordered mode becuase the sctp lib drops large buffered data in
+  // unordered mode.
+  bool ordered = true;
+
+  // Fill almost all of sctp library's send buffer.
+  ASSERT_TRUE(SendData(transport1(), /*sid=*/1,
+                       std::string(kSctpSendBufferSize / 2, 'a'), &result,
+                       ordered));
+
+  std::string buffered_message(kSctpSendBufferSize, 'b');
+  // SctpTransport accepts this message by buffering the second half.
+  ASSERT_TRUE(
+      SendData(transport1(), /*sid=*/1, buffered_message, &result, ordered));
+  ASSERT_TRUE(transport1()->ReadyToSendData());
+
+  // Sending anything else should block now.
+  ASSERT_FALSE(
+      SendData(transport1(), /*sid=*/1, "hello again", &result, ordered));
+  ASSERT_EQ(SDR_BLOCK, result);
+  ASSERT_FALSE(transport1()->ReadyToSendData());
+
+  // Make sure the ready-to-send count hasn't changed.
+  EXPECT_EQ(1, transport1_ready_to_send_count());
+  // Make the transport writable again and expect a "SignalReadyToSendData" at
+  // some point.
+  fake_dtls1()->SetWritable(true);
+  EXPECT_EQ_WAIT(2, transport1_ready_to_send_count(), kDefaultTimeout);
+  EXPECT_TRUE_WAIT(ReceivedData(receiver2(), 1, buffered_message),
+                   kDefaultTimeout);
+  EXPECT_EQ(2u, receiver2()->num_messages_received());
 }
 
 TEST_F(SctpTransportTest, SendData) {