| Index: ppapi/proxy/websocket_resource.cc
|
| diff --git a/ppapi/proxy/websocket_resource.cc b/ppapi/proxy/websocket_resource.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..de9ee2b5074bda8cc58d1bf9c1e813e328016fd6
|
| --- /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
|
|
|