1 // Copyright (c) 2019 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "net/third_party/quiche/src/quic/core/batch_writer/quic_batch_writer_buffer.h"
6 #include <memory>
7 #include <string>
8
9 #include "net/third_party/quiche/src/quic/core/quic_constants.h"
10 #include "net/third_party/quiche/src/quic/platform/api/quic_ip_address.h"
11 #include "net/third_party/quiche/src/quic/platform/api/quic_socket_address.h"
12 #include "net/third_party/quiche/src/quic/platform/api/quic_test.h"
13
14 namespace quic {
15 namespace test {
16 namespace {
17
18 class QUIC_EXPORT_PRIVATE TestQuicBatchWriterBuffer
19 : public QuicBatchWriterBuffer {
20 public:
21 using QuicBatchWriterBuffer::buffer_;
22 using QuicBatchWriterBuffer::buffered_writes_;
23 };
24
25 static const size_t kBatchBufferSize = QuicBatchWriterBuffer::kBufferSize;
26
27 class QuicBatchWriterBufferTest : public QuicTest {
28 public:
QuicBatchWriterBufferTest()29 QuicBatchWriterBufferTest() { SwitchToNewBuffer(); }
30
SwitchToNewBuffer()31 void SwitchToNewBuffer() {
32 batch_buffer_ = std::make_unique<TestQuicBatchWriterBuffer>();
33 }
34
35 // Fill packet_buffer_ with kMaxOutgoingPacketSize bytes of |c|s.
FillPacketBuffer(char c)36 char* FillPacketBuffer(char c) {
37 return FillPacketBuffer(c, packet_buffer_, kMaxOutgoingPacketSize);
38 }
39
40 // Fill |packet_buffer| with kMaxOutgoingPacketSize bytes of |c|s.
FillPacketBuffer(char c,char * packet_buffer)41 char* FillPacketBuffer(char c, char* packet_buffer) {
42 return FillPacketBuffer(c, packet_buffer, kMaxOutgoingPacketSize);
43 }
44
45 // Fill |packet_buffer| with |buf_len| bytes of |c|s.
FillPacketBuffer(char c,char * packet_buffer,size_t buf_len)46 char* FillPacketBuffer(char c, char* packet_buffer, size_t buf_len) {
47 memset(packet_buffer, c, buf_len);
48 return packet_buffer;
49 }
50
CheckBufferedWriteContent(int buffered_write_index,char buffer_content,size_t buf_len,const QuicIpAddress & self_addr,const QuicSocketAddress & peer_addr,const PerPacketOptions * options)51 void CheckBufferedWriteContent(int buffered_write_index,
52 char buffer_content,
53 size_t buf_len,
54 const QuicIpAddress& self_addr,
55 const QuicSocketAddress& peer_addr,
56 const PerPacketOptions* options) {
57 const BufferedWrite& buffered_write =
58 batch_buffer_->buffered_writes()[buffered_write_index];
59 EXPECT_EQ(buf_len, buffered_write.buf_len);
60 for (size_t i = 0; i < buf_len; ++i) {
61 EXPECT_EQ(buffer_content, buffered_write.buffer[i]);
62 if (buffer_content != buffered_write.buffer[i]) {
63 break;
64 }
65 }
66 EXPECT_EQ(self_addr, buffered_write.self_address);
67 EXPECT_EQ(peer_addr, buffered_write.peer_address);
68 if (options == nullptr) {
69 EXPECT_EQ(nullptr, buffered_write.options);
70 } else {
71 EXPECT_EQ(options->release_time_delay,
72 buffered_write.options->release_time_delay);
73 }
74 }
75
76 protected:
77 std::unique_ptr<TestQuicBatchWriterBuffer> batch_buffer_;
78 QuicIpAddress self_addr_;
79 QuicSocketAddress peer_addr_;
80 uint64_t release_time_ = 0;
81 char packet_buffer_[kMaxOutgoingPacketSize];
82 };
83
84 class BufferSizeSequence {
85 public:
BufferSizeSequence(std::vector<std::pair<std::vector<size_t>,size_t>> stages)86 explicit BufferSizeSequence(
87 std::vector<std::pair<std::vector<size_t>, size_t>> stages)
88 : stages_(std::move(stages)),
89 total_buf_len_(0),
90 stage_index_(0),
91 sequence_index_(0) {}
92
Next()93 size_t Next() {
94 const std::vector<size_t>& seq = stages_[stage_index_].first;
95 size_t buf_len = seq[sequence_index_++ % seq.size()];
96 total_buf_len_ += buf_len;
97 if (stages_[stage_index_].second <= total_buf_len_) {
98 stage_index_ = std::min(stage_index_ + 1, stages_.size() - 1);
99 }
100 return buf_len;
101 }
102
103 private:
104 const std::vector<std::pair<std::vector<size_t>, size_t>> stages_;
105 size_t total_buf_len_;
106 size_t stage_index_;
107 size_t sequence_index_;
108 };
109
110 // Test in-place pushes. A in-place push is a push with a buffer address that is
111 // equal to the result of GetNextWriteLocation().
TEST_F(QuicBatchWriterBufferTest,InPlacePushes)112 TEST_F(QuicBatchWriterBufferTest, InPlacePushes) {
113 std::vector<BufferSizeSequence> buffer_size_sequences = {
114 // Push large writes until the buffer is near full, then switch to 1-byte
115 // writes. This covers the edge cases when detecting insufficient buffer.
116 BufferSizeSequence({{{1350}, kBatchBufferSize - 3000}, {{1}, 1e6}}),
117 // A sequence that looks real.
118 BufferSizeSequence({{{1, 39, 97, 150, 1350, 1350, 1350, 1350}, 1e6}}),
119 };
120
121 for (auto& buffer_size_sequence : buffer_size_sequences) {
122 SwitchToNewBuffer();
123 int64_t num_push_failures = 0;
124
125 while (batch_buffer_->SizeInUse() < kBatchBufferSize) {
126 size_t buf_len = buffer_size_sequence.Next();
127 const bool has_enough_space =
128 (kBatchBufferSize - batch_buffer_->SizeInUse() >=
129 kMaxOutgoingPacketSize);
130
131 char* buffer = batch_buffer_->GetNextWriteLocation();
132
133 if (has_enough_space) {
134 EXPECT_EQ(batch_buffer_->buffer_ + batch_buffer_->SizeInUse(), buffer);
135 } else {
136 EXPECT_EQ(nullptr, buffer);
137 }
138
139 SCOPED_TRACE(testing::Message()
140 << "Before Push: buf_len=" << buf_len
141 << ", has_enough_space=" << has_enough_space
142 << ", batch_buffer=" << batch_buffer_->DebugString());
143
144 auto push_result = batch_buffer_->PushBufferedWrite(
145 buffer, buf_len, self_addr_, peer_addr_, nullptr, release_time_);
146 if (!push_result.succeeded) {
147 ++num_push_failures;
148 }
149 EXPECT_EQ(has_enough_space, push_result.succeeded);
150 EXPECT_FALSE(push_result.buffer_copied);
151 if (!has_enough_space) {
152 break;
153 }
154 }
155 // Expect one and only one failure from the final push operation.
156 EXPECT_EQ(1, num_push_failures);
157 }
158 }
159
160 // Test some in-place pushes mixed with pushes with external buffers.
TEST_F(QuicBatchWriterBufferTest,MixedPushes)161 TEST_F(QuicBatchWriterBufferTest, MixedPushes) {
162 // First, a in-place push.
163 char* buffer = batch_buffer_->GetNextWriteLocation();
164 auto push_result = batch_buffer_->PushBufferedWrite(
165 FillPacketBuffer('A', buffer), kDefaultMaxPacketSize, self_addr_,
166 peer_addr_, nullptr, release_time_);
167 EXPECT_TRUE(push_result.succeeded);
168 EXPECT_FALSE(push_result.buffer_copied);
169 CheckBufferedWriteContent(0, 'A', kDefaultMaxPacketSize, self_addr_,
170 peer_addr_, nullptr);
171
172 // Then a push with external buffer.
173 push_result = batch_buffer_->PushBufferedWrite(
174 FillPacketBuffer('B'), kDefaultMaxPacketSize, self_addr_, peer_addr_,
175 nullptr, release_time_);
176 EXPECT_TRUE(push_result.succeeded);
177 EXPECT_TRUE(push_result.buffer_copied);
178 CheckBufferedWriteContent(1, 'B', kDefaultMaxPacketSize, self_addr_,
179 peer_addr_, nullptr);
180
181 // Then another in-place push.
182 buffer = batch_buffer_->GetNextWriteLocation();
183 push_result = batch_buffer_->PushBufferedWrite(
184 FillPacketBuffer('C', buffer), kDefaultMaxPacketSize, self_addr_,
185 peer_addr_, nullptr, release_time_);
186 EXPECT_TRUE(push_result.succeeded);
187 EXPECT_FALSE(push_result.buffer_copied);
188 CheckBufferedWriteContent(2, 'C', kDefaultMaxPacketSize, self_addr_,
189 peer_addr_, nullptr);
190
191 // Then another push with external buffer.
192 push_result = batch_buffer_->PushBufferedWrite(
193 FillPacketBuffer('D'), kDefaultMaxPacketSize, self_addr_, peer_addr_,
194 nullptr, release_time_);
195 EXPECT_TRUE(push_result.succeeded);
196 EXPECT_TRUE(push_result.buffer_copied);
197 CheckBufferedWriteContent(3, 'D', kDefaultMaxPacketSize, self_addr_,
198 peer_addr_, nullptr);
199 }
200
TEST_F(QuicBatchWriterBufferTest,PopAll)201 TEST_F(QuicBatchWriterBufferTest, PopAll) {
202 const int kNumBufferedWrites = 10;
203 for (int i = 0; i < kNumBufferedWrites; ++i) {
204 EXPECT_TRUE(batch_buffer_
205 ->PushBufferedWrite(packet_buffer_, kDefaultMaxPacketSize,
206 self_addr_, peer_addr_, nullptr,
207 release_time_)
208 .succeeded);
209 }
210 EXPECT_EQ(kNumBufferedWrites,
211 static_cast<int>(batch_buffer_->buffered_writes().size()));
212
213 auto pop_result = batch_buffer_->PopBufferedWrite(kNumBufferedWrites);
214 EXPECT_EQ(0u, batch_buffer_->buffered_writes().size());
215 EXPECT_EQ(kNumBufferedWrites, pop_result.num_buffers_popped);
216 EXPECT_FALSE(pop_result.moved_remaining_buffers);
217 }
218
TEST_F(QuicBatchWriterBufferTest,PopPartial)219 TEST_F(QuicBatchWriterBufferTest, PopPartial) {
220 const int kNumBufferedWrites = 10;
221 for (int i = 0; i < kNumBufferedWrites; ++i) {
222 EXPECT_TRUE(batch_buffer_
223 ->PushBufferedWrite(FillPacketBuffer('A' + i),
224 kDefaultMaxPacketSize - i, self_addr_,
225 peer_addr_, nullptr, release_time_)
226 .succeeded);
227 }
228
229 for (size_t i = 0;
230 i < kNumBufferedWrites && !batch_buffer_->buffered_writes().empty();
231 ++i) {
232 const size_t size_before_pop = batch_buffer_->buffered_writes().size();
233 const size_t expect_size_after_pop =
234 size_before_pop < i ? 0 : size_before_pop - i;
235 batch_buffer_->PopBufferedWrite(i);
236 ASSERT_EQ(expect_size_after_pop, batch_buffer_->buffered_writes().size());
237 const char first_write_content =
238 'A' + kNumBufferedWrites - expect_size_after_pop;
239 const size_t first_write_len =
240 kDefaultMaxPacketSize - kNumBufferedWrites + expect_size_after_pop;
241 for (size_t j = 0; j < expect_size_after_pop; ++j) {
242 CheckBufferedWriteContent(j, first_write_content + j, first_write_len - j,
243 self_addr_, peer_addr_, nullptr);
244 }
245 }
246 }
247
TEST_F(QuicBatchWriterBufferTest,InPlacePushWithPops)248 TEST_F(QuicBatchWriterBufferTest, InPlacePushWithPops) {
249 // First, a in-place push.
250 char* buffer = batch_buffer_->GetNextWriteLocation();
251 const size_t first_packet_len = 2;
252 auto push_result = batch_buffer_->PushBufferedWrite(
253 FillPacketBuffer('A', buffer, first_packet_len), first_packet_len,
254 self_addr_, peer_addr_, nullptr, release_time_);
255 EXPECT_TRUE(push_result.succeeded);
256 EXPECT_FALSE(push_result.buffer_copied);
257 CheckBufferedWriteContent(0, 'A', first_packet_len, self_addr_, peer_addr_,
258 nullptr);
259
260 // Simulate the case where the writer wants to do another in-place push, but
261 // can't do so because it can't be batched with the first buffer.
262 buffer = batch_buffer_->GetNextWriteLocation();
263 const size_t second_packet_len = 1350;
264
265 // Flush the first buffer.
266 auto pop_result = batch_buffer_->PopBufferedWrite(1);
267 EXPECT_EQ(1, pop_result.num_buffers_popped);
268 EXPECT_FALSE(pop_result.moved_remaining_buffers);
269
270 // Now the second push.
271 push_result = batch_buffer_->PushBufferedWrite(
272 FillPacketBuffer('B', buffer, second_packet_len), second_packet_len,
273 self_addr_, peer_addr_, nullptr, release_time_);
274 EXPECT_TRUE(push_result.succeeded);
275 EXPECT_TRUE(push_result.buffer_copied);
276 CheckBufferedWriteContent(0, 'B', second_packet_len, self_addr_, peer_addr_,
277 nullptr);
278 }
279
280 } // namespace
281 } // namespace test
282 } // namespace quic
283