blob: 02b918e3c37ea65be603881cdffd0832be0813c7 [file] [log] [blame]
[email protected]9d5eadf2012-10-09 03:43:481// 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
avie029c4132015-12-23 06:45:227#include <stddef.h>
8
avibf0746c2015-12-09 19:53:149#include <limits>
[email protected]9d5eadf2012-10-09 03:43:4810#include <set>
[email protected]91d01b52013-07-26 08:47:4811#include <string>
[email protected]9d5eadf2012-10-09 03:43:4812#include <vector>
13
14#include "base/bind.h"
brettw669d47b12015-02-13 21:17:3815#include "base/numerics/safe_conversions.h"
[email protected]9d5eadf2012-10-09 03:43:4816#include "ppapi/c/pp_errors.h"
[email protected]e8bd3722012-10-29 00:02:1617#include "ppapi/proxy/dispatch_reply_message.h"
[email protected]9d5eadf2012-10-09 03:43:4818#include "ppapi/proxy/ppapi_messages.h"
19#include "ppapi/shared_impl/ppapi_globals.h"
20#include "ppapi/shared_impl/var.h"
21#include "ppapi/shared_impl/var_tracker.h"
[email protected]9d5eadf2012-10-09 03:43:4822
23namespace {
24
25const uint32_t kMaxReasonSizeInBytes = 123;
26const size_t kBaseFramingOverhead = 2;
27const size_t kMaskingKeyLength = 4;
28const size_t kMinimumPayloadSizeWithTwoByteExtendedPayloadLength = 126;
29const size_t kMinimumPayloadSizeWithEightByteExtendedPayloadLength = 0x10000;
30
31uint64_t SaturateAdd(uint64_t a, uint64_t b) {
avibf0746c2015-12-09 19:53:1432 if (std::numeric_limits<uint64_t>::max() - a < b)
33 return std::numeric_limits<uint64_t>::max();
[email protected]9d5eadf2012-10-09 03:43:4834 return a + b;
35}
36
37uint64_t GetFrameSize(uint64_t payload_size) {
38 uint64_t overhead = kBaseFramingOverhead + kMaskingKeyLength;
39 if (payload_size > kMinimumPayloadSizeWithEightByteExtendedPayloadLength)
40 overhead += 8;
41 else if (payload_size > kMinimumPayloadSizeWithTwoByteExtendedPayloadLength)
42 overhead += 2;
43 return SaturateAdd(payload_size, overhead);
44}
45
46bool InValidStateToReceive(PP_WebSocketReadyState state) {
47 return state == PP_WEBSOCKETREADYSTATE_OPEN ||
48 state == PP_WEBSOCKETREADYSTATE_CLOSING;
49}
50
51} // namespace
52
53
54namespace ppapi {
55namespace proxy {
56
57WebSocketResource::WebSocketResource(Connection connection,
58 PP_Instance instance)
59 : PluginResource(connection, instance),
60 state_(PP_WEBSOCKETREADYSTATE_INVALID),
61 error_was_received_(false),
62 receive_callback_var_(NULL),
63 empty_string_(new StringVar(std::string())),
64 close_code_(0),
65 close_reason_(NULL),
66 close_was_clean_(PP_FALSE),
67 extensions_(NULL),
68 protocol_(NULL),
69 url_(NULL),
70 buffered_amount_(0),
71 buffered_amount_after_close_(0) {
72}
73
74WebSocketResource::~WebSocketResource() {
75}
76
77thunk::PPB_WebSocket_API* WebSocketResource::AsPPB_WebSocket_API() {
78 return this;
79}
80
81int32_t WebSocketResource::Connect(
82 const PP_Var& url,
83 const PP_Var protocols[],
84 uint32_t protocol_count,
85 scoped_refptr<TrackedCallback> callback) {
86 if (TrackedCallback::IsPending(connect_callback_))
87 return PP_ERROR_INPROGRESS;
88
89 // Connect() can be called at most once.
90 if (state_ != PP_WEBSOCKETREADYSTATE_INVALID)
91 return PP_ERROR_INPROGRESS;
92 state_ = PP_WEBSOCKETREADYSTATE_CLOSED;
93
94 // Get the URL.
95 url_ = StringVar::FromPPVar(url);
[email protected]f0c86242013-06-02 21:25:4396 if (!url_.get())
[email protected]9d5eadf2012-10-09 03:43:4897 return PP_ERROR_BADARGUMENT;
98
99 // Get the protocols.
100 std::set<std::string> protocol_set;
101 std::vector<std::string> protocol_strings;
102 protocol_strings.reserve(protocol_count);
103 for (uint32_t i = 0; i < protocol_count; ++i) {
104 scoped_refptr<StringVar> protocol(StringVar::FromPPVar(protocols[i]));
105
106 // Check invalid and empty entries.
[email protected]f0c86242013-06-02 21:25:43107 if (!protocol.get() || !protocol->value().length())
[email protected]9d5eadf2012-10-09 03:43:48108 return PP_ERROR_BADARGUMENT;
109
110 // Check duplicated protocol entries.
111 if (protocol_set.find(protocol->value()) != protocol_set.end())
112 return PP_ERROR_BADARGUMENT;
113 protocol_set.insert(protocol->value());
114
115 protocol_strings.push_back(protocol->value());
116 }
117
118 // Install callback.
119 connect_callback_ = callback;
120
121 // Create remote host in the renderer, then request to check the URL and
122 // establish the connection.
123 state_ = PP_WEBSOCKETREADYSTATE_CONNECTING;
[email protected]9164da32012-10-16 03:40:57124 SendCreate(RENDERER, PpapiHostMsg_WebSocket_Create());
[email protected]9d5eadf2012-10-09 03:43:48125 PpapiHostMsg_WebSocket_Connect msg(url_->value(), protocol_strings);
[email protected]9164da32012-10-16 03:40:57126 Call<PpapiPluginMsg_WebSocket_ConnectReply>(RENDERER, msg,
[email protected]9d5eadf2012-10-09 03:43:48127 base::Bind(&WebSocketResource::OnPluginMsgConnectReply, this));
128
129 return PP_OK_COMPLETIONPENDING;
130}
131
132int32_t WebSocketResource::Close(uint16_t code,
133 const PP_Var& reason,
134 scoped_refptr<TrackedCallback> callback) {
135 if (TrackedCallback::IsPending(close_callback_))
136 return PP_ERROR_INPROGRESS;
137 if (state_ == PP_WEBSOCKETREADYSTATE_INVALID)
138 return PP_ERROR_FAILED;
139
140 // Validate |code| and |reason|.
141 scoped_refptr<StringVar> reason_string_var;
142 std::string reason_string;
[email protected]06a8a4722014-03-18 22:51:32143 if (code != PP_WEBSOCKETSTATUSCODE_NOT_SPECIFIED) {
144 if (code != PP_WEBSOCKETSTATUSCODE_NORMAL_CLOSURE &&
145 (code < PP_WEBSOCKETSTATUSCODE_USER_REGISTERED_MIN ||
146 code > PP_WEBSOCKETSTATUSCODE_USER_PRIVATE_MAX))
[email protected]9d5eadf2012-10-09 03:43:48147 // RFC 6455 limits applications to use reserved connection close code in
148 // section 7.4.2.. The WebSocket API (http://www.w3.org/TR/websockets/)
149 // defines this out of range error as InvalidAccessError in JavaScript.
150 return PP_ERROR_NOACCESS;
151
152 // |reason| must be ignored if it is PP_VARTYPE_UNDEFINED or |code| is
153 // PP_WEBSOCKETSTATUSCODE_NOT_SPECIFIED.
154 if (reason.type != PP_VARTYPE_UNDEFINED) {
155 // Validate |reason|.
156 reason_string_var = StringVar::FromPPVar(reason);
[email protected]f0c86242013-06-02 21:25:43157 if (!reason_string_var.get() ||
[email protected]9d5eadf2012-10-09 03:43:48158 reason_string_var->value().size() > kMaxReasonSizeInBytes)
159 return PP_ERROR_BADARGUMENT;
160 reason_string = reason_string_var->value();
161 }
162 }
163
164 // Check state.
165 if (state_ == PP_WEBSOCKETREADYSTATE_CLOSING)
166 return PP_ERROR_INPROGRESS;
167 if (state_ == PP_WEBSOCKETREADYSTATE_CLOSED)
168 return PP_OK;
169
170 // Install |callback|.
171 close_callback_ = callback;
172
173 // Abort ongoing connect.
174 if (TrackedCallback::IsPending(connect_callback_)) {
175 state_ = PP_WEBSOCKETREADYSTATE_CLOSING;
176 // Need to do a "Post" to avoid reentering the plugin.
177 connect_callback_->PostAbort();
178 connect_callback_ = NULL;
[email protected]9164da32012-10-16 03:40:57179 Post(RENDERER, PpapiHostMsg_WebSocket_Fail(
[email protected]9d5eadf2012-10-09 03:43:48180 "WebSocket was closed before the connection was established."));
181 return PP_OK_COMPLETIONPENDING;
182 }
183
184 // Abort ongoing receive.
185 if (TrackedCallback::IsPending(receive_callback_)) {
186 receive_callback_var_ = NULL;
187 // Need to do a "Post" to avoid reentering the plugin.
188 receive_callback_->PostAbort();
189 receive_callback_ = NULL;
190 }
191
192 // Close connection.
193 state_ = PP_WEBSOCKETREADYSTATE_CLOSING;
[email protected]06a8a4722014-03-18 22:51:32194 PpapiHostMsg_WebSocket_Close msg(static_cast<int32_t>(code),
[email protected]9d5eadf2012-10-09 03:43:48195 reason_string);
[email protected]9164da32012-10-16 03:40:57196 Call<PpapiPluginMsg_WebSocket_CloseReply>(RENDERER, msg,
[email protected]9d5eadf2012-10-09 03:43:48197 base::Bind(&WebSocketResource::OnPluginMsgCloseReply, this));
198 return PP_OK_COMPLETIONPENDING;
199}
200
201int32_t WebSocketResource::ReceiveMessage(
202 PP_Var* message,
203 scoped_refptr<TrackedCallback> callback) {
204 if (TrackedCallback::IsPending(receive_callback_))
205 return PP_ERROR_INPROGRESS;
206
207 // Check state.
208 if (state_ == PP_WEBSOCKETREADYSTATE_INVALID ||
209 state_ == PP_WEBSOCKETREADYSTATE_CONNECTING)
210 return PP_ERROR_BADARGUMENT;
211
212 // Just return received message if any received message is queued.
213 if (!received_messages_.empty()) {
214 receive_callback_var_ = message;
215 return DoReceive();
216 }
217
218 // Check state again. In CLOSED state, no more messages will be received.
219 if (state_ == PP_WEBSOCKETREADYSTATE_CLOSED)
220 return PP_ERROR_BADARGUMENT;
221
222 // Returns PP_ERROR_FAILED after an error is received and received messages
223 // is exhausted.
224 if (error_was_received_)
225 return PP_ERROR_FAILED;
226
227 // Or retain |message| as buffer to store and install |callback|.
228 receive_callback_var_ = message;
229 receive_callback_ = callback;
230
231 return PP_OK_COMPLETIONPENDING;
232}
233
234int32_t WebSocketResource::SendMessage(const PP_Var& message) {
235 // Check state.
236 if (state_ == PP_WEBSOCKETREADYSTATE_INVALID ||
237 state_ == PP_WEBSOCKETREADYSTATE_CONNECTING)
238 return PP_ERROR_BADARGUMENT;
239
240 if (state_ == PP_WEBSOCKETREADYSTATE_CLOSING ||
241 state_ == PP_WEBSOCKETREADYSTATE_CLOSED) {
242 // Handle buffered_amount_after_close_.
243 uint64_t payload_size = 0;
244 if (message.type == PP_VARTYPE_STRING) {
245 scoped_refptr<StringVar> message_string = StringVar::FromPPVar(message);
[email protected]f0c86242013-06-02 21:25:43246 if (message_string.get())
[email protected]9d5eadf2012-10-09 03:43:48247 payload_size += message_string->value().length();
248 } else if (message.type == PP_VARTYPE_ARRAY_BUFFER) {
249 scoped_refptr<ArrayBufferVar> message_array_buffer =
250 ArrayBufferVar::FromPPVar(message);
[email protected]f0c86242013-06-02 21:25:43251 if (message_array_buffer.get())
[email protected]9d5eadf2012-10-09 03:43:48252 payload_size += message_array_buffer->ByteLength();
253 } else {
254 // TODO(toyoshim): Support Blob.
255 return PP_ERROR_NOTSUPPORTED;
256 }
257
258 buffered_amount_after_close_ =
259 SaturateAdd(buffered_amount_after_close_, GetFrameSize(payload_size));
260
261 return PP_ERROR_FAILED;
262 }
263
264 // Send the message.
265 if (message.type == PP_VARTYPE_STRING) {
266 // Convert message to std::string, then send it.
267 scoped_refptr<StringVar> message_string = StringVar::FromPPVar(message);
[email protected]f0c86242013-06-02 21:25:43268 if (!message_string.get())
[email protected]9d5eadf2012-10-09 03:43:48269 return PP_ERROR_BADARGUMENT;
[email protected]9164da32012-10-16 03:40:57270 Post(RENDERER, PpapiHostMsg_WebSocket_SendText(message_string->value()));
[email protected]9d5eadf2012-10-09 03:43:48271 } else if (message.type == PP_VARTYPE_ARRAY_BUFFER) {
272 // Convert message to std::vector<uint8_t>, then send it.
273 scoped_refptr<ArrayBufferVar> message_arraybuffer =
274 ArrayBufferVar::FromPPVar(message);
[email protected]f0c86242013-06-02 21:25:43275 if (!message_arraybuffer.get())
[email protected]9d5eadf2012-10-09 03:43:48276 return PP_ERROR_BADARGUMENT;
277 uint8_t* message_data = static_cast<uint8_t*>(message_arraybuffer->Map());
avie029c4132015-12-23 06:45:22278 uint32_t message_length = message_arraybuffer->ByteLength();
[email protected]9d5eadf2012-10-09 03:43:48279 std::vector<uint8_t> message_vector(message_data,
280 message_data + message_length);
[email protected]9164da32012-10-16 03:40:57281 Post(RENDERER, PpapiHostMsg_WebSocket_SendBinary(message_vector));
[email protected]9d5eadf2012-10-09 03:43:48282 } else {
283 // TODO(toyoshim): Support Blob.
284 return PP_ERROR_NOTSUPPORTED;
285 }
286 return PP_OK;
287}
288
289uint64_t WebSocketResource::GetBufferedAmount() {
290 return SaturateAdd(buffered_amount_, buffered_amount_after_close_);
291}
292
293uint16_t WebSocketResource::GetCloseCode() {
294 return close_code_;
295}
296
297PP_Var WebSocketResource::GetCloseReason() {
[email protected]f0c86242013-06-02 21:25:43298 if (!close_reason_.get())
[email protected]9d5eadf2012-10-09 03:43:48299 return empty_string_->GetPPVar();
300 return close_reason_->GetPPVar();
301}
302
303PP_Bool WebSocketResource::GetCloseWasClean() {
304 return close_was_clean_;
305}
306
307PP_Var WebSocketResource::GetExtensions() {
308 return StringVar::StringToPPVar(std::string());
309}
310
311PP_Var WebSocketResource::GetProtocol() {
[email protected]f0c86242013-06-02 21:25:43312 if (!protocol_.get())
[email protected]9d5eadf2012-10-09 03:43:48313 return empty_string_->GetPPVar();
314 return protocol_->GetPPVar();
315}
316
317PP_WebSocketReadyState WebSocketResource::GetReadyState() {
318 return state_;
319}
320
321PP_Var WebSocketResource::GetURL() {
[email protected]f0c86242013-06-02 21:25:43322 if (!url_.get())
[email protected]9d5eadf2012-10-09 03:43:48323 return empty_string_->GetPPVar();
324 return url_->GetPPVar();
325}
326
327void WebSocketResource::OnReplyReceived(
328 const ResourceMessageReplyParams& params,
329 const IPC::Message& msg) {
[email protected]e8bd3722012-10-29 00:02:16330 if (params.sequence()) {
331 PluginResource::OnReplyReceived(params, msg);
332 return;
[email protected]9d5eadf2012-10-09 03:43:48333 }
[email protected]e8bd3722012-10-29 00:02:16334
[email protected]dade5f82014-05-13 21:59:21335 PPAPI_BEGIN_MESSAGE_MAP(WebSocketResource, msg)
[email protected]e8bd3722012-10-29 00:02:16336 PPAPI_DISPATCH_PLUGIN_RESOURCE_CALL(
337 PpapiPluginMsg_WebSocket_ReceiveTextReply,
338 OnPluginMsgReceiveTextReply)
339 PPAPI_DISPATCH_PLUGIN_RESOURCE_CALL(
340 PpapiPluginMsg_WebSocket_ReceiveBinaryReply,
341 OnPluginMsgReceiveBinaryReply)
342 PPAPI_DISPATCH_PLUGIN_RESOURCE_CALL_0(
343 PpapiPluginMsg_WebSocket_ErrorReply,
344 OnPluginMsgErrorReply)
345 PPAPI_DISPATCH_PLUGIN_RESOURCE_CALL(
346 PpapiPluginMsg_WebSocket_BufferedAmountReply,
347 OnPluginMsgBufferedAmountReply)
348 PPAPI_DISPATCH_PLUGIN_RESOURCE_CALL(
349 PpapiPluginMsg_WebSocket_StateReply,
350 OnPluginMsgStateReply)
351 PPAPI_DISPATCH_PLUGIN_RESOURCE_CALL(
352 PpapiPluginMsg_WebSocket_ClosedReply,
353 OnPluginMsgClosedReply)
354 PPAPI_DISPATCH_PLUGIN_RESOURCE_CALL_UNHANDLED(NOTREACHED())
[email protected]dade5f82014-05-13 21:59:21355 PPAPI_END_MESSAGE_MAP()
[email protected]9d5eadf2012-10-09 03:43:48356}
357
358void WebSocketResource::OnPluginMsgConnectReply(
359 const ResourceMessageReplyParams& params,
360 const std::string& url,
361 const std::string& protocol) {
[email protected]91d01b52013-07-26 08:47:48362 if (!TrackedCallback::IsPending(connect_callback_) ||
363 TrackedCallback::IsScheduledToRun(connect_callback_)) {
[email protected]9d5eadf2012-10-09 03:43:48364 return;
[email protected]91d01b52013-07-26 08:47:48365 }
[email protected]9d5eadf2012-10-09 03:43:48366
367 int32_t result = params.result();
368 if (result == PP_OK) {
369 state_ = PP_WEBSOCKETREADYSTATE_OPEN;
370 protocol_ = new StringVar(protocol);
371 url_ = new StringVar(url);
372 }
[email protected]c9eb50582012-11-05 20:08:24373 connect_callback_->Run(params.result());
[email protected]9d5eadf2012-10-09 03:43:48374}
375
376void WebSocketResource::OnPluginMsgCloseReply(
377 const ResourceMessageReplyParams& params,
378 unsigned long buffered_amount,
379 bool was_clean,
380 unsigned short code,
381 const std::string& reason) {
382 // Set close related properties.
383 state_ = PP_WEBSOCKETREADYSTATE_CLOSED;
384 buffered_amount_ = buffered_amount;
385 close_was_clean_ = PP_FromBool(was_clean);
386 close_code_ = code;
387 close_reason_ = new StringVar(reason);
388
389 if (TrackedCallback::IsPending(receive_callback_)) {
390 receive_callback_var_ = NULL;
[email protected]91d01b52013-07-26 08:47:48391 if (!TrackedCallback::IsScheduledToRun(receive_callback_))
392 receive_callback_->PostRun(PP_ERROR_FAILED);
[email protected]9d5eadf2012-10-09 03:43:48393 receive_callback_ = NULL;
394 }
395
396 if (TrackedCallback::IsPending(close_callback_)) {
[email protected]91d01b52013-07-26 08:47:48397 if (!TrackedCallback::IsScheduledToRun(close_callback_))
398 close_callback_->PostRun(params.result());
[email protected]9d5eadf2012-10-09 03:43:48399 close_callback_ = NULL;
400 }
401}
402
403void WebSocketResource::OnPluginMsgReceiveTextReply(
404 const ResourceMessageReplyParams& params,
405 const std::string& message) {
406 // Dispose packets after receiving an error or in invalid state.
407 if (error_was_received_ || !InValidStateToReceive(state_))
408 return;
409
410 // Append received data to queue.
411 received_messages_.push(scoped_refptr<Var>(new StringVar(message)));
412
[email protected]91d01b52013-07-26 08:47:48413 if (!TrackedCallback::IsPending(receive_callback_) ||
414 TrackedCallback::IsScheduledToRun(receive_callback_)) {
[email protected]9d5eadf2012-10-09 03:43:48415 return;
[email protected]91d01b52013-07-26 08:47:48416 }
[email protected]9d5eadf2012-10-09 03:43:48417
[email protected]c9eb50582012-11-05 20:08:24418 receive_callback_->Run(DoReceive());
[email protected]9d5eadf2012-10-09 03:43:48419}
420
421void WebSocketResource::OnPluginMsgReceiveBinaryReply(
422 const ResourceMessageReplyParams& params,
423 const std::vector<uint8_t>& message) {
424 // Dispose packets after receiving an error or in invalid state.
425 if (error_was_received_ || !InValidStateToReceive(state_))
426 return;
427
428 // Append received data to queue.
[email protected]8ced4f3f2013-02-04 16:53:07429 scoped_refptr<Var> message_var(
430 PpapiGlobals::Get()->GetVarTracker()->MakeArrayBufferVar(
brettw669d47b12015-02-13 21:17:38431 base::checked_cast<uint32_t>(message.size()),
[email protected]8ced4f3f2013-02-04 16:53:07432 &message.front()));
[email protected]9d5eadf2012-10-09 03:43:48433 received_messages_.push(message_var);
434
[email protected]91d01b52013-07-26 08:47:48435 if (!TrackedCallback::IsPending(receive_callback_) ||
436 TrackedCallback::IsScheduledToRun(receive_callback_)) {
[email protected]9d5eadf2012-10-09 03:43:48437 return;
[email protected]91d01b52013-07-26 08:47:48438 }
[email protected]9d5eadf2012-10-09 03:43:48439
[email protected]c9eb50582012-11-05 20:08:24440 receive_callback_->Run(DoReceive());
[email protected]9d5eadf2012-10-09 03:43:48441}
442
443void WebSocketResource::OnPluginMsgErrorReply(
444 const ResourceMessageReplyParams& params) {
445 error_was_received_ = true;
446
[email protected]91d01b52013-07-26 08:47:48447 if (!TrackedCallback::IsPending(receive_callback_) ||
448 TrackedCallback::IsScheduledToRun(receive_callback_)) {
[email protected]9d5eadf2012-10-09 03:43:48449 return;
[email protected]91d01b52013-07-26 08:47:48450 }
[email protected]9d5eadf2012-10-09 03:43:48451
452 // No more text or binary messages will be received. If there is ongoing
453 // ReceiveMessage(), we must invoke the callback with error code here.
454 receive_callback_var_ = NULL;
[email protected]c9eb50582012-11-05 20:08:24455 receive_callback_->Run(PP_ERROR_FAILED);
[email protected]9d5eadf2012-10-09 03:43:48456}
457
458void WebSocketResource::OnPluginMsgBufferedAmountReply(
459 const ResourceMessageReplyParams& params,
460 unsigned long buffered_amount) {
461 buffered_amount_ = buffered_amount;
462}
463
464void WebSocketResource::OnPluginMsgStateReply(
465 const ResourceMessageReplyParams& params,
466 int32_t state) {
467 state_ = static_cast<PP_WebSocketReadyState>(state);
468}
469
470void WebSocketResource::OnPluginMsgClosedReply(
471 const ResourceMessageReplyParams& params,
472 unsigned long buffered_amount,
473 bool was_clean,
474 unsigned short code,
475 const std::string& reason) {
476 OnPluginMsgCloseReply(params, buffered_amount, was_clean, code, reason);
477}
478
479int32_t WebSocketResource::DoReceive() {
480 if (!receive_callback_var_)
481 return PP_OK;
482
483 *receive_callback_var_ = received_messages_.front()->GetPPVar();
484 received_messages_.pop();
485 receive_callback_var_ = NULL;
486 return PP_OK;
487}
488
489} // namespace proxy
490} // namespace ppapi