Project import generated by Copybara.

PiperOrigin-RevId: 237361882
Change-Id: I109a68f44db867b20f8c6a7732b0ce657133e52a
diff --git a/quic/core/http/quic_spdy_stream.cc b/quic/core/http/quic_spdy_stream.cc
new file mode 100644
index 0000000..9e9f1b7
--- /dev/null
+++ b/quic/core/http/quic_spdy_stream.cc
@@ -0,0 +1,623 @@
+// Copyright 2013 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/http/quic_spdy_stream.h"
+
+#include <utility>
+
+#include "net/third_party/quiche/src/quic/core/http/quic_spdy_session.h"
+#include "net/third_party/quiche/src/quic/core/http/spdy_utils.h"
+#include "net/third_party/quiche/src/quic/core/quic_utils.h"
+#include "net/third_party/quiche/src/quic/core/quic_write_blocked_list.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_bug_tracker.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_flag_utils.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_flags.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_logging.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_mem_slice_storage.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_string.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_string_piece.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_text_utils.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_protocol.h"
+
+using spdy::SpdyHeaderBlock;
+using spdy::SpdyPriority;
+
+namespace quic {
+
+// Visitor of HttpDecoder that passes data frame to QuicSpdyStream and closes
+// the connection on unexpected frames.
+class QuicSpdyStream::HttpDecoderVisitor : public HttpDecoder::Visitor {
+ public:
+  explicit HttpDecoderVisitor(QuicSpdyStream* stream) : stream_(stream) {}
+  HttpDecoderVisitor(const HttpDecoderVisitor&) = delete;
+  HttpDecoderVisitor& operator=(const HttpDecoderVisitor&) = delete;
+
+  void OnError(HttpDecoder* decoder) override {
+    stream_->session()->connection()->CloseConnection(
+        QUIC_HTTP_DECODER_ERROR, "Http decoder internal error",
+        ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+  }
+
+  void OnPriorityFrame(const PriorityFrame& frame) override {
+    CloseConnectionOnWrongFrame("Priority");
+  }
+
+  void OnCancelPushFrame(const CancelPushFrame& frame) override {
+    CloseConnectionOnWrongFrame("Cancel Push");
+  }
+
+  void OnMaxPushIdFrame(const MaxPushIdFrame& frame) override {
+    CloseConnectionOnWrongFrame("Max Push Id");
+  }
+
+  void OnGoAwayFrame(const GoAwayFrame& frame) override {
+    CloseConnectionOnWrongFrame("Goaway");
+  }
+
+  void OnSettingsFrame(const SettingsFrame& frame) override {
+    CloseConnectionOnWrongFrame("Settings");
+  }
+
+  void OnDuplicatePushFrame(const DuplicatePushFrame& frame) override {
+    CloseConnectionOnWrongFrame("Duplicate Push");
+  }
+
+  void OnDataFrameStart(Http3FrameLengths frame_lengths) override {
+    stream_->OnDataFrameStart(frame_lengths);
+  }
+
+  void OnDataFramePayload(QuicStringPiece payload) override {
+    stream_->OnDataFramePayload(payload);
+  }
+
+  void OnDataFrameEnd() override { stream_->OnDataFrameEnd(); }
+
+  void OnHeadersFrameStart() override {
+    CloseConnectionOnWrongFrame("Headers");
+  }
+
+  void OnHeadersFramePayload(QuicStringPiece payload) override {
+    CloseConnectionOnWrongFrame("Headers");
+  }
+
+  void OnHeadersFrameEnd(QuicByteCount frame_len) override {
+    CloseConnectionOnWrongFrame("Headers");
+  }
+
+  void OnPushPromiseFrameStart(PushId push_id) override {
+    CloseConnectionOnWrongFrame("Push Promise");
+  }
+
+  void OnPushPromiseFramePayload(QuicStringPiece payload) override {
+    CloseConnectionOnWrongFrame("Push Promise");
+  }
+
+  void OnPushPromiseFrameEnd() override {
+    CloseConnectionOnWrongFrame("Push Promise");
+  }
+
+ private:
+  void CloseConnectionOnWrongFrame(QuicString frame_type) {
+    stream_->session()->connection()->CloseConnection(
+        QUIC_HTTP_DECODER_ERROR, frame_type + " frame received on data stream",
+        ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+  }
+
+  QuicSpdyStream* stream_;
+};
+
+#define ENDPOINT                                                   \
+  (session()->perspective() == Perspective::IS_SERVER ? "Server: " \
+                                                      : "Client:"  \
+                                                        " ")
+
+QuicSpdyStream::QuicSpdyStream(QuicStreamId id,
+                               QuicSpdySession* spdy_session,
+                               StreamType type)
+    : QuicStream(id, spdy_session, /*is_static=*/false, type),
+      spdy_session_(spdy_session),
+      visitor_(nullptr),
+      headers_decompressed_(false),
+      trailers_decompressed_(false),
+      trailers_consumed_(false),
+      http_decoder_visitor_(new HttpDecoderVisitor(this)),
+      body_buffer_(sequencer()),
+      ack_listener_(nullptr) {
+  DCHECK_NE(QuicUtils::GetCryptoStreamId(
+                spdy_session->connection()->transport_version()),
+            id);
+  // Don't receive any callbacks from the sequencer until headers
+  // are complete.
+  sequencer()->SetBlockedUntilFlush();
+
+  if (VersionHasDataFrameHeader(
+          spdy_session_->connection()->transport_version())) {
+    sequencer()->set_level_triggered(true);
+  }
+  decoder_.set_visitor(http_decoder_visitor_.get());
+}
+
+QuicSpdyStream::QuicSpdyStream(PendingStream pending,
+                               QuicSpdySession* spdy_session,
+                               StreamType type)
+    : QuicStream(std::move(pending), type),
+      spdy_session_(spdy_session),
+      visitor_(nullptr),
+      headers_decompressed_(false),
+      trailers_decompressed_(false),
+      trailers_consumed_(false),
+      http_decoder_visitor_(new HttpDecoderVisitor(this)),
+      body_buffer_(sequencer()),
+      ack_listener_(nullptr) {
+  DCHECK_NE(QuicUtils::GetCryptoStreamId(
+                spdy_session->connection()->transport_version()),
+            id());
+  // Don't receive any callbacks from the sequencer until headers
+  // are complete.
+  sequencer()->SetBlockedUntilFlush();
+
+  if (VersionHasDataFrameHeader(
+          spdy_session_->connection()->transport_version())) {
+    sequencer()->set_level_triggered(true);
+  }
+  decoder_.set_visitor(http_decoder_visitor_.get());
+}
+
+QuicSpdyStream::~QuicSpdyStream() {}
+
+size_t QuicSpdyStream::WriteHeaders(
+    SpdyHeaderBlock header_block,
+    bool fin,
+    QuicReferenceCountedPointer<QuicAckListenerInterface> ack_listener) {
+  size_t bytes_written =
+      WriteHeadersImpl(std::move(header_block), fin, std::move(ack_listener));
+  if (fin) {
+    // TODO(rch): Add test to ensure fin_sent_ is set whenever a fin is sent.
+    set_fin_sent(true);
+    CloseWriteSide();
+  }
+  return bytes_written;
+}
+
+void QuicSpdyStream::WriteOrBufferBody(QuicStringPiece data, bool fin) {
+  if (!VersionHasDataFrameHeader(
+          spdy_session_->connection()->transport_version()) ||
+      data.length() == 0) {
+    WriteOrBufferData(data, fin, nullptr);
+    return;
+  }
+  QuicConnection::ScopedPacketFlusher flusher(
+      spdy_session_->connection(), QuicConnection::SEND_ACK_IF_PENDING);
+
+  // Write frame header.
+  std::unique_ptr<char[]> buffer;
+  QuicByteCount header_length =
+      encoder_.SerializeDataFrameHeader(data.length(), &buffer);
+  unacked_frame_headers_offsets_.Add(
+      send_buffer().stream_offset(),
+      send_buffer().stream_offset() + header_length);
+  QUIC_DLOG(INFO) << "Stream " << id() << " is writing header of length "
+                  << header_length;
+  WriteOrBufferData(QuicStringPiece(buffer.get(), header_length), false,
+                    nullptr);
+
+  // Write body.
+  QUIC_DLOG(INFO) << "Stream " << id() << " is writing body of length "
+                  << data.length();
+  WriteOrBufferData(data, fin, nullptr);
+}
+
+size_t QuicSpdyStream::WriteTrailers(
+    SpdyHeaderBlock trailer_block,
+    QuicReferenceCountedPointer<QuicAckListenerInterface> ack_listener) {
+  if (fin_sent()) {
+    QUIC_BUG << "Trailers cannot be sent after a FIN, on stream " << id();
+    return 0;
+  }
+
+  // The header block must contain the final offset for this stream, as the
+  // trailers may be processed out of order at the peer.
+  QUIC_DLOG(INFO) << "Inserting trailer: (" << kFinalOffsetHeaderKey << ", "
+                  << stream_bytes_written() + BufferedDataBytes() << ")";
+  trailer_block.insert(
+      std::make_pair(kFinalOffsetHeaderKey,
+                     QuicTextUtils::Uint64ToString(stream_bytes_written() +
+                                                   BufferedDataBytes())));
+
+  // Write the trailing headers with a FIN, and close stream for writing:
+  // trailers are the last thing to be sent on a stream.
+  const bool kFin = true;
+  size_t bytes_written =
+      WriteHeadersImpl(std::move(trailer_block), kFin, std::move(ack_listener));
+  set_fin_sent(kFin);
+
+  // Trailers are the last thing to be sent on a stream, but if there is still
+  // queued data then CloseWriteSide() will cause it never to be sent.
+  if (BufferedDataBytes() == 0) {
+    CloseWriteSide();
+  }
+
+  return bytes_written;
+}
+
+QuicConsumedData QuicSpdyStream::WritevBody(const struct iovec* iov,
+                                            int count,
+                                            bool fin) {
+  if (!GetQuicReloadableFlag(quic_call_write_mem_slices)) {
+    return WritevData(iov, count, fin);
+  }
+  QUIC_RELOADABLE_FLAG_COUNT(quic_call_write_mem_slices);
+  QuicMemSliceStorage storage(
+      iov, count,
+      session()->connection()->helper()->GetStreamSendBufferAllocator(),
+      GetQuicFlag(FLAGS_quic_send_buffer_max_data_slice_size));
+  return WriteBodySlices(storage.ToSpan(), fin);
+}
+
+QuicConsumedData QuicSpdyStream::WriteBodySlices(QuicMemSliceSpan slices,
+                                                 bool fin) {
+  if (!VersionHasDataFrameHeader(
+          spdy_session_->connection()->transport_version()) ||
+      slices.empty()) {
+    return WriteMemSlices(slices, fin);
+  }
+
+  std::unique_ptr<char[]> buffer;
+  QuicByteCount header_length =
+      encoder_.SerializeDataFrameHeader(slices.total_length(), &buffer);
+  if (!CanWriteNewDataAfterData(header_length)) {
+    return {0, false};
+  }
+
+  QuicConnection::ScopedPacketFlusher flusher(
+      spdy_session_->connection(), QuicConnection::SEND_ACK_IF_PENDING);
+
+  // Write frame header.
+  struct iovec header_iov = {static_cast<void*>(buffer.get()), header_length};
+  QuicMemSliceStorage storage(
+      &header_iov, 1,
+      spdy_session_->connection()->helper()->GetStreamSendBufferAllocator(),
+      GetQuicFlag(FLAGS_quic_send_buffer_max_data_slice_size));
+  unacked_frame_headers_offsets_.Add(
+      send_buffer().stream_offset(),
+      send_buffer().stream_offset() + header_length);
+  QUIC_DLOG(INFO) << "Stream " << id() << " is writing header of length "
+                  << header_length;
+  WriteMemSlices(storage.ToSpan(), false);
+
+  // Write body.
+  QUIC_DLOG(INFO) << "Stream " << id() << " is writing body of length "
+                  << slices.total_length();
+  return WriteMemSlices(slices, fin);
+}
+
+size_t QuicSpdyStream::Readv(const struct iovec* iov, size_t iov_len) {
+  DCHECK(FinishedReadingHeaders());
+  if (!VersionHasDataFrameHeader(
+          spdy_session_->connection()->transport_version())) {
+    return sequencer()->Readv(iov, iov_len);
+  }
+  return body_buffer_.ReadBody(iov, iov_len);
+}
+
+int QuicSpdyStream::GetReadableRegions(iovec* iov, size_t iov_len) const {
+  DCHECK(FinishedReadingHeaders());
+  if (!VersionHasDataFrameHeader(
+          spdy_session_->connection()->transport_version())) {
+    return sequencer()->GetReadableRegions(iov, iov_len);
+  }
+  return body_buffer_.PeekBody(iov, iov_len);
+}
+
+void QuicSpdyStream::MarkConsumed(size_t num_bytes) {
+  DCHECK(FinishedReadingHeaders());
+  if (!VersionHasDataFrameHeader(
+          spdy_session_->connection()->transport_version())) {
+    sequencer()->MarkConsumed(num_bytes);
+    return;
+  }
+  body_buffer_.MarkBodyConsumed(num_bytes);
+}
+
+bool QuicSpdyStream::IsDoneReading() const {
+  bool done_reading_headers = FinishedReadingHeaders();
+  bool done_reading_body = sequencer()->IsClosed();
+  bool done_reading_trailers = FinishedReadingTrailers();
+  return done_reading_headers && done_reading_body && done_reading_trailers;
+}
+
+bool QuicSpdyStream::HasBytesToRead() const {
+  if (!VersionHasDataFrameHeader(
+          spdy_session_->connection()->transport_version())) {
+    return sequencer()->HasBytesToRead();
+  }
+  return body_buffer_.HasBytesToRead();
+}
+
+void QuicSpdyStream::MarkTrailersConsumed() {
+  trailers_consumed_ = true;
+}
+
+uint64_t QuicSpdyStream::total_body_bytes_read() const {
+  if (VersionHasDataFrameHeader(
+          spdy_session_->connection()->transport_version())) {
+    return body_buffer_.total_body_bytes_received();
+  }
+  return sequencer()->NumBytesConsumed();
+}
+
+void QuicSpdyStream::ConsumeHeaderList() {
+  header_list_.Clear();
+  if (FinishedReadingHeaders()) {
+    sequencer()->SetUnblocked();
+  }
+}
+
+void QuicSpdyStream::OnStreamHeadersPriority(SpdyPriority priority) {
+  DCHECK_EQ(Perspective::IS_SERVER, session()->connection()->perspective());
+  SetPriority(priority);
+}
+
+void QuicSpdyStream::OnStreamHeaderList(bool fin,
+                                        size_t frame_len,
+                                        const QuicHeaderList& header_list) {
+  // The headers list avoid infinite buffering by clearing the headers list
+  // if the current headers are too large. So if the list is empty here
+  // then the headers list must have been too large, and the stream should
+  // be reset.
+  // TODO(rch): Use an explicit "headers too large" signal. An empty header list
+  // might be acceptable if it corresponds to a trailing header frame.
+  if (header_list.empty()) {
+    OnHeadersTooLarge();
+    if (IsDoneReading()) {
+      return;
+    }
+  }
+  if (!headers_decompressed_) {
+    OnInitialHeadersComplete(fin, frame_len, header_list);
+  } else {
+    OnTrailingHeadersComplete(fin, frame_len, header_list);
+  }
+}
+
+void QuicSpdyStream::OnHeadersTooLarge() {
+  Reset(QUIC_HEADERS_TOO_LARGE);
+}
+
+void QuicSpdyStream::OnInitialHeadersComplete(
+    bool fin,
+    size_t /*frame_len*/,
+    const QuicHeaderList& header_list) {
+  headers_decompressed_ = true;
+  header_list_ = header_list;
+  if (fin) {
+    OnStreamFrame(QuicStreamFrame(id(), fin, 0, QuicStringPiece()));
+  }
+  if (FinishedReadingHeaders()) {
+    sequencer()->SetUnblocked();
+  }
+}
+
+void QuicSpdyStream::OnPromiseHeaderList(
+    QuicStreamId /* promised_id */,
+    size_t /* frame_len */,
+    const QuicHeaderList& /*header_list */) {
+  // To be overridden in QuicSpdyClientStream.  Not supported on
+  // server side.
+  session()->connection()->CloseConnection(
+      QUIC_INVALID_HEADERS_STREAM_DATA, "Promise headers received by server",
+      ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+}
+
+void QuicSpdyStream::OnTrailingHeadersComplete(
+    bool fin,
+    size_t /*frame_len*/,
+    const QuicHeaderList& header_list) {
+  DCHECK(!trailers_decompressed_);
+  if (fin_received()) {
+    QUIC_DLOG(ERROR) << "Received Trailers after FIN, on stream: " << id();
+    session()->connection()->CloseConnection(
+        QUIC_INVALID_HEADERS_STREAM_DATA, "Trailers after fin",
+        ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+    return;
+  }
+  if (!fin) {
+    QUIC_DLOG(ERROR) << "Trailers must have FIN set, on stream: " << id();
+    session()->connection()->CloseConnection(
+        QUIC_INVALID_HEADERS_STREAM_DATA, "Fin missing from trailers",
+        ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+    return;
+  }
+
+  size_t final_byte_offset = 0;
+  if (!SpdyUtils::CopyAndValidateTrailers(header_list, &final_byte_offset,
+                                          &received_trailers_)) {
+    QUIC_DLOG(ERROR) << "Trailers for stream " << id() << " are malformed.";
+    session()->connection()->CloseConnection(
+        QUIC_INVALID_HEADERS_STREAM_DATA, "Trailers are malformed",
+        ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+    return;
+  }
+  trailers_decompressed_ = true;
+  OnStreamFrame(
+      QuicStreamFrame(id(), fin, final_byte_offset, QuicStringPiece()));
+}
+
+size_t QuicSpdyStream::WriteHeadersImpl(
+    spdy::SpdyHeaderBlock header_block,
+    bool fin,
+    QuicReferenceCountedPointer<QuicAckListenerInterface> ack_listener) {
+  return spdy_session_->WriteHeadersOnHeadersStream(
+      id(), std::move(header_block), fin, priority(), std::move(ack_listener));
+}
+
+void QuicSpdyStream::OnPriorityFrame(SpdyPriority priority) {
+  DCHECK_EQ(Perspective::IS_SERVER, session()->connection()->perspective());
+  SetPriority(priority);
+}
+
+void QuicSpdyStream::OnStreamReset(const QuicRstStreamFrame& frame) {
+  if (frame.error_code != QUIC_STREAM_NO_ERROR) {
+    QuicStream::OnStreamReset(frame);
+    return;
+  }
+  QUIC_DVLOG(1) << "Received QUIC_STREAM_NO_ERROR, not discarding response";
+  set_rst_received(true);
+  MaybeIncreaseHighestReceivedOffset(frame.byte_offset);
+  set_stream_error(frame.error_code);
+  CloseWriteSide();
+}
+
+void QuicSpdyStream::OnDataAvailable() {
+  if (!VersionHasDataFrameHeader(
+          session()->connection()->transport_version())) {
+    OnBodyAvailable();
+    return;
+  }
+
+  iovec iov;
+  bool has_payload = false;
+  while (sequencer()->PrefetchNextRegion(&iov)) {
+    decoder_.ProcessInput(reinterpret_cast<const char*>(iov.iov_base),
+                          iov.iov_len);
+    if (decoder_.has_payload()) {
+      has_payload = true;
+    }
+  }
+
+  if (has_payload) {
+    OnBodyAvailable();
+    return;
+  }
+
+  if (sequencer()->IsClosed()) {
+    OnBodyAvailable();
+    return;
+  }
+}
+
+void QuicSpdyStream::OnClose() {
+  QuicStream::OnClose();
+
+  if (visitor_) {
+    Visitor* visitor = visitor_;
+    // Calling Visitor::OnClose() may result the destruction of the visitor,
+    // so we need to ensure we don't call it again.
+    visitor_ = nullptr;
+    visitor->OnClose(this);
+  }
+}
+
+void QuicSpdyStream::OnCanWrite() {
+  QuicStream::OnCanWrite();
+
+  // Trailers (and hence a FIN) may have been sent ahead of queued body bytes.
+  if (!HasBufferedData() && fin_sent()) {
+    CloseWriteSide();
+  }
+}
+
+bool QuicSpdyStream::FinishedReadingHeaders() const {
+  return headers_decompressed_ && header_list_.empty();
+}
+
+bool QuicSpdyStream::ParseHeaderStatusCode(const SpdyHeaderBlock& header,
+                                           int* status_code) const {
+  SpdyHeaderBlock::const_iterator it = header.find(spdy::kHttp2StatusHeader);
+  if (it == header.end()) {
+    return false;
+  }
+  const QuicStringPiece status(it->second);
+  if (status.size() != 3) {
+    return false;
+  }
+  // First character must be an integer in range [1,5].
+  if (status[0] < '1' || status[0] > '5') {
+    return false;
+  }
+  // The remaining two characters must be integers.
+  if (!isdigit(status[1]) || !isdigit(status[2])) {
+    return false;
+  }
+  return QuicTextUtils::StringToInt(status, status_code);
+}
+
+bool QuicSpdyStream::FinishedReadingTrailers() const {
+  // If no further trailing headers are expected, and the decompressed trailers
+  // (if any) have been consumed, then reading of trailers is finished.
+  if (!fin_received()) {
+    return false;
+  } else if (!trailers_decompressed_) {
+    return true;
+  } else {
+    return trailers_consumed_;
+  }
+}
+
+void QuicSpdyStream::ClearSession() {
+  spdy_session_ = nullptr;
+}
+
+void QuicSpdyStream::OnDataFrameStart(Http3FrameLengths frame_lengths) {
+  body_buffer_.OnDataHeader(frame_lengths);
+}
+
+void QuicSpdyStream::OnDataFramePayload(QuicStringPiece payload) {
+  body_buffer_.OnDataPayload(payload);
+}
+
+void QuicSpdyStream::OnDataFrameEnd() {
+  DVLOG(1) << "Reaches the end of a data frame. Total bytes received are "
+           << body_buffer_.total_body_bytes_received();
+}
+
+bool QuicSpdyStream::OnStreamFrameAcked(QuicStreamOffset offset,
+                                        QuicByteCount data_length,
+                                        bool fin_acked,
+                                        QuicTime::Delta ack_delay_time,
+                                        QuicByteCount* newly_acked_length) {
+  const bool new_data_acked = QuicStream::OnStreamFrameAcked(
+      offset, data_length, fin_acked, ack_delay_time, newly_acked_length);
+
+  const QuicByteCount newly_acked_header_length =
+      GetNumFrameHeadersInInterval(offset, data_length);
+  DCHECK_LE(newly_acked_header_length, *newly_acked_length);
+  unacked_frame_headers_offsets_.Difference(offset, offset + data_length);
+  if (ack_listener_ != nullptr && new_data_acked) {
+    ack_listener_->OnPacketAcked(
+        *newly_acked_length - newly_acked_header_length, ack_delay_time);
+  }
+  return new_data_acked;
+}
+
+void QuicSpdyStream::OnStreamFrameRetransmitted(QuicStreamOffset offset,
+                                                QuicByteCount data_length,
+                                                bool fin_retransmitted) {
+  QuicStream::OnStreamFrameRetransmitted(offset, data_length,
+                                         fin_retransmitted);
+
+  const QuicByteCount retransmitted_header_length =
+      GetNumFrameHeadersInInterval(offset, data_length);
+  DCHECK_LE(retransmitted_header_length, data_length);
+
+  if (ack_listener_ != nullptr) {
+    ack_listener_->OnPacketRetransmitted(data_length -
+                                         retransmitted_header_length);
+  }
+}
+
+QuicByteCount QuicSpdyStream::GetNumFrameHeadersInInterval(
+    QuicStreamOffset offset,
+    QuicByteCount data_length) const {
+  QuicByteCount header_acked_length = 0;
+  QuicIntervalSet<QuicStreamOffset> newly_acked(offset, offset + data_length);
+  newly_acked.Intersection(unacked_frame_headers_offsets_);
+  for (const auto& interval : newly_acked) {
+    header_acked_length += interval.Length();
+  }
+  return header_acked_length;
+}
+
+#undef ENDPOINT  // undef for jumbo builds
+}  // namespace quic