// 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.
#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/351564777): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif
#include <stddef.h>
#include <stdint.h>
#include "base/compiler_specific.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(const TestResource&) = delete;
TestResource& operator=(const TestResource&) = delete;
~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_;
};
class TestCallback {
public:
TestCallback() : called_(false), result_(PP_ERROR_FAILED) {
}
TestCallback(const TestCallback&) = delete;
TestCallback& operator=(const TestCallback&) = delete;
~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_;
};
class TestArrayOutput {
public:
explicit TestArrayOutput(PluginResourceTracker* resource_tracker)
: data_(NULL),
count_(0),
resource_tracker_(resource_tracker) {
}
TestArrayOutput(const TestArrayOutput&) = delete;
TestArrayOutput& operator=(const TestArrayOutput&) = delete;
~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_;
};
class TestMonitorDeviceChange {
public:
explicit TestMonitorDeviceChange(PluginVarTracker* var_tracker)
: called_(false),
same_as_expected_(false),
var_tracker_(var_tracker) {
}
TestMonitorDeviceChange(const TestMonitorDeviceChange&) = delete;
TestMonitorDeviceChange& operator=(const TestMonitorDeviceChange&) = delete;
~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_;
};
} // 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, ¶ms, &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, ¶ms, &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, ¶ms2, &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,
¶ms3, &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