blob: c998cd94b7206868cfa96ceb3d1566e823502482 [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
7#include <set>
[email protected]91d01b52013-07-26 08:47:488#include <string>
[email protected]9d5eadf2012-10-09 03:43:489#include <vector>
10
11#include "base/bind.h"
12#include "ppapi/c/pp_errors.h"
[email protected]e8bd3722012-10-29 00:02:1613#include "ppapi/proxy/dispatch_reply_message.h"
[email protected]9d5eadf2012-10-09 03:43:4814#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"
[email protected]2255a9332013-06-17 05:12:3118#include "third_party/WebKit/public/web/WebSocket.h"
[email protected]9d5eadf2012-10-09 03:43:4819
20namespace {
21
22const uint32_t kMaxReasonSizeInBytes = 123;
23const size_t kBaseFramingOverhead = 2;
24const size_t kMaskingKeyLength = 4;
25const size_t kMinimumPayloadSizeWithTwoByteExtendedPayloadLength = 126;
26const size_t kMinimumPayloadSizeWithEightByteExtendedPayloadLength = 0x10000;
27
28uint64_t SaturateAdd(uint64_t a, uint64_t b) {
29 if (kuint64max - a < b)
30 return kuint64max;
31 return a + b;
32}
33
34uint64_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
43bool InValidStateToReceive(PP_WebSocketReadyState state) {
44 return state == PP_WEBSOCKETREADYSTATE_OPEN ||
45 state == PP_WEBSOCKETREADYSTATE_CLOSING;
46}
47
48} // namespace
49
50
51namespace ppapi {
52namespace proxy {
53
54WebSocketResource::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
71WebSocketResource::~WebSocketResource() {
72}
73
74thunk::PPB_WebSocket_API* WebSocketResource::AsPPB_WebSocket_API() {
75 return this;
76}
77
78int32_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);
[email protected]f0c86242013-06-02 21:25:4393 if (!url_.get())
[email protected]9d5eadf2012-10-09 03:43:4894 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.
[email protected]f0c86242013-06-02 21:25:43104 if (!protocol.get() || !protocol->value().length())
[email protected]9d5eadf2012-10-09 03:43:48105 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;
[email protected]9164da32012-10-16 03:40:57121 SendCreate(RENDERER, PpapiHostMsg_WebSocket_Create());
[email protected]9d5eadf2012-10-09 03:43:48122 PpapiHostMsg_WebSocket_Connect msg(url_->value(), protocol_strings);
[email protected]9164da32012-10-16 03:40:57123 Call<PpapiPluginMsg_WebSocket_ConnectReply>(RENDERER, msg,
[email protected]9d5eadf2012-10-09 03:43:48124 base::Bind(&WebSocketResource::OnPluginMsgConnectReply, this));
125
126 return PP_OK_COMPLETIONPENDING;
127}
128
129int32_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;
[email protected]c32e27082013-11-07 06:58:20140 blink::WebSocket::CloseEventCode event_code =
141 static_cast<blink::WebSocket::CloseEventCode>(code);
[email protected]9d5eadf2012-10-09 03:43:48142 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.
[email protected]c32e27082013-11-07 06:58:20146 event_code = blink::WebSocket::CloseEventCodeNotSpecified;
[email protected]9d5eadf2012-10-09 03:43:48147 } 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);
[email protected]f0c86242013-06-02 21:25:43161 if (!reason_string_var.get() ||
[email protected]9d5eadf2012-10-09 03:43:48162 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;
[email protected]9164da32012-10-16 03:40:57183 Post(RENDERER, PpapiHostMsg_WebSocket_Fail(
[email protected]9d5eadf2012-10-09 03:43:48184 "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);
[email protected]9164da32012-10-16 03:40:57200 Call<PpapiPluginMsg_WebSocket_CloseReply>(RENDERER, msg,
[email protected]9d5eadf2012-10-09 03:43:48201 base::Bind(&WebSocketResource::OnPluginMsgCloseReply, this));
202 return PP_OK_COMPLETIONPENDING;
203}
204
205int32_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
238int32_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);
[email protected]f0c86242013-06-02 21:25:43250 if (message_string.get())
[email protected]9d5eadf2012-10-09 03:43:48251 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);
[email protected]f0c86242013-06-02 21:25:43255 if (message_array_buffer.get())
[email protected]9d5eadf2012-10-09 03:43:48256 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);
[email protected]f0c86242013-06-02 21:25:43272 if (!message_string.get())
[email protected]9d5eadf2012-10-09 03:43:48273 return PP_ERROR_BADARGUMENT;
[email protected]9164da32012-10-16 03:40:57274 Post(RENDERER, PpapiHostMsg_WebSocket_SendText(message_string->value()));
[email protected]9d5eadf2012-10-09 03:43:48275 } 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);
[email protected]f0c86242013-06-02 21:25:43279 if (!message_arraybuffer.get())
[email protected]9d5eadf2012-10-09 03:43:48280 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);
[email protected]9164da32012-10-16 03:40:57285 Post(RENDERER, PpapiHostMsg_WebSocket_SendBinary(message_vector));
[email protected]9d5eadf2012-10-09 03:43:48286 } else {
287 // TODO(toyoshim): Support Blob.
288 return PP_ERROR_NOTSUPPORTED;
289 }
290 return PP_OK;
291}
292
293uint64_t WebSocketResource::GetBufferedAmount() {
294 return SaturateAdd(buffered_amount_, buffered_amount_after_close_);
295}
296
297uint16_t WebSocketResource::GetCloseCode() {
298 return close_code_;
299}
300
301PP_Var WebSocketResource::GetCloseReason() {
[email protected]f0c86242013-06-02 21:25:43302 if (!close_reason_.get())
[email protected]9d5eadf2012-10-09 03:43:48303 return empty_string_->GetPPVar();
304 return close_reason_->GetPPVar();
305}
306
307PP_Bool WebSocketResource::GetCloseWasClean() {
308 return close_was_clean_;
309}
310
311PP_Var WebSocketResource::GetExtensions() {
312 return StringVar::StringToPPVar(std::string());
313}
314
315PP_Var WebSocketResource::GetProtocol() {
[email protected]f0c86242013-06-02 21:25:43316 if (!protocol_.get())
[email protected]9d5eadf2012-10-09 03:43:48317 return empty_string_->GetPPVar();
318 return protocol_->GetPPVar();
319}
320
321PP_WebSocketReadyState WebSocketResource::GetReadyState() {
322 return state_;
323}
324
325PP_Var WebSocketResource::GetURL() {
[email protected]f0c86242013-06-02 21:25:43326 if (!url_.get())
[email protected]9d5eadf2012-10-09 03:43:48327 return empty_string_->GetPPVar();
328 return url_->GetPPVar();
329}
330
331void WebSocketResource::OnReplyReceived(
332 const ResourceMessageReplyParams& params,
333 const IPC::Message& msg) {
[email protected]e8bd3722012-10-29 00:02:16334 if (params.sequence()) {
335 PluginResource::OnReplyReceived(params, msg);
336 return;
[email protected]9d5eadf2012-10-09 03:43:48337 }
[email protected]e8bd3722012-10-29 00:02:16338
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()
[email protected]9d5eadf2012-10-09 03:43:48360}
361
362void WebSocketResource::OnPluginMsgConnectReply(
363 const ResourceMessageReplyParams& params,
364 const std::string& url,
365 const std::string& protocol) {
[email protected]91d01b52013-07-26 08:47:48366 if (!TrackedCallback::IsPending(connect_callback_) ||
367 TrackedCallback::IsScheduledToRun(connect_callback_)) {
[email protected]9d5eadf2012-10-09 03:43:48368 return;
[email protected]91d01b52013-07-26 08:47:48369 }
[email protected]9d5eadf2012-10-09 03:43:48370
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 }
[email protected]c9eb50582012-11-05 20:08:24377 connect_callback_->Run(params.result());
[email protected]9d5eadf2012-10-09 03:43:48378}
379
380void 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;
[email protected]91d01b52013-07-26 08:47:48395 if (!TrackedCallback::IsScheduledToRun(receive_callback_))
396 receive_callback_->PostRun(PP_ERROR_FAILED);
[email protected]9d5eadf2012-10-09 03:43:48397 receive_callback_ = NULL;
398 }
399
400 if (TrackedCallback::IsPending(close_callback_)) {
[email protected]91d01b52013-07-26 08:47:48401 if (!TrackedCallback::IsScheduledToRun(close_callback_))
402 close_callback_->PostRun(params.result());
[email protected]9d5eadf2012-10-09 03:43:48403 close_callback_ = NULL;
404 }
405}
406
407void 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
[email protected]91d01b52013-07-26 08:47:48417 if (!TrackedCallback::IsPending(receive_callback_) ||
418 TrackedCallback::IsScheduledToRun(receive_callback_)) {
[email protected]9d5eadf2012-10-09 03:43:48419 return;
[email protected]91d01b52013-07-26 08:47:48420 }
[email protected]9d5eadf2012-10-09 03:43:48421
[email protected]c9eb50582012-11-05 20:08:24422 receive_callback_->Run(DoReceive());
[email protected]9d5eadf2012-10-09 03:43:48423}
424
425void 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.
[email protected]8ced4f3f2013-02-04 16:53:07433 scoped_refptr<Var> message_var(
434 PpapiGlobals::Get()->GetVarTracker()->MakeArrayBufferVar(
[email protected]9d5eadf2012-10-09 03:43:48435 message.size(),
[email protected]8ced4f3f2013-02-04 16:53:07436 &message.front()));
[email protected]9d5eadf2012-10-09 03:43:48437 received_messages_.push(message_var);
438
[email protected]91d01b52013-07-26 08:47:48439 if (!TrackedCallback::IsPending(receive_callback_) ||
440 TrackedCallback::IsScheduledToRun(receive_callback_)) {
[email protected]9d5eadf2012-10-09 03:43:48441 return;
[email protected]91d01b52013-07-26 08:47:48442 }
[email protected]9d5eadf2012-10-09 03:43:48443
[email protected]c9eb50582012-11-05 20:08:24444 receive_callback_->Run(DoReceive());
[email protected]9d5eadf2012-10-09 03:43:48445}
446
447void WebSocketResource::OnPluginMsgErrorReply(
448 const ResourceMessageReplyParams& params) {
449 error_was_received_ = true;
450
[email protected]91d01b52013-07-26 08:47:48451 if (!TrackedCallback::IsPending(receive_callback_) ||
452 TrackedCallback::IsScheduledToRun(receive_callback_)) {
[email protected]9d5eadf2012-10-09 03:43:48453 return;
[email protected]91d01b52013-07-26 08:47:48454 }
[email protected]9d5eadf2012-10-09 03:43:48455
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;
[email protected]c9eb50582012-11-05 20:08:24459 receive_callback_->Run(PP_ERROR_FAILED);
[email protected]9d5eadf2012-10-09 03:43:48460}
461
462void WebSocketResource::OnPluginMsgBufferedAmountReply(
463 const ResourceMessageReplyParams& params,
464 unsigned long buffered_amount) {
465 buffered_amount_ = buffered_amount;
466}
467
468void WebSocketResource::OnPluginMsgStateReply(
469 const ResourceMessageReplyParams& params,
470 int32_t state) {
471 state_ = static_cast<PP_WebSocketReadyState>(state);
472}
473
474void 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
483int32_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