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 "quiche/quic/core/batch_writer/quic_batch_writer_buffer.h"
6 
7 #include <memory>
8 #include <string>
9 
10 #include "quiche/quic/core/quic_constants.h"
11 #include "quiche/quic/platform/api/quic_ip_address.h"
12 #include "quiche/quic/platform/api/quic_socket_address.h"
13 #include "quiche/quic/platform/api/quic_test.h"
14 
15 namespace quic {
16 namespace test {
17 namespace {
18 
19 class QUICHE_EXPORT TestQuicBatchWriterBuffer : 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 *,const QuicPacketWriterParams & params)51   void CheckBufferedWriteContent(int buffered_write_index, char buffer_content,
52                                  size_t buf_len, const QuicIpAddress& self_addr,
53                                  const QuicSocketAddress& peer_addr,
54                                  const PerPacketOptions* /*options*/,
55                                  const QuicPacketWriterParams& params) {
56     const BufferedWrite& buffered_write =
57         batch_buffer_->buffered_writes()[buffered_write_index];
58     EXPECT_EQ(buf_len, buffered_write.buf_len);
59     for (size_t i = 0; i < buf_len; ++i) {
60       EXPECT_EQ(buffer_content, buffered_write.buffer[i]);
61       if (buffer_content != buffered_write.buffer[i]) {
62         break;
63       }
64     }
65     EXPECT_EQ(self_addr, buffered_write.self_address);
66     EXPECT_EQ(peer_addr, buffered_write.peer_address);
67     EXPECT_EQ(params.release_time_delay,
68               buffered_write.params.release_time_delay);
69   }
70 
71  protected:
72   std::unique_ptr<TestQuicBatchWriterBuffer> batch_buffer_;
73   QuicIpAddress self_addr_;
74   QuicSocketAddress peer_addr_;
75   uint64_t release_time_ = 0;
76   char packet_buffer_[kMaxOutgoingPacketSize];
77 };
78 
79 class BufferSizeSequence {
80  public:
BufferSizeSequence(std::vector<std::pair<std::vector<size_t>,size_t>> stages)81   explicit BufferSizeSequence(
82       std::vector<std::pair<std::vector<size_t>, size_t>> stages)
83       : stages_(std::move(stages)),
84         total_buf_len_(0),
85         stage_index_(0),
86         sequence_index_(0) {}
87 
Next()88   size_t Next() {
89     const std::vector<size_t>& seq = stages_[stage_index_].first;
90     size_t buf_len = seq[sequence_index_++ % seq.size()];
91     total_buf_len_ += buf_len;
92     if (stages_[stage_index_].second <= total_buf_len_) {
93       stage_index_ = std::min(stage_index_ + 1, stages_.size() - 1);
94     }
95     return buf_len;
96   }
97 
98  private:
99   const std::vector<std::pair<std::vector<size_t>, size_t>> stages_;
100   size_t total_buf_len_;
101   size_t stage_index_;
102   size_t sequence_index_;
103 };
104 
105 // Test in-place pushes. A in-place push is a push with a buffer address that is
106 // equal to the result of GetNextWriteLocation().
TEST_F(QuicBatchWriterBufferTest,InPlacePushes)107 TEST_F(QuicBatchWriterBufferTest, InPlacePushes) {
108   std::vector<BufferSizeSequence> buffer_size_sequences = {
109       // Push large writes until the buffer is near full, then switch to 1-byte
110       // writes. This covers the edge cases when detecting insufficient buffer.
111       BufferSizeSequence({{{1350}, kBatchBufferSize - 3000}, {{1}, 1000000}}),
112       // A sequence that looks real.
113       BufferSizeSequence({{{1, 39, 97, 150, 1350, 1350, 1350, 1350}, 1000000}}),
114   };
115 
116   for (auto& buffer_size_sequence : buffer_size_sequences) {
117     SwitchToNewBuffer();
118     int64_t num_push_failures = 0;
119 
120     while (batch_buffer_->SizeInUse() < kBatchBufferSize) {
121       size_t buf_len = buffer_size_sequence.Next();
122       const bool has_enough_space =
123           (kBatchBufferSize - batch_buffer_->SizeInUse() >=
124            kMaxOutgoingPacketSize);
125 
126       char* buffer = batch_buffer_->GetNextWriteLocation();
127 
128       if (has_enough_space) {
129         EXPECT_EQ(batch_buffer_->buffer_ + batch_buffer_->SizeInUse(), buffer);
130       } else {
131         EXPECT_EQ(nullptr, buffer);
132       }
133 
134       SCOPED_TRACE(testing::Message()
135                    << "Before Push: buf_len=" << buf_len
136                    << ", has_enough_space=" << has_enough_space
137                    << ", batch_buffer=" << batch_buffer_->DebugString());
138 
139       auto push_result = batch_buffer_->PushBufferedWrite(
140           buffer, buf_len, self_addr_, peer_addr_, nullptr,
141           QuicPacketWriterParams(), release_time_);
142       if (!push_result.succeeded) {
143         ++num_push_failures;
144       }
145       EXPECT_EQ(has_enough_space, push_result.succeeded);
146       EXPECT_FALSE(push_result.buffer_copied);
147       if (!has_enough_space) {
148         break;
149       }
150     }
151     // Expect one and only one failure from the final push operation.
152     EXPECT_EQ(1, num_push_failures);
153   }
154 }
155 
156 // Test some in-place pushes mixed with pushes with external buffers.
TEST_F(QuicBatchWriterBufferTest,MixedPushes)157 TEST_F(QuicBatchWriterBufferTest, MixedPushes) {
158   // First, a in-place push.
159   char* buffer = batch_buffer_->GetNextWriteLocation();
160   QuicPacketWriterParams params;
161   auto push_result = batch_buffer_->PushBufferedWrite(
162       FillPacketBuffer('A', buffer), kDefaultMaxPacketSize, self_addr_,
163       peer_addr_, nullptr, params, release_time_);
164   EXPECT_TRUE(push_result.succeeded);
165   EXPECT_FALSE(push_result.buffer_copied);
166   CheckBufferedWriteContent(0, 'A', kDefaultMaxPacketSize, self_addr_,
167                             peer_addr_, nullptr, params);
168 
169   // Then a push with external buffer.
170   push_result = batch_buffer_->PushBufferedWrite(
171       FillPacketBuffer('B'), kDefaultMaxPacketSize, self_addr_, peer_addr_,
172       nullptr, params, release_time_);
173   EXPECT_TRUE(push_result.succeeded);
174   EXPECT_TRUE(push_result.buffer_copied);
175   CheckBufferedWriteContent(1, 'B', kDefaultMaxPacketSize, self_addr_,
176                             peer_addr_, nullptr, params);
177 
178   // Then another in-place push.
179   buffer = batch_buffer_->GetNextWriteLocation();
180   push_result = batch_buffer_->PushBufferedWrite(
181       FillPacketBuffer('C', buffer), kDefaultMaxPacketSize, self_addr_,
182       peer_addr_, nullptr, params, release_time_);
183   EXPECT_TRUE(push_result.succeeded);
184   EXPECT_FALSE(push_result.buffer_copied);
185   CheckBufferedWriteContent(2, 'C', kDefaultMaxPacketSize, self_addr_,
186                             peer_addr_, nullptr, params);
187 
188   // Then another push with external buffer.
189   push_result = batch_buffer_->PushBufferedWrite(
190       FillPacketBuffer('D'), kDefaultMaxPacketSize, self_addr_, peer_addr_,
191       nullptr, params, release_time_);
192   EXPECT_TRUE(push_result.succeeded);
193   EXPECT_TRUE(push_result.buffer_copied);
194   CheckBufferedWriteContent(3, 'D', kDefaultMaxPacketSize, self_addr_,
195                             peer_addr_, nullptr, params);
196 }
197 
TEST_F(QuicBatchWriterBufferTest,PopAll)198 TEST_F(QuicBatchWriterBufferTest, PopAll) {
199   const int kNumBufferedWrites = 10;
200   QuicPacketWriterParams params;
201   for (int i = 0; i < kNumBufferedWrites; ++i) {
202     EXPECT_TRUE(batch_buffer_
203                     ->PushBufferedWrite(packet_buffer_, kDefaultMaxPacketSize,
204                                         self_addr_, peer_addr_, nullptr, params,
205                                         release_time_)
206                     .succeeded);
207   }
208   EXPECT_EQ(kNumBufferedWrites,
209             static_cast<int>(batch_buffer_->buffered_writes().size()));
210 
211   auto pop_result = batch_buffer_->PopBufferedWrite(kNumBufferedWrites);
212   EXPECT_EQ(0u, batch_buffer_->buffered_writes().size());
213   EXPECT_EQ(kNumBufferedWrites, pop_result.num_buffers_popped);
214   EXPECT_FALSE(pop_result.moved_remaining_buffers);
215 }
216 
TEST_F(QuicBatchWriterBufferTest,PopPartial)217 TEST_F(QuicBatchWriterBufferTest, PopPartial) {
218   const int kNumBufferedWrites = 10;
219   QuicPacketWriterParams params;
220   for (int i = 0; i < kNumBufferedWrites; ++i) {
221     EXPECT_TRUE(batch_buffer_
222                     ->PushBufferedWrite(
223                         FillPacketBuffer('A' + i), kDefaultMaxPacketSize - i,
224                         self_addr_, peer_addr_, nullptr, params, release_time_)
225                     .succeeded);
226   }
227 
228   for (size_t i = 0;
229        i < kNumBufferedWrites && !batch_buffer_->buffered_writes().empty();
230        ++i) {
231     const size_t size_before_pop = batch_buffer_->buffered_writes().size();
232     const size_t expect_size_after_pop =
233         size_before_pop < i ? 0 : size_before_pop - i;
234     batch_buffer_->PopBufferedWrite(i);
235     ASSERT_EQ(expect_size_after_pop, batch_buffer_->buffered_writes().size());
236     const char first_write_content =
237         'A' + kNumBufferedWrites - expect_size_after_pop;
238     const size_t first_write_len =
239         kDefaultMaxPacketSize - kNumBufferedWrites + expect_size_after_pop;
240     for (size_t j = 0; j < expect_size_after_pop; ++j) {
241       CheckBufferedWriteContent(j, first_write_content + j, first_write_len - j,
242                                 self_addr_, peer_addr_, nullptr, params);
243     }
244   }
245 }
246 
TEST_F(QuicBatchWriterBufferTest,InPlacePushWithPops)247 TEST_F(QuicBatchWriterBufferTest, InPlacePushWithPops) {
248   // First, a in-place push.
249   char* buffer = batch_buffer_->GetNextWriteLocation();
250   const size_t first_packet_len = 2;
251   QuicPacketWriterParams params;
252   auto push_result = batch_buffer_->PushBufferedWrite(
253       FillPacketBuffer('A', buffer, first_packet_len), first_packet_len,
254       self_addr_, peer_addr_, nullptr, params, 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, params);
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, params, 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, params);
278 }
279 
TEST_F(QuicBatchWriterBufferTest,BatchID)280 TEST_F(QuicBatchWriterBufferTest, BatchID) {
281   const int kNumBufferedWrites = 10;
282   QuicPacketWriterParams params;
283   auto first_push_result = batch_buffer_->PushBufferedWrite(
284       packet_buffer_, kDefaultMaxPacketSize, self_addr_, peer_addr_, nullptr,
285       params, release_time_);
286   ASSERT_TRUE(first_push_result.succeeded);
287   ASSERT_NE(first_push_result.batch_id, 0);
288   for (int i = 1; i < kNumBufferedWrites; ++i) {
289     EXPECT_EQ(batch_buffer_
290                   ->PushBufferedWrite(packet_buffer_, kDefaultMaxPacketSize,
291                                       self_addr_, peer_addr_, nullptr, params,
292                                       release_time_)
293                   .batch_id,
294               first_push_result.batch_id);
295   }
296 
297   batch_buffer_->PopBufferedWrite(kNumBufferedWrites);
298   EXPECT_TRUE(batch_buffer_->buffered_writes().empty());
299 
300   EXPECT_NE(
301       batch_buffer_
302           ->PushBufferedWrite(packet_buffer_, kDefaultMaxPacketSize, self_addr_,
303                               peer_addr_, nullptr, params, release_time_)
304           .batch_id,
305       first_push_result.batch_id);
306 }
307 
308 }  // namespace
309 }  // namespace test
310 }  // namespace quic
311