dcsctp: Add PacketSender

This is mainly a refactoring commit, to break out packet sending to a
dedicated component.

Bug: webrtc:12943
Change-Id: I78f18933776518caf49737d3952bda97f19ef335
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/228565
Reviewed-by: Florent Castelli <orphis@webrtc.org>
Commit-Queue: Victor Boivie <boivie@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#34772}
diff --git a/net/dcsctp/socket/BUILD.gn b/net/dcsctp/socket/BUILD.gn
index f24e60b..805c677 100644
--- a/net/dcsctp/socket/BUILD.gn
+++ b/net/dcsctp/socket/BUILD.gn
@@ -76,10 +76,25 @@
   ]
 }
 
+rtc_library("packet_sender") {
+  deps = [
+    "../packet:sctp_packet",
+    "../public:socket",
+    "../public:types",
+    "../timer",
+  ]
+  sources = [
+    "packet_sender.cc",
+    "packet_sender.h",
+  ]
+  absl_deps = []
+}
+
 rtc_library("transmission_control_block") {
   deps = [
     ":context",
     ":heartbeat_handler",
+    ":packet_sender",
     ":stream_reset_handler",
     "../../../api:array_view",
     "../../../rtc_base",
@@ -114,6 +129,7 @@
   deps = [
     ":context",
     ":heartbeat_handler",
+    ":packet_sender",
     ":stream_reset_handler",
     ":transmission_control_block",
     "../../../api:array_view",
@@ -201,6 +217,7 @@
       ":heartbeat_handler",
       ":mock_callbacks",
       ":mock_context",
+      ":packet_sender",
       ":stream_reset_handler",
       "../../../api:array_view",
       "../../../rtc_base:checks",
@@ -233,6 +250,7 @@
     sources = [
       "dcsctp_socket_test.cc",
       "heartbeat_handler_test.cc",
+      "packet_sender_test.cc",
       "state_cookie_test.cc",
       "stream_reset_handler_test.cc",
     ]
diff --git a/net/dcsctp/socket/dcsctp_socket.cc b/net/dcsctp/socket/dcsctp_socket.cc
index bb84d94..a39ec5c 100644
--- a/net/dcsctp/socket/dcsctp_socket.cc
+++ b/net/dcsctp/socket/dcsctp_socket.cc
@@ -168,6 +168,8 @@
           TimerOptions(options.t2_shutdown_timeout,
                        TimerBackoffAlgorithm::kExponential,
                        options.max_retransmissions))),
+      packet_sender_(callbacks_,
+                     absl::bind_front(&DcSctpSocket::OnSentPacket, this)),
       send_queue_(
           log_prefix_,
           options_.max_send_buffer_size,
@@ -251,7 +253,7 @@
                  connect_params_.initial_tsn, params_builder.Build());
   SctpPacket::Builder b(VerificationTag(0), options_);
   b.Add(init);
-  SendPacket(b);
+  packet_sender_.Send(b);
 }
 
 void DcSctpSocket::MakeConnectionParameters() {
@@ -316,7 +318,7 @@
                        Parameters::Builder()
                            .Add(UserInitiatedAbortCause("Close called"))
                            .Build()));
-      SendPacket(b);
+      packet_sender_.Send(b);
     }
     InternalClose(ErrorKind::kNoError, "");
   } else {
@@ -327,7 +329,7 @@
 }
 
 void DcSctpSocket::CloseConnectionBecauseOfTooManyTransmissionErrors() {
-  SendPacket(tcb_->PacketBuilder().Add(AbortChunk(
+  packet_sender_.Send(tcb_->PacketBuilder().Add(AbortChunk(
       true, Parameters::Builder()
                 .Add(UserInitiatedAbortCause("Too many retransmissions"))
                 .Build())));
@@ -412,7 +414,7 @@
   if (reconfig.has_value()) {
     SctpPacket::Builder builder = tcb_->PacketBuilder();
     builder.Add(*reconfig);
-    SendPacket(builder);
+    packet_sender_.Send(builder);
   }
 
   RTC_DCHECK(IsConsistent());
@@ -751,7 +753,7 @@
     // cause."
     if (tcb_ != nullptr) {
       // Need TCB - this chunk must be sent with a correct verification tag.
-      SendPacket(tcb_->PacketBuilder().Add(
+      packet_sender_.Send(tcb_->PacketBuilder().Add(
           ErrorChunk(Parameters::Builder()
                          .Add(UnrecognizedChunkTypeCause(std::vector<uint8_t>(
                              descriptor.data.begin(), descriptor.data.end())))
@@ -819,7 +821,7 @@
     // chunk to the protocol parameter 'Association.Max.Retrans'. If this
     // threshold is exceeded, the endpoint should destroy the TCB..."
 
-    SendPacket(tcb_->PacketBuilder().Add(
+    packet_sender_.Send(tcb_->PacketBuilder().Add(
         AbortChunk(true, Parameters::Builder()
                              .Add(UserInitiatedAbortCause(
                                  "Too many retransmissions of SHUTDOWN"))
@@ -838,28 +840,27 @@
   return tcb_->current_rto();
 }
 
-void DcSctpSocket::SendPacket(SctpPacket::Builder& builder) {
-  if (builder.empty()) {
-    return;
-  }
-
-  std::vector<uint8_t> payload = builder.Build();
-
-  if (RTC_DLOG_IS_ON) {
-    DebugPrintOutgoing(payload);
-  }
-
-  // The heartbeat interval timer is restarted for every sent packet, to
-  // fire when the outgoing channel is inactive.
-  if (tcb_ != nullptr) {
-    tcb_->heartbeat_handler().RestartTimer();
-  }
-
+void DcSctpSocket::OnSentPacket(rtc::ArrayView<const uint8_t> packet,
+                                SendPacketStatus status) {
+  // The packet observer is invoked even if the packet was failed to be sent, to
+  // indicate an attempt was made.
   if (packet_observer_ != nullptr) {
-    packet_observer_->OnSentPacket(callbacks_.TimeMillis(), payload);
+    packet_observer_->OnSentPacket(callbacks_.TimeMillis(), packet);
   }
-  ++metrics_.tx_packets_count;
-  callbacks_.SendPacketWithStatus(payload);
+
+  if (status == SendPacketStatus::kSuccess) {
+    if (RTC_DLOG_IS_ON) {
+      DebugPrintOutgoing(packet);
+    }
+
+    // The heartbeat interval timer is restarted for every sent packet, to
+    // fire when the outgoing channel is inactive.
+    if (tcb_ != nullptr) {
+      tcb_->heartbeat_handler().RestartTimer();
+    }
+
+    ++metrics_.tx_packets_count;
+  }
 }
 
 bool DcSctpSocket::ValidateHasTCB() {
@@ -902,7 +903,7 @@
 
   if (data.payload.empty()) {
     // Empty DATA chunks are illegal.
-    SendPacket(tcb_->PacketBuilder().Add(
+    packet_sender_.Send(tcb_->PacketBuilder().Add(
         ErrorChunk(Parameters::Builder().Add(NoUserDataCause(tsn)).Build())));
     callbacks_.OnError(ErrorKind::kProtocolViolation,
                        "Received DATA chunk with no user data");
@@ -922,7 +923,7 @@
     // specification only allows dropping gap-ack-blocks, and that's not
     // likely to help as the socket has been trying to fill gaps since the
     // watermark was reached.
-    SendPacket(tcb_->PacketBuilder().Add(AbortChunk(
+    packet_sender_.Send(tcb_->PacketBuilder().Add(AbortChunk(
         true, Parameters::Builder().Add(OutOfResourceErrorCause()).Build())));
     InternalClose(ErrorKind::kResourceExhaustion,
                   "Reassembly Queue is exhausted");
@@ -975,12 +976,13 @@
     // "A receiver of an INIT with the MIS value of 0 SHOULD abort the
     // association."
 
-    SendPacket(SctpPacket::Builder(VerificationTag(0), options_)
-                   .Add(AbortChunk(
-                       /*filled_in_verification_tag=*/false,
-                       Parameters::Builder()
-                           .Add(ProtocolViolationCause("INIT malformed"))
-                           .Build())));
+    packet_sender_.Send(
+        SctpPacket::Builder(VerificationTag(0), options_)
+            .Add(AbortChunk(
+                /*filled_in_verification_tag=*/false,
+                Parameters::Builder()
+                    .Add(ProtocolViolationCause("INIT malformed"))
+                    .Build())));
     InternalClose(ErrorKind::kProtocolViolation, "Received invalid INIT");
     return;
   }
@@ -1069,7 +1071,7 @@
                         options_.announced_maximum_incoming_streams,
                         connect_params_.initial_tsn, params_builder.Build());
   b.Add(init_ack);
-  SendPacket(b);
+  packet_sender_.Send(b);
 }
 
 void DcSctpSocket::HandleInitAck(
@@ -1091,12 +1093,13 @@
 
   auto cookie = chunk->parameters().get<StateCookieParameter>();
   if (!cookie.has_value()) {
-    SendPacket(SctpPacket::Builder(connect_params_.verification_tag, options_)
-                   .Add(AbortChunk(
-                       /*filled_in_verification_tag=*/false,
-                       Parameters::Builder()
-                           .Add(ProtocolViolationCause("INIT-ACK malformed"))
-                           .Build())));
+    packet_sender_.Send(
+        SctpPacket::Builder(connect_params_.verification_tag, options_)
+            .Add(AbortChunk(
+                /*filled_in_verification_tag=*/false,
+                Parameters::Builder()
+                    .Add(ProtocolViolationCause("INIT-ACK malformed"))
+                    .Build())));
     InternalClose(ErrorKind::kProtocolViolation,
                   "InitAck chunk doesn't contain a cookie");
     return;
@@ -1108,9 +1111,8 @@
       timer_manager_, log_prefix_, options_, capabilities, callbacks_,
       send_queue_, connect_params_.verification_tag,
       connect_params_.initial_tsn, chunk->initiate_tag(), chunk->initial_tsn(),
-      chunk->a_rwnd(), MakeTieTag(callbacks_),
-      [this]() { return state_ == State::kEstablished; },
-      absl::bind_front(&DcSctpSocket::SendPacket, this));
+      chunk->a_rwnd(), MakeTieTag(callbacks_), packet_sender_,
+      [this]() { return state_ == State::kEstablished; });
   RTC_DLOG(LS_VERBOSE) << log_prefix()
                        << "Created peer TCB: " << tcb_->ToString();
 
@@ -1171,8 +1173,7 @@
         callbacks_, send_queue_, connect_params_.verification_tag,
         connect_params_.initial_tsn, cookie->initiate_tag(),
         cookie->initial_tsn(), cookie->a_rwnd(), MakeTieTag(callbacks_),
-        [this]() { return state_ == State::kEstablished; },
-        absl::bind_front(&DcSctpSocket::SendPacket, this));
+        packet_sender_, [this]() { return state_ == State::kEstablished; });
     RTC_DLOG(LS_VERBOSE) << log_prefix()
                          << "Created peer TCB: " << tcb_->ToString();
   }
@@ -1213,7 +1214,7 @@
       b.Add(ErrorChunk(Parameters::Builder()
                            .Add(CookieReceivedWhileShuttingDownCause())
                            .Build()));
-      SendPacket(b);
+      packet_sender_.Send(b);
       callbacks_.OnError(ErrorKind::kWrongSequence,
                          "Received COOKIE-ECHO while shutting down");
       return false;
@@ -1445,7 +1446,7 @@
 
     SctpPacket::Builder b = tcb_->PacketBuilder();
     b.Add(ShutdownCompleteChunk(/*tag_reflected=*/false));
-    SendPacket(b);
+    packet_sender_.Send(b);
     InternalClose(ErrorKind::kNoError, "");
   } else {
     // https://tools.ietf.org/html/rfc4960#section-8.5.1
@@ -1464,7 +1465,7 @@
 
     SctpPacket::Builder b(header.verification_tag, options_);
     b.Add(ShutdownCompleteChunk(/*tag_reflected=*/true));
-    SendPacket(b);
+    packet_sender_.Send(b);
   }
 }
 
@@ -1516,7 +1517,7 @@
                              "I-FORWARD-TSN received, but not indicated "
                              "during connection establishment"))
                          .Build()));
-    SendPacket(b);
+    packet_sender_.Send(b);
 
     callbacks_.OnError(ErrorKind::kProtocolViolation,
                        "Received a FORWARD_TSN without announced peer support");
@@ -1564,11 +1565,11 @@
 void DcSctpSocket::SendShutdown() {
   SctpPacket::Builder b = tcb_->PacketBuilder();
   b.Add(ShutdownChunk(tcb_->data_tracker().last_cumulative_acked_tsn()));
-  SendPacket(b);
+  packet_sender_.Send(b);
 }
 
 void DcSctpSocket::SendShutdownAck() {
-  SendPacket(tcb_->PacketBuilder().Add(ShutdownAckChunk()));
+  packet_sender_.Send(tcb_->PacketBuilder().Add(ShutdownAckChunk()));
   t2_shutdown_->set_duration(tcb_->current_rto());
   t2_shutdown_->Start();
 }
diff --git a/net/dcsctp/socket/dcsctp_socket.h b/net/dcsctp/socket/dcsctp_socket.h
index e0aae38..60359bd 100644
--- a/net/dcsctp/socket/dcsctp_socket.h
+++ b/net/dcsctp/socket/dcsctp_socket.h
@@ -46,6 +46,7 @@
 #include "net/dcsctp/rx/data_tracker.h"
 #include "net/dcsctp/rx/reassembly_queue.h"
 #include "net/dcsctp/socket/callback_deferrer.h"
+#include "net/dcsctp/socket/packet_sender.h"
 #include "net/dcsctp/socket/state_cookie.h"
 #include "net/dcsctp/socket/transmission_control_block.h"
 #include "net/dcsctp/timer/timer.h"
@@ -141,8 +142,8 @@
   absl::optional<DurationMs> OnInitTimerExpiry();
   absl::optional<DurationMs> OnCookieTimerExpiry();
   absl::optional<DurationMs> OnShutdownTimerExpiry();
-  // Builds the packet from `builder` and sends it (through callbacks).
-  void SendPacket(SctpPacket::Builder& builder);
+  void OnSentPacket(rtc::ArrayView<const uint8_t> packet,
+                    SendPacketStatus status);
   // Sends SHUTDOWN or SHUTDOWN-ACK if the socket is shutting down and if all
   // outstanding data has been acknowledged.
   void MaybeSendShutdownOrAck();
@@ -258,6 +259,9 @@
   const std::unique_ptr<Timer> t1_cookie_;
   const std::unique_ptr<Timer> t2_shutdown_;
 
+  // Packets that failed to be sent, but should be retried.
+  PacketSender packet_sender_;
+
   // The actual SendQueue implementation. As data can be sent on a socket before
   // the connection is established, this component is not in the TCB.
   RRSendQueue send_queue_;
diff --git a/net/dcsctp/socket/packet_sender.cc b/net/dcsctp/socket/packet_sender.cc
new file mode 100644
index 0000000..85392e2
--- /dev/null
+++ b/net/dcsctp/socket/packet_sender.cc
@@ -0,0 +1,48 @@
+/*
+ *  Copyright (c) 2021 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+#include "net/dcsctp/socket/packet_sender.h"
+
+#include <utility>
+#include <vector>
+
+#include "net/dcsctp/public/types.h"
+
+namespace dcsctp {
+
+PacketSender::PacketSender(DcSctpSocketCallbacks& callbacks,
+                           std::function<void(rtc::ArrayView<const uint8_t>,
+                                              SendPacketStatus)> on_sent_packet)
+    : callbacks_(callbacks), on_sent_packet_(std::move(on_sent_packet)) {}
+
+bool PacketSender::Send(SctpPacket::Builder& builder) {
+  if (builder.empty()) {
+    return false;
+  }
+
+  std::vector<uint8_t> payload = builder.Build();
+
+  SendPacketStatus status = callbacks_.SendPacketWithStatus(payload);
+  on_sent_packet_(payload, status);
+  switch (status) {
+    case SendPacketStatus::kSuccess: {
+      return true;
+    }
+    case SendPacketStatus::kTemporaryFailure: {
+      // TODO(boivie): Queue this packet to be retried to be sent later.
+      return false;
+    }
+
+    case SendPacketStatus::kError: {
+      // Nothing that can be done.
+      return false;
+    }
+  }
+}
+}  // namespace dcsctp
diff --git a/net/dcsctp/socket/packet_sender.h b/net/dcsctp/socket/packet_sender.h
new file mode 100644
index 0000000..7af4d3c
--- /dev/null
+++ b/net/dcsctp/socket/packet_sender.h
@@ -0,0 +1,40 @@
+/*
+ *  Copyright (c) 2021 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+#ifndef NET_DCSCTP_SOCKET_PACKET_SENDER_H_
+#define NET_DCSCTP_SOCKET_PACKET_SENDER_H_
+
+#include "net/dcsctp/packet/sctp_packet.h"
+#include "net/dcsctp/public/dcsctp_socket.h"
+
+namespace dcsctp {
+
+// The PacketSender sends packets to the network using the provided callback
+// interface. When an attempt to send a packet is made, the `on_sent_packet`
+// callback will be triggered.
+class PacketSender {
+ public:
+  PacketSender(DcSctpSocketCallbacks& callbacks,
+               std::function<void(rtc::ArrayView<const uint8_t>,
+                                  SendPacketStatus)> on_sent_packet);
+
+  // Sends the packet, and returns true if it was sent successfully.
+  bool Send(SctpPacket::Builder& builder);
+
+ private:
+  DcSctpSocketCallbacks& callbacks_;
+
+  // Callback that will be triggered for every send attempt, indicating the
+  // status of the operation.
+  std::function<void(rtc::ArrayView<const uint8_t>, SendPacketStatus)>
+      on_sent_packet_;
+};
+}  // namespace dcsctp
+
+#endif  // NET_DCSCTP_SOCKET_PACKET_SENDER_H_
diff --git a/net/dcsctp/socket/packet_sender_test.cc b/net/dcsctp/socket/packet_sender_test.cc
new file mode 100644
index 0000000..079dc36
--- /dev/null
+++ b/net/dcsctp/socket/packet_sender_test.cc
@@ -0,0 +1,50 @@
+/*
+ *  Copyright (c) 2021 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+#include "net/dcsctp/socket/packet_sender.h"
+
+#include "net/dcsctp/common/internal_types.h"
+#include "net/dcsctp/packet/chunk/cookie_ack_chunk.h"
+#include "net/dcsctp/socket/mock_dcsctp_socket_callbacks.h"
+#include "rtc_base/gunit.h"
+#include "test/gmock.h"
+
+namespace dcsctp {
+namespace {
+using ::testing::_;
+
+constexpr VerificationTag kVerificationTag(123);
+
+class PacketSenderTest : public testing::Test {
+ protected:
+  PacketSenderTest() : sender_(callbacks_, on_send_fn_.AsStdFunction()) {}
+
+  SctpPacket::Builder PacketBuilder() const {
+    return SctpPacket::Builder(kVerificationTag, options_);
+  }
+
+  DcSctpOptions options_;
+  testing::NiceMock<MockDcSctpSocketCallbacks> callbacks_;
+  testing::MockFunction<void(rtc::ArrayView<const uint8_t>, SendPacketStatus)>
+      on_send_fn_;
+  PacketSender sender_;
+};
+
+TEST_F(PacketSenderTest, SendPacketCallsCallback) {
+  EXPECT_CALL(on_send_fn_, Call(_, SendPacketStatus::kSuccess));
+  EXPECT_TRUE(sender_.Send(PacketBuilder().Add(CookieAckChunk())));
+
+  EXPECT_CALL(callbacks_, SendPacketWithStatus)
+      .WillOnce(testing::Return(SendPacketStatus::kError));
+  EXPECT_CALL(on_send_fn_, Call(_, SendPacketStatus::kError));
+  EXPECT_FALSE(sender_.Send(PacketBuilder().Add(CookieAckChunk())));
+}
+
+}  // namespace
+}  // namespace dcsctp
diff --git a/net/dcsctp/socket/transmission_control_block.cc b/net/dcsctp/socket/transmission_control_block.cc
index 167534d..9ec275f 100644
--- a/net/dcsctp/socket/transmission_control_block.cc
+++ b/net/dcsctp/socket/transmission_control_block.cc
@@ -131,10 +131,10 @@
         builder.Add(DataChunk(tsn, std::move(data), false));
       }
     }
-    if (builder.empty()) {
+
+    if (!packet_sender_.Send(builder)) {
       break;
     }
-    Send(builder);
 
     if (cookie_echo_chunk_.has_value()) {
       // https://tools.ietf.org/html/rfc4960#section-5.1
diff --git a/net/dcsctp/socket/transmission_control_block.h b/net/dcsctp/socket/transmission_control_block.h
index e846451..b189ae8 100644
--- a/net/dcsctp/socket/transmission_control_block.h
+++ b/net/dcsctp/socket/transmission_control_block.h
@@ -29,6 +29,7 @@
 #include "net/dcsctp/socket/capabilities.h"
 #include "net/dcsctp/socket/context.h"
 #include "net/dcsctp/socket/heartbeat_handler.h"
+#include "net/dcsctp/socket/packet_sender.h"
 #include "net/dcsctp/socket/stream_reset_handler.h"
 #include "net/dcsctp/timer/timer.h"
 #include "net/dcsctp/tx/retransmission_error_counter.h"
@@ -55,8 +56,8 @@
                            TSN peer_initial_tsn,
                            size_t a_rwnd,
                            TieTag tie_tag,
-                           std::function<bool()> is_connection_established,
-                           std::function<void(SctpPacket::Builder&)> send_fn)
+                           PacketSender& packet_sender,
+                           std::function<bool()> is_connection_established)
       : log_prefix_(log_prefix),
         options_(options),
         timer_manager_(timer_manager),
@@ -79,7 +80,7 @@
         peer_initial_tsn_(peer_initial_tsn),
         tie_tag_(tie_tag),
         is_connection_established_(std::move(is_connection_established)),
-        send_fn_(std::move(send_fn)),
+        packet_sender_(packet_sender),
         rto_(options),
         tx_error_counter_(log_prefix, options),
         data_tracker_(log_prefix, delayed_ack_timer_.get(), peer_initial_tsn),
@@ -124,7 +125,9 @@
   bool HasTooManyTxErrors() const override {
     return tx_error_counter_.IsExhausted();
   }
-  void Send(SctpPacket::Builder& builder) override { send_fn_(builder); }
+  void Send(SctpPacket::Builder& builder) override {
+    packet_sender_.Send(builder);
+  }
 
   // Other accessors
   DataTracker& data_tracker() { return data_tracker_; }
@@ -202,7 +205,7 @@
   // Nonce, used to detect reconnections.
   const TieTag tie_tag_;
   const std::function<bool()> is_connection_established_;
-  const std::function<void(SctpPacket::Builder&)> send_fn_;
+  PacketSender& packet_sender_;
 
   RetransmissionTimeout rto_;
   RetransmissionErrorCounter tx_error_counter_;