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 #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 
     28 uint64_t SaturateAdd(uint64_t a, uint64_t b) {
     29   if (kuint64max - a < b)
     30     return kuint64max;
     31   return a + b;
     32 }
     33 
     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 
     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 
     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 
     71 WebSocketResource::~WebSocketResource() {
     72 }
     73 
     74 thunk::PPB_WebSocket_API* WebSocketResource::AsPPB_WebSocket_API() {
     75   return this;
     76 }
     77 
     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 
    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   WebKit::WebSocket::CloseEventCode event_code =
    141       static_cast<WebKit::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 = WebKit::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 
    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 
    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 
    293 uint64_t WebSocketResource::GetBufferedAmount() {
    294   return SaturateAdd(buffered_amount_, buffered_amount_after_close_);
    295 }
    296 
    297 uint16_t WebSocketResource::GetCloseCode() {
    298   return close_code_;
    299 }
    300 
    301 PP_Var WebSocketResource::GetCloseReason() {
    302   if (!close_reason_.get())
    303     return empty_string_->GetPPVar();
    304   return close_reason_->GetPPVar();
    305 }
    306 
    307 PP_Bool WebSocketResource::GetCloseWasClean() {
    308   return close_was_clean_;
    309 }
    310 
    311 PP_Var WebSocketResource::GetExtensions() {
    312   return StringVar::StringToPPVar(std::string());
    313 }
    314 
    315 PP_Var WebSocketResource::GetProtocol() {
    316   if (!protocol_.get())
    317     return empty_string_->GetPPVar();
    318   return protocol_->GetPPVar();
    319 }
    320 
    321 PP_WebSocketReadyState WebSocketResource::GetReadyState() {
    322   return state_;
    323 }
    324 
    325 PP_Var WebSocketResource::GetURL() {
    326   if (!url_.get())
    327     return empty_string_->GetPPVar();
    328   return url_->GetPPVar();
    329 }
    330 
    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 
    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 
    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 
    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 
    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 
    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 
    462 void WebSocketResource::OnPluginMsgBufferedAmountReply(
    463     const ResourceMessageReplyParams& params,
    464     unsigned long buffered_amount) {
    465   buffered_amount_ = buffered_amount;
    466 }
    467 
    468 void WebSocketResource::OnPluginMsgStateReply(
    469     const ResourceMessageReplyParams& params,
    470     int32_t state) {
    471   state_ = static_cast<PP_WebSocketReadyState>(state);
    472 }
    473 
    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 
    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