Implement QuicStreamSequencerBuffer::PeekRegion(). gfe-relnote: n/a, no functional change. PiperOrigin-RevId: 254856738 Change-Id: I34f1fd30429f8d5d403fa17fc1f4a127c074f952
diff --git a/quic/core/quic_stream_sequencer.cc b/quic/core/quic_stream_sequencer.cc index 9832fc9..9e47a6f 100644 --- a/quic/core/quic_stream_sequencer.cc +++ b/quic/core/quic_stream_sequencer.cc
@@ -156,6 +156,12 @@ return buffered_frames_.GetReadableRegion(iov); } +bool QuicStreamSequencer::PeekRegion(QuicStreamOffset offset, + iovec* iov) const { + DCHECK(!blocked_); + return buffered_frames_.PeekRegion(offset, iov); +} + bool QuicStreamSequencer::PrefetchNextRegion(iovec* iov) { DCHECK(!blocked_); return buffered_frames_.PrefetchNextRegion(iov);
diff --git a/quic/core/quic_stream_sequencer.h b/quic/core/quic_stream_sequencer.h index 7cecf4d..5888a4a 100644 --- a/quic/core/quic_stream_sequencer.h +++ b/quic/core/quic_stream_sequencer.h
@@ -81,7 +81,12 @@ // is no readable region available. bool GetReadableRegion(iovec* iov) const; - // Fill in one iovec with the next unread region for the quic spdy stream. + // Fills in one iovec with the region starting at |offset| and returns true. + // Returns false if no readable region is available, either because data has + // not been received yet or has already been consumed. + bool PeekRegion(QuicStreamOffset offset, iovec* iov) const; + + // Fills in one iovec with the next unread region. // Returns false if no readable region is available. bool PrefetchNextRegion(iovec* iov);
diff --git a/quic/core/quic_stream_sequencer_buffer.cc b/quic/core/quic_stream_sequencer_buffer.cc index 47211cc..f4ff276 100644 --- a/quic/core/quic_stream_sequencer_buffer.cc +++ b/quic/core/quic_stream_sequencer_buffer.cc
@@ -335,18 +335,23 @@ return GetReadableRegions(iov, 1) == 1; } -bool QuicStreamSequencerBuffer::PrefetchNextRegion(iovec* iov) { - DCHECK(iov != nullptr); - DCHECK_LE(total_bytes_read_, total_bytes_prefetched_); - DCHECK_LE(total_bytes_prefetched_, FirstMissingByte()); +bool QuicStreamSequencerBuffer::PeekRegion(QuicStreamOffset offset, + iovec* iov) const { + DCHECK(iov); - if (total_bytes_prefetched_ == FirstMissingByte()) { + if (offset < total_bytes_read_) { + // Data at |offset| has already been consumed. + return false; + } + + if (offset >= FirstMissingByte()) { + // Data at |offset| has not been received yet. return false; } // Beginning of region. - size_t block_idx = GetBlockIndex(total_bytes_prefetched_); - size_t block_offset = GetInBlockOffset(total_bytes_prefetched_); + size_t block_idx = GetBlockIndex(offset); + size_t block_offset = GetInBlockOffset(offset); iov->iov_base = blocks_[block_idx]->buffer + block_offset; // Determine if entire block has been received. @@ -359,6 +364,18 @@ iov->iov_len = GetBlockCapacity(block_idx) - block_offset; } + return true; +} + +bool QuicStreamSequencerBuffer::PrefetchNextRegion(iovec* iov) { + DCHECK(iov); + DCHECK_LE(total_bytes_read_, total_bytes_prefetched_); + DCHECK_LE(total_bytes_prefetched_, FirstMissingByte()); + + if (!PeekRegion(total_bytes_prefetched_, iov)) { + return false; + } + total_bytes_prefetched_ += iov->iov_len; return true; }
diff --git a/quic/core/quic_stream_sequencer_buffer.h b/quic/core/quic_stream_sequencer_buffer.h index 08a1ab4..6347b1c 100644 --- a/quic/core/quic_stream_sequencer_buffer.h +++ b/quic/core/quic_stream_sequencer_buffer.h
@@ -131,10 +131,17 @@ // Returns false if there is no readable region available. bool GetReadableRegion(iovec* iov) const; + // Returns true and sets |*iov| to point to a region starting at |offset|. + // Returns false if no data can be read at |offset|, which can be because data + // has not been received yet or it is already consumed. + // Does not consume data. + bool PeekRegion(QuicStreamOffset offset, iovec* iov) const; + + // DEPRECATED, use PeekRegion() instead. // Called to return the next region that has not been returned by this method - // previously. - // If this method is to be used along with Readv() or MarkConsumed(), make - // sure that they are consuming less data than is read by this method. + // previously, except if Clear() has been called then prefetch offset is set + // to BytesConsumed(), and if Readv() or MarkConsumed() reads further than + // prefetch offset, then offset is set to beginning of not yet consumed area. // This method only returns reference of underlying data. The caller is // responsible for copying and consuming the data. // Returns true if the data is read, false otherwise.
diff --git a/quic/core/quic_stream_sequencer_buffer_test.cc b/quic/core/quic_stream_sequencer_buffer_test.cc index 49f83f0..20dbe05 100644 --- a/quic/core/quic_stream_sequencer_buffer_test.cc +++ b/quic/core/quic_stream_sequencer_buffer_test.cc
@@ -602,14 +602,14 @@ iovec iov; EXPECT_TRUE(buffer_->PrefetchNextRegion(&iov)); EXPECT_EQ(source, IovecToStringPiece(iov)); - // The second frame goes into the same bucket. + // The second frame goes into the same block. std::string source2(800, 'a'); buffer_->OnStreamData(1024, source2, &written_, &error_details_); EXPECT_TRUE(buffer_->PrefetchNextRegion(&iov)); EXPECT_EQ(source2, IovecToStringPiece(iov)); } -TEST_F(QuicStreamSequencerBufferTest, PrefetchBufferWithMultipleBucket) { +TEST_F(QuicStreamSequencerBufferTest, PrefetchBufferWithMultipleBlocks) { const size_t data_size = 1024; std::string source(data_size, 'a'); buffer_->OnStreamData(0, source, &written_, &error_details_); @@ -678,6 +678,164 @@ EXPECT_FALSE(buffer_->PrefetchNextRegion(&iov)); } +TEST_F(QuicStreamSequencerBufferTest, PeekEmptyBuffer) { + iovec iov; + EXPECT_FALSE(buffer_->PeekRegion(0, &iov)); + EXPECT_FALSE(buffer_->PeekRegion(1, &iov)); + EXPECT_FALSE(buffer_->PeekRegion(100, &iov)); +} + +TEST_F(QuicStreamSequencerBufferTest, PeekSingleBlock) { + std::string source(kBlockSizeBytes, 'a'); + buffer_->OnStreamData(0, source, &written_, &error_details_); + + iovec iov; + EXPECT_TRUE(buffer_->PeekRegion(0, &iov)); + EXPECT_EQ(source, IovecToStringPiece(iov)); + + // Peeking again gives the same result. + EXPECT_TRUE(buffer_->PeekRegion(0, &iov)); + EXPECT_EQ(source, IovecToStringPiece(iov)); + + // Peek at a different offset. + EXPECT_TRUE(buffer_->PeekRegion(100, &iov)); + EXPECT_EQ(QuicStringPiece(source).substr(100), IovecToStringPiece(iov)); + + // Peeking at or after FirstMissingByte() returns false. + EXPECT_FALSE(buffer_->PeekRegion(kBlockSizeBytes, &iov)); + EXPECT_FALSE(buffer_->PeekRegion(kBlockSizeBytes + 1, &iov)); +} + +TEST_F(QuicStreamSequencerBufferTest, PeekTwoWritesInSingleBlock) { + const size_t length1 = 1024; + std::string source1(length1, 'a'); + buffer_->OnStreamData(0, source1, &written_, &error_details_); + + iovec iov; + EXPECT_TRUE(buffer_->PeekRegion(0, &iov)); + EXPECT_EQ(source1, IovecToStringPiece(iov)); + + // The second frame goes into the same block. + const size_t length2 = 800; + std::string source2(length2, 'b'); + buffer_->OnStreamData(length1, source2, &written_, &error_details_); + + EXPECT_TRUE(buffer_->PeekRegion(length1, &iov)); + EXPECT_EQ(source2, IovecToStringPiece(iov)); + + // Peek with an offset inside the first write. + const QuicStreamOffset offset1 = 500; + EXPECT_TRUE(buffer_->PeekRegion(offset1, &iov)); + EXPECT_EQ(QuicStringPiece(source1).substr(offset1), + IovecToStringPiece(iov).substr(0, length1 - offset1)); + EXPECT_EQ(QuicStringPiece(source2), + IovecToStringPiece(iov).substr(length1 - offset1)); + + // Peek with an offset inside the second write. + const QuicStreamOffset offset2 = 1500; + EXPECT_TRUE(buffer_->PeekRegion(offset2, &iov)); + EXPECT_EQ(QuicStringPiece(source2).substr(offset2 - length1), + IovecToStringPiece(iov)); + + // Peeking at or after FirstMissingByte() returns false. + EXPECT_FALSE(buffer_->PeekRegion(length1 + length2, &iov)); + EXPECT_FALSE(buffer_->PeekRegion(length1 + length2 + 1, &iov)); +} + +TEST_F(QuicStreamSequencerBufferTest, PeekBufferWithMultipleBlocks) { + const size_t length1 = 1024; + std::string source1(length1, 'a'); + buffer_->OnStreamData(0, source1, &written_, &error_details_); + + iovec iov; + EXPECT_TRUE(buffer_->PeekRegion(0, &iov)); + EXPECT_EQ(source1, IovecToStringPiece(iov)); + + const size_t length2 = kBlockSizeBytes + 2; + std::string source2(length2, 'b'); + buffer_->OnStreamData(length1, source2, &written_, &error_details_); + + // Peek with offset 0 returns the entire block. + EXPECT_TRUE(buffer_->PeekRegion(0, &iov)); + EXPECT_EQ(kBlockSizeBytes, iov.iov_len); + EXPECT_EQ(source1, IovecToStringPiece(iov).substr(0, length1)); + EXPECT_EQ(QuicStringPiece(source2).substr(0, kBlockSizeBytes - length1), + IovecToStringPiece(iov).substr(length1)); + + EXPECT_TRUE(buffer_->PeekRegion(length1, &iov)); + EXPECT_EQ(QuicStringPiece(source2).substr(0, kBlockSizeBytes - length1), + IovecToStringPiece(iov)); + + EXPECT_TRUE(buffer_->PeekRegion(kBlockSizeBytes, &iov)); + EXPECT_EQ(QuicStringPiece(source2).substr(kBlockSizeBytes - length1), + IovecToStringPiece(iov)); + + // Peeking at or after FirstMissingByte() returns false. + EXPECT_FALSE(buffer_->PeekRegion(length1 + length2, &iov)); + EXPECT_FALSE(buffer_->PeekRegion(length1 + length2 + 1, &iov)); +} + +TEST_F(QuicStreamSequencerBufferTest, PeekAfterConsumed) { + std::string source1(kBlockSizeBytes, 'a'); + buffer_->OnStreamData(0, source1, &written_, &error_details_); + + iovec iov; + EXPECT_TRUE(buffer_->PeekRegion(0, &iov)); + EXPECT_EQ(source1, IovecToStringPiece(iov)); + + // Consume some data. + EXPECT_TRUE(buffer_->MarkConsumed(1024)); + + // Peeking into consumed data fails. + EXPECT_FALSE(buffer_->PeekRegion(0, &iov)); + EXPECT_FALSE(buffer_->PeekRegion(512, &iov)); + + EXPECT_TRUE(buffer_->PeekRegion(1024, &iov)); + EXPECT_EQ(QuicStringPiece(source1).substr(1024), IovecToStringPiece(iov)); + + EXPECT_TRUE(buffer_->PeekRegion(1500, &iov)); + EXPECT_EQ(QuicStringPiece(source1).substr(1500), IovecToStringPiece(iov)); + + // Consume rest of block. + EXPECT_TRUE(buffer_->MarkConsumed(kBlockSizeBytes - 1024)); + + // Read new data. + std::string source2(300, 'b'); + buffer_->OnStreamData(kBlockSizeBytes, source2, &written_, &error_details_); + + // Peek into new data. + EXPECT_TRUE(buffer_->PeekRegion(kBlockSizeBytes, &iov)); + EXPECT_EQ(source2, IovecToStringPiece(iov)); + + EXPECT_TRUE(buffer_->PeekRegion(kBlockSizeBytes + 128, &iov)); + EXPECT_EQ(QuicStringPiece(source2).substr(128), IovecToStringPiece(iov)); + + // Peeking into consumed data still fails. + EXPECT_FALSE(buffer_->PeekRegion(0, &iov)); + EXPECT_FALSE(buffer_->PeekRegion(512, &iov)); + EXPECT_FALSE(buffer_->PeekRegion(1024, &iov)); + EXPECT_FALSE(buffer_->PeekRegion(1500, &iov)); +} + +TEST_F(QuicStreamSequencerBufferTest, PeekContinously) { + std::string source1(kBlockSizeBytes, 'a'); + buffer_->OnStreamData(0, source1, &written_, &error_details_); + + iovec iov; + EXPECT_TRUE(buffer_->PeekRegion(0, &iov)); + EXPECT_EQ(source1, IovecToStringPiece(iov)); + + std::string source2(kBlockSizeBytes, 'b'); + buffer_->OnStreamData(kBlockSizeBytes, source2, &written_, &error_details_); + + EXPECT_TRUE(buffer_->PeekRegion(kBlockSizeBytes, &iov)); + EXPECT_EQ(source2, IovecToStringPiece(iov)); + + // First block is still there. + EXPECT_TRUE(buffer_->PeekRegion(0, &iov)); + EXPECT_EQ(source1, IovecToStringPiece(iov)); +} + TEST_F(QuicStreamSequencerBufferTest, MarkConsumedInOneBlock) { // Write into [0, 1024) and then read out [0, 256) std::string source(1024, 'a');