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