blob: 9e9f1b7ed0e35db025621050db6ff40ec421d183 [file] [log] [blame]
QUICHE teama6ef0a62019-03-07 20:34:33 -05001// Copyright 2013 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/http/quic_spdy_stream.h"
6
7#include <utility>
8
9#include "net/third_party/quiche/src/quic/core/http/quic_spdy_session.h"
10#include "net/third_party/quiche/src/quic/core/http/spdy_utils.h"
11#include "net/third_party/quiche/src/quic/core/quic_utils.h"
12#include "net/third_party/quiche/src/quic/core/quic_write_blocked_list.h"
13#include "net/third_party/quiche/src/quic/platform/api/quic_bug_tracker.h"
14#include "net/third_party/quiche/src/quic/platform/api/quic_flag_utils.h"
15#include "net/third_party/quiche/src/quic/platform/api/quic_flags.h"
16#include "net/third_party/quiche/src/quic/platform/api/quic_logging.h"
17#include "net/third_party/quiche/src/quic/platform/api/quic_mem_slice_storage.h"
18#include "net/third_party/quiche/src/quic/platform/api/quic_string.h"
19#include "net/third_party/quiche/src/quic/platform/api/quic_string_piece.h"
20#include "net/third_party/quiche/src/quic/platform/api/quic_text_utils.h"
21#include "net/third_party/quiche/src/spdy/core/spdy_protocol.h"
22
23using spdy::SpdyHeaderBlock;
24using spdy::SpdyPriority;
25
26namespace quic {
27
28// Visitor of HttpDecoder that passes data frame to QuicSpdyStream and closes
29// the connection on unexpected frames.
30class QuicSpdyStream::HttpDecoderVisitor : public HttpDecoder::Visitor {
31 public:
32 explicit HttpDecoderVisitor(QuicSpdyStream* stream) : stream_(stream) {}
33 HttpDecoderVisitor(const HttpDecoderVisitor&) = delete;
34 HttpDecoderVisitor& operator=(const HttpDecoderVisitor&) = delete;
35
36 void OnError(HttpDecoder* decoder) override {
37 stream_->session()->connection()->CloseConnection(
38 QUIC_HTTP_DECODER_ERROR, "Http decoder internal error",
39 ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
40 }
41
42 void OnPriorityFrame(const PriorityFrame& frame) override {
43 CloseConnectionOnWrongFrame("Priority");
44 }
45
46 void OnCancelPushFrame(const CancelPushFrame& frame) override {
47 CloseConnectionOnWrongFrame("Cancel Push");
48 }
49
50 void OnMaxPushIdFrame(const MaxPushIdFrame& frame) override {
51 CloseConnectionOnWrongFrame("Max Push Id");
52 }
53
54 void OnGoAwayFrame(const GoAwayFrame& frame) override {
55 CloseConnectionOnWrongFrame("Goaway");
56 }
57
58 void OnSettingsFrame(const SettingsFrame& frame) override {
59 CloseConnectionOnWrongFrame("Settings");
60 }
61
62 void OnDuplicatePushFrame(const DuplicatePushFrame& frame) override {
63 CloseConnectionOnWrongFrame("Duplicate Push");
64 }
65
66 void OnDataFrameStart(Http3FrameLengths frame_lengths) override {
67 stream_->OnDataFrameStart(frame_lengths);
68 }
69
70 void OnDataFramePayload(QuicStringPiece payload) override {
71 stream_->OnDataFramePayload(payload);
72 }
73
74 void OnDataFrameEnd() override { stream_->OnDataFrameEnd(); }
75
76 void OnHeadersFrameStart() override {
77 CloseConnectionOnWrongFrame("Headers");
78 }
79
80 void OnHeadersFramePayload(QuicStringPiece payload) override {
81 CloseConnectionOnWrongFrame("Headers");
82 }
83
84 void OnHeadersFrameEnd(QuicByteCount frame_len) override {
85 CloseConnectionOnWrongFrame("Headers");
86 }
87
88 void OnPushPromiseFrameStart(PushId push_id) override {
89 CloseConnectionOnWrongFrame("Push Promise");
90 }
91
92 void OnPushPromiseFramePayload(QuicStringPiece payload) override {
93 CloseConnectionOnWrongFrame("Push Promise");
94 }
95
96 void OnPushPromiseFrameEnd() override {
97 CloseConnectionOnWrongFrame("Push Promise");
98 }
99
100 private:
101 void CloseConnectionOnWrongFrame(QuicString frame_type) {
102 stream_->session()->connection()->CloseConnection(
103 QUIC_HTTP_DECODER_ERROR, frame_type + " frame received on data stream",
104 ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
105 }
106
107 QuicSpdyStream* stream_;
108};
109
110#define ENDPOINT \
111 (session()->perspective() == Perspective::IS_SERVER ? "Server: " \
112 : "Client:" \
113 " ")
114
115QuicSpdyStream::QuicSpdyStream(QuicStreamId id,
116 QuicSpdySession* spdy_session,
117 StreamType type)
118 : QuicStream(id, spdy_session, /*is_static=*/false, type),
119 spdy_session_(spdy_session),
120 visitor_(nullptr),
121 headers_decompressed_(false),
122 trailers_decompressed_(false),
123 trailers_consumed_(false),
124 http_decoder_visitor_(new HttpDecoderVisitor(this)),
125 body_buffer_(sequencer()),
126 ack_listener_(nullptr) {
127 DCHECK_NE(QuicUtils::GetCryptoStreamId(
128 spdy_session->connection()->transport_version()),
129 id);
130 // Don't receive any callbacks from the sequencer until headers
131 // are complete.
132 sequencer()->SetBlockedUntilFlush();
133
134 if (VersionHasDataFrameHeader(
135 spdy_session_->connection()->transport_version())) {
136 sequencer()->set_level_triggered(true);
137 }
138 decoder_.set_visitor(http_decoder_visitor_.get());
139}
140
141QuicSpdyStream::QuicSpdyStream(PendingStream pending,
142 QuicSpdySession* spdy_session,
143 StreamType type)
144 : QuicStream(std::move(pending), type),
145 spdy_session_(spdy_session),
146 visitor_(nullptr),
147 headers_decompressed_(false),
148 trailers_decompressed_(false),
149 trailers_consumed_(false),
150 http_decoder_visitor_(new HttpDecoderVisitor(this)),
151 body_buffer_(sequencer()),
152 ack_listener_(nullptr) {
153 DCHECK_NE(QuicUtils::GetCryptoStreamId(
154 spdy_session->connection()->transport_version()),
155 id());
156 // Don't receive any callbacks from the sequencer until headers
157 // are complete.
158 sequencer()->SetBlockedUntilFlush();
159
160 if (VersionHasDataFrameHeader(
161 spdy_session_->connection()->transport_version())) {
162 sequencer()->set_level_triggered(true);
163 }
164 decoder_.set_visitor(http_decoder_visitor_.get());
165}
166
167QuicSpdyStream::~QuicSpdyStream() {}
168
169size_t QuicSpdyStream::WriteHeaders(
170 SpdyHeaderBlock header_block,
171 bool fin,
172 QuicReferenceCountedPointer<QuicAckListenerInterface> ack_listener) {
173 size_t bytes_written =
174 WriteHeadersImpl(std::move(header_block), fin, std::move(ack_listener));
175 if (fin) {
176 // TODO(rch): Add test to ensure fin_sent_ is set whenever a fin is sent.
177 set_fin_sent(true);
178 CloseWriteSide();
179 }
180 return bytes_written;
181}
182
183void QuicSpdyStream::WriteOrBufferBody(QuicStringPiece data, bool fin) {
184 if (!VersionHasDataFrameHeader(
185 spdy_session_->connection()->transport_version()) ||
186 data.length() == 0) {
187 WriteOrBufferData(data, fin, nullptr);
188 return;
189 }
190 QuicConnection::ScopedPacketFlusher flusher(
191 spdy_session_->connection(), QuicConnection::SEND_ACK_IF_PENDING);
192
193 // Write frame header.
194 std::unique_ptr<char[]> buffer;
195 QuicByteCount header_length =
196 encoder_.SerializeDataFrameHeader(data.length(), &buffer);
197 unacked_frame_headers_offsets_.Add(
198 send_buffer().stream_offset(),
199 send_buffer().stream_offset() + header_length);
200 QUIC_DLOG(INFO) << "Stream " << id() << " is writing header of length "
201 << header_length;
202 WriteOrBufferData(QuicStringPiece(buffer.get(), header_length), false,
203 nullptr);
204
205 // Write body.
206 QUIC_DLOG(INFO) << "Stream " << id() << " is writing body of length "
207 << data.length();
208 WriteOrBufferData(data, fin, nullptr);
209}
210
211size_t QuicSpdyStream::WriteTrailers(
212 SpdyHeaderBlock trailer_block,
213 QuicReferenceCountedPointer<QuicAckListenerInterface> ack_listener) {
214 if (fin_sent()) {
215 QUIC_BUG << "Trailers cannot be sent after a FIN, on stream " << id();
216 return 0;
217 }
218
219 // The header block must contain the final offset for this stream, as the
220 // trailers may be processed out of order at the peer.
221 QUIC_DLOG(INFO) << "Inserting trailer: (" << kFinalOffsetHeaderKey << ", "
222 << stream_bytes_written() + BufferedDataBytes() << ")";
223 trailer_block.insert(
224 std::make_pair(kFinalOffsetHeaderKey,
225 QuicTextUtils::Uint64ToString(stream_bytes_written() +
226 BufferedDataBytes())));
227
228 // Write the trailing headers with a FIN, and close stream for writing:
229 // trailers are the last thing to be sent on a stream.
230 const bool kFin = true;
231 size_t bytes_written =
232 WriteHeadersImpl(std::move(trailer_block), kFin, std::move(ack_listener));
233 set_fin_sent(kFin);
234
235 // Trailers are the last thing to be sent on a stream, but if there is still
236 // queued data then CloseWriteSide() will cause it never to be sent.
237 if (BufferedDataBytes() == 0) {
238 CloseWriteSide();
239 }
240
241 return bytes_written;
242}
243
244QuicConsumedData QuicSpdyStream::WritevBody(const struct iovec* iov,
245 int count,
246 bool fin) {
247 if (!GetQuicReloadableFlag(quic_call_write_mem_slices)) {
248 return WritevData(iov, count, fin);
249 }
250 QUIC_RELOADABLE_FLAG_COUNT(quic_call_write_mem_slices);
251 QuicMemSliceStorage storage(
252 iov, count,
253 session()->connection()->helper()->GetStreamSendBufferAllocator(),
254 GetQuicFlag(FLAGS_quic_send_buffer_max_data_slice_size));
255 return WriteBodySlices(storage.ToSpan(), fin);
256}
257
258QuicConsumedData QuicSpdyStream::WriteBodySlices(QuicMemSliceSpan slices,
259 bool fin) {
260 if (!VersionHasDataFrameHeader(
261 spdy_session_->connection()->transport_version()) ||
262 slices.empty()) {
263 return WriteMemSlices(slices, fin);
264 }
265
266 std::unique_ptr<char[]> buffer;
267 QuicByteCount header_length =
268 encoder_.SerializeDataFrameHeader(slices.total_length(), &buffer);
269 if (!CanWriteNewDataAfterData(header_length)) {
270 return {0, false};
271 }
272
273 QuicConnection::ScopedPacketFlusher flusher(
274 spdy_session_->connection(), QuicConnection::SEND_ACK_IF_PENDING);
275
276 // Write frame header.
277 struct iovec header_iov = {static_cast<void*>(buffer.get()), header_length};
278 QuicMemSliceStorage storage(
279 &header_iov, 1,
280 spdy_session_->connection()->helper()->GetStreamSendBufferAllocator(),
281 GetQuicFlag(FLAGS_quic_send_buffer_max_data_slice_size));
282 unacked_frame_headers_offsets_.Add(
283 send_buffer().stream_offset(),
284 send_buffer().stream_offset() + header_length);
285 QUIC_DLOG(INFO) << "Stream " << id() << " is writing header of length "
286 << header_length;
287 WriteMemSlices(storage.ToSpan(), false);
288
289 // Write body.
290 QUIC_DLOG(INFO) << "Stream " << id() << " is writing body of length "
291 << slices.total_length();
292 return WriteMemSlices(slices, fin);
293}
294
295size_t QuicSpdyStream::Readv(const struct iovec* iov, size_t iov_len) {
296 DCHECK(FinishedReadingHeaders());
297 if (!VersionHasDataFrameHeader(
298 spdy_session_->connection()->transport_version())) {
299 return sequencer()->Readv(iov, iov_len);
300 }
301 return body_buffer_.ReadBody(iov, iov_len);
302}
303
304int QuicSpdyStream::GetReadableRegions(iovec* iov, size_t iov_len) const {
305 DCHECK(FinishedReadingHeaders());
306 if (!VersionHasDataFrameHeader(
307 spdy_session_->connection()->transport_version())) {
308 return sequencer()->GetReadableRegions(iov, iov_len);
309 }
310 return body_buffer_.PeekBody(iov, iov_len);
311}
312
313void QuicSpdyStream::MarkConsumed(size_t num_bytes) {
314 DCHECK(FinishedReadingHeaders());
315 if (!VersionHasDataFrameHeader(
316 spdy_session_->connection()->transport_version())) {
317 sequencer()->MarkConsumed(num_bytes);
318 return;
319 }
320 body_buffer_.MarkBodyConsumed(num_bytes);
321}
322
323bool QuicSpdyStream::IsDoneReading() const {
324 bool done_reading_headers = FinishedReadingHeaders();
325 bool done_reading_body = sequencer()->IsClosed();
326 bool done_reading_trailers = FinishedReadingTrailers();
327 return done_reading_headers && done_reading_body && done_reading_trailers;
328}
329
330bool QuicSpdyStream::HasBytesToRead() const {
331 if (!VersionHasDataFrameHeader(
332 spdy_session_->connection()->transport_version())) {
333 return sequencer()->HasBytesToRead();
334 }
335 return body_buffer_.HasBytesToRead();
336}
337
338void QuicSpdyStream::MarkTrailersConsumed() {
339 trailers_consumed_ = true;
340}
341
342uint64_t QuicSpdyStream::total_body_bytes_read() const {
343 if (VersionHasDataFrameHeader(
344 spdy_session_->connection()->transport_version())) {
345 return body_buffer_.total_body_bytes_received();
346 }
347 return sequencer()->NumBytesConsumed();
348}
349
350void QuicSpdyStream::ConsumeHeaderList() {
351 header_list_.Clear();
352 if (FinishedReadingHeaders()) {
353 sequencer()->SetUnblocked();
354 }
355}
356
357void QuicSpdyStream::OnStreamHeadersPriority(SpdyPriority priority) {
358 DCHECK_EQ(Perspective::IS_SERVER, session()->connection()->perspective());
359 SetPriority(priority);
360}
361
362void QuicSpdyStream::OnStreamHeaderList(bool fin,
363 size_t frame_len,
364 const QuicHeaderList& header_list) {
365 // The headers list avoid infinite buffering by clearing the headers list
366 // if the current headers are too large. So if the list is empty here
367 // then the headers list must have been too large, and the stream should
368 // be reset.
369 // TODO(rch): Use an explicit "headers too large" signal. An empty header list
370 // might be acceptable if it corresponds to a trailing header frame.
371 if (header_list.empty()) {
372 OnHeadersTooLarge();
373 if (IsDoneReading()) {
374 return;
375 }
376 }
377 if (!headers_decompressed_) {
378 OnInitialHeadersComplete(fin, frame_len, header_list);
379 } else {
380 OnTrailingHeadersComplete(fin, frame_len, header_list);
381 }
382}
383
384void QuicSpdyStream::OnHeadersTooLarge() {
385 Reset(QUIC_HEADERS_TOO_LARGE);
386}
387
388void QuicSpdyStream::OnInitialHeadersComplete(
389 bool fin,
390 size_t /*frame_len*/,
391 const QuicHeaderList& header_list) {
392 headers_decompressed_ = true;
393 header_list_ = header_list;
394 if (fin) {
395 OnStreamFrame(QuicStreamFrame(id(), fin, 0, QuicStringPiece()));
396 }
397 if (FinishedReadingHeaders()) {
398 sequencer()->SetUnblocked();
399 }
400}
401
402void QuicSpdyStream::OnPromiseHeaderList(
403 QuicStreamId /* promised_id */,
404 size_t /* frame_len */,
405 const QuicHeaderList& /*header_list */) {
406 // To be overridden in QuicSpdyClientStream. Not supported on
407 // server side.
408 session()->connection()->CloseConnection(
409 QUIC_INVALID_HEADERS_STREAM_DATA, "Promise headers received by server",
410 ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
411}
412
413void QuicSpdyStream::OnTrailingHeadersComplete(
414 bool fin,
415 size_t /*frame_len*/,
416 const QuicHeaderList& header_list) {
417 DCHECK(!trailers_decompressed_);
418 if (fin_received()) {
419 QUIC_DLOG(ERROR) << "Received Trailers after FIN, on stream: " << id();
420 session()->connection()->CloseConnection(
421 QUIC_INVALID_HEADERS_STREAM_DATA, "Trailers after fin",
422 ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
423 return;
424 }
425 if (!fin) {
426 QUIC_DLOG(ERROR) << "Trailers must have FIN set, on stream: " << id();
427 session()->connection()->CloseConnection(
428 QUIC_INVALID_HEADERS_STREAM_DATA, "Fin missing from trailers",
429 ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
430 return;
431 }
432
433 size_t final_byte_offset = 0;
434 if (!SpdyUtils::CopyAndValidateTrailers(header_list, &final_byte_offset,
435 &received_trailers_)) {
436 QUIC_DLOG(ERROR) << "Trailers for stream " << id() << " are malformed.";
437 session()->connection()->CloseConnection(
438 QUIC_INVALID_HEADERS_STREAM_DATA, "Trailers are malformed",
439 ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
440 return;
441 }
442 trailers_decompressed_ = true;
443 OnStreamFrame(
444 QuicStreamFrame(id(), fin, final_byte_offset, QuicStringPiece()));
445}
446
447size_t QuicSpdyStream::WriteHeadersImpl(
448 spdy::SpdyHeaderBlock header_block,
449 bool fin,
450 QuicReferenceCountedPointer<QuicAckListenerInterface> ack_listener) {
451 return spdy_session_->WriteHeadersOnHeadersStream(
452 id(), std::move(header_block), fin, priority(), std::move(ack_listener));
453}
454
455void QuicSpdyStream::OnPriorityFrame(SpdyPriority priority) {
456 DCHECK_EQ(Perspective::IS_SERVER, session()->connection()->perspective());
457 SetPriority(priority);
458}
459
460void QuicSpdyStream::OnStreamReset(const QuicRstStreamFrame& frame) {
461 if (frame.error_code != QUIC_STREAM_NO_ERROR) {
462 QuicStream::OnStreamReset(frame);
463 return;
464 }
465 QUIC_DVLOG(1) << "Received QUIC_STREAM_NO_ERROR, not discarding response";
466 set_rst_received(true);
467 MaybeIncreaseHighestReceivedOffset(frame.byte_offset);
468 set_stream_error(frame.error_code);
469 CloseWriteSide();
470}
471
472void QuicSpdyStream::OnDataAvailable() {
473 if (!VersionHasDataFrameHeader(
474 session()->connection()->transport_version())) {
475 OnBodyAvailable();
476 return;
477 }
478
479 iovec iov;
480 bool has_payload = false;
481 while (sequencer()->PrefetchNextRegion(&iov)) {
482 decoder_.ProcessInput(reinterpret_cast<const char*>(iov.iov_base),
483 iov.iov_len);
484 if (decoder_.has_payload()) {
485 has_payload = true;
486 }
487 }
488
489 if (has_payload) {
490 OnBodyAvailable();
491 return;
492 }
493
494 if (sequencer()->IsClosed()) {
495 OnBodyAvailable();
496 return;
497 }
498}
499
500void QuicSpdyStream::OnClose() {
501 QuicStream::OnClose();
502
503 if (visitor_) {
504 Visitor* visitor = visitor_;
505 // Calling Visitor::OnClose() may result the destruction of the visitor,
506 // so we need to ensure we don't call it again.
507 visitor_ = nullptr;
508 visitor->OnClose(this);
509 }
510}
511
512void QuicSpdyStream::OnCanWrite() {
513 QuicStream::OnCanWrite();
514
515 // Trailers (and hence a FIN) may have been sent ahead of queued body bytes.
516 if (!HasBufferedData() && fin_sent()) {
517 CloseWriteSide();
518 }
519}
520
521bool QuicSpdyStream::FinishedReadingHeaders() const {
522 return headers_decompressed_ && header_list_.empty();
523}
524
525bool QuicSpdyStream::ParseHeaderStatusCode(const SpdyHeaderBlock& header,
526 int* status_code) const {
527 SpdyHeaderBlock::const_iterator it = header.find(spdy::kHttp2StatusHeader);
528 if (it == header.end()) {
529 return false;
530 }
531 const QuicStringPiece status(it->second);
532 if (status.size() != 3) {
533 return false;
534 }
535 // First character must be an integer in range [1,5].
536 if (status[0] < '1' || status[0] > '5') {
537 return false;
538 }
539 // The remaining two characters must be integers.
540 if (!isdigit(status[1]) || !isdigit(status[2])) {
541 return false;
542 }
543 return QuicTextUtils::StringToInt(status, status_code);
544}
545
546bool QuicSpdyStream::FinishedReadingTrailers() const {
547 // If no further trailing headers are expected, and the decompressed trailers
548 // (if any) have been consumed, then reading of trailers is finished.
549 if (!fin_received()) {
550 return false;
551 } else if (!trailers_decompressed_) {
552 return true;
553 } else {
554 return trailers_consumed_;
555 }
556}
557
558void QuicSpdyStream::ClearSession() {
559 spdy_session_ = nullptr;
560}
561
562void QuicSpdyStream::OnDataFrameStart(Http3FrameLengths frame_lengths) {
563 body_buffer_.OnDataHeader(frame_lengths);
564}
565
566void QuicSpdyStream::OnDataFramePayload(QuicStringPiece payload) {
567 body_buffer_.OnDataPayload(payload);
568}
569
570void QuicSpdyStream::OnDataFrameEnd() {
571 DVLOG(1) << "Reaches the end of a data frame. Total bytes received are "
572 << body_buffer_.total_body_bytes_received();
573}
574
575bool QuicSpdyStream::OnStreamFrameAcked(QuicStreamOffset offset,
576 QuicByteCount data_length,
577 bool fin_acked,
578 QuicTime::Delta ack_delay_time,
579 QuicByteCount* newly_acked_length) {
580 const bool new_data_acked = QuicStream::OnStreamFrameAcked(
581 offset, data_length, fin_acked, ack_delay_time, newly_acked_length);
582
583 const QuicByteCount newly_acked_header_length =
584 GetNumFrameHeadersInInterval(offset, data_length);
585 DCHECK_LE(newly_acked_header_length, *newly_acked_length);
586 unacked_frame_headers_offsets_.Difference(offset, offset + data_length);
587 if (ack_listener_ != nullptr && new_data_acked) {
588 ack_listener_->OnPacketAcked(
589 *newly_acked_length - newly_acked_header_length, ack_delay_time);
590 }
591 return new_data_acked;
592}
593
594void QuicSpdyStream::OnStreamFrameRetransmitted(QuicStreamOffset offset,
595 QuicByteCount data_length,
596 bool fin_retransmitted) {
597 QuicStream::OnStreamFrameRetransmitted(offset, data_length,
598 fin_retransmitted);
599
600 const QuicByteCount retransmitted_header_length =
601 GetNumFrameHeadersInInterval(offset, data_length);
602 DCHECK_LE(retransmitted_header_length, data_length);
603
604 if (ack_listener_ != nullptr) {
605 ack_listener_->OnPacketRetransmitted(data_length -
606 retransmitted_header_length);
607 }
608}
609
610QuicByteCount QuicSpdyStream::GetNumFrameHeadersInInterval(
611 QuicStreamOffset offset,
612 QuicByteCount data_length) const {
613 QuicByteCount header_acked_length = 0;
614 QuicIntervalSet<QuicStreamOffset> newly_acked(offset, offset + data_length);
615 newly_acked.Intersection(unacked_frame_headers_offsets_);
616 for (const auto& interval : newly_acked) {
617 header_acked_length += interval.Length();
618 }
619 return header_acked_length;
620}
621
622#undef ENDPOINT // undef for jumbo builds
623} // namespace quic