// 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/proxy/ppp_class_proxy.h"

#include "ppapi/c/dev/ppb_var_deprecated.h"
#include "ppapi/c/dev/ppp_class_deprecated.h"
#include "ppapi/c/pp_var.h"
#include "ppapi/proxy/dispatcher.h"
#include "ppapi/proxy/plugin_globals.h"
#include "ppapi/proxy/ppapi_messages.h"
#include "ppapi/proxy/serialized_var.h"
#include "ppapi/shared_impl/api_id.h"
#include "ppapi/shared_impl/proxy_lock.h"

namespace ppapi {
namespace proxy {

namespace {

// PPP_Class in the browser implementation -------------------------------------

// Represents a plugin-implemented class in the browser process. This just
// stores the data necessary to call back the plugin.
struct ObjectProxy {
  ObjectProxy(Dispatcher* d, int64_t p, int64_t ud)
      : dispatcher(d), ppp_class(p), user_data(ud) {}

  Dispatcher* dispatcher;
  int64_t ppp_class;
  int64_t user_data;
};

ObjectProxy* ToObjectProxy(void* data) {
  ObjectProxy* obj = reinterpret_cast<ObjectProxy*>(data);
  if (!obj || !obj->dispatcher)
    return NULL;
  if (!obj->dispatcher->permissions().HasPermission(PERMISSION_FLASH))
    return NULL;
  return obj;
}

bool HasProperty(void* object, PP_Var name, PP_Var* exception) {
  ObjectProxy* obj = ToObjectProxy(object);
  if (!obj)
    return false;

  bool result = false;
  ReceiveSerializedException se(obj->dispatcher, exception);
  obj->dispatcher->Send(new PpapiMsg_PPPClass_HasProperty(
      API_ID_PPP_CLASS, obj->ppp_class, obj->user_data,
      SerializedVarSendInput(obj->dispatcher, name), &se, &result));
  return result;
}

bool HasMethod(void* object, PP_Var name, PP_Var* exception) {
  ObjectProxy* obj = ToObjectProxy(object);
  if (!obj)
    return false;

  bool result = false;
  ReceiveSerializedException se(obj->dispatcher, exception);
  obj->dispatcher->Send(new PpapiMsg_PPPClass_HasMethod(
      API_ID_PPP_CLASS, obj->ppp_class, obj->user_data,
      SerializedVarSendInput(obj->dispatcher, name), &se, &result));
  return result;
}

PP_Var GetProperty(void* object,
                   PP_Var name,
                   PP_Var* exception) {
  ObjectProxy* obj = ToObjectProxy(object);
  if (!obj)
    return PP_MakeUndefined();

  ReceiveSerializedException se(obj->dispatcher, exception);
  ReceiveSerializedVarReturnValue result;
  obj->dispatcher->Send(new PpapiMsg_PPPClass_GetProperty(
      API_ID_PPP_CLASS, obj->ppp_class, obj->user_data,
      SerializedVarSendInput(obj->dispatcher, name), &se, &result));
  return result.Return(obj->dispatcher);
}

void GetAllPropertyNames(void* object,
                         uint32_t* property_count,
                         PP_Var** properties,
                         PP_Var* exception) {
  NOTIMPLEMENTED();
  // TODO(brettw) implement this.
}

void SetProperty(void* object,
                 PP_Var name,
                 PP_Var value,
                 PP_Var* exception) {
  ObjectProxy* obj = ToObjectProxy(object);
  if (!obj)
    return;

  ReceiveSerializedException se(obj->dispatcher, exception);
  obj->dispatcher->Send(new PpapiMsg_PPPClass_SetProperty(
      API_ID_PPP_CLASS, obj->ppp_class, obj->user_data,
      SerializedVarSendInput(obj->dispatcher, name),
      SerializedVarSendInput(obj->dispatcher, value), &se));
}

void RemoveProperty(void* object,
                    PP_Var name,
                    PP_Var* exception) {
  ObjectProxy* obj = ToObjectProxy(object);
  if (!obj)
    return;

  ReceiveSerializedException se(obj->dispatcher, exception);
  obj->dispatcher->Send(new PpapiMsg_PPPClass_RemoveProperty(
      API_ID_PPP_CLASS, obj->ppp_class, obj->user_data,
      SerializedVarSendInput(obj->dispatcher, name), &se));
}

PP_Var Call(void* object,
            PP_Var method_name,
            uint32_t argc,
            PP_Var* argv,
            PP_Var* exception) {
  ObjectProxy* obj = ToObjectProxy(object);
  if (!obj)
    return PP_MakeUndefined();

  ReceiveSerializedVarReturnValue result;
  ReceiveSerializedException se(obj->dispatcher, exception);
  std::vector<SerializedVar> argv_vect;
  SerializedVarSendInput::ConvertVector(obj->dispatcher, argv, argc,
                                        &argv_vect);

  obj->dispatcher->Send(new PpapiMsg_PPPClass_Call(
      API_ID_PPP_CLASS, obj->ppp_class, obj->user_data,
      SerializedVarSendInput(obj->dispatcher, method_name), argv_vect,
      &se, &result));
  return result.Return(obj->dispatcher);
}

PP_Var Construct(void* object,
                 uint32_t argc,
                 PP_Var* argv,
                 PP_Var* exception) {
  ObjectProxy* obj = ToObjectProxy(object);
  if (!obj)
    return PP_MakeUndefined();

  ReceiveSerializedVarReturnValue result;
  ReceiveSerializedException se(obj->dispatcher, exception);
  std::vector<SerializedVar> argv_vect;
  SerializedVarSendInput::ConvertVector(obj->dispatcher, argv, argc,
                                        &argv_vect);

  obj->dispatcher->Send(new PpapiMsg_PPPClass_Construct(
      API_ID_PPP_CLASS,
      obj->ppp_class, obj->user_data, argv_vect, &se, &result));
  return result.Return(obj->dispatcher);
}

void Deallocate(void* object) {
  ObjectProxy* obj = ToObjectProxy(object);
  if (!obj)
    return;

  obj->dispatcher->Send(new PpapiMsg_PPPClass_Deallocate(
      API_ID_PPP_CLASS, obj->ppp_class, obj->user_data));
  delete obj;
}

const PPP_Class_Deprecated class_interface = {
  &HasProperty,
  &HasMethod,
  &GetProperty,
  &GetAllPropertyNames,
  &SetProperty,
  &RemoveProperty,
  &Call,
  &Construct,
  &Deallocate
};

// Plugin helper functions -----------------------------------------------------

// Converts an int64_t object from IPC to a PPP_Class* for calling into the
// plugin's implementation.
const PPP_Class_Deprecated* ToPPPClass(int64_t value) {
  return reinterpret_cast<const PPP_Class_Deprecated*>(
      static_cast<intptr_t>(value));
}

// Converts an int64_t object from IPC to a void* for calling into the plugin's
// implementation as the user data.
void* ToUserData(int64_t value) {
  return reinterpret_cast<void*>(static_cast<intptr_t>(value));
}

}  // namespace

// PPP_Class_Proxy -------------------------------------------------------------

PPP_Class_Proxy::PPP_Class_Proxy(Dispatcher* dispatcher)
    : InterfaceProxy(dispatcher) {
}

PPP_Class_Proxy::~PPP_Class_Proxy() {
}

// static
InterfaceProxy* PPP_Class_Proxy::Create(Dispatcher* dispatcher) {
  return new PPP_Class_Proxy(dispatcher);
}

// static
PP_Var PPP_Class_Proxy::CreateProxiedObject(const PPB_Var_Deprecated* var,
                                            Dispatcher* dispatcher,
                                            PP_Instance instance_id,
                                            int64_t ppp_class,
                                            int64_t class_data) {
  ObjectProxy* object_proxy = new ObjectProxy(dispatcher,
                                              ppp_class, class_data);
  return var->CreateObject(instance_id, &class_interface, object_proxy);
}

// static
PP_Bool PPP_Class_Proxy::IsInstanceOf(const PPB_Var_Deprecated* ppb_var_impl,
                                      const PP_Var& var,
                                      int64_t ppp_class,
                                      int64_t* ppp_class_data) {
  void* proxied_object = NULL;
  if (ppb_var_impl->IsInstanceOf(var,
                                 &class_interface,
                                 &proxied_object)) {
    if (static_cast<ObjectProxy*>(proxied_object)->ppp_class == ppp_class) {
      DCHECK(ppp_class_data);
      *ppp_class_data = static_cast<ObjectProxy*>(proxied_object)->user_data;
      return PP_TRUE;
    }
  }
  return PP_FALSE;
}

bool PPP_Class_Proxy::OnMessageReceived(const IPC::Message& msg) {
  if (!dispatcher()->IsPlugin())
    return false;  // These messages are only valid from host->plugin.

  bool handled = true;
  IPC_BEGIN_MESSAGE_MAP(PPP_Class_Proxy, msg)
    IPC_MESSAGE_HANDLER(PpapiMsg_PPPClass_HasProperty,
                        OnMsgHasProperty)
    IPC_MESSAGE_HANDLER(PpapiMsg_PPPClass_HasMethod,
                        OnMsgHasMethod)
    IPC_MESSAGE_HANDLER(PpapiMsg_PPPClass_GetProperty,
                        OnMsgGetProperty)
    IPC_MESSAGE_HANDLER(PpapiMsg_PPPClass_EnumerateProperties,
                        OnMsgEnumerateProperties)
    IPC_MESSAGE_HANDLER(PpapiMsg_PPPClass_SetProperty,
                        OnMsgSetProperty)
    IPC_MESSAGE_HANDLER(PpapiMsg_PPPClass_Call,
                        OnMsgCall)
    IPC_MESSAGE_HANDLER(PpapiMsg_PPPClass_Construct,
                        OnMsgConstruct)
    IPC_MESSAGE_HANDLER(PpapiMsg_PPPClass_Deallocate,
                        OnMsgDeallocate)
    IPC_MESSAGE_UNHANDLED(handled = false)
  IPC_END_MESSAGE_MAP()
  return handled;
}

void PPP_Class_Proxy::OnMsgHasProperty(int64_t ppp_class,
                                       int64_t object,
                                       SerializedVarReceiveInput property,
                                       SerializedVarOutParam exception,
                                       bool* result) {
  if (!ValidateUserData(ppp_class, object, &exception))
    return;
  *result = CallWhileUnlocked(ToPPPClass(ppp_class)->HasProperty,
                              ToUserData(object),
                              property.Get(dispatcher()),
                              exception.OutParam(dispatcher()));
}

void PPP_Class_Proxy::OnMsgHasMethod(int64_t ppp_class,
                                     int64_t object,
                                     SerializedVarReceiveInput property,
                                     SerializedVarOutParam exception,
                                     bool* result) {
  if (!ValidateUserData(ppp_class, object, &exception))
    return;
  *result = CallWhileUnlocked(ToPPPClass(ppp_class)->HasMethod,
                              ToUserData(object),
                              property.Get(dispatcher()),
                              exception.OutParam(dispatcher()));
}

void PPP_Class_Proxy::OnMsgGetProperty(int64_t ppp_class,
                                       int64_t object,
                                       SerializedVarReceiveInput property,
                                       SerializedVarOutParam exception,
                                       SerializedVarReturnValue result) {
  if (!ValidateUserData(ppp_class, object, &exception))
    return;
  result.Return(dispatcher(), CallWhileUnlocked(
      ToPPPClass(ppp_class)->GetProperty,
      ToUserData(object), property.Get(dispatcher()),
      exception.OutParam(dispatcher())));
}

void PPP_Class_Proxy::OnMsgEnumerateProperties(
    int64_t ppp_class,
    int64_t object,
    std::vector<SerializedVar>* props,
    SerializedVarOutParam exception) {
  if (!ValidateUserData(ppp_class, object, &exception))
    return;
  NOTIMPLEMENTED();
  // TODO(brettw) implement this.
}

void PPP_Class_Proxy::OnMsgSetProperty(int64_t ppp_class,
                                       int64_t object,
                                       SerializedVarReceiveInput property,
                                       SerializedVarReceiveInput value,
                                       SerializedVarOutParam exception) {
  if (!ValidateUserData(ppp_class, object, &exception))
    return;
  CallWhileUnlocked(ToPPPClass(ppp_class)->SetProperty,
      ToUserData(object), property.Get(dispatcher()), value.Get(dispatcher()),
      exception.OutParam(dispatcher()));
}

void PPP_Class_Proxy::OnMsgRemoveProperty(int64_t ppp_class,
                                          int64_t object,
                                          SerializedVarReceiveInput property,
                                          SerializedVarOutParam exception) {
  if (!ValidateUserData(ppp_class, object, &exception))
    return;
  CallWhileUnlocked(ToPPPClass(ppp_class)->RemoveProperty,
      ToUserData(object), property.Get(dispatcher()),
      exception.OutParam(dispatcher()));
}

void PPP_Class_Proxy::OnMsgCall(int64_t ppp_class,
                                int64_t object,
                                SerializedVarReceiveInput method_name,
                                SerializedVarVectorReceiveInput arg_vector,
                                SerializedVarOutParam exception,
                                SerializedVarReturnValue result) {
  if (!ValidateUserData(ppp_class, object, &exception))
    return;
  uint32_t arg_count = 0;
  PP_Var* args = arg_vector.Get(dispatcher(), &arg_count);
  result.Return(dispatcher(), CallWhileUnlocked(ToPPPClass(ppp_class)->Call,
      ToUserData(object), method_name.Get(dispatcher()),
      arg_count, args, exception.OutParam(dispatcher())));
}

void PPP_Class_Proxy::OnMsgConstruct(int64_t ppp_class,
                                     int64_t object,
                                     SerializedVarVectorReceiveInput arg_vector,
                                     SerializedVarOutParam exception,
                                     SerializedVarReturnValue result) {
  if (!ValidateUserData(ppp_class, object, &exception))
    return;
  uint32_t arg_count = 0;
  PP_Var* args = arg_vector.Get(dispatcher(), &arg_count);
  result.Return(dispatcher(), CallWhileUnlocked(
      ToPPPClass(ppp_class)->Construct,
      ToUserData(object), arg_count, args, exception.OutParam(dispatcher())));
}

void PPP_Class_Proxy::OnMsgDeallocate(int64_t ppp_class, int64_t object) {
  if (!ValidateUserData(ppp_class, object, NULL))
    return;
  PluginGlobals::Get()->plugin_var_tracker()->PluginImplementedObjectDestroyed(
      ToUserData(object));
  CallWhileUnlocked(ToPPPClass(ppp_class)->Deallocate, ToUserData(object));
}

bool PPP_Class_Proxy::ValidateUserData(int64_t ppp_class,
                                       int64_t class_data,
                                       SerializedVarOutParam* exception) {
  if (!PluginGlobals::Get()->plugin_var_tracker()->ValidatePluginObjectCall(
          ToPPPClass(ppp_class), ToUserData(class_data))) {
    // Set the exception. This is so the caller will know about the error and
    // also that we won't assert that somebody forgot to call OutParam on the
    // output parameter. Although this exception of "1" won't be very useful
    // this shouldn't happen in normal usage, only when the renderer is being
    // malicious.
    if (exception)
      *exception->OutParam(dispatcher()) = PP_MakeInt32(1);
    return false;
  }
  return true;
}

}  // namespace proxy
}  // namespace ppapi
