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 "base/numerics/safe_conversions.h"
13 #include "ppapi/c/pp_errors.h"
14 #include "ppapi/proxy/dispatch_reply_message.h"
15 #include "ppapi/proxy/ppapi_messages.h"
16 #include "ppapi/shared_impl/ppapi_globals.h"
17 #include "ppapi/shared_impl/var.h"
18 #include "ppapi/shared_impl/var_tracker.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 if (code
!= PP_WEBSOCKETSTATUSCODE_NOT_SPECIFIED
) {
141 if (code
!= PP_WEBSOCKETSTATUSCODE_NORMAL_CLOSURE
&&
142 (code
< PP_WEBSOCKETSTATUSCODE_USER_REGISTERED_MIN
||
143 code
> PP_WEBSOCKETSTATUSCODE_USER_PRIVATE_MAX
))
144 // RFC 6455 limits applications to use reserved connection close code in
145 // section 7.4.2.. The WebSocket API (http://www.w3.org/TR/websockets/)
146 // defines this out of range error as InvalidAccessError in JavaScript.
147 return PP_ERROR_NOACCESS
;
149 // |reason| must be ignored if it is PP_VARTYPE_UNDEFINED or |code| is
150 // PP_WEBSOCKETSTATUSCODE_NOT_SPECIFIED.
151 if (reason
.type
!= PP_VARTYPE_UNDEFINED
) {
152 // Validate |reason|.
153 reason_string_var
= StringVar::FromPPVar(reason
);
154 if (!reason_string_var
.get() ||
155 reason_string_var
->value().size() > kMaxReasonSizeInBytes
)
156 return PP_ERROR_BADARGUMENT
;
157 reason_string
= reason_string_var
->value();
162 if (state_
== PP_WEBSOCKETREADYSTATE_CLOSING
)
163 return PP_ERROR_INPROGRESS
;
164 if (state_
== PP_WEBSOCKETREADYSTATE_CLOSED
)
167 // Install |callback|.
168 close_callback_
= callback
;
170 // Abort ongoing connect.
171 if (TrackedCallback::IsPending(connect_callback_
)) {
172 state_
= PP_WEBSOCKETREADYSTATE_CLOSING
;
173 // Need to do a "Post" to avoid reentering the plugin.
174 connect_callback_
->PostAbort();
175 connect_callback_
= NULL
;
176 Post(RENDERER
, PpapiHostMsg_WebSocket_Fail(
177 "WebSocket was closed before the connection was established."));
178 return PP_OK_COMPLETIONPENDING
;
181 // Abort ongoing receive.
182 if (TrackedCallback::IsPending(receive_callback_
)) {
183 receive_callback_var_
= NULL
;
184 // Need to do a "Post" to avoid reentering the plugin.
185 receive_callback_
->PostAbort();
186 receive_callback_
= NULL
;
190 state_
= PP_WEBSOCKETREADYSTATE_CLOSING
;
191 PpapiHostMsg_WebSocket_Close
msg(static_cast<int32_t>(code
),
193 Call
<PpapiPluginMsg_WebSocket_CloseReply
>(RENDERER
, msg
,
194 base::Bind(&WebSocketResource::OnPluginMsgCloseReply
, this));
195 return PP_OK_COMPLETIONPENDING
;
198 int32_t WebSocketResource::ReceiveMessage(
200 scoped_refptr
<TrackedCallback
> callback
) {
201 if (TrackedCallback::IsPending(receive_callback_
))
202 return PP_ERROR_INPROGRESS
;
205 if (state_
== PP_WEBSOCKETREADYSTATE_INVALID
||
206 state_
== PP_WEBSOCKETREADYSTATE_CONNECTING
)
207 return PP_ERROR_BADARGUMENT
;
209 // Just return received message if any received message is queued.
210 if (!received_messages_
.empty()) {
211 receive_callback_var_
= message
;
215 // Check state again. In CLOSED state, no more messages will be received.
216 if (state_
== PP_WEBSOCKETREADYSTATE_CLOSED
)
217 return PP_ERROR_BADARGUMENT
;
219 // Returns PP_ERROR_FAILED after an error is received and received messages
221 if (error_was_received_
)
222 return PP_ERROR_FAILED
;
224 // Or retain |message| as buffer to store and install |callback|.
225 receive_callback_var_
= message
;
226 receive_callback_
= callback
;
228 return PP_OK_COMPLETIONPENDING
;
231 int32_t WebSocketResource::SendMessage(const PP_Var
& message
) {
233 if (state_
== PP_WEBSOCKETREADYSTATE_INVALID
||
234 state_
== PP_WEBSOCKETREADYSTATE_CONNECTING
)
235 return PP_ERROR_BADARGUMENT
;
237 if (state_
== PP_WEBSOCKETREADYSTATE_CLOSING
||
238 state_
== PP_WEBSOCKETREADYSTATE_CLOSED
) {
239 // Handle buffered_amount_after_close_.
240 uint64_t payload_size
= 0;
241 if (message
.type
== PP_VARTYPE_STRING
) {
242 scoped_refptr
<StringVar
> message_string
= StringVar::FromPPVar(message
);
243 if (message_string
.get())
244 payload_size
+= message_string
->value().length();
245 } else if (message
.type
== PP_VARTYPE_ARRAY_BUFFER
) {
246 scoped_refptr
<ArrayBufferVar
> message_array_buffer
=
247 ArrayBufferVar::FromPPVar(message
);
248 if (message_array_buffer
.get())
249 payload_size
+= message_array_buffer
->ByteLength();
251 // TODO(toyoshim): Support Blob.
252 return PP_ERROR_NOTSUPPORTED
;
255 buffered_amount_after_close_
=
256 SaturateAdd(buffered_amount_after_close_
, GetFrameSize(payload_size
));
258 return PP_ERROR_FAILED
;
262 if (message
.type
== PP_VARTYPE_STRING
) {
263 // Convert message to std::string, then send it.
264 scoped_refptr
<StringVar
> message_string
= StringVar::FromPPVar(message
);
265 if (!message_string
.get())
266 return PP_ERROR_BADARGUMENT
;
267 Post(RENDERER
, PpapiHostMsg_WebSocket_SendText(message_string
->value()));
268 } else if (message
.type
== PP_VARTYPE_ARRAY_BUFFER
) {
269 // Convert message to std::vector<uint8_t>, then send it.
270 scoped_refptr
<ArrayBufferVar
> message_arraybuffer
=
271 ArrayBufferVar::FromPPVar(message
);
272 if (!message_arraybuffer
.get())
273 return PP_ERROR_BADARGUMENT
;
274 uint8_t* message_data
= static_cast<uint8_t*>(message_arraybuffer
->Map());
275 uint32 message_length
= message_arraybuffer
->ByteLength();
276 std::vector
<uint8_t> message_vector(message_data
,
277 message_data
+ message_length
);
278 Post(RENDERER
, PpapiHostMsg_WebSocket_SendBinary(message_vector
));
280 // TODO(toyoshim): Support Blob.
281 return PP_ERROR_NOTSUPPORTED
;
286 uint64_t WebSocketResource::GetBufferedAmount() {
287 return SaturateAdd(buffered_amount_
, buffered_amount_after_close_
);
290 uint16_t WebSocketResource::GetCloseCode() {
294 PP_Var
WebSocketResource::GetCloseReason() {
295 if (!close_reason_
.get())
296 return empty_string_
->GetPPVar();
297 return close_reason_
->GetPPVar();
300 PP_Bool
WebSocketResource::GetCloseWasClean() {
301 return close_was_clean_
;
304 PP_Var
WebSocketResource::GetExtensions() {
305 return StringVar::StringToPPVar(std::string());
308 PP_Var
WebSocketResource::GetProtocol() {
309 if (!protocol_
.get())
310 return empty_string_
->GetPPVar();
311 return protocol_
->GetPPVar();
314 PP_WebSocketReadyState
WebSocketResource::GetReadyState() {
318 PP_Var
WebSocketResource::GetURL() {
320 return empty_string_
->GetPPVar();
321 return url_
->GetPPVar();
324 void WebSocketResource::OnReplyReceived(
325 const ResourceMessageReplyParams
& params
,
326 const IPC::Message
& msg
) {
327 if (params
.sequence()) {
328 PluginResource::OnReplyReceived(params
, msg
);
332 PPAPI_BEGIN_MESSAGE_MAP(WebSocketResource
, msg
)
333 PPAPI_DISPATCH_PLUGIN_RESOURCE_CALL(
334 PpapiPluginMsg_WebSocket_ReceiveTextReply
,
335 OnPluginMsgReceiveTextReply
)
336 PPAPI_DISPATCH_PLUGIN_RESOURCE_CALL(
337 PpapiPluginMsg_WebSocket_ReceiveBinaryReply
,
338 OnPluginMsgReceiveBinaryReply
)
339 PPAPI_DISPATCH_PLUGIN_RESOURCE_CALL_0(
340 PpapiPluginMsg_WebSocket_ErrorReply
,
341 OnPluginMsgErrorReply
)
342 PPAPI_DISPATCH_PLUGIN_RESOURCE_CALL(
343 PpapiPluginMsg_WebSocket_BufferedAmountReply
,
344 OnPluginMsgBufferedAmountReply
)
345 PPAPI_DISPATCH_PLUGIN_RESOURCE_CALL(
346 PpapiPluginMsg_WebSocket_StateReply
,
347 OnPluginMsgStateReply
)
348 PPAPI_DISPATCH_PLUGIN_RESOURCE_CALL(
349 PpapiPluginMsg_WebSocket_ClosedReply
,
350 OnPluginMsgClosedReply
)
351 PPAPI_DISPATCH_PLUGIN_RESOURCE_CALL_UNHANDLED(NOTREACHED())
352 PPAPI_END_MESSAGE_MAP()
355 void WebSocketResource::OnPluginMsgConnectReply(
356 const ResourceMessageReplyParams
& params
,
357 const std::string
& url
,
358 const std::string
& protocol
) {
359 if (!TrackedCallback::IsPending(connect_callback_
) ||
360 TrackedCallback::IsScheduledToRun(connect_callback_
)) {
364 int32_t result
= params
.result();
365 if (result
== PP_OK
) {
366 state_
= PP_WEBSOCKETREADYSTATE_OPEN
;
367 protocol_
= new StringVar(protocol
);
368 url_
= new StringVar(url
);
370 connect_callback_
->Run(params
.result());
373 void WebSocketResource::OnPluginMsgCloseReply(
374 const ResourceMessageReplyParams
& params
,
375 unsigned long buffered_amount
,
378 const std::string
& reason
) {
379 // Set close related properties.
380 state_
= PP_WEBSOCKETREADYSTATE_CLOSED
;
381 buffered_amount_
= buffered_amount
;
382 close_was_clean_
= PP_FromBool(was_clean
);
384 close_reason_
= new StringVar(reason
);
386 if (TrackedCallback::IsPending(receive_callback_
)) {
387 receive_callback_var_
= NULL
;
388 if (!TrackedCallback::IsScheduledToRun(receive_callback_
))
389 receive_callback_
->PostRun(PP_ERROR_FAILED
);
390 receive_callback_
= NULL
;
393 if (TrackedCallback::IsPending(close_callback_
)) {
394 if (!TrackedCallback::IsScheduledToRun(close_callback_
))
395 close_callback_
->PostRun(params
.result());
396 close_callback_
= NULL
;
400 void WebSocketResource::OnPluginMsgReceiveTextReply(
401 const ResourceMessageReplyParams
& params
,
402 const std::string
& message
) {
403 // Dispose packets after receiving an error or in invalid state.
404 if (error_was_received_
|| !InValidStateToReceive(state_
))
407 // Append received data to queue.
408 received_messages_
.push(scoped_refptr
<Var
>(new StringVar(message
)));
410 if (!TrackedCallback::IsPending(receive_callback_
) ||
411 TrackedCallback::IsScheduledToRun(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(
427 PpapiGlobals::Get()->GetVarTracker()->MakeArrayBufferVar(
428 base::checked_cast
<uint32_t>(message
.size()),
430 received_messages_
.push(message_var
);
432 if (!TrackedCallback::IsPending(receive_callback_
) ||
433 TrackedCallback::IsScheduledToRun(receive_callback_
)) {
437 receive_callback_
->Run(DoReceive());
440 void WebSocketResource::OnPluginMsgErrorReply(
441 const ResourceMessageReplyParams
& params
) {
442 error_was_received_
= true;
444 if (!TrackedCallback::IsPending(receive_callback_
) ||
445 TrackedCallback::IsScheduledToRun(receive_callback_
)) {
449 // No more text or binary messages will be received. If there is ongoing
450 // ReceiveMessage(), we must invoke the callback with error code here.
451 receive_callback_var_
= NULL
;
452 receive_callback_
->Run(PP_ERROR_FAILED
);
455 void WebSocketResource::OnPluginMsgBufferedAmountReply(
456 const ResourceMessageReplyParams
& params
,
457 unsigned long buffered_amount
) {
458 buffered_amount_
= buffered_amount
;
461 void WebSocketResource::OnPluginMsgStateReply(
462 const ResourceMessageReplyParams
& params
,
464 state_
= static_cast<PP_WebSocketReadyState
>(state
);
467 void WebSocketResource::OnPluginMsgClosedReply(
468 const ResourceMessageReplyParams
& params
,
469 unsigned long buffered_amount
,
472 const std::string
& reason
) {
473 OnPluginMsgCloseReply(params
, buffered_amount
, was_clean
, code
, reason
);
476 int32_t WebSocketResource::DoReceive() {
477 if (!receive_callback_var_
)
480 *receive_callback_var_
= received_messages_
.front()->GetPPVar();
481 received_messages_
.pop();
482 receive_callback_var_
= NULL
;