Create a QuicAlarm-based source for random test data for Quartc.
This is the first element required to reimplement quic_quality_test. By basing
it on the QuicAlarm, we can use it in either a controlled environment (such as
the one provided by quic::simulator::Simulator) or with a real thread (eg. by
using QuartcAlarmFactory). Use in a controlled environment will make it easier
to write tests that reproduce specific corner cases in congestion control.
A quality test will likely use two of these in each direction, configured to
simulate an audio source and a video source. Note that audio and video
each have different bitrate constraints (min/max) and frame intervals.
The QuartcDataSource generates a frame of random data every frame_interval. The
frame's size depends on a bitrate allocation, which can be updated dynamically.
The bitrate can be configured with a min and max (to emulate streams of
different sizes, such as audio/video).
Each frame includes a 20-byte header which includes:
- A source id (to distinguish between sources when multiple are in use)
- A sequence number (incremented for every frame, useful for telling the
difference between 'source was disabled' and 'frames were lost')
- A send timestamp (when the data was generated, to compute end-to-end delay)
The remainder of each frame is randomly-generated payload data.
The utility also comes with a struct and parse function which may be used to
deserialize its output. This is useful for its own unit test, but is included
in the library so that other tests may use it to read data from the header.
gfe-relnote: n/a (Quartc/test only)
PiperOrigin-RevId: 242693623
Change-Id: Ia496667bedcb518232c92c4b73c41813cd20b9cb
diff --git a/quic/quartc/test/quartc_data_source.cc b/quic/quartc/test/quartc_data_source.cc
new file mode 100644
index 0000000..8f81ccb
--- /dev/null
+++ b/quic/quartc/test/quartc_data_source.cc
@@ -0,0 +1,138 @@
+// Copyright (c) 2017 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/quartc/test/quartc_data_source.h"
+
+#include "net/third_party/quiche/src/quic/core/quic_data_reader.h"
+#include "net/third_party/quiche/src/quic/core/quic_data_writer.h"
+
+namespace quic {
+namespace test {
+
+namespace {
+
+class SendAlarmDelegate : public QuicAlarm::Delegate {
+ public:
+ explicit SendAlarmDelegate(QuartcDataSource* source) : source_(source) {}
+
+ void OnAlarm() override { source_->OnSendAlarm(); }
+
+ private:
+ QuartcDataSource* source_;
+};
+
+} // namespace
+
+bool ParsedQuartcDataFrame::Parse(QuicStringPiece data,
+ ParsedQuartcDataFrame* out) {
+ QuicDataReader reader(data.data(), data.size());
+
+ uint32_t source_id;
+ if (!reader.ReadUInt32(&source_id)) {
+ return false;
+ }
+
+ uint64_t sequence_number;
+ if (!reader.ReadUInt64(&sequence_number)) {
+ return false;
+ }
+
+ uint64_t time_bits;
+ if (!reader.ReadUInt64(&time_bits)) {
+ return false;
+ }
+ QuicStringPiece payload = reader.ReadRemainingPayload();
+
+ out->source_id = source_id;
+ out->sequence_number = sequence_number;
+ out->send_time =
+ QuicTime::Zero() + QuicTime::Delta::FromMicroseconds(time_bits);
+ out->payload = std::string(payload.data(), payload.size());
+
+ return true;
+}
+
+QuartcDataSource::QuartcDataSource(const QuicClock* clock,
+ QuicAlarmFactory* alarm_factory,
+ QuicRandom* random,
+ const Config& config,
+ Delegate* delegate)
+ : clock_(clock),
+ alarm_factory_(alarm_factory),
+ random_(random),
+ config_(config),
+ delegate_(delegate),
+ send_alarm_(alarm_factory_->CreateAlarm(new SendAlarmDelegate(this))),
+ sequence_number_(0),
+ allocated_bandwidth_(config_.min_bandwidth),
+ last_send_time_(QuicTime::Zero()) {}
+
+void QuartcDataSource::OnSendAlarm() {
+ QuicTime now = clock_->Now();
+ QuicTime::Delta time_since_last_send(QuicTime::Delta::Zero());
+ if (last_send_time_.IsInitialized()) {
+ // If previous frames have been sent, use the actual time since the last
+ // send to compute the frame size.
+ time_since_last_send = now - last_send_time_;
+ } else {
+ // For the first frame, use the configured frame interval.
+ time_since_last_send = config_.frame_interval;
+ }
+
+ QuicByteCount bytes =
+ std::max(kDataFrameHeaderSize,
+ allocated_bandwidth_.ToBytesPerPeriod(time_since_last_send));
+ while (config_.max_frame_size >= kDataFrameHeaderSize &&
+ bytes > config_.max_frame_size) {
+ GenerateFrame(config_.max_frame_size, now);
+ bytes -= config_.max_frame_size;
+ }
+ GenerateFrame(bytes, now);
+
+ // Reset alarm.
+ last_send_time_ = now;
+ send_alarm_->Set(now + config_.frame_interval);
+}
+
+QuicBandwidth QuartcDataSource::AllocateBandwidth(QuicBandwidth bandwidth) {
+ allocated_bandwidth_ = std::max(config_.min_bandwidth,
+ std::min(bandwidth, config_.max_bandwidth));
+ return std::max(bandwidth - allocated_bandwidth_, QuicBandwidth::Zero());
+}
+
+bool QuartcDataSource::Enabled() {
+ return send_alarm_->IsSet();
+}
+
+void QuartcDataSource::SetEnabled(bool value) {
+ if (value) {
+ send_alarm_->Set(clock_->Now());
+ } else {
+ send_alarm_->Cancel();
+
+ // Reset the last send time. When re-enabled, the data source should
+ // produce a frame of approximately the right size for its current
+ // bandwidth allocation and frame interval, not a huge frame accounting for
+ // all the time since it was disabled.
+ last_send_time_ = QuicTime::Zero();
+ }
+}
+
+void QuartcDataSource::GenerateFrame(QuicByteCount frame_size, QuicTime now) {
+ if (buffer_.size() < frame_size) {
+ buffer_.resize(frame_size);
+ }
+
+ // Generate data.
+ QuicDataWriter writer(frame_size, buffer_.data());
+ writer.WriteUInt32(config_.id);
+ writer.WriteUInt64(sequence_number_++);
+ writer.WriteUInt64((now - QuicTime::Zero()).ToMicroseconds());
+ writer.WriteRandomBytes(random_, writer.remaining());
+
+ delegate_->OnDataProduced(writer.data(), writer.length());
+}
+
+} // namespace test
+} // namespace quic
diff --git a/quic/quartc/test/quartc_data_source.h b/quic/quartc/test/quartc_data_source.h
new file mode 100644
index 0000000..0e851d3
--- /dev/null
+++ b/quic/quartc/test/quartc_data_source.h
@@ -0,0 +1,113 @@
+// Copyright (c) 2017 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.
+
+#ifndef QUICHE_QUIC_QUARTC_TEST_QUARTC_DATA_SOURCE_H_
+#define QUICHE_QUIC_QUARTC_TEST_QUARTC_DATA_SOURCE_H_
+
+#include "net/third_party/quiche/src/quic/core/crypto/quic_random.h"
+#include "net/third_party/quiche/src/quic/core/quic_alarm.h"
+#include "net/third_party/quiche/src/quic/core/quic_alarm_factory.h"
+#include "net/third_party/quiche/src/quic/core/quic_bandwidth.h"
+#include "net/third_party/quiche/src/quic/core/quic_time.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_clock.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_string_piece.h"
+
+namespace quic {
+namespace test {
+
+// Frames sent by a QuartcDataSource have a 20-byte header (4 bytes for the
+// source id, 8 bytes for the sequence number, 8 bytes for the timestamp).
+constexpr QuicByteCount kDataFrameHeaderSize = 20;
+
+// Struct representing one frame of data sent by a QuartcDataSource.
+struct ParsedQuartcDataFrame {
+ // Parses the given data as a frame generated by QuartcDataSource. Returns
+ // true if parsing succeeds or false if parsing fails.
+ static bool Parse(QuicStringPiece data, ParsedQuartcDataFrame* out);
+
+ // Note that a properly formatted, parseable frame always contains these three
+ // header fields.
+ int32_t source_id = -1;
+ int64_t sequence_number = -1;
+ QuicTime send_time = QuicTime::Zero();
+ std::string payload;
+};
+
+// Alarm-based source of random data to send. QuartcDataSource is configured to
+// generate new data at fixed intervals.
+class QuartcDataSource {
+ public:
+ struct Config {
+ // 32-bit ID for this data source.
+ int32_t id = 0;
+
+ // Minimum bandwidth allocated to this data source.
+ QuicBandwidth min_bandwidth = QuicBandwidth::Zero();
+
+ // Maximum bandwidth allocated to this data source.
+ QuicBandwidth max_bandwidth = QuicBandwidth::Infinite();
+
+ // Interval between frames for this data source.
+ QuicTime::Delta frame_interval = QuicTime::Delta::FromMilliseconds(10);
+
+ // Maximum size of frames produced by this source. If this value is greater
+ // than or equal to kDataFrameHeaderSize, the source may produce multiple
+ // frames with the same timestamp rather than a single frame that is larger
+ // than this size. If less than kDataFrameHeaderSize, the value is ignored.
+ QuicByteCount max_frame_size = 0;
+ };
+
+ class Delegate {
+ public:
+ virtual ~Delegate() = default;
+
+ virtual void OnDataProduced(const char* data, size_t length) = 0;
+ };
+
+ QuartcDataSource(const QuicClock* clock,
+ QuicAlarmFactory* alarm_factory,
+ QuicRandom* random,
+ const Config& config,
+ Delegate* delegate);
+
+ void OnSendAlarm();
+
+ // Allocates bandwidth to this source. The source clamps the given value
+ // between its configured min and max bandwidth, and returns any amount in
+ // excess of its maximum allocation.
+ QuicBandwidth AllocateBandwidth(QuicBandwidth bandwidth);
+
+ // Whether the data source is enabled. The data source only produces data
+ // when enabled. When first enabled, the data source starts sending
+ // immediately. When disabled, the data source stops sending immediately.
+ bool Enabled();
+ void SetEnabled(bool value);
+
+ private:
+ void GenerateFrame(QuicByteCount frame_size, QuicTime now);
+
+ const QuicClock* clock_;
+ QuicAlarmFactory* alarm_factory_;
+ QuicRandom* random_;
+ const Config config_;
+ Delegate* delegate_;
+
+ std::unique_ptr<QuicAlarm> send_alarm_;
+
+ int64_t sequence_number_;
+ QuicBandwidth allocated_bandwidth_;
+ QuicTime last_send_time_;
+
+ // Buffer for frames of data generated by the source. The source writes each
+ // frame into this buffer, then hands the delegate a pointer to it. It's a
+ // std::vector simply to make it quick and easy to resize if necessary (eg. if
+ // |allocated_bandwidth_| increases and the frame size goes up. Otherwise, it
+ // would be a char[].
+ std::vector<char> buffer_;
+};
+
+} // namespace test
+} // namespace quic
+
+#endif // QUICHE_QUIC_QUARTC_TEST_QUARTC_DATA_SOURCE_H_
diff --git a/quic/quartc/test/quartc_data_source_test.cc b/quic/quartc/test/quartc_data_source_test.cc
new file mode 100644
index 0000000..d9a1cfe
--- /dev/null
+++ b/quic/quartc/test/quartc_data_source_test.cc
@@ -0,0 +1,290 @@
+// Copyright (c) 2017 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/quartc/test/quartc_data_source.h"
+
+#include <vector>
+
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_test.h"
+#include "net/third_party/quiche/src/quic/test_tools/simulator/simulator.h"
+
+namespace quic {
+namespace test {
+namespace {
+
+class FakeDelegate : public QuartcDataSource::Delegate {
+ public:
+ void OnDataProduced(const char* data, size_t length) override;
+
+ const std::vector<ParsedQuartcDataFrame>& frames() { return frames_; }
+
+ private:
+ std::vector<ParsedQuartcDataFrame> frames_;
+};
+
+void FakeDelegate::OnDataProduced(const char* data, size_t length) {
+ ParsedQuartcDataFrame frame;
+ QuicStringPiece message(data, length);
+ if (ParsedQuartcDataFrame::Parse(message, &frame)) {
+ frames_.push_back(frame);
+ } else {
+ LOG(FATAL) << "Data source produced a frame it can't parse: " << message;
+ }
+}
+
+class QuartcDataSourceTest : public QuicTest {
+ protected:
+ QuartcDataSourceTest() : simulator_() {}
+
+ simulator::Simulator simulator_;
+ FakeDelegate delegate_;
+
+ std::unique_ptr<QuartcDataSource> source_;
+};
+
+TEST_F(QuartcDataSourceTest, ProducesFrameEveryInterval) {
+ QuartcDataSource::Config config;
+ config.frame_interval = QuicTime::Delta::FromMilliseconds(20);
+ source_ = QuicMakeUnique<QuartcDataSource>(
+ simulator_.GetClock(), simulator_.GetAlarmFactory(),
+ simulator_.GetRandomGenerator(), config, &delegate_);
+ source_->AllocateBandwidth(
+ QuicBandwidth::FromBytesAndTimeDelta(1000, config.frame_interval));
+ source_->SetEnabled(true);
+
+ simulator_.RunFor(config.frame_interval);
+ EXPECT_EQ(delegate_.frames().size(), 1);
+
+ simulator_.RunFor(config.frame_interval);
+ EXPECT_EQ(delegate_.frames().size(), 2);
+
+ simulator_.RunFor(config.frame_interval * 20);
+ EXPECT_EQ(delegate_.frames().size(), 22);
+}
+
+TEST_F(QuartcDataSourceTest, DoesNotProduceFramesUntilEnabled) {
+ QuartcDataSource::Config config;
+ source_ = QuicMakeUnique<QuartcDataSource>(
+ simulator_.GetClock(), simulator_.GetAlarmFactory(),
+ simulator_.GetRandomGenerator(), config, &delegate_);
+ source_->AllocateBandwidth(
+ QuicBandwidth::FromBytesAndTimeDelta(1000, config.frame_interval));
+
+ simulator_.RunFor(config.frame_interval * 20);
+ EXPECT_EQ(delegate_.frames().size(), 0);
+
+ // The first frame is produced immediately (but asynchronously) upon enabling
+ // the source.
+ source_->SetEnabled(true);
+ simulator_.RunFor(QuicTime::Delta::FromMicroseconds(1));
+ EXPECT_EQ(delegate_.frames().size(), 1);
+}
+
+TEST_F(QuartcDataSourceTest, DisableAndEnable) {
+ QuartcDataSource::Config config;
+ source_ = QuicMakeUnique<QuartcDataSource>(
+ simulator_.GetClock(), simulator_.GetAlarmFactory(),
+ simulator_.GetRandomGenerator(), config, &delegate_);
+ source_->AllocateBandwidth(
+ QuicBandwidth::FromBytesAndTimeDelta(1000, config.frame_interval));
+
+ source_->SetEnabled(true);
+ simulator_.RunFor(config.frame_interval * 20);
+ EXPECT_EQ(delegate_.frames().size(), 20);
+
+ // No new frames while the source is disabled.
+ source_->SetEnabled(false);
+ simulator_.RunFor(config.frame_interval * 20);
+ EXPECT_EQ(delegate_.frames().size(), 20);
+
+ // The first frame is produced immediately (but asynchronously) upon enabling
+ // the source.
+ source_->SetEnabled(true);
+ simulator_.RunFor(QuicTime::Delta::FromMicroseconds(1));
+ ASSERT_EQ(delegate_.frames().size(), 21);
+
+ // The first frame after a pause should be no larger than previous frames.
+ EXPECT_EQ(delegate_.frames()[0].payload.size(),
+ delegate_.frames()[20].payload.size());
+
+ // The first frame after the pause should have a much later timestamp.
+ // Note that the previous frame (19) happens at the *start* of the 20th
+ // interval. Frame 20 would normally happen one interval later, but we've
+ // delayed it by an extra 20 intervals (for a total of 21 intervals later).
+ EXPECT_EQ(delegate_.frames()[20].send_time - delegate_.frames()[19].send_time,
+ 21 * config.frame_interval);
+}
+
+TEST_F(QuartcDataSourceTest, ProducesFramesWithConfiguredSourceId) {
+ QuartcDataSource::Config config;
+ config.id = 7;
+ source_ = QuicMakeUnique<QuartcDataSource>(
+ simulator_.GetClock(), simulator_.GetAlarmFactory(),
+ simulator_.GetRandomGenerator(), config, &delegate_);
+ source_->AllocateBandwidth(
+ QuicBandwidth::FromBytesAndTimeDelta(1000, config.frame_interval));
+ source_->SetEnabled(true);
+ simulator_.RunFor(config.frame_interval);
+
+ ASSERT_EQ(delegate_.frames().size(), 1);
+ EXPECT_EQ(delegate_.frames()[0].source_id, config.id);
+}
+
+TEST_F(QuartcDataSourceTest, ProducesFramesAtAllocatedBandwidth) {
+ QuartcDataSource::Config config;
+ source_ = QuicMakeUnique<QuartcDataSource>(
+ simulator_.GetClock(), simulator_.GetAlarmFactory(),
+ simulator_.GetRandomGenerator(), config, &delegate_);
+
+ constexpr QuicByteCount bytes_per_frame = 1000;
+ source_->AllocateBandwidth(QuicBandwidth::FromBytesAndTimeDelta(
+ bytes_per_frame, config.frame_interval));
+ source_->SetEnabled(true);
+ simulator_.RunFor(config.frame_interval);
+
+ ASSERT_EQ(delegate_.frames().size(), 1);
+ EXPECT_EQ(delegate_.frames()[0].payload.size(),
+ bytes_per_frame - kDataFrameHeaderSize);
+}
+
+TEST_F(QuartcDataSourceTest, AlwaysProducesParseableHeader) {
+ QuartcDataSource::Config config;
+ source_ = QuicMakeUnique<QuartcDataSource>(
+ simulator_.GetClock(), simulator_.GetAlarmFactory(),
+ simulator_.GetRandomGenerator(), config, &delegate_);
+
+ // Allocate less bandwidth than the source requires for its header.
+ source_->AllocateBandwidth(QuicBandwidth::FromBytesAndTimeDelta(
+ kDataFrameHeaderSize - 10, config.frame_interval));
+ source_->SetEnabled(true);
+
+ QuicTime start_time = simulator_.GetClock()->Now();
+ simulator_.RunFor(config.frame_interval);
+
+ ASSERT_EQ(delegate_.frames().size(), 1);
+ EXPECT_EQ(delegate_.frames()[0].payload.size(), 0);
+
+ // Header fields are still present and parseable.
+ EXPECT_EQ(delegate_.frames()[0].source_id, 0);
+ EXPECT_EQ(delegate_.frames()[0].sequence_number, 0);
+ EXPECT_EQ(delegate_.frames()[0].send_time, start_time);
+}
+
+TEST_F(QuartcDataSourceTest, ProducesSequenceNumbers) {
+ QuartcDataSource::Config config;
+ source_ = QuicMakeUnique<QuartcDataSource>(
+ simulator_.GetClock(), simulator_.GetAlarmFactory(),
+ simulator_.GetRandomGenerator(), config, &delegate_);
+ source_->AllocateBandwidth(
+ QuicBandwidth::FromBytesAndTimeDelta(1000, config.frame_interval));
+ source_->SetEnabled(true);
+
+ simulator_.RunFor(config.frame_interval * 20);
+
+ ASSERT_EQ(delegate_.frames().size(), 20);
+ for (int i = 0; i < 20; ++i) {
+ EXPECT_EQ(delegate_.frames()[i].sequence_number, i);
+ }
+}
+
+TEST_F(QuartcDataSourceTest, ProducesSendTimes) {
+ QuartcDataSource::Config config;
+ source_ = QuicMakeUnique<QuartcDataSource>(
+ simulator_.GetClock(), simulator_.GetAlarmFactory(),
+ simulator_.GetRandomGenerator(), config, &delegate_);
+ source_->AllocateBandwidth(
+ QuicBandwidth::FromBytesAndTimeDelta(1000, config.frame_interval));
+ source_->SetEnabled(true);
+
+ simulator_.RunFor(config.frame_interval * 20);
+
+ ASSERT_EQ(delegate_.frames().size(), 20);
+ QuicTime first_send_time = delegate_.frames()[0].send_time;
+ for (int i = 1; i < 20; ++i) {
+ EXPECT_EQ(delegate_.frames()[i].send_time,
+ first_send_time + i * config.frame_interval);
+ }
+}
+
+TEST_F(QuartcDataSourceTest, AllocateClampsToMin) {
+ QuartcDataSource::Config config;
+ config.min_bandwidth = QuicBandwidth::FromBitsPerSecond(8000);
+ config.frame_interval = QuicTime::Delta::FromMilliseconds(100);
+ source_ = QuicMakeUnique<QuartcDataSource>(
+ simulator_.GetClock(), simulator_.GetAlarmFactory(),
+ simulator_.GetRandomGenerator(), config, &delegate_);
+
+ // When allocating less than the minimum, there is nothing left over.
+ EXPECT_EQ(source_->AllocateBandwidth(QuicBandwidth::FromBitsPerSecond(6000)),
+ QuicBandwidth::Zero());
+
+ source_->SetEnabled(true);
+ simulator_.RunFor(config.frame_interval);
+
+ // The frames produced use min_bandwidth instead of the lower allocation.
+ ASSERT_EQ(delegate_.frames().size(), 1);
+ EXPECT_EQ(delegate_.frames()[0].payload.size(),
+ config.min_bandwidth.ToBytesPerPeriod(config.frame_interval) -
+ kDataFrameHeaderSize);
+}
+
+TEST_F(QuartcDataSourceTest, AllocateClampsToMax) {
+ QuartcDataSource::Config config;
+ config.max_bandwidth = QuicBandwidth::FromBitsPerSecond(8000);
+ config.frame_interval = QuicTime::Delta::FromMilliseconds(100);
+ source_ = QuicMakeUnique<QuartcDataSource>(
+ simulator_.GetClock(), simulator_.GetAlarmFactory(),
+ simulator_.GetRandomGenerator(), config, &delegate_);
+
+ // When allocating more than the maximum, the excess is returned.
+ EXPECT_EQ(source_->AllocateBandwidth(QuicBandwidth::FromBitsPerSecond(10000)),
+ QuicBandwidth::FromBitsPerSecond(2000));
+
+ source_->SetEnabled(true);
+ simulator_.RunFor(config.frame_interval);
+
+ // The frames produced use max_bandwidth instead of the higher allocation.
+ ASSERT_EQ(delegate_.frames().size(), 1);
+ EXPECT_EQ(delegate_.frames()[0].payload.size(),
+ config.max_bandwidth.ToBytesPerPeriod(config.frame_interval) -
+ kDataFrameHeaderSize);
+}
+
+TEST_F(QuartcDataSourceTest, MaxFrameSize) {
+ constexpr QuicByteCount bytes_per_frame = 1000;
+ QuartcDataSource::Config config;
+ config.max_frame_size = bytes_per_frame;
+ source_ = QuicMakeUnique<QuartcDataSource>(
+ simulator_.GetClock(), simulator_.GetAlarmFactory(),
+ simulator_.GetRandomGenerator(), config, &delegate_);
+
+ // Allocate enough bandwidth for more than one frame per interval.
+ source_->AllocateBandwidth(QuicBandwidth::FromBytesAndTimeDelta(
+ 3 * bytes_per_frame, config.frame_interval));
+ source_->SetEnabled(true);
+
+ QuicTime start_time = simulator_.GetClock()->Now();
+ simulator_.RunFor(config.frame_interval);
+
+ // Since there's enough bandwidth for three frames per interval, that's what
+ // the source should generate.
+ EXPECT_EQ(delegate_.frames().size(), 3);
+ int i = 0;
+ for (const auto& frame : delegate_.frames()) {
+ // Each of the frames should start with a header that can be parsed.
+ // Each gets the same timestamp, but a different sequence number.
+ EXPECT_EQ(frame.source_id, config.id);
+ EXPECT_EQ(frame.sequence_number, i++);
+ EXPECT_EQ(frame.send_time, start_time);
+
+ // Each of the frames should have the configured maximum size.
+ EXPECT_EQ(frame.payload.size(), bytes_per_frame - kDataFrameHeaderSize);
+ }
+}
+
+} // namespace
+} // namespace test
+} // namespace quic