Home | History | Annotate | Download | only in proxy
      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 
     27 uint64_t SaturateAdd(uint64_t a, uint64_t b) {
     28   if (kuint64max - a < b)
     29     return kuint64max;
     30   return a + b;
     31 }
     32 
     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 
     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 
     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 
     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     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 
    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 
    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 
    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 
    285 uint64_t WebSocketResource::GetBufferedAmount() {
    286   return SaturateAdd(buffered_amount_, buffered_amount_after_close_);
    287 }
    288 
    289 uint16_t WebSocketResource::GetCloseCode() {
    290   return close_code_;
    291 }
    292 
    293 PP_Var WebSocketResource::GetCloseReason() {
    294   if (!close_reason_.get())
    295     return empty_string_->GetPPVar();
    296   return close_reason_->GetPPVar();
    297 }
    298 
    299 PP_Bool WebSocketResource::GetCloseWasClean() {
    300   return close_was_clean_;
    301 }
    302 
    303 PP_Var WebSocketResource::GetExtensions() {
    304   return StringVar::StringToPPVar(std::string());
    305 }
    306 
    307 PP_Var WebSocketResource::GetProtocol() {
    308   if (!protocol_.get())
    309     return empty_string_->GetPPVar();
    310   return protocol_->GetPPVar();
    311 }
    312 
    313 PP_WebSocketReadyState WebSocketResource::GetReadyState() {
    314   return state_;
    315 }
    316 
    317 PP_Var WebSocketResource::GetURL() {
    318   if (!url_.get())
    319     return empty_string_->GetPPVar();
    320   return url_->GetPPVar();
    321 }
    322 
    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 
    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 
    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 
    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 
    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 
    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 
    454 void WebSocketResource::OnPluginMsgBufferedAmountReply(
    455     const ResourceMessageReplyParams& params,
    456     unsigned long buffered_amount) {
    457   buffered_amount_ = buffered_amount;
    458 }
    459 
    460 void WebSocketResource::OnPluginMsgStateReply(
    461     const ResourceMessageReplyParams& params,
    462     int32_t state) {
    463   state_ = static_cast<PP_WebSocketReadyState>(state);
    464 }
    465 
    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 
    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