dcsctp: Add Data Tracker

The Data Tracker's purpose is to keep track of all received DATA chunks
and to ACK/NACK that data, by generating SACK chunks reflecting its view
of what has been received and what has been lost.

It also contains logic for _when_ to send the SACKs, as that's different
depending on e.g. packet loss. Generally, SACKs are sent every second
packet on a connection with no packet loss, and can also be sent on a
delayed timer.

In case partial reliability is used, and the transmitter has decided
that some data shouldn't be retransmitted, it will send a FORWARD-TSN
chunk, which this class also handles, by "forgetting" about those
chunks.

Bug: webrtc:12614
Change-Id: Ifafb0c211f6a47872e81830165ab5fc43ee7f366
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/213664
Commit-Queue: Victor Boivie <boivie@webrtc.org>
Reviewed-by: Tommi <tommi@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#33676}
diff --git a/net/dcsctp/BUILD.gn b/net/dcsctp/BUILD.gn
index ff93f7e..9f7f541 100644
--- a/net/dcsctp/BUILD.gn
+++ b/net/dcsctp/BUILD.gn
@@ -16,6 +16,7 @@
       "common:dcsctp_common_unittests",
       "packet:dcsctp_packet_unittests",
       "public:dcsctp_public_unittests",
+      "rx:dcsctp_rx_unittests",
       "timer:dcsctp_timer_unittests",
     ]
   }
diff --git a/net/dcsctp/rx/BUILD.gn b/net/dcsctp/rx/BUILD.gn
new file mode 100644
index 0000000..224c546
--- /dev/null
+++ b/net/dcsctp/rx/BUILD.gn
@@ -0,0 +1,38 @@
+# 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.
+
+import("../../../webrtc.gni")
+
+rtc_library("data_tracker") {
+  deps = [
+    "../../../api:array_view",
+    "../../../rtc_base",
+    "../../../rtc_base:checks",
+    "../../../rtc_base:rtc_base_approved",
+  ]
+  sources = [
+    "data_tracker.cc",
+    "data_tracker.h",
+  ]
+}
+
+if (rtc_include_tests) {
+  rtc_library("dcsctp_rx_unittests") {
+    testonly = true
+
+    deps = [
+      ":data_tracker",
+      "../../../api:array_view",
+      "../../../rtc_base:checks",
+      "../../../rtc_base:gunit_helpers",
+      "../../../rtc_base:rtc_base_approved",
+      "../../../test:test_support",
+    ]
+    sources = [ "data_tracker_test.cc" ]
+  }
+}
diff --git a/net/dcsctp/rx/data_tracker.cc b/net/dcsctp/rx/data_tracker.cc
new file mode 100644
index 0000000..9e5cbe2
--- /dev/null
+++ b/net/dcsctp/rx/data_tracker.cc
@@ -0,0 +1,264 @@
+/*
+ *  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/rx/data_tracker.h"
+
+#include <cstdint>
+#include <iterator>
+#include <set>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "absl/strings/string_view.h"
+#include "absl/types/optional.h"
+#include "net/dcsctp/common/sequence_numbers.h"
+#include "net/dcsctp/packet/chunk/sack_chunk.h"
+#include "net/dcsctp/timer/timer.h"
+#include "rtc_base/logging.h"
+#include "rtc_base/strings/string_builder.h"
+
+namespace dcsctp {
+
+void DataTracker::Observe(TSN tsn,
+                          AnyDataChunk::ImmediateAckFlag immediate_ack) {
+  UnwrappedTSN unwrapped_tsn = tsn_unwrapper_.Unwrap(tsn);
+  // Old chunk already seen before?
+  if (unwrapped_tsn <= last_cumulative_acked_tsn_) {
+    // TODO(boivie) Set duplicate TSN, even if it's not used in SCTP yet.
+    return;
+  }
+
+  if (unwrapped_tsn == last_cumulative_acked_tsn_.next_value()) {
+    last_cumulative_acked_tsn_ = unwrapped_tsn;
+    // The cumulative acked tsn may be moved even further, if a gap was filled.
+    while (!additional_tsns.empty() &&
+           *additional_tsns.begin() ==
+               last_cumulative_acked_tsn_.next_value()) {
+      last_cumulative_acked_tsn_.Increment();
+      additional_tsns.erase(additional_tsns.begin());
+    }
+  } else {
+    additional_tsns.insert(unwrapped_tsn);
+  }
+
+  // https://tools.ietf.org/html/rfc4960#section-6.7
+  // "Upon the reception of a new DATA chunk, an endpoint shall examine the
+  // continuity of the TSNs received.  If the endpoint detects a gap in
+  // the received DATA chunk sequence, it SHOULD send a SACK with Gap Ack
+  // Blocks immediately.  The data receiver continues sending a SACK after
+  // receipt of each SCTP packet that doesn't fill the gap."
+  if (!additional_tsns.empty()) {
+    UpdateAckState(AckState::kImmediate, "packet loss");
+  }
+
+  // https://tools.ietf.org/html/rfc7053#section-5.2
+  // "Upon receipt of an SCTP packet containing a DATA chunk with the I
+  // bit set, the receiver SHOULD NOT delay the sending of the corresponding
+  // SACK chunk, i.e., the receiver SHOULD immediately respond with the
+  // corresponding SACK chunk."
+  if (*immediate_ack) {
+    UpdateAckState(AckState::kImmediate, "immediate-ack bit set");
+  }
+
+  if (!seen_packet_) {
+    // https://tools.ietf.org/html/rfc4960#section-5.1
+    // "After the reception of the first DATA chunk in an association the
+    // endpoint MUST immediately respond with a SACK to acknowledge the DATA
+    // chunk."
+    seen_packet_ = true;
+    UpdateAckState(AckState::kImmediate, "first DATA chunk");
+  }
+
+  // https://tools.ietf.org/html/rfc4960#section-6.2
+  // "Specifically, an acknowledgement SHOULD be generated for at least
+  // every second packet (not every second DATA chunk) received, and SHOULD be
+  // generated within 200 ms of the arrival of any unacknowledged DATA chunk."
+  if (ack_state_ == AckState::kIdle) {
+    UpdateAckState(AckState::kBecomingDelayed, "received DATA when idle");
+  } else if (ack_state_ == AckState::kDelayed) {
+    UpdateAckState(AckState::kImmediate, "received DATA when already delayed");
+  }
+}
+
+void DataTracker::HandleForwardTsn(TSN new_cumulative_ack) {
+  // ForwardTSN is sent to make the receiver (this socket) "forget" about partly
+  // received (or not received at all) data, up until `new_cumulative_ack`.
+
+  UnwrappedTSN unwrapped_tsn = tsn_unwrapper_.Unwrap(new_cumulative_ack);
+  UnwrappedTSN prev_last_cum_ack_tsn = last_cumulative_acked_tsn_;
+
+  // Old chunk already seen before?
+  if (unwrapped_tsn <= last_cumulative_acked_tsn_) {
+    // https://tools.ietf.org/html/rfc3758#section-3.6
+    // "Note, if the "New Cumulative TSN" value carried in the arrived
+    // FORWARD TSN chunk is found to be behind or at the current cumulative TSN
+    // point, the data receiver MUST treat this FORWARD TSN as out-of-date and
+    // MUST NOT update its Cumulative TSN.  The receiver SHOULD send a SACK to
+    // its peer (the sender of the FORWARD TSN) since such a duplicate may
+    // indicate the previous SACK was lost in the network."
+    UpdateAckState(AckState::kImmediate,
+                   "FORWARD_TSN new_cumulative_tsn was behind");
+    return;
+  }
+
+  // https://tools.ietf.org/html/rfc3758#section-3.6
+  // "When a FORWARD TSN chunk arrives, the data receiver MUST first update
+  // its cumulative TSN point to the value carried in the FORWARD TSN chunk, and
+  // then MUST further advance its cumulative TSN point locally if possible, as
+  // shown by the following example..."
+
+  // The `new_cumulative_ack` will become the current
+  // `last_cumulative_acked_tsn_`, and if there have been prior "gaps" that are
+  // now overlapping with the new value, remove them.
+  last_cumulative_acked_tsn_ = unwrapped_tsn;
+  int erased_additional_tsns = std::distance(
+      additional_tsns.begin(), additional_tsns.upper_bound(unwrapped_tsn));
+  additional_tsns.erase(additional_tsns.begin(),
+                        additional_tsns.upper_bound(unwrapped_tsn));
+
+  // See if the `last_cumulative_acked_tsn_` can be moved even further:
+  while (!additional_tsns.empty() &&
+         *additional_tsns.begin() == last_cumulative_acked_tsn_.next_value()) {
+    last_cumulative_acked_tsn_.Increment();
+    additional_tsns.erase(additional_tsns.begin());
+    ++erased_additional_tsns;
+  }
+
+  RTC_DLOG(LS_VERBOSE) << log_prefix_ << "FORWARD_TSN, cum_ack_tsn="
+                       << *prev_last_cum_ack_tsn.Wrap() << "->"
+                       << *new_cumulative_ack << "->"
+                       << *last_cumulative_acked_tsn_.Wrap() << ", removed "
+                       << erased_additional_tsns << " additional TSNs";
+
+  // https://tools.ietf.org/html/rfc3758#section-3.6
+  // "Any time a FORWARD TSN chunk arrives, for the purposes of sending a
+  // SACK, the receiver MUST follow the same rules as if a DATA chunk had been
+  // received (i.e., follow the delayed sack rules specified in ..."
+  if (ack_state_ == AckState::kIdle) {
+    UpdateAckState(AckState::kBecomingDelayed,
+                   "received FORWARD_TSN when idle");
+  } else if (ack_state_ == AckState::kDelayed) {
+    UpdateAckState(AckState::kImmediate,
+                   "received FORWARD_TSN when already delayed");
+  }
+}
+
+SackChunk DataTracker::CreateSelectiveAck(size_t a_rwnd) {
+  // Note that in SCTP, the receiver side is allowed to discard received data
+  // and signal that to the sender, but only chunks that have previously been
+  // reported in the gap-ack-blocks. However, this implementation will never do
+  // that. So this SACK produced is more like a NR-SACK as explained in
+  // https://ieeexplore.ieee.org/document/4697037 and which there is an RFC
+  // draft at https://tools.ietf.org/html/draft-tuexen-tsvwg-sctp-multipath-17.
+  std::vector<TSN> duplicate_tsns;
+  duplicate_tsns.reserve(duplicates_.size());
+  for (UnwrappedTSN tsn : duplicates_) {
+    duplicate_tsns.push_back(tsn.Wrap());
+  }
+  duplicates_.clear();
+
+  return SackChunk(last_cumulative_acked_tsn_.Wrap(), a_rwnd,
+                   CreateGapAckBlocks(), duplicate_tsns);
+}
+
+std::vector<SackChunk::GapAckBlock> DataTracker::CreateGapAckBlocks() const {
+  // This method will calculate the gaps between blocks of contiguous values in
+  // `additional_tsns_`, in the same format as the SACK chunk expects it;
+  // offsets from the "cumulative ack TSN value".
+  std::vector<SackChunk::GapAckBlock> gap_ack_blocks;
+
+  absl::optional<UnwrappedTSN> first_tsn_in_block = absl::nullopt;
+  absl::optional<UnwrappedTSN> last_tsn_in_block = absl::nullopt;
+
+  auto flush = [&]() {
+    if (first_tsn_in_block.has_value()) {
+      int start_diff =
+          first_tsn_in_block->Difference(last_cumulative_acked_tsn_);
+      int end_diff = last_tsn_in_block->Difference(last_cumulative_acked_tsn_);
+      gap_ack_blocks.emplace_back(static_cast<uint16_t>(start_diff),
+                                  static_cast<uint16_t>(end_diff));
+      first_tsn_in_block = absl::nullopt;
+      last_tsn_in_block = absl::nullopt;
+    }
+  };
+  for (UnwrappedTSN tsn : additional_tsns) {
+    if (last_tsn_in_block.has_value() &&
+        last_tsn_in_block->next_value() == tsn) {
+      // Continuing the same block.
+      last_tsn_in_block = tsn;
+    } else {
+      // New block, or a gap from the old block's last value.
+      flush();
+      first_tsn_in_block = tsn;
+      last_tsn_in_block = tsn;
+    }
+  }
+  flush();
+  return gap_ack_blocks;
+}
+
+bool DataTracker::ShouldSendAck(bool also_if_delayed) {
+  if (ack_state_ == AckState::kImmediate ||
+      (also_if_delayed && (ack_state_ == AckState::kBecomingDelayed ||
+                           ack_state_ == AckState::kDelayed))) {
+    UpdateAckState(AckState::kIdle, "sending SACK");
+    return true;
+  }
+
+  return false;
+}
+
+bool DataTracker::will_increase_cum_ack_tsn(TSN tsn) const {
+  UnwrappedTSN unwrapped = tsn_unwrapper_.PeekUnwrap(tsn);
+  return unwrapped == last_cumulative_acked_tsn_.next_value();
+}
+
+void DataTracker::ForceImmediateSack() {
+  ack_state_ = AckState::kImmediate;
+}
+
+void DataTracker::HandleDelayedAckTimerExpiry() {
+  UpdateAckState(AckState::kImmediate, "delayed ack timer expired");
+}
+
+void DataTracker::ObservePacketEnd() {
+  if (ack_state_ == AckState::kBecomingDelayed) {
+    UpdateAckState(AckState::kDelayed, "packet end");
+  }
+}
+
+void DataTracker::UpdateAckState(AckState new_state, absl::string_view reason) {
+  if (new_state != ack_state_) {
+    RTC_DLOG(LS_VERBOSE) << log_prefix_ << "State changed from "
+                         << ToString(ack_state_) << " to "
+                         << ToString(new_state) << " due to " << reason;
+    if (ack_state_ == AckState::kDelayed) {
+      delayed_ack_timer_.Stop();
+    } else if (new_state == AckState::kDelayed) {
+      delayed_ack_timer_.Start();
+    }
+    ack_state_ = new_state;
+  }
+}
+
+absl::string_view DataTracker::ToString(AckState ack_state) {
+  switch (ack_state) {
+    case AckState::kIdle:
+      return "IDLE";
+    case AckState::kBecomingDelayed:
+      return "BECOMING_DELAYED";
+    case AckState::kDelayed:
+      return "DELAYED";
+    case AckState::kImmediate:
+      return "IMMEDIATE";
+  }
+}
+
+}  // namespace dcsctp
diff --git a/net/dcsctp/rx/data_tracker.h b/net/dcsctp/rx/data_tracker.h
new file mode 100644
index 0000000..a528967
--- /dev/null
+++ b/net/dcsctp/rx/data_tracker.h
@@ -0,0 +1,121 @@
+/*
+ *  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_RX_DATA_TRACKER_H_
+#define NET_DCSCTP_RX_DATA_TRACKER_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <cstdint>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "absl/strings/string_view.h"
+#include "net/dcsctp/common/sequence_numbers.h"
+#include "net/dcsctp/packet/chunk/data_common.h"
+#include "net/dcsctp/packet/chunk/sack_chunk.h"
+#include "net/dcsctp/packet/data.h"
+#include "net/dcsctp/timer/timer.h"
+
+namespace dcsctp {
+
+// Keeps track of received DATA chunks and handles all logic for _when_ to
+// create SACKs and also _how_ to generate them.
+//
+// It only uses TSNs to track delivery and doesn't need to be aware of streams.
+//
+// SACKs are optimally sent every second packet on connections with no packet
+// loss. When packet loss is detected, it's sent for every packet. When SACKs
+// are not sent directly, a timer is used to send a SACK delayed (by RTO/2, or
+// 200ms, whatever is smallest).
+class DataTracker {
+ public:
+  explicit DataTracker(absl::string_view log_prefix,
+                       Timer* delayed_ack_timer,
+                       TSN peer_initial_tsn)
+      : log_prefix_(std::string(log_prefix) + "dtrack: "),
+        delayed_ack_timer_(*delayed_ack_timer),
+        last_cumulative_acked_tsn_(
+            tsn_unwrapper_.Unwrap(TSN(*peer_initial_tsn - 1))) {}
+
+  // Call for every incoming data chunk.
+  void Observe(TSN tsn,
+               AnyDataChunk::ImmediateAckFlag immediate_ack =
+                   AnyDataChunk::ImmediateAckFlag(false));
+  // Called at the end of processing an SCTP packet.
+  void ObservePacketEnd();
+
+  // Called for incoming FORWARD-TSN/I-FORWARD-TSN chunks
+  void HandleForwardTsn(TSN new_cumulative_ack);
+
+  // Indicates if a SACK should be sent. There may be other reasons to send a
+  // SACK, but if this function indicates so, it should be sent as soon as
+  // possible. Calling this function will make it clear a flag so that if it's
+  // called again, it will probably return false.
+  //
+  // If the delayed ack timer is running, this method will return false _unless_
+  // `also_if_delayed` is set to true. Then it will return true as well.
+  bool ShouldSendAck(bool also_if_delayed = false);
+
+  // Returns the last cumulative ack TSN - the last seen data chunk's TSN
+  // value before any packet loss was detected.
+  TSN last_cumulative_acked_tsn() const {
+    return TSN(last_cumulative_acked_tsn_.Wrap());
+  }
+
+  // Returns true if the received `tsn` would increase the cumulative ack TSN.
+  bool will_increase_cum_ack_tsn(TSN tsn) const;
+
+  // Forces `ShouldSendSack` to return true.
+  void ForceImmediateSack();
+
+  // Note that this will clear `duplicates_`, so every SackChunk that is
+  // consumed must be sent.
+  SackChunk CreateSelectiveAck(size_t a_rwnd);
+
+  void HandleDelayedAckTimerExpiry();
+
+ private:
+  enum class AckState {
+    // No need to send an ACK.
+    kIdle,
+
+    // Has received data chunks (but not yet end of packet).
+    kBecomingDelayed,
+
+    // Has received data chunks and the end of a packet. Delayed ack timer is
+    // running and a SACK will be sent on expiry, or if DATA is sent, or after
+    // next packet with data.
+    kDelayed,
+
+    // Send a SACK immediately after handling this packet.
+    kImmediate,
+  };
+  std::vector<SackChunk::GapAckBlock> CreateGapAckBlocks() const;
+  void UpdateAckState(AckState new_state, absl::string_view reason);
+  static absl::string_view ToString(AckState ack_state);
+
+  const std::string log_prefix_;
+  // If a packet has ever been seen.
+  bool seen_packet_ = false;
+  Timer& delayed_ack_timer_;
+  AckState ack_state_ = AckState::kIdle;
+  UnwrappedTSN::Unwrapper tsn_unwrapper_;
+
+  // All TSNs up until (and including) this value have been seen.
+  UnwrappedTSN last_cumulative_acked_tsn_;
+  // Received TSNs that are not directly following `last_cumulative_acked_tsn_`.
+  std::set<UnwrappedTSN> additional_tsns;
+  std::set<UnwrappedTSN> duplicates_;
+};
+}  // namespace dcsctp
+
+#endif  // NET_DCSCTP_RX_DATA_TRACKER_H_
diff --git a/net/dcsctp/rx/data_tracker_test.cc b/net/dcsctp/rx/data_tracker_test.cc
new file mode 100644
index 0000000..1bad807
--- /dev/null
+++ b/net/dcsctp/rx/data_tracker_test.cc
@@ -0,0 +1,209 @@
+/*
+ *  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/rx/data_tracker.h"
+
+#include <cstdint>
+#include <initializer_list>
+#include <memory>
+
+#include "absl/types/optional.h"
+#include "api/array_view.h"
+#include "net/dcsctp/packet/chunk/sack_chunk.h"
+#include "net/dcsctp/timer/fake_timeout.h"
+#include "net/dcsctp/timer/timer.h"
+#include "rtc_base/gunit.h"
+#include "test/gmock.h"
+
+namespace dcsctp {
+namespace {
+using ::testing::ElementsAre;
+using ::testing::IsEmpty;
+
+constexpr size_t kArwnd = 10000;
+
+class DataTrackerTest : public testing::Test {
+ protected:
+  DataTrackerTest()
+      : timeout_manager_([this]() { return now_; }),
+        timer_manager_([this]() { return timeout_manager_.CreateTimeout(); }),
+        timer_(timer_manager_.CreateTimer(
+            "test/delayed_ack",
+            []() { return absl::nullopt; },
+            TimerOptions(DurationMs(0)))),
+        buf_("log: ", timer_.get(), /*peer_initial_tsn=*/TSN(11)) {}
+
+  void Observer(std::initializer_list<uint32_t> tsns) {
+    for (const uint32_t tsn : tsns) {
+      buf_.Observe(TSN(tsn), AnyDataChunk::ImmediateAckFlag(false));
+    }
+  }
+
+  TimeMs now_ = TimeMs(0);
+  FakeTimeoutManager timeout_manager_;
+  TimerManager timer_manager_;
+  std::unique_ptr<Timer> timer_;
+  DataTracker buf_;
+};
+
+TEST_F(DataTrackerTest, Empty) {
+  SackChunk sack = buf_.CreateSelectiveAck(kArwnd);
+  EXPECT_EQ(sack.cumulative_tsn_ack(), TSN(10));
+  EXPECT_THAT(sack.gap_ack_blocks(), IsEmpty());
+  EXPECT_THAT(sack.duplicate_tsns(), IsEmpty());
+}
+
+TEST_F(DataTrackerTest, ObserverSingleInOrderPacket) {
+  Observer({11});
+  SackChunk sack = buf_.CreateSelectiveAck(kArwnd);
+  EXPECT_EQ(sack.cumulative_tsn_ack(), TSN(11));
+  EXPECT_THAT(sack.gap_ack_blocks(), IsEmpty());
+  EXPECT_THAT(sack.duplicate_tsns(), IsEmpty());
+}
+
+TEST_F(DataTrackerTest, ObserverManyInOrderMovesCumulativeTsnAck) {
+  Observer({11, 12, 13});
+  SackChunk sack = buf_.CreateSelectiveAck(kArwnd);
+  EXPECT_EQ(sack.cumulative_tsn_ack(), TSN(13));
+  EXPECT_THAT(sack.gap_ack_blocks(), IsEmpty());
+  EXPECT_THAT(sack.duplicate_tsns(), IsEmpty());
+}
+
+TEST_F(DataTrackerTest, ObserveOutOfOrderMovesCumulativeTsnAck) {
+  Observer({12, 13, 14, 11});
+  SackChunk sack = buf_.CreateSelectiveAck(kArwnd);
+  EXPECT_EQ(sack.cumulative_tsn_ack(), TSN(14));
+  EXPECT_THAT(sack.gap_ack_blocks(), IsEmpty());
+  EXPECT_THAT(sack.duplicate_tsns(), IsEmpty());
+}
+
+TEST_F(DataTrackerTest, SingleGap) {
+  Observer({12});
+  SackChunk sack = buf_.CreateSelectiveAck(kArwnd);
+  EXPECT_EQ(sack.cumulative_tsn_ack(), TSN(10));
+  EXPECT_THAT(sack.gap_ack_blocks(), ElementsAre(SackChunk::GapAckBlock(2, 2)));
+  EXPECT_THAT(sack.duplicate_tsns(), IsEmpty());
+}
+
+TEST_F(DataTrackerTest, ExampleFromRFC4960Section334) {
+  Observer({11, 12, 14, 15, 17});
+  SackChunk sack = buf_.CreateSelectiveAck(kArwnd);
+  EXPECT_EQ(sack.cumulative_tsn_ack(), TSN(12));
+  EXPECT_THAT(sack.gap_ack_blocks(), ElementsAre(SackChunk::GapAckBlock(2, 3),
+                                                 SackChunk::GapAckBlock(5, 5)));
+  EXPECT_THAT(sack.duplicate_tsns(), IsEmpty());
+}
+
+TEST_F(DataTrackerTest, AckAlreadyReceivedChunk) {
+  Observer({11});
+  SackChunk sack1 = buf_.CreateSelectiveAck(kArwnd);
+  EXPECT_EQ(sack1.cumulative_tsn_ack(), TSN(11));
+  EXPECT_THAT(sack1.gap_ack_blocks(), IsEmpty());
+
+  // Receive old chunk
+  Observer({8});
+  SackChunk sack2 = buf_.CreateSelectiveAck(kArwnd);
+  EXPECT_EQ(sack2.cumulative_tsn_ack(), TSN(11));
+  EXPECT_THAT(sack2.gap_ack_blocks(), IsEmpty());
+}
+
+TEST_F(DataTrackerTest, DoubleSendRetransmittedChunk) {
+  Observer({11, 13, 14, 15});
+  SackChunk sack1 = buf_.CreateSelectiveAck(kArwnd);
+  EXPECT_EQ(sack1.cumulative_tsn_ack(), TSN(11));
+  EXPECT_THAT(sack1.gap_ack_blocks(),
+              ElementsAre(SackChunk::GapAckBlock(2, 4)));
+
+  // Fill in the hole.
+  Observer({12, 16, 17, 18});
+  SackChunk sack2 = buf_.CreateSelectiveAck(kArwnd);
+  EXPECT_EQ(sack2.cumulative_tsn_ack(), TSN(18));
+  EXPECT_THAT(sack2.gap_ack_blocks(), IsEmpty());
+
+  // Receive chunk 12 again.
+  Observer({12, 19, 20, 21});
+  SackChunk sack3 = buf_.CreateSelectiveAck(kArwnd);
+  EXPECT_EQ(sack3.cumulative_tsn_ack(), TSN(21));
+  EXPECT_THAT(sack3.gap_ack_blocks(), IsEmpty());
+}
+
+TEST_F(DataTrackerTest, ForwardTsnSimple) {
+  // Messages (11, 12, 13), (14, 15) - first message expires.
+  Observer({11, 12, 15});
+
+  buf_.HandleForwardTsn(TSN(13));
+
+  SackChunk sack = buf_.CreateSelectiveAck(kArwnd);
+  EXPECT_EQ(sack.cumulative_tsn_ack(), TSN(13));
+  EXPECT_THAT(sack.gap_ack_blocks(), ElementsAre(SackChunk::GapAckBlock(2, 2)));
+}
+
+TEST_F(DataTrackerTest, ForwardTsnSkipsFromGapBlock) {
+  // Messages (11, 12, 13), (14, 15) - first message expires.
+  Observer({11, 12, 14});
+
+  buf_.HandleForwardTsn(TSN(13));
+
+  SackChunk sack = buf_.CreateSelectiveAck(kArwnd);
+  EXPECT_EQ(sack.cumulative_tsn_ack(), TSN(14));
+  EXPECT_THAT(sack.gap_ack_blocks(), IsEmpty());
+}
+
+TEST_F(DataTrackerTest, ExampleFromRFC3758) {
+  buf_.HandleForwardTsn(TSN(102));
+
+  Observer({102, 104, 105, 107});
+
+  buf_.HandleForwardTsn(TSN(103));
+
+  SackChunk sack = buf_.CreateSelectiveAck(kArwnd);
+  EXPECT_EQ(sack.cumulative_tsn_ack(), TSN(105));
+  EXPECT_THAT(sack.gap_ack_blocks(), ElementsAre(SackChunk::GapAckBlock(2, 2)));
+}
+
+TEST_F(DataTrackerTest, EmptyAllAcks) {
+  Observer({11, 13, 14, 15});
+
+  buf_.HandleForwardTsn(TSN(100));
+
+  SackChunk sack = buf_.CreateSelectiveAck(kArwnd);
+  EXPECT_EQ(sack.cumulative_tsn_ack(), TSN(100));
+  EXPECT_THAT(sack.gap_ack_blocks(), IsEmpty());
+}
+
+TEST_F(DataTrackerTest, SetsArwndCorrectly) {
+  SackChunk sack1 = buf_.CreateSelectiveAck(/*a_rwnd=*/100);
+  EXPECT_EQ(sack1.a_rwnd(), 100u);
+
+  SackChunk sack2 = buf_.CreateSelectiveAck(/*a_rwnd=*/101);
+  EXPECT_EQ(sack2.a_rwnd(), 101u);
+}
+
+TEST_F(DataTrackerTest, WillIncreaseCumAckTsn) {
+  EXPECT_EQ(buf_.last_cumulative_acked_tsn(), TSN(10));
+  EXPECT_FALSE(buf_.will_increase_cum_ack_tsn(TSN(10)));
+  EXPECT_TRUE(buf_.will_increase_cum_ack_tsn(TSN(11)));
+  EXPECT_FALSE(buf_.will_increase_cum_ack_tsn(TSN(12)));
+
+  Observer({11, 12, 13, 14, 15});
+  EXPECT_EQ(buf_.last_cumulative_acked_tsn(), TSN(15));
+  EXPECT_FALSE(buf_.will_increase_cum_ack_tsn(TSN(15)));
+  EXPECT_TRUE(buf_.will_increase_cum_ack_tsn(TSN(16)));
+  EXPECT_FALSE(buf_.will_increase_cum_ack_tsn(TSN(17)));
+}
+
+TEST_F(DataTrackerTest, ForceShouldSendSackImmediately) {
+  EXPECT_FALSE(buf_.ShouldSendAck());
+
+  buf_.ForceImmediateSack();
+
+  EXPECT_TRUE(buf_.ShouldSendAck());
+}
+}  // namespace
+}  // namespace dcsctp