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