// 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_transport.h" #include #include #include #include #include "ppapi/c/dev/ppb_testing_dev.h" #include "ppapi/c/pp_errors.h" #include "ppapi/c/pp_macros.h" #include "ppapi/cpp/dev/transport_dev.h" #include "ppapi/cpp/instance.h" #include "ppapi/cpp/module.h" #include "ppapi/cpp/var.h" #include "ppapi/tests/test_utils.h" #include "ppapi/tests/testing_instance.h" #include "ppapi/utility/completion_callback_factory.h" REGISTER_TEST_CASE(Transport); namespace { const char kTestChannelName[] = "test"; const int kReadBufferSize = 65536; class StreamReader { public: StreamReader(pp::Transport_Dev* transport, int expected_size, pp::CompletionCallback done_callback) : expected_size_(expected_size), done_callback_(done_callback), PP_ALLOW_THIS_IN_INITIALIZER_LIST(callback_factory_(this)), transport_(transport), received_size_(0) { Read(); } const std::list >& received() { return received_; } std::list errors() { return errors_; } private: void Read() { while (true) { buffer_.resize(kReadBufferSize); int result = transport_->Recv( &buffer_[0], buffer_.size(), callback_factory_.NewOptionalCallback(&StreamReader::OnReadFinished)); if (result > 0) DidFinishRead(result); else break; } } void OnReadFinished(int result) { DidFinishRead(result); if (result > 0) Read(); } void DidFinishRead(int result) { if (result > 0) { if (result > static_cast(buffer_.size())) { errors_.push_back(TestCase::MakeFailureMessage( __FILE__, __LINE__, "Recv() returned value that is bigger than the buffer.")); } buffer_.resize(result); received_.push_back(buffer_); received_size_ += buffer_.size(); if (received_size_ >= expected_size_) done_callback_.Run(0); } } int expected_size_; pp::CompletionCallback done_callback_; pp::CompletionCallbackFactory callback_factory_; pp::Transport_Dev* transport_; std::vector buffer_; std::list > received_; int received_size_; std::list errors_; }; } // namespace TestTransport::~TestTransport() { delete transport1_; delete transport2_; } bool TestTransport::Init() { transport_interface_ = static_cast( pp::Module::Get()->GetBrowserInterface(PPB_TRANSPORT_DEV_INTERFACE)); return transport_interface_ && CheckTestingInterface(); } void TestTransport::RunTests(const std::string& filter) { RUN_TEST(Create, filter); RUN_TEST_FORCEASYNC_AND_NOT(Connect, filter); RUN_TEST(SetProperty, filter); RUN_TEST_FORCEASYNC_AND_NOT(SendDataUdp, filter); RUN_TEST_FORCEASYNC_AND_NOT(SendDataTcp, filter); RUN_TEST_FORCEASYNC_AND_NOT(ConnectAndCloseUdp, filter); RUN_TEST_FORCEASYNC_AND_NOT(ConnectAndCloseTcp, filter); } std::string TestTransport::InitTargets(PP_TransportType type) { transport1_ = new pp::Transport_Dev(instance_, kTestChannelName, type); transport2_ = new pp::Transport_Dev(instance_, kTestChannelName, type); ASSERT_NE(NULL, transport1_); ASSERT_NE(NULL, transport2_); PASS(); } std::string TestTransport::Connect() { TestCompletionCallback connect_cb1(instance_->pp_instance()); TestCompletionCallback connect_cb2(instance_->pp_instance()); ASSERT_EQ(transport1_->Connect(connect_cb1), PP_OK_COMPLETIONPENDING); ASSERT_EQ(transport2_->Connect(connect_cb2), PP_OK_COMPLETIONPENDING); pp::Var address1; pp::Var address2; TestCompletionCallback next_address_cb1(instance_->pp_instance()); TestCompletionCallback next_address_cb2(instance_->pp_instance()); ASSERT_EQ(transport1_->GetNextAddress(&address1, next_address_cb1), PP_OK_COMPLETIONPENDING); ASSERT_EQ(transport2_->GetNextAddress(&address2, next_address_cb2), PP_OK_COMPLETIONPENDING); ASSERT_EQ(next_address_cb1.WaitForResult(), PP_OK); ASSERT_EQ(next_address_cb2.WaitForResult(), PP_OK); ASSERT_EQ(transport1_->GetNextAddress(&address1, next_address_cb1), PP_OK); ASSERT_EQ(transport2_->GetNextAddress(&address2, next_address_cb2), PP_OK); ASSERT_EQ(transport1_->ReceiveRemoteAddress(address2), PP_OK); ASSERT_EQ(transport2_->ReceiveRemoteAddress(address1), PP_OK); ASSERT_EQ(connect_cb1.WaitForResult(), PP_OK); ASSERT_EQ(connect_cb2.WaitForResult(), PP_OK); ASSERT_TRUE(transport1_->IsWritable()); ASSERT_TRUE(transport2_->IsWritable()); PASS(); } std::string TestTransport::Clean() { delete transport1_; transport1_ = NULL; delete transport2_; transport2_ = NULL; PASS(); } std::string TestTransport::TestCreate() { ASSERT_SUBTEST_SUCCESS(InitTargets(PP_TRANSPORTTYPE_DATAGRAM)); Clean(); PASS(); } std::string TestTransport::TestSetProperty() { ASSERT_SUBTEST_SUCCESS(InitTargets(PP_TRANSPORTTYPE_STREAM)); // Try settings STUN and Relay properties. ASSERT_EQ(transport1_->SetProperty( PP_TRANSPORTPROPERTY_STUN_SERVER, pp::Var("stun.example.com:19302")), PP_OK); ASSERT_EQ(transport1_->SetProperty( PP_TRANSPORTPROPERTY_RELAY_SERVER, pp::Var("ralay.example.com:80")), PP_OK); ASSERT_EQ(transport1_->SetProperty( PP_TRANSPORTPROPERTY_RELAY_USERNAME, pp::Var("USERNAME")), PP_OK); ASSERT_EQ(transport1_->SetProperty( PP_TRANSPORTPROPERTY_RELAY_PASSWORD, pp::Var("PASSWORD")), PP_OK); // Try changing TCP window size. ASSERT_EQ(transport1_->SetProperty(PP_TRANSPORTPROPERTY_TCP_RECEIVE_WINDOW, pp::Var(65536)), PP_OK); ASSERT_EQ(transport1_->SetProperty(PP_TRANSPORTPROPERTY_TCP_RECEIVE_WINDOW, pp::Var(10000000)), PP_ERROR_BADARGUMENT); ASSERT_EQ(transport1_->SetProperty(PP_TRANSPORTPROPERTY_TCP_SEND_WINDOW, pp::Var(65536)), PP_OK); ASSERT_EQ(transport1_->SetProperty(PP_TRANSPORTPROPERTY_TCP_SEND_WINDOW, pp::Var(10000000)), PP_ERROR_BADARGUMENT); ASSERT_EQ(transport1_->SetProperty(PP_TRANSPORTPROPERTY_TCP_NO_DELAY, pp::Var(true)), PP_OK); ASSERT_EQ(transport1_->SetProperty(PP_TRANSPORTPROPERTY_TCP_ACK_DELAY, pp::Var(10)), PP_OK); ASSERT_EQ(transport1_->SetProperty(PP_TRANSPORTPROPERTY_TCP_ACK_DELAY, pp::Var(10000)), PP_ERROR_BADARGUMENT); TestCompletionCallback connect_cb(instance_->pp_instance()); ASSERT_EQ(transport1_->Connect(connect_cb), PP_OK_COMPLETIONPENDING); // SetProperty() should fail after we've connected. ASSERT_EQ(transport1_->SetProperty( PP_TRANSPORTPROPERTY_STUN_SERVER, pp::Var("stun.example.com:31323")), PP_ERROR_FAILED); Clean(); ASSERT_EQ(connect_cb.WaitForResult(), PP_ERROR_ABORTED); PASS(); } std::string TestTransport::TestConnect() { ASSERT_SUBTEST_SUCCESS(InitTargets(PP_TRANSPORTTYPE_DATAGRAM)); ASSERT_SUBTEST_SUCCESS(Connect()); Clean(); PASS(); } // Creating datagram connection and try sending data over it. Verify // that at least some packets are received (some packets may be lost). std::string TestTransport::TestSendDataUdp() { ASSERT_SUBTEST_SUCCESS(InitTargets(PP_TRANSPORTTYPE_DATAGRAM)); ASSERT_SUBTEST_SUCCESS(Connect()); const int kNumPackets = 100; const int kSendBufferSize = 1200; const int kUdpWaitTimeMs = 1000; // 1 second. TestCompletionCallback done_cb(instance_->pp_instance()); StreamReader reader(transport1_, kSendBufferSize * kNumPackets, done_cb); std::map > sent_packets; for (int i = 0; i < kNumPackets; ++i) { std::vector send_buffer(kSendBufferSize); for (size_t j = 0; j < send_buffer.size(); ++j) { send_buffer[j] = rand() % 256; } // Put packet index in the beginning. memcpy(&send_buffer[0], &i, sizeof(i)); TestCompletionCallback send_cb(instance_->pp_instance(), force_async_); int32_t result = transport2_->Send(&send_buffer[0], send_buffer.size(), send_cb); if (force_async_) { ASSERT_EQ(result, PP_OK_COMPLETIONPENDING); ASSERT_EQ(send_cb.WaitForResult(), static_cast(send_buffer.size())); } else { ASSERT_EQ(result, static_cast(send_buffer.size())); } sent_packets[i] = send_buffer; } // Limit waiting time. TestCompletionCallback timeout_cb(instance_->pp_instance()); pp::Module::Get()->core()->CallOnMainThread(kUdpWaitTimeMs, timeout_cb); ASSERT_EQ(timeout_cb.WaitForResult(), PP_OK); ASSERT_TRUE(reader.errors().size() == 0); ASSERT_TRUE(reader.received().size() > 0); for (std::list >::const_iterator it = reader.received().begin(); it != reader.received().end(); ++it) { int index; memcpy(&index, &(*it)[0], sizeof(index)); ASSERT_TRUE(sent_packets[index] == *it); } Clean(); PASS(); } // Creating reliable (TCP-like) connection and try sending data over // it. Verify that all data is received correctly. std::string TestTransport::TestSendDataTcp() { ASSERT_SUBTEST_SUCCESS(InitTargets(PP_TRANSPORTTYPE_STREAM)); ASSERT_SUBTEST_SUCCESS(Connect()); const int kTcpSendSize = 100000; TestCompletionCallback done_cb(instance_->pp_instance()); StreamReader reader(transport1_, kTcpSendSize, done_cb); std::vector send_buffer(kTcpSendSize); for (size_t j = 0; j < send_buffer.size(); ++j) { send_buffer[j] = rand() % 256; } int pos = 0; while (pos < static_cast(send_buffer.size())) { TestCompletionCallback send_cb(instance_->pp_instance(), force_async_); int result = transport2_->Send( &send_buffer[0] + pos, send_buffer.size() - pos, send_cb); if (force_async_) ASSERT_EQ(result, PP_OK_COMPLETIONPENDING); if (result == PP_OK_COMPLETIONPENDING) result = send_cb.WaitForResult(); ASSERT_TRUE(result > 0); pos += result; } ASSERT_EQ(done_cb.WaitForResult(), PP_OK); ASSERT_TRUE(reader.errors().size() == 0); std::vector received_data; for (std::list >::const_iterator it = reader.received().begin(); it != reader.received().end(); ++it) { received_data.insert(received_data.end(), it->begin(), it->end()); } ASSERT_EQ(send_buffer, received_data); Clean(); PASS(); } std::string TestTransport::TestConnectAndCloseUdp() { ASSERT_SUBTEST_SUCCESS(InitTargets(PP_TRANSPORTTYPE_DATAGRAM)); ASSERT_SUBTEST_SUCCESS(Connect()); std::vector recv_buffer(kReadBufferSize); TestCompletionCallback recv_cb(instance_->pp_instance()); ASSERT_EQ( transport1_->Recv(&recv_buffer[0], recv_buffer.size(), recv_cb), PP_OK_COMPLETIONPENDING); // Close the transport and verify that callback is aborted. ASSERT_EQ(transport1_->Close(), PP_OK); ASSERT_EQ(recv_cb.run_count(), 1); ASSERT_EQ(recv_cb.result(), PP_ERROR_ABORTED); Clean(); PASS(); } std::string TestTransport::TestConnectAndCloseTcp() { ASSERT_SUBTEST_SUCCESS(InitTargets(PP_TRANSPORTTYPE_STREAM)); ASSERT_SUBTEST_SUCCESS(Connect()); std::vector recv_buffer(kReadBufferSize); TestCompletionCallback recv_cb(instance_->pp_instance()); ASSERT_EQ( transport1_->Recv(&recv_buffer[0], recv_buffer.size(), recv_cb), PP_OK_COMPLETIONPENDING); // Close the transport and verify that callback is aborted. ASSERT_EQ(transport1_->Close(), PP_OK); ASSERT_EQ(recv_cb.run_count(), 1); ASSERT_EQ(recv_cb.result(), PP_ERROR_ABORTED); Clean(); PASS(); }