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