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.
4
5 #include "ppapi/proxy/websocket_resource.h"
6
7 #include <set>
8 #include <string>
9 #include <vector>
10
11 #include "base/bind.h"
12 #include "ppapi/c/pp_errors.h"
13 #include "ppapi/proxy/dispatch_reply_message.h"
14 #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"
18
19 namespace {
20
21 const uint32_t kMaxReasonSizeInBytes = 123;
22 const size_t kBaseFramingOverhead = 2;
23 const size_t kMaskingKeyLength = 4;
24 const size_t kMinimumPayloadSizeWithTwoByteExtendedPayloadLength = 126;
25 const size_t kMinimumPayloadSizeWithEightByteExtendedPayloadLength = 0x10000;
26
SaturateAdd(uint64_t a,uint64_t b)27 uint64_t SaturateAdd(uint64_t a, uint64_t b) {
28 if (kuint64max - a < b)
29 return kuint64max;
30 return a + b;
31 }
32
GetFrameSize(uint64_t payload_size)33 uint64_t GetFrameSize(uint64_t payload_size) {
34 uint64_t overhead = kBaseFramingOverhead + kMaskingKeyLength;
35 if (payload_size > kMinimumPayloadSizeWithEightByteExtendedPayloadLength)
36 overhead += 8;
37 else if (payload_size > kMinimumPayloadSizeWithTwoByteExtendedPayloadLength)
38 overhead += 2;
39 return SaturateAdd(payload_size, overhead);
40 }
41
InValidStateToReceive(PP_WebSocketReadyState state)42 bool InValidStateToReceive(PP_WebSocketReadyState state) {
43 return state == PP_WEBSOCKETREADYSTATE_OPEN ||
44 state == PP_WEBSOCKETREADYSTATE_CLOSING;
45 }
46
47 } // namespace
48
49
50 namespace ppapi {
51 namespace proxy {
52
WebSocketResource(Connection connection,PP_Instance instance)53 WebSocketResource::WebSocketResource(Connection connection,
54 PP_Instance instance)
55 : PluginResource(connection, instance),
56 state_(PP_WEBSOCKETREADYSTATE_INVALID),
57 error_was_received_(false),
58 receive_callback_var_(NULL),
59 empty_string_(new StringVar(std::string())),
60 close_code_(0),
61 close_reason_(NULL),
62 close_was_clean_(PP_FALSE),
63 extensions_(NULL),
64 protocol_(NULL),
65 url_(NULL),
66 buffered_amount_(0),
67 buffered_amount_after_close_(0) {
68 }
69
~WebSocketResource()70 WebSocketResource::~WebSocketResource() {
71 }
72
AsPPB_WebSocket_API()73 thunk::PPB_WebSocket_API* WebSocketResource::AsPPB_WebSocket_API() {
74 return this;
75 }
76
Connect(const PP_Var & url,const PP_Var protocols[],uint32_t protocol_count,scoped_refptr<TrackedCallback> callback)77 int32_t WebSocketResource::Connect(
78 const PP_Var& url,
79 const PP_Var protocols[],
80 uint32_t protocol_count,
81 scoped_refptr<TrackedCallback> callback) {
82 if (TrackedCallback::IsPending(connect_callback_))
83 return PP_ERROR_INPROGRESS;
84
85 // Connect() can be called at most once.
86 if (state_ != PP_WEBSOCKETREADYSTATE_INVALID)
87 return PP_ERROR_INPROGRESS;
88 state_ = PP_WEBSOCKETREADYSTATE_CLOSED;
89
90 // Get the URL.
91 url_ = StringVar::FromPPVar(url);
92 if (!url_.get())
93 return PP_ERROR_BADARGUMENT;
94
95 // Get the protocols.
96 std::set<std::string> protocol_set;
97 std::vector<std::string> protocol_strings;
98 protocol_strings.reserve(protocol_count);
99 for (uint32_t i = 0; i < protocol_count; ++i) {
100 scoped_refptr<StringVar> protocol(StringVar::FromPPVar(protocols[i]));
101
102 // Check invalid and empty entries.
103 if (!protocol.get() || !protocol->value().length())
104 return PP_ERROR_BADARGUMENT;
105
106 // Check duplicated protocol entries.
107 if (protocol_set.find(protocol->value()) != protocol_set.end())
108 return PP_ERROR_BADARGUMENT;
109 protocol_set.insert(protocol->value());
110
111 protocol_strings.push_back(protocol->value());
112 }
113
114 // Install callback.
115 connect_callback_ = callback;
116
117 // Create remote host in the renderer, then request to check the URL and
118 // establish the connection.
119 state_ = PP_WEBSOCKETREADYSTATE_CONNECTING;
120 SendCreate(RENDERER, PpapiHostMsg_WebSocket_Create());
121 PpapiHostMsg_WebSocket_Connect msg(url_->value(), protocol_strings);
122 Call<PpapiPluginMsg_WebSocket_ConnectReply>(RENDERER, msg,
123 base::Bind(&WebSocketResource::OnPluginMsgConnectReply, this));
124
125 return PP_OK_COMPLETIONPENDING;
126 }
127
Close(uint16_t code,const PP_Var & reason,scoped_refptr<TrackedCallback> callback)128 int32_t WebSocketResource::Close(uint16_t code,
129 const PP_Var& reason,
130 scoped_refptr<TrackedCallback> callback) {
131 if (TrackedCallback::IsPending(close_callback_))
132 return PP_ERROR_INPROGRESS;
133 if (state_ == PP_WEBSOCKETREADYSTATE_INVALID)
134 return PP_ERROR_FAILED;
135
136 // Validate |code| and |reason|.
137 scoped_refptr<StringVar> reason_string_var;
138 std::string reason_string;
139 if (code != PP_WEBSOCKETSTATUSCODE_NOT_SPECIFIED) {
140 if (code != PP_WEBSOCKETSTATUSCODE_NORMAL_CLOSURE &&
141 (code < PP_WEBSOCKETSTATUSCODE_USER_REGISTERED_MIN ||
142 code > PP_WEBSOCKETSTATUSCODE_USER_PRIVATE_MAX))
143 // RFC 6455 limits applications to use reserved connection close code in
144 // section 7.4.2.. The WebSocket API (http://www.w3.org/TR/websockets/)
145 // defines this out of range error as InvalidAccessError in JavaScript.
146 return PP_ERROR_NOACCESS;
147
148 // |reason| must be ignored if it is PP_VARTYPE_UNDEFINED or |code| is
149 // PP_WEBSOCKETSTATUSCODE_NOT_SPECIFIED.
150 if (reason.type != PP_VARTYPE_UNDEFINED) {
151 // Validate |reason|.
152 reason_string_var = StringVar::FromPPVar(reason);
153 if (!reason_string_var.get() ||
154 reason_string_var->value().size() > kMaxReasonSizeInBytes)
155 return PP_ERROR_BADARGUMENT;
156 reason_string = reason_string_var->value();
157 }
158 }
159
160 // Check state.
161 if (state_ == PP_WEBSOCKETREADYSTATE_CLOSING)
162 return PP_ERROR_INPROGRESS;
163 if (state_ == PP_WEBSOCKETREADYSTATE_CLOSED)
164 return PP_OK;
165
166 // Install |callback|.
167 close_callback_ = callback;
168
169 // Abort ongoing connect.
170 if (TrackedCallback::IsPending(connect_callback_)) {
171 state_ = PP_WEBSOCKETREADYSTATE_CLOSING;
172 // Need to do a "Post" to avoid reentering the plugin.
173 connect_callback_->PostAbort();
174 connect_callback_ = NULL;
175 Post(RENDERER, PpapiHostMsg_WebSocket_Fail(
176 "WebSocket was closed before the connection was established."));
177 return PP_OK_COMPLETIONPENDING;
178 }
179
180 // Abort ongoing receive.
181 if (TrackedCallback::IsPending(receive_callback_)) {
182 receive_callback_var_ = NULL;
183 // Need to do a "Post" to avoid reentering the plugin.
184 receive_callback_->PostAbort();
185 receive_callback_ = NULL;
186 }
187
188 // Close connection.
189 state_ = PP_WEBSOCKETREADYSTATE_CLOSING;
190 PpapiHostMsg_WebSocket_Close msg(static_cast<int32_t>(code),
191 reason_string);
192 Call<PpapiPluginMsg_WebSocket_CloseReply>(RENDERER, msg,
193 base::Bind(&WebSocketResource::OnPluginMsgCloseReply, this));
194 return PP_OK_COMPLETIONPENDING;
195 }
196
ReceiveMessage(PP_Var * message,scoped_refptr<TrackedCallback> callback)197 int32_t WebSocketResource::ReceiveMessage(
198 PP_Var* message,
199 scoped_refptr<TrackedCallback> callback) {
200 if (TrackedCallback::IsPending(receive_callback_))
201 return PP_ERROR_INPROGRESS;
202
203 // Check state.
204 if (state_ == PP_WEBSOCKETREADYSTATE_INVALID ||
205 state_ == PP_WEBSOCKETREADYSTATE_CONNECTING)
206 return PP_ERROR_BADARGUMENT;
207
208 // Just return received message if any received message is queued.
209 if (!received_messages_.empty()) {
210 receive_callback_var_ = message;
211 return DoReceive();
212 }
213
214 // Check state again. In CLOSED state, no more messages will be received.
215 if (state_ == PP_WEBSOCKETREADYSTATE_CLOSED)
216 return PP_ERROR_BADARGUMENT;
217
218 // Returns PP_ERROR_FAILED after an error is received and received messages
219 // is exhausted.
220 if (error_was_received_)
221 return PP_ERROR_FAILED;
222
223 // Or retain |message| as buffer to store and install |callback|.
224 receive_callback_var_ = message;
225 receive_callback_ = callback;
226
227 return PP_OK_COMPLETIONPENDING;
228 }
229
SendMessage(const PP_Var & message)230 int32_t WebSocketResource::SendMessage(const PP_Var& message) {
231 // Check state.
232 if (state_ == PP_WEBSOCKETREADYSTATE_INVALID ||
233 state_ == PP_WEBSOCKETREADYSTATE_CONNECTING)
234 return PP_ERROR_BADARGUMENT;
235
236 if (state_ == PP_WEBSOCKETREADYSTATE_CLOSING ||
237 state_ == PP_WEBSOCKETREADYSTATE_CLOSED) {
238 // Handle buffered_amount_after_close_.
239 uint64_t payload_size = 0;
240 if (message.type == PP_VARTYPE_STRING) {
241 scoped_refptr<StringVar> message_string = StringVar::FromPPVar(message);
242 if (message_string.get())
243 payload_size += message_string->value().length();
244 } else if (message.type == PP_VARTYPE_ARRAY_BUFFER) {
245 scoped_refptr<ArrayBufferVar> message_array_buffer =
246 ArrayBufferVar::FromPPVar(message);
247 if (message_array_buffer.get())
248 payload_size += message_array_buffer->ByteLength();
249 } else {
250 // TODO(toyoshim): Support Blob.
251 return PP_ERROR_NOTSUPPORTED;
252 }
253
254 buffered_amount_after_close_ =
255 SaturateAdd(buffered_amount_after_close_, GetFrameSize(payload_size));
256
257 return PP_ERROR_FAILED;
258 }
259
260 // Send the message.
261 if (message.type == PP_VARTYPE_STRING) {
262 // Convert message to std::string, then send it.
263 scoped_refptr<StringVar> message_string = StringVar::FromPPVar(message);
264 if (!message_string.get())
265 return PP_ERROR_BADARGUMENT;
266 Post(RENDERER, PpapiHostMsg_WebSocket_SendText(message_string->value()));
267 } else if (message.type == PP_VARTYPE_ARRAY_BUFFER) {
268 // Convert message to std::vector<uint8_t>, then send it.
269 scoped_refptr<ArrayBufferVar> message_arraybuffer =
270 ArrayBufferVar::FromPPVar(message);
271 if (!message_arraybuffer.get())
272 return PP_ERROR_BADARGUMENT;
273 uint8_t* message_data = static_cast<uint8_t*>(message_arraybuffer->Map());
274 uint32 message_length = message_arraybuffer->ByteLength();
275 std::vector<uint8_t> message_vector(message_data,
276 message_data + message_length);
277 Post(RENDERER, PpapiHostMsg_WebSocket_SendBinary(message_vector));
278 } else {
279 // TODO(toyoshim): Support Blob.
280 return PP_ERROR_NOTSUPPORTED;
281 }
282 return PP_OK;
283 }
284
GetBufferedAmount()285 uint64_t WebSocketResource::GetBufferedAmount() {
286 return SaturateAdd(buffered_amount_, buffered_amount_after_close_);
287 }
288
GetCloseCode()289 uint16_t WebSocketResource::GetCloseCode() {
290 return close_code_;
291 }
292
GetCloseReason()293 PP_Var WebSocketResource::GetCloseReason() {
294 if (!close_reason_.get())
295 return empty_string_->GetPPVar();
296 return close_reason_->GetPPVar();
297 }
298
GetCloseWasClean()299 PP_Bool WebSocketResource::GetCloseWasClean() {
300 return close_was_clean_;
301 }
302
GetExtensions()303 PP_Var WebSocketResource::GetExtensions() {
304 return StringVar::StringToPPVar(std::string());
305 }
306
GetProtocol()307 PP_Var WebSocketResource::GetProtocol() {
308 if (!protocol_.get())
309 return empty_string_->GetPPVar();
310 return protocol_->GetPPVar();
311 }
312
GetReadyState()313 PP_WebSocketReadyState WebSocketResource::GetReadyState() {
314 return state_;
315 }
316
GetURL()317 PP_Var WebSocketResource::GetURL() {
318 if (!url_.get())
319 return empty_string_->GetPPVar();
320 return url_->GetPPVar();
321 }
322
OnReplyReceived(const ResourceMessageReplyParams & params,const IPC::Message & msg)323 void WebSocketResource::OnReplyReceived(
324 const ResourceMessageReplyParams& params,
325 const IPC::Message& msg) {
326 if (params.sequence()) {
327 PluginResource::OnReplyReceived(params, msg);
328 return;
329 }
330
331 PPAPI_BEGIN_MESSAGE_MAP(WebSocketResource, msg)
332 PPAPI_DISPATCH_PLUGIN_RESOURCE_CALL(
333 PpapiPluginMsg_WebSocket_ReceiveTextReply,
334 OnPluginMsgReceiveTextReply)
335 PPAPI_DISPATCH_PLUGIN_RESOURCE_CALL(
336 PpapiPluginMsg_WebSocket_ReceiveBinaryReply,
337 OnPluginMsgReceiveBinaryReply)
338 PPAPI_DISPATCH_PLUGIN_RESOURCE_CALL_0(
339 PpapiPluginMsg_WebSocket_ErrorReply,
340 OnPluginMsgErrorReply)
341 PPAPI_DISPATCH_PLUGIN_RESOURCE_CALL(
342 PpapiPluginMsg_WebSocket_BufferedAmountReply,
343 OnPluginMsgBufferedAmountReply)
344 PPAPI_DISPATCH_PLUGIN_RESOURCE_CALL(
345 PpapiPluginMsg_WebSocket_StateReply,
346 OnPluginMsgStateReply)
347 PPAPI_DISPATCH_PLUGIN_RESOURCE_CALL(
348 PpapiPluginMsg_WebSocket_ClosedReply,
349 OnPluginMsgClosedReply)
350 PPAPI_DISPATCH_PLUGIN_RESOURCE_CALL_UNHANDLED(NOTREACHED())
351 PPAPI_END_MESSAGE_MAP()
352 }
353
OnPluginMsgConnectReply(const ResourceMessageReplyParams & params,const std::string & url,const std::string & protocol)354 void WebSocketResource::OnPluginMsgConnectReply(
355 const ResourceMessageReplyParams& params,
356 const std::string& url,
357 const std::string& protocol) {
358 if (!TrackedCallback::IsPending(connect_callback_) ||
359 TrackedCallback::IsScheduledToRun(connect_callback_)) {
360 return;
361 }
362
363 int32_t result = params.result();
364 if (result == PP_OK) {
365 state_ = PP_WEBSOCKETREADYSTATE_OPEN;
366 protocol_ = new StringVar(protocol);
367 url_ = new StringVar(url);
368 }
369 connect_callback_->Run(params.result());
370 }
371
OnPluginMsgCloseReply(const ResourceMessageReplyParams & params,unsigned long buffered_amount,bool was_clean,unsigned short code,const std::string & reason)372 void WebSocketResource::OnPluginMsgCloseReply(
373 const ResourceMessageReplyParams& params,
374 unsigned long buffered_amount,
375 bool was_clean,
376 unsigned short code,
377 const std::string& reason) {
378 // Set close related properties.
379 state_ = PP_WEBSOCKETREADYSTATE_CLOSED;
380 buffered_amount_ = buffered_amount;
381 close_was_clean_ = PP_FromBool(was_clean);
382 close_code_ = code;
383 close_reason_ = new StringVar(reason);
384
385 if (TrackedCallback::IsPending(receive_callback_)) {
386 receive_callback_var_ = NULL;
387 if (!TrackedCallback::IsScheduledToRun(receive_callback_))
388 receive_callback_->PostRun(PP_ERROR_FAILED);
389 receive_callback_ = NULL;
390 }
391
392 if (TrackedCallback::IsPending(close_callback_)) {
393 if (!TrackedCallback::IsScheduledToRun(close_callback_))
394 close_callback_->PostRun(params.result());
395 close_callback_ = NULL;
396 }
397 }
398
OnPluginMsgReceiveTextReply(const ResourceMessageReplyParams & params,const std::string & message)399 void WebSocketResource::OnPluginMsgReceiveTextReply(
400 const ResourceMessageReplyParams& params,
401 const std::string& message) {
402 // Dispose packets after receiving an error or in invalid state.
403 if (error_was_received_ || !InValidStateToReceive(state_))
404 return;
405
406 // Append received data to queue.
407 received_messages_.push(scoped_refptr<Var>(new StringVar(message)));
408
409 if (!TrackedCallback::IsPending(receive_callback_) ||
410 TrackedCallback::IsScheduledToRun(receive_callback_)) {
411 return;
412 }
413
414 receive_callback_->Run(DoReceive());
415 }
416
OnPluginMsgReceiveBinaryReply(const ResourceMessageReplyParams & params,const std::vector<uint8_t> & message)417 void WebSocketResource::OnPluginMsgReceiveBinaryReply(
418 const ResourceMessageReplyParams& params,
419 const std::vector<uint8_t>& message) {
420 // Dispose packets after receiving an error or in invalid state.
421 if (error_was_received_ || !InValidStateToReceive(state_))
422 return;
423
424 // Append received data to queue.
425 scoped_refptr<Var> message_var(
426 PpapiGlobals::Get()->GetVarTracker()->MakeArrayBufferVar(
427 message.size(),
428 &message.front()));
429 received_messages_.push(message_var);
430
431 if (!TrackedCallback::IsPending(receive_callback_) ||
432 TrackedCallback::IsScheduledToRun(receive_callback_)) {
433 return;
434 }
435
436 receive_callback_->Run(DoReceive());
437 }
438
OnPluginMsgErrorReply(const ResourceMessageReplyParams & params)439 void WebSocketResource::OnPluginMsgErrorReply(
440 const ResourceMessageReplyParams& params) {
441 error_was_received_ = true;
442
443 if (!TrackedCallback::IsPending(receive_callback_) ||
444 TrackedCallback::IsScheduledToRun(receive_callback_)) {
445 return;
446 }
447
448 // No more text or binary messages will be received. If there is ongoing
449 // ReceiveMessage(), we must invoke the callback with error code here.
450 receive_callback_var_ = NULL;
451 receive_callback_->Run(PP_ERROR_FAILED);
452 }
453
OnPluginMsgBufferedAmountReply(const ResourceMessageReplyParams & params,unsigned long buffered_amount)454 void WebSocketResource::OnPluginMsgBufferedAmountReply(
455 const ResourceMessageReplyParams& params,
456 unsigned long buffered_amount) {
457 buffered_amount_ = buffered_amount;
458 }
459
OnPluginMsgStateReply(const ResourceMessageReplyParams & params,int32_t state)460 void WebSocketResource::OnPluginMsgStateReply(
461 const ResourceMessageReplyParams& params,
462 int32_t state) {
463 state_ = static_cast<PP_WebSocketReadyState>(state);
464 }
465
OnPluginMsgClosedReply(const ResourceMessageReplyParams & params,unsigned long buffered_amount,bool was_clean,unsigned short code,const std::string & reason)466 void WebSocketResource::OnPluginMsgClosedReply(
467 const ResourceMessageReplyParams& params,
468 unsigned long buffered_amount,
469 bool was_clean,
470 unsigned short code,
471 const std::string& reason) {
472 OnPluginMsgCloseReply(params, buffered_amount, was_clean, code, reason);
473 }
474
DoReceive()475 int32_t WebSocketResource::DoReceive() {
476 if (!receive_callback_var_)
477 return PP_OK;
478
479 *receive_callback_var_ = received_messages_.front()->GetPPVar();
480 received_messages_.pop();
481 receive_callback_var_ = NULL;
482 return PP_OK;
483 }
484
485 } // namespace proxy
486 } // namespace ppapi
487