• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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_gso_batch_writer.h"
6 
7 #include <cstdint>
8 #include <limits>
9 #include <memory>
10 #include <utility>
11 
12 #include "quiche/quic/platform/api/quic_ip_address.h"
13 #include "quiche/quic/platform/api/quic_test.h"
14 #include "quiche/quic/test_tools/quic_mock_syscall_wrapper.h"
15 
16 using testing::_;
17 using testing::Invoke;
18 using testing::StrictMock;
19 
20 namespace quic {
21 namespace test {
22 namespace {
23 
PacketLength(const msghdr * msg)24 size_t PacketLength(const msghdr* msg) {
25   size_t length = 0;
26   for (size_t i = 0; i < msg->msg_iovlen; ++i) {
27     length += msg->msg_iov[i].iov_len;
28   }
29   return length;
30 }
31 
MillisToNanos(uint64_t milliseconds)32 uint64_t MillisToNanos(uint64_t milliseconds) { return milliseconds * 1000000; }
33 
34 class QUIC_EXPORT_PRIVATE TestQuicGsoBatchWriter : public QuicGsoBatchWriter {
35  public:
36   using QuicGsoBatchWriter::batch_buffer;
37   using QuicGsoBatchWriter::buffered_writes;
38   using QuicGsoBatchWriter::CanBatch;
39   using QuicGsoBatchWriter::CanBatchResult;
40   using QuicGsoBatchWriter::GetReleaseTime;
41   using QuicGsoBatchWriter::MaxSegments;
42   using QuicGsoBatchWriter::QuicGsoBatchWriter;
43   using QuicGsoBatchWriter::ReleaseTime;
44 
45   static std::unique_ptr<TestQuicGsoBatchWriter>
NewInstanceWithReleaseTimeSupport()46   NewInstanceWithReleaseTimeSupport() {
47     return std::unique_ptr<TestQuicGsoBatchWriter>(new TestQuicGsoBatchWriter(
48         std::make_unique<QuicBatchWriterBuffer>(),
49         /*fd=*/-1, CLOCK_MONOTONIC, ReleaseTimeForceEnabler()));
50   }
51 
NowInNanosForReleaseTime() const52   uint64_t NowInNanosForReleaseTime() const override {
53     return MillisToNanos(forced_release_time_ms_);
54   }
55 
ForceReleaseTimeMs(uint64_t forced_release_time_ms)56   void ForceReleaseTimeMs(uint64_t forced_release_time_ms) {
57     forced_release_time_ms_ = forced_release_time_ms;
58   }
59 
60  private:
61   uint64_t forced_release_time_ms_ = 1;
62 };
63 
64 struct QUIC_EXPORT_PRIVATE TestPerPacketOptions : public PerPacketOptions {
Clonequic::test::__anon1373e6820111::TestPerPacketOptions65   std::unique_ptr<quic::PerPacketOptions> Clone() const override {
66     return std::make_unique<TestPerPacketOptions>(*this);
67   }
68 };
69 
70 // TestBufferedWrite is a copy-constructible BufferedWrite.
71 struct QUIC_EXPORT_PRIVATE TestBufferedWrite : public BufferedWrite {
72   using BufferedWrite::BufferedWrite;
TestBufferedWritequic::test::__anon1373e6820111::TestBufferedWrite73   TestBufferedWrite(const TestBufferedWrite& other)
74       : BufferedWrite(other.buffer, other.buf_len, other.self_address,
75                       other.peer_address,
76                       other.options ? other.options->Clone()
77                                     : std::unique_ptr<PerPacketOptions>(),
78                       other.release_time) {}
79 };
80 
81 // Pointed to by all instances of |BatchCriteriaTestData|. Content not used.
82 static char unused_packet_buffer[kMaxOutgoingPacketSize];
83 
84 struct QUIC_EXPORT_PRIVATE BatchCriteriaTestData {
BatchCriteriaTestDataquic::test::__anon1373e6820111::BatchCriteriaTestData85   BatchCriteriaTestData(size_t buf_len, const QuicIpAddress& self_address,
86                         const QuicSocketAddress& peer_address,
87                         uint64_t release_time, bool can_batch, bool must_flush)
88       : buffered_write(unused_packet_buffer, buf_len, self_address,
89                        peer_address, std::unique_ptr<PerPacketOptions>(),
90                        release_time),
91         can_batch(can_batch),
92         must_flush(must_flush) {}
93 
94   TestBufferedWrite buffered_write;
95   // Expected value of CanBatchResult.can_batch when batching |buffered_write|.
96   bool can_batch;
97   // Expected value of CanBatchResult.must_flush when batching |buffered_write|.
98   bool must_flush;
99 };
100 
BatchCriteriaTestData_SizeDecrease()101 std::vector<BatchCriteriaTestData> BatchCriteriaTestData_SizeDecrease() {
102   const QuicIpAddress self_addr;
103   const QuicSocketAddress peer_addr;
104   std::vector<BatchCriteriaTestData> test_data_table = {
105       // clang-format off
106   // buf_len   self_addr   peer_addr   t_rel   can_batch       must_flush
107     {1350,     self_addr,  peer_addr,  0,      true,           false},
108     {1350,     self_addr,  peer_addr,  0,      true,           false},
109     {1350,     self_addr,  peer_addr,  0,      true,           false},
110     {39,       self_addr,  peer_addr,  0,      true,           true},
111     {39,       self_addr,  peer_addr,  0,      false,          true},
112     {1350,     self_addr,  peer_addr,  0,      false,          true},
113       // clang-format on
114   };
115   return test_data_table;
116 }
117 
BatchCriteriaTestData_SizeIncrease()118 std::vector<BatchCriteriaTestData> BatchCriteriaTestData_SizeIncrease() {
119   const QuicIpAddress self_addr;
120   const QuicSocketAddress peer_addr;
121   std::vector<BatchCriteriaTestData> test_data_table = {
122       // clang-format off
123   // buf_len   self_addr   peer_addr   t_rel   can_batch       must_flush
124     {1350,     self_addr,  peer_addr,  0,      true,           false},
125     {1350,     self_addr,  peer_addr,  0,      true,           false},
126     {1350,     self_addr,  peer_addr,  0,      true,           false},
127     {1351,     self_addr,  peer_addr,  0,      false,          true},
128       // clang-format on
129   };
130   return test_data_table;
131 }
132 
BatchCriteriaTestData_AddressChange()133 std::vector<BatchCriteriaTestData> BatchCriteriaTestData_AddressChange() {
134   const QuicIpAddress self_addr1 = QuicIpAddress::Loopback4();
135   const QuicIpAddress self_addr2 = QuicIpAddress::Loopback6();
136   const QuicSocketAddress peer_addr1(self_addr1, 666);
137   const QuicSocketAddress peer_addr2(self_addr1, 777);
138   const QuicSocketAddress peer_addr3(self_addr2, 666);
139   const QuicSocketAddress peer_addr4(self_addr2, 777);
140   std::vector<BatchCriteriaTestData> test_data_table = {
141       // clang-format off
142   // buf_len   self_addr   peer_addr    t_rel  can_batch       must_flush
143     {1350,     self_addr1, peer_addr1,  0,     true,           false},
144     {1350,     self_addr1, peer_addr1,  0,     true,           false},
145     {1350,     self_addr1, peer_addr1,  0,     true,           false},
146     {1350,     self_addr2, peer_addr1,  0,     false,          true},
147     {1350,     self_addr1, peer_addr2,  0,     false,          true},
148     {1350,     self_addr1, peer_addr3,  0,     false,          true},
149     {1350,     self_addr1, peer_addr4,  0,     false,          true},
150     {1350,     self_addr1, peer_addr4,  0,     false,          true},
151       // clang-format on
152   };
153   return test_data_table;
154 }
155 
BatchCriteriaTestData_ReleaseTime1()156 std::vector<BatchCriteriaTestData> BatchCriteriaTestData_ReleaseTime1() {
157   const QuicIpAddress self_addr;
158   const QuicSocketAddress peer_addr;
159   std::vector<BatchCriteriaTestData> test_data_table = {
160       // clang-format off
161   // buf_len   self_addr   peer_addr   t_rel   can_batch       must_flush
162     {1350,     self_addr,  peer_addr,  5,      true,           false},
163     {1350,     self_addr,  peer_addr,  5,      true,           false},
164     {1350,     self_addr,  peer_addr,  5,      true,           false},
165     {1350,     self_addr,  peer_addr,  9,      false,          true},
166       // clang-format on
167   };
168   return test_data_table;
169 }
170 
BatchCriteriaTestData_ReleaseTime2()171 std::vector<BatchCriteriaTestData> BatchCriteriaTestData_ReleaseTime2() {
172   const QuicIpAddress self_addr;
173   const QuicSocketAddress peer_addr;
174   std::vector<BatchCriteriaTestData> test_data_table = {
175       // clang-format off
176   // buf_len   self_addr   peer_addr   t_rel   can_batch       must_flush
177     {1350,     self_addr,  peer_addr,  0,      true,           false},
178     {1350,     self_addr,  peer_addr,  0,      true,           false},
179     {1350,     self_addr,  peer_addr,  0,      true,           false},
180     {1350,     self_addr,  peer_addr,  9,      false,          true},
181       // clang-format on
182   };
183   return test_data_table;
184 }
185 
BatchCriteriaTestData_MaxSegments(size_t gso_size)186 std::vector<BatchCriteriaTestData> BatchCriteriaTestData_MaxSegments(
187     size_t gso_size) {
188   const QuicIpAddress self_addr;
189   const QuicSocketAddress peer_addr;
190   std::vector<BatchCriteriaTestData> test_data_table;
191   size_t max_segments = TestQuicGsoBatchWriter::MaxSegments(gso_size);
192   for (size_t i = 0; i < max_segments; ++i) {
193     bool is_last_in_batch = (i + 1 == max_segments);
194     test_data_table.push_back({gso_size, self_addr, peer_addr,
195                                /*release_time=*/0, true, is_last_in_batch});
196   }
197   test_data_table.push_back(
198       {gso_size, self_addr, peer_addr, /*release_time=*/0, false, true});
199   return test_data_table;
200 }
201 
202 class QuicGsoBatchWriterTest : public QuicTest {
203  protected:
WritePacket(QuicGsoBatchWriter * writer,size_t packet_size)204   WriteResult WritePacket(QuicGsoBatchWriter* writer, size_t packet_size) {
205     return writer->WritePacket(&packet_buffer_[0], packet_size, self_address_,
206                                peer_address_, nullptr);
207   }
208 
WritePacketWithOptions(QuicGsoBatchWriter * writer,PerPacketOptions * options)209   WriteResult WritePacketWithOptions(QuicGsoBatchWriter* writer,
210                                      PerPacketOptions* options) {
211     return writer->WritePacket(&packet_buffer_[0], 1350, self_address_,
212                                peer_address_, options);
213   }
214 
215   QuicIpAddress self_address_ = QuicIpAddress::Any4();
216   QuicSocketAddress peer_address_{QuicIpAddress::Any4(), 443};
217   char packet_buffer_[1500];
218   StrictMock<MockQuicSyscallWrapper> mock_syscalls_;
219   ScopedGlobalSyscallWrapperOverride syscall_override_{&mock_syscalls_};
220 };
221 
TEST_F(QuicGsoBatchWriterTest,BatchCriteria)222 TEST_F(QuicGsoBatchWriterTest, BatchCriteria) {
223   std::unique_ptr<TestQuicGsoBatchWriter> writer;
224 
225   std::vector<std::vector<BatchCriteriaTestData>> test_data_tables;
226   test_data_tables.emplace_back(BatchCriteriaTestData_SizeDecrease());
227   test_data_tables.emplace_back(BatchCriteriaTestData_SizeIncrease());
228   test_data_tables.emplace_back(BatchCriteriaTestData_AddressChange());
229   test_data_tables.emplace_back(BatchCriteriaTestData_ReleaseTime1());
230   test_data_tables.emplace_back(BatchCriteriaTestData_ReleaseTime2());
231   test_data_tables.emplace_back(BatchCriteriaTestData_MaxSegments(1));
232   test_data_tables.emplace_back(BatchCriteriaTestData_MaxSegments(2));
233   test_data_tables.emplace_back(BatchCriteriaTestData_MaxSegments(1350));
234 
235   for (size_t i = 0; i < test_data_tables.size(); ++i) {
236     writer = TestQuicGsoBatchWriter::NewInstanceWithReleaseTimeSupport();
237 
238     const auto& test_data_table = test_data_tables[i];
239     for (size_t j = 0; j < test_data_table.size(); ++j) {
240       const BatchCriteriaTestData& test_data = test_data_table[j];
241       SCOPED_TRACE(testing::Message() << "i=" << i << ", j=" << j);
242       TestPerPacketOptions options;
243       options.release_time_delay = QuicTime::Delta::FromMicroseconds(
244           test_data.buffered_write.release_time);
245       TestQuicGsoBatchWriter::CanBatchResult result = writer->CanBatch(
246           test_data.buffered_write.buffer, test_data.buffered_write.buf_len,
247           test_data.buffered_write.self_address,
248           test_data.buffered_write.peer_address, &options,
249           test_data.buffered_write.release_time);
250 
251       ASSERT_EQ(test_data.can_batch, result.can_batch);
252       ASSERT_EQ(test_data.must_flush, result.must_flush);
253 
254       if (result.can_batch) {
255         ASSERT_TRUE(writer->batch_buffer()
256                         .PushBufferedWrite(
257                             test_data.buffered_write.buffer,
258                             test_data.buffered_write.buf_len,
259                             test_data.buffered_write.self_address,
260                             test_data.buffered_write.peer_address, &options,
261                             test_data.buffered_write.release_time)
262                         .succeeded);
263       }
264     }
265   }
266 }
267 
TEST_F(QuicGsoBatchWriterTest,WriteSuccess)268 TEST_F(QuicGsoBatchWriterTest, WriteSuccess) {
269   TestQuicGsoBatchWriter writer(/*fd=*/-1);
270 
271   ASSERT_EQ(WriteResult(WRITE_STATUS_OK, 0), WritePacket(&writer, 1000));
272 
273   EXPECT_CALL(mock_syscalls_, Sendmsg(_, _, _))
274       .WillOnce(Invoke([](int /*sockfd*/, const msghdr* msg, int /*flags*/) {
275         EXPECT_EQ(1100u, PacketLength(msg));
276         return 1100;
277       }));
278   ASSERT_EQ(WriteResult(WRITE_STATUS_OK, 1100), WritePacket(&writer, 100));
279   ASSERT_EQ(0u, writer.batch_buffer().SizeInUse());
280   ASSERT_EQ(0u, writer.buffered_writes().size());
281 }
282 
TEST_F(QuicGsoBatchWriterTest,WriteBlockDataNotBuffered)283 TEST_F(QuicGsoBatchWriterTest, WriteBlockDataNotBuffered) {
284   TestQuicGsoBatchWriter writer(/*fd=*/-1);
285 
286   ASSERT_EQ(WriteResult(WRITE_STATUS_OK, 0), WritePacket(&writer, 100));
287   ASSERT_EQ(WriteResult(WRITE_STATUS_OK, 0), WritePacket(&writer, 100));
288 
289   EXPECT_CALL(mock_syscalls_, Sendmsg(_, _, _))
290       .WillOnce(Invoke([](int /*sockfd*/, const msghdr* msg, int /*flags*/) {
291         EXPECT_EQ(200u, PacketLength(msg));
292         errno = EWOULDBLOCK;
293         return -1;
294       }));
295   ASSERT_EQ(WriteResult(WRITE_STATUS_BLOCKED, EWOULDBLOCK),
296             WritePacket(&writer, 150));
297   ASSERT_EQ(200u, writer.batch_buffer().SizeInUse());
298   ASSERT_EQ(2u, writer.buffered_writes().size());
299 }
300 
TEST_F(QuicGsoBatchWriterTest,WriteBlockDataBuffered)301 TEST_F(QuicGsoBatchWriterTest, WriteBlockDataBuffered) {
302   TestQuicGsoBatchWriter writer(/*fd=*/-1);
303 
304   ASSERT_EQ(WriteResult(WRITE_STATUS_OK, 0), WritePacket(&writer, 100));
305   ASSERT_EQ(WriteResult(WRITE_STATUS_OK, 0), WritePacket(&writer, 100));
306 
307   EXPECT_CALL(mock_syscalls_, Sendmsg(_, _, _))
308       .WillOnce(Invoke([](int /*sockfd*/, const msghdr* msg, int /*flags*/) {
309         EXPECT_EQ(250u, PacketLength(msg));
310         errno = EWOULDBLOCK;
311         return -1;
312       }));
313   ASSERT_EQ(WriteResult(WRITE_STATUS_BLOCKED_DATA_BUFFERED, EWOULDBLOCK),
314             WritePacket(&writer, 50));
315 
316   EXPECT_TRUE(writer.IsWriteBlocked());
317 
318   ASSERT_EQ(250u, writer.batch_buffer().SizeInUse());
319   ASSERT_EQ(3u, writer.buffered_writes().size());
320 }
321 
TEST_F(QuicGsoBatchWriterTest,WriteErrorWithoutDataBuffered)322 TEST_F(QuicGsoBatchWriterTest, WriteErrorWithoutDataBuffered) {
323   TestQuicGsoBatchWriter writer(/*fd=*/-1);
324 
325   ASSERT_EQ(WriteResult(WRITE_STATUS_OK, 0), WritePacket(&writer, 100));
326   ASSERT_EQ(WriteResult(WRITE_STATUS_OK, 0), WritePacket(&writer, 100));
327 
328   EXPECT_CALL(mock_syscalls_, Sendmsg(_, _, _))
329       .WillOnce(Invoke([](int /*sockfd*/, const msghdr* msg, int /*flags*/) {
330         EXPECT_EQ(200u, PacketLength(msg));
331         errno = EPERM;
332         return -1;
333       }));
334   WriteResult error_result = WritePacket(&writer, 150);
335   ASSERT_EQ(WriteResult(WRITE_STATUS_ERROR, EPERM), error_result);
336 
337   ASSERT_EQ(3u, error_result.dropped_packets);
338   ASSERT_EQ(0u, writer.batch_buffer().SizeInUse());
339   ASSERT_EQ(0u, writer.buffered_writes().size());
340 }
341 
TEST_F(QuicGsoBatchWriterTest,WriteErrorAfterDataBuffered)342 TEST_F(QuicGsoBatchWriterTest, WriteErrorAfterDataBuffered) {
343   TestQuicGsoBatchWriter writer(/*fd=*/-1);
344 
345   ASSERT_EQ(WriteResult(WRITE_STATUS_OK, 0), WritePacket(&writer, 100));
346   ASSERT_EQ(WriteResult(WRITE_STATUS_OK, 0), WritePacket(&writer, 100));
347 
348   EXPECT_CALL(mock_syscalls_, Sendmsg(_, _, _))
349       .WillOnce(Invoke([](int /*sockfd*/, const msghdr* msg, int /*flags*/) {
350         EXPECT_EQ(250u, PacketLength(msg));
351         errno = EPERM;
352         return -1;
353       }));
354   WriteResult error_result = WritePacket(&writer, 50);
355   ASSERT_EQ(WriteResult(WRITE_STATUS_ERROR, EPERM), error_result);
356 
357   ASSERT_EQ(3u, error_result.dropped_packets);
358   ASSERT_EQ(0u, writer.batch_buffer().SizeInUse());
359   ASSERT_EQ(0u, writer.buffered_writes().size());
360 }
361 
TEST_F(QuicGsoBatchWriterTest,FlushError)362 TEST_F(QuicGsoBatchWriterTest, FlushError) {
363   TestQuicGsoBatchWriter writer(/*fd=*/-1);
364 
365   ASSERT_EQ(WriteResult(WRITE_STATUS_OK, 0), WritePacket(&writer, 100));
366   ASSERT_EQ(WriteResult(WRITE_STATUS_OK, 0), WritePacket(&writer, 100));
367 
368   EXPECT_CALL(mock_syscalls_, Sendmsg(_, _, _))
369       .WillOnce(Invoke([](int /*sockfd*/, const msghdr* msg, int /*flags*/) {
370         EXPECT_EQ(200u, PacketLength(msg));
371         errno = EINVAL;
372         return -1;
373       }));
374   WriteResult error_result = writer.Flush();
375   ASSERT_EQ(WriteResult(WRITE_STATUS_ERROR, EINVAL), error_result);
376 
377   ASSERT_EQ(2u, error_result.dropped_packets);
378   ASSERT_EQ(0u, writer.batch_buffer().SizeInUse());
379   ASSERT_EQ(0u, writer.buffered_writes().size());
380 }
381 
TEST_F(QuicGsoBatchWriterTest,ReleaseTimeNullOptions)382 TEST_F(QuicGsoBatchWriterTest, ReleaseTimeNullOptions) {
383   auto writer = TestQuicGsoBatchWriter::NewInstanceWithReleaseTimeSupport();
384   EXPECT_EQ(0u, writer->GetReleaseTime(nullptr).actual_release_time);
385 }
386 
TEST_F(QuicGsoBatchWriterTest,ReleaseTime)387 TEST_F(QuicGsoBatchWriterTest, ReleaseTime) {
388   const WriteResult write_buffered(WRITE_STATUS_OK, 0);
389 
390   auto writer = TestQuicGsoBatchWriter::NewInstanceWithReleaseTimeSupport();
391 
392   TestPerPacketOptions options;
393   EXPECT_TRUE(options.release_time_delay.IsZero());
394   EXPECT_FALSE(options.allow_burst);
395   EXPECT_EQ(MillisToNanos(1),
396             writer->GetReleaseTime(&options).actual_release_time);
397 
398   // The 1st packet has no delay.
399   WriteResult result = WritePacketWithOptions(writer.get(), &options);
400   ASSERT_EQ(write_buffered, result);
401   EXPECT_EQ(MillisToNanos(1), writer->buffered_writes().back().release_time);
402   EXPECT_EQ(result.send_time_offset, QuicTime::Delta::Zero());
403 
404   // The 2nd packet has some delay, but allows burst.
405   options.release_time_delay = QuicTime::Delta::FromMilliseconds(3);
406   options.allow_burst = true;
407   result = WritePacketWithOptions(writer.get(), &options);
408   ASSERT_EQ(write_buffered, result);
409   EXPECT_EQ(MillisToNanos(1), writer->buffered_writes().back().release_time);
410   EXPECT_EQ(result.send_time_offset, QuicTime::Delta::FromMilliseconds(-3));
411 
412   // The 3rd packet has more delay and does not allow burst.
413   // The first 2 packets are flushed due to different release time.
414   EXPECT_CALL(mock_syscalls_, Sendmsg(_, _, _))
415       .WillOnce(Invoke([](int /*sockfd*/, const msghdr* msg, int /*flags*/) {
416         EXPECT_EQ(2700u, PacketLength(msg));
417         errno = 0;
418         return 0;
419       }));
420   options.release_time_delay = QuicTime::Delta::FromMilliseconds(5);
421   options.allow_burst = false;
422   result = WritePacketWithOptions(writer.get(), &options);
423   ASSERT_EQ(WriteResult(WRITE_STATUS_OK, 2700), result);
424   EXPECT_EQ(MillisToNanos(6), writer->buffered_writes().back().release_time);
425   EXPECT_EQ(result.send_time_offset, QuicTime::Delta::Zero());
426 
427   // The 4th packet has same delay, but allows burst.
428   options.allow_burst = true;
429   result = WritePacketWithOptions(writer.get(), &options);
430   ASSERT_EQ(write_buffered, result);
431   EXPECT_EQ(MillisToNanos(6), writer->buffered_writes().back().release_time);
432   EXPECT_EQ(result.send_time_offset, QuicTime::Delta::Zero());
433 
434   // The 5th packet has same delay, allows burst, but is shorter.
435   // Packets 3,4 and 5 are flushed.
436   EXPECT_CALL(mock_syscalls_, Sendmsg(_, _, _))
437       .WillOnce(Invoke([](int /*sockfd*/, const msghdr* msg, int /*flags*/) {
438         EXPECT_EQ(3000u, PacketLength(msg));
439         errno = 0;
440         return 0;
441       }));
442   options.allow_burst = true;
443   EXPECT_EQ(MillisToNanos(6),
444             writer->GetReleaseTime(&options).actual_release_time);
445   ASSERT_EQ(WriteResult(WRITE_STATUS_OK, 3000),
446             writer->WritePacket(&packet_buffer_[0], 300, self_address_,
447                                 peer_address_, &options));
448   EXPECT_TRUE(writer->buffered_writes().empty());
449 
450   // Pretend 1ms has elapsed and the 6th packet has 1ms less delay. In other
451   // words, the release time should still be the same as packets 3-5.
452   writer->ForceReleaseTimeMs(2);
453   options.release_time_delay = QuicTime::Delta::FromMilliseconds(4);
454   result = WritePacketWithOptions(writer.get(), &options);
455   ASSERT_EQ(write_buffered, result);
456   EXPECT_EQ(MillisToNanos(6), writer->buffered_writes().back().release_time);
457   EXPECT_EQ(result.send_time_offset, QuicTime::Delta::Zero());
458 }
459 
460 }  // namespace
461 }  // namespace test
462 }  // namespace quic
463