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"
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"
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
|| !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 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
;
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
;
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
||
161 reason_string_var
->value().size() > kMaxReasonSizeInBytes
)
162 return PP_ERROR_BADARGUMENT
;
163 reason_string
= reason_string_var
->value();
168 if (state_
== PP_WEBSOCKETREADYSTATE_CLOSING
)
169 return PP_ERROR_INPROGRESS
;
170 if (state_
== PP_WEBSOCKETREADYSTATE_CLOSED
)
173 // Install |callback|.
174 close_callback_
= callback
;
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
;
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
;
196 state_
= PP_WEBSOCKETREADYSTATE_CLOSING
;
197 PpapiHostMsg_WebSocket_Close
msg(static_cast<int32_t>(event_code
),
199 Call
<PpapiPluginMsg_WebSocket_CloseReply
>(RENDERER
, msg
,
200 base::Bind(&WebSocketResource::OnPluginMsgCloseReply
, this));
201 return PP_OK_COMPLETIONPENDING
;
204 int32_t WebSocketResource::ReceiveMessage(
206 scoped_refptr
<TrackedCallback
> callback
) {
207 if (TrackedCallback::IsPending(receive_callback_
))
208 return PP_ERROR_INPROGRESS
;
211 if (state_
== PP_WEBSOCKETREADYSTATE_INVALID
||
212 state_
== PP_WEBSOCKETREADYSTATE_CONNECTING
)
213 return PP_ERROR_BADARGUMENT
;
215 // Just return received message if any received message is queued.
216 if (!received_messages_
.empty()) {
217 receive_callback_var_
= message
;
221 // Check state again. In CLOSED state, no more messages will be received.
222 if (state_
== PP_WEBSOCKETREADYSTATE_CLOSED
)
223 return PP_ERROR_BADARGUMENT
;
225 // Returns PP_ERROR_FAILED after an error is received and received messages
227 if (error_was_received_
)
228 return PP_ERROR_FAILED
;
230 // Or retain |message| as buffer to store and install |callback|.
231 receive_callback_var_
= message
;
232 receive_callback_
= callback
;
234 return PP_OK_COMPLETIONPENDING
;
237 int32_t WebSocketResource::SendMessage(const PP_Var
& message
) {
239 if (state_
== PP_WEBSOCKETREADYSTATE_INVALID
||
240 state_
== PP_WEBSOCKETREADYSTATE_CONNECTING
)
241 return PP_ERROR_BADARGUMENT
;
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
);
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
)
255 payload_size
+= message_array_buffer
->ByteLength();
257 // TODO(toyoshim): Support Blob.
258 return PP_ERROR_NOTSUPPORTED
;
261 buffered_amount_after_close_
=
262 SaturateAdd(buffered_amount_after_close_
, GetFrameSize(payload_size
));
264 return PP_ERROR_FAILED
;
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
);
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
)
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
));
286 // TODO(toyoshim): Support Blob.
287 return PP_ERROR_NOTSUPPORTED
;
292 uint64_t WebSocketResource::GetBufferedAmount() {
293 return SaturateAdd(buffered_amount_
, buffered_amount_after_close_
);
296 uint16_t WebSocketResource::GetCloseCode() {
300 PP_Var
WebSocketResource::GetCloseReason() {
302 return empty_string_
->GetPPVar();
303 return close_reason_
->GetPPVar();
306 PP_Bool
WebSocketResource::GetCloseWasClean() {
307 return close_was_clean_
;
310 PP_Var
WebSocketResource::GetExtensions() {
311 return StringVar::StringToPPVar(std::string());
314 PP_Var
WebSocketResource::GetProtocol() {
316 return empty_string_
->GetPPVar();
317 return protocol_
->GetPPVar();
320 PP_WebSocketReadyState
WebSocketResource::GetReadyState() {
324 PP_Var
WebSocketResource::GetURL() {
326 return empty_string_
->GetPPVar();
327 return url_
->GetPPVar();
330 void WebSocketResource::OnReplyReceived(
331 const ResourceMessageReplyParams
& params
,
332 const IPC::Message
& msg
) {
333 if (params
.sequence()) {
334 PluginResource::OnReplyReceived(params
, msg
);
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()
361 void WebSocketResource::OnPluginMsgConnectReply(
362 const ResourceMessageReplyParams
& params
,
363 const std::string
& url
,
364 const std::string
& protocol
) {
365 if (!TrackedCallback::IsPending(connect_callback_
))
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
);
374 connect_callback_
->Run(params
.result());
377 void WebSocketResource::OnPluginMsgCloseReply(
378 const ResourceMessageReplyParams
& params
,
379 unsigned long buffered_amount
,
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
);
388 close_reason_
= new StringVar(reason
);
390 if (TrackedCallback::IsPending(receive_callback_
)) {
391 receive_callback_var_
= NULL
;
392 receive_callback_
->PostRun(PP_ERROR_FAILED
);
393 receive_callback_
= NULL
;
396 if (TrackedCallback::IsPending(close_callback_
)) {
397 close_callback_
->PostRun(params
.result());
398 close_callback_
= NULL
;
402 void 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_
))
409 // Append received data to queue.
410 received_messages_
.push(scoped_refptr
<Var
>(new StringVar(message
)));
412 if (!TrackedCallback::IsPending(receive_callback_
))
415 receive_callback_
->Run(DoReceive());
418 void 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_
))
425 // Append received data to queue.
426 scoped_refptr
<Var
> message_var(ArrayBufferVar::FromPPVar(
427 PpapiGlobals::Get()->GetVarTracker()->MakeArrayBufferPPVar(
430 received_messages_
.push(message_var
);
432 if (!TrackedCallback::IsPending(receive_callback_
))
435 receive_callback_
->Run(DoReceive());
438 void WebSocketResource::OnPluginMsgErrorReply(
439 const ResourceMessageReplyParams
& params
) {
440 error_was_received_
= true;
442 if (!TrackedCallback::IsPending(receive_callback_
))
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
);
451 void WebSocketResource::OnPluginMsgBufferedAmountReply(
452 const ResourceMessageReplyParams
& params
,
453 unsigned long buffered_amount
) {
454 buffered_amount_
= buffered_amount
;
457 void WebSocketResource::OnPluginMsgStateReply(
458 const ResourceMessageReplyParams
& params
,
460 state_
= static_cast<PP_WebSocketReadyState
>(state
);
463 void WebSocketResource::OnPluginMsgClosedReply(
464 const ResourceMessageReplyParams
& params
,
465 unsigned long buffered_amount
,
468 const std::string
& reason
) {
469 OnPluginMsgCloseReply(params
, buffered_amount
, was_clean
, code
, reason
);
472 int32_t WebSocketResource::DoReceive() {
473 if (!receive_callback_var_
)
476 *receive_callback_var_
= received_messages_
.front()->GetPPVar();
477 received_messages_
.pop();
478 receive_callback_var_
= NULL
;