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

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: Change close status code to uint16 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;
48 size_t total_bytes;
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 state_(FRESHLY_CONSTRUCTED) {}
85
86 WebSocketChannel::~WebSocketChannel() {
87 // The stream may hold a pointer to read_frame_chunks_, and so it needs to be
88 // destroyed first.
89 stream_.reset();
90 }
91
92 void WebSocketChannel::SendAddChannelRequest(
93 const std::vector<std::string>& requested_subprotocols,
94 const GURL& origin,
95 URLRequestContext* url_request_context) {
96 // Delegate to the tested version.
97 SendAddChannelRequestWithFactory(
98 requested_subprotocols,
99 origin,
100 url_request_context,
101 base::Bind(&WebSocketStream::CreateAndConnectStream));
102 }
103
104 void WebSocketChannel::SendAddChannelRequestWithFactory(
105 const std::vector<std::string>& requested_subprotocols,
106 const GURL& origin,
107 URLRequestContext* url_request_context,
108 base::Callback<scoped_ptr<WebSocketStreamRequest>(
109 const GURL&,
110 const std::vector<std::string>&,
111 const GURL&,
112 URLRequestContext*,
113 const BoundNetLog&,
114 scoped_ptr<WebSocketStream::ConnectDelegate>)> factory) {
115 DCHECK_EQ(FRESHLY_CONSTRUCTED, state_);
116 scoped_ptr<WebSocketStream::ConnectDelegate> connect_delegate(
117 new WebSocketChannel::ConnectDelegate(this));
118 stream_request_ = factory.Run(socket_url_,
119 requested_subprotocols,
120 origin,
121 url_request_context,
122 BoundNetLog(),
123 connect_delegate.Pass());
124 state_ = CONNECTING;
125 }
126
127 void WebSocketChannel::OnConnectSuccess(scoped_ptr<WebSocketStream> stream) {
128 DCHECK(stream);
129 DCHECK_EQ(CONNECTING, state_);
130 stream_ = stream.Pass();
131 state_ = CONNECTED;
132 event_interface_->OnAddChannelResponse(false, stream_->GetSubProtocol());
133
134 // TODO(ricea): Get flow control information from the WebSocketStream once we
135 // have a multiplexing WebSocketStream.
136 current_send_quota_ = send_quota_high_water_mark_;
137 event_interface_->OnFlowControl(send_quota_high_water_mark_);
138
139 // We don't need this any more.
140 stream_request_.reset();
141 ReadFrames();
142 }
143
144 void WebSocketChannel::OnConnectFailure(uint16 web_socket_error) {
tyoshino (SeeGerritForStatus) 2013/07/02 08:23:01 websocket_error
Adam Rice 2013/07/02 13:49:29 Done.
145 DCHECK_EQ(CONNECTING, state_);
146 state_ = CLOSED;
147 stream_request_.reset();
148 event_interface_->OnAddChannelResponse(true, "");
149 }
150
151 void WebSocketChannel::SendFrame(bool fin,
152 WebSocketFrameHeader::OpCode op_code,
153 const std::vector<char>& data) {
154 if (data.size() > INT_MAX) {
155 NOTREACHED() << "Frame size sanity check failed";
156 return;
157 }
158 if (stream_ == NULL) {
159 LOG(DFATAL) << "Got SendFrame without a connection established; "
160 << "misbehaving renderer? fin=" << fin << " op_code=" << op_code
161 << " data.size()=" << data.size();
162 return;
163 }
164 if (state_ == SEND_CLOSED || state_ == RECV_CLOSED || state_ == CLOSED) {
165 VLOG(1) << "SendFrame called in state " << state_
166 << ". This may be a bug, or a harmless race.";
167 return;
168 }
169 if (state_ != CONNECTED) {
170 NOTREACHED() << "SendFrame() called in state " << state_;
171 return;
172 }
173 if (data.size() > base::checked_numeric_cast<size_t>(current_send_quota_)) {
174 FailChannel(SEND_INTERNAL_ERROR,
175 kWebSocketMuxErrorSendQuotaViolation,
176 "Send quota exceeded");
177 return;
178 }
179 if (!WebSocketFrameHeader::IsKnownDataOpCode(op_code)) {
180 LOG(DFATAL) << "Got SendFrame with bogus op_code " << op_code
181 << "; misbehaving renderer? fin=" << fin
182 << " data.size()=" << data.size();
183 return;
184 }
185 current_send_quota_ -= data.size();
186 // TODO(ricea): If current_send_quota_ has dropped below
187 // send_quota_low_water_mark_, we may want to consider increasing the "low
188 // water mark" and "high water mark", but only if we think we are not
189 // saturating the link to the WebSocket server.
190 // TODO(ricea): For kOpCodeText, do UTF-8 validation?
191 scoped_refptr<IOBufferWithSize> buffer(new IOBufferWithSize(data.size()));
192 std::copy(data.begin(), data.end(), buffer->data());
193 SendIOBufferWithSize(fin, op_code, buffer);
194 }
195
196 void WebSocketChannel::SendIOBufferWithSize(
197 bool fin,
198 WebSocketFrameHeader::OpCode op_code,
199 const scoped_refptr<IOBufferWithSize>& buffer) {
200 DCHECK(state_ == CONNECTED || state_ == RECV_CLOSED);
201 DCHECK(stream_);
202 scoped_ptr<WebSocketFrameHeader> header(new WebSocketFrameHeader(op_code));
203 header->final = fin;
204 header->masked = true;
205 header->payload_length = buffer->size();
206 scoped_ptr<WebSocketFrameChunk> chunk(new WebSocketFrameChunk());
207 chunk->header = header.Pass();
208 chunk->final_chunk = true;
209 chunk->data = buffer;
210 if (data_being_sent_) {
211 // Either the link to the WebSocket server is saturated, or we are simply
212 // processing a batch of messages.
213 // TODO(ricea): We need to keep some statistics to work out which situation
214 // we are in and adjust quota appropriately.
215 if (!data_to_send_next_) {
216 data_to_send_next_.reset(new SendBuffer);
217 }
218 data_to_send_next_->frames.push_back(chunk.release());
219 data_to_send_next_->total_bytes += buffer->size();
220 } else {
221 data_being_sent_.reset(new SendBuffer);
222 data_being_sent_->frames.push_back(chunk.release());
223 data_being_sent_->total_bytes += buffer->size();
224 WriteFrames();
225 }
226 }
227
228 void WebSocketChannel::WriteFrames() {
229 // This is safe because we own the WebSocketStream and destroying it cancels
230 // all callbacks.
231 int result = stream_->WriteFrames(
232 &(data_being_sent_->frames),
233 base::Bind(&WebSocketChannel::OnWriteDone, base::Unretained(this)));
234 if (result != ERR_IO_PENDING) {
235 OnWriteDone(result);
tyoshino (SeeGerritForStatus) 2013/07/02 08:23:01 let's use loop. recursive call to WriteFrames from
Adam Rice 2013/07/02 13:49:29 Done.
236 }
237 }
238
239 void WebSocketChannel::OnWriteDone(int result) {
240 DCHECK(state_ != FRESHLY_CONSTRUCTED && state_ != CONNECTING);
241 DCHECK_NE(ERR_IO_PENDING, result);
242 DCHECK(data_being_sent_);
243 switch (result) {
244 case OK:
245 if (data_to_send_next_) {
246 data_being_sent_ = data_to_send_next_.Pass();
247 WriteFrames();
248 } else {
249 data_being_sent_.reset();
250 if (current_send_quota_ < send_quota_low_water_mark_) {
251 // TODO(ricea): Increase low_water_mark and high_water_mark if
252 // throughput is high, reduce them if throughput is low. Low water
253 // mark needs to be >= the bandwidth delay product *of the IPC
254 // channel*. Because factors like context-switch time, thread wake-up
255 // time, and bus speed come into play it is complex and probably needs
256 // to be determined empirically.
257 DCHECK_LE(send_quota_low_water_mark_, send_quota_high_water_mark_);
258 // TODO(ricea): Truncate quota by the quota specified by the remote
259 // server, if the protocol in use supports quota.
260 int fresh_quota = send_quota_high_water_mark_ - current_send_quota_;
261 current_send_quota_ += fresh_quota;
262 event_interface_->OnFlowControl(fresh_quota);
263 }
264 }
265 break;
266
267 // If a recoverable error condition existed, it would go here.
268
269 default:
270 DCHECK_LT(result, 0)
271 << "WriteFrames() should only return OK or ERR_ codes";
272 stream_->Close();
273 state_ = CLOSED;
274 event_interface_->OnDropChannel(kWebSocketErrorAbnormalClosure,
275 "Abnormal Closure");
276 break;
277 }
278 }
279
280 void WebSocketChannel::ReadFrames() {
281 // This use of base::Unretained is safe because we own the WebSocketStream,
282 // and any pending reads will be cancelled when it is destroyed.
283 int result = stream_->ReadFrames(
284 &read_frame_chunks_,
285 base::Bind(&WebSocketChannel::OnReadDone, base::Unretained(this)));
286 if (result != ERR_IO_PENDING) {
287 OnReadDone(result);
288 }
tyoshino (SeeGerritForStatus) 2013/07/02 08:23:01 ditto (loop)
Adam Rice 2013/07/02 13:49:29 Done.
289 }
290
291 void WebSocketChannel::OnReadDone(int result) {
292 DCHECK(state_ != FRESHLY_CONSTRUCTED && state_ != CONNECTING);
293 DCHECK_NE(ERR_IO_PENDING, result);
294 switch (result) {
295 case OK:
296 // ReadFrames() must use ERR_CONNECTION_CLOSED for a closed connection
297 // with no data read, not an empty response.
298 DCHECK(!read_frame_chunks_.empty())
299 << "ReadFrames() returned OK, but nothing was read.";
300 for (size_t i = 0; i < read_frame_chunks_.size(); ++i) {
301 scoped_ptr<WebSocketFrameChunk> chunk(read_frame_chunks_[i]);
302 read_frame_chunks_[i] = NULL;
303 ProcessFrameChunk(chunk.Pass());
304 }
305 read_frame_chunks_.clear();
306 // We need to always keep a call to ReadFrames pending.
307 ReadFrames();
308 return;
309
310 case ERR_CONNECTION_CLOSED: {
tyoshino (SeeGerritForStatus) 2013/07/02 08:23:01 stream_->Close() is unnecessary for this case?
Adam Rice 2013/07/02 13:49:29 It is probably correct to call Close() here. Which
311 State old_state = state_;
312 state_ = CLOSED;
313 if (old_state != RECV_CLOSED && old_state != CLOSED) {
314 // We need to inform the render process of the unexpected closure.
315 event_interface_->OnDropChannel(kWebSocketErrorAbnormalClosure,
316 "Abnormal Closure");
317 }
318 return;
319 }
320
321 default: {
322 DCHECK_LT(result, 0)
323 << "ReadFrames() should only return OK or ERR_ codes";
324 stream_->Close();
325 State old_state = state_;
326 state_ = CLOSED;
327 if (old_state != RECV_CLOSED && old_state != CLOSED) {
328 event_interface_->OnDropChannel(kWebSocketErrorAbnormalClosure,
329 "Abnormal Closure");
330 }
331 return;
332 }
333 }
334 }
335
336 // TODO(ricea): This method is too long. Break it up.
337 void WebSocketChannel::ProcessFrameChunk(
338 scoped_ptr<WebSocketFrameChunk> chunk) {
339 bool first_chunk = false;
340 if (chunk->header) {
tyoshino (SeeGerritForStatus) 2013/07/02 08:23:01 Check if current_frame_header_ is NULL.
Adam Rice 2013/07/02 13:49:29 Done.
341 first_chunk = true;
342 current_frame_header_.swap(chunk->header);
343 if (current_frame_header_->masked) {
344 // RFC6455 Section 5.1 "A client MUST close a connection if it detects a
345 // masked frame."
346 FailChannel(SEND_REAL_ERROR,
347 kWebSocketErrorProtocolError,
348 "Masked frame from server");
349 return;
350 }
351 }
352 if (!current_frame_header_) {
353 DCHECK(state_ != CONNECTED) << "Unexpected header-less frame received "
tyoshino (SeeGerritForStatus) 2013/07/02 08:23:01 why checking state_?
Adam Rice 2013/07/02 13:49:29 If the header was invalid, then we will have calle
tyoshino (SeeGerritForStatus) 2013/07/02 16:34:19 I see. Thanks for adding the comment.
354 << "(final_chunk = " << chunk->final_chunk
355 << ", data size = " << chunk->data->size()
356 << ")";
357 return;
358 }
359 scoped_refptr<IOBufferWithSize> data_buffer;
360 data_buffer.swap(chunk->data);
tyoshino (SeeGerritForStatus) 2013/07/02 08:23:01 let's save chunk->final_chunk and call chunk.reset
Adam Rice 2013/07/02 13:49:29 Done. I thought about splitting the method here,
tyoshino (SeeGerritForStatus) 2013/07/02 16:34:19 ok
361 WebSocketFrameHeader::OpCode opcode = current_frame_header_->opcode;
362 if (WebSocketFrameHeader::IsKnownControlOpCode(opcode)) {
363 if (chunk->final_chunk) {
364 if (incomplete_control_frame_body_) {
365 VLOG(2) << "Rejoining a split control frame, opcode " << opcode;
366 data_buffer =
367 ConcatenateIOBuffers(incomplete_control_frame_body_, data_buffer);
368 incomplete_control_frame_body_ = NULL;
369 }
370 } else {
371 // TODO(ricea): Enforce a maximum size of 125 bytes on the control frames
372 // we accept.
373 VLOG(2) << "Encountered a split control frame, opcode " << opcode;
374 if (incomplete_control_frame_body_) {
375 // The really horrid case. We need to create a new IOBufferWithSize
376 // combining the new one and the old one. This should virtually never
377 // happen.
378 // TODO(ricea): This algorithm is O(N^2). Use a fixed 127-byte byffer
tyoshino (SeeGerritForStatus) 2013/07/02 08:23:01 buffer you could do similar to SocketStream::pend
Adam Rice 2013/07/02 13:49:29 Done.
379 // instead.
380 VLOG(3) << "Hit the really horrid case";
381 incomplete_control_frame_body_ =
382 ConcatenateIOBuffers(incomplete_control_frame_body_, data_buffer);
383 } else {
384 // The merely horrid case. Store the IOBufferWithSize to use when the
385 // rest of the control frame arrives.
386 incomplete_control_frame_body_.swap(data_buffer);
387 }
388 return;
389 }
390 }
391
tyoshino (SeeGerritForStatus) 2013/07/02 08:23:01 L392 to L485 can be factored out into a separate m
Adam Rice 2013/07/02 13:49:29 Done.
392 switch (opcode) {
393 case WebSocketFrameHeader::kOpCodeText: // fall-thru
394 case WebSocketFrameHeader::kOpCodeBinary:
395 if (!first_chunk) {
396 opcode = WebSocketFrameHeader::kOpCodeContinuation;
397 }
398 // fall-thru
399 case WebSocketFrameHeader::kOpCodeContinuation:
400 if (state_ == RECV_CLOSED) {
tyoshino (SeeGerritForStatus) 2013/07/02 08:23:01 l400, l425, l440 can be merged and moved out to L3
Adam Rice 2013/07/02 13:49:29 Well-spotted. I did not notice that. Done.
401 FailChannel(SEND_REAL_ERROR,
402 kWebSocketErrorProtocolError,
403 "Data packet received after close");
404 return;
405 } else if (state_ == CONNECTED) {
406 const bool final = chunk->final_chunk && current_frame_header_->final;
407 // TODO(ricea): Can this copy be eliminated?
408 const char* const data_begin = data_buffer->data();
409 const char* const data_end = data_begin + data_buffer->size();
410 const std::vector<char> data(data_begin, data_end);
411 // TODO(ricea): Handle the (improbable) case when ReadFrames returns far
412 // more data at once than we want to send in a single IPC (in which case
413 // we need to buffer the data and return to the event loop with a
414 // callback to send the rest in 32K chunks).
415
416 // Send the received frame to the renderer process.
417 event_interface_->OnDataFrame(final, opcode, data);
418 } else {
419 VLOG(3) << "Ignored data packet received in state " << state_;
420 }
421 break;
422
423 case WebSocketFrameHeader::kOpCodePing:
424 VLOG(1) << "Got Ping of size " << data_buffer->size();
425 if (state_ == RECV_CLOSED) {
426 FailChannel(SEND_REAL_ERROR,
427 kWebSocketErrorProtocolError,
428 "Ping received after Close");
429 return;
430 } else if (state_ == CONNECTED) {
431 SendIOBufferWithSize(
432 true, WebSocketFrameHeader::kOpCodePong, data_buffer);
433 } else {
434 VLOG(3) << "Ignored ping in state " << state_;
435 }
436 break;
437
438 case WebSocketFrameHeader::kOpCodePong:
439 VLOG(1) << "Got Pong of size " << data_buffer->size();
440 if (state_ == RECV_CLOSED) {
441 FailChannel(SEND_REAL_ERROR,
442 kWebSocketErrorProtocolError,
443 "Pong received after Close");
444 return;
445 }
446 // We do not need to do anything with pong messages.
447 break;
448
449 case WebSocketFrameHeader::kOpCodeClose: {
450 uint16 code = kWebSocketNormalClosure;
451 std::string reason;
452 ParseClose(data_buffer, &code, &reason);
453 // TODO(ricea): Find a way to safely log the message from the close
454 // message (escape control codes and so on).
455 VLOG(1) << "Got Close with code " << code;
456 switch (state_) {
457 case CONNECTED:
458 state_ = RECV_CLOSED;
459 SendClose(code, reason);
460 event_interface_->OnDropChannel(code, reason);
461 break;
462
463 case RECV_CLOSED:
464 FailChannel(SEND_REAL_ERROR,
465 kWebSocketErrorProtocolError,
466 "Close received after Close");
467 break;
468
469 case SEND_CLOSED:
470 state_ = CLOSED;
471 event_interface_->OnDropChannel(code, reason);
472 break;
473
474 default:
475 LOG(DFATAL) << "Got Close in unexpected state " << state_;
476 break;
477 }
478 break;
479 }
480
481 default:
482 FailChannel(
483 SEND_REAL_ERROR, kWebSocketErrorProtocolError, "Unknown opcode");
484 break;
485 }
486 if (chunk->final_chunk) {
487 // Make sure we do not apply this frame header to any future chunks.
488 current_frame_header_.reset();
489 }
490 }
491
492 void WebSocketChannel::SendFlowControl(int64 quota) {
493 DCHECK_EQ(CONNECTED, state_);
494 // TODO(ricea): Add interface to WebSocketStream and implement.
495 // stream_->SendFlowControl(quota);
496 }
497
498 void WebSocketChannel::SendDropChannel(uint16 code,
499 const std::string& reason) {
500 if (state_ == SEND_CLOSED || state_ == CLOSED) {
501 VLOG(1) << "SendDropChannel called in state " << state_
502 << ". This may be a bug, or a harmless race.";
503 return;
504 }
505 if (state_ != CONNECTED) {
506 NOTREACHED() << "SendDropChannel() called in state " << state_;
507 return;
508 }
509 // TODO(ricea): Validate |code|? Check that |reason| is valid UTF-8?
510 // TODO(ricea): There should be a timeout for the closing handshake.
511 SendClose(code, reason);
tyoshino (SeeGerritForStatus) 2013/07/02 08:23:01 shouldn't SendDropChannel just close the stream? S
Adam Rice 2013/07/02 13:49:29 I'm not sure I get your point. SendDropChannel() i
tyoshino (SeeGerritForStatus) 2013/07/02 16:34:19 I see. So, for ws.close(), this code looks good. B
Adam Rice 2013/07/03 07:24:21 Done.
512 }
513
514 void WebSocketChannel::FailChannel(ExposeError expose,
515 uint16 code,
516 const std::string& reason) {
517 // TODO(ricea): Logging.
518 State old_state = state_;
519 if (state_ == CONNECTED) {
520 uint16 send_code = kWebSocketErrorGoingAway;
521 std::string send_reason = "Internal Error";
522 if (expose == SEND_REAL_ERROR) {
523 send_code = code;
524 send_reason = reason;
525 }
526 SendClose(send_code, send_reason);
527 }
528 // This method is mostly called in response to an invalid frame, in
529 // which case we should not re-use the header.
530 current_frame_header_.reset();
531 if (old_state != RECV_CLOSED && old_state != CLOSED) {
532 event_interface_->OnDropChannel(code, reason);
533 }
tyoshino (SeeGerritForStatus) 2013/07/02 08:23:01 set state_ to CLOSED and maybe close stream?
Adam Rice 2013/07/02 13:49:29 I don't think I should be calling OnDropChannel he
tyoshino (SeeGerritForStatus) 2013/07/02 16:34:19 See also http://tools.ietf.org/html/rfc6455#sectio
Adam Rice 2013/07/03 07:24:21 Oh. I think I misinterpreted the RFC. I was thinki
534 }
535
536 void WebSocketChannel::SendClose(uint16 code,
537 const std::string& reason) {
538 DCHECK(state_ == CONNECTED || state_ == RECV_CLOSED);
539 uint64 payload_length = kWebSocketCloseCodeLength + reason.length();
540 scoped_refptr<IOBufferWithSize> body = new IOBufferWithSize(payload_length);
541 WriteBigEndian(body->data(), code);
542 COMPILE_ASSERT(sizeof(code) == kWebSocketCloseCodeLength,
543 they_should_both_be_two);
544 std::copy(
545 reason.begin(), reason.end(), body->data() + kWebSocketCloseCodeLength);
546 SendIOBufferWithSize(true, WebSocketFrameHeader::kOpCodeClose, body);
547 state_ = state_ == CONNECTED ? SEND_CLOSED : CLOSED;
548 }
549
550 void WebSocketChannel::ParseClose(const scoped_refptr<IOBufferWithSize>& buffer,
551 uint16* code,
552 std::string* reason) {
553 const char* data = buffer->data();
554 size_t size = base::checked_numeric_cast<size_t>(buffer->size());
555 reason->clear();
556 if (size < kWebSocketCloseCodeLength) {
557 *code = kWebSocketErrorNoStatusReceived;
558 if (size != 0) {
559 VLOG(1) << "Close frame with payload size " << size << " received "
560 << "(the first byte is " << std::hex << static_cast<int>(data[0])
561 << ")";
562 return;
563 }
564 return;
565 }
566 uint16 unchecked_code = 0;
567 ReadBigEndian(data, &unchecked_code);
568 COMPILE_ASSERT(sizeof(unchecked_code) == kWebSocketCloseCodeLength,
569 they_should_both_be_two_bytes);
tyoshino (SeeGerritForStatus) 2013/07/02 08:23:01 sorry, is this normalization specified in the spec
Adam Rice 2013/07/02 13:49:29 It's a bit ambiguous, to be honest. The clearest l
tyoshino (SeeGerritForStatus) 2013/07/02 16:34:19 That makes some sense, but not to confuse users, w
570 if (unchecked_code >= static_cast<uint16>(kWebSocketNormalClosure) &&
571 unchecked_code <=
572 static_cast<uint16>(kWebSocketErrorPrivateReservedMax)) {
573 *code = unchecked_code;
574 } else {
575 VLOG(1) << "Close frame contained code outside of the valid range: "
576 << unchecked_code;
577 *code = kWebSocketErrorProtocolError;
tyoshino (SeeGerritForStatus) 2013/07/02 08:23:01 this is confusing. please pass the received code a
Adam Rice 2013/07/02 13:49:29 I agree that it is confusing, but I think it is th
tyoshino (SeeGerritForStatus) 2013/07/02 16:34:19 I understand, but we must use some code that can t
Adam Rice 2013/07/03 07:24:21 Okay, that makes sense. However, if all other brow
578 }
579 std::string text(data + kWebSocketCloseCodeLength, data + size);
580 // TODO(ricea): Is this check strict enough? In particular, check the
581 // "Security Considerations" from RFC3629.
582 if (IsStringUTF8(text)) {
583 using std::swap;
584 swap(*reason, text);
tyoshino (SeeGerritForStatus) 2013/07/02 08:23:01 is this swap more efficient than substitution?
Adam Rice 2013/07/02 13:49:29 I'm not sure what you mean by a substitution. It i
tyoshino (SeeGerritForStatus) 2013/07/02 16:34:19 Thanks. Never mind.
585 }
586 }
587
588 } // namespace net
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698