• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2013 The Chromium Authors
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/websockets/websocket_basic_stream.h"
6 
7 #include <stddef.h>
8 #include <stdint.h>
9 
10 #include <algorithm>
11 #include <limits>
12 #include <utility>
13 
14 #include "base/functional/bind.h"
15 #include "base/logging.h"
16 #include "base/numerics/safe_conversions.h"
17 #include "build/build_config.h"
18 #include "net/base/io_buffer.h"
19 #include "net/base/net_errors.h"
20 #include "net/socket/client_socket_handle.h"
21 #include "net/websockets/websocket_basic_stream_adapters.h"
22 #include "net/websockets/websocket_errors.h"
23 #include "net/websockets/websocket_frame.h"
24 
25 namespace net {
26 
27 namespace {
28 
29 // Please refer to the comment in class header if the usage changes.
30 constexpr net::NetworkTrafficAnnotationTag kTrafficAnnotation =
31     net::DefineNetworkTrafficAnnotation("websocket_basic_stream", R"(
32       semantics {
33         sender: "WebSocket Basic Stream"
34         description:
35           "Implementation of WebSocket API from web content (a page the user "
36           "visits)."
37         trigger: "Website calls the WebSocket API."
38         data:
39           "Any data provided by web content, masked and framed in accordance "
40           "with RFC6455."
41         destination: OTHER
42         destination_other:
43           "The address that the website has chosen to communicate to."
44       }
45       policy {
46         cookies_allowed: YES
47         cookies_store: "user"
48         setting: "These requests cannot be disabled."
49         policy_exception_justification:
50           "Not implemented. WebSocket is a core web platform API."
51       }
52       comments:
53         "The browser will never add cookies to a WebSocket message. But the "
54         "handshake that was performed when the WebSocket connection was "
55         "established may have contained cookies."
56       )");
57 
58 // This uses type uint64_t to match the definition of
59 // WebSocketFrameHeader::payload_length in websocket_frame.h.
60 constexpr uint64_t kMaxControlFramePayload = 125;
61 
62 // The number of bytes to attempt to read at a time. It's used only for high
63 // throughput connections.
64 // TODO(ricea): See if there is a better number or algorithm to fulfill our
65 // requirements:
66 //  1. We would like to use minimal memory on low-bandwidth or idle connections
67 //  2. We would like to read as close to line speed as possible on
68 //     high-bandwidth connections
69 //  3. We can't afford to cause jank on the IO thread by copying large buffers
70 //     around
71 //  4. We would like to hit any sweet-spots that might exist in terms of network
72 //     packet sizes / encryption block sizes / IPC alignment issues, etc.
73 #if BUILDFLAG(IS_ANDROID)
74 constexpr size_t kLargeReadBufferSize = 32 * 1024;
75 #else
76 // |2^n - delta| is better than 2^n on Linux. See crrev.com/c/1792208.
77 constexpr size_t kLargeReadBufferSize = 131000;
78 #endif
79 
80 // The number of bytes to attempt to read at a time. It's set as an initial read
81 // buffer size and used for low throughput connections.
82 constexpr size_t kSmallReadBufferSize = 1000;
83 
84 // The threshold to decide whether to switch the read buffer size.
85 constexpr double kThresholdInBytesPerSecond = 1200 * 1000;
86 
87 // Returns the total serialized size of |frames|. This function assumes that
88 // |frames| will be serialized with mask field. This function forces the
89 // masked bit of the frames on.
CalculateSerializedSizeAndTurnOnMaskBit(std::vector<std::unique_ptr<WebSocketFrame>> * frames)90 int CalculateSerializedSizeAndTurnOnMaskBit(
91     std::vector<std::unique_ptr<WebSocketFrame>>* frames) {
92   const uint64_t kMaximumTotalSize = std::numeric_limits<int>::max();
93 
94   uint64_t total_size = 0;
95   for (const auto& frame : *frames) {
96     // Force the masked bit on.
97     frame->header.masked = true;
98     // We enforce flow control so the renderer should never be able to force us
99     // to cache anywhere near 2GB of frames.
100     uint64_t frame_size = frame->header.payload_length +
101                           GetWebSocketFrameHeaderSize(frame->header);
102     CHECK_LE(frame_size, kMaximumTotalSize - total_size)
103         << "Aborting to prevent overflow";
104     total_size += frame_size;
105   }
106   return static_cast<int>(total_size);
107 }
108 
NetLogBufferSizeParam(int buffer_size)109 base::Value::Dict NetLogBufferSizeParam(int buffer_size) {
110   base::Value::Dict dict;
111   dict.Set("read_buffer_size_in_bytes", buffer_size);
112   return dict;
113 }
114 
NetLogFrameHeaderParam(const WebSocketFrameHeader * header)115 base::Value::Dict NetLogFrameHeaderParam(const WebSocketFrameHeader* header) {
116   base::Value::Dict dict;
117   dict.Set("final", header->final);
118   dict.Set("reserved1", header->reserved1);
119   dict.Set("reserved2", header->reserved2);
120   dict.Set("reserved3", header->reserved3);
121   dict.Set("opcode", header->opcode);
122   dict.Set("masked", header->masked);
123   dict.Set("payload_length", static_cast<double>(header->payload_length));
124   return dict;
125 }
126 
127 }  // namespace
128 
129 WebSocketBasicStream::BufferSizeManager::BufferSizeManager() = default;
130 
131 WebSocketBasicStream::BufferSizeManager::~BufferSizeManager() = default;
132 
OnRead(base::TimeTicks now)133 void WebSocketBasicStream::BufferSizeManager::OnRead(base::TimeTicks now) {
134   read_start_timestamps_.push(now);
135 }
136 
OnReadComplete(base::TimeTicks now,int size)137 void WebSocketBasicStream::BufferSizeManager::OnReadComplete(
138     base::TimeTicks now,
139     int size) {
140   DCHECK_GT(size, 0);
141   // This cannot overflow because the result is at most
142   // kLargeReadBufferSize*rolling_average_window_.
143   rolling_byte_total_ += size;
144   recent_read_sizes_.push(size);
145   DCHECK_LE(read_start_timestamps_.size(), rolling_average_window_);
146   if (read_start_timestamps_.size() == rolling_average_window_) {
147     DCHECK_EQ(read_start_timestamps_.size(), recent_read_sizes_.size());
148     base::TimeDelta duration = now - read_start_timestamps_.front();
149     base::TimeDelta threshold_duration =
150         base::Seconds(rolling_byte_total_ / kThresholdInBytesPerSecond);
151     read_start_timestamps_.pop();
152     rolling_byte_total_ -= recent_read_sizes_.front();
153     recent_read_sizes_.pop();
154     if (threshold_duration < duration) {
155       buffer_size_ = BufferSize::kSmall;
156     } else {
157       buffer_size_ = BufferSize::kLarge;
158     }
159   }
160 }
161 
WebSocketBasicStream(std::unique_ptr<Adapter> connection,const scoped_refptr<GrowableIOBuffer> & http_read_buffer,const std::string & sub_protocol,const std::string & extensions,const NetLogWithSource & net_log)162 WebSocketBasicStream::WebSocketBasicStream(
163     std::unique_ptr<Adapter> connection,
164     const scoped_refptr<GrowableIOBuffer>& http_read_buffer,
165     const std::string& sub_protocol,
166     const std::string& extensions,
167     const NetLogWithSource& net_log)
168     : read_buffer_(
169           base::MakeRefCounted<IOBufferWithSize>(kSmallReadBufferSize)),
170       target_read_buffer_size_(read_buffer_->size()),
171       connection_(std::move(connection)),
172       http_read_buffer_(http_read_buffer),
173       sub_protocol_(sub_protocol),
174       extensions_(extensions),
175       net_log_(net_log),
176       generate_websocket_masking_key_(&GenerateWebSocketMaskingKey) {
177   // http_read_buffer_ should not be set if it contains no data.
178   if (http_read_buffer_.get() && http_read_buffer_->offset() == 0)
179     http_read_buffer_ = nullptr;
180   DCHECK(connection_->is_initialized());
181 }
182 
~WebSocketBasicStream()183 WebSocketBasicStream::~WebSocketBasicStream() { Close(); }
184 
ReadFrames(std::vector<std::unique_ptr<WebSocketFrame>> * frames,CompletionOnceCallback callback)185 int WebSocketBasicStream::ReadFrames(
186     std::vector<std::unique_ptr<WebSocketFrame>>* frames,
187     CompletionOnceCallback callback) {
188   read_callback_ = std::move(callback);
189   complete_control_frame_body_.clear();
190   if (http_read_buffer_ && is_http_read_buffer_decoded_) {
191     http_read_buffer_.reset();
192   }
193   return ReadEverything(frames);
194 }
195 
WriteFrames(std::vector<std::unique_ptr<WebSocketFrame>> * frames,CompletionOnceCallback callback)196 int WebSocketBasicStream::WriteFrames(
197     std::vector<std::unique_ptr<WebSocketFrame>>* frames,
198     CompletionOnceCallback callback) {
199   // This function always concatenates all frames into a single buffer.
200   // TODO(ricea): Investigate whether it would be better in some cases to
201   // perform multiple writes with smaller buffers.
202 
203   write_callback_ = std::move(callback);
204 
205   // First calculate the size of the buffer we need to allocate.
206   int total_size = CalculateSerializedSizeAndTurnOnMaskBit(frames);
207   auto combined_buffer = base::MakeRefCounted<IOBufferWithSize>(total_size);
208 
209   char* dest = combined_buffer->data();
210   int remaining_size = total_size;
211   for (const auto& frame : *frames) {
212     net_log_.AddEvent(net::NetLogEventType::WEBSOCKET_SENT_FRAME_HEADER,
213                       [&] { return NetLogFrameHeaderParam(&frame->header); });
214     WebSocketMaskingKey mask = generate_websocket_masking_key_();
215     int result =
216         WriteWebSocketFrameHeader(frame->header, &mask, dest, remaining_size);
217     DCHECK_NE(ERR_INVALID_ARGUMENT, result)
218         << "WriteWebSocketFrameHeader() says that " << remaining_size
219         << " is not enough to write the header in. This should not happen.";
220     CHECK_GE(result, 0) << "Potentially security-critical check failed";
221     dest += result;
222     remaining_size -= result;
223 
224     CHECK_LE(frame->header.payload_length,
225              static_cast<uint64_t>(remaining_size));
226     const int frame_size = static_cast<int>(frame->header.payload_length);
227     if (frame_size > 0) {
228       const char* const frame_data = frame->payload;
229       std::copy(frame_data, frame_data + frame_size, dest);
230       MaskWebSocketFramePayload(mask, 0, dest, frame_size);
231       dest += frame_size;
232       remaining_size -= frame_size;
233     }
234   }
235   DCHECK_EQ(0, remaining_size) << "Buffer size calculation was wrong; "
236                                << remaining_size << " bytes left over.";
237   auto drainable_buffer = base::MakeRefCounted<DrainableIOBuffer>(
238       std::move(combined_buffer), total_size);
239   return WriteEverything(drainable_buffer);
240 }
241 
Close()242 void WebSocketBasicStream::Close() {
243   connection_->Disconnect();
244 }
245 
GetSubProtocol() const246 std::string WebSocketBasicStream::GetSubProtocol() const {
247   return sub_protocol_;
248 }
249 
GetExtensions() const250 std::string WebSocketBasicStream::GetExtensions() const { return extensions_; }
251 
GetNetLogWithSource() const252 const NetLogWithSource& WebSocketBasicStream::GetNetLogWithSource() const {
253   return net_log_;
254 }
255 
256 /*static*/
257 std::unique_ptr<WebSocketBasicStream>
CreateWebSocketBasicStreamForTesting(std::unique_ptr<ClientSocketHandle> connection,const scoped_refptr<GrowableIOBuffer> & http_read_buffer,const std::string & sub_protocol,const std::string & extensions,const NetLogWithSource & net_log,WebSocketMaskingKeyGeneratorFunction key_generator_function)258 WebSocketBasicStream::CreateWebSocketBasicStreamForTesting(
259     std::unique_ptr<ClientSocketHandle> connection,
260     const scoped_refptr<GrowableIOBuffer>& http_read_buffer,
261     const std::string& sub_protocol,
262     const std::string& extensions,
263     const NetLogWithSource& net_log,
264     WebSocketMaskingKeyGeneratorFunction key_generator_function) {
265   auto stream = std::make_unique<WebSocketBasicStream>(
266       std::make_unique<WebSocketClientSocketHandleAdapter>(
267           std::move(connection)),
268       http_read_buffer, sub_protocol, extensions, net_log);
269   stream->generate_websocket_masking_key_ = key_generator_function;
270   return stream;
271 }
272 
ReadEverything(std::vector<std::unique_ptr<WebSocketFrame>> * frames)273 int WebSocketBasicStream::ReadEverything(
274     std::vector<std::unique_ptr<WebSocketFrame>>* frames) {
275   DCHECK(frames->empty());
276 
277   // If there is data left over after parsing the HTTP headers, attempt to parse
278   // it as WebSocket frames.
279   if (http_read_buffer_.get() && !is_http_read_buffer_decoded_) {
280     DCHECK_GE(http_read_buffer_->offset(), 0);
281     is_http_read_buffer_decoded_ = true;
282     std::vector<std::unique_ptr<WebSocketFrameChunk>> frame_chunks;
283     if (!parser_.Decode(http_read_buffer_->StartOfBuffer(),
284                         http_read_buffer_->offset(), &frame_chunks))
285       return WebSocketErrorToNetError(parser_.websocket_error());
286     if (!frame_chunks.empty()) {
287       int result = ConvertChunksToFrames(&frame_chunks, frames);
288       if (result != ERR_IO_PENDING)
289         return result;
290     }
291   }
292 
293   // Run until socket stops giving us data or we get some frames.
294   while (true) {
295     if (buffer_size_manager_.buffer_size() != buffer_size_) {
296       read_buffer_ = base::MakeRefCounted<IOBufferWithSize>(
297           buffer_size_manager_.buffer_size() == BufferSize::kSmall
298               ? kSmallReadBufferSize
299               : kLargeReadBufferSize);
300       buffer_size_ = buffer_size_manager_.buffer_size();
301       net_log_.AddEvent(
302           net::NetLogEventType::WEBSOCKET_READ_BUFFER_SIZE_CHANGED,
303           [&] { return NetLogBufferSizeParam(read_buffer_->size()); });
304     }
305     buffer_size_manager_.OnRead(base::TimeTicks::Now());
306 
307     // base::Unretained(this) here is safe because net::Socket guarantees not to
308     // call any callbacks after Disconnect(), which we call from the destructor.
309     // The caller of ReadEverything() is required to keep |frames| valid.
310     int result = connection_->Read(
311         read_buffer_.get(), read_buffer_->size(),
312         base::BindOnce(&WebSocketBasicStream::OnReadComplete,
313                        base::Unretained(this), base::Unretained(frames)));
314     if (result == ERR_IO_PENDING)
315       return result;
316     result = HandleReadResult(result, frames);
317     if (result != ERR_IO_PENDING)
318       return result;
319     DCHECK(frames->empty());
320   }
321 }
322 
OnReadComplete(std::vector<std::unique_ptr<WebSocketFrame>> * frames,int result)323 void WebSocketBasicStream::OnReadComplete(
324     std::vector<std::unique_ptr<WebSocketFrame>>* frames,
325     int result) {
326   result = HandleReadResult(result, frames);
327   if (result == ERR_IO_PENDING)
328     result = ReadEverything(frames);
329   if (result != ERR_IO_PENDING)
330     std::move(read_callback_).Run(result);
331 }
332 
WriteEverything(const scoped_refptr<DrainableIOBuffer> & buffer)333 int WebSocketBasicStream::WriteEverything(
334     const scoped_refptr<DrainableIOBuffer>& buffer) {
335   while (buffer->BytesRemaining() > 0) {
336     // The use of base::Unretained() here is safe because on destruction we
337     // disconnect the socket, preventing any further callbacks.
338     int result = connection_->Write(
339         buffer.get(), buffer->BytesRemaining(),
340         base::BindOnce(&WebSocketBasicStream::OnWriteComplete,
341                        base::Unretained(this), buffer),
342         kTrafficAnnotation);
343     if (result > 0) {
344       buffer->DidConsume(result);
345     } else {
346       return result;
347     }
348   }
349   return OK;
350 }
351 
OnWriteComplete(const scoped_refptr<DrainableIOBuffer> & buffer,int result)352 void WebSocketBasicStream::OnWriteComplete(
353     const scoped_refptr<DrainableIOBuffer>& buffer,
354     int result) {
355   if (result < 0) {
356     DCHECK_NE(ERR_IO_PENDING, result);
357     std::move(write_callback_).Run(result);
358     return;
359   }
360 
361   DCHECK_NE(0, result);
362 
363   buffer->DidConsume(result);
364   result = WriteEverything(buffer);
365   if (result != ERR_IO_PENDING)
366     std::move(write_callback_).Run(result);
367 }
368 
HandleReadResult(int result,std::vector<std::unique_ptr<WebSocketFrame>> * frames)369 int WebSocketBasicStream::HandleReadResult(
370     int result,
371     std::vector<std::unique_ptr<WebSocketFrame>>* frames) {
372   DCHECK_NE(ERR_IO_PENDING, result);
373   DCHECK(frames->empty());
374   if (result < 0)
375     return result;
376   if (result == 0)
377     return ERR_CONNECTION_CLOSED;
378 
379   buffer_size_manager_.OnReadComplete(base::TimeTicks::Now(), result);
380 
381   std::vector<std::unique_ptr<WebSocketFrameChunk>> frame_chunks;
382   if (!parser_.Decode(read_buffer_->data(), result, &frame_chunks))
383     return WebSocketErrorToNetError(parser_.websocket_error());
384   if (frame_chunks.empty())
385     return ERR_IO_PENDING;
386   return ConvertChunksToFrames(&frame_chunks, frames);
387 }
388 
ConvertChunksToFrames(std::vector<std::unique_ptr<WebSocketFrameChunk>> * frame_chunks,std::vector<std::unique_ptr<WebSocketFrame>> * frames)389 int WebSocketBasicStream::ConvertChunksToFrames(
390     std::vector<std::unique_ptr<WebSocketFrameChunk>>* frame_chunks,
391     std::vector<std::unique_ptr<WebSocketFrame>>* frames) {
392   for (size_t i = 0; i < frame_chunks->size(); ++i) {
393     auto& chunk = (*frame_chunks)[i];
394     DCHECK(chunk == frame_chunks->back() || chunk->final_chunk)
395         << "Only last chunk can have |final_chunk| set to be false.";
396     if (const auto& header = chunk->header) {
397       net_log_.AddEvent(net::NetLogEventType::WEBSOCKET_RECV_FRAME_HEADER,
398                         [&] { return NetLogFrameHeaderParam(header.get()); });
399     }
400     std::unique_ptr<WebSocketFrame> frame;
401     int result = ConvertChunkToFrame(std::move(chunk), &frame);
402     if (result != OK)
403       return result;
404     if (frame)
405       frames->push_back(std::move(frame));
406   }
407   frame_chunks->clear();
408   if (frames->empty())
409     return ERR_IO_PENDING;
410   return OK;
411 }
412 
ConvertChunkToFrame(std::unique_ptr<WebSocketFrameChunk> chunk,std::unique_ptr<WebSocketFrame> * frame)413 int WebSocketBasicStream::ConvertChunkToFrame(
414     std::unique_ptr<WebSocketFrameChunk> chunk,
415     std::unique_ptr<WebSocketFrame>* frame) {
416   DCHECK(frame->get() == nullptr);
417   bool is_first_chunk = false;
418   if (chunk->header) {
419     DCHECK(current_frame_header_ == nullptr)
420         << "Received the header for a new frame without notification that "
421         << "the previous frame was complete (bug in WebSocketFrameParser?)";
422     is_first_chunk = true;
423     current_frame_header_.swap(chunk->header);
424   }
425   DCHECK(current_frame_header_) << "Unexpected header-less chunk received "
426                                 << "(final_chunk = " << chunk->final_chunk
427                                 << ", payload size = " << chunk->payload.size()
428                                 << ") (bug in WebSocketFrameParser?)";
429   const bool is_final_chunk = chunk->final_chunk;
430   const WebSocketFrameHeader::OpCode opcode = current_frame_header_->opcode;
431   if (WebSocketFrameHeader::IsKnownControlOpCode(opcode)) {
432     bool protocol_error = false;
433     if (!current_frame_header_->final) {
434       DVLOG(1) << "WebSocket protocol error. Control frame, opcode=" << opcode
435                << " received with FIN bit unset.";
436       protocol_error = true;
437     }
438     if (current_frame_header_->payload_length > kMaxControlFramePayload) {
439       DVLOG(1) << "WebSocket protocol error. Control frame, opcode=" << opcode
440                << ", payload_length=" << current_frame_header_->payload_length
441                << " exceeds maximum payload length for a control message.";
442       protocol_error = true;
443     }
444     if (protocol_error) {
445       current_frame_header_.reset();
446       return ERR_WS_PROTOCOL_ERROR;
447     }
448 
449     if (!is_final_chunk) {
450       DVLOG(2) << "Encountered a split control frame, opcode " << opcode;
451       AddToIncompleteControlFrameBody(chunk->payload);
452       return OK;
453     }
454 
455     if (!incomplete_control_frame_body_.empty()) {
456       DVLOG(2) << "Rejoining a split control frame, opcode " << opcode;
457       AddToIncompleteControlFrameBody(chunk->payload);
458       DCHECK(is_final_chunk);
459       DCHECK(complete_control_frame_body_.empty());
460       complete_control_frame_body_ = std::move(incomplete_control_frame_body_);
461       *frame = CreateFrame(is_final_chunk, complete_control_frame_body_);
462       return OK;
463     }
464   }
465 
466   // Apply basic sanity checks to the |payload_length| field from the frame
467   // header. A check for exact equality can only be used when the whole frame
468   // arrives in one chunk.
469   DCHECK_GE(current_frame_header_->payload_length,
470             base::checked_cast<uint64_t>(chunk->payload.size()));
471   DCHECK(!is_first_chunk || !is_final_chunk ||
472          current_frame_header_->payload_length ==
473              base::checked_cast<uint64_t>(chunk->payload.size()));
474 
475   // Convert the chunk to a complete frame.
476   *frame = CreateFrame(is_final_chunk, chunk->payload);
477   return OK;
478 }
479 
CreateFrame(bool is_final_chunk,base::span<const char> data)480 std::unique_ptr<WebSocketFrame> WebSocketBasicStream::CreateFrame(
481     bool is_final_chunk,
482     base::span<const char> data) {
483   std::unique_ptr<WebSocketFrame> result_frame;
484   const bool is_final_chunk_in_message =
485       is_final_chunk && current_frame_header_->final;
486   const WebSocketFrameHeader::OpCode opcode = current_frame_header_->opcode;
487   // Empty frames convey no useful information unless they are the first frame
488   // (containing the type and flags) or have the "final" bit set.
489   if (is_final_chunk_in_message || data.size() > 0 ||
490       current_frame_header_->opcode !=
491           WebSocketFrameHeader::kOpCodeContinuation) {
492     result_frame = std::make_unique<WebSocketFrame>(opcode);
493     result_frame->header.CopyFrom(*current_frame_header_);
494     result_frame->header.final = is_final_chunk_in_message;
495     result_frame->header.payload_length = data.size();
496     result_frame->payload = data.data();
497     // Ensure that opcodes Text and Binary are only used for the first frame in
498     // the message. Also clear the reserved bits.
499     // TODO(ricea): If a future extension requires the reserved bits to be
500     // retained on continuation frames, make this behaviour conditional on a
501     // flag set at construction time.
502     if (!is_final_chunk && WebSocketFrameHeader::IsKnownDataOpCode(opcode)) {
503       current_frame_header_->opcode = WebSocketFrameHeader::kOpCodeContinuation;
504       current_frame_header_->reserved1 = false;
505       current_frame_header_->reserved2 = false;
506       current_frame_header_->reserved3 = false;
507     }
508   }
509   // Make sure that a frame header is not applied to any chunks that do not
510   // belong to it.
511   if (is_final_chunk)
512     current_frame_header_.reset();
513   return result_frame;
514 }
515 
AddToIncompleteControlFrameBody(base::span<const char> data)516 void WebSocketBasicStream::AddToIncompleteControlFrameBody(
517     base::span<const char> data) {
518   if (data.empty()) {
519     return;
520   }
521   incomplete_control_frame_body_.insert(incomplete_control_frame_body_.end(),
522                                         data.begin(), data.end());
523   // This method checks for oversize control frames above, so as long as
524   // the frame parser is working correctly, this won't overflow. If a bug
525   // does cause it to overflow, it will CHECK() in
526   // AddToIncompleteControlFrameBody() without writing outside the buffer.
527   CHECK_LE(incomplete_control_frame_body_.size(), kMaxControlFramePayload)
528       << "Control frame body larger than frame header indicates; frame parser "
529          "bug?";
530 }
531 
532 }  // namespace net
533