Refactor android test results logging.
[chromium-blink-merge.git] / ppapi / proxy / websocket_resource.cc
blobc45de909b55e6b36744d513c90375e450ba9ef9c
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"
7 #include <set>
8 #include <vector>
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"
19 namespace {
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)
29 return kuint64max;
30 return a + b;
33 uint64_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);
42 bool InValidStateToReceive(PP_WebSocketReadyState state) {
43 return state == PP_WEBSOCKETREADYSTATE_OPEN ||
44 state == PP_WEBSOCKETREADYSTATE_CLOSING;
47 } // namespace
50 namespace ppapi {
51 namespace proxy {
53 WebSocketResource::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) {
70 WebSocketResource::~WebSocketResource() {
73 thunk::PPB_WebSocket_API* WebSocketResource::AsPPB_WebSocket_API() {
74 return this;
77 int32_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;
85 // Connect() can be called at most once.
86 if (state_ != PP_WEBSOCKETREADYSTATE_INVALID)
87 return PP_ERROR_INPROGRESS;
88 state_ = PP_WEBSOCKETREADYSTATE_CLOSED;
90 // Get the URL.
91 url_ = StringVar::FromPPVar(url);
92 if (!url_)
93 return PP_ERROR_BADARGUMENT;
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]));
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());
114 // Install callback.
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;
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;
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();
167 // Check state.
168 if (state_ == PP_WEBSOCKETREADYSTATE_CLOSING)
169 return PP_ERROR_INPROGRESS;
170 if (state_ == PP_WEBSOCKETREADYSTATE_CLOSED)
171 return PP_OK;
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;
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;
204 int32_t WebSocketResource::ReceiveMessage(
205 PP_Var* message,
206 scoped_refptr<TrackedCallback> callback) {
207 if (TrackedCallback::IsPending(receive_callback_))
208 return PP_ERROR_INPROGRESS;
210 // Check state.
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;
218 return DoReceive();
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
226 // is exhausted.
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) {
238 // Check state.
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);
249 if (message_string)
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();
256 } else {
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;
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)
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));
285 } else {
286 // TODO(toyoshim): Support Blob.
287 return PP_ERROR_NOTSUPPORTED;
289 return PP_OK;
292 uint64_t WebSocketResource::GetBufferedAmount() {
293 return SaturateAdd(buffered_amount_, buffered_amount_after_close_);
296 uint16_t WebSocketResource::GetCloseCode() {
297 return close_code_;
300 PP_Var WebSocketResource::GetCloseReason() {
301 if (!close_reason_)
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() {
315 if (!protocol_)
316 return empty_string_->GetPPVar();
317 return protocol_->GetPPVar();
320 PP_WebSocketReadyState WebSocketResource::GetReadyState() {
321 return state_;
324 PP_Var WebSocketResource::GetURL() {
325 if (!url_)
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);
335 return;
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_))
366 return;
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,
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);
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_))
407 return;
409 // Append received data to queue.
410 received_messages_.push(scoped_refptr<Var>(new StringVar(message)));
412 if (!TrackedCallback::IsPending(receive_callback_))
413 return;
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_))
423 return;
425 // Append received data to queue.
426 scoped_refptr<Var> message_var(ArrayBufferVar::FromPPVar(
427 PpapiGlobals::Get()->GetVarTracker()->MakeArrayBufferPPVar(
428 message.size(),
429 &message.front())));
430 received_messages_.push(message_var);
432 if (!TrackedCallback::IsPending(receive_callback_))
433 return;
435 receive_callback_->Run(DoReceive());
438 void WebSocketResource::OnPluginMsgErrorReply(
439 const ResourceMessageReplyParams& params) {
440 error_was_received_ = true;
442 if (!TrackedCallback::IsPending(receive_callback_))
443 return;
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,
459 int32_t state) {
460 state_ = static_cast<PP_WebSocketReadyState>(state);
463 void 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);
472 int32_t WebSocketResource::DoReceive() {
473 if (!receive_callback_var_)
474 return PP_OK;
476 *receive_callback_var_ = received_messages_.front()->GetPPVar();
477 received_messages_.pop();
478 receive_callback_var_ = NULL;
479 return PP_OK;
482 } // namespace proxy
483 } // namespace ppapi