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