Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / ppapi / proxy / websocket_resource.cc
blobbc4e8535520be3abbda67352ca3200df6984b38d
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.
5 #include "ppapi/proxy/websocket_resource.h"
7 #include <set>
8 #include <string>
9 #include <vector>
11 #include "base/bind.h"
12 #include "base/numerics/safe_conversions.h"
13 #include "ppapi/c/pp_errors.h"
14 #include "ppapi/proxy/dispatch_reply_message.h"
15 #include "ppapi/proxy/ppapi_messages.h"
16 #include "ppapi/shared_impl/ppapi_globals.h"
17 #include "ppapi/shared_impl/var.h"
18 #include "ppapi/shared_impl/var_tracker.h"
20 namespace {
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;
28 uint64_t SaturateAdd(uint64_t a, uint64_t b) {
29 if (kuint64max - a < b)
30 return kuint64max;
31 return a + b;
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);
43 bool InValidStateToReceive(PP_WebSocketReadyState state) {
44 return state == PP_WEBSOCKETREADYSTATE_OPEN ||
45 state == PP_WEBSOCKETREADYSTATE_CLOSING;
48 } // namespace
51 namespace ppapi {
52 namespace proxy {
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) {
71 WebSocketResource::~WebSocketResource() {
74 thunk::PPB_WebSocket_API* WebSocketResource::AsPPB_WebSocket_API() {
75 return this;
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;
86 // Connect() can be called at most once.
87 if (state_ != PP_WEBSOCKETREADYSTATE_INVALID)
88 return PP_ERROR_INPROGRESS;
89 state_ = PP_WEBSOCKETREADYSTATE_CLOSED;
91 // Get the URL.
92 url_ = StringVar::FromPPVar(url);
93 if (!url_.get())
94 return PP_ERROR_BADARGUMENT;
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]));
103 // Check invalid and empty entries.
104 if (!protocol.get() || !protocol->value().length())
105 return PP_ERROR_BADARGUMENT;
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());
112 protocol_strings.push_back(protocol->value());
115 // Install callback.
116 connect_callback_ = callback;
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));
126 return PP_OK_COMPLETIONPENDING;
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;
137 // Validate |code| and |reason|.
138 scoped_refptr<StringVar> reason_string_var;
139 std::string reason_string;
140 if (code != PP_WEBSOCKETSTATUSCODE_NOT_SPECIFIED) {
141 if (code != PP_WEBSOCKETSTATUSCODE_NORMAL_CLOSURE &&
142 (code < PP_WEBSOCKETSTATUSCODE_USER_REGISTERED_MIN ||
143 code > PP_WEBSOCKETSTATUSCODE_USER_PRIVATE_MAX))
144 // RFC 6455 limits applications to use reserved connection close code in
145 // section 7.4.2.. The WebSocket API (http://www.w3.org/TR/websockets/)
146 // defines this out of range error as InvalidAccessError in JavaScript.
147 return PP_ERROR_NOACCESS;
149 // |reason| must be ignored if it is PP_VARTYPE_UNDEFINED or |code| is
150 // PP_WEBSOCKETSTATUSCODE_NOT_SPECIFIED.
151 if (reason.type != PP_VARTYPE_UNDEFINED) {
152 // Validate |reason|.
153 reason_string_var = StringVar::FromPPVar(reason);
154 if (!reason_string_var.get() ||
155 reason_string_var->value().size() > kMaxReasonSizeInBytes)
156 return PP_ERROR_BADARGUMENT;
157 reason_string = reason_string_var->value();
161 // Check state.
162 if (state_ == PP_WEBSOCKETREADYSTATE_CLOSING)
163 return PP_ERROR_INPROGRESS;
164 if (state_ == PP_WEBSOCKETREADYSTATE_CLOSED)
165 return PP_OK;
167 // Install |callback|.
168 close_callback_ = callback;
170 // Abort ongoing connect.
171 if (TrackedCallback::IsPending(connect_callback_)) {
172 state_ = PP_WEBSOCKETREADYSTATE_CLOSING;
173 // Need to do a "Post" to avoid reentering the plugin.
174 connect_callback_->PostAbort();
175 connect_callback_ = NULL;
176 Post(RENDERER, PpapiHostMsg_WebSocket_Fail(
177 "WebSocket was closed before the connection was established."));
178 return PP_OK_COMPLETIONPENDING;
181 // Abort ongoing receive.
182 if (TrackedCallback::IsPending(receive_callback_)) {
183 receive_callback_var_ = NULL;
184 // Need to do a "Post" to avoid reentering the plugin.
185 receive_callback_->PostAbort();
186 receive_callback_ = NULL;
189 // Close connection.
190 state_ = PP_WEBSOCKETREADYSTATE_CLOSING;
191 PpapiHostMsg_WebSocket_Close msg(static_cast<int32_t>(code),
192 reason_string);
193 Call<PpapiPluginMsg_WebSocket_CloseReply>(RENDERER, msg,
194 base::Bind(&WebSocketResource::OnPluginMsgCloseReply, this));
195 return PP_OK_COMPLETIONPENDING;
198 int32_t WebSocketResource::ReceiveMessage(
199 PP_Var* message,
200 scoped_refptr<TrackedCallback> callback) {
201 if (TrackedCallback::IsPending(receive_callback_))
202 return PP_ERROR_INPROGRESS;
204 // Check state.
205 if (state_ == PP_WEBSOCKETREADYSTATE_INVALID ||
206 state_ == PP_WEBSOCKETREADYSTATE_CONNECTING)
207 return PP_ERROR_BADARGUMENT;
209 // Just return received message if any received message is queued.
210 if (!received_messages_.empty()) {
211 receive_callback_var_ = message;
212 return DoReceive();
215 // Check state again. In CLOSED state, no more messages will be received.
216 if (state_ == PP_WEBSOCKETREADYSTATE_CLOSED)
217 return PP_ERROR_BADARGUMENT;
219 // Returns PP_ERROR_FAILED after an error is received and received messages
220 // is exhausted.
221 if (error_was_received_)
222 return PP_ERROR_FAILED;
224 // Or retain |message| as buffer to store and install |callback|.
225 receive_callback_var_ = message;
226 receive_callback_ = callback;
228 return PP_OK_COMPLETIONPENDING;
231 int32_t WebSocketResource::SendMessage(const PP_Var& message) {
232 // Check state.
233 if (state_ == PP_WEBSOCKETREADYSTATE_INVALID ||
234 state_ == PP_WEBSOCKETREADYSTATE_CONNECTING)
235 return PP_ERROR_BADARGUMENT;
237 if (state_ == PP_WEBSOCKETREADYSTATE_CLOSING ||
238 state_ == PP_WEBSOCKETREADYSTATE_CLOSED) {
239 // Handle buffered_amount_after_close_.
240 uint64_t payload_size = 0;
241 if (message.type == PP_VARTYPE_STRING) {
242 scoped_refptr<StringVar> message_string = StringVar::FromPPVar(message);
243 if (message_string.get())
244 payload_size += message_string->value().length();
245 } else if (message.type == PP_VARTYPE_ARRAY_BUFFER) {
246 scoped_refptr<ArrayBufferVar> message_array_buffer =
247 ArrayBufferVar::FromPPVar(message);
248 if (message_array_buffer.get())
249 payload_size += message_array_buffer->ByteLength();
250 } else {
251 // TODO(toyoshim): Support Blob.
252 return PP_ERROR_NOTSUPPORTED;
255 buffered_amount_after_close_ =
256 SaturateAdd(buffered_amount_after_close_, GetFrameSize(payload_size));
258 return PP_ERROR_FAILED;
261 // Send the message.
262 if (message.type == PP_VARTYPE_STRING) {
263 // Convert message to std::string, then send it.
264 scoped_refptr<StringVar> message_string = StringVar::FromPPVar(message);
265 if (!message_string.get())
266 return PP_ERROR_BADARGUMENT;
267 Post(RENDERER, PpapiHostMsg_WebSocket_SendText(message_string->value()));
268 } else if (message.type == PP_VARTYPE_ARRAY_BUFFER) {
269 // Convert message to std::vector<uint8_t>, then send it.
270 scoped_refptr<ArrayBufferVar> message_arraybuffer =
271 ArrayBufferVar::FromPPVar(message);
272 if (!message_arraybuffer.get())
273 return PP_ERROR_BADARGUMENT;
274 uint8_t* message_data = static_cast<uint8_t*>(message_arraybuffer->Map());
275 uint32 message_length = message_arraybuffer->ByteLength();
276 std::vector<uint8_t> message_vector(message_data,
277 message_data + message_length);
278 Post(RENDERER, PpapiHostMsg_WebSocket_SendBinary(message_vector));
279 } else {
280 // TODO(toyoshim): Support Blob.
281 return PP_ERROR_NOTSUPPORTED;
283 return PP_OK;
286 uint64_t WebSocketResource::GetBufferedAmount() {
287 return SaturateAdd(buffered_amount_, buffered_amount_after_close_);
290 uint16_t WebSocketResource::GetCloseCode() {
291 return close_code_;
294 PP_Var WebSocketResource::GetCloseReason() {
295 if (!close_reason_.get())
296 return empty_string_->GetPPVar();
297 return close_reason_->GetPPVar();
300 PP_Bool WebSocketResource::GetCloseWasClean() {
301 return close_was_clean_;
304 PP_Var WebSocketResource::GetExtensions() {
305 return StringVar::StringToPPVar(std::string());
308 PP_Var WebSocketResource::GetProtocol() {
309 if (!protocol_.get())
310 return empty_string_->GetPPVar();
311 return protocol_->GetPPVar();
314 PP_WebSocketReadyState WebSocketResource::GetReadyState() {
315 return state_;
318 PP_Var WebSocketResource::GetURL() {
319 if (!url_.get())
320 return empty_string_->GetPPVar();
321 return url_->GetPPVar();
324 void WebSocketResource::OnReplyReceived(
325 const ResourceMessageReplyParams& params,
326 const IPC::Message& msg) {
327 if (params.sequence()) {
328 PluginResource::OnReplyReceived(params, msg);
329 return;
332 PPAPI_BEGIN_MESSAGE_MAP(WebSocketResource, msg)
333 PPAPI_DISPATCH_PLUGIN_RESOURCE_CALL(
334 PpapiPluginMsg_WebSocket_ReceiveTextReply,
335 OnPluginMsgReceiveTextReply)
336 PPAPI_DISPATCH_PLUGIN_RESOURCE_CALL(
337 PpapiPluginMsg_WebSocket_ReceiveBinaryReply,
338 OnPluginMsgReceiveBinaryReply)
339 PPAPI_DISPATCH_PLUGIN_RESOURCE_CALL_0(
340 PpapiPluginMsg_WebSocket_ErrorReply,
341 OnPluginMsgErrorReply)
342 PPAPI_DISPATCH_PLUGIN_RESOURCE_CALL(
343 PpapiPluginMsg_WebSocket_BufferedAmountReply,
344 OnPluginMsgBufferedAmountReply)
345 PPAPI_DISPATCH_PLUGIN_RESOURCE_CALL(
346 PpapiPluginMsg_WebSocket_StateReply,
347 OnPluginMsgStateReply)
348 PPAPI_DISPATCH_PLUGIN_RESOURCE_CALL(
349 PpapiPluginMsg_WebSocket_ClosedReply,
350 OnPluginMsgClosedReply)
351 PPAPI_DISPATCH_PLUGIN_RESOURCE_CALL_UNHANDLED(NOTREACHED())
352 PPAPI_END_MESSAGE_MAP()
355 void WebSocketResource::OnPluginMsgConnectReply(
356 const ResourceMessageReplyParams& params,
357 const std::string& url,
358 const std::string& protocol) {
359 if (!TrackedCallback::IsPending(connect_callback_) ||
360 TrackedCallback::IsScheduledToRun(connect_callback_)) {
361 return;
364 int32_t result = params.result();
365 if (result == PP_OK) {
366 state_ = PP_WEBSOCKETREADYSTATE_OPEN;
367 protocol_ = new StringVar(protocol);
368 url_ = new StringVar(url);
370 connect_callback_->Run(params.result());
373 void WebSocketResource::OnPluginMsgCloseReply(
374 const ResourceMessageReplyParams& params,
375 unsigned long buffered_amount,
376 bool was_clean,
377 unsigned short code,
378 const std::string& reason) {
379 // Set close related properties.
380 state_ = PP_WEBSOCKETREADYSTATE_CLOSED;
381 buffered_amount_ = buffered_amount;
382 close_was_clean_ = PP_FromBool(was_clean);
383 close_code_ = code;
384 close_reason_ = new StringVar(reason);
386 if (TrackedCallback::IsPending(receive_callback_)) {
387 receive_callback_var_ = NULL;
388 if (!TrackedCallback::IsScheduledToRun(receive_callback_))
389 receive_callback_->PostRun(PP_ERROR_FAILED);
390 receive_callback_ = NULL;
393 if (TrackedCallback::IsPending(close_callback_)) {
394 if (!TrackedCallback::IsScheduledToRun(close_callback_))
395 close_callback_->PostRun(params.result());
396 close_callback_ = NULL;
400 void WebSocketResource::OnPluginMsgReceiveTextReply(
401 const ResourceMessageReplyParams& params,
402 const std::string& message) {
403 // Dispose packets after receiving an error or in invalid state.
404 if (error_was_received_ || !InValidStateToReceive(state_))
405 return;
407 // Append received data to queue.
408 received_messages_.push(scoped_refptr<Var>(new StringVar(message)));
410 if (!TrackedCallback::IsPending(receive_callback_) ||
411 TrackedCallback::IsScheduledToRun(receive_callback_)) {
412 return;
415 receive_callback_->Run(DoReceive());
418 void 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;
425 // Append received data to queue.
426 scoped_refptr<Var> message_var(
427 PpapiGlobals::Get()->GetVarTracker()->MakeArrayBufferVar(
428 base::checked_cast<uint32_t>(message.size()),
429 &message.front()));
430 received_messages_.push(message_var);
432 if (!TrackedCallback::IsPending(receive_callback_) ||
433 TrackedCallback::IsScheduledToRun(receive_callback_)) {
434 return;
437 receive_callback_->Run(DoReceive());
440 void WebSocketResource::OnPluginMsgErrorReply(
441 const ResourceMessageReplyParams& params) {
442 error_was_received_ = true;
444 if (!TrackedCallback::IsPending(receive_callback_) ||
445 TrackedCallback::IsScheduledToRun(receive_callback_)) {
446 return;
449 // No more text or binary messages will be received. If there is ongoing
450 // ReceiveMessage(), we must invoke the callback with error code here.
451 receive_callback_var_ = NULL;
452 receive_callback_->Run(PP_ERROR_FAILED);
455 void WebSocketResource::OnPluginMsgBufferedAmountReply(
456 const ResourceMessageReplyParams& params,
457 unsigned long buffered_amount) {
458 buffered_amount_ = buffered_amount;
461 void WebSocketResource::OnPluginMsgStateReply(
462 const ResourceMessageReplyParams& params,
463 int32_t state) {
464 state_ = static_cast<PP_WebSocketReadyState>(state);
467 void WebSocketResource::OnPluginMsgClosedReply(
468 const ResourceMessageReplyParams& params,
469 unsigned long buffered_amount,
470 bool was_clean,
471 unsigned short code,
472 const std::string& reason) {
473 OnPluginMsgCloseReply(params, buffered_amount, was_clean, code, reason);
476 int32_t WebSocketResource::DoReceive() {
477 if (!receive_callback_var_)
478 return PP_OK;
480 *receive_callback_var_ = received_messages_.front()->GetPPVar();
481 received_messages_.pop();
482 receive_callback_var_ = NULL;
483 return PP_OK;
486 } // namespace proxy
487 } // namespace ppapi