Introduce an inlining version of QuicStreamSendBuffer.

Protected by FLAGS_quic_reloadable_flag_quic_use_inlining_send_buffer2.

PiperOrigin-RevId: 764011313
diff --git a/build/source_list.bzl b/build/source_list.bzl
index fac6ffe..5a8ee78 100644
--- a/build/source_list.bzl
+++ b/build/source_list.bzl
@@ -364,6 +364,7 @@
     "quic/core/quic_stream_priority.h",
     "quic/core/quic_stream_send_buffer.h",
     "quic/core/quic_stream_send_buffer_base.h",
+    "quic/core/quic_stream_send_buffer_inlining.h",
     "quic/core/quic_stream_sequencer.h",
     "quic/core/quic_stream_sequencer_buffer.h",
     "quic/core/quic_sustained_bandwidth_recorder.h",
@@ -687,6 +688,7 @@
     "quic/core/quic_stream_priority.cc",
     "quic/core/quic_stream_send_buffer.cc",
     "quic/core/quic_stream_send_buffer_base.cc",
+    "quic/core/quic_stream_send_buffer_inlining.cc",
     "quic/core/quic_stream_sequencer.cc",
     "quic/core/quic_stream_sequencer_buffer.cc",
     "quic/core/quic_sustained_bandwidth_recorder.cc",
diff --git a/build/source_list.gni b/build/source_list.gni
index 93a4c55..89c4626 100644
--- a/build/source_list.gni
+++ b/build/source_list.gni
@@ -364,6 +364,7 @@
     "src/quiche/quic/core/quic_stream_priority.h",
     "src/quiche/quic/core/quic_stream_send_buffer.h",
     "src/quiche/quic/core/quic_stream_send_buffer_base.h",
+    "src/quiche/quic/core/quic_stream_send_buffer_inlining.h",
     "src/quiche/quic/core/quic_stream_sequencer.h",
     "src/quiche/quic/core/quic_stream_sequencer_buffer.h",
     "src/quiche/quic/core/quic_sustained_bandwidth_recorder.h",
@@ -687,6 +688,7 @@
     "src/quiche/quic/core/quic_stream_priority.cc",
     "src/quiche/quic/core/quic_stream_send_buffer.cc",
     "src/quiche/quic/core/quic_stream_send_buffer_base.cc",
+    "src/quiche/quic/core/quic_stream_send_buffer_inlining.cc",
     "src/quiche/quic/core/quic_stream_sequencer.cc",
     "src/quiche/quic/core/quic_stream_sequencer_buffer.cc",
     "src/quiche/quic/core/quic_sustained_bandwidth_recorder.cc",
diff --git a/build/source_list.json b/build/source_list.json
index 1cabec8..3a262e2 100644
--- a/build/source_list.json
+++ b/build/source_list.json
@@ -363,6 +363,7 @@
     "quiche/quic/core/quic_stream_priority.h",
     "quiche/quic/core/quic_stream_send_buffer.h",
     "quiche/quic/core/quic_stream_send_buffer_base.h",
+    "quiche/quic/core/quic_stream_send_buffer_inlining.h",
     "quiche/quic/core/quic_stream_sequencer.h",
     "quiche/quic/core/quic_stream_sequencer_buffer.h",
     "quiche/quic/core/quic_sustained_bandwidth_recorder.h",
@@ -686,6 +687,7 @@
     "quiche/quic/core/quic_stream_priority.cc",
     "quiche/quic/core/quic_stream_send_buffer.cc",
     "quiche/quic/core/quic_stream_send_buffer_base.cc",
+    "quiche/quic/core/quic_stream_send_buffer_inlining.cc",
     "quiche/quic/core/quic_stream_sequencer.cc",
     "quiche/quic/core/quic_stream_sequencer_buffer.cc",
     "quiche/quic/core/quic_sustained_bandwidth_recorder.cc",
diff --git a/quiche/common/quiche_feature_flags_list.h b/quiche/common/quiche_feature_flags_list.h
index 6bb4791..09e6b2a 100755
--- a/quiche/common/quiche_feature_flags_list.h
+++ b/quiche/common/quiche_feature_flags_list.h
@@ -57,6 +57,7 @@
 QUICHE_FLAG(bool, quiche_reloadable_flag_quic_test_peer_addr_change_after_normalize, false, false, "If true, QuicConnection::ProcessValidatedPacket will use normalized address to test peer address changes.")
 QUICHE_FLAG(bool, quiche_reloadable_flag_quic_testonly_default_false, false, false, "A testonly reloadable flag that will always default to false.")
 QUICHE_FLAG(bool, quiche_reloadable_flag_quic_testonly_default_true, true, true, "A testonly reloadable flag that will always default to true.")
+QUICHE_FLAG(bool, quiche_reloadable_flag_quic_use_inlining_send_buffer2, false, false, "Uses an inlining version of QuicSendStreamBuffer.")
 QUICHE_FLAG(bool, quiche_reloadable_flag_quic_use_received_client_addresses_cache, true, true, "If true, use a LRU cache to record client addresses of packets received on server's original address.")
 QUICHE_FLAG(bool, quiche_restart_flag_quic_support_release_time_for_gso, false, false, "If true, QuicGsoBatchWriter will support release time if it is available and the process has the permission to do so.")
 QUICHE_FLAG(bool, quiche_restart_flag_quic_testonly_default_false, false, false, "A testonly restart flag that will always default to false.")
diff --git a/quiche/quic/core/http/quic_spdy_stream.cc b/quiche/quic/core/http/quic_spdy_stream.cc
index 70a6b2e..bc00e87 100644
--- a/quiche/quic/core/http/quic_spdy_stream.cc
+++ b/quiche/quic/core/http/quic_spdy_stream.cc
@@ -430,6 +430,8 @@
       send_buffer().stream_offset() + header.size());
   QUIC_DVLOG(1) << ENDPOINT << "Stream " << id()
                 << " is writing DATA frame header of length " << header.size();
+  // TODO: b/417402601 - once we always use inlining send buffer, the code below
+  // should always use WriteOrBufferData.
   if (can_write) {
     // Save one copy and allocation if send buffer can accomodate the header.
     quiche::QuicheMemSlice header_slice(std::move(header));
diff --git a/quiche/quic/core/quic_stream.cc b/quiche/quic/core/quic_stream.cc
index 2d28091..63e4253 100644
--- a/quiche/quic/core/quic_stream.cc
+++ b/quiche/quic/core/quic_stream.cc
@@ -5,17 +5,35 @@
 #include "quiche/quic/core/quic_stream.h"
 
 #include <algorithm>
+#include <cstdint>
 #include <limits>
+#include <memory>
 #include <optional>
 #include <string>
 #include <utility>
 
 #include "absl/strings/str_cat.h"
 #include "absl/strings/string_view.h"
+#include "absl/types/span.h"
+#include "quiche/http2/core/spdy_protocol.h"
+#include "quiche/quic/core/frames/quic_connection_close_frame.h"
 #include "quiche/quic/core/frames/quic_reset_stream_at_frame.h"
+#include "quiche/quic/core/frames/quic_rst_stream_frame.h"
+#include "quiche/quic/core/frames/quic_stream_frame.h"
+#include "quiche/quic/core/frames/quic_window_update_frame.h"
+#include "quiche/quic/core/quic_ack_listener_interface.h"
+#include "quiche/quic/core/quic_constants.h"
+#include "quiche/quic/core/quic_data_writer.h"
 #include "quiche/quic/core/quic_error_codes.h"
 #include "quiche/quic/core/quic_flow_controller.h"
+#include "quiche/quic/core/quic_interval_set.h"
 #include "quiche/quic/core/quic_session.h"
+#include "quiche/quic/core/quic_stream_priority.h"
+#include "quiche/quic/core/quic_stream_send_buffer.h"
+#include "quiche/quic/core/quic_stream_send_buffer_base.h"
+#include "quiche/quic/core/quic_stream_send_buffer_inlining.h"
+#include "quiche/quic/core/quic_stream_sequencer.h"
+#include "quiche/quic/core/quic_time.h"
 #include "quiche/quic/core/quic_types.h"
 #include "quiche/quic/core/quic_utils.h"
 #include "quiche/quic/core/quic_versions.h"
@@ -24,6 +42,8 @@
 #include "quiche/quic/platform/api/quic_flags.h"
 #include "quiche/quic/platform/api/quic_logging.h"
 #include "quiche/common/platform/api/quiche_logging.h"
+#include "quiche/common/platform/api/quiche_reference_counted.h"
+#include "quiche/common/quiche_buffer_allocator.h"
 #include "quiche/common/quiche_mem_slice.h"
 
 using spdy::SpdyPriority;
@@ -109,6 +129,17 @@
   return DefaultFlowControlWindow(version);
 }
 
+std::unique_ptr<QuicStreamSendBufferBase> CreateSendBuffer(
+    QuicSession* session) {
+  quiche::QuicheBufferAllocator* allocator =
+      session->connection()->helper()->GetStreamSendBufferAllocator();
+  if (GetQuicReloadableFlag(quic_use_inlining_send_buffer2)) {
+    QUIC_RELOADABLE_FLAG_COUNT(quic_use_inlining_send_buffer2);
+    return std::make_unique<QuicStreamSendBufferInlining>(allocator);
+  }
+  return std::make_unique<QuicStreamSendBuffer>(allocator);
+}
+
 }  // namespace
 
 PendingStream::PendingStream(QuicStreamId id, QuicSession* session)
@@ -387,8 +418,7 @@
       stream_contributes_to_connection_flow_control_(true),
       busy_counter_(0),
       add_random_padding_after_fin_(false),
-      send_buffer_(std::make_unique<QuicStreamSendBuffer>(
-          session->connection()->helper()->GetStreamSendBufferAllocator())),
+      send_buffer_(CreateSendBuffer(session)),
       buffered_data_threshold_(GetQuicFlag(quic_buffered_data_threshold)),
       is_static_(is_static),
       deadline_(QuicTime::Zero()),
diff --git a/quiche/quic/core/quic_stream_send_buffer.cc b/quiche/quic/core/quic_stream_send_buffer.cc
index f2b9f51..ad78782 100644
--- a/quiche/quic/core/quic_stream_send_buffer.cc
+++ b/quiche/quic/core/quic_stream_send_buffer.cc
@@ -184,4 +184,13 @@
   return last_slice;
 }
 
+QuicByteCount QuicStreamSendBuffer::TotalDataBufferedForTest() {
+  QuicByteCount length = 0;
+  for (auto slice = interval_deque_.DataBegin();
+       slice != interval_deque_.DataEnd(); ++slice) {
+    length += slice->slice.length();
+  }
+  return length;
+}
+
 }  // namespace quic
diff --git a/quiche/quic/core/quic_stream_send_buffer.h b/quiche/quic/core/quic_stream_send_buffer.h
index fa88e6d..d9b25c3 100644
--- a/quiche/quic/core/quic_stream_send_buffer.h
+++ b/quiche/quic/core/quic_stream_send_buffer.h
@@ -83,6 +83,7 @@
 
   void SetStreamOffsetForTest(QuicStreamOffset new_offset) override;
   absl::string_view LatestWriteForTest() override;
+  QuicByteCount TotalDataBufferedForTest() override;
 
  private:
   friend class test::QuicStreamSendBufferPeer;
diff --git a/quiche/quic/core/quic_stream_send_buffer_base.h b/quiche/quic/core/quic_stream_send_buffer_base.h
index b2d32ff..9a14b34 100644
--- a/quiche/quic/core/quic_stream_send_buffer_base.h
+++ b/quiche/quic/core/quic_stream_send_buffer_base.h
@@ -114,6 +114,7 @@
 
   virtual void SetStreamOffsetForTest(QuicStreamOffset new_offset);
   virtual absl::string_view LatestWriteForTest() = 0;
+  virtual QuicByteCount TotalDataBufferedForTest() = 0;
 
  private:
   friend class test::QuicStreamSendBufferPeer;
diff --git a/quiche/quic/core/quic_stream_send_buffer_inlining.cc b/quiche/quic/core/quic_stream_send_buffer_inlining.cc
new file mode 100644
index 0000000..bd4924e
--- /dev/null
+++ b/quiche/quic/core/quic_stream_send_buffer_inlining.cc
@@ -0,0 +1,232 @@
+// Copyright (c) 2017 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 "quiche/quic/core/quic_stream_send_buffer_inlining.h"
+
+#include <algorithm>
+#include <cstddef>
+#include <utility>
+
+#include "absl/strings/string_view.h"
+#include "absl/types/span.h"
+#include "quiche/quic/core/quic_data_writer.h"
+#include "quiche/quic/core/quic_interval.h"
+#include "quiche/quic/core/quic_interval_set.h"
+#include "quiche/quic/core/quic_stream_send_buffer_base.h"
+#include "quiche/quic/core/quic_types.h"
+#include "quiche/quic/platform/api/quic_bug_tracker.h"
+#include "quiche/quic/platform/api/quic_flags.h"
+#include "quiche/quic/platform/api/quic_logging.h"
+#include "quiche/common/platform/api/quiche_logging.h"
+#include "quiche/common/quiche_buffer_allocator.h"
+#include "quiche/common/quiche_mem_slice.h"
+
+namespace quic {
+
+namespace {
+
+struct CompareOffset {
+  bool operator()(const BufferedSliceInlining& slice,
+                  QuicStreamOffset offset) const {
+    return slice.offset + slice.slice.size() < offset;
+  }
+};
+
+constexpr bool WillInline(absl::string_view data) {
+  return data.size() <= kSendBufferMaxInlinedSize;
+}
+
+}  // namespace
+
+BufferedSliceInlining::BufferedSliceInlining(absl::string_view slice,
+                                             QuicStreamOffset offset)
+    : slice(slice), offset(offset) {}
+
+BufferedSliceInlining::BufferedSliceInlining(BufferedSliceInlining&& other) =
+    default;
+
+BufferedSliceInlining& BufferedSliceInlining::operator=(
+    BufferedSliceInlining&& other) = default;
+
+BufferedSliceInlining::~BufferedSliceInlining() {}
+
+QuicInterval<std::size_t> BufferedSliceInlining::interval() const {
+  const std::size_t length = slice.size();
+  return QuicInterval<std::size_t>(offset, offset + length);
+}
+
+QuicStreamSendBufferInlining::QuicStreamSendBufferInlining(
+    quiche::QuicheBufferAllocator* allocator)
+    : allocator_(allocator) {}
+
+void QuicStreamSendBufferInlining::SaveStreamData(absl::string_view data) {
+  QUIC_DVLOG(2) << "Save stream data offset " << stream_offset_ << " length "
+                << data.length();
+  QUICHE_DCHECK(!data.empty());
+
+  if (WillInline(data)) {
+    // Skip memory allocation for inlined writes.
+    SaveMemSlice(quiche::QuicheMemSlice(
+        data.data(), data.size(), +[](absl::string_view) {}));
+    return;
+  }
+
+  // Latch the maximum data slice size.
+  const QuicByteCount max_data_slice_size =
+      GetQuicFlag(quic_send_buffer_max_data_slice_size);
+  while (!data.empty()) {
+    auto slice_len = std::min<absl::string_view::size_type>(
+        data.length(), max_data_slice_size);
+    auto buffer =
+        quiche::QuicheBuffer::Copy(allocator_, data.substr(0, slice_len));
+    SaveMemSlice(quiche::QuicheMemSlice(std::move(buffer)));
+
+    data = data.substr(slice_len);
+  }
+}
+
+void QuicStreamSendBufferInlining::SaveMemSlice(quiche::QuicheMemSlice slice) {
+  QUIC_DVLOG(2) << "Save slice offset " << stream_offset_ << " length "
+                << slice.length();
+  if (slice.empty()) {
+    QUIC_BUG(quic_bug_10853_1) << "Try to save empty MemSlice to send buffer.";
+    return;
+  }
+  const absl::string_view data = slice.AsStringView();
+  const bool is_inlined = WillInline(data);
+  interval_deque_.PushBack(BufferedSliceInlining(data, stream_offset_));
+  QUICHE_DCHECK_EQ(interval_deque_.DataAt(stream_offset_)->slice.IsInlined(),
+                   is_inlined);
+  if (!is_inlined) {
+    auto [it, success] =
+        owned_slices_.emplace(stream_offset_, std::move(slice));
+    QUICHE_DCHECK(success);
+  }
+  stream_offset_ += data.size();
+}
+
+QuicByteCount QuicStreamSendBufferInlining::SaveMemSliceSpan(
+    absl::Span<quiche::QuicheMemSlice> span) {
+  QuicByteCount total = 0;
+  for (quiche::QuicheMemSlice& slice : span) {
+    if (slice.empty()) {
+      // Skip empty slices.
+      continue;
+    }
+    total += slice.length();
+    SaveMemSlice(std::move(slice));
+  }
+  return total;
+}
+
+bool QuicStreamSendBufferInlining::WriteStreamData(QuicStreamOffset offset,
+                                                   QuicByteCount data_length,
+                                                   QuicDataWriter* writer) {
+  // 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.size() - 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(quic_bug_10853_2) << "Writer fails to write.";
+      return false;
+    }
+    offset += copy_length;
+    data_length -= copy_length;
+  }
+  return data_length == 0;
+}
+
+bool QuicStreamSendBufferInlining::FreeMemSlices(QuicStreamOffset start,
+                                                 QuicStreamOffset end) {
+  auto it = interval_deque_.DataBegin();
+  if (it == interval_deque_.DataEnd() || it->slice.empty()) {
+    QUIC_BUG(quic_bug_10853_4)
+        << "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(quic_bug_10853_5)
+        << "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.size())) {
+      ClearSlice(*it);
+    }
+  }
+  return true;
+}
+
+void QuicStreamSendBufferInlining::CleanUpBufferedSlices() {
+  while (!interval_deque_.Empty() &&
+         interval_deque_.DataBegin()->slice.empty()) {
+    interval_deque_.PopFront();
+  }
+}
+
+size_t QuicStreamSendBufferInlining::size() const {
+  return interval_deque_.Size();
+}
+
+void QuicStreamSendBufferInlining::SetStreamOffsetForTest(
+    QuicStreamOffset new_offset) {
+  QuicStreamSendBufferBase::SetStreamOffsetForTest(new_offset);
+  stream_offset_ = new_offset;
+}
+
+absl::string_view QuicStreamSendBufferInlining::LatestWriteForTest() {
+  absl::string_view last_slice = "";
+  for (auto it = interval_deque_.DataBegin(); it != interval_deque_.DataEnd();
+       ++it) {
+    last_slice = it->slice.view();
+  }
+  return last_slice;
+}
+
+void QuicStreamSendBufferInlining::ClearSlice(BufferedSliceInlining& slice) {
+  if (slice.slice.empty()) {
+    return;
+  }
+  const bool was_inlined = slice.slice.IsInlined();
+  slice.slice.clear();
+  if (!was_inlined) {
+    bool deleted = owned_slices_.erase(slice.offset);
+    QUICHE_DCHECK(deleted);
+  }
+}
+
+QuicByteCount QuicStreamSendBufferInlining::TotalDataBufferedForTest() {
+  QuicByteCount length = 0;
+  for (auto slice = interval_deque_.DataBegin();
+       slice != interval_deque_.DataEnd(); ++slice) {
+    length += slice->slice.size();
+  }
+  return length;
+}
+
+}  // namespace quic
diff --git a/quiche/quic/core/quic_stream_send_buffer_inlining.h b/quiche/quic/core/quic_stream_send_buffer_inlining.h
new file mode 100644
index 0000000..6cb0f95
--- /dev/null
+++ b/quiche/quic/core/quic_stream_send_buffer_inlining.h
@@ -0,0 +1,126 @@
+// Copyright (c) 2017 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_STREAM_SEND_BUFFER_INLINING_H_
+#define QUICHE_QUIC_CORE_QUIC_STREAM_SEND_BUFFER_INLINING_H_
+
+#include <cstddef>
+
+#include "absl/container/flat_hash_map.h"
+#include "absl/strings/string_view.h"
+#include "absl/types/span.h"
+#include "quiche/quic/core/quic_inlined_string_view.h"
+#include "quiche/quic/core/quic_interval.h"
+#include "quiche/quic/core/quic_interval_deque.h"
+#include "quiche/quic/core/quic_stream_send_buffer_base.h"
+#include "quiche/quic/core/quic_types.h"
+#include "quiche/common/platform/api/quiche_export.h"
+#include "quiche/common/quiche_buffer_allocator.h"
+#include "quiche/common/quiche_mem_slice.h"
+
+namespace quic {
+
+namespace test {
+class QuicStreamSendBufferPeer;
+}
+
+class QuicDataWriter;
+
+constexpr size_t kSendBufferMaxInlinedSize = 15;
+
+// BufferedSliceInlining is an entry in the send buffer.  It contains a pointer
+// to the buffered data (or data itself, if it is inlined), the size of the data
+// and the offset in the buffer.
+//
+// BufferedSliceInlining does not own contents of the slice; those are freed
+// separately. Since we perform a search over an array of BufferedSliceInlining,
+// it is important for this data structure to be compact.
+struct QUICHE_EXPORT BufferedSliceInlining {
+  BufferedSliceInlining(absl::string_view slice, QuicStreamOffset offset);
+  BufferedSliceInlining(BufferedSliceInlining&& other);
+  BufferedSliceInlining& operator=(BufferedSliceInlining&& other);
+
+  BufferedSliceInlining(const BufferedSliceInlining& other) = delete;
+  BufferedSliceInlining& operator=(const BufferedSliceInlining& other) = delete;
+  ~BufferedSliceInlining();
+
+  // Return an interval representing the offset and length.
+  QuicInterval<std::size_t> interval() const;
+
+  // Stream data of this data slice.
+  QuicInlinedStringView<kSendBufferMaxInlinedSize + 1> slice;
+
+  // Location of this data slice in the stream.
+  QuicStreamOffset offset;
+};
+
+// QuicStreamSendBuffer contains all of the outstanding (provided by the
+// application and not yet acknowledged by the peer) stream data.  Internally it
+// is a circular deque of (potentially inlined) QuicheMemSlices, indexed by the
+// offset in the stream.  The stream can be accessed randomly in O(log(n)) time,
+// though if the offsets are accessed sequentially, the access will be O(1).
+class QUICHE_EXPORT QuicStreamSendBufferInlining
+    : public QuicStreamSendBufferBase {
+ public:
+  explicit QuicStreamSendBufferInlining(
+      quiche::QuicheBufferAllocator* allocator);
+
+  // Save |data| to send buffer.
+  void SaveStreamData(absl::string_view data) override;
+
+  // Save |slice| to send buffer.
+  void SaveMemSlice(quiche::QuicheMemSlice slice) override;
+
+  // Save all slices in |span| to send buffer. Return total bytes saved.
+  QuicByteCount SaveMemSliceSpan(
+      absl::Span<quiche::QuicheMemSlice> span) override;
+
+  // Write |data_length| of data starts at |offset|. Returns true if all data
+  // was successfully written. Returns false if the writer fails to write, or if
+  // the data was already marked as acked, or if the data was never saved in the
+  // first place.
+  bool WriteStreamData(QuicStreamOffset offset, QuicByteCount data_length,
+                       QuicDataWriter* writer) override;
+
+  // Number of data slices in send buffer.
+  size_t size() const override;
+
+  QuicStreamOffset stream_offset() const override { return stream_offset_; }
+
+  void SetStreamOffsetForTest(QuicStreamOffset new_offset) override;
+  absl::string_view LatestWriteForTest() override;
+  QuicByteCount TotalDataBufferedForTest() override;
+
+ private:
+  friend class test::QuicStreamSendBufferPeer;
+
+  // Called when data within offset [start, end) gets acked. Frees fully
+  // acked buffered slices if any. Returns false if the corresponding data does
+  // not exist or has been acked.
+  bool FreeMemSlices(QuicStreamOffset start, QuicStreamOffset end) override;
+
+  // Cleanup acked data from the start of the interval.
+  void CleanUpBufferedSlices() override;
+
+  // Frees an individual buffered slice.
+  void ClearSlice(BufferedSliceInlining& slice);
+
+  // Contains actual stream data.
+  QuicIntervalDeque<BufferedSliceInlining> interval_deque_;
+
+  // Offset of next inserted byte.
+  QuicStreamOffset stream_offset_ = 0;
+
+  // For slices that are not inlined, contains a map from the offset of the
+  // slice in the buffer to the slice release callback.  Those are stored
+  // separately from `interval_deque_`, since the callbacks themselves can be
+  // quite large, and for many slices, those would not be present.
+  absl::flat_hash_map<QuicStreamOffset, quiche::QuicheMemSlice> owned_slices_;
+
+  quiche::QuicheBufferAllocator* allocator_;
+};
+
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_CORE_QUIC_STREAM_SEND_BUFFER_INLINING_H_
diff --git a/quiche/quic/core/quic_stream_send_buffer_test.cc b/quiche/quic/core/quic_stream_send_buffer_test.cc
index d74a757..312f502 100644
--- a/quiche/quic/core/quic_stream_send_buffer_test.cc
+++ b/quiche/quic/core/quic_stream_send_buffer_test.cc
@@ -4,29 +4,58 @@
 
 #include "quiche/quic/core/quic_stream_send_buffer.h"
 
+#include <cstddef>
+#include <cstring>
+#include <memory>
 #include <string>
 #include <utility>
 #include <vector>
 
+#include "absl/strings/str_cat.h"
 #include "absl/strings/string_view.h"
+#include "absl/types/span.h"
 #include "quiche/quic/core/quic_data_writer.h"
+#include "quiche/quic/core/quic_stream_send_buffer_base.h"
+#include "quiche/quic/core/quic_stream_send_buffer_inlining.h"
+#include "quiche/quic/core/quic_types.h"
 #include "quiche/quic/platform/api/quic_expect_bug.h"
 #include "quiche/quic/platform/api/quic_flags.h"
-#include "quiche/quic/platform/api/quic_test.h"
 #include "quiche/quic/test_tools/quic_stream_send_buffer_peer.h"
 #include "quiche/quic/test_tools/quic_test_utils.h"
+#include "quiche/common/platform/api/quiche_test.h"
+#include "quiche/common/quiche_buffer_allocator.h"
+#include "quiche/common/quiche_endian.h"
+#include "quiche/common/quiche_mem_slice.h"
 #include "quiche/common/simple_buffer_allocator.h"
 
 namespace quic {
 namespace test {
 namespace {
 
-class QuicStreamSendBufferTest : public QuicTest {
+enum class SendBufferType {
+  kDefault,
+  kInlining,
+};
+
+std::string SendBufferTypeName(
+    const testing::TestParamInfo<SendBufferType>& type) {
+  switch (type.param) {
+    case SendBufferType::kDefault:
+      return "Default";
+    case SendBufferType::kInlining:
+      return "Inlining";
+  }
+  return "<invalid>";
+}
+
+class QuicStreamSendBufferTest
+    : public quiche::test::QuicheTestWithParam<SendBufferType> {
  public:
-  QuicStreamSendBufferTest() : send_buffer_(&allocator_) {
-    EXPECT_EQ(0u, send_buffer_.size());
-    EXPECT_EQ(0u, send_buffer_.stream_bytes_written());
-    EXPECT_EQ(0u, send_buffer_.stream_bytes_outstanding());
+  QuicStreamSendBufferTest() {
+    send_buffer_ = CreateBuffer();
+    EXPECT_EQ(0u, send_buffer_->size());
+    EXPECT_EQ(0u, send_buffer_->stream_bytes_written());
+    EXPECT_EQ(0u, send_buffer_->stream_bytes_outstanding());
 
     std::string data1 = absl::StrCat(
         std::string(1536, 'a'), std::string(256, 'b'), std::string(256, 'c'));
@@ -41,37 +70,52 @@
 
     // `data` will be split into two BufferedSlices.
     SetQuicFlag(quic_send_buffer_max_data_slice_size, 1024);
-    send_buffer_.SaveStreamData(data1);
+    send_buffer_->SaveStreamData(data1);
 
-    send_buffer_.SaveMemSlice(std::move(slice1));
+    send_buffer_->SaveMemSlice(std::move(slice1));
     EXPECT_TRUE(slice1.empty());
-    send_buffer_.SaveMemSlice(std::move(slice2));
+    send_buffer_->SaveMemSlice(std::move(slice2));
     EXPECT_TRUE(slice2.empty());
 
-    EXPECT_EQ(4u, send_buffer_.size());
-    // At this point, `send_buffer_.interval_deque_` looks like this:
+    EXPECT_EQ(4u, send_buffer_->size());
+    // At this point, `send_buffer_->interval_deque_` looks like this:
     // BufferedSlice1: 'a' * 1024
     // BufferedSlice2: 'a' * 512 + 'b' * 256 + 'c' * 256
     // BufferedSlice3: 'c' * 1024
     // BufferedSlice4: 'd' * 768
   }
 
+  std::unique_ptr<QuicStreamSendBufferBase> CreateBuffer() {
+    switch (GetParam()) {
+      case SendBufferType::kDefault:
+        return std::make_unique<QuicStreamSendBuffer>(&allocator_);
+      case SendBufferType::kInlining:
+        return std::make_unique<QuicStreamSendBufferInlining>(&allocator_);
+    }
+    return nullptr;
+  }
+
   void WriteAllData() {
     // Write all data.
     char buf[4000];
     QuicDataWriter writer(4000, buf, quiche::HOST_BYTE_ORDER);
-    EXPECT_TRUE(send_buffer_.WriteStreamData(0, 3840u, &writer));
+    EXPECT_TRUE(send_buffer_->WriteStreamData(0, 3840u, &writer));
 
-    send_buffer_.OnStreamDataConsumed(3840u);
-    EXPECT_EQ(3840u, send_buffer_.stream_bytes_written());
-    EXPECT_EQ(3840u, send_buffer_.stream_bytes_outstanding());
+    send_buffer_->OnStreamDataConsumed(3840u);
+    EXPECT_EQ(3840u, send_buffer_->stream_bytes_written());
+    EXPECT_EQ(3840u, send_buffer_->stream_bytes_outstanding());
   }
 
   quiche::SimpleBufferAllocator allocator_;
-  QuicStreamSendBuffer send_buffer_;
+  std::unique_ptr<QuicStreamSendBufferBase> send_buffer_;
 };
 
-TEST_F(QuicStreamSendBufferTest, CopyDataToBuffer) {
+INSTANTIATE_TEST_SUITE_P(QuicStreamSendBufferTests, QuicStreamSendBufferTest,
+                         testing::Values(SendBufferType::kDefault,
+                                         SendBufferType::kInlining),
+                         SendBufferTypeName);
+
+TEST_P(QuicStreamSendBufferTest, CopyDataToBuffer) {
   char buf[4000];
   QuicDataWriter writer(4000, buf, quiche::HOST_BYTE_ORDER);
   std::string copy1(1024, 'a');
@@ -80,38 +124,38 @@
   std::string copy3(1024, 'c');
   std::string copy4(768, 'd');
 
-  ASSERT_TRUE(send_buffer_.WriteStreamData(0, 1024, &writer));
+  ASSERT_TRUE(send_buffer_->WriteStreamData(0, 1024, &writer));
   EXPECT_EQ(copy1, absl::string_view(buf, 1024));
-  ASSERT_TRUE(send_buffer_.WriteStreamData(1024, 1024, &writer));
+  ASSERT_TRUE(send_buffer_->WriteStreamData(1024, 1024, &writer));
   EXPECT_EQ(copy2, absl::string_view(buf + 1024, 1024));
-  ASSERT_TRUE(send_buffer_.WriteStreamData(2048, 1024, &writer));
+  ASSERT_TRUE(send_buffer_->WriteStreamData(2048, 1024, &writer));
   EXPECT_EQ(copy3, absl::string_view(buf + 2048, 1024));
-  ASSERT_TRUE(send_buffer_.WriteStreamData(3072, 768, &writer));
+  ASSERT_TRUE(send_buffer_->WriteStreamData(3072, 768, &writer));
   EXPECT_EQ(copy4, absl::string_view(buf + 3072, 768));
 
   // Test data piece across boundries.
   QuicDataWriter writer2(4000, buf, quiche::HOST_BYTE_ORDER);
   std::string copy5 =
       std::string(536, 'a') + std::string(256, 'b') + std::string(232, 'c');
-  ASSERT_TRUE(send_buffer_.WriteStreamData(1000, 1024, &writer2));
+  ASSERT_TRUE(send_buffer_->WriteStreamData(1000, 1024, &writer2));
   EXPECT_EQ(copy5, absl::string_view(buf, 1024));
-  ASSERT_TRUE(send_buffer_.WriteStreamData(2500, 1024, &writer2));
+  ASSERT_TRUE(send_buffer_->WriteStreamData(2500, 1024, &writer2));
   std::string copy6 = std::string(572, 'c') + std::string(452, 'd');
   EXPECT_EQ(copy6, absl::string_view(buf + 1024, 1024));
 
   // Invalid data copy.
   QuicDataWriter writer3(4000, buf, quiche::HOST_BYTE_ORDER);
-  EXPECT_FALSE(send_buffer_.WriteStreamData(3000, 1024, &writer3));
-  EXPECT_QUIC_BUG(send_buffer_.WriteStreamData(0, 4000, &writer3),
+  EXPECT_FALSE(send_buffer_->WriteStreamData(3000, 1024, &writer3));
+  EXPECT_QUIC_BUG(send_buffer_->WriteStreamData(0, 4000, &writer3),
                   "Writer fails to write.");
 
-  send_buffer_.OnStreamDataConsumed(3840);
-  EXPECT_EQ(3840u, send_buffer_.stream_bytes_written());
-  EXPECT_EQ(3840u, send_buffer_.stream_bytes_outstanding());
+  send_buffer_->OnStreamDataConsumed(3840);
+  EXPECT_EQ(3840u, send_buffer_->stream_bytes_written());
+  EXPECT_EQ(3840u, send_buffer_->stream_bytes_outstanding());
 }
 
 // Regression test for b/143491027.
-TEST_F(QuicStreamSendBufferTest,
+TEST_P(QuicStreamSendBufferTest,
        WriteStreamDataContainsBothRetransmissionAndNewData) {
   std::string copy1(1024, 'a');
   std::string copy2 =
@@ -120,180 +164,185 @@
   char buf[6000];
   QuicDataWriter writer(6000, buf, quiche::HOST_BYTE_ORDER);
   // Write more than one slice.
-  EXPECT_EQ(0, QuicStreamSendBufferPeer::write_index(&send_buffer_));
-  ASSERT_TRUE(send_buffer_.WriteStreamData(0, 1024, &writer));
+  if (GetParam() == SendBufferType::kDefault) {
+    EXPECT_EQ(0, QuicStreamSendBufferPeer::write_index(
+                     static_cast<QuicStreamSendBuffer*>(send_buffer_.get())));
+  }
+  ASSERT_TRUE(send_buffer_->WriteStreamData(0, 1024, &writer));
   EXPECT_EQ(copy1, absl::string_view(buf, 1024));
-  EXPECT_EQ(1, QuicStreamSendBufferPeer::write_index(&send_buffer_));
+  if (GetParam() == SendBufferType::kDefault) {
+    EXPECT_EQ(1, QuicStreamSendBufferPeer::write_index(
+                     static_cast<QuicStreamSendBuffer*>(send_buffer_.get())));
+  }
 
   // Retransmit the first frame and also send new data.
-  ASSERT_TRUE(send_buffer_.WriteStreamData(0, 2048, &writer));
+  ASSERT_TRUE(send_buffer_->WriteStreamData(0, 2048, &writer));
   EXPECT_EQ(copy1 + copy2, absl::string_view(buf + 1024, 2048));
 
   // Write new data.
-  ASSERT_TRUE(send_buffer_.WriteStreamData(2048, 50, &writer));
+  ASSERT_TRUE(send_buffer_->WriteStreamData(2048, 50, &writer));
   EXPECT_EQ(std::string(50, 'c'), absl::string_view(buf + 1024 + 2048, 50));
-  ASSERT_TRUE(send_buffer_.WriteStreamData(2048, 1124, &writer));
+  ASSERT_TRUE(send_buffer_->WriteStreamData(2048, 1124, &writer));
   EXPECT_EQ(copy3, absl::string_view(buf + 1024 + 2048 + 50, 1124));
 }
 
-TEST_F(QuicStreamSendBufferTest, RemoveStreamFrame) {
+TEST_P(QuicStreamSendBufferTest, RemoveStreamFrame) {
   WriteAllData();
 
   QuicByteCount newly_acked_length;
-  EXPECT_TRUE(send_buffer_.OnStreamDataAcked(1024, 1024, &newly_acked_length));
+  EXPECT_TRUE(send_buffer_->OnStreamDataAcked(1024, 1024, &newly_acked_length));
   EXPECT_EQ(1024u, newly_acked_length);
-  EXPECT_EQ(4u, send_buffer_.size());
+  EXPECT_EQ(4u, send_buffer_->size());
 
-  EXPECT_TRUE(send_buffer_.OnStreamDataAcked(2048, 1024, &newly_acked_length));
+  EXPECT_TRUE(send_buffer_->OnStreamDataAcked(2048, 1024, &newly_acked_length));
   EXPECT_EQ(1024u, newly_acked_length);
-  EXPECT_EQ(4u, send_buffer_.size());
+  EXPECT_EQ(4u, send_buffer_->size());
 
-  EXPECT_TRUE(send_buffer_.OnStreamDataAcked(0, 1024, &newly_acked_length));
+  EXPECT_TRUE(send_buffer_->OnStreamDataAcked(0, 1024, &newly_acked_length));
   EXPECT_EQ(1024u, newly_acked_length);
 
   // Send buffer is cleaned up in order.
-  EXPECT_EQ(1u, send_buffer_.size());
-  EXPECT_TRUE(send_buffer_.OnStreamDataAcked(3072, 768, &newly_acked_length));
+  EXPECT_EQ(1u, send_buffer_->size());
+  EXPECT_TRUE(send_buffer_->OnStreamDataAcked(3072, 768, &newly_acked_length));
   EXPECT_EQ(768u, newly_acked_length);
-  EXPECT_EQ(0u, send_buffer_.size());
+  EXPECT_EQ(0u, send_buffer_->size());
 }
 
-TEST_F(QuicStreamSendBufferTest, RemoveStreamFrameAcrossBoundaries) {
+TEST_P(QuicStreamSendBufferTest, RemoveStreamFrameAcrossBoundaries) {
   WriteAllData();
 
   QuicByteCount newly_acked_length;
-  EXPECT_TRUE(send_buffer_.OnStreamDataAcked(2024, 576, &newly_acked_length));
+  EXPECT_TRUE(send_buffer_->OnStreamDataAcked(2024, 576, &newly_acked_length));
   EXPECT_EQ(576u, newly_acked_length);
-  EXPECT_EQ(4u, send_buffer_.size());
+  EXPECT_EQ(4u, send_buffer_->size());
 
-  EXPECT_TRUE(send_buffer_.OnStreamDataAcked(0, 1000, &newly_acked_length));
+  EXPECT_TRUE(send_buffer_->OnStreamDataAcked(0, 1000, &newly_acked_length));
   EXPECT_EQ(1000u, newly_acked_length);
-  EXPECT_EQ(4u, send_buffer_.size());
+  EXPECT_EQ(4u, send_buffer_->size());
 
-  EXPECT_TRUE(send_buffer_.OnStreamDataAcked(1000, 1024, &newly_acked_length));
+  EXPECT_TRUE(send_buffer_->OnStreamDataAcked(1000, 1024, &newly_acked_length));
   EXPECT_EQ(1024u, newly_acked_length);
   // Send buffer is cleaned up in order.
-  EXPECT_EQ(2u, send_buffer_.size());
+  EXPECT_EQ(2u, send_buffer_->size());
 
-  EXPECT_TRUE(send_buffer_.OnStreamDataAcked(2600, 1024, &newly_acked_length));
+  EXPECT_TRUE(send_buffer_->OnStreamDataAcked(2600, 1024, &newly_acked_length));
   EXPECT_EQ(1024u, newly_acked_length);
-  EXPECT_EQ(1u, send_buffer_.size());
+  EXPECT_EQ(1u, send_buffer_->size());
 
-  EXPECT_TRUE(send_buffer_.OnStreamDataAcked(3624, 216, &newly_acked_length));
+  EXPECT_TRUE(send_buffer_->OnStreamDataAcked(3624, 216, &newly_acked_length));
   EXPECT_EQ(216u, newly_acked_length);
-  EXPECT_EQ(0u, send_buffer_.size());
+  EXPECT_EQ(0u, send_buffer_->size());
 }
 
-TEST_F(QuicStreamSendBufferTest, AckStreamDataMultipleTimes) {
+TEST_P(QuicStreamSendBufferTest, AckStreamDataMultipleTimes) {
   WriteAllData();
   QuicByteCount newly_acked_length;
-  EXPECT_TRUE(send_buffer_.OnStreamDataAcked(100, 1500, &newly_acked_length));
+  EXPECT_TRUE(send_buffer_->OnStreamDataAcked(100, 1500, &newly_acked_length));
   EXPECT_EQ(1500u, newly_acked_length);
-  EXPECT_EQ(4u, send_buffer_.size());
+  EXPECT_EQ(4u, send_buffer_->size());
 
-  EXPECT_TRUE(send_buffer_.OnStreamDataAcked(2000, 500, &newly_acked_length));
+  EXPECT_TRUE(send_buffer_->OnStreamDataAcked(2000, 500, &newly_acked_length));
   EXPECT_EQ(500u, newly_acked_length);
-  EXPECT_EQ(4u, send_buffer_.size());
+  EXPECT_EQ(4u, send_buffer_->size());
 
-  EXPECT_TRUE(send_buffer_.OnStreamDataAcked(0, 2600, &newly_acked_length));
+  EXPECT_TRUE(send_buffer_->OnStreamDataAcked(0, 2600, &newly_acked_length));
   EXPECT_EQ(600u, newly_acked_length);
   // Send buffer is cleaned up in order.
-  EXPECT_EQ(2u, send_buffer_.size());
+  EXPECT_EQ(2u, send_buffer_->size());
 
-  EXPECT_TRUE(send_buffer_.OnStreamDataAcked(2200, 1640, &newly_acked_length));
+  EXPECT_TRUE(send_buffer_->OnStreamDataAcked(2200, 1640, &newly_acked_length));
   EXPECT_EQ(1240u, newly_acked_length);
-  EXPECT_EQ(0u, send_buffer_.size());
+  EXPECT_EQ(0u, send_buffer_->size());
 
-  EXPECT_FALSE(send_buffer_.OnStreamDataAcked(4000, 100, &newly_acked_length));
+  EXPECT_FALSE(send_buffer_->OnStreamDataAcked(4000, 100, &newly_acked_length));
 }
 
-TEST_F(QuicStreamSendBufferTest, AckStreamDataOutOfOrder) {
+TEST_P(QuicStreamSendBufferTest, AckStreamDataOutOfOrder) {
   WriteAllData();
   QuicByteCount newly_acked_length;
-  EXPECT_TRUE(send_buffer_.OnStreamDataAcked(500, 1000, &newly_acked_length));
+  EXPECT_TRUE(send_buffer_->OnStreamDataAcked(500, 1000, &newly_acked_length));
   EXPECT_EQ(1000u, newly_acked_length);
-  EXPECT_EQ(4u, send_buffer_.size());
-  EXPECT_EQ(3840u, QuicStreamSendBufferPeer::TotalLength(&send_buffer_));
+  EXPECT_EQ(4u, send_buffer_->size());
+  EXPECT_EQ(3840u, QuicStreamSendBufferPeer::TotalLength(send_buffer_.get()));
 
-  EXPECT_TRUE(send_buffer_.OnStreamDataAcked(1200, 1000, &newly_acked_length));
+  EXPECT_TRUE(send_buffer_->OnStreamDataAcked(1200, 1000, &newly_acked_length));
   EXPECT_EQ(700u, newly_acked_length);
-  EXPECT_EQ(4u, send_buffer_.size());
+  EXPECT_EQ(4u, send_buffer_->size());
   // Slice 2 gets fully acked.
-  EXPECT_EQ(2816u, QuicStreamSendBufferPeer::TotalLength(&send_buffer_));
+  EXPECT_EQ(2816u, QuicStreamSendBufferPeer::TotalLength(send_buffer_.get()));
 
-  EXPECT_TRUE(send_buffer_.OnStreamDataAcked(2000, 1840, &newly_acked_length));
+  EXPECT_TRUE(send_buffer_->OnStreamDataAcked(2000, 1840, &newly_acked_length));
   EXPECT_EQ(1640u, newly_acked_length);
-  EXPECT_EQ(4u, send_buffer_.size());
+  EXPECT_EQ(4u, send_buffer_->size());
   // Slices 3 and 4 get fully acked.
-  EXPECT_EQ(1024u, QuicStreamSendBufferPeer::TotalLength(&send_buffer_));
+  EXPECT_EQ(1024u, QuicStreamSendBufferPeer::TotalLength(send_buffer_.get()));
 
-  EXPECT_TRUE(send_buffer_.OnStreamDataAcked(0, 1000, &newly_acked_length));
+  EXPECT_TRUE(send_buffer_->OnStreamDataAcked(0, 1000, &newly_acked_length));
   EXPECT_EQ(500u, newly_acked_length);
-  EXPECT_EQ(0u, send_buffer_.size());
-  EXPECT_EQ(0u, QuicStreamSendBufferPeer::TotalLength(&send_buffer_));
+  EXPECT_EQ(0u, send_buffer_->size());
+  EXPECT_EQ(0u, QuicStreamSendBufferPeer::TotalLength(send_buffer_.get()));
 }
 
-TEST_F(QuicStreamSendBufferTest, PendingRetransmission) {
+TEST_P(QuicStreamSendBufferTest, PendingRetransmission) {
   WriteAllData();
-  EXPECT_TRUE(send_buffer_.IsStreamDataOutstanding(0, 3840));
-  EXPECT_FALSE(send_buffer_.HasPendingRetransmission());
+  EXPECT_TRUE(send_buffer_->IsStreamDataOutstanding(0, 3840));
+  EXPECT_FALSE(send_buffer_->HasPendingRetransmission());
   // Lost data [0, 1200).
-  send_buffer_.OnStreamDataLost(0, 1200);
+  send_buffer_->OnStreamDataLost(0, 1200);
   // Lost data [1500, 2000).
-  send_buffer_.OnStreamDataLost(1500, 500);
-  EXPECT_TRUE(send_buffer_.HasPendingRetransmission());
+  send_buffer_->OnStreamDataLost(1500, 500);
+  EXPECT_TRUE(send_buffer_->HasPendingRetransmission());
 
   EXPECT_EQ(StreamPendingRetransmission(0, 1200),
-            send_buffer_.NextPendingRetransmission());
+            send_buffer_->NextPendingRetransmission());
   // Retransmit data [0, 500).
-  send_buffer_.OnStreamDataRetransmitted(0, 500);
-  EXPECT_TRUE(send_buffer_.IsStreamDataOutstanding(0, 500));
+  send_buffer_->OnStreamDataRetransmitted(0, 500);
+  EXPECT_TRUE(send_buffer_->IsStreamDataOutstanding(0, 500));
   EXPECT_EQ(StreamPendingRetransmission(500, 700),
-            send_buffer_.NextPendingRetransmission());
+            send_buffer_->NextPendingRetransmission());
   // Ack data [500, 1200).
   QuicByteCount newly_acked_length = 0;
-  EXPECT_TRUE(send_buffer_.OnStreamDataAcked(500, 700, &newly_acked_length));
-  EXPECT_FALSE(send_buffer_.IsStreamDataOutstanding(500, 700));
-  EXPECT_TRUE(send_buffer_.HasPendingRetransmission());
+  EXPECT_TRUE(send_buffer_->OnStreamDataAcked(500, 700, &newly_acked_length));
+  EXPECT_FALSE(send_buffer_->IsStreamDataOutstanding(500, 700));
+  EXPECT_TRUE(send_buffer_->HasPendingRetransmission());
   EXPECT_EQ(StreamPendingRetransmission(1500, 500),
-            send_buffer_.NextPendingRetransmission());
+            send_buffer_->NextPendingRetransmission());
   // Retransmit data [1500, 2000).
-  send_buffer_.OnStreamDataRetransmitted(1500, 500);
-  EXPECT_FALSE(send_buffer_.HasPendingRetransmission());
+  send_buffer_->OnStreamDataRetransmitted(1500, 500);
+  EXPECT_FALSE(send_buffer_->HasPendingRetransmission());
 
   // Lost [200, 800).
-  send_buffer_.OnStreamDataLost(200, 600);
-  EXPECT_TRUE(send_buffer_.HasPendingRetransmission());
+  send_buffer_->OnStreamDataLost(200, 600);
+  EXPECT_TRUE(send_buffer_->HasPendingRetransmission());
   // Verify [200, 500) is considered as lost, as [500, 800) has been acked.
   EXPECT_EQ(StreamPendingRetransmission(200, 300),
-            send_buffer_.NextPendingRetransmission());
+            send_buffer_->NextPendingRetransmission());
 
   // Verify 0 length data is not outstanding.
-  EXPECT_FALSE(send_buffer_.IsStreamDataOutstanding(100, 0));
+  EXPECT_FALSE(send_buffer_->IsStreamDataOutstanding(100, 0));
   // Verify partially acked data is outstanding.
-  EXPECT_TRUE(send_buffer_.IsStreamDataOutstanding(400, 800));
+  EXPECT_TRUE(send_buffer_->IsStreamDataOutstanding(400, 800));
 }
 
-TEST_F(QuicStreamSendBufferTest, OutOfOrderWrites) {
+TEST_P(QuicStreamSendBufferTest, OutOfOrderWrites) {
   char buf[3840] = {};
   // Write data out of order.
   QuicDataWriter writer2(sizeof(buf) - 1000, buf + 1000);
-  EXPECT_TRUE(send_buffer_.WriteStreamData(1000u, 1000u, &writer2));
+  EXPECT_TRUE(send_buffer_->WriteStreamData(1000u, 1000u, &writer2));
   QuicDataWriter writer4(sizeof(buf) - 3000, buf + 3000);
-  EXPECT_TRUE(send_buffer_.WriteStreamData(3000u, 840u, &writer4));
+  EXPECT_TRUE(send_buffer_->WriteStreamData(3000u, 840u, &writer4));
   QuicDataWriter writer3(sizeof(buf) - 2000, buf + 2000);
-  EXPECT_TRUE(send_buffer_.WriteStreamData(2000u, 1000u, &writer3));
+  EXPECT_TRUE(send_buffer_->WriteStreamData(2000u, 1000u, &writer3));
   QuicDataWriter writer1(sizeof(buf), buf);
-  EXPECT_TRUE(send_buffer_.WriteStreamData(0u, 1000u, &writer1));
+  EXPECT_TRUE(send_buffer_->WriteStreamData(0u, 1000u, &writer1));
   // Make sure it is correct.
   EXPECT_EQ(absl::string_view(buf, sizeof(buf)),
             absl::StrCat(std::string(1536, 'a'), std::string(256, 'b'),
                          std::string(1280, 'c'), std::string(768, 'd')));
 }
 
-TEST_F(QuicStreamSendBufferTest, SaveMemSliceSpan) {
-  quiche::SimpleBufferAllocator allocator;
-  QuicStreamSendBuffer send_buffer(&allocator);
+TEST_P(QuicStreamSendBufferTest, SaveMemSliceSpan) {
+  std::unique_ptr<QuicStreamSendBufferBase> send_buffer = CreateBuffer();
 
   std::string data(1024, 'a');
   std::vector<quiche::QuicheMemSlice> buffers;
@@ -301,13 +350,12 @@
     buffers.push_back(MemSliceFromString(data));
   }
 
-  EXPECT_EQ(10 * 1024u, send_buffer.SaveMemSliceSpan(absl::MakeSpan(buffers)));
-  EXPECT_EQ(10u, send_buffer.size());
+  EXPECT_EQ(10 * 1024u, send_buffer->SaveMemSliceSpan(absl::MakeSpan(buffers)));
+  EXPECT_EQ(10u, send_buffer->size());
 }
 
-TEST_F(QuicStreamSendBufferTest, SaveEmptyMemSliceSpan) {
-  quiche::SimpleBufferAllocator allocator;
-  QuicStreamSendBuffer send_buffer(&allocator);
+TEST_P(QuicStreamSendBufferTest, SaveEmptyMemSliceSpan) {
+  std::unique_ptr<QuicStreamSendBufferBase> send_buffer = CreateBuffer();
 
   std::string data(1024, 'a');
   std::vector<quiche::QuicheMemSlice> buffers;
@@ -315,9 +363,23 @@
     buffers.push_back(MemSliceFromString(data));
   }
 
-  EXPECT_EQ(10 * 1024u, send_buffer.SaveMemSliceSpan(absl::MakeSpan(buffers)));
+  EXPECT_EQ(10 * 1024u, send_buffer->SaveMemSliceSpan(absl::MakeSpan(buffers)));
   // Verify the empty slice does not get saved.
-  EXPECT_EQ(10u, send_buffer.size());
+  EXPECT_EQ(10u, send_buffer->size());
+}
+
+TEST_P(QuicStreamSendBufferTest, SmallWrite) {
+  std::unique_ptr<QuicStreamSendBufferBase> send_buffer = CreateBuffer();
+
+  constexpr absl::string_view kData = "abcd";
+  send_buffer->SaveStreamData(kData);
+  EXPECT_EQ(1u, send_buffer->size());
+  EXPECT_EQ(4u, send_buffer->TotalDataBufferedForTest());
+
+  char buffer[16];
+  QuicDataWriter writer(sizeof(buffer), buffer);
+  ASSERT_TRUE(send_buffer->WriteStreamData(0, 4, &writer));
+  EXPECT_EQ(absl::string_view(buffer, 4), kData);
 }
 
 }  // namespace
diff --git a/quiche/quic/test_tools/quic_stream_send_buffer_peer.cc b/quiche/quic/test_tools/quic_stream_send_buffer_peer.cc
index c2c2806..35bca77 100644
--- a/quiche/quic/test_tools/quic_stream_send_buffer_peer.cc
+++ b/quiche/quic/test_tools/quic_stream_send_buffer_peer.cc
@@ -29,13 +29,8 @@
 
 // static
 QuicByteCount QuicStreamSendBufferPeer::TotalLength(
-    QuicStreamSendBuffer* send_buffer) {
-  QuicByteCount length = 0;
-  for (auto slice = send_buffer->interval_deque_.DataBegin();
-       slice != send_buffer->interval_deque_.DataEnd(); ++slice) {
-    length += slice->slice.length();
-  }
-  return length;
+    QuicStreamSendBufferBase* send_buffer) {
+  return send_buffer->TotalDataBufferedForTest();
 }
 
 // static
diff --git a/quiche/quic/test_tools/quic_stream_send_buffer_peer.h b/quiche/quic/test_tools/quic_stream_send_buffer_peer.h
index 8969241..d65a1bf 100644
--- a/quiche/quic/test_tools/quic_stream_send_buffer_peer.h
+++ b/quiche/quic/test_tools/quic_stream_send_buffer_peer.h
@@ -19,7 +19,7 @@
   static const BufferedSlice* CurrentWriteSlice(
       QuicStreamSendBuffer* send_buffer);
 
-  static QuicByteCount TotalLength(QuicStreamSendBuffer* send_buffer);
+  static QuicByteCount TotalLength(QuicStreamSendBufferBase* send_buffer);
 
   static int32_t write_index(QuicStreamSendBuffer* send_buffer);
 };