Rewrite QuicSpdyStreamBodyBuffer's consumed byte tracking algorithm.

This is motivated by having to consume unknown frames (in a later CL), see
cr/258682203 for the end goal.

There is some behavioral change as a side effect of the new algorithm: DATA
frame headers are consumed immediately, not only when some of the corresponding
payload is consumed.  This hinges on the previously undocumented property of HttpDecoder that in only calls Visitor::On*FrameStart() methods after the entire frame header is processed.  This behavioral change necessitates minor updates to tests.

I plan four more CLs after this: one to move HEADERS frame header and
payload consumption calculations from QuicSpdyStream to
QuicSpdyStreamBodyBuffer; one to consume unknown frames; one to rename
QuicSpdyStreamBodyBuffer to QuicSpdyStreamBodyManager or something similar;
and one to remove Http3FrameLengths if by that point it's entirely unused.

gfe-relnote: n/a, change to QUIC v99-only code.  Protected by existing disabled gfe2_reloadable_flag_quic_enable_version_99.
PiperOrigin-RevId: 259987101
Change-Id: Id8a734fb36466b3502373097faba2c6c81c793de
diff --git a/quic/core/http/http_decoder.h b/quic/core/http/http_decoder.h
index 99c4d10..80ba385 100644
--- a/quic/core/http/http_decoder.h
+++ b/quic/core/http/http_decoder.h
@@ -49,6 +49,9 @@
 
     // All the following methods return true to continue decoding,
     // and false to pause it.
+    // On*FrameStart() methods are called after the frame header is completely
+    // processed.  At that point it is safe to consume
+    // |frame_length.header_length| bytes.
 
     // Called when a PRIORITY frame has been received.
     // |frame_length| contains PRIORITY frame length and payload length.
diff --git a/quic/core/http/quic_spdy_stream.cc b/quic/core/http/quic_spdy_stream.cc
index 97991e8..aee3ba5 100644
--- a/quic/core/http/quic_spdy_stream.cc
+++ b/quic/core/http/quic_spdy_stream.cc
@@ -769,7 +769,9 @@
     return false;
   }
 
-  body_buffer_.OnDataHeader(frame_lengths);
+  sequencer()->MarkConsumed(
+      body_buffer_.OnDataHeader(frame_lengths.header_length));
+
   return true;
 }
 
@@ -777,6 +779,7 @@
   DCHECK(VersionHasDataFrameHeader(transport_version()));
 
   body_buffer_.OnDataPayload(payload);
+
   return true;
 }
 
diff --git a/quic/core/http/quic_spdy_stream_body_buffer.cc b/quic/core/http/quic_spdy_stream_body_buffer.cc
index c29c766..2ae72b5 100644
--- a/quic/core/http/quic_spdy_stream_body_buffer.cc
+++ b/quic/core/http/quic_spdy_stream_body_buffer.cc
@@ -11,89 +11,78 @@
 namespace quic {
 
 QuicSpdyStreamBodyBuffer::QuicSpdyStreamBodyBuffer()
-    : bytes_remaining_(0),
-      total_body_bytes_readable_(0),
-      total_body_bytes_received_(0),
-      total_payload_lengths_(0) {}
+    : total_body_bytes_received_(0) {}
 
-void QuicSpdyStreamBodyBuffer::OnDataHeader(Http3FrameLengths frame_lengths) {
-  frame_meta_.push_back(frame_lengths);
-  total_payload_lengths_ += frame_lengths.payload_length;
+size_t QuicSpdyStreamBodyBuffer::OnDataHeader(QuicByteCount length) {
+  DCHECK_NE(0u, length);
+
+  if (fragments_.empty()) {
+    // DATA frame header can be consumed immediately, because all previously
+    // received DATA frame payload bytes have been read.
+    return length;
+  }
+
+  // DATA frame header length will be consumed after last fragment is read.
+  fragments_.back().trailing_consumable_bytes += length;
+  return 0;
 }
 
 void QuicSpdyStreamBodyBuffer::OnDataPayload(QuicStringPiece payload) {
   DCHECK(!payload.empty());
-  bodies_.push_back(payload);
+
+  fragments_.push_back({payload, 0});
   total_body_bytes_received_ += payload.length();
-  total_body_bytes_readable_ += payload.length();
-  DCHECK_LE(total_body_bytes_received_, total_payload_lengths_);
 }
 
 size_t QuicSpdyStreamBodyBuffer::OnBodyConsumed(size_t num_bytes) {
-  // Check if the stream has enough decoded data.
-  if (num_bytes > total_body_bytes_readable_) {
-    QUIC_BUG << "Invalid argument to OnBodyConsumed."
-             << " expect to consume: " << num_bytes
-             << ", but not enough bytes available. "
-             << "Total bytes readable are: " << total_body_bytes_readable_;
-    return 0;
-  }
-  // Discard references in the stream before the sequencer marks them consumed.
-  size_t remaining = num_bytes;
-  while (remaining > 0) {
-    if (bodies_.empty()) {
-      QUIC_BUG << "Failed to consume because body buffer is empty.";
+  QuicByteCount bytes_to_consume = 0;
+  size_t remaining_bytes = num_bytes;
+
+  while (remaining_bytes > 0) {
+    if (fragments_.empty()) {
+      QUIC_BUG << "Not enough available body to consume.";
       return 0;
     }
-    auto body = bodies_.front();
-    bodies_.pop_front();
-    if (body.length() <= remaining) {
-      remaining -= body.length();
-    } else {
-      body = body.substr(remaining, body.length() - remaining);
-      bodies_.push_front(body);
-      remaining = 0;
-    }
-  }
 
-  // Consume headers.
-  size_t bytes_to_consume = 0;
-  while (bytes_remaining_ < num_bytes) {
-    if (frame_meta_.empty()) {
-      QUIC_BUG << "Faild to consume because frame header buffer is empty.";
-      return 0;
-    }
-    auto meta = frame_meta_.front();
-    frame_meta_.pop_front();
-    bytes_remaining_ += meta.payload_length;
-    bytes_to_consume += meta.header_length;
-  }
-  bytes_to_consume += num_bytes;
+    Fragment& fragment = fragments_.front();
+    const QuicStringPiece body = fragment.body;
 
-  // Update accountings.
-  bytes_remaining_ -= num_bytes;
-  total_body_bytes_readable_ -= num_bytes;
+    if (body.length() > remaining_bytes) {
+      // Consume leading |remaining_bytes| bytes of body.
+      bytes_to_consume += remaining_bytes;
+      fragment.body = body.substr(remaining_bytes);
+      return bytes_to_consume;
+    }
+
+    // Consume entire fragment and the following
+    // |trailing_consumable_bytes| bytes.
+    remaining_bytes -= body.length();
+    bytes_to_consume += body.length() + fragment.trailing_consumable_bytes;
+    fragments_.pop_front();
+  }
 
   return bytes_to_consume;
 }
 
 int QuicSpdyStreamBodyBuffer::PeekBody(iovec* iov, size_t iov_len) const {
-  DCHECK(iov != nullptr);
+  DCHECK(iov);
   DCHECK_GT(iov_len, 0u);
 
-  if (bodies_.empty()) {
+  // TODO(bnc): Is this really necessary?
+  if (fragments_.empty()) {
     iov[0].iov_base = nullptr;
     iov[0].iov_len = 0;
     return 0;
   }
-  // Fill iovs with references from the stream.
+
   size_t iov_filled = 0;
-  while (iov_filled < bodies_.size() && iov_filled < iov_len) {
-    QuicStringPiece body = bodies_[iov_filled];
+  while (iov_filled < fragments_.size() && iov_filled < iov_len) {
+    QuicStringPiece body = fragments_[iov_filled].body;
     iov[iov_filled].iov_base = const_cast<char*>(body.data());
     iov[iov_filled].iov_len = body.size();
     iov_filled++;
   }
+
   return iov_filled;
 }
 
@@ -101,32 +90,50 @@
                                           size_t iov_len,
                                           size_t* total_bytes_read) {
   *total_bytes_read = 0;
-  QuicByteCount total_remaining = total_body_bytes_readable_;
+  QuicByteCount bytes_to_consume = 0;
+
+  // The index of iovec to write to.
   size_t index = 0;
-  size_t src_offset = 0;
-  for (size_t i = 0; i < iov_len && total_remaining > 0; ++i) {
-    char* dest = reinterpret_cast<char*>(iov[i].iov_base);
-    size_t dest_remaining = iov[i].iov_len;
-    while (dest_remaining > 0 && total_remaining > 0) {
-      auto body = bodies_[index];
-      size_t bytes_to_copy =
-          std::min<size_t>(body.length() - src_offset, dest_remaining);
-      memcpy(dest, body.substr(src_offset, bytes_to_copy).data(),
-             bytes_to_copy);
+  // Address to write to within current iovec.
+  char* dest = reinterpret_cast<char*>(iov[index].iov_base);
+  // Remaining space in current iovec.
+  size_t dest_remaining = iov[index].iov_len;
+
+  while (!fragments_.empty()) {
+    Fragment& fragment = fragments_.front();
+    const QuicStringPiece body = fragment.body;
+
+    const size_t bytes_to_copy =
+        std::min<size_t>(body.length(), dest_remaining);
+    memcpy(dest, body.data(), bytes_to_copy);
+    bytes_to_consume += bytes_to_copy;
+    *total_bytes_read += bytes_to_copy;
+
+    if (bytes_to_copy == body.length()) {
+      // Entire fragment read.
+      bytes_to_consume += fragment.trailing_consumable_bytes;
+      fragments_.pop_front();
+    } else {
+      // Consume leading |bytes_to_copy| bytes of body.
+      fragment.body = body.substr(bytes_to_copy);
+    }
+
+    if (bytes_to_copy == dest_remaining) {
+      // Current iovec full.
+      ++index;
+      if (index == iov_len) {
+        break;
+      }
+      dest = reinterpret_cast<char*>(iov[index].iov_base);
+      dest_remaining = iov[index].iov_len;
+    } else {
+      // Advance destination parameters within this iovec.
       dest += bytes_to_copy;
       dest_remaining -= bytes_to_copy;
-      *total_bytes_read += bytes_to_copy;
-      total_remaining -= bytes_to_copy;
-      if (bytes_to_copy < body.length() - src_offset) {
-        src_offset += bytes_to_copy;
-      } else {
-        index++;
-        src_offset = 0;
-      }
     }
   }
 
-  return OnBodyConsumed(*total_bytes_read);
+  return bytes_to_consume;
 }
 
 }  // namespace quic
diff --git a/quic/core/http/quic_spdy_stream_body_buffer.h b/quic/core/http/quic_spdy_stream_body_buffer.h
index a37f3bf..77c17b8 100644
--- a/quic/core/http/quic_spdy_stream_body_buffer.h
+++ b/quic/core/http/quic_spdy_stream_body_buffer.h
@@ -5,72 +5,81 @@
 #ifndef QUICHE_QUIC_CORE_HTTP_QUIC_SPDY_STREAM_BODY_BUFFER_H_
 #define QUICHE_QUIC_CORE_HTTP_QUIC_SPDY_STREAM_BODY_BUFFER_H_
 
-#include "net/third_party/quiche/src/quic/core/http/http_decoder.h"
+#include "net/third_party/quiche/src/quic/core/quic_constants.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_export.h"
 #include "net/third_party/quiche/src/quic/platform/api/quic_iovec.h"
 #include "net/third_party/quiche/src/quic/platform/api/quic_macros.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_string_piece.h"
 
 namespace quic {
 
-class QuicStreamSequencer;
-
-// Buffer decoded body for QuicSpdyStream. It also talks to the sequencer to
-// consume data.
+// "Body" means DATA frame payload.
+// QuicSpdyStreamBodyBuffer does two things: it keeps references to body
+// fragments (owned by QuicStreamSequencer) and offers methods to read them; and
+// calculates the total number of bytes (including DATA frame headers) the
+// caller needs to mark consumed (with QuicStreamSequencer) whenever DATA frame
+// headers are received or body bytes are read.
+// TODO(bnc): Rename to QuicSpdyStreamBodyManager or similar.
 class QUIC_EXPORT_PRIVATE QuicSpdyStreamBodyBuffer {
  public:
   QuicSpdyStreamBodyBuffer();
   ~QuicSpdyStreamBodyBuffer() = default;
 
-  // Add metadata of the frame to accountings.
-  // Called when QuicSpdyStream receives data frame header.
-  void OnDataHeader(Http3FrameLengths frame_lengths);
+  // Called when DATA frame header bytes are received.  |length| must be
+  // positive.  Returns number of bytes the caller shall mark consumed, which
+  // might be zero.
+  QUIC_MUST_USE_RESULT size_t OnDataHeader(QuicByteCount length);
 
-  // Add new data payload to buffer.
-  // Called when QuicSpdyStream received data payload.
-  // Data pointed by payload must be alive until consumed by
-  // QuicStreamSequencer::MarkConsumed().
+  // Called when DATA frame payload is received.  |payload| is added to
+  // |fragments_|.  The data pointed to by |payload| must be kept alive until an
+  // OnBodyConsumed() or ReadBody() call consumes it.  |payload| must not be
+  // empty.
   void OnDataPayload(QuicStringPiece payload);
 
-  // Internally marks |num_bytes| of DATA frame payload consumed, and returns
-  // the number of bytes that the caller should mark consumed with the
-  // sequencer, including DATA frame header bytes, if any.
+  // Internally marks |num_bytes| of DATA frame payload consumed.  |num_bytes|
+  // might be zero.  Returns the number of bytes that the caller should mark
+  // consumed with the sequencer, which is the sum of |num_bytes| for payload
+  // and additional DATA frame header bytes, if any.
   QUIC_MUST_USE_RESULT size_t OnBodyConsumed(size_t num_bytes);
 
-  // Fill up to |iov_len| with bodies available in buffer. No data is consumed.
-  // |iov|.iov_base will point to data in the buffer, and |iov|.iov_len will
-  // be set to the underlying data length accordingly.
-  // Returns the number of iov used.
+  // Set up to |iov_len| elements of iov[] to point to available bodies: each
+  // iov[i].iov_base will point to a body fragment, and iov[i].iov_len will be
+  // set to its length.  No data is copied, no data is consumed.  Returns the
+  // number of iov set.
   int PeekBody(iovec* iov, size_t iov_len) const;
 
-  // Copies from buffer into |iov| up to |iov_len|, and consume data in
-  // sequencer. |iov.iov_base| and |iov.iov_len| are preassigned and will not be
-  // changed.  |*total_bytes_read| is set to the number of bytes read.  Returns
-  // the number of bytes that should be marked consumed with the sequencer.
+  // Copies data from available bodies into at most |iov_len| elements of iov[].
+  // Internally consumes copied payload bytes as well as all interleaving and
+  // immediately following DATA frame header bytes.  |iov.iov_base| and
+  // |iov.iov_len| are preassigned and will not be changed.  Returns the total
+  // number of bytes the caller shall mark consumed.  Sets
+  // |*total_bytes_read| to the total number of DATA payload bytes read.
   QUIC_MUST_USE_RESULT size_t ReadBody(const struct iovec* iov,
                                        size_t iov_len,
                                        size_t* total_bytes_read);
 
-  bool HasBytesToRead() const { return !bodies_.empty(); }
+  bool HasBytesToRead() const { return !fragments_.empty(); }
 
   uint64_t total_body_bytes_received() const {
     return total_body_bytes_received_;
   }
 
  private:
-  // Storage for decoded data.
-  QuicDeque<QuicStringPiece> bodies_;
-  // Storage for header lengths.
-  QuicDeque<Http3FrameLengths> frame_meta_;
-  // Bytes in the first available data frame that are not consumed yet.
-  QuicByteCount bytes_remaining_;
-  // Total available body data in the stream.
-  QuicByteCount total_body_bytes_readable_;
-  // Total bytes read from the stream excluding headers.
+  // A Fragment instance represents a body fragment with a count of bytes
+  // received afterwards but before the next body fragment that can be marked
+  // consumed as soon as all of the body fragment is read.
+  struct Fragment {
+    // |body| must not be empty.
+    QuicStringPiece body;
+    // Might be zero.
+    QuicByteCount trailing_consumable_bytes;
+  };
+  // Queue of DATA frame payload fragments and byte counts.
+  QuicDeque<Fragment> fragments_;
+  // Total body bytes received.
   QuicByteCount total_body_bytes_received_;
-  // Total length of payloads tracked by frame_meta_.
-  QuicByteCount total_payload_lengths_;
 };
 
 }  // namespace quic
diff --git a/quic/core/http/quic_spdy_stream_body_buffer_test.cc b/quic/core/http/quic_spdy_stream_body_buffer_test.cc
index d5e8161..7c4787d 100644
--- a/quic/core/http/quic_spdy_stream_body_buffer_test.cc
+++ b/quic/core/http/quic_spdy_stream_body_buffer_test.cc
@@ -4,8 +4,11 @@
 
 #include "net/third_party/quiche/src/quic/core/http/quic_spdy_stream_body_buffer.h"
 
+#include <algorithm>
+#include <numeric>
 #include <string>
 
+#include "net/third_party/quiche/src/quic/platform/api/quic_arraysize.h"
 #include "net/third_party/quiche/src/quic/platform/api/quic_expect_bug.h"
 #include "net/third_party/quiche/src/quic/platform/api/quic_logging.h"
 #include "net/third_party/quiche/src/quic/platform/api/quic_test.h"
@@ -21,139 +24,255 @@
   QuicSpdyStreamBodyBuffer body_buffer_;
 };
 
-TEST_F(QuicSpdyStreamBodyBufferTest, ReceiveBodies) {
-  std::string body(1024, 'a');
+TEST_F(QuicSpdyStreamBodyBufferTest, HasBytesToRead) {
   EXPECT_FALSE(body_buffer_.HasBytesToRead());
-  body_buffer_.OnDataHeader(Http3FrameLengths(3, 1024));
-  body_buffer_.OnDataPayload(body);
-  EXPECT_EQ(1024u, body_buffer_.total_body_bytes_received());
-  EXPECT_TRUE(body_buffer_.HasBytesToRead());
-}
+  EXPECT_EQ(0u, body_buffer_.total_body_bytes_received());
 
-TEST_F(QuicSpdyStreamBodyBufferTest, PeekBody) {
-  std::string body(1024, 'a');
-  body_buffer_.OnDataHeader(Http3FrameLengths(3, 1024));
-  body_buffer_.OnDataPayload(body);
-  EXPECT_EQ(1024u, body_buffer_.total_body_bytes_received());
-  iovec vec;
-  EXPECT_EQ(1, body_buffer_.PeekBody(&vec, 1));
-  EXPECT_EQ(1024u, vec.iov_len);
-  EXPECT_EQ(body,
-            QuicStringPiece(static_cast<const char*>(vec.iov_base), 1024));
-}
-
-// Buffer only receives 1 frame. Stream consumes less or equal than a frame.
-TEST_F(QuicSpdyStreamBodyBufferTest, MarkConsumedPartialSingleFrame) {
-  testing::InSequence seq;
-  std::string body(1024, 'a');
   const QuicByteCount header_length = 3;
-  Http3FrameLengths lengths(header_length, 1024);
-  body_buffer_.OnDataHeader(lengths);
-  body_buffer_.OnDataPayload(body);
-  EXPECT_EQ(header_length + 1024, body_buffer_.OnBodyConsumed(1024));
-}
+  EXPECT_EQ(header_length, body_buffer_.OnDataHeader(header_length));
 
-// Buffer received 2 frames. Stream consumes multiple times.
-TEST_F(QuicSpdyStreamBodyBufferTest, MarkConsumedMultipleFrames) {
-  testing::InSequence seq;
-  // 1st frame.
-  std::string body1(1024, 'a');
-  const QuicByteCount header_length1 = 2;
-  Http3FrameLengths lengths1(header_length1, 1024);
-  body_buffer_.OnDataHeader(lengths1);
-  body_buffer_.OnDataPayload(body1);
+  EXPECT_FALSE(body_buffer_.HasBytesToRead());
+  EXPECT_EQ(0u, body_buffer_.total_body_bytes_received());
 
-  // 2nd frame.
-  std::string body2(2048, 'b');
-  const QuicByteCount header_length2 = 4;
-  Http3FrameLengths lengths2(header_length2, 2048);
-  body_buffer_.OnDataHeader(lengths2);
-  body_buffer_.OnDataPayload(body2);
-
-  EXPECT_EQ(header_length1 + 512, body_buffer_.OnBodyConsumed(512));
-  EXPECT_EQ(header_length2 + 2048, body_buffer_.OnBodyConsumed(2048));
-  EXPECT_EQ(512u, body_buffer_.OnBodyConsumed(512));
-}
-
-TEST_F(QuicSpdyStreamBodyBufferTest, MarkConsumedMoreThanBuffered) {
   std::string body(1024, 'a');
-  Http3FrameLengths lengths(3, 1024);
-  body_buffer_.OnDataHeader(lengths);
+  body_buffer_.OnDataPayload(body);
+
+  EXPECT_TRUE(body_buffer_.HasBytesToRead());
+  EXPECT_EQ(1024u, body_buffer_.total_body_bytes_received());
+}
+
+TEST_F(QuicSpdyStreamBodyBufferTest, ConsumeMoreThanAvailable) {
+  std::string body(1024, 'a');
   body_buffer_.OnDataPayload(body);
   size_t bytes_to_consume = 0;
-  EXPECT_QUIC_BUG(
-      bytes_to_consume = body_buffer_.OnBodyConsumed(2048),
-      "Invalid argument to OnBodyConsumed. expect to consume: 2048, but not "
-      "enough bytes available. Total bytes readable are: 1024");
+  EXPECT_QUIC_BUG(bytes_to_consume = body_buffer_.OnBodyConsumed(2048),
+                  "Not enough available body to consume.");
   EXPECT_EQ(0u, bytes_to_consume);
 }
 
-// Buffer receives 1 frame. Stream read from the buffer.
-TEST_F(QuicSpdyStreamBodyBufferTest, ReadSingleBody) {
-  testing::InSequence seq;
-  std::string body(1024, 'a');
-  const QuicByteCount header_length = 2;
-  Http3FrameLengths lengths(header_length, 1024);
-  body_buffer_.OnDataHeader(lengths);
-  body_buffer_.OnDataPayload(body);
+struct {
+  std::vector<QuicByteCount> frame_header_lengths;
+  std::vector<const char*> frame_payloads;
+  std::vector<QuicByteCount> body_bytes_to_read;
+  std::vector<QuicByteCount> expected_return_values;
+} const kOnBodyConsumedTestData[] = {
+    // One frame consumed in one call.
+    {{2}, {"foobar"}, {6}, {6}},
+    // Two frames consumed in one call.
+    {{3, 5}, {"foobar", "baz"}, {9}, {14}},
+    // One frame consumed in two calls.
+    {{2}, {"foobar"}, {4, 2}, {4, 2}},
+    // Two frames consumed in two calls matching frame boundaries.
+    {{3, 5}, {"foobar", "baz"}, {6, 3}, {11, 3}},
+    // Two frames consumed in two calls,
+    // the first call only consuming part of the first frame.
+    {{3, 5}, {"foobar", "baz"}, {5, 4}, {5, 9}},
+    // Two frames consumed in two calls,
+    // the first call consuming the entire first frame and part of the second.
+    {{3, 5}, {"foobar", "baz"}, {7, 2}, {12, 2}},
+};
 
-  char base[1024];
-  iovec iov = {&base[0], 1024};
-  size_t total_bytes_read = 0;
-  EXPECT_EQ(header_length + 1024,
-            body_buffer_.ReadBody(&iov, 1, &total_bytes_read));
-  EXPECT_EQ(1024u, total_bytes_read);
-  EXPECT_EQ(1024u, iov.iov_len);
-  EXPECT_EQ(body,
-            QuicStringPiece(static_cast<const char*>(iov.iov_base), 1024));
+TEST_F(QuicSpdyStreamBodyBufferTest, OnBodyConsumed) {
+  for (size_t test_case_index = 0;
+       test_case_index < QUIC_ARRAYSIZE(kOnBodyConsumedTestData);
+       ++test_case_index) {
+    const std::vector<QuicByteCount>& frame_header_lengths =
+        kOnBodyConsumedTestData[test_case_index].frame_header_lengths;
+    const std::vector<const char*>& frame_payloads =
+        kOnBodyConsumedTestData[test_case_index].frame_payloads;
+    const std::vector<QuicByteCount>& body_bytes_to_read =
+        kOnBodyConsumedTestData[test_case_index].body_bytes_to_read;
+    const std::vector<QuicByteCount>& expected_return_values =
+        kOnBodyConsumedTestData[test_case_index].expected_return_values;
+
+    for (size_t frame_index = 0; frame_index < frame_header_lengths.size();
+         ++frame_index) {
+      // Frame header of first frame can immediately be consumed, but not the
+      // other frames.  Each test case start with an empty
+      // QuicSpdyStreamBodyBuffer.
+      EXPECT_EQ(frame_index == 0 ? frame_header_lengths[frame_index] : 0u,
+                body_buffer_.OnDataHeader(frame_header_lengths[frame_index]));
+      body_buffer_.OnDataPayload(frame_payloads[frame_index]);
+    }
+
+    for (size_t call_index = 0; call_index < body_bytes_to_read.size();
+         ++call_index) {
+      EXPECT_EQ(expected_return_values[call_index],
+                body_buffer_.OnBodyConsumed(body_bytes_to_read[call_index]));
+    }
+
+    EXPECT_FALSE(body_buffer_.HasBytesToRead());
+  }
 }
 
-// Buffer receives 2 frames, stream read from the buffer multiple times.
-TEST_F(QuicSpdyStreamBodyBufferTest, ReadMultipleBody) {
-  testing::InSequence seq;
-  // 1st frame.
-  std::string body1(1024, 'a');
-  const QuicByteCount header_length1 = 2;
-  Http3FrameLengths lengths1(header_length1, 1024);
-  body_buffer_.OnDataHeader(lengths1);
-  body_buffer_.OnDataPayload(body1);
+struct {
+  std::vector<QuicByteCount> frame_header_lengths;
+  std::vector<const char*> frame_payloads;
+  size_t iov_len;
+} const kPeekBodyTestData[] = {
+    // No frames, more iovecs than frames.
+    {{}, {}, 1},
+    // One frame, same number of iovecs.
+    {{3}, {"foobar"}, 1},
+    // One frame, more iovecs than frames.
+    {{3}, {"foobar"}, 2},
+    // Two frames, fewer iovecs than frames.
+    {{3, 5}, {"foobar", "baz"}, 1},
+    // Two frames, same number of iovecs.
+    {{3, 5}, {"foobar", "baz"}, 2},
+    // Two frames, more iovecs than frames.
+    {{3, 5}, {"foobar", "baz"}, 3},
+};
 
-  // 2nd frame.
-  std::string body2(2048, 'b');
-  const QuicByteCount header_length2 = 4;
-  Http3FrameLengths lengths2(header_length2, 2048);
-  body_buffer_.OnDataHeader(lengths2);
-  body_buffer_.OnDataPayload(body2);
+TEST_F(QuicSpdyStreamBodyBufferTest, PeekBody) {
+  for (size_t test_case_index = 0;
+       test_case_index < QUIC_ARRAYSIZE(kPeekBodyTestData); ++test_case_index) {
+    const std::vector<QuicByteCount>& frame_header_lengths =
+        kPeekBodyTestData[test_case_index].frame_header_lengths;
+    const std::vector<const char*>& frame_payloads =
+        kPeekBodyTestData[test_case_index].frame_payloads;
+    size_t iov_len = kPeekBodyTestData[test_case_index].iov_len;
 
-  // First read of 512 bytes.
-  char base[512];
-  iovec iov = {&base[0], 512};
-  size_t total_bytes_read = 0;
-  EXPECT_EQ(header_length1 + 512,
-            body_buffer_.ReadBody(&iov, 1, &total_bytes_read));
-  EXPECT_EQ(512u, total_bytes_read);
-  EXPECT_EQ(512u, iov.iov_len);
-  EXPECT_EQ(body1.substr(0, 512),
-            QuicStringPiece(static_cast<const char*>(iov.iov_base), 512));
+    QuicSpdyStreamBodyBuffer body_buffer;
 
-  // Second read of 2048 bytes.
-  char base2[2048];
-  iovec iov2 = {&base2[0], 2048};
-  EXPECT_EQ(header_length2 + 2048,
-            body_buffer_.ReadBody(&iov2, 1, &total_bytes_read));
-  EXPECT_EQ(2048u, total_bytes_read);
-  EXPECT_EQ(2048u, iov2.iov_len);
-  EXPECT_EQ(body1.substr(512, 512) + body2.substr(0, 1536),
-            QuicStringPiece(static_cast<const char*>(iov2.iov_base), 2048));
+    for (size_t frame_index = 0; frame_index < frame_header_lengths.size();
+         ++frame_index) {
+      // Frame header of first frame can immediately be consumed, but not the
+      // other frames.  Each test case uses a new QuicSpdyStreamBodyBuffer
+      // instance.
+      EXPECT_EQ(frame_index == 0 ? frame_header_lengths[frame_index] : 0u,
+                body_buffer.OnDataHeader(frame_header_lengths[frame_index]));
+      body_buffer.OnDataPayload(frame_payloads[frame_index]);
+    }
 
-  // Third read of the rest 512 bytes.
-  char base3[512];
-  iovec iov3 = {&base3[0], 512};
-  EXPECT_EQ(512u, body_buffer_.ReadBody(&iov3, 1, &total_bytes_read));
-  EXPECT_EQ(512u, total_bytes_read);
-  EXPECT_EQ(512u, iov3.iov_len);
-  EXPECT_EQ(body2.substr(1536, 512),
-            QuicStringPiece(static_cast<const char*>(iov3.iov_base), 512));
+    std::vector<iovec> iovecs;
+    iovecs.resize(iov_len);
+    size_t iovs_filled = std::min(frame_payloads.size(), iov_len);
+    ASSERT_EQ(iovs_filled,
+              static_cast<size_t>(body_buffer.PeekBody(&iovecs[0], iov_len)));
+    for (size_t iovec_index = 0; iovec_index < iovs_filled; ++iovec_index) {
+      EXPECT_EQ(frame_payloads[iovec_index],
+                QuicStringPiece(
+                    static_cast<const char*>(iovecs[iovec_index].iov_base),
+                    iovecs[iovec_index].iov_len));
+    }
+  }
+}
+
+struct {
+  std::vector<QuicByteCount> frame_header_lengths;
+  std::vector<const char*> frame_payloads;
+  std::vector<std::vector<QuicByteCount>> iov_lengths;
+  std::vector<QuicByteCount> expected_total_bytes_read;
+  std::vector<QuicByteCount> expected_return_values;
+} const kReadBodyTestData[] = {
+    // One frame, one read with smaller iovec.
+    {{4}, {"foo"}, {{2}}, {2}, {2}},
+    // One frame, one read with same size iovec.
+    {{4}, {"foo"}, {{3}}, {3}, {3}},
+    // One frame, one read with larger iovec.
+    {{4}, {"foo"}, {{5}}, {3}, {3}},
+    // One frame, one read with two iovecs, smaller total size.
+    {{4}, {"foobar"}, {{2, 3}}, {5}, {5}},
+    // One frame, one read with two iovecs, same total size.
+    {{4}, {"foobar"}, {{2, 4}}, {6}, {6}},
+    // One frame, one read with two iovecs, larger total size in last iovec.
+    {{4}, {"foobar"}, {{2, 6}}, {6}, {6}},
+    // One frame, one read with extra iovecs, body ends at iovec boundary.
+    {{4}, {"foobar"}, {{2, 4, 4, 3}}, {6}, {6}},
+    // One frame, one read with extra iovecs, body ends not at iovec boundary.
+    {{4}, {"foobar"}, {{2, 7, 4, 3}}, {6}, {6}},
+    // One frame, two reads with two iovecs each, smaller total size.
+    {{4}, {"foobarbaz"}, {{2, 1}, {3, 2}}, {3, 5}, {3, 5}},
+    // One frame, two reads with two iovecs each, same total size.
+    {{4}, {"foobarbaz"}, {{2, 1}, {4, 2}}, {3, 6}, {3, 6}},
+    // One frame, two reads with two iovecs each, larger total size.
+    {{4}, {"foobarbaz"}, {{2, 1}, {4, 10}}, {3, 6}, {3, 6}},
+    // Two frames, one read with smaller iovec.
+    {{4, 3}, {"foobar", "baz"}, {{8}}, {8}, {11}},
+    // Two frames, one read with same size iovec.
+    {{4, 3}, {"foobar", "baz"}, {{9}}, {9}, {12}},
+    // Two frames, one read with larger iovec.
+    {{4, 3}, {"foobar", "baz"}, {{10}}, {9}, {12}},
+    // Two frames, one read with two iovecs, smaller total size.
+    {{4, 3}, {"foobar", "baz"}, {{4, 3}}, {7}, {10}},
+    // Two frames, one read with two iovecs, same total size.
+    {{4, 3}, {"foobar", "baz"}, {{4, 5}}, {9}, {12}},
+    // Two frames, one read with two iovecs, larger total size in last iovec.
+    {{4, 3}, {"foobar", "baz"}, {{4, 6}}, {9}, {12}},
+    // Two frames, one read with extra iovecs, body ends at iovec boundary.
+    {{4, 3}, {"foobar", "baz"}, {{4, 6, 4, 3}}, {9}, {12}},
+    // Two frames, one read with extra iovecs, body ends not at iovec boundary.
+    {{4, 3}, {"foobar", "baz"}, {{4, 7, 4, 3}}, {9}, {12}},
+    // Two frames, two reads with two iovecs each, reads end on frame boundary.
+    {{4, 3}, {"foobar", "baz"}, {{2, 4}, {2, 1}}, {6, 3}, {9, 3}},
+    // Three frames, three reads, extra iovecs, no iovec ends on frame boundary.
+    {{4, 3, 6},
+     {"foobar", "bazquux", "qux"},
+     {{4, 3}, {2, 3}, {5, 3}},
+     {7, 5, 4},
+     {10, 5, 10}},
+};
+
+TEST_F(QuicSpdyStreamBodyBufferTest, ReadBody) {
+  for (size_t test_case_index = 0;
+       test_case_index < QUIC_ARRAYSIZE(kReadBodyTestData); ++test_case_index) {
+    const std::vector<QuicByteCount>& frame_header_lengths =
+        kReadBodyTestData[test_case_index].frame_header_lengths;
+    const std::vector<const char*>& frame_payloads =
+        kReadBodyTestData[test_case_index].frame_payloads;
+    const std::vector<std::vector<QuicByteCount>>& iov_lengths =
+        kReadBodyTestData[test_case_index].iov_lengths;
+    const std::vector<QuicByteCount>& expected_total_bytes_read =
+        kReadBodyTestData[test_case_index].expected_total_bytes_read;
+    const std::vector<QuicByteCount>& expected_return_values =
+        kReadBodyTestData[test_case_index].expected_return_values;
+
+    QuicSpdyStreamBodyBuffer body_buffer;
+
+    std::string received_body;
+
+    for (size_t frame_index = 0; frame_index < frame_header_lengths.size();
+         ++frame_index) {
+      // Frame header of first frame can immediately be consumed, but not the
+      // other frames.  Each test case uses a new QuicSpdyStreamBodyBuffer
+      // instance.
+      EXPECT_EQ(frame_index == 0 ? frame_header_lengths[frame_index] : 0u,
+                body_buffer.OnDataHeader(frame_header_lengths[frame_index]));
+      body_buffer.OnDataPayload(frame_payloads[frame_index]);
+      received_body.append(frame_payloads[frame_index]);
+    }
+
+    std::string read_body;
+
+    for (size_t call_index = 0; call_index < iov_lengths.size(); ++call_index) {
+      // Allocate single buffer for iovecs.
+      size_t total_iov_length = std::accumulate(iov_lengths[call_index].begin(),
+                                                iov_lengths[call_index].end(),
+                                                static_cast<size_t>(0));
+      std::string buffer(total_iov_length, 'z');
+
+      // Construct iovecs pointing to contiguous areas in the buffer.
+      std::vector<iovec> iovecs;
+      size_t offset = 0;
+      for (size_t iov_length : iov_lengths[call_index]) {
+        CHECK(offset + iov_length <= buffer.size());
+        iovecs.push_back({&buffer[offset], iov_length});
+        offset += iov_length;
+      }
+
+      // Make sure |total_bytes_read| differs from |expected_total_bytes_read|.
+      size_t total_bytes_read = expected_total_bytes_read[call_index] + 12;
+      EXPECT_EQ(
+          expected_return_values[call_index],
+          body_buffer.ReadBody(&iovecs[0], iovecs.size(), &total_bytes_read));
+      read_body.append(buffer.substr(0, total_bytes_read));
+    }
+
+    EXPECT_EQ(received_body.substr(0, read_body.size()), read_body);
+    EXPECT_EQ(read_body.size() < received_body.size(),
+              body_buffer.HasBytesToRead());
+  }
 }
 
 }  // anonymous namespace
diff --git a/quic/core/http/quic_spdy_stream_test.cc b/quic/core/http/quic_spdy_stream_test.cc
index f9777ec..ddfb651 100644
--- a/quic/core/http/quic_spdy_stream_test.cc
+++ b/quic/core/http/quic_spdy_stream_test.cc
@@ -2104,16 +2104,18 @@
   // DATA frame.
   QuicStringPiece data_payload(kDataFramePayload);
   std::string data_frame = DataFrame(data_payload);
+  QuicByteCount data_frame_header_length =
+      data_frame.size() - data_payload.size();
 
-  // DATA frame is not consumed because payload has to be buffered.
-  // TODO(bnc): Consume frame header as soon as possible.
+  // DATA frame header is consumed.
+  // DATA frame payload is not consumed because payload has to be buffered.
   OnStreamFrame(data_frame);
-  EXPECT_EQ(0u, NewlyConsumedBytes());
+  EXPECT_EQ(data_frame_header_length, NewlyConsumedBytes());
 
   // Consume all but last byte of data.
   EXPECT_EQ(data_payload.substr(0, data_payload.size() - 1),
             ReadFromStream(data_payload.size() - 1));
-  EXPECT_EQ(data_frame.size() - 1, NewlyConsumedBytes());
+  EXPECT_EQ(data_payload.size() - 1, NewlyConsumedBytes());
 
   // Trailing HEADERS frame with QPACK encoded
   // single header field "custom-key: custom-value".