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"
18 #include "third_party/WebKit/public/web/WebSocket.h"
22 const uint32_t kMaxReasonSizeInBytes
= 123;
23 const size_t kBaseFramingOverhead
= 2;
24 const size_t kMaskingKeyLength
= 4;
25 const size_t kMinimumPayloadSizeWithTwoByteExtendedPayloadLength
= 126;
26 const size_t kMinimumPayloadSizeWithEightByteExtendedPayloadLength
= 0x10000;
28 uint64_t SaturateAdd(uint64_t a
, uint64_t b
) {
29 if (kuint64max
- a
< b
)
34 uint64_t GetFrameSize(uint64_t payload_size
) {
35 uint64_t overhead
= kBaseFramingOverhead
+ kMaskingKeyLength
;
36 if (payload_size
> kMinimumPayloadSizeWithEightByteExtendedPayloadLength
)
38 else if (payload_size
> kMinimumPayloadSizeWithTwoByteExtendedPayloadLength
)
40 return SaturateAdd(payload_size
, overhead
);
43 bool InValidStateToReceive(PP_WebSocketReadyState state
) {
44 return state
== PP_WEBSOCKETREADYSTATE_OPEN
||
45 state
== PP_WEBSOCKETREADYSTATE_CLOSING
;
54 WebSocketResource::WebSocketResource(Connection connection
,
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())),
63 close_was_clean_(PP_FALSE
),
68 buffered_amount_after_close_(0) {
71 WebSocketResource::~WebSocketResource() {
74 thunk::PPB_WebSocket_API
* WebSocketResource::AsPPB_WebSocket_API() {
78 int32_t WebSocketResource::Connect(
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
;
86 // Connect() can be called at most once.
87 if (state_
!= PP_WEBSOCKETREADYSTATE_INVALID
)
88 return PP_ERROR_INPROGRESS
;
89 state_
= PP_WEBSOCKETREADYSTATE_CLOSED
;
92 url_
= StringVar::FromPPVar(url
);
94 return PP_ERROR_BADARGUMENT
;
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
]));
103 // Check invalid and empty entries.
104 if (!protocol
.get() || !protocol
->value().length())
105 return PP_ERROR_BADARGUMENT
;
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());
112 protocol_strings
.push_back(protocol
->value());
116 connect_callback_
= callback
;
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));
126 return PP_OK_COMPLETIONPENDING
;
129 int32_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
;
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
;
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
;
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();
169 if (state_
== PP_WEBSOCKETREADYSTATE_CLOSING
)
170 return PP_ERROR_INPROGRESS
;
171 if (state_
== PP_WEBSOCKETREADYSTATE_CLOSED
)
174 // Install |callback|.
175 close_callback_
= callback
;
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
;
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
;
197 state_
= PP_WEBSOCKETREADYSTATE_CLOSING
;
198 PpapiHostMsg_WebSocket_Close
msg(static_cast<int32_t>(event_code
),
200 Call
<PpapiPluginMsg_WebSocket_CloseReply
>(RENDERER
, msg
,
201 base::Bind(&WebSocketResource::OnPluginMsgCloseReply
, this));
202 return PP_OK_COMPLETIONPENDING
;
205 int32_t WebSocketResource::ReceiveMessage(
207 scoped_refptr
<TrackedCallback
> callback
) {
208 if (TrackedCallback::IsPending(receive_callback_
))
209 return PP_ERROR_INPROGRESS
;
212 if (state_
== PP_WEBSOCKETREADYSTATE_INVALID
||
213 state_
== PP_WEBSOCKETREADYSTATE_CONNECTING
)
214 return PP_ERROR_BADARGUMENT
;
216 // Just return received message if any received message is queued.
217 if (!received_messages_
.empty()) {
218 receive_callback_var_
= message
;
222 // Check state again. In CLOSED state, no more messages will be received.
223 if (state_
== PP_WEBSOCKETREADYSTATE_CLOSED
)
224 return PP_ERROR_BADARGUMENT
;
226 // Returns PP_ERROR_FAILED after an error is received and received messages
228 if (error_was_received_
)
229 return PP_ERROR_FAILED
;
231 // Or retain |message| as buffer to store and install |callback|.
232 receive_callback_var_
= message
;
233 receive_callback_
= callback
;
235 return PP_OK_COMPLETIONPENDING
;
238 int32_t WebSocketResource::SendMessage(const PP_Var
& message
) {
240 if (state_
== PP_WEBSOCKETREADYSTATE_INVALID
||
241 state_
== PP_WEBSOCKETREADYSTATE_CONNECTING
)
242 return PP_ERROR_BADARGUMENT
;
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();
258 // TODO(toyoshim): Support Blob.
259 return PP_ERROR_NOTSUPPORTED
;
262 buffered_amount_after_close_
=
263 SaturateAdd(buffered_amount_after_close_
, GetFrameSize(payload_size
));
265 return PP_ERROR_FAILED
;
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
));
287 // TODO(toyoshim): Support Blob.
288 return PP_ERROR_NOTSUPPORTED
;
293 uint64_t WebSocketResource::GetBufferedAmount() {
294 return SaturateAdd(buffered_amount_
, buffered_amount_after_close_
);
297 uint16_t WebSocketResource::GetCloseCode() {
301 PP_Var
WebSocketResource::GetCloseReason() {
302 if (!close_reason_
.get())
303 return empty_string_
->GetPPVar();
304 return close_reason_
->GetPPVar();
307 PP_Bool
WebSocketResource::GetCloseWasClean() {
308 return close_was_clean_
;
311 PP_Var
WebSocketResource::GetExtensions() {
312 return StringVar::StringToPPVar(std::string());
315 PP_Var
WebSocketResource::GetProtocol() {
316 if (!protocol_
.get())
317 return empty_string_
->GetPPVar();
318 return protocol_
->GetPPVar();
321 PP_WebSocketReadyState
WebSocketResource::GetReadyState() {
325 PP_Var
WebSocketResource::GetURL() {
327 return empty_string_
->GetPPVar();
328 return url_
->GetPPVar();
331 void WebSocketResource::OnReplyReceived(
332 const ResourceMessageReplyParams
& params
,
333 const IPC::Message
& msg
) {
334 if (params
.sequence()) {
335 PluginResource::OnReplyReceived(params
, msg
);
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()
362 void 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_
)) {
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
);
377 connect_callback_
->Run(params
.result());
380 void WebSocketResource::OnPluginMsgCloseReply(
381 const ResourceMessageReplyParams
& params
,
382 unsigned long buffered_amount
,
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
);
391 close_reason_
= new StringVar(reason
);
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
;
400 if (TrackedCallback::IsPending(close_callback_
)) {
401 if (!TrackedCallback::IsScheduledToRun(close_callback_
))
402 close_callback_
->PostRun(params
.result());
403 close_callback_
= NULL
;
407 void 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_
))
414 // Append received data to queue.
415 received_messages_
.push(scoped_refptr
<Var
>(new StringVar(message
)));
417 if (!TrackedCallback::IsPending(receive_callback_
) ||
418 TrackedCallback::IsScheduledToRun(receive_callback_
)) {
422 receive_callback_
->Run(DoReceive());
425 void 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_
))
432 // Append received data to queue.
433 scoped_refptr
<Var
> message_var(
434 PpapiGlobals::Get()->GetVarTracker()->MakeArrayBufferVar(
437 received_messages_
.push(message_var
);
439 if (!TrackedCallback::IsPending(receive_callback_
) ||
440 TrackedCallback::IsScheduledToRun(receive_callback_
)) {
444 receive_callback_
->Run(DoReceive());
447 void WebSocketResource::OnPluginMsgErrorReply(
448 const ResourceMessageReplyParams
& params
) {
449 error_was_received_
= true;
451 if (!TrackedCallback::IsPending(receive_callback_
) ||
452 TrackedCallback::IsScheduledToRun(receive_callback_
)) {
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
);
462 void WebSocketResource::OnPluginMsgBufferedAmountReply(
463 const ResourceMessageReplyParams
& params
,
464 unsigned long buffered_amount
) {
465 buffered_amount_
= buffered_amount
;
468 void WebSocketResource::OnPluginMsgStateReply(
469 const ResourceMessageReplyParams
& params
,
471 state_
= static_cast<PP_WebSocketReadyState
>(state
);
474 void WebSocketResource::OnPluginMsgClosedReply(
475 const ResourceMessageReplyParams
& params
,
476 unsigned long buffered_amount
,
479 const std::string
& reason
) {
480 OnPluginMsgCloseReply(params
, buffered_amount
, was_clean
, code
, reason
);
483 int32_t WebSocketResource::DoReceive() {
484 if (!receive_callback_var_
)
487 *receive_callback_var_
= received_messages_
.front()->GetPPVar();
488 received_messages_
.pop();
489 receive_callback_var_
= NULL
;