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#include "third_party/WebKit/public/web/WebSocket.h"
19
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);
93  if (!url_.get())
94    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.
104    if (!protocol.get() || !protocol->value().length())
105      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;
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));
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;
140  blink::WebSocket::CloseEventCode event_code =
141      static_cast<blink::WebSocket::CloseEventCode>(code);
142  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.
146    event_code = blink::WebSocket::CloseEventCodeNotSpecified;
147  } 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);
161      if (!reason_string_var.get() ||
162          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;
183    Post(RENDERER, PpapiHostMsg_WebSocket_Fail(
184        "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);
200  Call<PpapiPluginMsg_WebSocket_CloseReply>(RENDERER, msg,
201      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);
250      if (message_string.get())
251        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);
255      if (message_array_buffer.get())
256        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);
272    if (!message_string.get())
273      return PP_ERROR_BADARGUMENT;
274    Post(RENDERER, PpapiHostMsg_WebSocket_SendText(message_string->value()));
275  } 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);
279    if (!message_arraybuffer.get())
280      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);
285    Post(RENDERER, PpapiHostMsg_WebSocket_SendBinary(message_vector));
286  } 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() {
302  if (!close_reason_.get())
303    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() {
316  if (!protocol_.get())
317    return empty_string_->GetPPVar();
318  return protocol_->GetPPVar();
319}
320
321PP_WebSocketReadyState WebSocketResource::GetReadyState() {
322  return state_;
323}
324
325PP_Var WebSocketResource::GetURL() {
326  if (!url_.get())
327    return empty_string_->GetPPVar();
328  return url_->GetPPVar();
329}
330
331void WebSocketResource::OnReplyReceived(
332    const ResourceMessageReplyParams& params,
333    const IPC::Message& msg) {
334  if (params.sequence()) {
335    PluginResource::OnReplyReceived(params, msg);
336    return;
337  }
338
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()
360}
361
362void WebSocketResource::OnPluginMsgConnectReply(
363    const ResourceMessageReplyParams& params,
364    const std::string& url,
365    const std::string& protocol) {
366  if (!TrackedCallback::IsPending(connect_callback_) ||
367      TrackedCallback::IsScheduledToRun(connect_callback_)) {
368    return;
369  }
370
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  }
377  connect_callback_->Run(params.result());
378}
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;
395    if (!TrackedCallback::IsScheduledToRun(receive_callback_))
396      receive_callback_->PostRun(PP_ERROR_FAILED);
397    receive_callback_ = NULL;
398  }
399
400  if (TrackedCallback::IsPending(close_callback_)) {
401    if (!TrackedCallback::IsScheduledToRun(close_callback_))
402      close_callback_->PostRun(params.result());
403    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
417  if (!TrackedCallback::IsPending(receive_callback_) ||
418      TrackedCallback::IsScheduledToRun(receive_callback_)) {
419    return;
420  }
421
422  receive_callback_->Run(DoReceive());
423}
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.
433  scoped_refptr<Var> message_var(
434      PpapiGlobals::Get()->GetVarTracker()->MakeArrayBufferVar(
435          message.size(),
436          &message.front()));
437  received_messages_.push(message_var);
438
439  if (!TrackedCallback::IsPending(receive_callback_) ||
440      TrackedCallback::IsScheduledToRun(receive_callback_)) {
441    return;
442  }
443
444  receive_callback_->Run(DoReceive());
445}
446
447void WebSocketResource::OnPluginMsgErrorReply(
448    const ResourceMessageReplyParams& params) {
449  error_was_received_ = true;
450
451  if (!TrackedCallback::IsPending(receive_callback_) ||
452      TrackedCallback::IsScheduledToRun(receive_callback_)) {
453    return;
454  }
455
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;
459  receive_callback_->Run(PP_ERROR_FAILED);
460}
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
495