websocket_resource.cc revision 868fa2fe829687343ffae624259930155e16dbd8
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 <vector>
9
10#include "base/bind.h"
11#include "ppapi/c/pp_errors.h"
12#include "ppapi/proxy/dispatch_reply_message.h"
13#include "ppapi/proxy/ppapi_messages.h"
14#include "ppapi/shared_impl/ppapi_globals.h"
15#include "ppapi/shared_impl/var.h"
16#include "ppapi/shared_impl/var_tracker.h"
17#include "third_party/WebKit/Source/WebKit/chromium/public/WebSocket.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  WebKit::WebSocket::CloseEventCode event_code =
140      static_cast<WebKit::WebSocket::CloseEventCode>(code);
141  if (code == PP_WEBSOCKETSTATUSCODE_NOT_SPECIFIED) {
142    // PP_WEBSOCKETSTATUSCODE_NOT_SPECIFIED and CloseEventCodeNotSpecified are
143    // assigned to different values. A conversion is needed if
144    // PP_WEBSOCKETSTATUSCODE_NOT_SPECIFIED is specified.
145    event_code = WebKit::WebSocket::CloseEventCodeNotSpecified;
146  } else {
147    if (!(code == PP_WEBSOCKETSTATUSCODE_NORMAL_CLOSURE ||
148        (PP_WEBSOCKETSTATUSCODE_USER_REGISTERED_MIN <= code &&
149        code <= PP_WEBSOCKETSTATUSCODE_USER_PRIVATE_MAX)))
150      // RFC 6455 limits applications to use reserved connection close code in
151      // section 7.4.2.. The WebSocket API (http://www.w3.org/TR/websockets/)
152      // defines this out of range error as InvalidAccessError in JavaScript.
153      return PP_ERROR_NOACCESS;
154
155    // |reason| must be ignored if it is PP_VARTYPE_UNDEFINED or |code| is
156    // PP_WEBSOCKETSTATUSCODE_NOT_SPECIFIED.
157    if (reason.type != PP_VARTYPE_UNDEFINED) {
158      // Validate |reason|.
159      reason_string_var = StringVar::FromPPVar(reason);
160      if (!reason_string_var.get() ||
161          reason_string_var->value().size() > kMaxReasonSizeInBytes)
162        return PP_ERROR_BADARGUMENT;
163      reason_string = reason_string_var->value();
164    }
165  }
166
167  // Check state.
168  if (state_ == PP_WEBSOCKETREADYSTATE_CLOSING)
169    return PP_ERROR_INPROGRESS;
170  if (state_ == PP_WEBSOCKETREADYSTATE_CLOSED)
171    return PP_OK;
172
173  // Install |callback|.
174  close_callback_ = callback;
175
176  // Abort ongoing connect.
177  if (TrackedCallback::IsPending(connect_callback_)) {
178    state_ = PP_WEBSOCKETREADYSTATE_CLOSING;
179    // Need to do a "Post" to avoid reentering the plugin.
180    connect_callback_->PostAbort();
181    connect_callback_ = NULL;
182    Post(RENDERER, PpapiHostMsg_WebSocket_Fail(
183        "WebSocket was closed before the connection was established."));
184    return PP_OK_COMPLETIONPENDING;
185  }
186
187  // Abort ongoing receive.
188  if (TrackedCallback::IsPending(receive_callback_)) {
189    receive_callback_var_ = NULL;
190    // Need to do a "Post" to avoid reentering the plugin.
191    receive_callback_->PostAbort();
192    receive_callback_ = NULL;
193  }
194
195  // Close connection.
196  state_ = PP_WEBSOCKETREADYSTATE_CLOSING;
197  PpapiHostMsg_WebSocket_Close msg(static_cast<int32_t>(event_code),
198                                   reason_string);
199  Call<PpapiPluginMsg_WebSocket_CloseReply>(RENDERER, msg,
200      base::Bind(&WebSocketResource::OnPluginMsgCloseReply, this));
201  return PP_OK_COMPLETIONPENDING;
202}
203
204int32_t WebSocketResource::ReceiveMessage(
205    PP_Var* message,
206    scoped_refptr<TrackedCallback> callback) {
207  if (TrackedCallback::IsPending(receive_callback_))
208    return PP_ERROR_INPROGRESS;
209
210  // Check state.
211  if (state_ == PP_WEBSOCKETREADYSTATE_INVALID ||
212      state_ == PP_WEBSOCKETREADYSTATE_CONNECTING)
213    return PP_ERROR_BADARGUMENT;
214
215  // Just return received message if any received message is queued.
216  if (!received_messages_.empty()) {
217    receive_callback_var_ = message;
218    return DoReceive();
219  }
220
221  // Check state again. In CLOSED state, no more messages will be received.
222  if (state_ == PP_WEBSOCKETREADYSTATE_CLOSED)
223    return PP_ERROR_BADARGUMENT;
224
225  // Returns PP_ERROR_FAILED after an error is received and received messages
226  // is exhausted.
227  if (error_was_received_)
228    return PP_ERROR_FAILED;
229
230  // Or retain |message| as buffer to store and install |callback|.
231  receive_callback_var_ = message;
232  receive_callback_ = callback;
233
234  return PP_OK_COMPLETIONPENDING;
235}
236
237int32_t WebSocketResource::SendMessage(const PP_Var& message) {
238  // Check state.
239  if (state_ == PP_WEBSOCKETREADYSTATE_INVALID ||
240      state_ == PP_WEBSOCKETREADYSTATE_CONNECTING)
241    return PP_ERROR_BADARGUMENT;
242
243  if (state_ == PP_WEBSOCKETREADYSTATE_CLOSING ||
244      state_ == PP_WEBSOCKETREADYSTATE_CLOSED) {
245    // Handle buffered_amount_after_close_.
246    uint64_t payload_size = 0;
247    if (message.type == PP_VARTYPE_STRING) {
248      scoped_refptr<StringVar> message_string = StringVar::FromPPVar(message);
249      if (message_string.get())
250        payload_size += message_string->value().length();
251    } else if (message.type == PP_VARTYPE_ARRAY_BUFFER) {
252      scoped_refptr<ArrayBufferVar> message_array_buffer =
253          ArrayBufferVar::FromPPVar(message);
254      if (message_array_buffer.get())
255        payload_size += message_array_buffer->ByteLength();
256    } else {
257      // TODO(toyoshim): Support Blob.
258      return PP_ERROR_NOTSUPPORTED;
259    }
260
261    buffered_amount_after_close_ =
262        SaturateAdd(buffered_amount_after_close_, GetFrameSize(payload_size));
263
264    return PP_ERROR_FAILED;
265  }
266
267  // Send the message.
268  if (message.type == PP_VARTYPE_STRING) {
269    // Convert message to std::string, then send it.
270    scoped_refptr<StringVar> message_string = StringVar::FromPPVar(message);
271    if (!message_string.get())
272      return PP_ERROR_BADARGUMENT;
273    Post(RENDERER, PpapiHostMsg_WebSocket_SendText(message_string->value()));
274  } else if (message.type == PP_VARTYPE_ARRAY_BUFFER) {
275    // Convert message to std::vector<uint8_t>, then send it.
276    scoped_refptr<ArrayBufferVar> message_arraybuffer =
277        ArrayBufferVar::FromPPVar(message);
278    if (!message_arraybuffer.get())
279      return PP_ERROR_BADARGUMENT;
280    uint8_t* message_data = static_cast<uint8_t*>(message_arraybuffer->Map());
281    uint32 message_length = message_arraybuffer->ByteLength();
282    std::vector<uint8_t> message_vector(message_data,
283                                        message_data + message_length);
284    Post(RENDERER, PpapiHostMsg_WebSocket_SendBinary(message_vector));
285  } else {
286    // TODO(toyoshim): Support Blob.
287    return PP_ERROR_NOTSUPPORTED;
288  }
289  return PP_OK;
290}
291
292uint64_t WebSocketResource::GetBufferedAmount() {
293  return SaturateAdd(buffered_amount_, buffered_amount_after_close_);
294}
295
296uint16_t WebSocketResource::GetCloseCode() {
297  return close_code_;
298}
299
300PP_Var WebSocketResource::GetCloseReason() {
301  if (!close_reason_.get())
302    return empty_string_->GetPPVar();
303  return close_reason_->GetPPVar();
304}
305
306PP_Bool WebSocketResource::GetCloseWasClean() {
307  return close_was_clean_;
308}
309
310PP_Var WebSocketResource::GetExtensions() {
311  return StringVar::StringToPPVar(std::string());
312}
313
314PP_Var WebSocketResource::GetProtocol() {
315  if (!protocol_.get())
316    return empty_string_->GetPPVar();
317  return protocol_->GetPPVar();
318}
319
320PP_WebSocketReadyState WebSocketResource::GetReadyState() {
321  return state_;
322}
323
324PP_Var WebSocketResource::GetURL() {
325  if (!url_.get())
326    return empty_string_->GetPPVar();
327  return url_->GetPPVar();
328}
329
330void WebSocketResource::OnReplyReceived(
331    const ResourceMessageReplyParams& params,
332    const IPC::Message& msg) {
333  if (params.sequence()) {
334    PluginResource::OnReplyReceived(params, msg);
335    return;
336  }
337
338  IPC_BEGIN_MESSAGE_MAP(WebSocketResource, msg)
339    PPAPI_DISPATCH_PLUGIN_RESOURCE_CALL(
340        PpapiPluginMsg_WebSocket_ReceiveTextReply,
341        OnPluginMsgReceiveTextReply)
342    PPAPI_DISPATCH_PLUGIN_RESOURCE_CALL(
343        PpapiPluginMsg_WebSocket_ReceiveBinaryReply,
344        OnPluginMsgReceiveBinaryReply)
345    PPAPI_DISPATCH_PLUGIN_RESOURCE_CALL_0(
346        PpapiPluginMsg_WebSocket_ErrorReply,
347        OnPluginMsgErrorReply)
348    PPAPI_DISPATCH_PLUGIN_RESOURCE_CALL(
349        PpapiPluginMsg_WebSocket_BufferedAmountReply,
350        OnPluginMsgBufferedAmountReply)
351    PPAPI_DISPATCH_PLUGIN_RESOURCE_CALL(
352        PpapiPluginMsg_WebSocket_StateReply,
353        OnPluginMsgStateReply)
354    PPAPI_DISPATCH_PLUGIN_RESOURCE_CALL(
355        PpapiPluginMsg_WebSocket_ClosedReply,
356        OnPluginMsgClosedReply)
357    PPAPI_DISPATCH_PLUGIN_RESOURCE_CALL_UNHANDLED(NOTREACHED())
358  IPC_END_MESSAGE_MAP()
359}
360
361void WebSocketResource::OnPluginMsgConnectReply(
362    const ResourceMessageReplyParams& params,
363    const std::string& url,
364    const std::string& protocol) {
365  if (!TrackedCallback::IsPending(connect_callback_))
366    return;
367
368  int32_t result = params.result();
369  if (result == PP_OK) {
370    state_ = PP_WEBSOCKETREADYSTATE_OPEN;
371    protocol_ = new StringVar(protocol);
372    url_ = new StringVar(url);
373  }
374  connect_callback_->Run(params.result());
375}
376
377void WebSocketResource::OnPluginMsgCloseReply(
378    const ResourceMessageReplyParams& params,
379    unsigned long buffered_amount,
380    bool was_clean,
381    unsigned short code,
382    const std::string& reason) {
383  // Set close related properties.
384  state_ = PP_WEBSOCKETREADYSTATE_CLOSED;
385  buffered_amount_ = buffered_amount;
386  close_was_clean_ = PP_FromBool(was_clean);
387  close_code_ = code;
388  close_reason_ = new StringVar(reason);
389
390  if (TrackedCallback::IsPending(receive_callback_)) {
391    receive_callback_var_ = NULL;
392    receive_callback_->PostRun(PP_ERROR_FAILED);
393    receive_callback_ = NULL;
394  }
395
396  if (TrackedCallback::IsPending(close_callback_)) {
397    close_callback_->PostRun(params.result());
398    close_callback_ = NULL;
399  }
400}
401
402void WebSocketResource::OnPluginMsgReceiveTextReply(
403    const ResourceMessageReplyParams& params,
404    const std::string& message) {
405  // Dispose packets after receiving an error or in invalid state.
406  if (error_was_received_ || !InValidStateToReceive(state_))
407    return;
408
409  // Append received data to queue.
410  received_messages_.push(scoped_refptr<Var>(new StringVar(message)));
411
412  if (!TrackedCallback::IsPending(receive_callback_))
413    return;
414
415  receive_callback_->Run(DoReceive());
416}
417
418void WebSocketResource::OnPluginMsgReceiveBinaryReply(
419    const ResourceMessageReplyParams& params,
420    const std::vector<uint8_t>& message) {
421  // Dispose packets after receiving an error or in invalid state.
422  if (error_was_received_ || !InValidStateToReceive(state_))
423    return;
424
425  // Append received data to queue.
426  scoped_refptr<Var> message_var(
427      PpapiGlobals::Get()->GetVarTracker()->MakeArrayBufferVar(
428          message.size(),
429          &message.front()));
430  received_messages_.push(message_var);
431
432  if (!TrackedCallback::IsPending(receive_callback_))
433    return;
434
435  receive_callback_->Run(DoReceive());
436}
437
438void WebSocketResource::OnPluginMsgErrorReply(
439    const ResourceMessageReplyParams& params) {
440  error_was_received_ = true;
441
442  if (!TrackedCallback::IsPending(receive_callback_))
443    return;
444
445  // No more text or binary messages will be received. If there is ongoing
446  // ReceiveMessage(), we must invoke the callback with error code here.
447  receive_callback_var_ = NULL;
448  receive_callback_->Run(PP_ERROR_FAILED);
449}
450
451void WebSocketResource::OnPluginMsgBufferedAmountReply(
452    const ResourceMessageReplyParams& params,
453    unsigned long buffered_amount) {
454  buffered_amount_ = buffered_amount;
455}
456
457void WebSocketResource::OnPluginMsgStateReply(
458    const ResourceMessageReplyParams& params,
459    int32_t state) {
460  state_ = static_cast<PP_WebSocketReadyState>(state);
461}
462
463void WebSocketResource::OnPluginMsgClosedReply(
464    const ResourceMessageReplyParams& params,
465    unsigned long buffered_amount,
466    bool was_clean,
467    unsigned short code,
468    const std::string& reason) {
469  OnPluginMsgCloseReply(params, buffered_amount, was_clean, code, reason);
470}
471
472int32_t WebSocketResource::DoReceive() {
473  if (!receive_callback_var_)
474    return PP_OK;
475
476  *receive_callback_var_ = received_messages_.front()->GetPPVar();
477  received_messages_.pop();
478  receive_callback_var_ = NULL;
479  return PP_OK;
480}
481
482}  // namespace proxy
483}  // namespace ppapi
484