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.
5 #include "ppapi/proxy/websocket_resource.h"
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"
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;
27 uint64_t SaturateAdd(uint64_t a
, uint64_t b
) {
28 if (kuint64max
- a
< b
)
33 uint64_t GetFrameSize(uint64_t payload_size
) {
34 uint64_t overhead
= kBaseFramingOverhead
+ kMaskingKeyLength
;
35 if (payload_size
> kMinimumPayloadSizeWithEightByteExtendedPayloadLength
)
37 else if (payload_size
> kMinimumPayloadSizeWithTwoByteExtendedPayloadLength
)
39 return SaturateAdd(payload_size
, overhead
);
42 bool InValidStateToReceive(PP_WebSocketReadyState state
) {
43 return state
== PP_WEBSOCKETREADYSTATE_OPEN
||
44 state
== PP_WEBSOCKETREADYSTATE_CLOSING
;
53 WebSocketResource::WebSocketResource(Connection connection
,
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())),
62 close_was_clean_(PP_FALSE
),
67 buffered_amount_after_close_(0) {
70 WebSocketResource::~WebSocketResource() {
73 thunk::PPB_WebSocket_API
* WebSocketResource::AsPPB_WebSocket_API() {
77 int32_t WebSocketResource::Connect(
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
;
85 // Connect() can be called at most once.
86 if (state_
!= PP_WEBSOCKETREADYSTATE_INVALID
)
87 return PP_ERROR_INPROGRESS
;
88 state_
= PP_WEBSOCKETREADYSTATE_CLOSED
;
91 url_
= StringVar::FromPPVar(url
);
93 return PP_ERROR_BADARGUMENT
;
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
]));
102 // Check invalid and empty entries.
103 if (!protocol
.get() || !protocol
->value().length())
104 return PP_ERROR_BADARGUMENT
;
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());
111 protocol_strings
.push_back(protocol
->value());
115 connect_callback_
= callback
;
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));
125 return PP_OK_COMPLETIONPENDING
;
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
;
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
;
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();
161 if (state_
== PP_WEBSOCKETREADYSTATE_CLOSING
)
162 return PP_ERROR_INPROGRESS
;
163 if (state_
== PP_WEBSOCKETREADYSTATE_CLOSED
)
166 // Install |callback|.
167 close_callback_
= callback
;
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
;
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
;
189 state_
= PP_WEBSOCKETREADYSTATE_CLOSING
;
190 PpapiHostMsg_WebSocket_Close
msg(static_cast<int32_t>(code
),
192 Call
<PpapiPluginMsg_WebSocket_CloseReply
>(RENDERER
, msg
,
193 base::Bind(&WebSocketResource::OnPluginMsgCloseReply
, this));
194 return PP_OK_COMPLETIONPENDING
;
197 int32_t WebSocketResource::ReceiveMessage(
199 scoped_refptr
<TrackedCallback
> callback
) {
200 if (TrackedCallback::IsPending(receive_callback_
))
201 return PP_ERROR_INPROGRESS
;
204 if (state_
== PP_WEBSOCKETREADYSTATE_INVALID
||
205 state_
== PP_WEBSOCKETREADYSTATE_CONNECTING
)
206 return PP_ERROR_BADARGUMENT
;
208 // Just return received message if any received message is queued.
209 if (!received_messages_
.empty()) {
210 receive_callback_var_
= message
;
214 // Check state again. In CLOSED state, no more messages will be received.
215 if (state_
== PP_WEBSOCKETREADYSTATE_CLOSED
)
216 return PP_ERROR_BADARGUMENT
;
218 // Returns PP_ERROR_FAILED after an error is received and received messages
220 if (error_was_received_
)
221 return PP_ERROR_FAILED
;
223 // Or retain |message| as buffer to store and install |callback|.
224 receive_callback_var_
= message
;
225 receive_callback_
= callback
;
227 return PP_OK_COMPLETIONPENDING
;
230 int32_t WebSocketResource::SendMessage(const PP_Var
& message
) {
232 if (state_
== PP_WEBSOCKETREADYSTATE_INVALID
||
233 state_
== PP_WEBSOCKETREADYSTATE_CONNECTING
)
234 return PP_ERROR_BADARGUMENT
;
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();
250 // TODO(toyoshim): Support Blob.
251 return PP_ERROR_NOTSUPPORTED
;
254 buffered_amount_after_close_
=
255 SaturateAdd(buffered_amount_after_close_
, GetFrameSize(payload_size
));
257 return PP_ERROR_FAILED
;
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
));
279 // TODO(toyoshim): Support Blob.
280 return PP_ERROR_NOTSUPPORTED
;
285 uint64_t WebSocketResource::GetBufferedAmount() {
286 return SaturateAdd(buffered_amount_
, buffered_amount_after_close_
);
289 uint16_t WebSocketResource::GetCloseCode() {
293 PP_Var
WebSocketResource::GetCloseReason() {
294 if (!close_reason_
.get())
295 return empty_string_
->GetPPVar();
296 return close_reason_
->GetPPVar();
299 PP_Bool
WebSocketResource::GetCloseWasClean() {
300 return close_was_clean_
;
303 PP_Var
WebSocketResource::GetExtensions() {
304 return StringVar::StringToPPVar(std::string());
307 PP_Var
WebSocketResource::GetProtocol() {
308 if (!protocol_
.get())
309 return empty_string_
->GetPPVar();
310 return protocol_
->GetPPVar();
313 PP_WebSocketReadyState
WebSocketResource::GetReadyState() {
317 PP_Var
WebSocketResource::GetURL() {
319 return empty_string_
->GetPPVar();
320 return url_
->GetPPVar();
323 void WebSocketResource::OnReplyReceived(
324 const ResourceMessageReplyParams
& params
,
325 const IPC::Message
& msg
) {
326 if (params
.sequence()) {
327 PluginResource::OnReplyReceived(params
, msg
);
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()
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_
)) {
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
);
369 connect_callback_
->Run(params
.result());
372 void WebSocketResource::OnPluginMsgCloseReply(
373 const ResourceMessageReplyParams
& params
,
374 unsigned long buffered_amount
,
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
);
383 close_reason_
= new StringVar(reason
);
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
;
392 if (TrackedCallback::IsPending(close_callback_
)) {
393 if (!TrackedCallback::IsScheduledToRun(close_callback_
))
394 close_callback_
->PostRun(params
.result());
395 close_callback_
= NULL
;
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_
))
406 // Append received data to queue.
407 received_messages_
.push(scoped_refptr
<Var
>(new StringVar(message
)));
409 if (!TrackedCallback::IsPending(receive_callback_
) ||
410 TrackedCallback::IsScheduledToRun(receive_callback_
)) {
414 receive_callback_
->Run(DoReceive());
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_
))
424 // Append received data to queue.
425 scoped_refptr
<Var
> message_var(
426 PpapiGlobals::Get()->GetVarTracker()->MakeArrayBufferVar(
429 received_messages_
.push(message_var
);
431 if (!TrackedCallback::IsPending(receive_callback_
) ||
432 TrackedCallback::IsScheduledToRun(receive_callback_
)) {
436 receive_callback_
->Run(DoReceive());
439 void WebSocketResource::OnPluginMsgErrorReply(
440 const ResourceMessageReplyParams
& params
) {
441 error_was_received_
= true;
443 if (!TrackedCallback::IsPending(receive_callback_
) ||
444 TrackedCallback::IsScheduledToRun(receive_callback_
)) {
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
);
454 void WebSocketResource::OnPluginMsgBufferedAmountReply(
455 const ResourceMessageReplyParams
& params
,
456 unsigned long buffered_amount
) {
457 buffered_amount_
= buffered_amount
;
460 void WebSocketResource::OnPluginMsgStateReply(
461 const ResourceMessageReplyParams
& params
,
463 state_
= static_cast<PP_WebSocketReadyState
>(state
);
466 void WebSocketResource::OnPluginMsgClosedReply(
467 const ResourceMessageReplyParams
& params
,
468 unsigned long buffered_amount
,
471 const std::string
& reason
) {
472 OnPluginMsgCloseReply(params
, buffered_amount
, was_clean
, code
, reason
);
475 int32_t WebSocketResource::DoReceive() {
476 if (!receive_callback_var_
)
479 *receive_callback_var_
= received_messages_
.front()->GetPPVar();
480 received_messages_
.pop();
481 receive_callback_var_
= NULL
;