blob: a234e5ede3a43d98b3878095f315a625bfb6f5c6 [file] [log] [blame]
// Copyright (c) 2019 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 "quic/core/batch_writer/quic_batch_writer_buffer.h"
#include <memory>
#include <string>
#include "quic/core/quic_constants.h"
#include "quic/platform/api/quic_ip_address.h"
#include "quic/platform/api/quic_socket_address.h"
#include "quic/platform/api/quic_test.h"
namespace quic {
namespace test {
namespace {
class QUIC_EXPORT_PRIVATE TestQuicBatchWriterBuffer
: public QuicBatchWriterBuffer {
public:
using QuicBatchWriterBuffer::buffer_;
using QuicBatchWriterBuffer::buffered_writes_;
};
static const size_t kBatchBufferSize = QuicBatchWriterBuffer::kBufferSize;
class QuicBatchWriterBufferTest : public QuicTest {
public:
QuicBatchWriterBufferTest() { SwitchToNewBuffer(); }
void SwitchToNewBuffer() {
batch_buffer_ = std::make_unique<TestQuicBatchWriterBuffer>();
}
// Fill packet_buffer_ with kMaxOutgoingPacketSize bytes of |c|s.
char* FillPacketBuffer(char c) {
return FillPacketBuffer(c, packet_buffer_, kMaxOutgoingPacketSize);
}
// Fill |packet_buffer| with kMaxOutgoingPacketSize bytes of |c|s.
char* FillPacketBuffer(char c, char* packet_buffer) {
return FillPacketBuffer(c, packet_buffer, kMaxOutgoingPacketSize);
}
// Fill |packet_buffer| with |buf_len| bytes of |c|s.
char* FillPacketBuffer(char c, char* packet_buffer, size_t buf_len) {
memset(packet_buffer, c, buf_len);
return packet_buffer;
}
void CheckBufferedWriteContent(int buffered_write_index,
char buffer_content,
size_t buf_len,
const QuicIpAddress& self_addr,
const QuicSocketAddress& peer_addr,
const PerPacketOptions* options) {
const BufferedWrite& buffered_write =
batch_buffer_->buffered_writes()[buffered_write_index];
EXPECT_EQ(buf_len, buffered_write.buf_len);
for (size_t i = 0; i < buf_len; ++i) {
EXPECT_EQ(buffer_content, buffered_write.buffer[i]);
if (buffer_content != buffered_write.buffer[i]) {
break;
}
}
EXPECT_EQ(self_addr, buffered_write.self_address);
EXPECT_EQ(peer_addr, buffered_write.peer_address);
if (options == nullptr) {
EXPECT_EQ(nullptr, buffered_write.options);
} else {
EXPECT_EQ(options->release_time_delay,
buffered_write.options->release_time_delay);
}
}
protected:
std::unique_ptr<TestQuicBatchWriterBuffer> batch_buffer_;
QuicIpAddress self_addr_;
QuicSocketAddress peer_addr_;
uint64_t release_time_ = 0;
char packet_buffer_[kMaxOutgoingPacketSize];
};
class BufferSizeSequence {
public:
explicit BufferSizeSequence(
std::vector<std::pair<std::vector<size_t>, size_t>> stages)
: stages_(std::move(stages)),
total_buf_len_(0),
stage_index_(0),
sequence_index_(0) {}
size_t Next() {
const std::vector<size_t>& seq = stages_[stage_index_].first;
size_t buf_len = seq[sequence_index_++ % seq.size()];
total_buf_len_ += buf_len;
if (stages_[stage_index_].second <= total_buf_len_) {
stage_index_ = std::min(stage_index_ + 1, stages_.size() - 1);
}
return buf_len;
}
private:
const std::vector<std::pair<std::vector<size_t>, size_t>> stages_;
size_t total_buf_len_;
size_t stage_index_;
size_t sequence_index_;
};
// Test in-place pushes. A in-place push is a push with a buffer address that is
// equal to the result of GetNextWriteLocation().
TEST_F(QuicBatchWriterBufferTest, InPlacePushes) {
std::vector<BufferSizeSequence> buffer_size_sequences = {
// Push large writes until the buffer is near full, then switch to 1-byte
// writes. This covers the edge cases when detecting insufficient buffer.
BufferSizeSequence({{{1350}, kBatchBufferSize - 3000}, {{1}, 1e6}}),
// A sequence that looks real.
BufferSizeSequence({{{1, 39, 97, 150, 1350, 1350, 1350, 1350}, 1e6}}),
};
for (auto& buffer_size_sequence : buffer_size_sequences) {
SwitchToNewBuffer();
int64_t num_push_failures = 0;
while (batch_buffer_->SizeInUse() < kBatchBufferSize) {
size_t buf_len = buffer_size_sequence.Next();
const bool has_enough_space =
(kBatchBufferSize - batch_buffer_->SizeInUse() >=
kMaxOutgoingPacketSize);
char* buffer = batch_buffer_->GetNextWriteLocation();
if (has_enough_space) {
EXPECT_EQ(batch_buffer_->buffer_ + batch_buffer_->SizeInUse(), buffer);
} else {
EXPECT_EQ(nullptr, buffer);
}
SCOPED_TRACE(testing::Message()
<< "Before Push: buf_len=" << buf_len
<< ", has_enough_space=" << has_enough_space
<< ", batch_buffer=" << batch_buffer_->DebugString());
auto push_result = batch_buffer_->PushBufferedWrite(
buffer, buf_len, self_addr_, peer_addr_, nullptr, release_time_);
if (!push_result.succeeded) {
++num_push_failures;
}
EXPECT_EQ(has_enough_space, push_result.succeeded);
EXPECT_FALSE(push_result.buffer_copied);
if (!has_enough_space) {
break;
}
}
// Expect one and only one failure from the final push operation.
EXPECT_EQ(1, num_push_failures);
}
}
// Test some in-place pushes mixed with pushes with external buffers.
TEST_F(QuicBatchWriterBufferTest, MixedPushes) {
// First, a in-place push.
char* buffer = batch_buffer_->GetNextWriteLocation();
auto push_result = batch_buffer_->PushBufferedWrite(
FillPacketBuffer('A', buffer), kDefaultMaxPacketSize, self_addr_,
peer_addr_, nullptr, release_time_);
EXPECT_TRUE(push_result.succeeded);
EXPECT_FALSE(push_result.buffer_copied);
CheckBufferedWriteContent(0, 'A', kDefaultMaxPacketSize, self_addr_,
peer_addr_, nullptr);
// Then a push with external buffer.
push_result = batch_buffer_->PushBufferedWrite(
FillPacketBuffer('B'), kDefaultMaxPacketSize, self_addr_, peer_addr_,
nullptr, release_time_);
EXPECT_TRUE(push_result.succeeded);
EXPECT_TRUE(push_result.buffer_copied);
CheckBufferedWriteContent(1, 'B', kDefaultMaxPacketSize, self_addr_,
peer_addr_, nullptr);
// Then another in-place push.
buffer = batch_buffer_->GetNextWriteLocation();
push_result = batch_buffer_->PushBufferedWrite(
FillPacketBuffer('C', buffer), kDefaultMaxPacketSize, self_addr_,
peer_addr_, nullptr, release_time_);
EXPECT_TRUE(push_result.succeeded);
EXPECT_FALSE(push_result.buffer_copied);
CheckBufferedWriteContent(2, 'C', kDefaultMaxPacketSize, self_addr_,
peer_addr_, nullptr);
// Then another push with external buffer.
push_result = batch_buffer_->PushBufferedWrite(
FillPacketBuffer('D'), kDefaultMaxPacketSize, self_addr_, peer_addr_,
nullptr, release_time_);
EXPECT_TRUE(push_result.succeeded);
EXPECT_TRUE(push_result.buffer_copied);
CheckBufferedWriteContent(3, 'D', kDefaultMaxPacketSize, self_addr_,
peer_addr_, nullptr);
}
TEST_F(QuicBatchWriterBufferTest, PopAll) {
const int kNumBufferedWrites = 10;
for (int i = 0; i < kNumBufferedWrites; ++i) {
EXPECT_TRUE(batch_buffer_
->PushBufferedWrite(packet_buffer_, kDefaultMaxPacketSize,
self_addr_, peer_addr_, nullptr,
release_time_)
.succeeded);
}
EXPECT_EQ(kNumBufferedWrites,
static_cast<int>(batch_buffer_->buffered_writes().size()));
auto pop_result = batch_buffer_->PopBufferedWrite(kNumBufferedWrites);
EXPECT_EQ(0u, batch_buffer_->buffered_writes().size());
EXPECT_EQ(kNumBufferedWrites, pop_result.num_buffers_popped);
EXPECT_FALSE(pop_result.moved_remaining_buffers);
}
TEST_F(QuicBatchWriterBufferTest, PopPartial) {
const int kNumBufferedWrites = 10;
for (int i = 0; i < kNumBufferedWrites; ++i) {
EXPECT_TRUE(batch_buffer_
->PushBufferedWrite(FillPacketBuffer('A' + i),
kDefaultMaxPacketSize - i, self_addr_,
peer_addr_, nullptr, release_time_)
.succeeded);
}
for (size_t i = 0;
i < kNumBufferedWrites && !batch_buffer_->buffered_writes().empty();
++i) {
const size_t size_before_pop = batch_buffer_->buffered_writes().size();
const size_t expect_size_after_pop =
size_before_pop < i ? 0 : size_before_pop - i;
batch_buffer_->PopBufferedWrite(i);
ASSERT_EQ(expect_size_after_pop, batch_buffer_->buffered_writes().size());
const char first_write_content =
'A' + kNumBufferedWrites - expect_size_after_pop;
const size_t first_write_len =
kDefaultMaxPacketSize - kNumBufferedWrites + expect_size_after_pop;
for (size_t j = 0; j < expect_size_after_pop; ++j) {
CheckBufferedWriteContent(j, first_write_content + j, first_write_len - j,
self_addr_, peer_addr_, nullptr);
}
}
}
TEST_F(QuicBatchWriterBufferTest, InPlacePushWithPops) {
// First, a in-place push.
char* buffer = batch_buffer_->GetNextWriteLocation();
const size_t first_packet_len = 2;
auto push_result = batch_buffer_->PushBufferedWrite(
FillPacketBuffer('A', buffer, first_packet_len), first_packet_len,
self_addr_, peer_addr_, nullptr, release_time_);
EXPECT_TRUE(push_result.succeeded);
EXPECT_FALSE(push_result.buffer_copied);
CheckBufferedWriteContent(0, 'A', first_packet_len, self_addr_, peer_addr_,
nullptr);
// Simulate the case where the writer wants to do another in-place push, but
// can't do so because it can't be batched with the first buffer.
buffer = batch_buffer_->GetNextWriteLocation();
const size_t second_packet_len = 1350;
// Flush the first buffer.
auto pop_result = batch_buffer_->PopBufferedWrite(1);
EXPECT_EQ(1, pop_result.num_buffers_popped);
EXPECT_FALSE(pop_result.moved_remaining_buffers);
// Now the second push.
push_result = batch_buffer_->PushBufferedWrite(
FillPacketBuffer('B', buffer, second_packet_len), second_packet_len,
self_addr_, peer_addr_, nullptr, release_time_);
EXPECT_TRUE(push_result.succeeded);
EXPECT_TRUE(push_result.buffer_copied);
CheckBufferedWriteContent(0, 'B', second_packet_len, self_addr_, peer_addr_,
nullptr);
}
} // namespace
} // namespace test
} // namespace quic