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
19namespace {
20
21const uint32_t kMaxReasonSizeInBytes = 123;
22const size_t kBaseFramingOverhead = 2;
23const size_t kMaskingKeyLength = 4;
24const size_t kMinimumPayloadSizeWithTwoByteExtendedPayloadLength = 126;
25const size_t kMinimumPayloadSizeWithEightByteExtendedPayloadLength = 0x10000;
26
27uint64_t SaturateAdd(uint64_t a, uint64_t b) {
28  if (kuint64max - a < b)
29    return kuint64max;
30  return a + b;
31}
32
33uint64_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
42bool InValidStateToReceive(PP_WebSocketReadyState state) {
43  return state == PP_WEBSOCKETREADYSTATE_OPEN ||
44         state == PP_WEBSOCKETREADYSTATE_CLOSING;
45}
46
47}  // namespace
48
49
50namespace ppapi {
51namespace proxy {
52
53WebSocketResource::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
70WebSocketResource::~WebSocketResource() {
71}
72
73thunk::PPB_WebSocket_API* WebSocketResource::AsPPB_WebSocket_API() {
74  return this;
75}
76
77int32_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
128int32_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
197int32_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
230int32_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
285uint64_t WebSocketResource::GetBufferedAmount() {
286  return SaturateAdd(buffered_amount_, buffered_amount_after_close_);
287}
288
289uint16_t WebSocketResource::GetCloseCode() {
290  return close_code_;
291}
292
293PP_Var WebSocketResource::GetCloseReason() {
294  if (!close_reason_.get())
295    return empty_string_->GetPPVar();
296  return close_reason_->GetPPVar();
297}
298
299PP_Bool WebSocketResource::GetCloseWasClean() {
300  return close_was_clean_;
301}
302
303PP_Var WebSocketResource::GetExtensions() {
304  return StringVar::StringToPPVar(std::string());
305}
306
307PP_Var WebSocketResource::GetProtocol() {
308  if (!protocol_.get())
309    return empty_string_->GetPPVar();
310  return protocol_->GetPPVar();
311}
312
313PP_WebSocketReadyState WebSocketResource::GetReadyState() {
314  return state_;
315}
316
317PP_Var WebSocketResource::GetURL() {
318  if (!url_.get())
319    return empty_string_->GetPPVar();
320  return url_->GetPPVar();
321}
322
323void 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
354void 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
372void 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
399void 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
417void 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
439void 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
454void WebSocketResource::OnPluginMsgBufferedAmountReply(
455    const ResourceMessageReplyParams& params,
456    unsigned long buffered_amount) {
457  buffered_amount_ = buffered_amount;
458}
459
460void WebSocketResource::OnPluginMsgStateReply(
461    const ResourceMessageReplyParams& params,
462    int32_t state) {
463  state_ = static_cast<PP_WebSocketReadyState>(state);
464}
465
466void 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
475int32_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