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

Side by Side Diff: ppapi/proxy/websocket_resource.cc

Issue 10944005: Pepper WebSocket API: Implement new design Chrome IPC (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: retry Created 8 years, 2 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 | Annotate | Revision Log
OLDNEW
(Empty)
1 // Copyright (c) 2012 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 "ppapi/proxy/websocket_resource.h"
6
7 #include <set>
8 #include <vector>
9
10 #include "ppapi/c/pp_errors.h"
11 #include "ppapi/proxy/ppapi_messages.h"
12 #include "ppapi/shared_impl/ppapi_globals.h"
13 #include "ppapi/shared_impl/var.h"
14 #include "ppapi/shared_impl/var_tracker.h"
15 #include "third_party/WebKit/Source/WebKit/chromium/public/WebSocket.h"
16
17 namespace {
18
19 const uint32_t kMaxReasonSizeInBytes = 123;
20 const size_t kBaseFramingOverhead = 2;
21 const size_t kMaskingKeyLength = 4;
22 const size_t kMinimumPayloadSizeWithTwoByteExtendedPayloadLength = 126;
23 const size_t kMinimumPayloadSizeWithEightByteExtendedPayloadLength = 0x10000;
24
25 uint64_t SaturateAdd(uint64_t a, uint64_t b) {
26 if (kuint64max - a < b)
27 return kuint64max;
28 return a + b;
29 }
30
31 uint64_t GetFrameSize(uint64_t payload_size) {
32 uint64_t overhead = kBaseFramingOverhead + kMaskingKeyLength;
33 if (payload_size > kMinimumPayloadSizeWithEightByteExtendedPayloadLength)
34 overhead += 8;
35 else if (payload_size > kMinimumPayloadSizeWithTwoByteExtendedPayloadLength)
36 overhead += 2;
37 return SaturateAdd(payload_size, overhead);
38 }
39
40 bool InValidStateToReceive(PP_WebSocketReadyState state) {
41 return state == PP_WEBSOCKETREADYSTATE_OPEN ||
42 state == PP_WEBSOCKETREADYSTATE_CLOSING;
43 }
44
45 } // namespace
46
47
48 namespace ppapi {
49 namespace proxy {
50
51 WebSocketResource::WebSocketResource(Connection connection,
52 PP_Instance instance)
53 : PluginResource(connection, instance),
54 connect_request_id_(0),
55 close_request_id_(0),
56 state_(PP_WEBSOCKETREADYSTATE_INVALID),
57 error_was_received_(false),
58 receive_callback_var_(NULL),
59 empty_string_(new StringVar("", 0)),
60 close_code_(0),
61 close_reason_(NULL),
62 close_was_clean_(PP_FALSE),
63 extensions_(NULL),
64 protocol_(NULL),
65 url_(NULL),
66 buffered_amount_(0),
67 buffered_amount_after_close_(0) {
68 }
69
70 WebSocketResource::~WebSocketResource() {
71 }
72
73 thunk::PPB_WebSocket_API* WebSocketResource::AsPPB_WebSocket_API() {
74 return this;
75 }
76
77 int32_t WebSocketResource::Connect(
78 PP_Var url,
brettw 2012/10/03 04:54:02 Can you make this (in the _api as well) a const re
Takashi Toyoshima 2012/10/03 11:42:20 I think we have not used const ref for PP_Var usua
brettw 2012/10/03 20:30:24 We shouldn't change the C bindings, which are the
Takashi Toyoshima 2012/10/05 07:35:01 Thank you. Now I understand the reason of PP_Var c
79 const PP_Var protocols[],
80 uint32_t protocol_count,
81 scoped_refptr<TrackedCallback> callback) {
82 if (TrackedCallback::IsPending(connect_callback_))
83 return PP_ERROR_INPROGRESS;
84
85 // Connect() can be called at most once.
86 if (state_ != PP_WEBSOCKETREADYSTATE_INVALID)
87 return PP_ERROR_INPROGRESS;
88 state_ = PP_WEBSOCKETREADYSTATE_CLOSED;
89
90 // Get the URL.
91 scoped_refptr<StringVar> url_string = StringVar::FromPPVar(url);
92 if (!url_string)
93 return PP_ERROR_BADARGUMENT;
94
95 // Validate protocols.
96 std::string protocol_string;
97 std::set<std::string> protocol_set;
98 for (uint32_t i = 0; i < protocol_count; i++) {
99 scoped_refptr<StringVar> protocol(StringVar::FromPPVar(protocols[i]));
100
101 // Check invalid and empty entries.
102 if (!protocol || !protocol->value().length())
103 return PP_ERROR_BADARGUMENT;
104
105 // Check duplicated protocol entries.
106 if (protocol_set.find(protocol->value()) != protocol_set.end())
107 return PP_ERROR_BADARGUMENT;
108 protocol_set.insert(protocol->value());
109
110 // Check containing characters.
111 for (std::string::const_iterator it = protocol->value().begin();
112 it != protocol->value().end();
113 ++it) {
114 uint8_t character = *it;
115 // WebSocket specification says "(Subprotocol string must consist of)
116 // characters in the range U+0021 to U+007E not including separator
117 // characters as defined in [RFC2616]."
118 const uint8_t minimumProtocolCharacter = '!'; // U+0021.
brettw 2012/10/03 04:54:02 To what extent is this checking security sensitive
Takashi Toyoshima 2012/10/03 11:46:28 Firstly, I think this check is safe to run in NaCl
119 const uint8_t maximumProtocolCharacter = '~'; // U+007E.
120 if (character < minimumProtocolCharacter ||
121 character > maximumProtocolCharacter ||
122 character == '"' || character == '(' || character == ')' ||
123 character == ',' || character == '/' ||
124 (character >= ':' && character <= '@') || // U+003A - U+0040
125 (character >= '[' && character <= ']') || // U+005B - u+005D
126 character == '{' || character == '}')
127 return PP_ERROR_BADARGUMENT;
128 }
129 // Join protocols with the comma separator.
130 if (i != 0)
131 protocol_string.append(",");
132 protocol_string.append(protocol->value());
133 }
134
135 // Install callback.
136 connect_callback_ = callback;
137
138 // Create remote host in the renderer, then request to check the URL and
139 // establish the connection.
140 state_ = PP_WEBSOCKETREADYSTATE_CONNECTING;
141 SendCreateToRenderer(PpapiHostMsg_WebSocket_Create());
142 connect_request_id_ =
143 CallRenderer(PpapiHostMsg_WebSocket_Connect(url_string->value(),
144 protocol_string));
145
146 return PP_OK_COMPLETIONPENDING;
147 }
148
149 int32_t WebSocketResource::Close(uint16_t code,
150 PP_Var reason,
151 scoped_refptr<TrackedCallback> callback) {
152 if (TrackedCallback::IsPending(close_callback_))
153 return PP_ERROR_INPROGRESS;
154 if (state_ == PP_WEBSOCKETREADYSTATE_INVALID)
155 return PP_ERROR_FAILED;
156
157 // Validate |code| and |reason|.
158 scoped_refptr<StringVar> reason_string_var;
159 std::string reason_string;
160 WebKit::WebSocket::CloseEventCode event_code =
161 static_cast<WebKit::WebSocket::CloseEventCode>(code);
162 if (code == PP_WEBSOCKETSTATUSCODE_NOT_SPECIFIED) {
163 // PP_WEBSOCKETSTATUSCODE_NOT_SPECIFIED and CloseEventCodeNotSpecified are
164 // assigned to different values. A conversion is needed if
165 // PP_WEBSOCKETSTATUSCODE_NOT_SPECIFIED is specified.
166 event_code = WebKit::WebSocket::CloseEventCodeNotSpecified;
167 } else {
168 if (!(code == PP_WEBSOCKETSTATUSCODE_NORMAL_CLOSURE ||
169 (PP_WEBSOCKETSTATUSCODE_USER_REGISTERED_MIN <= code &&
170 code <= PP_WEBSOCKETSTATUSCODE_USER_PRIVATE_MAX)))
171 // RFC 6455 limits applications to use reserved connection close code in
172 // section 7.4.2.. The WebSocket API (http://www.w3.org/TR/websockets/)
173 // defines this out of range error as InvalidAccessError in JavaScript.
174 return PP_ERROR_NOACCESS;
175
176 // |reason| must be ignored if it is PP_VARTYPE_UNDEFINED or |code| is
177 // PP_WEBSOCKETSTATUSCODE_NOT_SPECIFIED.
178 if (reason.type != PP_VARTYPE_UNDEFINED) {
179 // Validate |reason|.
180 reason_string_var = StringVar::FromPPVar(reason);
181 if (!reason_string_var ||
182 reason_string_var->value().size() > kMaxReasonSizeInBytes)
183 return PP_ERROR_BADARGUMENT;
184 reason_string = reason_string_var->value();
185 }
186 }
187
188 // Check state.
189 if (state_ == PP_WEBSOCKETREADYSTATE_CLOSING)
190 return PP_ERROR_INPROGRESS;
191 if (state_ == PP_WEBSOCKETREADYSTATE_CLOSED)
192 return PP_OK;
193
194 // Install |callback|.
195 close_callback_ = callback;
196
197 // Abort ongoing connect.
198 if (connect_callback_) {
199 state_ = PP_WEBSOCKETREADYSTATE_CLOSING;
200 // Need to do a "Post" to avoid reentering the plugin.
201 connect_callback_->PostAbort();
202 connect_callback_ = NULL;
203 CallRenderer(PpapiHostMsg_WebSocket_Fail(
204 "WebSocket was closed before the connection was established."));
205 return PP_OK_COMPLETIONPENDING;
206 }
207
208 // Abort ongoing receive.
209 if (receive_callback_) {
210 receive_callback_var_ = NULL;
211 // Need to do a "Post" to avoid reentering the plugin.
212 receive_callback_->PostAbort();
213 receive_callback_ = NULL;
214 }
215
216 // Close connection.
217 state_ = PP_WEBSOCKETREADYSTATE_CLOSING;
218 close_request_id_ =
219 CallRenderer(PpapiHostMsg_WebSocket_Close((int32_t)event_code,
brettw 2012/10/03 04:54:02 Nit: use C++ casts.
Takashi Toyoshima 2012/10/03 11:42:20 Done.
220 reason_string));
221 return PP_OK_COMPLETIONPENDING;
222 }
223
224 int32_t WebSocketResource::ReceiveMessage(
225 PP_Var* message,
226 scoped_refptr<TrackedCallback> callback) {
227 if (TrackedCallback::IsPending(receive_callback_))
228 return PP_ERROR_INPROGRESS;
229
230 // Check state.
231 if (state_ == PP_WEBSOCKETREADYSTATE_INVALID ||
232 state_ == PP_WEBSOCKETREADYSTATE_CONNECTING)
233 return PP_ERROR_BADARGUMENT;
234
235 // Just return received message if any received message is queued.
236 if (!received_messages_.empty()) {
237 receive_callback_var_ = message;
238 return DoReceive();
239 }
240
241 // Check state again. In CLOSED state, no more messages will be received.
242 if (state_ == PP_WEBSOCKETREADYSTATE_CLOSED)
243 return PP_ERROR_BADARGUMENT;
244
245 // Returns PP_ERROR_FAILED after an error is received and received messages
246 // is exhausted.
247 if (error_was_received_)
248 return PP_ERROR_FAILED;
249
250 // Or retain |message| as buffer to store and install |callback|.
251 receive_callback_var_ = message;
252 receive_callback_ = callback;
253
254 return PP_OK_COMPLETIONPENDING;
255 }
256
257 int32_t WebSocketResource::SendMessage(PP_Var message) {
258 // Check state.
259 if (state_ == PP_WEBSOCKETREADYSTATE_INVALID ||
260 state_ == PP_WEBSOCKETREADYSTATE_CONNECTING)
261 return PP_ERROR_BADARGUMENT;
262
263 if (state_ == PP_WEBSOCKETREADYSTATE_CLOSING ||
264 state_ == PP_WEBSOCKETREADYSTATE_CLOSED) {
265 // Handle buffered_amount_after_close_.
266 uint64_t payload_size = 0;
267 if (message.type == PP_VARTYPE_STRING) {
268 scoped_refptr<StringVar> message_string = StringVar::FromPPVar(message);
269 if (message_string)
270 payload_size += message_string->value().length();
271 } else if (message.type == PP_VARTYPE_ARRAY_BUFFER) {
272 scoped_refptr<ArrayBufferVar> message_array_buffer =
273 ArrayBufferVar::FromPPVar(message);
274 if (message_array_buffer)
275 payload_size += message_array_buffer->ByteLength();
276 } else {
277 // TODO(toyoshim): Support Blob.
278 return PP_ERROR_NOTSUPPORTED;
279 }
280
281 buffered_amount_after_close_ =
282 SaturateAdd(buffered_amount_after_close_, GetFrameSize(payload_size));
283
284 return PP_ERROR_FAILED;
285 }
286
287 // Send the message.
288 // TODO(toyoshim): Check if ppapi::proxy::SerializedVar can reduce copy.
brettw 2012/10/03 04:54:02 Nope, doing this won't save a copy, so you can jus
Takashi Toyoshima 2012/10/03 11:42:20 Thanks!
289 if (message.type == PP_VARTYPE_STRING) {
290 // Convert message to std::string, then send it.
291 scoped_refptr<StringVar> message_string = StringVar::FromPPVar(message);
292 if (!message_string)
293 return PP_ERROR_BADARGUMENT;
294 CallRenderer(PpapiHostMsg_WebSocket_SendText(message_string->value()));
295 } else if (message.type == PP_VARTYPE_ARRAY_BUFFER) {
296 // Convert message to std::vector<uint8_t>, then send it.
297 scoped_refptr<ArrayBufferVar> message_arraybuffer =
298 ArrayBufferVar::FromPPVar(message);
299 if (!message_arraybuffer)
300 return PP_ERROR_BADARGUMENT;
301 uint8_t* message_data = static_cast<uint8_t*>(message_arraybuffer->Map());
302 uint32 message_length = message_arraybuffer->ByteLength();
303 std::vector<uint8_t> message_vector(message_data,
304 message_data + message_length);
305 CallRenderer(PpapiHostMsg_WebSocket_SendBinary(message_vector));
306 } else {
307 // TODO(toyoshim): Support Blob.
308 return PP_ERROR_NOTSUPPORTED;
309 }
310 return PP_OK;
311 }
312
313 uint64_t WebSocketResource::GetBufferedAmount() {
314 return SaturateAdd(buffered_amount_, buffered_amount_after_close_);
315 }
316
317 uint16_t WebSocketResource::GetCloseCode() {
318 return close_code_;
319 }
320
321 PP_Var WebSocketResource::GetCloseReason() {
322 if (!close_reason_)
323 return empty_string_->GetPPVar();
324 return close_reason_->GetPPVar();
325 }
326
327 PP_Bool WebSocketResource::GetCloseWasClean() {
328 return close_was_clean_;
329 }
330
331 PP_Var WebSocketResource::GetExtensions() {
332 return StringVar::StringToPPVar("");
brettw 2012/10/03 04:54:02 Can you use std::string() instead of ""? It's more
Takashi Toyoshima 2012/10/03 11:42:20 Done.
333 }
334
335 PP_Var WebSocketResource::GetProtocol() {
336 if (!protocol_)
337 return empty_string_->GetPPVar();
338 return protocol_->GetPPVar();
339 }
340
341 PP_WebSocketReadyState WebSocketResource::GetReadyState() {
342 return state_;
343 }
344
345 PP_Var WebSocketResource::GetURL() {
346 if (!url_)
347 return empty_string_->GetPPVar();
348 return url_->GetPPVar();
349 }
350
351 void WebSocketResource::OnReplyReceived(
352 const ResourceMessageReplyParams& params,
353 const IPC::Message& msg) {
354 // TODO(toyoshim): DISPATCH_RESOURCE_REPLY will be replaced with other
355 // mechanisms. WebSocket dispatches IPC messages manually for now and must be
356 // replaced with the new one in the future.
357 int32_t sequence = params.sequence();
358 if (!sequence) {
359 // Handle unsolicited IPCs.
360 switch (msg.type()) {
361 case PpapiPluginMsg_WebSocket_ReceiveTextReply::ID: {
362 PpapiPluginMsg_WebSocket_ReceiveTextReply::Schema::Param p;
363 if (PpapiPluginMsg_WebSocket_ReceiveTextReply::Read(&msg, &p))
364 OnPluginMsgReceiveTextReply(params, p.a);
365 else
366 NOTREACHED();
367 break; }
brettw 2012/10/03 04:54:02 I've usually seen the closing } on the next line.
Takashi Toyoshima 2012/10/03 11:42:20 Done.
368 case PpapiPluginMsg_WebSocket_ReceiveBinaryReply::ID: {
369 PpapiPluginMsg_WebSocket_ReceiveBinaryReply::Schema::Param p;
370 if (PpapiPluginMsg_WebSocket_ReceiveBinaryReply::Read(&msg, &p))
371 OnPluginMsgReceiveBinaryReply(params, p.a);
372 else
373 NOTREACHED();
374 break; }
375 case PpapiPluginMsg_WebSocket_ErrorReply::ID: {
376 OnPluginMsgErrorReply(params);
377 break; }
378 case PpapiPluginMsg_WebSocket_BufferedAmountReply::ID: {
379 PpapiPluginMsg_WebSocket_BufferedAmountReply::Schema::Param p;
380 if (PpapiPluginMsg_WebSocket_BufferedAmountReply::Read(&msg, &p))
381 OnPluginMsgBufferedAmountReply(params, p.a);
382 else
383 NOTREACHED();
384 break; }
385 case PpapiPluginMsg_WebSocket_StateReply::ID: {
386 PpapiPluginMsg_WebSocket_StateReply::Schema::Param p;
387 if (PpapiPluginMsg_WebSocket_StateReply::Read(&msg, &p))
388 OnPluginMsgStateReply(params, p.a);
389 else
390 NOTREACHED();
391 break; }
392 case PpapiPluginMsg_WebSocket_ClosedReply::ID: {
393 PpapiPluginMsg_WebSocket_ClosedReply::Schema::Param p;
394 if (PpapiPluginMsg_WebSocket_ClosedReply::Read(&msg, &p))
395 OnPluginMsgClosedReply(params, p.a, p.b, p.c, p.d);
396 else
397 NOTREACHED();
398 break; }
399 default:
400 NOTREACHED();
401 }
402 } else if (sequence == connect_request_id_) {
403 PpapiPluginMsg_WebSocket_ConnectReply::Schema::Param p;
404 if (PpapiPluginMsg_WebSocket_ConnectReply::Read(&msg, &p))
405 OnPluginMsgConnectReply(params, p.a, p.b);
406 else // On error use default params and run the callback.
407 OnPluginMsgConnectReply(params, std::string(), std::string());
408 } else if (sequence == close_request_id_) {
409 PpapiPluginMsg_WebSocket_CloseReply::Schema::Param p;
410 if (PpapiPluginMsg_WebSocket_CloseReply::Read(&msg, &p))
411 OnPluginMsgCloseReply(params, p.a, p.b, p.c, p.d);
412 else // On error use default params and run the callback.
413 OnPluginMsgCloseReply(params, 0, false, 0, std::string());
414 } else {
415 NOTREACHED();
416 }
417 }
418
419 void WebSocketResource::OnPluginMsgConnectReply(
420 const ResourceMessageReplyParams& params,
421 const std::string& url,
422 const std::string& protocol) {
423 if (!TrackedCallback::IsPending(connect_callback_))
424 return;
425
426 int32_t result = params.result();
427 if (result == PP_OK) {
428 state_ = PP_WEBSOCKETREADYSTATE_OPEN;
429 protocol_ = new StringVar(protocol);
430 } else {
431 url_ = new StringVar(url);
432 }
433 if (result != PP_OK_COMPLETIONPENDING)
434 TrackedCallback::ClearAndRun(&connect_callback_, params.result());
435 }
436
437 void WebSocketResource::OnPluginMsgCloseReply(
438 const ResourceMessageReplyParams& params,
439 unsigned long buffered_amount,
440 bool was_clean,
441 unsigned short code,
442 const std::string& reason) {
443 // Set close related properties.
444 state_ = PP_WEBSOCKETREADYSTATE_CLOSED;
445 buffered_amount_ = buffered_amount;
446 close_was_clean_ = PP_FromBool(was_clean);
447 close_code_ = code;
448 close_reason_ = new StringVar(reason);
449
450 bool post = false;
451 if (TrackedCallback::IsPending(connect_callback_)) {
452 connect_callback_->PostRun(PP_ERROR_FAILED);
453 post = true;
454 }
455
456 if (TrackedCallback::IsPending(receive_callback_)) {
457 receive_callback_var_ = NULL;
458 receive_callback_->PostRun(PP_ERROR_FAILED);
459 post = true;
460 }
461
462 if (!TrackedCallback::IsPending(close_callback_))
463 return;
464
465 if (post)
466 close_callback_->PostRun(params.result());
467 else
468 TrackedCallback::ClearAndRun(&close_callback_, params.result());
469 }
470
471 void WebSocketResource::OnPluginMsgReceiveTextReply(
472 const ResourceMessageReplyParams& params,
473 const std::string& message) {
474 // Dispose packets after receiving an error or in invalid state.
475 if (error_was_received_ || !InValidStateToReceive(state_))
476 return;
477
478 // Append received data to queue.
479 received_messages_.push(scoped_refptr<Var>(new StringVar(message)));
480
481 if (!TrackedCallback::IsPending(receive_callback_))
482 return;
483
484 TrackedCallback::ClearAndRun(&receive_callback_, DoReceive());
485 }
486
487 void WebSocketResource::OnPluginMsgReceiveBinaryReply(
488 const ResourceMessageReplyParams& params,
489 const std::vector<uint8_t>& message) {
490 // Dispose packets after receiving an error or in invalid state.
491 if (error_was_received_ || !InValidStateToReceive(state_))
492 return;
493
494 // Append received data to queue.
495 scoped_refptr<Var> message_var(ArrayBufferVar::FromPPVar(
496 PpapiGlobals::Get()->GetVarTracker()->MakeArrayBufferPPVar(
497 message.size(),
498 &message.front())));
499 received_messages_.push(message_var);
500
501 if (!TrackedCallback::IsPending(receive_callback_))
502 return;
503
504 TrackedCallback::ClearAndRun(&receive_callback_, DoReceive());
505
brettw 2012/10/03 04:54:02 Nit: blank line
Takashi Toyoshima 2012/10/03 11:42:20 Done.
506 }
507
508 void WebSocketResource::OnPluginMsgErrorReply(
509 const ResourceMessageReplyParams& params) {
510 }
511
512 void WebSocketResource::OnPluginMsgBufferedAmountReply(
513 const ResourceMessageReplyParams& params,
514 unsigned long buffered_amount) {
515 buffered_amount_ = buffered_amount;
516 }
517
518 void WebSocketResource::OnPluginMsgStateReply(
519 const ResourceMessageReplyParams& params,
520 int32_t state) {
521 state_ = static_cast<PP_WebSocketReadyState>(state);
522 }
523
524 void WebSocketResource::OnPluginMsgClosedReply(
525 const ResourceMessageReplyParams& params,
526 unsigned long buffered_amount,
527 bool was_clean,
528 unsigned short code,
529 const std::string& reason) {
530 OnPluginMsgCloseReply(params, buffered_amount, was_clean, code, reason);
531 }
532
533 int32_t WebSocketResource::DoReceive() {
534 if (!receive_callback_var_)
535 return PP_OK;
536
537 *receive_callback_var_ = received_messages_.front()->GetPPVar();
538 received_messages_.pop();
539 receive_callback_var_ = NULL;
540 return PP_OK;
541 }
542
543 } // namespace proxy
544 } // namespace ppapi
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698