text: // 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 <stddef.h>
  #include <stdint.h>
  
  #include "base/compiler_specific.h"
  #include "base/macros.h"
  #include "ppapi/c/pp_errors.h"
  #include "ppapi/proxy/connection.h"
  #include "ppapi/proxy/device_enumeration_resource_helper.h"
  #include "ppapi/proxy/plugin_message_filter.h"
  #include "ppapi/proxy/plugin_resource.h"
  #include "ppapi/proxy/plugin_resource_tracker.h"
  #include "ppapi/proxy/plugin_var_tracker.h"
  #include "ppapi/proxy/ppapi_message_utils.h"
  #include "ppapi/proxy/ppapi_messages.h"
  #include "ppapi/proxy/ppapi_proxy_test.h"
  #include "ppapi/shared_impl/ppb_device_ref_shared.h"
  #include "ppapi/shared_impl/proxy_lock.h"
  #include "ppapi/shared_impl/var.h"
  #include "ppapi/thunk/enter.h"
  #include "ppapi/thunk/ppb_device_ref_api.h"
  #include "ppapi/thunk/thunk.h"
  
  namespace ppapi {
  namespace proxy {
  
  namespace {
  
  typedef PluginProxyTest DeviceEnumerationResourceHelperTest;
  
  Connection GetConnection(PluginProxyTestHarness* harness) {
    CHECK(harness->GetGlobals()->IsPluginGlobals());
  
    return Connection(
        static_cast<PluginGlobals*>(harness->GetGlobals())->GetBrowserSender(),
        harness->plugin_dispatcher(), 0);
  }
  
  bool CompareDeviceRef(PluginVarTracker* var_tracker,
                        PP_Resource resource,
                        const DeviceRefData& expected) {
    thunk::EnterResourceNoLock<thunk::PPB_DeviceRef_API> enter(resource, true);
    if (enter.failed())
      return false;
  
    if (expected.type != enter.object()->GetType())
      return false;
  
    PP_Var name_pp_var = enter.object()->GetName();
    bool result = false;
    do {
      Var* name_var = var_tracker->GetVar(name_pp_var);
      if (!name_var)
        break;
      StringVar* name_string_var = name_var->AsStringVar();
      if (!name_string_var)
        break;
      if (expected.name != name_string_var->value())
        break;
  
      result = true;
    } while (false);
    var_tracker->ReleaseVar(name_pp_var);
    return result;
  }
  
  class TestResource : public PluginResource {
   public:
    TestResource(Connection connection, PP_Instance instance)
        : PluginResource(connection, instance),
          device_enumeration_(this) {
    }
  
    ~TestResource() override {}
  
    void OnReplyReceived(const ResourceMessageReplyParams& params,
                         const IPC::Message& msg) override {
      if (!device_enumeration_.HandleReply(params, msg))
        PluginResource::OnReplyReceived(params, msg);
    }
  
    DeviceEnumerationResourceHelper& device_enumeration() {
      return device_enumeration_;
    }
  
   private:
    DeviceEnumerationResourceHelper device_enumeration_;
  
    DISALLOW_COPY_AND_ASSIGN(TestResource);
  };
  
  class TestCallback {
   public:
    TestCallback() : called_(false), result_(PP_ERROR_FAILED) {
    }
    ~TestCallback() {
      CHECK(called_);
    }
  
    PP_CompletionCallback MakeCompletionCallback() {
      return PP_MakeCompletionCallback(&CompletionCallbackBody, this);
    }
  
    bool called() const { return called_; }
    int32_t result() const { return result_; }
  
   private:
    static void CompletionCallbackBody(void* user_data, int32_t result) {
      TestCallback* callback = static_cast<TestCallback*>(user_data);
  
      CHECK(!callback->called_);
      callback->called_ = true;
      callback->result_ = result;
    }
  
    bool called_;
    int32_t result_;
  
    DISALLOW_COPY_AND_ASSIGN(TestCallback);
  };
  
  class TestArrayOutput {
   public:
    explicit TestArrayOutput(PluginResourceTracker* resource_tracker)
        : data_(NULL),
          count_(0),
          resource_tracker_(resource_tracker) {
    }
  
    ~TestArrayOutput() {
      if (count_ > 0) {
        for (size_t i = 0; i < count_; ++i)
          resource_tracker_->ReleaseResource(data_[i]);
        delete [] data_;
      }
    }
  
    PP_ArrayOutput MakeArrayOutput() {
      PP_ArrayOutput array_output = { &GetDataBuffer, this };
      return array_output;
    }
  
    const PP_Resource* data() const { return data_; }
    uint32_t count() const { return count_; }
  
   private:
    static void* GetDataBuffer(void* user_data,
                               uint32_t element_count,
                               uint32_t element_size) {
      CHECK_EQ(element_size, sizeof(PP_Resource));
  
      TestArrayOutput* output = static_cast<TestArrayOutput*>(user_data);
      CHECK(!output->data_);
  
      output->count_ = element_count;
      if (element_count > 0)
        output->data_ = new PP_Resource[element_count];
      else
        output->data_ = NULL;
  
      return output->data_;
    }
  
    PP_Resource* data_;
    uint32_t count_;
    PluginResourceTracker* resource_tracker_;
  
    DISALLOW_COPY_AND_ASSIGN(TestArrayOutput);
  };
  
  class TestMonitorDeviceChange {
   public:
    explicit TestMonitorDeviceChange(PluginVarTracker* var_tracker)
        : called_(false),
          same_as_expected_(false),
          var_tracker_(var_tracker) {
    }
  
    ~TestMonitorDeviceChange() {}
  
    void SetExpectedResult(const std::vector<DeviceRefData>& expected) {
      called_ = false;
      same_as_expected_ = false;
      expected_ = expected;
    }
  
    bool called() const { return called_; }
  
    bool same_as_expected() const { return same_as_expected_; }
  
    static void MonitorDeviceChangeCallback(void* user_data,
                                            uint32_t device_count,
                                            const PP_Resource devices[]) {
      ProxyAutoLock lock;
      TestMonitorDeviceChange* helper =
          static_cast<TestMonitorDeviceChange*>(user_data);
      CHECK(!helper->called_);
  
      helper->called_ = true;
      helper->same_as_expected_ = false;
      if (device_count != helper->expected_.size())
        return;
      for (size_t i = 0; i < device_count; ++i) {
        if (!CompareDeviceRef(helper->var_tracker_, devices[i],
                              helper->expected_[i])) {
          return;
        }
      }
      helper->same_as_expected_ = true;
    }
  
   private:
    bool called_;
    bool same_as_expected_;
    std::vector<DeviceRefData> expected_;
    PluginVarTracker* var_tracker_;
  
    DISALLOW_COPY_AND_ASSIGN(TestMonitorDeviceChange);
  };
  
  }  // namespace
  
  TEST_F(DeviceEnumerationResourceHelperTest, EnumerateDevices) {
    ProxyAutoLock lock;
  
    scoped_refptr<TestResource> resource(
        new TestResource(GetConnection(this), pp_instance()));
    DeviceEnumerationResourceHelper& device_enumeration =
        resource->device_enumeration();
  
    TestArrayOutput output(&resource_tracker());
    TestCallback callback;
    scoped_refptr<TrackedCallback> tracked_callback(
        new TrackedCallback(resource.get(), callback.MakeCompletionCallback()));
    int32_t result = device_enumeration.EnumerateDevices(output.MakeArrayOutput(),
                                                         tracked_callback);
    ASSERT_EQ(PP_OK_COMPLETIONPENDING, result);
  
    // Should have sent an EnumerateDevices message.
    ResourceMessageCallParams params;
    IPC::Message msg;
    ASSERT_TRUE(sink().GetFirstResourceCallMatching(
        PpapiHostMsg_DeviceEnumeration_EnumerateDevices::ID, &params, &msg));
  
    // Synthesize a response.
    ResourceMessageReplyParams reply_params(params.pp_resource(),
                                            params.sequence());
    reply_params.set_result(PP_OK);
    std::vector<DeviceRefData> data;
    DeviceRefData data_item;
    data_item.type = PP_DEVICETYPE_DEV_AUDIOCAPTURE;
    data_item.name = "name_1";
    data_item.id = "id_1";
    data.push_back(data_item);
    data_item.type = PP_DEVICETYPE_DEV_VIDEOCAPTURE;
    data_item.name = "name_2";
    data_item.id = "id_2";
    data.push_back(data_item);
  
    {
      ProxyAutoUnlock unlock;
      PluginMessageFilter::DispatchResourceReplyForTest(
          reply_params,
          PpapiPluginMsg_DeviceEnumeration_EnumerateDevicesReply(data));
    }
    EXPECT_TRUE(callback.called());
    EXPECT_EQ(PP_OK, callback.result());
    EXPECT_EQ(2U, output.count());
    for (size_t i = 0; i < output.count(); ++i)
      EXPECT_TRUE(CompareDeviceRef(&var_tracker(), output.data()[i], data[i]));
  }
  
  TEST_F(DeviceEnumerationResourceHelperTest, MonitorDeviceChange) {
    ProxyAutoLock lock;
  
    scoped_refptr<TestResource> resource(
        new TestResource(GetConnection(this), pp_instance()));
    DeviceEnumerationResourceHelper& device_enumeration =
        resource->device_enumeration();
  
    TestMonitorDeviceChange helper(&var_tracker());
  
    int32_t result = device_enumeration.MonitorDeviceChange(
        &TestMonitorDeviceChange::MonitorDeviceChangeCallback, &helper);
    ASSERT_EQ(PP_OK, result);
  
    // Should have sent a MonitorDeviceChange message.
    ResourceMessageCallParams params;
    IPC::Message msg;
    ASSERT_TRUE(sink().GetFirstResourceCallMatching(
        PpapiHostMsg_DeviceEnumeration_MonitorDeviceChange::ID, &params, &msg));
    sink().ClearMessages();
  
    uint32_t callback_id = 0;
    ASSERT_TRUE(UnpackMessage<PpapiHostMsg_DeviceEnumeration_MonitorDeviceChange>(
        msg, &callback_id));
  
    ResourceMessageReplyParams reply_params(params.pp_resource(), 0);
    reply_params.set_result(PP_OK);
    std::vector<DeviceRefData> data;
  
    helper.SetExpectedResult(data);
  
    {
      ProxyAutoUnlock unlock;
      // Synthesize a response with no device.
      PluginMessageFilter::DispatchResourceReplyForTest(
          reply_params,
          PpapiPluginMsg_DeviceEnumeration_NotifyDeviceChange(
              callback_id, data));
    }
    EXPECT_TRUE(helper.called() && helper.same_as_expected());
  
    DeviceRefData data_item;
    data_item.type = PP_DEVICETYPE_DEV_AUDIOCAPTURE;
    data_item.name = "name_1";
    data_item.id = "id_1";
    data.push_back(data_item);
    data_item.type = PP_DEVICETYPE_DEV_VIDEOCAPTURE;
    data_item.name = "name_2";
    data_item.id = "id_2";
    data.push_back(data_item);
  
    helper.SetExpectedResult(data);
  
    {
      ProxyAutoUnlock unlock;
      // Synthesize a response with some devices.
      PluginMessageFilter::DispatchResourceReplyForTest(
          reply_params,
          PpapiPluginMsg_DeviceEnumeration_NotifyDeviceChange(
              callback_id, data));
    }
    EXPECT_TRUE(helper.called() && helper.same_as_expected());
  
    TestMonitorDeviceChange helper2(&var_tracker());
  
    result = device_enumeration.MonitorDeviceChange(
        &TestMonitorDeviceChange::MonitorDeviceChangeCallback, &helper2);
    ASSERT_EQ(PP_OK, result);
  
    // Should have sent another MonitorDeviceChange message.
    ResourceMessageCallParams params2;
    IPC::Message msg2;
    ASSERT_TRUE(sink().GetFirstResourceCallMatching(
        PpapiHostMsg_DeviceEnumeration_MonitorDeviceChange::ID, &params2, &msg2));
    sink().ClearMessages();
  
    uint32_t callback_id2 = 0;
    ASSERT_TRUE(UnpackMessage<PpapiHostMsg_DeviceEnumeration_MonitorDeviceChange>(
        msg2, &callback_id2));
  
    helper.SetExpectedResult(data);
    helper2.SetExpectedResult(data);
    {
      ProxyAutoUnlock unlock;
      // |helper2| should receive the result while |helper| shouldn't.
      PluginMessageFilter::DispatchResourceReplyForTest(
          reply_params,
          PpapiPluginMsg_DeviceEnumeration_NotifyDeviceChange(
              callback_id2, data));
    }
    EXPECT_TRUE(helper2.called() && helper2.same_as_expected());
    EXPECT_FALSE(helper.called());
  
    helper.SetExpectedResult(data);
    helper2.SetExpectedResult(data);
    {
      ProxyAutoUnlock unlock;
      // Even if a message with |callback_id| arrives. |helper| shouldn't receive
      // the result.
      PluginMessageFilter::DispatchResourceReplyForTest(
          reply_params,
          PpapiPluginMsg_DeviceEnumeration_NotifyDeviceChange(
              callback_id, data));
    }
    EXPECT_FALSE(helper2.called());
    EXPECT_FALSE(helper.called());
  
    result = device_enumeration.MonitorDeviceChange(NULL, NULL);
    ASSERT_EQ(PP_OK, result);
  
    // Should have sent a StopMonitoringDeviceChange message.
    ResourceMessageCallParams params3;
    IPC::Message msg3;
    ASSERT_TRUE(sink().GetFirstResourceCallMatching(
        PpapiHostMsg_DeviceEnumeration_StopMonitoringDeviceChange::ID,
        &params3, &msg3));
    sink().ClearMessages();
  
    helper2.SetExpectedResult(data);
    {
      ProxyAutoUnlock unlock;
      // |helper2| shouldn't receive any result any more.
      PluginMessageFilter::DispatchResourceReplyForTest(
          reply_params,
          PpapiPluginMsg_DeviceEnumeration_NotifyDeviceChange(
              callback_id2, data));
    }
    EXPECT_FALSE(helper2.called());
  }
  
  }  // namespace proxy
  }  // namespace ppapi
  