diff options
author | toyoshim@chromium.org <toyoshim@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-10-09 03:43:48 +0000 |
---|---|---|
committer | toyoshim@chromium.org <toyoshim@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-10-09 03:43:48 +0000 |
commit | 9d5eadf3af4cdd5cb2fd9bce44b87532e919ec82 (patch) | |
tree | d441385646386a78ec7adf995b6965081c7bfbd6 /ppapi/proxy | |
parent | 7f06df0de9f7d7ee95d8205be4cff7a7617813e7 (diff) | |
download | chromium_src-9d5eadf3af4cdd5cb2fd9bce44b87532e919ec82.zip chromium_src-9d5eadf3af4cdd5cb2fd9bce44b87532e919ec82.tar.gz chromium_src-9d5eadf3af4cdd5cb2fd9bce44b87532e919ec82.tar.bz2 |
Pepper WebSocket API: Implement new design Chrome IPC.
This change implements new Chrome IPC for PPB_WebSocket.
After this change, all mode including out of process
will work with new design. It doesn't depend on old SRPC design any more.
BUG=87310,116317
Review URL: https://chromiumcodereview.appspot.com/10944005
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@160783 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'ppapi/proxy')
-rw-r--r-- | ppapi/proxy/plugin_dispatcher.cc | 3 | ||||
-rw-r--r-- | ppapi/proxy/ppapi_messages.h | 80 | ||||
-rw-r--r-- | ppapi/proxy/resource_creation_proxy.cc | 10 | ||||
-rw-r--r-- | ppapi/proxy/resource_creation_proxy.h | 2 | ||||
-rw-r--r-- | ppapi/proxy/websocket_resource.cc | 509 | ||||
-rw-r--r-- | ppapi/proxy/websocket_resource.h | 157 | ||||
-rw-r--r-- | ppapi/proxy/websocket_resource_unittest.cc | 168 |
7 files changed, 922 insertions, 7 deletions
diff --git a/ppapi/proxy/plugin_dispatcher.cc b/ppapi/proxy/plugin_dispatcher.cc index 51a8ff2..51272fd 100644 --- a/ppapi/proxy/plugin_dispatcher.cc +++ b/ppapi/proxy/plugin_dispatcher.cc @@ -272,7 +272,8 @@ void PluginDispatcher::DispatchResourceReply( Resource* resource = PpapiGlobals::Get()->GetResourceTracker()->GetResource( reply_params.pp_resource()); if (!resource) { - NOTREACHED(); + if (reply_params.sequence()) + NOTREACHED(); return; } resource->OnReplyReceived(reply_params, nested_msg); diff --git a/ppapi/proxy/ppapi_messages.h b/ppapi/proxy/ppapi_messages.h index 8ac0886..3e9dca2 100644 --- a/ppapi/proxy/ppapi_messages.h +++ b/ppapi/proxy/ppapi_messages.h @@ -1582,3 +1582,83 @@ IPC_MESSAGE_CONTROL0(PpapiHostMsg_Printing_Create) IPC_MESSAGE_CONTROL0(PpapiHostMsg_Printing_GetDefaultPrintSettings) IPC_MESSAGE_CONTROL1(PpapiPluginMsg_Printing_GetDefaultPrintSettingsReply, PP_PrintSettings_Dev /* print_settings */) + +// WebSocket ------------------------------------------------------------------ + +IPC_MESSAGE_CONTROL0(PpapiHostMsg_WebSocket_Create) + +// Establishes the connection to a server. This message requires +// WebSocket_ConnectReply as a reply message. +IPC_MESSAGE_CONTROL2(PpapiHostMsg_WebSocket_Connect, + std::string /* url */, + std::vector<std::string> /* protocols */) + +// Closes established connection with graceful closing handshake. This message +// requires WebSocket_CloseReply as a reply message. +IPC_MESSAGE_CONTROL2(PpapiHostMsg_WebSocket_Close, + int32_t /* code */, + std::string /* reason */) + +// Sends a text frame to the server. No reply is defined. +IPC_MESSAGE_CONTROL1(PpapiHostMsg_WebSocket_SendText, + std::string /* message */) + +// Sends a binary frame to the server. No reply is defined. +IPC_MESSAGE_CONTROL1(PpapiHostMsg_WebSocket_SendBinary, + std::vector<uint8_t> /* message */) + +// Fails the connection. This message invokes RFC6455 defined +// _Fail the WebSocket Connection_ operation. No reply is defined. +IPC_MESSAGE_CONTROL1(PpapiHostMsg_WebSocket_Fail, + std::string /* message */) + +// This message is a reply to WebSocket_Connect. If the |url| and |protocols| +// are invalid, WebSocket_ConnectReply is issued immediately and it contains +// proper error code in its result. Otherwise, WebSocket_ConnectReply is sent +// with valid |url|, |protocol|, and result PP_OK. |protocol| is not a passed +// |protocols|, but a result of opening handshake negotiation. If the +// connection can not be established successfully, WebSocket_ConnectReply is +// not issued, but WebSocket_ClosedReply is sent instead. +IPC_MESSAGE_CONTROL2(PpapiPluginMsg_WebSocket_ConnectReply, + std::string /* url */, + std::string /* protocol */) + +// This message is a reply to WebSocket_Close. If the operation fails, +// WebSocket_CloseReply is issued immediately and it contains PP_ERROR_FAILED. +// Otherwise, CloseReply will be issued after the closing handshake is +// finished. All arguments will be valid iff the result is PP_OK and it means +// that the client initiated closing handshake is finished gracefully. +IPC_MESSAGE_CONTROL4(PpapiPluginMsg_WebSocket_CloseReply, + unsigned long /* buffered_amount */, + bool /* was_clean */, + unsigned short /* code */, + std::string /* reason */) + +// Unsolicited reply message to transmit a receiving text frame. +IPC_MESSAGE_CONTROL1(PpapiPluginMsg_WebSocket_ReceiveTextReply, + std::string /* message */) + +// Unsolicited reply message to transmit a receiving binary frame. +IPC_MESSAGE_CONTROL1(PpapiPluginMsg_WebSocket_ReceiveBinaryReply, + std::vector<uint8_t> /* message */) + +// Unsolicited reply message to notify a error on underlying network connetion. +IPC_MESSAGE_CONTROL0(PpapiPluginMsg_WebSocket_ErrorReply) + +// Unsolicited reply message to update the buffered amount value. +IPC_MESSAGE_CONTROL1(PpapiPluginMsg_WebSocket_BufferedAmountReply, + unsigned long /* buffered_amount */) + +// Unsolicited reply message to update |state| because of incoming external +// events, e.g., protocol error, or unexpected network closure. +IPC_MESSAGE_CONTROL1(PpapiPluginMsg_WebSocket_StateReply, + int32_t /* state */) + +// Unsolicited reply message to notify that the connection is closed without +// any WebSocket_Close request. Server initiated closing handshake or +// unexpected network errors will invoke this message. +IPC_MESSAGE_CONTROL4(PpapiPluginMsg_WebSocket_ClosedReply, + unsigned long /* buffered_amount */, + bool /* was_clean */, + unsigned short /* code */, + std::string /* reason */) diff --git a/ppapi/proxy/resource_creation_proxy.cc b/ppapi/proxy/resource_creation_proxy.cc index 373b9d7..78123a6 100644 --- a/ppapi/proxy/resource_creation_proxy.cc +++ b/ppapi/proxy/resource_creation_proxy.cc @@ -39,6 +39,7 @@ #include "ppapi/proxy/ppb_x509_certificate_private_proxy.h" #include "ppapi/proxy/printing_resource.h" #include "ppapi/proxy/url_request_info_resource.h" +#include "ppapi/proxy/websocket_resource.h" #include "ppapi/shared_impl/api_id.h" #include "ppapi/shared_impl/host_resource.h" #include "ppapi/shared_impl/ppb_audio_config_shared.h" @@ -256,6 +257,10 @@ PP_Resource ResourceCreationProxy::CreateUDPSocketPrivate( return PPB_UDPSocket_Private_Proxy::CreateProxyResource(instance); } +PP_Resource ResourceCreationProxy::CreateWebSocket(PP_Instance instance) { + return (new WebSocketResource(GetConnection(), instance))->GetReference(); +} + PP_Resource ResourceCreationProxy::CreateX509CertificatePrivate( PP_Instance instance) { return PPB_X509Certificate_Private_Proxy::CreateProxyResource(instance); @@ -351,11 +356,6 @@ PP_Resource ResourceCreationProxy::CreateVideoDecoder( instance, context3d_id, profile); } -PP_Resource ResourceCreationProxy::CreateWebSocket(PP_Instance instance) { - NOTIMPLEMENTED(); - return 0; -} - #endif // !defined(OS_NACL) bool ResourceCreationProxy::Send(IPC::Message* msg) { diff --git a/ppapi/proxy/resource_creation_proxy.h b/ppapi/proxy/resource_creation_proxy.h index 7ab07e0..4860cbf 100644 --- a/ppapi/proxy/resource_creation_proxy.h +++ b/ppapi/proxy/resource_creation_proxy.h @@ -122,6 +122,7 @@ class ResourceCreationProxy : public InterfaceProxy, PP_Instance instance) OVERRIDE; virtual PP_Resource CreateTCPSocketPrivate(PP_Instance instance) OVERRIDE; virtual PP_Resource CreateUDPSocketPrivate(PP_Instance instance) OVERRIDE; + virtual PP_Resource CreateWebSocket(PP_Instance instance) OVERRIDE; virtual PP_Resource CreateX509CertificatePrivate( PP_Instance instance) OVERRIDE; #if !defined(OS_NACL) @@ -155,7 +156,6 @@ class ResourceCreationProxy : public InterfaceProxy, PP_Instance instance, PP_Resource context3d_id, PP_VideoDecoder_Profile profile) OVERRIDE; - virtual PP_Resource CreateWebSocket(PP_Instance instance) OVERRIDE; #endif // !defined(OS_NACL) virtual bool Send(IPC::Message* msg) OVERRIDE; diff --git a/ppapi/proxy/websocket_resource.cc b/ppapi/proxy/websocket_resource.cc new file mode 100644 index 0000000..de9ee2b --- /dev/null +++ b/ppapi/proxy/websocket_resource.cc @@ -0,0 +1,509 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ppapi/proxy/websocket_resource.h" + +#include <set> +#include <vector> + +#include "base/bind.h" +#include "ppapi/c/pp_errors.h" +#include "ppapi/proxy/ppapi_messages.h" +#include "ppapi/shared_impl/ppapi_globals.h" +#include "ppapi/shared_impl/var.h" +#include "ppapi/shared_impl/var_tracker.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebSocket.h" + +namespace { + +const uint32_t kMaxReasonSizeInBytes = 123; +const size_t kBaseFramingOverhead = 2; +const size_t kMaskingKeyLength = 4; +const size_t kMinimumPayloadSizeWithTwoByteExtendedPayloadLength = 126; +const size_t kMinimumPayloadSizeWithEightByteExtendedPayloadLength = 0x10000; + +uint64_t SaturateAdd(uint64_t a, uint64_t b) { + if (kuint64max - a < b) + return kuint64max; + return a + b; +} + +uint64_t GetFrameSize(uint64_t payload_size) { + uint64_t overhead = kBaseFramingOverhead + kMaskingKeyLength; + if (payload_size > kMinimumPayloadSizeWithEightByteExtendedPayloadLength) + overhead += 8; + else if (payload_size > kMinimumPayloadSizeWithTwoByteExtendedPayloadLength) + overhead += 2; + return SaturateAdd(payload_size, overhead); +} + +bool InValidStateToReceive(PP_WebSocketReadyState state) { + return state == PP_WEBSOCKETREADYSTATE_OPEN || + state == PP_WEBSOCKETREADYSTATE_CLOSING; +} + +} // namespace + + +namespace ppapi { +namespace proxy { + +WebSocketResource::WebSocketResource(Connection connection, + PP_Instance instance) + : PluginResource(connection, instance), + state_(PP_WEBSOCKETREADYSTATE_INVALID), + error_was_received_(false), + receive_callback_var_(NULL), + empty_string_(new StringVar(std::string())), + close_code_(0), + close_reason_(NULL), + close_was_clean_(PP_FALSE), + extensions_(NULL), + protocol_(NULL), + url_(NULL), + buffered_amount_(0), + buffered_amount_after_close_(0) { +} + +WebSocketResource::~WebSocketResource() { +} + +thunk::PPB_WebSocket_API* WebSocketResource::AsPPB_WebSocket_API() { + return this; +} + +int32_t WebSocketResource::Connect( + const PP_Var& url, + const PP_Var protocols[], + uint32_t protocol_count, + scoped_refptr<TrackedCallback> callback) { + if (TrackedCallback::IsPending(connect_callback_)) + return PP_ERROR_INPROGRESS; + + // Connect() can be called at most once. + if (state_ != PP_WEBSOCKETREADYSTATE_INVALID) + return PP_ERROR_INPROGRESS; + state_ = PP_WEBSOCKETREADYSTATE_CLOSED; + + // Get the URL. + url_ = StringVar::FromPPVar(url); + if (!url_) + return PP_ERROR_BADARGUMENT; + + // Get the protocols. + std::set<std::string> protocol_set; + std::vector<std::string> protocol_strings; + protocol_strings.reserve(protocol_count); + for (uint32_t i = 0; i < protocol_count; ++i) { + scoped_refptr<StringVar> protocol(StringVar::FromPPVar(protocols[i])); + + // Check invalid and empty entries. + if (!protocol || !protocol->value().length()) + return PP_ERROR_BADARGUMENT; + + // Check duplicated protocol entries. + if (protocol_set.find(protocol->value()) != protocol_set.end()) + return PP_ERROR_BADARGUMENT; + protocol_set.insert(protocol->value()); + + protocol_strings.push_back(protocol->value()); + } + + // Install callback. + connect_callback_ = callback; + + // Create remote host in the renderer, then request to check the URL and + // establish the connection. + state_ = PP_WEBSOCKETREADYSTATE_CONNECTING; + SendCreateToRenderer(PpapiHostMsg_WebSocket_Create()); + PpapiHostMsg_WebSocket_Connect msg(url_->value(), protocol_strings); + CallRenderer<PpapiPluginMsg_WebSocket_ConnectReply>(msg, + base::Bind(&WebSocketResource::OnPluginMsgConnectReply, this)); + + return PP_OK_COMPLETIONPENDING; +} + +int32_t WebSocketResource::Close(uint16_t code, + const PP_Var& reason, + scoped_refptr<TrackedCallback> callback) { + if (TrackedCallback::IsPending(close_callback_)) + return PP_ERROR_INPROGRESS; + if (state_ == PP_WEBSOCKETREADYSTATE_INVALID) + return PP_ERROR_FAILED; + + // Validate |code| and |reason|. + scoped_refptr<StringVar> reason_string_var; + std::string reason_string; + WebKit::WebSocket::CloseEventCode event_code = + static_cast<WebKit::WebSocket::CloseEventCode>(code); + if (code == PP_WEBSOCKETSTATUSCODE_NOT_SPECIFIED) { + // PP_WEBSOCKETSTATUSCODE_NOT_SPECIFIED and CloseEventCodeNotSpecified are + // assigned to different values. A conversion is needed if + // PP_WEBSOCKETSTATUSCODE_NOT_SPECIFIED is specified. + event_code = WebKit::WebSocket::CloseEventCodeNotSpecified; + } else { + if (!(code == PP_WEBSOCKETSTATUSCODE_NORMAL_CLOSURE || + (PP_WEBSOCKETSTATUSCODE_USER_REGISTERED_MIN <= code && + code <= PP_WEBSOCKETSTATUSCODE_USER_PRIVATE_MAX))) + // RFC 6455 limits applications to use reserved connection close code in + // section 7.4.2.. The WebSocket API (http://www.w3.org/TR/websockets/) + // defines this out of range error as InvalidAccessError in JavaScript. + return PP_ERROR_NOACCESS; + + // |reason| must be ignored if it is PP_VARTYPE_UNDEFINED or |code| is + // PP_WEBSOCKETSTATUSCODE_NOT_SPECIFIED. + if (reason.type != PP_VARTYPE_UNDEFINED) { + // Validate |reason|. + reason_string_var = StringVar::FromPPVar(reason); + if (!reason_string_var || + reason_string_var->value().size() > kMaxReasonSizeInBytes) + return PP_ERROR_BADARGUMENT; + reason_string = reason_string_var->value(); + } + } + + // Check state. + if (state_ == PP_WEBSOCKETREADYSTATE_CLOSING) + return PP_ERROR_INPROGRESS; + if (state_ == PP_WEBSOCKETREADYSTATE_CLOSED) + return PP_OK; + + // Install |callback|. + close_callback_ = callback; + + // Abort ongoing connect. + if (TrackedCallback::IsPending(connect_callback_)) { + state_ = PP_WEBSOCKETREADYSTATE_CLOSING; + // Need to do a "Post" to avoid reentering the plugin. + connect_callback_->PostAbort(); + connect_callback_ = NULL; + PostToRenderer(PpapiHostMsg_WebSocket_Fail( + "WebSocket was closed before the connection was established.")); + return PP_OK_COMPLETIONPENDING; + } + + // Abort ongoing receive. + if (TrackedCallback::IsPending(receive_callback_)) { + receive_callback_var_ = NULL; + // Need to do a "Post" to avoid reentering the plugin. + receive_callback_->PostAbort(); + receive_callback_ = NULL; + } + + // Close connection. + state_ = PP_WEBSOCKETREADYSTATE_CLOSING; + PpapiHostMsg_WebSocket_Close msg(static_cast<int32_t>(event_code), + reason_string); + CallRenderer<PpapiPluginMsg_WebSocket_CloseReply>(msg, + base::Bind(&WebSocketResource::OnPluginMsgCloseReply, this)); + return PP_OK_COMPLETIONPENDING; +} + +int32_t WebSocketResource::ReceiveMessage( + PP_Var* message, + scoped_refptr<TrackedCallback> callback) { + if (TrackedCallback::IsPending(receive_callback_)) + return PP_ERROR_INPROGRESS; + + // Check state. + if (state_ == PP_WEBSOCKETREADYSTATE_INVALID || + state_ == PP_WEBSOCKETREADYSTATE_CONNECTING) + return PP_ERROR_BADARGUMENT; + + // Just return received message if any received message is queued. + if (!received_messages_.empty()) { + receive_callback_var_ = message; + return DoReceive(); + } + + // Check state again. In CLOSED state, no more messages will be received. + if (state_ == PP_WEBSOCKETREADYSTATE_CLOSED) + return PP_ERROR_BADARGUMENT; + + // Returns PP_ERROR_FAILED after an error is received and received messages + // is exhausted. + if (error_was_received_) + return PP_ERROR_FAILED; + + // Or retain |message| as buffer to store and install |callback|. + receive_callback_var_ = message; + receive_callback_ = callback; + + return PP_OK_COMPLETIONPENDING; +} + +int32_t WebSocketResource::SendMessage(const PP_Var& message) { + // Check state. + if (state_ == PP_WEBSOCKETREADYSTATE_INVALID || + state_ == PP_WEBSOCKETREADYSTATE_CONNECTING) + return PP_ERROR_BADARGUMENT; + + if (state_ == PP_WEBSOCKETREADYSTATE_CLOSING || + state_ == PP_WEBSOCKETREADYSTATE_CLOSED) { + // Handle buffered_amount_after_close_. + uint64_t payload_size = 0; + if (message.type == PP_VARTYPE_STRING) { + scoped_refptr<StringVar> message_string = StringVar::FromPPVar(message); + if (message_string) + payload_size += message_string->value().length(); + } else if (message.type == PP_VARTYPE_ARRAY_BUFFER) { + scoped_refptr<ArrayBufferVar> message_array_buffer = + ArrayBufferVar::FromPPVar(message); + if (message_array_buffer) + payload_size += message_array_buffer->ByteLength(); + } else { + // TODO(toyoshim): Support Blob. + return PP_ERROR_NOTSUPPORTED; + } + + buffered_amount_after_close_ = + SaturateAdd(buffered_amount_after_close_, GetFrameSize(payload_size)); + + return PP_ERROR_FAILED; + } + + // Send the message. + if (message.type == PP_VARTYPE_STRING) { + // Convert message to std::string, then send it. + scoped_refptr<StringVar> message_string = StringVar::FromPPVar(message); + if (!message_string) + return PP_ERROR_BADARGUMENT; + PostToRenderer(PpapiHostMsg_WebSocket_SendText(message_string->value())); + } else if (message.type == PP_VARTYPE_ARRAY_BUFFER) { + // Convert message to std::vector<uint8_t>, then send it. + scoped_refptr<ArrayBufferVar> message_arraybuffer = + ArrayBufferVar::FromPPVar(message); + if (!message_arraybuffer) + return PP_ERROR_BADARGUMENT; + uint8_t* message_data = static_cast<uint8_t*>(message_arraybuffer->Map()); + uint32 message_length = message_arraybuffer->ByteLength(); + std::vector<uint8_t> message_vector(message_data, + message_data + message_length); + PostToRenderer(PpapiHostMsg_WebSocket_SendBinary(message_vector)); + } else { + // TODO(toyoshim): Support Blob. + return PP_ERROR_NOTSUPPORTED; + } + return PP_OK; +} + +uint64_t WebSocketResource::GetBufferedAmount() { + return SaturateAdd(buffered_amount_, buffered_amount_after_close_); +} + +uint16_t WebSocketResource::GetCloseCode() { + return close_code_; +} + +PP_Var WebSocketResource::GetCloseReason() { + if (!close_reason_) + return empty_string_->GetPPVar(); + return close_reason_->GetPPVar(); +} + +PP_Bool WebSocketResource::GetCloseWasClean() { + return close_was_clean_; +} + +PP_Var WebSocketResource::GetExtensions() { + return StringVar::StringToPPVar(std::string()); +} + +PP_Var WebSocketResource::GetProtocol() { + if (!protocol_) + return empty_string_->GetPPVar(); + return protocol_->GetPPVar(); +} + +PP_WebSocketReadyState WebSocketResource::GetReadyState() { + return state_; +} + +PP_Var WebSocketResource::GetURL() { + if (!url_) + return empty_string_->GetPPVar(); + return url_->GetPPVar(); +} + +void WebSocketResource::OnReplyReceived( + const ResourceMessageReplyParams& params, + const IPC::Message& msg) { + if (params.sequence()) + return PluginResource::OnReplyReceived(params, msg); + + // TODO(toyoshim): Currently, following unsolicited reply IPCs are handled + // manually. We should introduce more useful mechanism for that. + switch (msg.type()) { + case PpapiPluginMsg_WebSocket_ReceiveTextReply::ID: { + PpapiPluginMsg_WebSocket_ReceiveTextReply::Schema::Param p; + if (PpapiPluginMsg_WebSocket_ReceiveTextReply::Read(&msg, &p)) + OnPluginMsgReceiveTextReply(params, p.a); + else + NOTREACHED(); + break; + } + case PpapiPluginMsg_WebSocket_ReceiveBinaryReply::ID: { + PpapiPluginMsg_WebSocket_ReceiveBinaryReply::Schema::Param p; + if (PpapiPluginMsg_WebSocket_ReceiveBinaryReply::Read(&msg, &p)) + OnPluginMsgReceiveBinaryReply(params, p.a); + else + NOTREACHED(); + break; + } + case PpapiPluginMsg_WebSocket_ErrorReply::ID: { + OnPluginMsgErrorReply(params); + break; + } + case PpapiPluginMsg_WebSocket_BufferedAmountReply::ID: { + PpapiPluginMsg_WebSocket_BufferedAmountReply::Schema::Param p; + if (PpapiPluginMsg_WebSocket_BufferedAmountReply::Read(&msg, &p)) + OnPluginMsgBufferedAmountReply(params, p.a); + else + NOTREACHED(); + break; + } + case PpapiPluginMsg_WebSocket_StateReply::ID: { + PpapiPluginMsg_WebSocket_StateReply::Schema::Param p; + if (PpapiPluginMsg_WebSocket_StateReply::Read(&msg, &p)) + OnPluginMsgStateReply(params, p.a); + else + NOTREACHED(); + break; + } + case PpapiPluginMsg_WebSocket_ClosedReply::ID: { + PpapiPluginMsg_WebSocket_ClosedReply::Schema::Param p; + if (PpapiPluginMsg_WebSocket_ClosedReply::Read(&msg, &p)) + OnPluginMsgClosedReply(params, p.a, p.b, p.c, p.d); + else + NOTREACHED(); + break; + } + default: + NOTREACHED(); + } +} + +void WebSocketResource::OnPluginMsgConnectReply( + const ResourceMessageReplyParams& params, + const std::string& url, + const std::string& protocol) { + if (!TrackedCallback::IsPending(connect_callback_)) + return; + + int32_t result = params.result(); + if (result == PP_OK) { + state_ = PP_WEBSOCKETREADYSTATE_OPEN; + protocol_ = new StringVar(protocol); + url_ = new StringVar(url); + } + TrackedCallback::ClearAndRun(&connect_callback_, params.result()); +} + +void WebSocketResource::OnPluginMsgCloseReply( + const ResourceMessageReplyParams& params, + unsigned long buffered_amount, + bool was_clean, + unsigned short code, + const std::string& reason) { + // Set close related properties. + state_ = PP_WEBSOCKETREADYSTATE_CLOSED; + buffered_amount_ = buffered_amount; + close_was_clean_ = PP_FromBool(was_clean); + close_code_ = code; + close_reason_ = new StringVar(reason); + + if (TrackedCallback::IsPending(receive_callback_)) { + receive_callback_var_ = NULL; + receive_callback_->PostRun(PP_ERROR_FAILED); + receive_callback_ = NULL; + } + + if (TrackedCallback::IsPending(close_callback_)) { + close_callback_->PostRun(params.result()); + close_callback_ = NULL; + } +} + +void WebSocketResource::OnPluginMsgReceiveTextReply( + const ResourceMessageReplyParams& params, + const std::string& message) { + // Dispose packets after receiving an error or in invalid state. + if (error_was_received_ || !InValidStateToReceive(state_)) + return; + + // Append received data to queue. + received_messages_.push(scoped_refptr<Var>(new StringVar(message))); + + if (!TrackedCallback::IsPending(receive_callback_)) + return; + + TrackedCallback::ClearAndRun(&receive_callback_, DoReceive()); +} + +void WebSocketResource::OnPluginMsgReceiveBinaryReply( + const ResourceMessageReplyParams& params, + const std::vector<uint8_t>& message) { + // Dispose packets after receiving an error or in invalid state. + if (error_was_received_ || !InValidStateToReceive(state_)) + return; + + // Append received data to queue. + scoped_refptr<Var> message_var(ArrayBufferVar::FromPPVar( + PpapiGlobals::Get()->GetVarTracker()->MakeArrayBufferPPVar( + message.size(), + &message.front()))); + received_messages_.push(message_var); + + if (!TrackedCallback::IsPending(receive_callback_)) + return; + + TrackedCallback::ClearAndRun(&receive_callback_, DoReceive()); +} + +void WebSocketResource::OnPluginMsgErrorReply( + const ResourceMessageReplyParams& params) { + error_was_received_ = true; + + if (!TrackedCallback::IsPending(receive_callback_)) + return; + + // No more text or binary messages will be received. If there is ongoing + // ReceiveMessage(), we must invoke the callback with error code here. + receive_callback_var_ = NULL; + TrackedCallback::ClearAndRun(&receive_callback_, PP_ERROR_FAILED); +} + +void WebSocketResource::OnPluginMsgBufferedAmountReply( + const ResourceMessageReplyParams& params, + unsigned long buffered_amount) { + buffered_amount_ = buffered_amount; +} + +void WebSocketResource::OnPluginMsgStateReply( + const ResourceMessageReplyParams& params, + int32_t state) { + state_ = static_cast<PP_WebSocketReadyState>(state); +} + +void WebSocketResource::OnPluginMsgClosedReply( + const ResourceMessageReplyParams& params, + unsigned long buffered_amount, + bool was_clean, + unsigned short code, + const std::string& reason) { + OnPluginMsgCloseReply(params, buffered_amount, was_clean, code, reason); +} + +int32_t WebSocketResource::DoReceive() { + if (!receive_callback_var_) + return PP_OK; + + *receive_callback_var_ = received_messages_.front()->GetPPVar(); + received_messages_.pop(); + receive_callback_var_ = NULL; + return PP_OK; +} + +} // namespace proxy +} // namespace ppapi diff --git a/ppapi/proxy/websocket_resource.h b/ppapi/proxy/websocket_resource.h new file mode 100644 index 0000000..49353e4 --- /dev/null +++ b/ppapi/proxy/websocket_resource.h @@ -0,0 +1,157 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef PPAPI_PROXY_WEBSOCKET_RESOURCE_H_ +#define PPAPI_PROXY_WEBSOCKET_RESOURCE_H_ + +#include <queue> + +#include "ppapi/c/ppb_websocket.h" +#include "ppapi/proxy/plugin_resource.h" +#include "ppapi/shared_impl/tracked_callback.h" +#include "ppapi/thunk/ppb_websocket_api.h" + +namespace ppapi { + +class StringVar; +class Var; + +namespace proxy { + +// This class contains protocol checks which doesn't affect security when it +// run with untrusted code. +class PPAPI_PROXY_EXPORT WebSocketResource + : public PluginResource, + public NON_EXPORTED_BASE(thunk::PPB_WebSocket_API) { + public: + WebSocketResource(Connection connection, PP_Instance instance); + virtual ~WebSocketResource(); + + // PluginResource implementation. + virtual thunk::PPB_WebSocket_API* AsPPB_WebSocket_API() OVERRIDE; + + // PPB_WebSocket_API implementation. + virtual int32_t Connect(const PP_Var& url, + const PP_Var protocols[], + uint32_t protocol_count, + scoped_refptr<TrackedCallback> callback) OVERRIDE; + virtual int32_t Close(uint16_t code, + const PP_Var& reason, + scoped_refptr<TrackedCallback> callback) OVERRIDE; + virtual int32_t ReceiveMessage( + PP_Var* message, + scoped_refptr<TrackedCallback> callback) OVERRIDE; + virtual int32_t SendMessage(const PP_Var& message) OVERRIDE; + virtual uint64_t GetBufferedAmount() OVERRIDE; + virtual uint16_t GetCloseCode() OVERRIDE; + virtual PP_Var GetCloseReason() OVERRIDE; + virtual PP_Bool GetCloseWasClean() OVERRIDE; + virtual PP_Var GetExtensions() OVERRIDE; + virtual PP_Var GetProtocol() OVERRIDE; + virtual PP_WebSocketReadyState GetReadyState() OVERRIDE; + virtual PP_Var GetURL() OVERRIDE; + + private: + // PluginResource override. + virtual void OnReplyReceived(const ResourceMessageReplyParams& params, + const IPC::Message& msg) OVERRIDE; + + // IPC message handlers. + void OnPluginMsgConnectReply(const ResourceMessageReplyParams& params, + const std::string& url, + const std::string& protocol); + void OnPluginMsgCloseReply(const ResourceMessageReplyParams& params, + unsigned long buffered_amount, + bool was_clean, + unsigned short code, + const std::string& reason); + void OnPluginMsgReceiveTextReply(const ResourceMessageReplyParams& params, + const std::string& message); + void OnPluginMsgReceiveBinaryReply(const ResourceMessageReplyParams& params, + const std::vector<uint8_t>& message); + void OnPluginMsgErrorReply(const ResourceMessageReplyParams& params); + void OnPluginMsgBufferedAmountReply(const ResourceMessageReplyParams& params, + unsigned long buffered_amount); + void OnPluginMsgStateReply(const ResourceMessageReplyParams& params, + int32_t state); + void OnPluginMsgClosedReply(const ResourceMessageReplyParams& params, + unsigned long buffered_amount, + bool was_clean, + unsigned short code, + const std::string& reason); + + // Picks up a received message and moves it to user receiving buffer. This + // function is used in both ReceiveMessage for fast returning path, and + // OnPluginMsgReceiveTextReply and OnPluginMsgReceiveBinaryReply for delayed + // callback invocations. + int32_t DoReceive(); + + // Holds user callbacks to invoke later. + scoped_refptr<TrackedCallback> connect_callback_; + scoped_refptr<TrackedCallback> close_callback_; + scoped_refptr<TrackedCallback> receive_callback_; + + // Represents readyState described in the WebSocket API specification. It can + // be read via GetReadyState(). + PP_WebSocketReadyState state_; + + // Becomes true if any error is detected. Incoming data will be disposed + // if this variable is true, then ReceiveMessage() returns PP_ERROR_FAILED + // after returning all received data. + bool error_was_received_; + + // Keeps a pointer to PP_Var which is provided via ReceiveMessage(). + // Received data will be copied to this PP_Var on ready. + PP_Var* receive_callback_var_; + + // Keeps received data until ReceiveMessage() requests. + std::queue<scoped_refptr<Var> > received_messages_; + + // Keeps empty string for functions to return empty string. + scoped_refptr<StringVar> empty_string_; + + // Keeps the status code field of closing handshake. It can be read via + // GetCloseCode(). + uint16_t close_code_; + + // Keeps the reason field of closing handshake. It can be read via + // GetCloseReason(). + scoped_refptr<StringVar> close_reason_; + + // Becomes true when closing handshake is performed successfully. It can be + // read via GetCloseWasClean(). + PP_Bool close_was_clean_; + + // Represents extensions described in the WebSocket API specification. It can + // be read via GetExtensions(). + scoped_refptr<StringVar> extensions_; + + // Represents protocol described in the WebSocket API specification. It can be + // read via GetProtocol(). + scoped_refptr<StringVar> protocol_; + + // Represents url described in the WebSocket API specification. It can be + // read via GetURL(). + scoped_refptr<StringVar> url_; + + // Keeps the number of bytes of application data that have been queued using + // SendMessage(). WebKit side implementation calculates the actual amount. + // This is a cached value which is notified through a WebKit callback. + // This value is used to calculate bufferedAmount in the WebSocket API + // specification. The calculated value can be read via GetBufferedAmount(). + uint64_t buffered_amount_; + + // Keeps the number of bytes of application data that have been ignored + // because the connection was already closed. + // This value is used to calculate bufferedAmount in the WebSocket API + // specification. The calculated value can be read via GetBufferedAmount(). + uint64_t buffered_amount_after_close_; + + DISALLOW_COPY_AND_ASSIGN(WebSocketResource); +}; + +} // namespace proxy +} // namespace ppapi + +#endif // PPAPI_PROXY_WEBSOCKET_RESOURCE_H_ diff --git a/ppapi/proxy/websocket_resource_unittest.cc b/ppapi/proxy/websocket_resource_unittest.cc new file mode 100644 index 0000000..61e8925 --- /dev/null +++ b/ppapi/proxy/websocket_resource_unittest.cc @@ -0,0 +1,168 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/memory/ref_counted.h" +#include "base/message_loop.h" +#include "ppapi/c/pp_errors.h" +#include "ppapi/c/ppb_websocket.h" +#include "ppapi/c/ppb_var.h" +#include "ppapi/proxy/websocket_resource.h" +#include "ppapi/proxy/ppapi_messages.h" +#include "ppapi/proxy/ppapi_proxy_test.h" +#include "ppapi/shared_impl/ppb_var_shared.h" +#include "ppapi/shared_impl/scoped_pp_resource.h" +#include "ppapi/shared_impl/scoped_pp_var.h" +#include "ppapi/shared_impl/tracked_callback.h" +#include "ppapi/shared_impl/var.h" +#include "ppapi/thunk/thunk.h" + +namespace ppapi { +namespace proxy { + +namespace { + +typedef PluginProxyTest WebSocketResourceTest; + +bool g_callback_called; +int32_t g_callback_result; +const PPB_Var* ppb_var_ = NULL; + +void Callback(void* user_data, int32_t result) { + g_callback_called = true; + g_callback_result = result; +} + +PP_CompletionCallback MakeCallback() { + g_callback_called = false; + g_callback_result = PP_OK; + return PP_MakeCompletionCallback(Callback, NULL); +} + +PP_Var MakeStringVar(const std::string& string) { + if (!ppb_var_) + ppb_var_ = ppapi::PPB_Var_Shared::GetVarInterface1_1(); + return ppb_var_->VarFromUtf8(string.c_str(), string.length()); +} + +} // namespace + + +// Does a test of Connect(). +TEST_F(WebSocketResourceTest, Connect) { + const PPB_WebSocket_1_0* websocket_iface = + thunk::GetPPB_WebSocket_1_0_Thunk(); + + std::string url("ws://ws.google.com"); + std::string protocol0("x-foo"); + std::string protocol1("x-bar"); + PP_Var url_var = MakeStringVar(url); + PP_Var protocols[] = { MakeStringVar(protocol0), MakeStringVar(protocol1) }; + + ScopedPPResource res(ScopedPPResource::PassRef(), + websocket_iface->Create(pp_instance())); + + int32_t result = + websocket_iface->Connect(res, url_var, protocols, 2, MakeCallback()); + ASSERT_EQ(PP_OK_COMPLETIONPENDING, result); + + // Should be sent a "Connect" message. + ResourceMessageCallParams params; + IPC::Message msg; + ASSERT_TRUE(sink().GetFirstResourceCallMatching( + PpapiHostMsg_WebSocket_Connect::ID, ¶ms, &msg)); + PpapiHostMsg_WebSocket_Connect::Schema::Param p; + PpapiHostMsg_WebSocket_Connect::Read(&msg, &p); + EXPECT_EQ(url, p.a); + EXPECT_EQ(protocol0, p.b[0]); + EXPECT_EQ(protocol1, p.b[1]); + + // Synthesize a response. + ResourceMessageReplyParams reply_params(params.pp_resource(), + params.sequence()); + reply_params.set_result(PP_OK); + ASSERT_TRUE(plugin_dispatcher()->OnMessageReceived( + PpapiPluginMsg_ResourceReply(reply_params, + PpapiPluginMsg_WebSocket_ConnectReply(url, protocol1)))); + + EXPECT_EQ(PP_OK, g_callback_result); + EXPECT_EQ(true, g_callback_called); +} + +// Does a test for unsolicited replies. +TEST_F(WebSocketResourceTest, UnsolicitedReplies) { + const PPB_WebSocket_1_0* websocket_iface = + thunk::GetPPB_WebSocket_1_0_Thunk(); + + ScopedPPResource res(ScopedPPResource::PassRef(), + websocket_iface->Create(pp_instance())); + + // Check if BufferedAmountReply is handled. + ResourceMessageReplyParams reply_params(res, 0); + reply_params.set_result(PP_OK); + ASSERT_TRUE(plugin_dispatcher()->OnMessageReceived( + PpapiPluginMsg_ResourceReply( + reply_params, + PpapiPluginMsg_WebSocket_BufferedAmountReply(19760227u)))); + + uint64_t amount = websocket_iface->GetBufferedAmount(res); + EXPECT_EQ(19760227u, amount); + + // Check if StateReply is handled. + ASSERT_TRUE(plugin_dispatcher()->OnMessageReceived( + PpapiPluginMsg_ResourceReply( + reply_params, + PpapiPluginMsg_WebSocket_StateReply( + static_cast<int32_t>(PP_WEBSOCKETREADYSTATE_CLOSING))))); + + PP_WebSocketReadyState state = websocket_iface->GetReadyState(res); + EXPECT_EQ(PP_WEBSOCKETREADYSTATE_CLOSING, state); +} + +TEST_F(WebSocketResourceTest, MessageError) { + const PPB_WebSocket_1_0* websocket_iface = + thunk::GetPPB_WebSocket_1_0_Thunk(); + + std::string url("ws://ws.google.com"); + PP_Var url_var = MakeStringVar(url); + + ScopedPPResource res(ScopedPPResource::PassRef(), + websocket_iface->Create(pp_instance())); + + // Establish the connection virtually. + int32_t result = + websocket_iface->Connect(res, url_var, NULL, 0, MakeCallback()); + ASSERT_EQ(PP_OK_COMPLETIONPENDING, result); + + ResourceMessageCallParams params; + IPC::Message msg; + ASSERT_TRUE(sink().GetFirstResourceCallMatching( + PpapiHostMsg_WebSocket_Connect::ID, ¶ms, &msg)); + + ResourceMessageReplyParams connect_reply_params(params.pp_resource(), + params.sequence()); + connect_reply_params.set_result(PP_OK); + ASSERT_TRUE(plugin_dispatcher()->OnMessageReceived( + PpapiPluginMsg_ResourceReply(connect_reply_params, + PpapiPluginMsg_WebSocket_ConnectReply(url, std::string())))); + + EXPECT_EQ(PP_OK, g_callback_result); + EXPECT_EQ(true, g_callback_called); + + PP_Var message; + result = websocket_iface->ReceiveMessage(res, &message, MakeCallback()); + EXPECT_EQ(false, g_callback_called); + + // Synthesize a WebSocket_ErrorReply message. + ResourceMessageReplyParams error_reply_params(res, 0); + error_reply_params.set_result(PP_OK); + ASSERT_TRUE(plugin_dispatcher()->OnMessageReceived( + PpapiPluginMsg_ResourceReply(error_reply_params, + PpapiPluginMsg_WebSocket_ErrorReply()))); + + EXPECT_EQ(PP_ERROR_FAILED, g_callback_result); + EXPECT_EQ(true, g_callback_called); +} + +} // namespace proxy +} // namespace ppapi |