| // 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_utils.h" | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #if defined(_MSC_VER) | |
| #include <windows.h> | |
| #else | |
| #include <unistd.h> | |
| #endif | |
| #include "ppapi/c/pp_errors.h" | |
| #include "ppapi/cpp/instance_handle.h" | |
| #include "ppapi/cpp/module.h" | |
| #include "ppapi/cpp/net_address.h" | |
| #include "ppapi/cpp/private/host_resolver_private.h" | |
| #include "ppapi/cpp/private/net_address_private.h" | |
| #include "ppapi/cpp/url_loader.h" | |
| #include "ppapi/cpp/var.h" | |
| namespace { | |
| bool IsBigEndian() { | |
| union { | |
| uint32_t integer32; | |
| uint8_t integer8[4]; | |
| } data = { 0x01020304 }; | |
| return data.integer8[0] == 1; | |
| } | |
| void DoNothing(void* user_data, int32_t result) {} | |
| } // namespace | |
| const int kActionTimeoutMs = 10000; | |
| const PPB_Testing_Private* GetTestingInterface() { | |
| static const PPB_Testing_Private* g_testing_interface = | |
| static_cast<const PPB_Testing_Private*>( | |
| pp::Module::Get()->GetBrowserInterface( | |
| PPB_TESTING_PRIVATE_INTERFACE)); | |
| return g_testing_interface; | |
| } | |
| std::string ReportError(const char* method, int32_t error) { | |
| char error_as_string[12]; | |
| sprintf(error_as_string, "%d", static_cast<int>(error)); | |
| std::string result = method + std::string(" failed with error: ") + | |
| error_as_string; | |
| return result; | |
| } | |
| void PlatformSleep(int duration_ms) { | |
| #if defined(_MSC_VER) | |
| ::Sleep(duration_ms); | |
| #else | |
| usleep(duration_ms * 1000); | |
| #endif | |
| } | |
| bool GetLocalHostPort(PP_Instance instance, std::string* host, uint16_t* port) { | |
| if (!host || !port) | |
| return false; | |
| const PPB_Testing_Private* testing = GetTestingInterface(); | |
| if (!testing) | |
| return false; | |
| PP_URLComponents_Dev components; | |
| pp::Var pp_url(pp::PASS_REF, | |
| testing->GetDocumentURL(instance, &components)); | |
| if (!pp_url.is_string()) | |
| return false; | |
| std::string url = pp_url.AsString(); | |
| if (components.host.len < 0) | |
| return false; | |
| host->assign(url.substr(components.host.begin, components.host.len)); | |
| if (components.port.len <= 0) | |
| return false; | |
| int i = atoi(url.substr(components.port.begin, components.port.len).c_str()); | |
| if (i < 0 || i > 65535) | |
| return false; | |
| *port = static_cast<uint16_t>(i); | |
| return true; | |
| } | |
| uint16_t ConvertFromNetEndian16(uint16_t x) { | |
| if (IsBigEndian()) | |
| return x; | |
| else | |
| return (x << 8) | (x >> 8); | |
| } | |
| uint16_t ConvertToNetEndian16(uint16_t x) { | |
| if (IsBigEndian()) | |
| return x; | |
| else | |
| return (x << 8) | (x >> 8); | |
| } | |
| bool EqualNetAddress(const pp::NetAddress& addr1, const pp::NetAddress& addr2) { | |
| if (addr1.GetFamily() == PP_NETADDRESS_FAMILY_UNSPECIFIED || | |
| addr2.GetFamily() == PP_NETADDRESS_FAMILY_UNSPECIFIED) { | |
| return false; | |
| } | |
| if (addr1.GetFamily() == PP_NETADDRESS_FAMILY_IPV4) { | |
| PP_NetAddress_IPv4 ipv4_addr1, ipv4_addr2; | |
| if (!addr1.DescribeAsIPv4Address(&ipv4_addr1) || | |
| !addr2.DescribeAsIPv4Address(&ipv4_addr2)) { | |
| return false; | |
| } | |
| return ipv4_addr1.port == ipv4_addr2.port && | |
| !memcmp(ipv4_addr1.addr, ipv4_addr2.addr, sizeof(ipv4_addr1.addr)); | |
| } else { | |
| PP_NetAddress_IPv6 ipv6_addr1, ipv6_addr2; | |
| if (!addr1.DescribeAsIPv6Address(&ipv6_addr1) || | |
| !addr2.DescribeAsIPv6Address(&ipv6_addr2)) { | |
| return false; | |
| } | |
| return ipv6_addr1.port == ipv6_addr2.port && | |
| !memcmp(ipv6_addr1.addr, ipv6_addr2.addr, sizeof(ipv6_addr1.addr)); | |
| } | |
| } | |
| bool ResolveHost(PP_Instance instance, | |
| const std::string& host, | |
| uint16_t port, | |
| pp::NetAddress* addr) { | |
| // TODO(yzshen): Change to use the public host resolver once it is supported. | |
| pp::InstanceHandle instance_handle(instance); | |
| pp::HostResolverPrivate host_resolver(instance_handle); | |
| PP_HostResolver_Private_Hint hint = | |
| { PP_NETADDRESSFAMILY_PRIVATE_UNSPECIFIED, 0 }; | |
| TestCompletionCallback callback(instance); | |
| callback.WaitForResult( | |
| host_resolver.Resolve(host, port, hint, callback.GetCallback())); | |
| PP_NetAddress_Private addr_private; | |
| if (callback.result() != PP_OK || host_resolver.GetSize() == 0 || | |
| !host_resolver.GetNetAddress(0, &addr_private)) { | |
| return false; | |
| } | |
| switch (pp::NetAddressPrivate::GetFamily(addr_private)) { | |
| case PP_NETADDRESSFAMILY_PRIVATE_IPV4: { | |
| PP_NetAddress_IPv4 ipv4_addr; | |
| ipv4_addr.port = ConvertToNetEndian16( | |
| pp::NetAddressPrivate::GetPort(addr_private)); | |
| if (!pp::NetAddressPrivate::GetAddress(addr_private, ipv4_addr.addr, | |
| sizeof(ipv4_addr.addr))) { | |
| return false; | |
| } | |
| *addr = pp::NetAddress(instance_handle, ipv4_addr); | |
| return true; | |
| } | |
| case PP_NETADDRESSFAMILY_PRIVATE_IPV6: { | |
| PP_NetAddress_IPv6 ipv6_addr; | |
| ipv6_addr.port = ConvertToNetEndian16( | |
| pp::NetAddressPrivate::GetPort(addr_private)); | |
| if (!pp::NetAddressPrivate::GetAddress(addr_private, ipv6_addr.addr, | |
| sizeof(ipv6_addr.addr))) { | |
| return false; | |
| } | |
| *addr = pp::NetAddress(instance_handle, ipv6_addr); | |
| return true; | |
| } | |
| default: { | |
| return false; | |
| } | |
| } | |
| } | |
| bool ReplacePort(PP_Instance instance, | |
| const pp::NetAddress& input_addr, | |
| uint16_t port, | |
| pp::NetAddress* output_addr) { | |
| switch (input_addr.GetFamily()) { | |
| case PP_NETADDRESS_FAMILY_IPV4: { | |
| PP_NetAddress_IPv4 ipv4_addr; | |
| if (!input_addr.DescribeAsIPv4Address(&ipv4_addr)) | |
| return false; | |
| ipv4_addr.port = ConvertToNetEndian16(port); | |
| *output_addr = pp::NetAddress(pp::InstanceHandle(instance), ipv4_addr); | |
| return true; | |
| } | |
| case PP_NETADDRESS_FAMILY_IPV6: { | |
| PP_NetAddress_IPv6 ipv6_addr; | |
| if (!input_addr.DescribeAsIPv6Address(&ipv6_addr)) | |
| return false; | |
| ipv6_addr.port = ConvertToNetEndian16(port); | |
| *output_addr = pp::NetAddress(pp::InstanceHandle(instance), ipv6_addr); | |
| return true; | |
| } | |
| default: { | |
| return false; | |
| } | |
| } | |
| } | |
| uint16_t GetPort(const pp::NetAddress& addr) { | |
| switch (addr.GetFamily()) { | |
| case PP_NETADDRESS_FAMILY_IPV4: { | |
| PP_NetAddress_IPv4 ipv4_addr; | |
| if (!addr.DescribeAsIPv4Address(&ipv4_addr)) | |
| return 0; | |
| return ConvertFromNetEndian16(ipv4_addr.port); | |
| } | |
| case PP_NETADDRESS_FAMILY_IPV6: { | |
| PP_NetAddress_IPv6 ipv6_addr; | |
| if (!addr.DescribeAsIPv6Address(&ipv6_addr)) | |
| return 0; | |
| return ConvertFromNetEndian16(ipv6_addr.port); | |
| } | |
| default: { | |
| return 0; | |
| } | |
| } | |
| } | |
| void NestedEvent::Wait() { | |
| PP_DCHECK(pp::Module::Get()->core()->IsMainThread()); | |
| // Don't allow nesting more than once; it doesn't work with the code as-is, | |
| // and probably is a bad idea most of the time anyway. | |
| PP_DCHECK(!waiting_); | |
| if (signalled_) | |
| return; | |
| waiting_ = true; | |
| while (!signalled_) | |
| GetTestingInterface()->RunMessageLoop(instance_); | |
| waiting_ = false; | |
| } | |
| void NestedEvent::Signal() { | |
| if (pp::Module::Get()->core()->IsMainThread()) | |
| SignalOnMainThread(); | |
| else | |
| PostSignal(0); | |
| } | |
| void NestedEvent::PostSignal(int32_t wait_ms) { | |
| pp::Module::Get()->core()->CallOnMainThread( | |
| wait_ms, | |
| pp::CompletionCallback(&SignalThunk, this), | |
| 0); | |
| } | |
| void NestedEvent::Reset() { | |
| PP_DCHECK(pp::Module::Get()->core()->IsMainThread()); | |
| // It doesn't make sense to reset when we're still waiting. | |
| PP_DCHECK(!waiting_); | |
| signalled_ = false; | |
| } | |
| void NestedEvent::SignalOnMainThread() { | |
| PP_DCHECK(pp::Module::Get()->core()->IsMainThread()); | |
| signalled_ = true; | |
| if (waiting_) | |
| GetTestingInterface()->QuitMessageLoop(instance_); | |
| } | |
| void NestedEvent::SignalThunk(void* event, int32_t /* result */) { | |
| static_cast<NestedEvent*>(event)->SignalOnMainThread(); | |
| } | |
| pp::CompletionCallback DoNothingCallback() { | |
| return pp::CompletionCallback(&DoNothing, NULL, | |
| PP_COMPLETIONCALLBACK_FLAG_OPTIONAL); | |
| } | |
| TestCompletionCallback::TestCompletionCallback(PP_Instance instance) | |
| : wait_for_result_called_(false), | |
| have_result_(false), | |
| result_(PP_OK_COMPLETIONPENDING), | |
| // TODO(dmichael): The default should probably be PP_REQUIRED, but this is | |
| // what the tests currently expect. | |
| callback_type_(PP_OPTIONAL), | |
| post_quit_task_(false), | |
| instance_(instance), | |
| delegate_(NULL) { | |
| } | |
| TestCompletionCallback::TestCompletionCallback(PP_Instance instance, | |
| bool force_async) | |
| : wait_for_result_called_(false), | |
| have_result_(false), | |
| result_(PP_OK_COMPLETIONPENDING), | |
| callback_type_(force_async ? PP_REQUIRED : PP_OPTIONAL), | |
| post_quit_task_(false), | |
| instance_(instance), | |
| delegate_(NULL) { | |
| } | |
| TestCompletionCallback::TestCompletionCallback(PP_Instance instance, | |
| CallbackType callback_type) | |
| : wait_for_result_called_(false), | |
| have_result_(false), | |
| result_(PP_OK_COMPLETIONPENDING), | |
| callback_type_(callback_type), | |
| post_quit_task_(false), | |
| instance_(instance), | |
| delegate_(NULL) { | |
| } | |
| void TestCompletionCallback::WaitForResult(int32_t result) { | |
| PP_DCHECK(!wait_for_result_called_); | |
| wait_for_result_called_ = true; | |
| errors_.clear(); | |
| if (result == PP_OK_COMPLETIONPENDING) { | |
| if (!have_result_) { | |
| post_quit_task_ = true; | |
| RunMessageLoop(); | |
| } | |
| if (callback_type_ == PP_BLOCKING) { | |
| errors_.assign( | |
| ReportError("TestCompletionCallback: Call did not run synchronously " | |
| "when passed a blocking completion callback!", | |
| result_)); | |
| return; | |
| } | |
| } else { | |
| result_ = result; | |
| have_result_ = true; | |
| if (callback_type_ == PP_REQUIRED) { | |
| errors_.assign( | |
| ReportError("TestCompletionCallback: Call ran synchronously when " | |
| "passed a required completion callback!", | |
| result_)); | |
| return; | |
| } | |
| } | |
| PP_DCHECK(have_result_ == true); | |
| } | |
| void TestCompletionCallback::WaitForAbortResult(int32_t result) { | |
| WaitForResult(result); | |
| int32_t final_result = result_; | |
| if (result == PP_OK_COMPLETIONPENDING) { | |
| if (final_result != PP_ERROR_ABORTED) { | |
| errors_.assign( | |
| ReportError("TestCompletionCallback: Expected PP_ERROR_ABORTED or " | |
| "PP_OK. Ran asynchronously.", | |
| final_result)); | |
| return; | |
| } | |
| } else if (result < PP_OK) { | |
| errors_.assign( | |
| ReportError("TestCompletionCallback: Expected PP_ERROR_ABORTED or " | |
| "non-error response. Ran synchronously.", | |
| result)); | |
| return; | |
| } | |
| } | |
| pp::CompletionCallback TestCompletionCallback::GetCallback() { | |
| Reset(); | |
| int32_t flags = 0; | |
| if (callback_type_ == PP_BLOCKING) | |
| return pp::CompletionCallback(); | |
| else if (callback_type_ == PP_OPTIONAL) | |
| flags = PP_COMPLETIONCALLBACK_FLAG_OPTIONAL; | |
| target_loop_ = pp::MessageLoop::GetCurrent(); | |
| return pp::CompletionCallback(&TestCompletionCallback::Handler, | |
| const_cast<TestCompletionCallback*>(this), | |
| flags); | |
| } | |
| void TestCompletionCallback::Reset() { | |
| wait_for_result_called_ = false; | |
| result_ = PP_OK_COMPLETIONPENDING; | |
| have_result_ = false; | |
| post_quit_task_ = false; | |
| delegate_ = NULL; | |
| errors_.clear(); | |
| } | |
| // static | |
| void TestCompletionCallback::Handler(void* user_data, int32_t result) { | |
| TestCompletionCallback* callback = | |
| static_cast<TestCompletionCallback*>(user_data); | |
| // If this check fails, it means that the callback was invoked twice or that | |
| // the PPAPI call completed synchronously, but also ran the callback. | |
| PP_DCHECK(!callback->have_result_); | |
| callback->result_ = result; | |
| callback->have_result_ = true; | |
| if (callback->delegate_) | |
| callback->delegate_->OnCallback(user_data, result); | |
| if (callback->post_quit_task_) { | |
| callback->post_quit_task_ = false; | |
| callback->QuitMessageLoop(); | |
| } | |
| if (callback->target_loop_ != pp::MessageLoop::GetCurrent()) { | |
| // Note, in-process, loop_ and GetCurrent() will both be NULL, so should | |
| // still be equal. | |
| callback->errors_.assign( | |
| ReportError("TestCompletionCallback: Callback ran on the wrong message " | |
| "loop!", | |
| result)); | |
| } | |
| } | |
| void TestCompletionCallback::RunMessageLoop() { | |
| pp::MessageLoop loop(pp::MessageLoop::GetCurrent()); | |
| // If we don't have a message loop, we're probably running in process, where | |
| // PPB_MessageLoop is not supported. Just use the Testing message loop. | |
| if (loop.is_null() || loop == pp::MessageLoop::GetForMainThread()) | |
| GetTestingInterface()->RunMessageLoop(instance_); | |
| else | |
| loop.Run(); | |
| } | |
| void TestCompletionCallback::QuitMessageLoop() { | |
| pp::MessageLoop loop(pp::MessageLoop::GetCurrent()); | |
| // If we don't have a message loop, we're probably running in process, where | |
| // PPB_MessageLoop is not supported. Just use the Testing message loop. | |
| if (loop.is_null() || loop == pp::MessageLoop::GetForMainThread()) { | |
| GetTestingInterface()->QuitMessageLoop(instance_); | |
| } else { | |
| const bool should_quit = false; | |
| loop.PostQuit(should_quit); | |
| } | |
| } | |
| int32_t OpenURLRequest(PP_Instance instance, | |
| pp::URLLoader* loader, | |
| const pp::URLRequestInfo& request, | |
| CallbackType callback_type, | |
| std::string* response_body) { | |
| { | |
| TestCompletionCallback open_callback(instance, callback_type); | |
| open_callback.WaitForResult( | |
| loader->Open(request, open_callback.GetCallback())); | |
| if (open_callback.result() != PP_OK) | |
| return open_callback.result(); | |
| } | |
| int32_t bytes_read = 0; | |
| do { | |
| char buffer[1024]; | |
| TestCompletionCallback read_callback(instance, callback_type); | |
| read_callback.WaitForResult(loader->ReadResponseBody( | |
| &buffer, sizeof(buffer), read_callback.GetCallback())); | |
| bytes_read = read_callback.result(); | |
| if (bytes_read < 0) | |
| return bytes_read; | |
| if (response_body) | |
| response_body->append(std::string(buffer, bytes_read)); | |
| } while (bytes_read > 0); | |
| return PP_OK; | |
| } |