// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "ppapi/tests/test_websocket.h" #include #include #include "ppapi/c/dev/ppb_testing_dev.h" #include "ppapi/c/pp_errors.h" #include "ppapi/c/pp_var.h" #include "ppapi/c/pp_completion_callback.h" #include "ppapi/c/ppb_core.h" #include "ppapi/c/ppb_var.h" #include "ppapi/c/ppb_var_array_buffer.h" #include "ppapi/c/ppb_websocket.h" #include "ppapi/cpp/instance.h" #include "ppapi/cpp/module.h" #include "ppapi/cpp/websocket.h" #include "ppapi/tests/test_utils.h" #include "ppapi/tests/testing_instance.h" const char kEchoServerURL[] = "ws://localhost:8880/websocket/tests/hybi/echo"; const char kCloseServerURL[] = "ws://localhost:8880/websocket/tests/hybi/close"; const char kProtocolTestServerURL[] = "ws://localhost:8880/websocket/tests/hybi/protocol-test?protocol="; const char* const kInvalidURLs[] = { "http://www.google.com/invalid_scheme", "ws://www.google.com/invalid#fragment", "ws://www.google.com:65535/invalid_port", NULL }; // Connection close code is defined in WebSocket protocol specification. // The magic number 1000 means gracefull closure without any error. // See section 7.4.1. of RFC 6455. const uint16_t kCloseCodeNormalClosure = 1000U; // Internal packet sizes. const uint64_t kCloseFrameSize = 6; const uint64_t kMessageFrameOverhead = 6; REGISTER_TEST_CASE(WebSocket); bool TestWebSocket::Init() { websocket_interface_ = static_cast( pp::Module::Get()->GetBrowserInterface(PPB_WEBSOCKET_INTERFACE)); var_interface_ = static_cast( pp::Module::Get()->GetBrowserInterface(PPB_VAR_INTERFACE)); arraybuffer_interface_ = static_cast( pp::Module::Get()->GetBrowserInterface( PPB_VAR_ARRAY_BUFFER_INTERFACE)); core_interface_ = static_cast( pp::Module::Get()->GetBrowserInterface(PPB_CORE_INTERFACE)); if (!websocket_interface_ || !var_interface_ || !arraybuffer_interface_ || !core_interface_) return false; return CheckTestingInterface(); } void TestWebSocket::RunTests(const std::string& filter) { RUN_TEST_WITH_REFERENCE_CHECK(IsWebSocket, filter); RUN_TEST_WITH_REFERENCE_CHECK(UninitializedPropertiesAccess, filter); RUN_TEST_WITH_REFERENCE_CHECK(InvalidConnect, filter); RUN_TEST_WITH_REFERENCE_CHECK(Protocols, filter); RUN_TEST_WITH_REFERENCE_CHECK(GetURL, filter); RUN_TEST_WITH_REFERENCE_CHECK(ValidConnect, filter); RUN_TEST_WITH_REFERENCE_CHECK(InvalidClose, filter); RUN_TEST_WITH_REFERENCE_CHECK(ValidClose, filter); RUN_TEST_WITH_REFERENCE_CHECK(GetProtocol, filter); RUN_TEST_WITH_REFERENCE_CHECK(TextSendReceive, filter); RUN_TEST_WITH_REFERENCE_CHECK(BinarySendReceive, filter); RUN_TEST_WITH_REFERENCE_CHECK(BufferedAmount, filter); RUN_TEST_WITH_REFERENCE_CHECK(CcInterfaces, filter); } PP_Var TestWebSocket::CreateVarString(const char* string) { return var_interface_->VarFromUtf8(string, strlen(string)); } PP_Var TestWebSocket::CreateVarBinary(const uint8_t* data, uint32_t size) { PP_Var var = arraybuffer_interface_->Create(size); void* var_data = arraybuffer_interface_->Map(var); memcpy(var_data, data, size); return var; } void TestWebSocket::ReleaseVar(const PP_Var& var) { var_interface_->Release(var); } bool TestWebSocket::AreEqualWithString(const PP_Var& var, const char* string) { if (var.type != PP_VARTYPE_STRING) return false; uint32_t utf8_length; const char* utf8 = var_interface_->VarToUtf8(var, &utf8_length); uint32_t string_length = strlen(string); if (utf8_length != string_length) return false; if (strncmp(utf8, string, utf8_length)) return false; return true; } bool TestWebSocket::AreEqualWithBinary(const PP_Var& var, const uint8_t* data, uint32_t size) { uint32_t buffer_size = 0; PP_Bool success = arraybuffer_interface_->ByteLength(var, &buffer_size); if (!success || buffer_size != size) return false; if (memcmp(arraybuffer_interface_->Map(var), data, size)) return false; return true; } PP_Resource TestWebSocket::Connect( const char* url, int32_t* result, const char* protocol) { PP_Var protocols[] = { PP_MakeUndefined() }; PP_Resource ws = websocket_interface_->Create(instance_->pp_instance()); if (!ws) return 0; PP_Var url_var = CreateVarString(url); TestCompletionCallback callback(instance_->pp_instance(), force_async_); uint32_t protocol_count = 0U; if (protocol) { protocols[0] = CreateVarString(protocol); protocol_count = 1U; } *result = websocket_interface_->Connect( ws, url_var, protocols, protocol_count, static_cast(callback).pp_completion_callback()); ReleaseVar(url_var); if (protocol) ReleaseVar(protocols[0]); if (*result == PP_OK_COMPLETIONPENDING) *result = callback.WaitForResult(); return ws; } std::string TestWebSocket::TestIsWebSocket() { // Test that a NULL resource isn't a websocket. pp::Resource null_resource; PP_Bool result = websocket_interface_->IsWebSocket(null_resource.pp_resource()); ASSERT_FALSE(result); PP_Resource ws = websocket_interface_->Create(instance_->pp_instance()); ASSERT_TRUE(ws); result = websocket_interface_->IsWebSocket(ws); ASSERT_TRUE(result); core_interface_->ReleaseResource(ws); PASS(); } std::string TestWebSocket::TestUninitializedPropertiesAccess() { PP_Resource ws = websocket_interface_->Create(instance_->pp_instance()); ASSERT_TRUE(ws); uint64_t bufferedAmount = websocket_interface_->GetBufferedAmount(ws); ASSERT_EQ(0U, bufferedAmount); uint16_t close_code = websocket_interface_->GetCloseCode(ws); ASSERT_EQ(0U, close_code); PP_Var close_reason = websocket_interface_->GetCloseReason(ws); ASSERT_TRUE(AreEqualWithString(close_reason, "")); ReleaseVar(close_reason); PP_Bool close_was_clean = websocket_interface_->GetCloseWasClean(ws); ASSERT_EQ(PP_FALSE, close_was_clean); PP_Var extensions = websocket_interface_->GetExtensions(ws); ASSERT_TRUE(AreEqualWithString(extensions, "")); ReleaseVar(extensions); PP_Var protocol = websocket_interface_->GetProtocol(ws); ASSERT_TRUE(AreEqualWithString(protocol, "")); ReleaseVar(protocol); PP_WebSocketReadyState ready_state = websocket_interface_->GetReadyState(ws); ASSERT_EQ(PP_WEBSOCKETREADYSTATE_INVALID, ready_state); PP_Var url = websocket_interface_->GetURL(ws); ASSERT_TRUE(AreEqualWithString(url, "")); ReleaseVar(url); core_interface_->ReleaseResource(ws); PASS(); } std::string TestWebSocket::TestInvalidConnect() { PP_Var protocols[] = { PP_MakeUndefined() }; PP_Resource ws = websocket_interface_->Create(instance_->pp_instance()); ASSERT_TRUE(ws); TestCompletionCallback callback(instance_->pp_instance(), force_async_); int32_t result = websocket_interface_->Connect( ws, PP_MakeUndefined(), protocols, 1U, static_cast(callback).pp_completion_callback()); ASSERT_EQ(PP_ERROR_BADARGUMENT, result); result = websocket_interface_->Connect( ws, PP_MakeUndefined(), protocols, 1U, static_cast(callback).pp_completion_callback()); ASSERT_EQ(PP_ERROR_INPROGRESS, result); core_interface_->ReleaseResource(ws); for (int i = 0; kInvalidURLs[i]; ++i) { ws = Connect(kInvalidURLs[i], &result, NULL); ASSERT_TRUE(ws); ASSERT_EQ(PP_ERROR_BADARGUMENT, result); core_interface_->ReleaseResource(ws); } PASS(); } std::string TestWebSocket::TestProtocols() { PP_Var url = CreateVarString(kEchoServerURL); PP_Var bad_protocols[] = { CreateVarString("x-test"), CreateVarString("x-test") }; PP_Var good_protocols[] = { CreateVarString("x-test"), CreateVarString("x-yatest") }; PP_Resource ws = websocket_interface_->Create(instance_->pp_instance()); ASSERT_TRUE(ws); TestCompletionCallback callback(instance_->pp_instance(), force_async_); int32_t result = websocket_interface_->Connect( ws, url, bad_protocols, 2U, static_cast(callback).pp_completion_callback()); if (result == PP_OK_COMPLETIONPENDING) result = callback.WaitForResult(); ASSERT_EQ(PP_ERROR_BADARGUMENT, result); core_interface_->ReleaseResource(ws); ws = websocket_interface_->Create(instance_->pp_instance()); ASSERT_TRUE(ws); result = websocket_interface_->Connect( ws, url, good_protocols, 2U, PP_BlockUntilComplete()); ASSERT_EQ(PP_ERROR_BLOCKS_MAIN_THREAD, result); core_interface_->ReleaseResource(ws); ReleaseVar(url); for (int i = 0; i < 2; ++i) { ReleaseVar(bad_protocols[i]); ReleaseVar(good_protocols[i]); } core_interface_->ReleaseResource(ws); PASS(); } std::string TestWebSocket::TestGetURL() { for (int i = 0; kInvalidURLs[i]; ++i) { int32_t result; PP_Resource ws = Connect(kInvalidURLs[i], &result, NULL); ASSERT_TRUE(ws); PP_Var url = websocket_interface_->GetURL(ws); ASSERT_TRUE(AreEqualWithString(url, kInvalidURLs[i])); ASSERT_EQ(PP_ERROR_BADARGUMENT, result); ReleaseVar(url); core_interface_->ReleaseResource(ws); } PASS(); } std::string TestWebSocket::TestValidConnect() { int32_t result; PP_Resource ws = Connect(kEchoServerURL, &result, NULL); ASSERT_TRUE(ws); ASSERT_EQ(PP_OK, result); core_interface_->ReleaseResource(ws); PASS(); } std::string TestWebSocket::TestInvalidClose() { PP_Var reason = CreateVarString("close for test"); TestCompletionCallback callback(instance_->pp_instance()); // Close before connect. PP_Resource ws = websocket_interface_->Create(instance_->pp_instance()); int32_t result = websocket_interface_->Close( ws, kCloseCodeNormalClosure, reason, static_cast(callback).pp_completion_callback()); ASSERT_EQ(PP_ERROR_FAILED, result); core_interface_->ReleaseResource(ws); // Close with bad arguments. ws = Connect(kEchoServerURL, &result, NULL); ASSERT_TRUE(ws); ASSERT_EQ(PP_OK, result); result = websocket_interface_->Close(ws, 1U, reason, static_cast(callback).pp_completion_callback()); ASSERT_EQ(PP_ERROR_NOACCESS, result); core_interface_->ReleaseResource(ws); ReleaseVar(reason); PASS(); } std::string TestWebSocket::TestValidClose() { PP_Var reason = CreateVarString("close for test"); PP_Var url = CreateVarString(kEchoServerURL); PP_Var protocols[] = { PP_MakeUndefined() }; TestCompletionCallback callback(instance_->pp_instance()); TestCompletionCallback another_callback(instance_->pp_instance()); // Close. int32_t result; PP_Resource ws = Connect(kEchoServerURL, &result, NULL); ASSERT_TRUE(ws); ASSERT_EQ(PP_OK, result); result = websocket_interface_->Close(ws, kCloseCodeNormalClosure, reason, static_cast(callback).pp_completion_callback()); ASSERT_EQ(PP_OK_COMPLETIONPENDING, result); result = callback.WaitForResult(); ASSERT_EQ(PP_OK, result); core_interface_->ReleaseResource(ws); // Close in connecting. // The ongoing connect failed with PP_ERROR_ABORTED, then the close is done // successfully. ws = websocket_interface_->Create(instance_->pp_instance()); result = websocket_interface_->Connect(ws, url, protocols, 0U, static_cast(callback).pp_completion_callback()); ASSERT_EQ(PP_OK_COMPLETIONPENDING, result); result = websocket_interface_->Close(ws, kCloseCodeNormalClosure, reason, static_cast( another_callback).pp_completion_callback()); ASSERT_EQ(PP_OK_COMPLETIONPENDING, result); result = callback.WaitForResult(); ASSERT_EQ(PP_ERROR_ABORTED, result); result = another_callback.WaitForResult(); ASSERT_EQ(PP_OK, result); core_interface_->ReleaseResource(ws); // Close in closing. // The first close will be done successfully, then the second one failed with // with PP_ERROR_INPROGRESS immediately. ws = Connect(kEchoServerURL, &result, NULL); ASSERT_TRUE(ws); ASSERT_EQ(PP_OK, result); result = websocket_interface_->Close(ws, kCloseCodeNormalClosure, reason, static_cast(callback).pp_completion_callback()); ASSERT_EQ(PP_OK_COMPLETIONPENDING, result); result = websocket_interface_->Close(ws, kCloseCodeNormalClosure, reason, static_cast( another_callback).pp_completion_callback()); ASSERT_EQ(PP_ERROR_INPROGRESS, result); result = callback.WaitForResult(); ASSERT_EQ(PP_OK, result); core_interface_->ReleaseResource(ws); // Close with ongoing receive message. ws = Connect(kEchoServerURL, &result, NULL); ASSERT_TRUE(ws); ASSERT_EQ(PP_OK, result); PP_Var receive_message_var; result = websocket_interface_->ReceiveMessage(ws, &receive_message_var, static_cast(callback).pp_completion_callback()); ASSERT_EQ(PP_OK_COMPLETIONPENDING, result); result = websocket_interface_->Close(ws, kCloseCodeNormalClosure, reason, static_cast( another_callback).pp_completion_callback()); ASSERT_EQ(PP_OK_COMPLETIONPENDING, result); result = callback.WaitForResult(); ASSERT_EQ(PP_ERROR_ABORTED, result); result = another_callback.WaitForResult(); ASSERT_EQ(PP_OK, result); core_interface_->ReleaseResource(ws); ReleaseVar(reason); ReleaseVar(url); PASS(); } std::string TestWebSocket::TestGetProtocol() { const char* expected_protocols[] = { "x-chat", "hoehoe", NULL }; for (int i = 0; expected_protocols[i]; ++i) { std::string url(kProtocolTestServerURL); url += expected_protocols[i]; int32_t result; PP_Resource ws = Connect(url.c_str(), &result, expected_protocols[i]); ASSERT_TRUE(ws); ASSERT_EQ(PP_OK, result); PP_Var protocol = websocket_interface_->GetProtocol(ws); ASSERT_TRUE(AreEqualWithString(protocol, expected_protocols[i])); ReleaseVar(protocol); core_interface_->ReleaseResource(ws); } PASS(); } std::string TestWebSocket::TestTextSendReceive() { // Connect to test echo server. int32_t connect_result; PP_Resource ws = Connect(kEchoServerURL, &connect_result, NULL); ASSERT_TRUE(ws); ASSERT_EQ(PP_OK, connect_result); // Send 'hello pepper' text message. const char* message = "hello pepper"; PP_Var message_var = CreateVarString(message); int32_t result = websocket_interface_->SendMessage(ws, message_var); ReleaseVar(message_var); ASSERT_EQ(PP_OK, result); // Receive echoed 'hello pepper'. TestCompletionCallback callback(instance_->pp_instance(), force_async_); PP_Var received_message; result = websocket_interface_->ReceiveMessage(ws, &received_message, static_cast(callback).pp_completion_callback()); ASSERT_FALSE(result != PP_OK && result != PP_OK_COMPLETIONPENDING); if (result == PP_OK_COMPLETIONPENDING) result = callback.WaitForResult(); ASSERT_EQ(PP_OK, result); ASSERT_TRUE(AreEqualWithString(received_message, message)); ReleaseVar(received_message); core_interface_->ReleaseResource(ws); PASS(); } std::string TestWebSocket::TestBinarySendReceive() { // Connect to test echo server. int32_t connect_result; PP_Resource ws = Connect(kEchoServerURL, &connect_result, NULL); ASSERT_TRUE(ws); ASSERT_EQ(PP_OK, connect_result); // Send binary message. uint32_t len = 256; uint8_t data[256]; for (uint32_t i = 0; i < len; ++i) data[i] = i; PP_Var message_var = CreateVarBinary(data, len); int32_t result = websocket_interface_->SendMessage(ws, message_var); ReleaseVar(message_var); ASSERT_EQ(PP_OK, result); // Receive echoed binary. TestCompletionCallback callback(instance_->pp_instance(), force_async_); PP_Var received_message; result = websocket_interface_->ReceiveMessage(ws, &received_message, static_cast(callback).pp_completion_callback()); ASSERT_TRUE(result == PP_OK || result == PP_OK_COMPLETIONPENDING); if (result == PP_OK_COMPLETIONPENDING) result = callback.WaitForResult(); ASSERT_EQ(PP_OK, result); ASSERT_TRUE(AreEqualWithBinary(received_message, data, len)); ReleaseVar(received_message); core_interface_->ReleaseResource(ws); PASS(); } std::string TestWebSocket::TestBufferedAmount() { // Connect to test echo server. int32_t connect_result; PP_Resource ws = Connect(kEchoServerURL, &connect_result, NULL); ASSERT_TRUE(ws); ASSERT_EQ(PP_OK, connect_result); // Prepare a large message that is not aligned with the internal buffer // sizes. char message[8194]; memset(message, 'x', 8193); message[8193] = 0; PP_Var message_var = CreateVarString(message); uint64_t buffered_amount = 0; int32_t result; for (int i = 0; i < 100; i++) { result = websocket_interface_->SendMessage(ws, message_var); ASSERT_EQ(PP_OK, result); buffered_amount = websocket_interface_->GetBufferedAmount(ws); // Buffered amount size 262144 is too big for the internal buffer size. if (buffered_amount > 262144) break; } // Close connection. std::string reason_str = "close while busy"; PP_Var reason = CreateVarString(reason_str.c_str()); TestCompletionCallback callback(instance_->pp_instance()); result = websocket_interface_->Close(ws, kCloseCodeNormalClosure, reason, static_cast(callback).pp_completion_callback()); ASSERT_EQ(PP_OK_COMPLETIONPENDING, result); ASSERT_EQ(PP_WEBSOCKETREADYSTATE_CLOSING, websocket_interface_->GetReadyState(ws)); result = callback.WaitForResult(); ASSERT_EQ(PP_OK, result); ASSERT_EQ(PP_WEBSOCKETREADYSTATE_CLOSED, websocket_interface_->GetReadyState(ws)); uint64_t base_buffered_amount = websocket_interface_->GetBufferedAmount(ws); // After connection closure, all sending requests fail and just increase // the bufferedAmount property. PP_Var empty_string = CreateVarString(""); result = websocket_interface_->SendMessage(ws, empty_string); ASSERT_EQ(PP_ERROR_FAILED, result); buffered_amount = websocket_interface_->GetBufferedAmount(ws); ASSERT_EQ(base_buffered_amount + kMessageFrameOverhead, buffered_amount); base_buffered_amount = buffered_amount; result = websocket_interface_->SendMessage(ws, reason); ASSERT_EQ(PP_ERROR_FAILED, result); buffered_amount = websocket_interface_->GetBufferedAmount(ws); uint64_t reason_frame_size = kMessageFrameOverhead + reason_str.length(); ASSERT_EQ(base_buffered_amount + reason_frame_size, buffered_amount); ReleaseVar(message_var); ReleaseVar(reason); ReleaseVar(empty_string); core_interface_->ReleaseResource(ws); PASS(); } std::string TestWebSocket::TestCcInterfaces() { // C++ bindings is simple straightforward, then just verifies interfaces work // as a interface bridge fine. pp::WebSocket ws(instance_); // Check uninitialized properties access. ASSERT_EQ(0, ws.GetBufferedAmount()); ASSERT_EQ(0, ws.GetCloseCode()); ASSERT_TRUE(AreEqualWithString(ws.GetCloseReason().pp_var(), "")); ASSERT_EQ(false, ws.GetCloseWasClean()); ASSERT_TRUE(AreEqualWithString(ws.GetExtensions().pp_var(), "")); ASSERT_TRUE(AreEqualWithString(ws.GetProtocol().pp_var(), "")); ASSERT_EQ(PP_WEBSOCKETREADYSTATE_INVALID, ws.GetReadyState()); ASSERT_TRUE(AreEqualWithString(ws.GetURL().pp_var(), "")); // Check communication interfaces (connect, send, receive, and close). TestCompletionCallback connect_callback(instance_->pp_instance()); int32_t result = ws.Connect(pp::Var(std::string(kCloseServerURL)), NULL, 0U, connect_callback); ASSERT_EQ(PP_OK_COMPLETIONPENDING, result); result = connect_callback.WaitForResult(); ASSERT_EQ(PP_OK, result); std::string text_message("hello C++"); result = ws.SendMessage(pp::Var(text_message)); ASSERT_EQ(PP_OK, result); uint32_t binary_length = 256; uint8_t binary_message[256]; for (uint32_t i = 0; i < binary_length; ++i) binary_message[i] = i; result = ws.SendMessage(pp::Var( pp::Var::PassRef(), CreateVarBinary(binary_message, binary_length))); ASSERT_EQ(PP_OK, result); pp::Var text_receive_var; TestCompletionCallback text_receive_callback(instance_->pp_instance()); result = ws.ReceiveMessage(&text_receive_var, text_receive_callback); if (result == PP_OK_COMPLETIONPENDING) result = text_receive_callback.WaitForResult(); ASSERT_EQ(PP_OK, result); ASSERT_TRUE( AreEqualWithString(text_receive_var.pp_var(), text_message.c_str())); pp::Var binary_receive_var; TestCompletionCallback binary_receive_callback(instance_->pp_instance()); result = ws.ReceiveMessage(&binary_receive_var, binary_receive_callback); if (result == PP_OK_COMPLETIONPENDING) result = binary_receive_callback.WaitForResult(); ASSERT_EQ(PP_OK, result); ASSERT_TRUE(AreEqualWithBinary( binary_receive_var.pp_var(), binary_message, binary_length)); TestCompletionCallback close_callback(instance_->pp_instance()); std::string reason("bye"); result = ws.Close(kCloseCodeNormalClosure, pp::Var(reason), close_callback); ASSERT_EQ(PP_OK_COMPLETIONPENDING, result); result = close_callback.WaitForResult(); ASSERT_EQ(PP_OK, result); // Check initialized properties access. ASSERT_EQ(0, ws.GetBufferedAmount()); ASSERT_EQ(kCloseCodeNormalClosure, ws.GetCloseCode()); ASSERT_TRUE(AreEqualWithString(ws.GetCloseReason().pp_var(), reason.c_str())); ASSERT_EQ(true, ws.GetCloseWasClean()); ASSERT_TRUE(AreEqualWithString(ws.GetExtensions().pp_var(), "")); ASSERT_TRUE(AreEqualWithString(ws.GetProtocol().pp_var(), "")); ASSERT_EQ(PP_WEBSOCKETREADYSTATE_CLOSED, ws.GetReadyState()); ASSERT_TRUE(AreEqualWithString(ws.GetURL().pp_var(), kCloseServerURL)); PASS(); }