Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(165)

Side by Side Diff: net/websockets/websocket_channel.cc

Issue 12764006: WebSocketChannel implementation (Closed) Base URL: http://git.chromium.org/chromium/src.git@web_socket_dispatcher
Patch Set: Comment clarifications and formatting tweak Created 7 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // 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/websockets/websocket_channel.h"
6
7 #include <algorithm>
8
9 #include "base/basictypes.h" // for size_t
10 #include "base/bind.h"
11 #include "base/safe_numerics.h"
12 #include "base/strings/string_util.h"
13 #include "net/base/big_endian.h"
14 #include "net/base/io_buffer.h"
15 #include "net/base/net_log.h"
16 #include "net/websockets/websocket_errors.h"
17 #include "net/websockets/websocket_event_interface.h"
18 #include "net/websockets/websocket_frame.h"
19 #include "net/websockets/websocket_mux.h"
20 #include "net/websockets/websocket_stream.h"
21
22 namespace net {
23
24 namespace {
25
26 const int kDefaultSendQuotaLowWaterMark = 1 << 16;
27 const int kDefaultSendQuotaHighWaterMark = 1 << 17;
28 const size_t kWebSocketCloseCodeLength = 2;
29
30 // Concatenate the data from two IOBufferWithSize objects into a single one.
31 IOBufferWithSize* ConcatenateIOBuffers(
32 const scoped_refptr<IOBufferWithSize>& part1,
33 const scoped_refptr<IOBufferWithSize>& part2) {
34 int newsize = part1->size() + part2->size();
35 IOBufferWithSize* newbuffer = new IOBufferWithSize(newsize);
36 std::copy(part1->data(), part1->data() + part1->size(), newbuffer->data());
37 std::copy(part2->data(),
38 part2->data() + part2->size(),
39 newbuffer->data() + part1->size());
40 return newbuffer;
41 }
42
43 } // namespace
44
45 struct WebSocketChannel::SendBuffer {
46 SendBuffer() : total_bytes(0) {}
47 ScopedVector<WebSocketFrameChunk> frames;
szym 2013/07/09 15:59:51 nit: add blank line between methods and fields.
Adam Rice 2013/07/11 13:28:17 Done.
48 size_t total_bytes;
szym 2013/07/09 15:59:51 To ensure consistency add an inline method which a
Adam Rice 2013/07/11 13:28:17 I changed it into a class. This does make the call
49 };
50
51 // Implementation of WebSocketStream::ConnectDelegate that simply forwards the
52 // calls on to the WebSocketChannel that created it.
53 class WebSocketChannel::ConnectDelegate
54 : public WebSocketStream::ConnectDelegate {
55 public:
56 explicit ConnectDelegate(WebSocketChannel* creator) : creator_(creator) {}
57
58 virtual void OnSuccess(scoped_ptr<WebSocketStream> stream) OVERRIDE {
59 creator_->OnConnectSuccess(stream.Pass());
60 }
61
62 virtual void OnFailure(uint16 websocket_error) OVERRIDE {
63 creator_->OnConnectFailure(websocket_error);
64 }
65
66 private:
67 // A pointer to the WebSocketChannel that created us. We do not need to worry
68 // about this pointer being stale, because deleting WebSocketChannel cancels
69 // the connect process, deleting this object and preventing its callbacks from
70 // being called.
71 WebSocketChannel* const creator_;
72
73 DISALLOW_COPY_AND_ASSIGN(ConnectDelegate);
74 };
75
76 WebSocketChannel::WebSocketChannel(
77 const GURL& socket_url,
78 scoped_ptr<WebSocketEventInterface> event_interface)
79 : socket_url_(socket_url),
80 event_interface_(event_interface.Pass()),
81 send_quota_low_water_mark_(kDefaultSendQuotaLowWaterMark),
82 send_quota_high_water_mark_(kDefaultSendQuotaHighWaterMark),
83 current_send_quota_(0),
84 closing_code_(0),
85 state_(FRESHLY_CONSTRUCTED) {}
86
87 WebSocketChannel::~WebSocketChannel() {
88 // The stream may hold a pointer to read_frame_chunks_, and so it needs to be
89 // destroyed first.
90 stream_.reset();
91 }
92
93 void WebSocketChannel::SendAddChannelRequest(
94 const std::vector<std::string>& requested_subprotocols,
95 const GURL& origin,
96 URLRequestContext* url_request_context) {
97 // Delegate to the tested version.
98 SendAddChannelRequestWithFactory(
99 requested_subprotocols,
100 origin,
101 url_request_context,
102 base::Bind(&WebSocketStream::CreateAndConnectStream));
103 }
104
105 void WebSocketChannel::SendAddChannelRequestWithFactory(
106 const std::vector<std::string>& requested_subprotocols,
107 const GURL& origin,
108 URLRequestContext* url_request_context,
109 base::Callback<scoped_ptr<WebSocketStreamRequest>(
110 const GURL&,
111 const std::vector<std::string>&,
112 const GURL&,
113 URLRequestContext*,
114 const BoundNetLog&,
115 scoped_ptr<WebSocketStream::ConnectDelegate>)> factory) {
116 DCHECK_EQ(FRESHLY_CONSTRUCTED, state_);
117 scoped_ptr<WebSocketStream::ConnectDelegate> connect_delegate(
118 new WebSocketChannel::ConnectDelegate(this));
szym 2013/07/09 15:59:51 Remove WebSocketChannel::
Adam Rice 2013/07/11 13:28:17 Done.
119 stream_request_ = factory.Run(socket_url_,
120 requested_subprotocols,
121 origin,
122 url_request_context,
123 BoundNetLog(),
124 connect_delegate.Pass());
125 state_ = CONNECTING;
126 }
127
128 void WebSocketChannel::OnConnectSuccess(scoped_ptr<WebSocketStream> stream) {
129 DCHECK(stream);
130 DCHECK_EQ(CONNECTING, state_);
131 stream_ = stream.Pass();
132 state_ = CONNECTED;
133 event_interface_->OnAddChannelResponse(false, stream_->GetSubProtocol());
134
135 // TODO(ricea): Get flow control information from the WebSocketStream once we
136 // have a multiplexing WebSocketStream.
137 current_send_quota_ = send_quota_high_water_mark_;
138 event_interface_->OnFlowControl(send_quota_high_water_mark_);
139
140 // We don't need this any more.
141 stream_request_.reset();
142 ReadFrames();
143 }
144
145 void WebSocketChannel::OnConnectFailure(uint16 websocket_error) {
146 DCHECK_EQ(CONNECTING, state_);
147 state_ = CLOSED;
148 stream_request_.reset();
149 event_interface_->OnAddChannelResponse(true, "");
150 }
151
152 void WebSocketChannel::SendFrame(bool fin,
szym 2013/07/09 15:59:51 Keep the order of methods in implementation the sa
Adam Rice 2013/07/11 13:28:17 Done.
153 WebSocketFrameHeader::OpCode op_code,
154 const std::vector<char>& data) {
155 if (data.size() > INT_MAX) {
156 NOTREACHED() << "Frame size sanity check failed";
157 return;
158 }
159 if (stream_ == NULL) {
160 LOG(DFATAL) << "Got SendFrame without a connection established; "
161 << "misbehaving renderer? fin=" << fin << " op_code=" << op_code
162 << " data.size()=" << data.size();
163 return;
164 }
165 if (state_ == SEND_CLOSED || state_ == CLOSED) {
166 VLOG(1) << "SendFrame called in state " << state_
167 << ". This may be a bug, or a harmless race.";
168 return;
169 }
170 if (state_ != CONNECTED) {
171 NOTREACHED() << "SendFrame() called in state " << state_;
172 return;
173 }
174 if (data.size() > base::checked_numeric_cast<size_t>(current_send_quota_)) {
175 FailChannel(SEND_GOING_AWAY,
176 kWebSocketMuxErrorSendQuotaViolation,
177 "Send quota exceeded");
178 return;
179 }
180 if (!WebSocketFrameHeader::IsKnownDataOpCode(op_code)) {
181 LOG(DFATAL) << "Got SendFrame with bogus op_code " << op_code
182 << "; misbehaving renderer? fin=" << fin
183 << " data.size()=" << data.size();
184 return;
185 }
186 current_send_quota_ -= data.size();
187 // TODO(ricea): If current_send_quota_ has dropped below
188 // send_quota_low_water_mark_, we may want to consider increasing the "low
189 // water mark" and "high water mark", but only if we think we are not
190 // saturating the link to the WebSocket server.
191 // TODO(ricea): For kOpCodeText, do UTF-8 validation?
192 scoped_refptr<IOBufferWithSize> buffer(new IOBufferWithSize(data.size()));
193 std::copy(data.begin(), data.end(), buffer->data());
194 SendIOBufferWithSize(fin, op_code, buffer);
195 }
196
197 void WebSocketChannel::SendIOBufferWithSize(
198 bool fin,
199 WebSocketFrameHeader::OpCode op_code,
200 const scoped_refptr<IOBufferWithSize>& buffer) {
201 DCHECK(state_ == CONNECTED || state_ == RECV_CLOSED);
202 DCHECK(stream_);
203 scoped_ptr<WebSocketFrameHeader> header(new WebSocketFrameHeader(op_code));
204 header->final = fin;
205 header->masked = true;
206 header->payload_length = buffer->size();
207 scoped_ptr<WebSocketFrameChunk> chunk(new WebSocketFrameChunk());
208 chunk->header = header.Pass();
209 chunk->final_chunk = true;
210 chunk->data = buffer;
211 if (data_being_sent_) {
212 // Either the link to the WebSocket server is saturated, or we are simply
213 // processing a batch of messages.
214 // TODO(ricea): We need to keep some statistics to work out which situation
215 // we are in and adjust quota appropriately.
216 if (!data_to_send_next_) {
217 data_to_send_next_.reset(new SendBuffer);
218 }
szym 2013/07/09 15:59:51 nit: {} unnecessary when both condition and body a
Adam Rice 2013/07/11 13:28:17 Done.
219 data_to_send_next_->frames.push_back(chunk.release());
220 data_to_send_next_->total_bytes += buffer->size();
221 } else {
222 data_being_sent_.reset(new SendBuffer);
223 data_being_sent_->frames.push_back(chunk.release());
224 data_being_sent_->total_bytes += buffer->size();
225 WriteFrames();
226 }
227 }
228
229 void WebSocketChannel::WriteFrames() {
230 int result = OK;
231 do {
232 // This use of base::Unretained is safe because we own the WebSocketStream
233 // and destroying it cancels all callbacks.
234 result = stream_->WriteFrames(
235 &(data_being_sent_->frames),
236 base::Bind(
237 &WebSocketChannel::OnWriteDone, base::Unretained(this), false));
238 if (result != ERR_IO_PENDING) {
239 OnWriteDone(true, result);
240 }
241 } while (result == OK && data_being_sent_);
242 }
243
244 void WebSocketChannel::OnWriteDone(bool synchronous, int result) {
245 DCHECK(state_ != FRESHLY_CONSTRUCTED && state_ != CONNECTING);
szym 2013/07/09 15:59:51 ditto: split into two DCHECK_NEs
Adam Rice 2013/07/11 13:28:17 Done.
246 DCHECK_NE(ERR_IO_PENDING, result);
247 DCHECK(data_being_sent_);
248 switch (result) {
249 case OK:
250 if (data_to_send_next_) {
251 data_being_sent_ = data_to_send_next_.Pass();
252 if (!synchronous) {
253 WriteFrames();
254 }
255 } else {
256 data_being_sent_.reset();
257 if (current_send_quota_ < send_quota_low_water_mark_) {
258 // TODO(ricea): Increase low_water_mark and high_water_mark if
259 // throughput is high, reduce them if throughput is low. Low water
260 // mark needs to be >= the bandwidth delay product *of the IPC
261 // channel*. Because factors like context-switch time, thread wake-up
262 // time, and bus speed come into play it is complex and probably needs
263 // to be determined empirically.
264 DCHECK_LE(send_quota_low_water_mark_, send_quota_high_water_mark_);
265 // TODO(ricea): Truncate quota by the quota specified by the remote
266 // server, if the protocol in use supports quota.
267 int fresh_quota = send_quota_high_water_mark_ - current_send_quota_;
268 current_send_quota_ += fresh_quota;
269 event_interface_->OnFlowControl(fresh_quota);
270 }
271 }
272 return;
273
274 // If a recoverable error condition existed, it would go here.
275
276 default:
277 DCHECK_LT(result, 0)
278 << "WriteFrames() should only return OK or ERR_ codes";
279 stream_->Close();
280 state_ = CLOSED;
281 event_interface_->OnDropChannel(kWebSocketErrorAbnormalClosure,
282 "Abnormal Closure");
283 return;
284 }
285 }
286
287 void WebSocketChannel::ReadFrames() {
288 int result = OK;
289 do {
290 // This use of base::Unretained is safe because we own the WebSocketStream,
291 // and any pending reads will be cancelled when it is destroyed.
292 result = stream_->ReadFrames(
293 &read_frame_chunks_,
294 base::Bind(
295 &WebSocketChannel::OnReadDone, base::Unretained(this), false));
296 if (result != ERR_IO_PENDING) {
297 OnReadDone(true, result);
298 }
299 } while (result == OK);
300 }
301
302 void WebSocketChannel::OnReadDone(bool synchronous, int result) {
303 DCHECK(state_ != FRESHLY_CONSTRUCTED && state_ != CONNECTING);
szym 2013/07/09 15:59:51 ditto here
Adam Rice 2013/07/11 13:28:17 Done.
304 DCHECK_NE(ERR_IO_PENDING, result);
305 switch (result) {
306 case OK:
307 // ReadFrames() must use ERR_CONNECTION_CLOSED for a closed connection
308 // with no data read, not an empty response.
309 DCHECK(!read_frame_chunks_.empty())
310 << "ReadFrames() returned OK, but nothing was read.";
311 for (size_t i = 0; i < read_frame_chunks_.size(); ++i) {
312 scoped_ptr<WebSocketFrameChunk> chunk(read_frame_chunks_[i]);
313 read_frame_chunks_[i] = NULL;
314 ProcessFrameChunk(chunk.Pass());
szym 2013/07/09 15:59:51 BUG: This could be use after free. ProcessFrameCh
Adam Rice 2013/07/11 13:28:17 Thank you for noticing. I was pretty sure I had go
315 }
316 read_frame_chunks_.clear();
317 // We need to always keep a call to ReadFrames pending.
318 if (!synchronous) {
319 ReadFrames();
320 }
321 return;
322
323 default: {
324 DCHECK_LT(result, 0)
325 << "ReadFrames() should only return OK or ERR_ codes";
326 stream_->Close();
327 state_ = CLOSED;
328 uint16 code = kWebSocketErrorAbnormalClosure;
329 std::string reason = "Abnormal Closure";
330 if (closing_code_ != 0) {
331 code = closing_code_;
332 reason = closing_reason_;
333 }
334 event_interface_->OnDropChannel(code, reason);
335 return;
336 }
337 }
338 }
339
340 void WebSocketChannel::ProcessFrameChunk(
341 scoped_ptr<WebSocketFrameChunk> chunk) {
342 bool is_first_chunk = false;
343 if (chunk->header) {
344 DCHECK(current_frame_header_ == NULL)
345 << "Received the header for a new frame without notification that "
346 << "the previous frame was complete.";
347 is_first_chunk = true;
348 current_frame_header_.swap(chunk->header);
349 if (current_frame_header_->masked) {
350 // RFC6455 Section 5.1 "A client MUST close a connection if it detects a
351 // masked frame."
352 FailChannel(SEND_REAL_ERROR,
353 kWebSocketErrorProtocolError,
354 "Masked frame from server");
355 return;
356 }
357 }
358 if (!current_frame_header_) {
359 // If we rejected the previous chunk as invalid, then we will have reset
360 // current_frame_header_ to avoid using it. More chunks of the invalid frame
361 // may still arrive, so this is not necessarily a bug on our side. However,
362 // if this happens when state_ is CONNECTED, it is definitely a bug.
363 DCHECK(state_ != CONNECTED) << "Unexpected header-less frame received "
364 << "(final_chunk = " << chunk->final_chunk
365 << ", data size = " << chunk->data->size()
366 << ")";
367 return;
368 }
369 scoped_refptr<IOBufferWithSize> data_buffer;
370 data_buffer.swap(chunk->data);
371 const bool is_final_chunk = chunk->final_chunk;
372 chunk.reset();
373 WebSocketFrameHeader::OpCode opcode = current_frame_header_->opcode;
374 if (WebSocketFrameHeader::IsKnownControlOpCode(opcode)) {
375 if (is_final_chunk) {
szym 2013/07/09 15:59:51 Reorganize this so that the early exit case goes f
Adam Rice 2013/07/11 13:28:17 Done.
376 if (incomplete_control_frame_body_) {
377 VLOG(2) << "Rejoining a split control frame, opcode " << opcode;
378 data_buffer =
379 ConcatenateIOBuffers(incomplete_control_frame_body_, data_buffer);
380 incomplete_control_frame_body_ = NULL;
381 }
382 } else {
383 // TODO(ricea): Enforce a maximum size of 125 bytes on the control frames
384 // we accept.
385 VLOG(2) << "Encountered a split control frame, opcode " << opcode;
386 if (incomplete_control_frame_body_) {
387 // The really horrid case. We need to create a new IOBufferWithSize
388 // combining the new one and the old one. This should virtually never
389 // happen.
390 // TODO(ricea): This algorithm is O(N^2). Use a fixed 127-byte buffer
391 // instead.
392 VLOG(3) << "Hit the really horrid case";
393 incomplete_control_frame_body_ =
394 ConcatenateIOBuffers(incomplete_control_frame_body_, data_buffer);
395 } else {
396 // The merely horrid case. Store the IOBufferWithSize to use when the
397 // rest of the control frame arrives.
398 incomplete_control_frame_body_.swap(data_buffer);
399 }
400 return;
401 }
402 }
403
404 HandleFrame(opcode, is_first_chunk, is_final_chunk, data_buffer);
405
406 if (is_final_chunk) {
407 // Make sure we do not apply this frame header to any future chunks.
408 current_frame_header_.reset();
szym 2013/07/09 15:59:51 BUG: This could be use after free. HandleFrame...
Adam Rice 2013/07/11 13:28:17 Ack. Fixed by disallowing delete from OnDropChanne
409 }
410 }
411
412 void WebSocketChannel::HandleFrame(
413 const WebSocketFrameHeader::OpCode opcode,
414 const bool is_first_chunk,
415 const bool is_final_chunk,
416 const scoped_refptr<IOBufferWithSize>& data_buffer) {
417 DCHECK_NE(RECV_CLOSED, state_)
418 << "HandleFrame() does not support being called re-entrantly from within "
419 "SendClose()";
420 if (state_ == CLOSED) {
421 std::string frame_name;
422 switch (opcode) {
423 case WebSocketFrameHeader::kOpCodeText: // fall-thru
424 case WebSocketFrameHeader::kOpCodeBinary: // fall-thru
425 case WebSocketFrameHeader::kOpCodeContinuation:
426 frame_name = "Data frame";
427 break;
428
429 case WebSocketFrameHeader::kOpCodePing:
430 frame_name = "Ping";
431 break;
432
433 case WebSocketFrameHeader::kOpCodePong:
434 frame_name = "Pong";
435 break;
436
437 case WebSocketFrameHeader::kOpCodeClose:
438 frame_name = "Close";
439 break;
440
441 default:
442 frame_name = "Unknown frame type";
443 break;
444 }
445 // SEND_REAL_ERROR makes no difference here, as we won't send another Close
446 // frame.
447 FailChannel(SEND_REAL_ERROR,
448 kWebSocketErrorProtocolError,
449 frame_name + " received after close");
450 return;
451 }
452 switch (opcode) {
453 case WebSocketFrameHeader::kOpCodeText: // fall-thru
454 case WebSocketFrameHeader::kOpCodeBinary: // fall-thru
455 case WebSocketFrameHeader::kOpCodeContinuation:
456 if (state_ == CONNECTED) {
457 const bool final = is_final_chunk && current_frame_header_->final;
458 // TODO(ricea): Can this copy be eliminated?
459 const char* const data_begin = data_buffer->data();
460 const char* const data_end = data_begin + data_buffer->size();
461 const std::vector<char> data(data_begin, data_end);
462 // TODO(ricea): Handle the (improbable) case when ReadFrames returns far
463 // more data at once than we want to send in a single IPC (in which case
464 // we need to buffer the data and return to the event loop with a
465 // callback to send the rest in 32K chunks).
466
467 // Send the received frame to the renderer process.
468 event_interface_->OnDataFrame(
469 final,
470 is_first_chunk ? opcode : WebSocketFrameHeader::kOpCodeContinuation,
471 data);
472 } else {
473 VLOG(3) << "Ignored data packet received in state " << state_;
474 }
475 return;
476
477 case WebSocketFrameHeader::kOpCodePing:
478 VLOG(1) << "Got Ping of size " << data_buffer->size();
479 if (state_ == CONNECTED) {
480 SendIOBufferWithSize(
481 true, WebSocketFrameHeader::kOpCodePong, data_buffer);
482 } else {
483 VLOG(3) << "Ignored ping in state " << state_;
484 }
485 return;
486
487 case WebSocketFrameHeader::kOpCodePong:
488 VLOG(1) << "Got Pong of size " << data_buffer->size();
489 // We do not need to do anything with pong messages.
490 return;
491
492 case WebSocketFrameHeader::kOpCodeClose: {
493 uint16 code = kWebSocketNormalClosure;
494 std::string reason;
495 ParseClose(data_buffer, &code, &reason);
496 // TODO(ricea): Find a way to safely log the message from the close
497 // message (escape control codes and so on).
498 VLOG(1) << "Got Close with code " << code;
499 switch (state_) {
500 case CONNECTED:
501 state_ = RECV_CLOSED;
502 SendClose(code, reason); // Sets state_ to CLOSED
503 event_interface_->OnClosingHandshake();
504 closing_code_ = code;
505 closing_reason_ = reason;
506 break;
507
508 case SEND_CLOSED:
509 state_ = CLOSED;
510 // From RFC6455 section 7.1.5: "Each endpoint
511 // will see the status code sent by the other end as _The WebSocket
512 // Connection Close Code_."
513 closing_code_ = code;
514 closing_reason_ = reason;
515 break;
516
517 default:
518 LOG(DFATAL) << "Got Close in unexpected state " << state_;
519 break;
520 }
521 return;
522 }
523
524 default:
525 FailChannel(
526 SEND_REAL_ERROR, kWebSocketErrorProtocolError, "Unknown opcode");
527 return;
528 }
529 }
530
531 void WebSocketChannel::SendFlowControl(int64 quota) {
532 DCHECK_EQ(CONNECTED, state_);
533 // TODO(ricea): Add interface to WebSocketStream and implement.
534 // stream_->SendFlowControl(quota);
535 }
536
537 void WebSocketChannel::StartClosingHandshake(uint16 code,
538 const std::string& reason) {
539 if (state_ == SEND_CLOSED || state_ == CLOSED) {
540 VLOG(1) << "StartClosingHandshake called in state " << state_
541 << ". This may be a bug, or a harmless race.";
542 return;
543 }
544 if (state_ != CONNECTED) {
545 NOTREACHED() << "StartClosingHandshake() called in state " << state_;
546 return;
547 }
548 // TODO(ricea): Validate |code|? Check that |reason| is valid UTF-8?
549 // TODO(ricea): There should be a timeout for the closing handshake.
550 SendClose(code, reason); // Sets state_ to SEND_CLOSED
551 }
552
553 void WebSocketChannel::FailChannel(ExposeError expose,
554 uint16 code,
555 const std::string& reason) {
556 DCHECK(state_ != FRESHLY_CONSTRUCTED && state_ != CONNECTING);
szym 2013/07/09 15:59:51 I suggest splitting && DCHECK into two so that fai
Adam Rice 2013/07/11 13:28:17 Done.
557 // TODO(ricea): Logging.
558 State old_state = state_;
559 if (state_ == CONNECTED) {
560 uint16 send_code = kWebSocketErrorGoingAway;
561 std::string send_reason = "Internal Error";
562 if (expose == SEND_REAL_ERROR) {
563 send_code = code;
564 send_reason = reason;
565 }
566 SendClose(send_code, send_reason); // Sets state_ to SEND_CLOSED
567 }
568 // Careful study of RFC6455 section 7.1.7 and 7.1.1 indicates we should close
569 // the connection ourselves without waiting for the closing handshake.
570 stream_->Close();
571 state_ = CLOSED;
572
573 // We may be in the middle of processing several chunks. We should not re-use
574 // the frame header.
575 current_frame_header_.reset();
576 if (old_state != CLOSED) {
577 event_interface_->OnDropChannel(code, reason);
578 }
579 }
580
581 void WebSocketChannel::SendClose(uint16 code, const std::string& reason) {
582 DCHECK(state_ == CONNECTED || state_ == RECV_CLOSED);
583 uint64 payload_length = kWebSocketCloseCodeLength + reason.length();
szym 2013/07/09 15:59:51 why is this uint64 rather than size_t?
Adam Rice 2013/07/11 13:28:17 I have no idea. It's been like that since the firs
584 scoped_refptr<IOBufferWithSize> body = new IOBufferWithSize(payload_length);
585 WriteBigEndian(body->data(), code);
586 COMPILE_ASSERT(sizeof(code) == kWebSocketCloseCodeLength,
587 they_should_both_be_two);
588 std::copy(
589 reason.begin(), reason.end(), body->data() + kWebSocketCloseCodeLength);
590 SendIOBufferWithSize(true, WebSocketFrameHeader::kOpCodeClose, body);
591 state_ = state_ == CONNECTED ? SEND_CLOSED : CLOSED;
szym 2013/07/09 15:59:51 parens around the condition would help readability
Adam Rice 2013/07/11 13:28:17 Done.
592 }
593
594 void WebSocketChannel::ParseClose(const scoped_refptr<IOBufferWithSize>& buffer,
595 uint16* code,
596 std::string* reason) {
597 const char* data = buffer->data();
598 size_t size = base::checked_numeric_cast<size_t>(buffer->size());
599 reason->clear();
600 if (size < kWebSocketCloseCodeLength) {
601 *code = kWebSocketErrorNoStatusReceived;
602 if (size != 0) {
603 VLOG(1) << "Close frame with payload size " << size << " received "
604 << "(the first byte is " << std::hex << static_cast<int>(data[0])
605 << ")";
606 return;
607 }
608 return;
609 }
610 uint16 unchecked_code = 0;
611 ReadBigEndian(data, &unchecked_code);
612 COMPILE_ASSERT(sizeof(unchecked_code) == kWebSocketCloseCodeLength,
613 they_should_both_be_two_bytes);
614 if (unchecked_code >= static_cast<uint16>(kWebSocketNormalClosure) &&
615 unchecked_code <=
616 static_cast<uint16>(kWebSocketErrorPrivateReservedMax)) {
617 *code = unchecked_code;
618 } else {
619 VLOG(1) << "Close frame contained code outside of the valid range: "
620 << unchecked_code;
621 *code = kWebSocketErrorAbnormalClosure;
622 }
623 std::string text(data + kWebSocketCloseCodeLength, data + size);
624 // TODO(ricea): Is this check strict enough? In particular, check the
625 // "Security Considerations" from RFC3629.
626 if (IsStringUTF8(text)) {
627 using std::swap;
628 swap(*reason, text);
szym 2013/07/09 15:59:51 just call std::swap
Adam Rice 2013/07/11 13:28:17 I'm not completely sure that the specialisation of
629 }
630 }
631
632 } // namespace net
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698