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 blink::WebSocket::CloseEventCode event_code = 141 static_cast<blink::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 = blink::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