chromium/ppapi/tests/test_utils.cc

// Copyright 2012 The Chromium Authors
// 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];
  snprintf(error_as_string, sizeof(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;
}