Created and integrated QuicIntervalDeque class for index management. Improves code readability.

gfe-relnote: Integrated QuicIntervalDeque class into QuicStreamSendBuffer affecting the QUIC GFE path. Protected by --gfe2_reloadable_flag_quic_interval_deque
PiperOrigin-RevId: 286471930
Change-Id: Ida91b6c7d066d9710d06932c9f4726946bfbd430
diff --git a/quic/core/quic_interval_deque.h b/quic/core/quic_interval_deque.h
new file mode 100644
index 0000000..ed77d68
--- /dev/null
+++ b/quic/core/quic_interval_deque.h
@@ -0,0 +1,389 @@
+// Copyright (c) 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_QUIC_CORE_QUIC_SEEKER_H_
+#define QUICHE_QUIC_CORE_QUIC_SEEKER_H_
+
+#include <algorithm>
+#include "net/third_party/quiche/src/quic/core/quic_interval.h"
+#include "net/third_party/quiche/src/quic/core/quic_types.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_bug_tracker.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_containers.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_logging.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_optional.h"
+
+namespace quic {
+
+namespace test {
+class QuicIntervalDequePeer;
+}  // namespace test
+
+// QuicIntervalDeque<T, C> is a templated wrapper container, wrapping random
+// access data structures. The wrapper allows items to be added to the
+// underlying container with intervals associated with each item. The intervals
+// _should_ be added already sorted and represent searchable indices. The search
+// is optimized for sequential usage.
+//
+// As the intervals are to be searched sequentially the search for the next
+// interval can be achieved in O(1), by simply remembering the last interval
+// consumed. The structure also checks for an "off-by-one" use case wherein the
+// |cached_index_| is off by one index as the caller didn't call operator |++|
+// to increment the index. Other intervals can be found in O(log(n)) as they are
+// binary searchable. A use case for this structure is packet buffering: Packets
+// are sent sequentially but can sometimes needed for retransmission. The
+// packets and their payloads are added with associated intervals representing
+// data ranges they carry. When a user asks for a particular interval it's very
+// likely they are requesting the next sequential interval, receiving it in O(1)
+// time. Updating the sequential index is done automatically through the
+// |DataAt| method and its iterator operator |++|.
+//
+// The intervals are represented using the usual C++ STL convention, namely as
+// the half-open QuicInterval [min, max). A point p is considered to be
+// contained in the QuicInterval iff p >= min && p < max. One consequence of
+// this definition is that for any non-empty QuicInterval, min is contained in
+// the QuicInterval but max is not. There is no canonical representation for the
+// empty QuicInterval; and empty intervals are forbidden from being added to
+// this container as they would be unsearchable.
+//
+// The type T is required to be copy-constructable or move-constructable. The
+// type T is also expected to have an |interval()| method returning a
+// QuicInterval<std::size> for the particular value. The type C is required to
+// be a random access container supporting the methods |pop_front|, |push_back|,
+// |operator[]|, |size|, and iterator support for |std::lower_bound| eg. a
+// |deque| or |vector|.
+//
+// The QuicIntervalDeque<T, C>, like other C++ STL random access containers,
+// doesn't have any explicit support for any equality operators.
+//
+//
+// Examples with internal state:
+//
+//   // An example class to be stored inside the Interval Deque.
+//   struct IntervalVal {
+//     const int32_t val;
+//     const size_t interval_begin, interval_end;
+//     QuicInterval<size_t> interval();
+//   };
+//   typedef IntervalVal IV;
+//   QuicIntervialDeque<IntervalValue> deque;
+//
+//   // State:
+//   //   cached_index -> None
+//   //   container -> {}
+//
+//   // Add interval items
+//   deque.PushBack(IV(val: 0, interval_begin: 0, interval_end: 10));
+//   deque.PushBack(IV(val: 1, interval_begin: 20, interval_end: 25));
+//   deque.PushBack(IV(val: 2, interval_begin: 25, interval_end: 30));
+//
+//   // State:
+//   //   cached_index -> 0
+//   //   container -> {{0, [0, 10)}, {1, [20, 25)}, {2, [25, 30)}}
+//
+//   // Look for 0 and return [0, 10). Time: O(1)
+//   auto it = deque.DataAt(0);
+//   assert(it->val == 0);
+//   it++;  // Increment and move the |cached_index_| over [0, 10) to [20, 25).
+//   assert(it->val == 1);
+//
+//   // State:
+//   //   cached_index -> 1
+//   //   container -> {{0, [0, 10)}, {1, [20, 25)}, {2, [25, 30)}}
+//
+//   // Look for 20 and return [20, 25). Time: O(1)
+//   auto it = deque.DataAt(20); // |cached_index_| remains unchanged.
+//   assert(it->val == 1);
+//
+//   // State:
+//   //   cached_index -> 1
+//   //   container -> {{0, [0, 10)}, {1, [20, 25)}, {2, [25, 30)}}
+//
+//   // Look for 15 and return deque.DataEnd(). Time: O(log(n))
+//   auto it = deque.DataAt(15); // |cached_index_| remains unchanged.
+//   assert(it == deque.DataEnd());
+//
+//   // Look for 25 and return [25, 30). Time: O(1) with off-by-one.
+//   auto it = deque.DataAt(25); // |cached_index_| is updated to 2.
+//   assert(it->val == 2);
+//   it++; // |cached_index_| is set to |None| as all data has been iterated.
+//
+//
+//   // State:
+//   //   cached_index -> None
+//   //   container -> {{0, [0, 10)}, {1, [20, 25)}, {2, [25, 30)}}
+//
+//   // Look again for 0 and return [0, 10). Time: O(log(n))
+//   auto it = deque.DataAt(0);
+//
+//
+//   deque.PopFront();  // Pop -> {0, [0, 10)}
+//
+//   // State:
+//   //   cached_index -> None
+//   //   container -> {{1, [20, 25)}, {2, [25, 30)}}
+//
+//   deque.PopFront();  // Pop -> {1, [20, 25)}
+//
+//   // State:
+//   //   cached_index -> None
+//   //   container -> {{2, [25, 30)}}
+//
+//   deque.PushBack(IV(val: 3, interval_begin: 35, interval_end: 50));
+//
+//   // State:
+//   //   cached_index -> 1
+//   //   container -> {{2, [25, 30)}, {3, [35, 50)}}
+
+template <class T, class C = QUIC_NO_EXPORT QuicDeque<T>>
+class QUIC_EXPORT_PRIVATE QuicIntervalDeque {
+ public:
+  class QUIC_EXPORT_PRIVATE Iterator {
+   public:
+    // Used by |std::lower_bound|
+    using iterator_category = std::forward_iterator_tag;
+    using value_type = T;
+    using difference_type = std::ptrdiff_t;
+    using pointer = T*;
+    using reference = T&;
+
+    // Every increment of the iterator will increment the |cached_index_| if
+    // |index_| is larger than the current |cached_index_|. |index_| is bounded
+    // at |deque_.size()| and any attempt to increment above that will be
+    // ignored. Once an iterator has iterated all elements the |cached_index_|
+    // will be reset.
+    Iterator(std::size_t index, QuicIntervalDeque* deque)
+        : index_(index), deque_(deque) {}
+    // Only the ++ operator attempts to update the cached index. Other operators
+    // are used by |lower_bound| to binary search and are thus private.
+    Iterator& operator++() {
+      // Don't increment when we are at the end.
+      const std::size_t container_size = deque_->container_.size();
+      if (index_ >= container_size) {
+        QUIC_BUG << "Iterator out of bounds.";
+        return *this;
+      }
+      index_++;
+      if (deque_->cached_index_.has_value()) {
+        const std::size_t cached_index = deque_->cached_index_.value();
+        // If all items are iterated then reset the |cached_index_|
+        if (index_ == container_size) {
+          deque_->cached_index_.reset();
+        } else {
+          // Otherwise the new |cached_index_| is the max of itself and |index_|
+          if (cached_index < index_) {
+            deque_->cached_index_ = index_;
+          }
+        }
+      }
+      return *this;
+    }
+    Iterator operator++(int) {
+      Iterator copy = *this;
+      ++(*this);
+      return copy;
+    }
+    reference operator*() { return deque_->container_[index_]; }
+    pointer operator->() { return &deque_->container_[index_]; }
+    bool operator==(const Iterator& rhs) const {
+      return index_ == rhs.index_ && deque_ == rhs.deque_;
+    }
+    bool operator!=(const Iterator& rhs) const { return !(*this == rhs); }
+
+   private:
+    // A set of private operators for |std::lower_bound|
+    Iterator operator+(difference_type amount) const {
+      Iterator copy = *this;
+      copy.index_ += amount;
+      DCHECK(copy.index_ < copy.deque_->size());
+      return copy;
+    }
+    Iterator& operator+=(difference_type amount) {
+      index_ += amount;
+      DCHECK(index_ < deque_->size());
+      return *this;
+    }
+    difference_type operator-(const Iterator& rhs) const {
+      return static_cast<difference_type>(index_) -
+             static_cast<difference_type>(rhs.index_);
+    }
+
+    // |index_| is the index of the item in |*deque_|.
+    std::size_t index_;
+    // |deque_| is a pointer to the container the iterator came from.
+    QuicIntervalDeque* deque_;
+
+    friend class QuicIntervalDeque;
+  };
+
+  QuicIntervalDeque();
+
+  // Adds an item to the underlying container. The |item|'s interval _should_ be
+  // strictly greater than the last interval added.
+  void PushBack(T&& item);
+  void PushBack(const T& item);
+  // Removes the front/top of the underlying container and the associated
+  // interval.
+  void PopFront();
+  // Returns an iterator to the beginning of the data. The iterator will move
+  // the |cached_index_| as the iterator moves.
+  Iterator DataBegin();
+  // Returns an iterator to the end of the data.
+  Iterator DataEnd();
+  // Returns an iterator pointing to the item in |interval_begin|. The iterator
+  // will move the |cached_index_| as the iterator moves.
+  Iterator DataAt(const std::size_t interval_begin);
+
+  // Returns the number of items contained inside the structure.
+  std::size_t Size() const;
+  // Returns whether the structure is empty.
+  bool Empty() const;
+
+ private:
+  struct QUIC_EXPORT_PRIVATE IntervalCompare {
+    bool operator()(const T& item, std::size_t interval_begin) const {
+      return item.interval().max() <= interval_begin;
+    }
+  };
+
+  template <class U>
+  void PushBackUniversal(U&& item);
+
+  Iterator Search(const std::size_t interval_begin,
+                  const std::size_t begin_index,
+                  const std::size_t end_index);
+
+  // For accessing the |cached_index_|
+  friend class test::QuicIntervalDequePeer;
+
+  C container_;
+  QuicOptional<std::size_t> cached_index_;
+};
+
+template <class T, class C>
+QuicIntervalDeque<T, C>::QuicIntervalDeque() {}
+
+template <class T, class C>
+void QuicIntervalDeque<T, C>::PushBack(T&& item) {
+  PushBackUniversal(std::move(item));
+}
+
+template <class T, class C>
+void QuicIntervalDeque<T, C>::PushBack(const T& item) {
+  PushBackUniversal(item);
+}
+
+template <class T, class C>
+void QuicIntervalDeque<T, C>::PopFront() {
+  if (container_.size() == 0) {
+    QUIC_BUG << "Trying to pop from an empty container.";
+    return;
+  }
+  container_.pop_front();
+  if (container_.size() == 0) {
+    cached_index_.reset();
+  }
+  if (cached_index_.value_or(0) > 0) {
+    cached_index_ = cached_index_.value() - 1;
+  }
+}
+
+template <class T, class C>
+typename QuicIntervalDeque<T, C>::Iterator
+QuicIntervalDeque<T, C>::DataBegin() {
+  return Iterator(0, this);
+}
+
+template <class T, class C>
+typename QuicIntervalDeque<T, C>::Iterator QuicIntervalDeque<T, C>::DataEnd() {
+  return Iterator(container_.size(), this);
+}
+
+template <class T, class C>
+typename QuicIntervalDeque<T, C>::Iterator QuicIntervalDeque<T, C>::DataAt(
+    const std::size_t interval_begin) {
+  // No |cached_index_| value means all items can be searched.
+  if (!cached_index_.has_value()) {
+    return Search(interval_begin, 0, container_.size());
+  }
+
+  const std::size_t cached_index = cached_index_.value();
+  DCHECK(cached_index < container_.size());
+
+  const QuicInterval<size_t> cached_interval =
+      container_[cached_index].interval();
+  // Does our cached index point directly to what we want?
+  if (cached_interval.Contains(interval_begin)) {
+    return Iterator(cached_index, this);
+  }
+
+  // Are we off-by-one?
+  const std::size_t next_index = cached_index + 1;
+  if (next_index < container_.size()) {
+    if (container_[next_index].interval().Contains(interval_begin)) {
+      cached_index_ = next_index;
+      return Iterator(next_index, this);
+    }
+  }
+
+  // Small optimization:
+  // Determine if we should binary search above or below the cached interval.
+  const std::size_t cached_begin = cached_interval.min();
+  bool looking_below = interval_begin < cached_begin;
+  const std::size_t lower = looking_below ? 0 : cached_index + 1;
+  const std::size_t upper = looking_below ? cached_index : container_.size();
+  Iterator ret = Search(interval_begin, lower, upper);
+  if (ret == DataEnd()) {
+    return ret;
+  }
+  // Update the |cached_index_| to point to the higher index.
+  if (!looking_below) {
+    cached_index_ = ret.index_;
+  }
+  return ret;
+}
+
+template <class T, class C>
+std::size_t QuicIntervalDeque<T, C>::Size() const {
+  return container_.size();
+}
+
+template <class T, class C>
+bool QuicIntervalDeque<T, C>::Empty() const {
+  return container_.size() == 0;
+}
+
+template <class T, class C>
+template <class U>
+void QuicIntervalDeque<T, C>::PushBackUniversal(U&& item) {
+  QuicInterval<std::size_t> interval = item.interval();
+  // Adding an empty interval is a bug.
+  if (interval.Empty()) {
+    QUIC_BUG << "Trying to save empty interval to QuicDeque.";
+    return;
+  }
+  container_.push_back(std::forward<U>(item));
+  if (!cached_index_.has_value()) {
+    cached_index_ = container_.size() - 1;
+  }
+}
+
+template <class T, class C>
+typename QuicIntervalDeque<T, C>::Iterator QuicIntervalDeque<T, C>::Search(
+    const std::size_t interval_begin,
+    const std::size_t begin_index,
+    const std::size_t end_index) {
+  auto begin = container_.begin() + begin_index;
+  auto end = container_.begin() + end_index;
+  auto res = std::lower_bound(begin, end, interval_begin, IntervalCompare());
+  // Just because we run |lower_bound| and it didn't return |container_.end()|
+  // doesn't mean we found our desired interval.
+  if (res != end && res->interval().Contains(interval_begin)) {
+    return Iterator(std::distance(begin, res) + begin_index, this);
+  }
+  return DataEnd();
+}
+
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_CORE_QUIC_SEEKER_H_
diff --git a/quic/core/quic_interval_deque_test.cc b/quic/core/quic_interval_deque_test.cc
new file mode 100644
index 0000000..c31bebe
--- /dev/null
+++ b/quic/core/quic_interval_deque_test.cc
@@ -0,0 +1,364 @@
+// Copyright (c) 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/quic/core/quic_interval_deque.h"
+#include <cstdint>
+#include <ostream>
+#include "net/third_party/quiche/src/quic/core/quic_interval.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_expect_bug.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_test.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_interval_deque_peer.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_test_utils.h"
+
+namespace quic {
+namespace test {
+namespace {
+
+struct IntervalItem {
+  int32_t val;
+  std::size_t interval_start, interval_end;
+  QuicInterval<std::size_t> interval() const {
+    return QuicInterval<std::size_t>(interval_start, interval_end);
+  }
+  IntervalItem(int32_t val,
+               std::size_t interval_start,
+               std::size_t interval_end)
+      : val(val), interval_start(interval_start), interval_end(interval_end) {}
+};
+
+namespace {
+
+typedef QuicIntervalDeque<IntervalItem> QID;
+
+const int32_t kSize = 100;
+const std::size_t kIntervalStep = 10;
+
+}  // namespace
+
+class QuicIntervalDequeTest : public QuicTest {
+ public:
+  QuicIntervalDequeTest() {
+    // Add items with intervals of |kIntervalStep| size.
+    for (int32_t i = 0; i < kSize; ++i) {
+      const std::size_t interval_begin = kIntervalStep * i;
+      const std::size_t interval_end = interval_begin + kIntervalStep;
+      qid_.PushBack(IntervalItem(i, interval_begin, interval_end));
+    }
+  }
+
+  QID qid_;
+};
+
+// The goal of this test is to show insertion/push_back, iteration, and and
+// deletion/pop_front from the container.
+TEST_F(QuicIntervalDequeTest, QuicDequeInsertRemoveSize) {
+  QID qid;
+
+  EXPECT_EQ(qid.Size(), std::size_t(0));
+  qid.PushBack(IntervalItem(0, 0, 10));
+  EXPECT_EQ(qid.Size(), std::size_t(1));
+  qid.PushBack(IntervalItem(1, 10, 20));
+  EXPECT_EQ(qid.Size(), std::size_t(2));
+  qid.PushBack(IntervalItem(2, 20, 30));
+  EXPECT_EQ(qid.Size(), std::size_t(3));
+  qid.PushBack(IntervalItem(3, 30, 40));
+  EXPECT_EQ(qid.Size(), std::size_t(4));
+
+  // Advance the index all the way...
+  int32_t i = 0;
+  for (auto it = qid.DataAt(0); it != qid.DataEnd(); ++it, ++i) {
+    const int32_t index = QuicIntervalDequePeer::GetCachedIndex(&qid);
+    EXPECT_EQ(index, i);
+    EXPECT_EQ(it->val, i);
+  }
+  const int32_t index = QuicIntervalDequePeer::GetCachedIndex(&qid);
+  EXPECT_EQ(index, -1);
+
+  qid.PopFront();
+  EXPECT_EQ(qid.Size(), std::size_t(3));
+  qid.PopFront();
+  EXPECT_EQ(qid.Size(), std::size_t(2));
+  qid.PopFront();
+  EXPECT_EQ(qid.Size(), std::size_t(1));
+  qid.PopFront();
+  EXPECT_EQ(qid.Size(), std::size_t(0));
+
+  EXPECT_QUIC_DEBUG_DEATH(qid.PopFront(),
+                          "Trying to pop from an empty container.");
+}
+
+// The goal of this test is to push data into the container at specific
+// intervals and show how the |DataAt| method can move the |cached_index| as the
+// iterator moves through the data.
+TEST_F(QuicIntervalDequeTest, QuicDequeInsertIterateWhole) {
+  // The write index should point to the beginning of the container.
+  const int32_t cached_index = QuicIntervalDequePeer::GetCachedIndex(&qid_);
+  EXPECT_EQ(cached_index, 0);
+
+  auto it = qid_.DataBegin();
+  auto end = qid_.DataEnd();
+  for (int32_t i = 0; i < kSize; ++i, ++it) {
+    EXPECT_EQ(it->val, i);
+    const std::size_t current_iteraval_begin = i * kIntervalStep;
+    // The |DataAt| method should find the correct interval.
+    auto lookup = qid_.DataAt(current_iteraval_begin);
+    EXPECT_EQ(i, lookup->val);
+    // Make sure the index hasn't changed just from using |DataAt|
+    const int32_t index_before = QuicIntervalDequePeer::GetCachedIndex(&qid_);
+    EXPECT_EQ(index_before, i);
+    // This increment should move the index forward.
+    lookup++;
+    // Check that the index has changed.
+    const int32_t index_after = QuicIntervalDequePeer::GetCachedIndex(&qid_);
+    const int32_t after_i = (i + 1) == kSize ? -1 : (i + 1);
+    EXPECT_EQ(index_after, after_i);
+    EXPECT_NE(it, end);
+  }
+}
+
+// The goal of this test is to push data into the container at specific
+// intervals and show how the |DataAt| method can move the |cached_index| using
+// the off-by-one logic.
+TEST_F(QuicIntervalDequeTest, QuicDequeOffByOne) {
+  // The write index should point to the beginning of the container.
+  const int32_t cached_index = QuicIntervalDequePeer::GetCachedIndex(&qid_);
+  EXPECT_EQ(cached_index, 0);
+
+  auto it = qid_.DataBegin();
+  auto end = qid_.DataEnd();
+  for (int32_t i = 0; i < kSize - 1; ++i, ++it) {
+    EXPECT_EQ(it->val, i);
+    const int32_t off_by_one_i = i + 1;
+    const std::size_t current_iteraval_begin = off_by_one_i * kIntervalStep;
+    // Make sure the index has changed just from using |DataAt|
+    const int32_t index_before = QuicIntervalDequePeer::GetCachedIndex(&qid_);
+    EXPECT_EQ(index_before, i);
+    // The |DataAt| method should find the correct interval.
+    auto lookup = qid_.DataAt(current_iteraval_begin);
+    EXPECT_EQ(off_by_one_i, lookup->val);
+    // Check that the index has changed.
+    const int32_t index_after = QuicIntervalDequePeer::GetCachedIndex(&qid_);
+    const int32_t after_i = off_by_one_i == kSize ? -1 : off_by_one_i;
+    EXPECT_EQ(index_after, after_i);
+    EXPECT_NE(it, end);
+  }
+}
+
+// The goal of this test is to push data into the container at specific
+// intervals and show modify the structure with a live iterator.
+TEST_F(QuicIntervalDequeTest, QuicDequeIteratorInvalidation) {
+  // The write index should point to the beginning of the container.
+  const int32_t cached_index = QuicIntervalDequePeer::GetCachedIndex(&qid_);
+  EXPECT_EQ(cached_index, 0);
+
+  const std::size_t iteraval_begin = (kSize - 1) * kIntervalStep;
+  auto lookup = qid_.DataAt(iteraval_begin);
+  EXPECT_EQ((*lookup).val, (kSize - 1));
+  qid_.PopFront();
+  EXPECT_QUIC_BUG(lookup++, "Iterator out of bounds.");
+  auto lookup_end = qid_.DataAt(iteraval_begin + kIntervalStep);
+  EXPECT_EQ(lookup_end, qid_.DataEnd());
+}
+
+// The goal of this test is the same as |QuicDequeInsertIterateWhole| but to
+// skip certain intervals and show the |cached_index| is updated properly.
+TEST_F(QuicIntervalDequeTest, QuicDequeInsertIterateSkip) {
+  // The write index should point to the beginning of the container.
+  const int32_t cached_index = QuicIntervalDequePeer::GetCachedIndex(&qid_);
+  EXPECT_EQ(cached_index, 0);
+
+  const std::size_t step = 4;
+  for (int32_t i = 0; i < kSize; i += 4) {
+    if (i != 0) {
+      const int32_t before_i = (i - (step - 1));
+      EXPECT_EQ(QuicIntervalDequePeer::GetCachedIndex(&qid_), before_i);
+    }
+    const std::size_t current_iteraval_begin = i * kIntervalStep;
+    // The |DataAt| method should find the correct interval.
+    auto lookup = qid_.DataAt(current_iteraval_begin);
+    EXPECT_EQ(i, lookup->val);
+    // Make sure the index _has_ changed just from using |DataAt| since we're
+    // skipping data.
+    const int32_t index_before = QuicIntervalDequePeer::GetCachedIndex(&qid_);
+    EXPECT_EQ(index_before, i);
+    // This increment should move the index forward.
+    lookup++;
+    // Check that the index has changed.
+    const int32_t index_after = QuicIntervalDequePeer::GetCachedIndex(&qid_);
+    const int32_t after_i = (i + 1) == kSize ? -1 : (i + 1);
+    EXPECT_EQ(index_after, after_i);
+  }
+}
+
+// The goal of this test is the same as |QuicDequeInsertIterateWhole| but it has
+// |PopFront| calls interleaved to show the |cached_index| updates correctly.
+TEST_F(QuicIntervalDequeTest, QuicDequeInsertDeleteIterate) {
+  // The write index should point to the beginning of the container.
+  const int32_t index = QuicIntervalDequePeer::GetCachedIndex(&qid_);
+  EXPECT_EQ(index, 0);
+
+  std::size_t limit = 0;
+  for (int32_t i = 0; limit < qid_.Size(); ++i, ++limit) {
+    // Always point to the beginning of the container.
+    auto it = qid_.DataBegin();
+    EXPECT_EQ(it->val, i);
+
+    // Get an iterator.
+    const std::size_t current_iteraval_begin = i * kIntervalStep;
+    auto lookup = qid_.DataAt(current_iteraval_begin);
+    const int32_t index_before = QuicIntervalDequePeer::GetCachedIndex(&qid_);
+    // The index should always point to 0.
+    EXPECT_EQ(index_before, 0);
+    // This iterator increment should effect the index.
+    lookup++;
+    const int32_t index_after = QuicIntervalDequePeer::GetCachedIndex(&qid_);
+    EXPECT_EQ(index_after, 1);
+    // Decrement the |temp_size| and pop from the front.
+    qid_.PopFront();
+    // Show the index has been updated to point to 0 again (from 1).
+    const int32_t index_after_pop =
+        QuicIntervalDequePeer::GetCachedIndex(&qid_);
+    EXPECT_EQ(index_after_pop, 0);
+  }
+}
+
+// The goal of this test is to move the index to the end and then add more data
+// to show it can be reset to a valid index.
+TEST_F(QuicIntervalDequeTest, QuicDequeInsertIterateInsert) {
+  // The write index should point to the beginning of the container.
+  const int32_t index = QuicIntervalDequePeer::GetCachedIndex(&qid_);
+  EXPECT_EQ(index, 0);
+
+  int32_t iterated_elements = 0;
+  for (int32_t i = 0; i < kSize; ++i, ++iterated_elements) {
+    // Get an iterator.
+    const std::size_t current_iteraval_begin = i * kIntervalStep;
+    auto lookup = qid_.DataAt(current_iteraval_begin);
+    const int32_t index_before = QuicIntervalDequePeer::GetCachedIndex(&qid_);
+    // The index should always point to i.
+    EXPECT_EQ(index_before, i);
+    // This iterator increment should effect the index.
+    lookup++;
+    // Show the index has been updated to point to i + 1 or -1 if at the end.
+    const int32_t index_after = QuicIntervalDequePeer::GetCachedIndex(&qid_);
+    const int32_t after_i = (i + 1) == kSize ? -1 : (i + 1);
+    EXPECT_EQ(index_after, after_i);
+  }
+  const int32_t invalid_index = QuicIntervalDequePeer::GetCachedIndex(&qid_);
+  EXPECT_EQ(invalid_index, -1);
+
+  // Add more data to the container, making the index valid.
+  const std::size_t offset = qid_.Size();
+  for (int32_t i = 0; i < kSize; ++i) {
+    const std::size_t interval_begin = offset + (kIntervalStep * i);
+    const std::size_t interval_end = offset + interval_begin + kIntervalStep;
+    qid_.PushBack(IntervalItem(i + offset, interval_begin, interval_end));
+    const int32_t index_current = QuicIntervalDequePeer::GetCachedIndex(&qid_);
+    // Index should now be valid and equal to the size of the container before
+    // adding more items to it.
+    EXPECT_EQ(index_current, iterated_elements);
+  }
+  // Show the index is still valid and hasn't changed since the first iteration
+  // of the loop.
+  const int32_t index_after_add = QuicIntervalDequePeer::GetCachedIndex(&qid_);
+  EXPECT_EQ(index_after_add, iterated_elements);
+
+  // Iterate over all the data in the container and eventually reset the index
+  // as we did before.
+  for (int32_t i = 0; i < kSize; ++i, ++iterated_elements) {
+    const std::size_t interval_begin = offset + (kIntervalStep * i);
+    const int32_t index_current = QuicIntervalDequePeer::GetCachedIndex(&qid_);
+    EXPECT_EQ(index_current, iterated_elements);
+    auto lookup = qid_.DataAt(interval_begin);
+    const int32_t expected_value = i + offset;
+    EXPECT_EQ(lookup->val, expected_value);
+    lookup++;
+    const int32_t after_inc =
+        (iterated_elements + 1) == (kSize * 2) ? -1 : (iterated_elements + 1);
+    const int32_t after_index = QuicIntervalDequePeer::GetCachedIndex(&qid_);
+    EXPECT_EQ(after_index, after_inc);
+  }
+  // Show the index is now invalid.
+  const int32_t invalid_index_again =
+      QuicIntervalDequePeer::GetCachedIndex(&qid_);
+  EXPECT_EQ(invalid_index_again, -1);
+}
+
+// The goal of this test is to push data into the container at specific
+// intervals and show how the |DataAt| can iterate over already scanned data.
+TEST_F(QuicIntervalDequeTest, QuicDequeRescanData) {
+  // The write index should point to the beginning of the container.
+  const int32_t index = QuicIntervalDequePeer::GetCachedIndex(&qid_);
+  EXPECT_EQ(index, 0);
+
+  auto it = qid_.DataBegin();
+  auto end = qid_.DataEnd();
+  for (int32_t i = 0; i < kSize - 1; ++i, ++it) {
+    EXPECT_EQ(it->val, i);
+    const std::size_t current_iteraval_begin = i * kIntervalStep;
+    // The |DataAt| method should find the correct interval.
+    auto lookup = qid_.DataAt(current_iteraval_begin);
+    EXPECT_EQ(i, lookup->val);
+    // Make sure the index has changed just from using |DataAt|
+    const int32_t cached_index_before =
+        QuicIntervalDequePeer::GetCachedIndex(&qid_);
+    EXPECT_EQ(cached_index_before, i);
+    // Ensure the real index has changed just from using |DataAt| and the
+    // off-by-one logic
+    const int32_t index_before = QuicIntervalDequePeer::GetCachedIndex(&qid_);
+    const int32_t before_i = i;
+    EXPECT_EQ(index_before, before_i);
+    // This increment should move the cached index forward.
+    lookup++;
+    // Check that the cached index has moved foward.
+    const int32_t cached_index_after =
+        QuicIntervalDequePeer::GetCachedIndex(&qid_);
+    const int32_t after_i = (i + 1);
+    EXPECT_EQ(cached_index_after, after_i);
+    EXPECT_NE(it, end);
+  }
+
+  // Iterate over items which have been consumed before.
+  int32_t expected_index = static_cast<int32_t>(kSize - 1);
+  for (int32_t i = 0; i < kSize - 1; ++i) {
+    const std::size_t current_iteraval_begin = i * kIntervalStep;
+    // The |DataAt| method should find the correct interval.
+    auto lookup = qid_.DataAt(current_iteraval_begin);
+    EXPECT_EQ(i, lookup->val);
+    // This increment shouldn't move the index forward as the index is currently
+    // ahead.
+    lookup++;
+    // Check that the index hasn't moved foward.
+    const int32_t index_after = QuicIntervalDequePeer::GetCachedIndex(&qid_);
+    EXPECT_EQ(index_after, expected_index);
+    EXPECT_NE(it, end);
+  }
+}
+
+// The goal of this test is to show that popping from an empty container is a
+// bug.
+TEST_F(QuicIntervalDequeTest, QuicDequePopEmpty) {
+  QID qid;
+  EXPECT_TRUE(qid.Empty());
+  EXPECT_QUIC_BUG(qid.PopFront(), "Trying to pop from an empty container.");
+}
+
+// The goal of this test is to show that adding a zero-sized interval is a bug.
+TEST_F(QuicIntervalDequeTest, QuicDequeZeroSizedInterval) {
+  QID qid;
+  EXPECT_QUIC_BUG(qid.PushBack(IntervalItem(0, 0, 0)),
+                  "Trying to save empty interval to QuicDeque.");
+}
+
+// The goal of this test is to show that an iterator to an empty container
+// returns |DataEnd|.
+TEST_F(QuicIntervalDequeTest, QuicDequeIteratorEmpty) {
+  QID qid;
+  auto it = qid.DataAt(0);
+  EXPECT_EQ(it, qid.DataEnd());
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace quic
diff --git a/quic/core/quic_stream_send_buffer.cc b/quic/core/quic_stream_send_buffer.cc
index 158afd8..2008cf2 100644
--- a/quic/core/quic_stream_send_buffer.cc
+++ b/quic/core/quic_stream_send_buffer.cc
@@ -35,13 +35,21 @@
 
 BufferedSlice::~BufferedSlice() {}
 
+QuicInterval<std::size_t> BufferedSlice::interval() const {
+  const std::size_t length = slice.length();
+  return QuicInterval<std::size_t>(offset, offset + length);
+}
+
 bool StreamPendingRetransmission::operator==(
     const StreamPendingRetransmission& other) const {
   return offset == other.offset && length == other.length;
 }
 
 QuicStreamSendBuffer::QuicStreamSendBuffer(QuicBufferAllocator* allocator)
-    : stream_offset_(0),
+    // TODO(b/144690240): Remove this variable once quic_seeker is deprecated.
+    : interval_deque_active_(GetQuicReloadableFlag(quic_interval_deque)),
+      current_end_offset_(0),
+      stream_offset_(0),
       allocator_(allocator),
       stream_bytes_written_(0),
       stream_bytes_outstanding_(0),
@@ -76,9 +84,20 @@
     return;
   }
   size_t length = slice.length();
-  buffered_slices_.emplace_back(std::move(slice), stream_offset_);
-  if (write_index_ == -1) {
-    write_index_ = buffered_slices_.size() - 1;
+  if (interval_deque_active_) {
+    QUIC_RELOADABLE_FLAG_COUNT_N(quic_interval_deque, 1, 5);
+    // Need to start the offsets at the right interval.
+    if (interval_deque_.Empty()) {
+      const QuicStreamOffset end = stream_offset_ + length;
+      current_end_offset_ = std::max(current_end_offset_, end);
+    }
+    BufferedSlice bs = BufferedSlice(std::move(slice), stream_offset_);
+    interval_deque_.PushBack(std::move(bs));
+  } else {
+    buffered_slices_.emplace_back(std::move(slice), stream_offset_);
+    if (write_index_ == -1) {
+      write_index_ = buffered_slices_.size() - 1;
+    }
   }
   stream_offset_ += length;
 }
@@ -96,6 +115,38 @@
 bool QuicStreamSendBuffer::WriteStreamData(QuicStreamOffset offset,
                                            QuicByteCount data_length,
                                            QuicDataWriter* writer) {
+  if (interval_deque_active_) {
+    QUIC_RELOADABLE_FLAG_COUNT_N(quic_interval_deque, 2, 5);
+    QUIC_BUG_IF(current_end_offset_ < offset)
+        << "Tried to write data out of sequence. last_offset_end:"
+        << current_end_offset_ << ", offset:" << offset;
+    // The iterator returned from |interval_deque_| will automatically advance
+    // the internal write index for the QuicIntervalDeque. The incrementing is
+    // done in operator++.
+    for (auto slice_it = interval_deque_.DataAt(offset);
+         slice_it != interval_deque_.DataEnd(); ++slice_it) {
+      if (data_length == 0 || offset < slice_it->offset) {
+        break;
+      }
+
+      QuicByteCount slice_offset = offset - slice_it->offset;
+      QuicByteCount available_bytes_in_slice =
+          slice_it->slice.length() - slice_offset;
+      QuicByteCount copy_length =
+          std::min(data_length, available_bytes_in_slice);
+      if (!writer->WriteBytes(slice_it->slice.data() + slice_offset,
+                              copy_length)) {
+        QUIC_BUG << "Writer fails to write.";
+        return false;
+      }
+      offset += copy_length;
+      data_length -= copy_length;
+      const QuicStreamOffset new_end =
+          slice_it->offset + slice_it->slice.length();
+      current_end_offset_ = std::max(current_end_offset_, new_end);
+    }
+    return data_length == 0;
+  }
   // TODO(renjietang): Remove this variable once quic_coalesce_stream_frames_2
   // is deprecated.
   bool write_index_hit = false;
@@ -164,7 +215,7 @@
     }
   } else if (write_index_hit &&
              static_cast<size_t>(write_index_) == buffered_slices_.size()) {
-    // Already write to the end off buffer.
+    // Already write to the end of buffer.
     QUIC_DVLOG(2) << "Finish writing out all buffered data.";
     write_index_ = -1;
   }
@@ -264,6 +315,39 @@
 
 bool QuicStreamSendBuffer::FreeMemSlices(QuicStreamOffset start,
                                          QuicStreamOffset end) {
+  if (interval_deque_active_) {
+    QUIC_RELOADABLE_FLAG_COUNT_N(quic_interval_deque, 3, 5);
+    auto it = interval_deque_.DataBegin();
+    if (it == interval_deque_.DataEnd() || it->slice.empty()) {
+      QUIC_BUG << "Trying to ack stream data [" << start << ", " << end << "), "
+               << (it == interval_deque_.DataEnd()
+                       ? "and there is no outstanding data."
+                       : "and the first slice is empty.");
+      return false;
+    }
+    if (!it->interval().Contains(start)) {
+      // Slow path that not the earliest outstanding data gets acked.
+      it = std::lower_bound(interval_deque_.DataBegin(),
+                            interval_deque_.DataEnd(), start, CompareOffset());
+    }
+    if (it == interval_deque_.DataEnd() || it->slice.empty()) {
+      QUIC_BUG << "Offset " << start << " with iterator offset: " << it->offset
+               << (it == interval_deque_.DataEnd()
+                       ? " does not exist."
+                       : " has already been acked.");
+      return false;
+    }
+    for (; it != interval_deque_.DataEnd(); ++it) {
+      if (it->offset >= end) {
+        break;
+      }
+      if (!it->slice.empty() &&
+          bytes_acked_.Contains(it->offset, it->offset + it->slice.length())) {
+        it->slice.Reset();
+      }
+    }
+    return true;
+  }
   auto it = buffered_slices_.begin();
   // Find it, such that buffered_slices_[it - 1].end < start <=
   // buffered_slices_[it].end.
@@ -297,6 +381,19 @@
 }
 
 void QuicStreamSendBuffer::CleanUpBufferedSlices() {
+  if (interval_deque_active_) {
+    QUIC_RELOADABLE_FLAG_COUNT_N(quic_interval_deque, 4, 5);
+    while (!interval_deque_.Empty() &&
+           interval_deque_.DataBegin()->slice.empty()) {
+      QUIC_BUG_IF(interval_deque_.DataBegin()->offset > current_end_offset_)
+          << "Fail to pop front from interval_deque_. Front element contained "
+             "a slice whose data has not all be written. Front offset "
+          << interval_deque_.DataBegin()->offset << " length "
+          << interval_deque_.DataBegin()->slice.length();
+      interval_deque_.PopFront();
+    }
+    return;
+  }
   while (!buffered_slices_.empty() && buffered_slices_.front().slice.empty()) {
     // Remove data which stops waiting for acks. Please note, mem slices can
     // be released out of order, but send buffer is cleaned up in order.
@@ -323,7 +420,12 @@
 }
 
 size_t QuicStreamSendBuffer::size() const {
-  return buffered_slices_.size();
+  if (interval_deque_active_) {
+    QUIC_RELOADABLE_FLAG_COUNT_N(quic_interval_deque, 5, 5);
+    return interval_deque_.Size();
+  } else {
+    return buffered_slices_.size();
+  }
 }
 
 }  // namespace quic
diff --git a/quic/core/quic_stream_send_buffer.h b/quic/core/quic_stream_send_buffer.h
index 51a10e9..57be51d 100644
--- a/quic/core/quic_stream_send_buffer.h
+++ b/quic/core/quic_stream_send_buffer.h
@@ -6,6 +6,7 @@
 #define QUICHE_QUIC_CORE_QUIC_STREAM_SEND_BUFFER_H_
 
 #include "net/third_party/quiche/src/quic/core/frames/quic_stream_frame.h"
+#include "net/third_party/quiche/src/quic/core/quic_interval_deque.h"
 #include "net/third_party/quiche/src/quic/core/quic_interval_set.h"
 #include "net/third_party/quiche/src/quic/core/quic_types.h"
 #include "net/third_party/quiche/src/quic/platform/api/quic_containers.h"
@@ -35,6 +36,9 @@
   BufferedSlice& operator=(const BufferedSlice& other) = delete;
   ~BufferedSlice();
 
+  // Return an interval representing the offset and length.
+  QuicInterval<std::size_t> interval() const;
+
   // Stream data of this data slice.
   QuicMemSlice slice;
   // Location of this data slice in the stream.
@@ -141,7 +145,12 @@
   // Cleanup empty slices in order from buffered_slices_.
   void CleanUpBufferedSlices();
 
+  bool interval_deque_active_;
   QuicDeque<BufferedSlice> buffered_slices_;
+  // |current_end_offset_| stores the end offset of the current slice to ensure
+  // data isn't being written out of order when using the |interval_deque_|.
+  QuicStreamOffset current_end_offset_;
+  QuicIntervalDeque<BufferedSlice> interval_deque_;
 
   // Offset of next inserted byte.
   QuicStreamOffset stream_offset_;
diff --git a/quic/core/quic_stream_send_buffer_test.cc b/quic/core/quic_stream_send_buffer_test.cc
index 5c1bff3..8f864c6 100644
--- a/quic/core/quic_stream_send_buffer_test.cc
+++ b/quic/core/quic_stream_send_buffer_test.cc
@@ -44,9 +44,14 @@
     QuicMemSlice slice2(&allocator_, 768);
     memset(const_cast<char*>(slice2.data()), 'd', 768);
 
-    // Index starts from not pointing to any slice.
-    EXPECT_EQ(nullptr,
-              QuicStreamSendBufferPeer::CurrentWriteSlice(&send_buffer_));
+    if (!GetQuicReloadableFlag(quic_interval_deque)) {
+      // Index starts from not pointing to any slice.
+      EXPECT_EQ(nullptr,
+                QuicStreamSendBufferPeer::CurrentWriteSlice(&send_buffer_));
+    } else {
+      // The stream offset should be 0 since nothing is written.
+      EXPECT_EQ(0u, QuicStreamSendBufferPeer::EndOffset(&send_buffer_));
+    }
 
     // Save all data.
     SetQuicFlag(FLAGS_quic_send_buffer_max_data_slice_size, 1024);
@@ -136,11 +141,12 @@
   EXPECT_EQ(copy1 + copy2, quiche::QuicheStringPiece(buf + 1024, 2048));
 
   // Write new data.
-  if (!GetQuicRestartFlag(quic_coalesce_stream_frames_2)) {
+  if (!GetQuicRestartFlag(quic_coalesce_stream_frames_2) &&
+      !GetQuicReloadableFlag(quic_interval_deque)) {
     EXPECT_EQ(1, QuicStreamSendBufferPeer::write_index(&send_buffer_));
     EXPECT_QUIC_DEBUG_DEATH(send_buffer_.WriteStreamData(2048, 50, &writer),
                             "Tried to write data out of sequence.");
-  } else {
+  } else if (!GetQuicReloadableFlag(quic_interval_deque)) {
     EXPECT_EQ(2, QuicStreamSendBufferPeer::write_index(&send_buffer_));
     ASSERT_TRUE(send_buffer_.WriteStreamData(2048, 50, &writer));
     EXPECT_EQ(std::string(50, 'c'),
@@ -149,6 +155,17 @@
     ASSERT_TRUE(send_buffer_.WriteStreamData(2048, 1124, &writer));
     EXPECT_EQ(copy3, quiche::QuicheStringPiece(buf + 1024 + 2048 + 50, 1124));
     EXPECT_EQ(3, QuicStreamSendBufferPeer::write_index(&send_buffer_));
+  } else {
+    // if |quic_interval_deque| is true then |write_index_| has no value.
+    // Instead we ensure the |current_end_offet| has an appropriate value.
+    EXPECT_EQ(2048u, QuicStreamSendBufferPeer::EndOffset(&send_buffer_));
+    ASSERT_TRUE(send_buffer_.WriteStreamData(2048, 50, &writer));
+    EXPECT_EQ(std::string(50, 'c'),
+              quiche::QuicheStringPiece(buf + 1024 + 2048, 50));
+    EXPECT_EQ(3072u, QuicStreamSendBufferPeer::EndOffset(&send_buffer_));
+    ASSERT_TRUE(send_buffer_.WriteStreamData(2048, 1124, &writer));
+    EXPECT_EQ(copy3, quiche::QuicheStringPiece(buf + 1024 + 2048 + 50, 1124));
+    EXPECT_EQ(3840u, QuicStreamSendBufferPeer::EndOffset(&send_buffer_));
   }
 }
 
@@ -290,39 +307,72 @@
   EXPECT_TRUE(send_buffer_.IsStreamDataOutstanding(400, 800));
 }
 
+// TODO(b/144690240): Rename to EndOffset when deprecating --quic_interval_deque
 TEST_F(QuicStreamSendBufferTest, CurrentWriteIndex) {
   char buf[4000];
   QuicDataWriter writer(4000, buf, quiche::HOST_BYTE_ORDER);
-  // With data buffered, index points to the 1st slice of data.
-  EXPECT_EQ(0u,
-            QuicStreamSendBufferPeer::CurrentWriteSlice(&send_buffer_)->offset);
+  if (!GetQuicReloadableFlag(quic_interval_deque)) {
+    // With data buffered, index points to the 1st slice of data.
+    EXPECT_EQ(
+        0u, QuicStreamSendBufferPeer::CurrentWriteSlice(&send_buffer_)->offset);
+  } else {
+    EXPECT_EQ(1024u, QuicStreamSendBufferPeer::EndOffset(&send_buffer_));
+  }
   ASSERT_TRUE(send_buffer_.WriteStreamData(0, 1024, &writer));
   // Wrote all data on 1st slice, index points to next slice.
-  EXPECT_EQ(1024u,
-            QuicStreamSendBufferPeer::CurrentWriteSlice(&send_buffer_)->offset);
+  if (!GetQuicReloadableFlag(quic_interval_deque)) {
+    EXPECT_EQ(
+        1024u,
+        QuicStreamSendBufferPeer::CurrentWriteSlice(&send_buffer_)->offset);
+  } else {
+    // Last offset we've seen is 1024
+    EXPECT_EQ(1024u, QuicStreamSendBufferPeer::EndOffset(&send_buffer_));
+  }
   ASSERT_TRUE(send_buffer_.WriteStreamData(1024, 512, &writer));
   // Last write didn't finish a whole slice. Index remains.
-  EXPECT_EQ(1024u,
-            QuicStreamSendBufferPeer::CurrentWriteSlice(&send_buffer_)->offset);
+  if (!GetQuicReloadableFlag(quic_interval_deque)) {
+    EXPECT_EQ(
+        1024u,
+        QuicStreamSendBufferPeer::CurrentWriteSlice(&send_buffer_)->offset);
+  } else {
+    // Last offset is now 2048 as that's the end of the next slice.
+    EXPECT_EQ(2048u, QuicStreamSendBufferPeer::EndOffset(&send_buffer_));
+  }
   send_buffer_.OnStreamDataConsumed(1024);
 
   // If data in 1st slice gets ACK'ed, it shouldn't change the indexed slice
   QuicByteCount newly_acked_length;
   EXPECT_TRUE(send_buffer_.OnStreamDataAcked(0, 1024, &newly_acked_length));
-  EXPECT_EQ(1024u,
-            QuicStreamSendBufferPeer::CurrentWriteSlice(&send_buffer_)->offset);
+  if (!GetQuicReloadableFlag(quic_interval_deque)) {
+    EXPECT_EQ(
+        1024u,
+        QuicStreamSendBufferPeer::CurrentWriteSlice(&send_buffer_)->offset);
+  } else {
+    // Last offset is still 2048.
+    EXPECT_EQ(2048u, QuicStreamSendBufferPeer::EndOffset(&send_buffer_));
+  }
 
   ASSERT_TRUE(
       send_buffer_.WriteStreamData(1024 + 512, 3840 - 1024 - 512, &writer));
   // After writing all buffered data, index become invalid again.
-  EXPECT_EQ(nullptr,
-            QuicStreamSendBufferPeer::CurrentWriteSlice(&send_buffer_));
+  if (!GetQuicReloadableFlag(quic_interval_deque)) {
+    EXPECT_EQ(nullptr,
+              QuicStreamSendBufferPeer::CurrentWriteSlice(&send_buffer_));
+  } else {
+    // Last offset is end offset of last slice.
+    EXPECT_EQ(3840u, QuicStreamSendBufferPeer::EndOffset(&send_buffer_));
+  }
   QuicMemSlice slice(&allocator_, 60);
   memset(const_cast<char*>(slice.data()), 'e', 60);
   send_buffer_.SaveMemSlice(std::move(slice));
-  // With new data, index points to the new data.
-  EXPECT_EQ(3840u,
-            QuicStreamSendBufferPeer::CurrentWriteSlice(&send_buffer_)->offset);
+  if (!GetQuicReloadableFlag(quic_interval_deque)) {
+    // With new data, index points to the new data.
+    EXPECT_EQ(
+        3840u,
+        QuicStreamSendBufferPeer::CurrentWriteSlice(&send_buffer_)->offset);
+  } else {
+    EXPECT_EQ(3840u, QuicStreamSendBufferPeer::EndOffset(&send_buffer_));
+  }
 }
 
 TEST_F(QuicStreamSendBufferTest, SaveMemSliceSpan) {
diff --git a/quic/test_tools/quic_interval_deque_peer.h b/quic/test_tools/quic_interval_deque_peer.h
new file mode 100644
index 0000000..e9a99ba
--- /dev/null
+++ b/quic/test_tools/quic_interval_deque_peer.h
@@ -0,0 +1,35 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_QUIC_TEST_TOOLS_QUIC_INTERVAL_DEQUE_PEER_H_
+#define QUICHE_QUIC_TEST_TOOLS_QUIC_INTERVAL_DEQUE_PEER_H_
+
+#include "net/third_party/quiche/src/quic/core/quic_interval_deque.h"
+
+namespace quic {
+
+namespace test {
+
+class QuicIntervalDequePeer {
+ public:
+  template <class T, class C>
+  static int32_t GetCachedIndex(QuicIntervalDeque<T, C>* interval_deque) {
+    if (!interval_deque->cached_index_.has_value()) {
+      return -1;
+    }
+    return interval_deque->cached_index_.value();
+  }
+
+  template <class T, class C>
+  static T* GetItem(QuicIntervalDeque<T, C>* interval_deque,
+                    const std::size_t index) {
+    return &interval_deque->container_[index];
+  }
+};
+
+}  // namespace test
+
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_TEST_TOOLS_QUIC_INTERVAL_DEQUE_PEER_H_
diff --git a/quic/test_tools/quic_stream_send_buffer_peer.cc b/quic/test_tools/quic_stream_send_buffer_peer.cc
index 22e5e56..2135d3f 100644
--- a/quic/test_tools/quic_stream_send_buffer_peer.cc
+++ b/quic/test_tools/quic_stream_send_buffer_peer.cc
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 #include "net/third_party/quiche/src/quic/test_tools/quic_stream_send_buffer_peer.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_interval_deque_peer.h"
 
 namespace quic {
 
@@ -15,25 +16,58 @@
   send_buffer->stream_offset_ = stream_offset;
 }
 
+// TODO(b/144690240): Remove CurrentWriteSlice when deprecating
+// --quic_interval_deque
 // static
 const BufferedSlice* QuicStreamSendBufferPeer::CurrentWriteSlice(
     QuicStreamSendBuffer* send_buffer) {
-  if (send_buffer->write_index_ == -1) {
+  auto wi = write_index(send_buffer);
+
+  if (wi == -1) {
     return nullptr;
   }
-  return &send_buffer->buffered_slices_[send_buffer->write_index_];
+  if (GetQuicReloadableFlag(quic_interval_deque)) {
+    return QuicIntervalDequePeer::GetItem(&send_buffer->interval_deque_, wi);
+  } else {
+    return &send_buffer->buffered_slices_[wi];
+  }
+}
+
+QuicStreamOffset QuicStreamSendBufferPeer::EndOffset(
+    QuicStreamSendBuffer* send_buffer) {
+  if (GetQuicReloadableFlag(quic_interval_deque)) {
+    return send_buffer->current_end_offset_;
+  }
+  return 0;
 }
 
 // static
 QuicByteCount QuicStreamSendBufferPeer::TotalLength(
     QuicStreamSendBuffer* send_buffer) {
   QuicByteCount length = 0;
-  for (const auto& slice : send_buffer->buffered_slices_) {
-    length += slice.slice.length();
+  if (GetQuicReloadableFlag(quic_interval_deque)) {
+    for (auto slice = send_buffer->interval_deque_.DataBegin();
+         slice != send_buffer->interval_deque_.DataEnd(); ++slice) {
+      length += slice->slice.length();
+    }
+  } else {
+    for (const auto& slice : send_buffer->buffered_slices_) {
+      length += slice.slice.length();
+    }
   }
   return length;
 }
 
+// static
+int32_t QuicStreamSendBufferPeer::write_index(
+    QuicStreamSendBuffer* send_buffer) {
+  if (send_buffer->interval_deque_active_) {
+    return QuicIntervalDequePeer::GetCachedIndex(&send_buffer->interval_deque_);
+  } else {
+    return send_buffer->write_index_;
+  }
+}
+
 }  // namespace test
 
 }  // namespace quic
diff --git a/quic/test_tools/quic_stream_send_buffer_peer.h b/quic/test_tools/quic_stream_send_buffer_peer.h
index 3adb173..310cda9 100644
--- a/quic/test_tools/quic_stream_send_buffer_peer.h
+++ b/quic/test_tools/quic_stream_send_buffer_peer.h
@@ -19,11 +19,11 @@
   static const BufferedSlice* CurrentWriteSlice(
       QuicStreamSendBuffer* send_buffer);
 
+  static QuicStreamOffset EndOffset(QuicStreamSendBuffer* send_buffer);
+
   static QuicByteCount TotalLength(QuicStreamSendBuffer* send_buffer);
 
-  static int32_t write_index(QuicStreamSendBuffer* send_buffer) {
-    return send_buffer->write_index_;
-  }
+  static int32_t write_index(QuicStreamSendBuffer* send_buffer);
 };
 
 }  // namespace test