OLD | NEW |
---|---|
(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 state_(PP_WEBSOCKETREADYSTATE_INVALID), | |
55 error_was_received_(false), | |
56 receive_callback_var_(NULL), | |
57 empty_string_(new StringVar("", 0)), | |
brettw
2012/10/03 20:30:24
Can you use "new StringVar(std::string())," here i
Takashi Toyoshima
2012/10/05 07:35:01
Done.
| |
58 close_code_(0), | |
59 close_reason_(NULL), | |
60 close_was_clean_(PP_FALSE), | |
61 extensions_(NULL), | |
62 protocol_(NULL), | |
63 url_(NULL), | |
64 buffered_amount_(0), | |
65 buffered_amount_after_close_(0) { | |
66 } | |
67 | |
68 WebSocketResource::~WebSocketResource() { | |
69 } | |
70 | |
71 thunk::PPB_WebSocket_API* WebSocketResource::AsPPB_WebSocket_API() { | |
72 return this; | |
73 } | |
74 | |
75 int32_t WebSocketResource::Connect( | |
76 const PP_Var& url, | |
77 const PP_Var protocols[], | |
78 uint32_t protocol_count, | |
79 scoped_refptr<TrackedCallback> callback) { | |
80 if (TrackedCallback::IsPending(connect_callback_)) | |
81 return PP_ERROR_INPROGRESS; | |
82 | |
83 // Connect() can be called at most once. | |
84 if (state_ != PP_WEBSOCKETREADYSTATE_INVALID) | |
85 return PP_ERROR_INPROGRESS; | |
86 state_ = PP_WEBSOCKETREADYSTATE_CLOSED; | |
87 | |
88 // Get the URL. | |
89 scoped_refptr<StringVar> url_string = StringVar::FromPPVar(url); | |
90 if (!url_string) | |
91 return PP_ERROR_BADARGUMENT; | |
92 | |
93 // Get the protocols. | |
94 std::set<std::string> protocol_set; | |
95 std::vector<std::string> protocol_strings; | |
96 protocol_strings.reserve(protocol_count); | |
97 for (uint32_t i = 0; i < protocol_count; ++i) { | |
98 scoped_refptr<StringVar> protocol(StringVar::FromPPVar(protocols[i])); | |
99 | |
100 // Check invalid and empty entries. | |
101 if (!protocol || !protocol->value().length()) | |
102 return PP_ERROR_BADARGUMENT; | |
103 | |
104 // Check duplicated protocol entries. | |
105 if (protocol_set.find(protocol->value()) != protocol_set.end()) | |
106 return PP_ERROR_BADARGUMENT; | |
107 protocol_set.insert(protocol->value()); | |
108 | |
109 protocol_strings.push_back(protocol->value()); | |
110 } | |
111 | |
112 // Install callback. | |
113 connect_callback_ = callback; | |
114 | |
115 // Create remote host in the renderer, then request to check the URL and | |
116 // establish the connection. | |
117 state_ = PP_WEBSOCKETREADYSTATE_CONNECTING; | |
118 SendCreateToRenderer(PpapiHostMsg_WebSocket_Create()); | |
119 CallRenderer(PpapiHostMsg_WebSocket_Connect(url_string->value(), | |
120 protocol_strings)); | |
121 | |
122 return PP_OK_COMPLETIONPENDING; | |
123 } | |
124 | |
125 int32_t WebSocketResource::Close(uint16_t code, | |
126 const PP_Var& reason, | |
127 scoped_refptr<TrackedCallback> callback) { | |
128 if (TrackedCallback::IsPending(close_callback_)) | |
129 return PP_ERROR_INPROGRESS; | |
130 if (state_ == PP_WEBSOCKETREADYSTATE_INVALID) | |
131 return PP_ERROR_FAILED; | |
132 | |
133 // Validate |code| and |reason|. | |
134 scoped_refptr<StringVar> reason_string_var; | |
135 std::string reason_string; | |
136 WebKit::WebSocket::CloseEventCode event_code = | |
137 static_cast<WebKit::WebSocket::CloseEventCode>(code); | |
138 if (code == PP_WEBSOCKETSTATUSCODE_NOT_SPECIFIED) { | |
139 // PP_WEBSOCKETSTATUSCODE_NOT_SPECIFIED and CloseEventCodeNotSpecified are | |
140 // assigned to different values. A conversion is needed if | |
141 // PP_WEBSOCKETSTATUSCODE_NOT_SPECIFIED is specified. | |
142 event_code = WebKit::WebSocket::CloseEventCodeNotSpecified; | |
143 } else { | |
144 if (!(code == PP_WEBSOCKETSTATUSCODE_NORMAL_CLOSURE || | |
145 (PP_WEBSOCKETSTATUSCODE_USER_REGISTERED_MIN <= code && | |
146 code <= PP_WEBSOCKETSTATUSCODE_USER_PRIVATE_MAX))) | |
147 // RFC 6455 limits applications to use reserved connection close code in | |
148 // section 7.4.2.. The WebSocket API (http://www.w3.org/TR/websockets/) | |
149 // defines this out of range error as InvalidAccessError in JavaScript. | |
150 return PP_ERROR_NOACCESS; | |
151 | |
152 // |reason| must be ignored if it is PP_VARTYPE_UNDEFINED or |code| is | |
153 // PP_WEBSOCKETSTATUSCODE_NOT_SPECIFIED. | |
154 if (reason.type != PP_VARTYPE_UNDEFINED) { | |
155 // Validate |reason|. | |
156 reason_string_var = StringVar::FromPPVar(reason); | |
157 if (!reason_string_var || | |
158 reason_string_var->value().size() > kMaxReasonSizeInBytes) | |
159 return PP_ERROR_BADARGUMENT; | |
160 reason_string = reason_string_var->value(); | |
161 } | |
162 } | |
163 | |
164 // Check state. | |
165 if (state_ == PP_WEBSOCKETREADYSTATE_CLOSING) | |
166 return PP_ERROR_INPROGRESS; | |
167 if (state_ == PP_WEBSOCKETREADYSTATE_CLOSED) | |
168 return PP_OK; | |
169 | |
170 // Install |callback|. | |
171 close_callback_ = callback; | |
172 | |
173 // Abort ongoing connect. | |
174 if (connect_callback_) { | |
175 state_ = PP_WEBSOCKETREADYSTATE_CLOSING; | |
176 // Need to do a "Post" to avoid reentering the plugin. | |
177 connect_callback_->PostAbort(); | |
178 connect_callback_ = NULL; | |
179 CallRenderer(PpapiHostMsg_WebSocket_Fail( | |
180 "WebSocket was closed before the connection was established.")); | |
181 return PP_OK_COMPLETIONPENDING; | |
182 } | |
183 | |
184 // Abort ongoing receive. | |
185 if (receive_callback_) { | |
186 receive_callback_var_ = NULL; | |
187 // Need to do a "Post" to avoid reentering the plugin. | |
188 receive_callback_->PostAbort(); | |
189 receive_callback_ = NULL; | |
190 } | |
191 | |
192 // Close connection. | |
193 state_ = PP_WEBSOCKETREADYSTATE_CLOSING; | |
194 CallRenderer(PpapiHostMsg_WebSocket_Close(static_cast<int32_t>(event_code), | |
195 reason_string)); | |
196 return PP_OK_COMPLETIONPENDING; | |
197 } | |
198 | |
199 int32_t WebSocketResource::ReceiveMessage( | |
200 PP_Var* message, | |
201 scoped_refptr<TrackedCallback> callback) { | |
202 if (TrackedCallback::IsPending(receive_callback_)) | |
203 return PP_ERROR_INPROGRESS; | |
204 | |
205 // Check state. | |
206 if (state_ == PP_WEBSOCKETREADYSTATE_INVALID || | |
207 state_ == PP_WEBSOCKETREADYSTATE_CONNECTING) | |
208 return PP_ERROR_BADARGUMENT; | |
209 | |
210 // Just return received message if any received message is queued. | |
211 if (!received_messages_.empty()) { | |
212 receive_callback_var_ = message; | |
213 return DoReceive(); | |
214 } | |
215 | |
216 // Check state again. In CLOSED state, no more messages will be received. | |
217 if (state_ == PP_WEBSOCKETREADYSTATE_CLOSED) | |
218 return PP_ERROR_BADARGUMENT; | |
219 | |
220 // Returns PP_ERROR_FAILED after an error is received and received messages | |
221 // is exhausted. | |
222 if (error_was_received_) | |
223 return PP_ERROR_FAILED; | |
224 | |
225 // Or retain |message| as buffer to store and install |callback|. | |
226 receive_callback_var_ = message; | |
227 receive_callback_ = callback; | |
228 | |
229 return PP_OK_COMPLETIONPENDING; | |
230 } | |
231 | |
232 int32_t WebSocketResource::SendMessage(const PP_Var& message) { | |
233 // Check state. | |
234 if (state_ == PP_WEBSOCKETREADYSTATE_INVALID || | |
235 state_ == PP_WEBSOCKETREADYSTATE_CONNECTING) | |
236 return PP_ERROR_BADARGUMENT; | |
237 | |
238 if (state_ == PP_WEBSOCKETREADYSTATE_CLOSING || | |
239 state_ == PP_WEBSOCKETREADYSTATE_CLOSED) { | |
240 // Handle buffered_amount_after_close_. | |
241 uint64_t payload_size = 0; | |
242 if (message.type == PP_VARTYPE_STRING) { | |
243 scoped_refptr<StringVar> message_string = StringVar::FromPPVar(message); | |
244 if (message_string) | |
245 payload_size += message_string->value().length(); | |
246 } else if (message.type == PP_VARTYPE_ARRAY_BUFFER) { | |
247 scoped_refptr<ArrayBufferVar> message_array_buffer = | |
248 ArrayBufferVar::FromPPVar(message); | |
249 if (message_array_buffer) | |
250 payload_size += message_array_buffer->ByteLength(); | |
251 } else { | |
252 // TODO(toyoshim): Support Blob. | |
253 return PP_ERROR_NOTSUPPORTED; | |
254 } | |
255 | |
256 buffered_amount_after_close_ = | |
257 SaturateAdd(buffered_amount_after_close_, GetFrameSize(payload_size)); | |
258 | |
259 return PP_ERROR_FAILED; | |
260 } | |
261 | |
262 // Send the message. | |
263 if (message.type == PP_VARTYPE_STRING) { | |
264 // Convert message to std::string, then send it. | |
265 scoped_refptr<StringVar> message_string = StringVar::FromPPVar(message); | |
266 if (!message_string) | |
267 return PP_ERROR_BADARGUMENT; | |
268 CallRenderer(PpapiHostMsg_WebSocket_SendText(message_string->value())); | |
269 } else if (message.type == PP_VARTYPE_ARRAY_BUFFER) { | |
270 // Convert message to std::vector<uint8_t>, then send it. | |
271 scoped_refptr<ArrayBufferVar> message_arraybuffer = | |
272 ArrayBufferVar::FromPPVar(message); | |
273 if (!message_arraybuffer) | |
274 return PP_ERROR_BADARGUMENT; | |
275 uint8_t* message_data = static_cast<uint8_t*>(message_arraybuffer->Map()); | |
276 uint32 message_length = message_arraybuffer->ByteLength(); | |
277 std::vector<uint8_t> message_vector(message_data, | |
278 message_data + message_length); | |
279 CallRenderer(PpapiHostMsg_WebSocket_SendBinary(message_vector)); | |
brettw
2012/10/03 20:30:24
For the cases where you don't need a callback, you
Takashi Toyoshima
2012/10/05 07:35:01
Thank you.
I misunderstood about params.has_callba
| |
280 } else { | |
281 // TODO(toyoshim): Support Blob. | |
282 return PP_ERROR_NOTSUPPORTED; | |
283 } | |
284 return PP_OK; | |
285 } | |
286 | |
287 uint64_t WebSocketResource::GetBufferedAmount() { | |
288 return SaturateAdd(buffered_amount_, buffered_amount_after_close_); | |
289 } | |
290 | |
291 uint16_t WebSocketResource::GetCloseCode() { | |
292 return close_code_; | |
293 } | |
294 | |
295 PP_Var WebSocketResource::GetCloseReason() { | |
296 if (!close_reason_) | |
297 return empty_string_->GetPPVar(); | |
298 return close_reason_->GetPPVar(); | |
299 } | |
300 | |
301 PP_Bool WebSocketResource::GetCloseWasClean() { | |
302 return close_was_clean_; | |
303 } | |
304 | |
305 PP_Var WebSocketResource::GetExtensions() { | |
306 return StringVar::StringToPPVar(std::string()); | |
307 } | |
308 | |
309 PP_Var WebSocketResource::GetProtocol() { | |
310 if (!protocol_) | |
311 return empty_string_->GetPPVar(); | |
312 return protocol_->GetPPVar(); | |
313 } | |
314 | |
315 PP_WebSocketReadyState WebSocketResource::GetReadyState() { | |
316 return state_; | |
317 } | |
318 | |
319 PP_Var WebSocketResource::GetURL() { | |
320 if (!url_) | |
321 return empty_string_->GetPPVar(); | |
322 return url_->GetPPVar(); | |
323 } | |
324 | |
325 void WebSocketResource::OnReplyReceived( | |
326 const ResourceMessageReplyParams& params, | |
327 const IPC::Message& msg) { | |
328 // TODO(toyoshim): DISPATCH_RESOURCE_REPLY will be replaced with other | |
329 // mechanisms. WebSocket dispatches IPC messages manually for now and must be | |
330 // replaced with the new one in the future. | |
331 switch (msg.type()) { | |
332 case PpapiPluginMsg_WebSocket_ConnectReply::ID: { | |
333 PpapiPluginMsg_WebSocket_ConnectReply::Schema::Param p; | |
334 if (PpapiPluginMsg_WebSocket_ConnectReply::Read(&msg, &p)) | |
335 OnPluginMsgConnectReply(params, p.a, p.b); | |
336 else // On error use default params and run the callback. | |
337 OnPluginMsgConnectReply(params, std::string(), std::string()); | |
338 break; | |
339 } | |
340 case PpapiPluginMsg_WebSocket_CloseReply::ID: { | |
341 PpapiPluginMsg_WebSocket_CloseReply::Schema::Param p; | |
342 if (PpapiPluginMsg_WebSocket_CloseReply::Read(&msg, &p)) | |
343 OnPluginMsgCloseReply(params, p.a, p.b, p.c, p.d); | |
344 else // On error use default params and run the callback. | |
345 OnPluginMsgCloseReply(params, 0, false, 0, std::string()); | |
346 break; | |
347 } | |
348 case PpapiPluginMsg_WebSocket_ReceiveTextReply::ID: { | |
349 PpapiPluginMsg_WebSocket_ReceiveTextReply::Schema::Param p; | |
350 if (PpapiPluginMsg_WebSocket_ReceiveTextReply::Read(&msg, &p)) | |
351 OnPluginMsgReceiveTextReply(params, p.a); | |
352 else | |
353 NOTREACHED(); | |
354 break; | |
355 } | |
356 case PpapiPluginMsg_WebSocket_ReceiveBinaryReply::ID: { | |
357 PpapiPluginMsg_WebSocket_ReceiveBinaryReply::Schema::Param p; | |
358 if (PpapiPluginMsg_WebSocket_ReceiveBinaryReply::Read(&msg, &p)) | |
359 OnPluginMsgReceiveBinaryReply(params, p.a); | |
360 else | |
361 NOTREACHED(); | |
362 break; | |
363 } | |
364 case PpapiPluginMsg_WebSocket_ErrorReply::ID: { | |
365 OnPluginMsgErrorReply(params); | |
366 break; | |
367 } | |
368 case PpapiPluginMsg_WebSocket_BufferedAmountReply::ID: { | |
369 PpapiPluginMsg_WebSocket_BufferedAmountReply::Schema::Param p; | |
370 if (PpapiPluginMsg_WebSocket_BufferedAmountReply::Read(&msg, &p)) | |
371 OnPluginMsgBufferedAmountReply(params, p.a); | |
372 else | |
373 NOTREACHED(); | |
374 break; | |
375 } | |
376 case PpapiPluginMsg_WebSocket_StateReply::ID: { | |
377 PpapiPluginMsg_WebSocket_StateReply::Schema::Param p; | |
378 if (PpapiPluginMsg_WebSocket_StateReply::Read(&msg, &p)) | |
379 OnPluginMsgStateReply(params, p.a); | |
380 else | |
381 NOTREACHED(); | |
382 break; | |
383 } | |
384 case PpapiPluginMsg_WebSocket_ClosedReply::ID: { | |
385 PpapiPluginMsg_WebSocket_ClosedReply::Schema::Param p; | |
386 if (PpapiPluginMsg_WebSocket_ClosedReply::Read(&msg, &p)) | |
387 OnPluginMsgClosedReply(params, p.a, p.b, p.c, p.d); | |
388 else | |
389 NOTREACHED(); | |
390 break; | |
391 } | |
392 default: | |
393 NOTREACHED(); | |
394 } | |
395 } | |
396 | |
397 void WebSocketResource::OnPluginMsgConnectReply( | |
398 const ResourceMessageReplyParams& params, | |
399 const std::string& url, | |
400 const std::string& protocol) { | |
401 if (!TrackedCallback::IsPending(connect_callback_)) | |
402 return; | |
403 | |
404 int32_t result = params.result(); | |
405 if (result == PP_OK) { | |
406 state_ = PP_WEBSOCKETREADYSTATE_OPEN; | |
407 protocol_ = new StringVar(protocol); | |
408 } else { | |
409 url_ = new StringVar(url); | |
410 } | |
411 if (result != PP_OK_COMPLETIONPENDING) | |
412 TrackedCallback::ClearAndRun(&connect_callback_, params.result()); | |
413 } | |
414 | |
415 void WebSocketResource::OnPluginMsgCloseReply( | |
416 const ResourceMessageReplyParams& params, | |
417 unsigned long buffered_amount, | |
418 bool was_clean, | |
419 unsigned short code, | |
420 const std::string& reason) { | |
421 // Set close related properties. | |
422 state_ = PP_WEBSOCKETREADYSTATE_CLOSED; | |
423 buffered_amount_ = buffered_amount; | |
424 close_was_clean_ = PP_FromBool(was_clean); | |
425 close_code_ = code; | |
426 close_reason_ = new StringVar(reason); | |
427 | |
428 if (TrackedCallback::IsPending(connect_callback_)) { | |
429 connect_callback_->PostRun(PP_ERROR_FAILED); | |
430 } | |
431 | |
432 if (TrackedCallback::IsPending(receive_callback_)) { | |
433 receive_callback_var_ = NULL; | |
434 receive_callback_->PostRun(PP_ERROR_FAILED); | |
435 } | |
436 | |
437 if (!TrackedCallback::IsPending(close_callback_)) | |
438 return; | |
439 | |
440 close_callback_->PostRun(params.result()); | |
441 } | |
442 | |
443 void WebSocketResource::OnPluginMsgReceiveTextReply( | |
444 const ResourceMessageReplyParams& params, | |
445 const std::string& message) { | |
446 // Dispose packets after receiving an error or in invalid state. | |
447 if (error_was_received_ || !InValidStateToReceive(state_)) | |
448 return; | |
449 | |
450 // Append received data to queue. | |
451 received_messages_.push(scoped_refptr<Var>(new StringVar(message))); | |
452 | |
453 if (!TrackedCallback::IsPending(receive_callback_)) | |
454 return; | |
455 | |
456 TrackedCallback::ClearAndRun(&receive_callback_, DoReceive()); | |
457 } | |
458 | |
459 void WebSocketResource::OnPluginMsgReceiveBinaryReply( | |
460 const ResourceMessageReplyParams& params, | |
461 const std::vector<uint8_t>& message) { | |
462 // Dispose packets after receiving an error or in invalid state. | |
463 if (error_was_received_ || !InValidStateToReceive(state_)) | |
464 return; | |
brettw
2012/10/03 20:30:24
Indenting is one space too much. This is also the
Takashi Toyoshima
2012/10/05 07:35:01
Oops.
Thank you for catching these nits.
| |
465 | |
466 // Append received data to queue. | |
467 scoped_refptr<Var> message_var(ArrayBufferVar::FromPPVar( | |
468 PpapiGlobals::Get()->GetVarTracker()->MakeArrayBufferPPVar( | |
469 message.size(), | |
470 &message.front()))); | |
471 received_messages_.push(message_var); | |
472 | |
473 if (!TrackedCallback::IsPending(receive_callback_)) | |
474 return; | |
475 | |
476 TrackedCallback::ClearAndRun(&receive_callback_, DoReceive()); | |
477 } | |
478 | |
479 void WebSocketResource::OnPluginMsgErrorReply( | |
480 const ResourceMessageReplyParams& params) { | |
481 } | |
482 | |
483 void WebSocketResource::OnPluginMsgBufferedAmountReply( | |
484 const ResourceMessageReplyParams& params, | |
485 unsigned long buffered_amount) { | |
486 buffered_amount_ = buffered_amount; | |
487 } | |
488 | |
489 void WebSocketResource::OnPluginMsgStateReply( | |
490 const ResourceMessageReplyParams& params, | |
491 int32_t state) { | |
492 state_ = static_cast<PP_WebSocketReadyState>(state); | |
493 } | |
494 | |
495 void WebSocketResource::OnPluginMsgClosedReply( | |
496 const ResourceMessageReplyParams& params, | |
497 unsigned long buffered_amount, | |
498 bool was_clean, | |
499 unsigned short code, | |
500 const std::string& reason) { | |
501 OnPluginMsgCloseReply(params, buffered_amount, was_clean, code, reason); | |
502 } | |
503 | |
504 int32_t WebSocketResource::DoReceive() { | |
505 if (!receive_callback_var_) | |
506 return PP_OK; | |
507 | |
508 *receive_callback_var_ = received_messages_.front()->GetPPVar(); | |
509 received_messages_.pop(); | |
510 receive_callback_var_ = NULL; | |
511 return PP_OK; | |
512 } | |
513 | |
514 } // namespace proxy | |
515 } // namespace ppapi | |
OLD | NEW |