// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <stdint.h>
#include <memory>
#include <string>
#include <utility>
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ref.h"
#include "base/no_destructor.h"
#include "base/run_loop.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/task_traits.h"
#include "base/test/test_future.h"
#include "base/threading/thread.h"
#include "content/browser/media/media_devices_util.h" // nogncheck
#include "content/browser/media/media_internals.h" // nogncheck
#include "content/browser/renderer_host/media/fake_video_capture_provider.h" // nogncheck
#include "content/browser/renderer_host/media/in_process_video_capture_provider.h" // nogncheck
#include "content/browser/renderer_host/media/media_stream_manager.h" // nogncheck
#include "content/browser/renderer_host/media/media_stream_ui_proxy.h" // nogncheck
#include "content/browser/renderer_host/media/service_video_capture_provider.h" // nogncheck
#include "content/browser/renderer_host/media/video_capture_host.h" // nogncheck
#include "content/browser/renderer_host/media/video_capture_manager.h" // nogncheck
#include "content/browser/renderer_host/media/video_capture_provider_switcher.h" // nogncheck
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/global_routing_id.h"
#include "content/public/browser/media_device_id.h"
#include "content/public/test/browser_task_environment.h"
#include "content/public/test/test_browser_context.h"
#include "content/test/fuzzer/mojolpm_fuzzer_support.h"
#include "content/test/fuzzer/video_capture_host_mojolpm_fuzzer.pb.h"
#include "content/test/test_content_browser_client.h"
#include "media/audio/audio_system_impl.h"
#include "media/audio/mock_audio_manager.h"
#include "media/audio/test_audio_thread.h"
#include "media/capture/mojom/video_capture.mojom-mojolpm.h"
#include "media/capture/video/create_video_capture_device_factory.h"
#include "media/capture/video/linux/fake_device_provider.h"
#include "media/capture/video/linux/fake_v4l2_impl.h"
#include "media/capture/video/linux/video_capture_device_factory_v4l2.h"
#include "media/capture/video/video_capture_system_impl.h"
#include "media/capture/video_capture_types.h"
#include "third_party/blink/public/common/mediastream/media_devices.h"
#include "third_party/libprotobuf-mutator/src/src/libfuzzer/libfuzzer_macro.h"
const char* kCmdline[] = {"video_capture_host_mojolpm_fuzzer", nullptr};
// Describe all the devices (as descriptors).
const uint32_t kNumDeviceDescriptors = 4;
const media::VideoCaptureDeviceDescriptors kDeviceDescriptors{
{"dev_name_1", "dev_id_1"},
{"dev_name_2", "dev_id_2"},
{"dev_name_3", "dev_id_3"},
{"dev_name_4", "dev_id_4"}};
// Specifies number of render process ids (counted from 0).
// All devices are opened for each id.
const uint32_t kNumRenderProcessIds = 2;
using blink::mojom::MediaDeviceType;
content::mojolpm::FuzzerEnvironment& GetEnvironment() {
static base::NoDestructor<content::mojolpm::FuzzerEnvironment> environment(
1, kCmdline);
return *environment;
}
scoped_refptr<base::SequencedTaskRunner> GetFuzzerTaskRunner() {
return GetEnvironment().fuzzer_task_runner();
}
// Per-testcase state needed to run the interface being tested.
//
// The lifetime of this is scoped to a single testcase, and it is created and
// destroyed from the fuzzer sequence.
//
// The component under fuzz `VideoCaptureHost` is created on call
// `AddVideoCaptureHost` (singly bound using a self owned receiver).
// This directly relies on `MediaStreamManager`, which is owned here (as well as
// indirectly reliant on other components owned here).
//
// `MediaStreamManager` is a `CurrentThread::DestructionObserver`, so it must
// outlive the `BrowserTaskEnvironment`. So the task environment is scoped per
// testcase (undesirable for performance).
class VideoCaptureHostTestcase {
public:
VideoCaptureHostTestcase(
const content::fuzzing::video_capture_host::proto::Testcase& testcase);
~VideoCaptureHostTestcase();
// Returns true once either all of the actions in the testcase have been
// performed, or the per-testcase action limit has been exceeded.
//
// This should only be called from the fuzzer sequence.
bool IsFinished();
// If there are still actions remaining in the testcase, this will perform the
// next sequence of actions before returning.
//
// If IsFinished() would return true, then calling this function is a no-op.
//
// This should only be called from the fuzzer sequence.
void NextAction();
private:
using Action = content::fuzzing::video_capture_host::proto::Action;
void SetUp();
void SetUpOnIOThreadFirst();
void SetUpOnUIThread();
void SetUpOnIOThreadSecond();
// We want to open video sessions, to enable behaviour for the fuzzer.
// To do this, we enumerate with the `MediaDeviceManager` (on UI thread),
// then `OpenDevice` with the `MediaStreamManager` (on IO thread).
// `OpenSession` is repeated for each `render_process_id`.
// So the same devices are opened for multiple ids.
void OpenSession(int render_process_id,
int render_frame_id,
int requester_id,
int page_request_id);
void OpenSessionOnUIThread(
int render_process_id,
int render_frame_id,
content::MediaDeviceSaltAndOrigin* salt_and_origin);
void OpenSessionOnIOThread(
int render_process_id,
int render_frame_id,
int requester_id,
int page_request_id,
const content::MediaDeviceSaltAndOrigin& salt_and_origin,
base::OnceClosure quit_closure);
void TearDown();
void TearDownOnIOThread();
void TearDownOnUIThread();
std::unique_ptr<content::FakeMediaStreamUIProxy> CreateFakeUI();
// A callback to receive the enumerated devices in the
// `WebMediaDeviceInfoArray`.
void VideoInputDevicesEnumerated(
base::OnceClosure quit_closure,
const std::string& salt,
const url::Origin& security_origin,
blink::WebMediaDeviceInfoArray* out,
const content::MediaDeviceEnumeration& enumeration);
// A callback which confirms opening device success. This provides the
// session ids for the devices, stored in `opened_session_ids_`.
void OnDeviceOpened(base::OnceClosure quit_closure,
int render_process_id,
uint32_t device_index,
bool success,
const std::string& label,
const blink::MediaStreamDevice& opened_device);
// Create and bind a new instance for fuzzing. This needs to make sure that
// the new instance has been created and bound on the correct sequence
// before returning.
void AddVideoCaptureHost(uint32_t id,
uint32_t render_process_id,
uint32_t routing_id);
// This wraps `HandleRemoteAction`, making the call for the correct device.
// As it requires specifying the `render_process_id` and `device_index`.
// to find the correct session id for the device.
void HandleDeviceRemoteAction(
const content::fuzzing::video_capture_host::proto::
VideoCaptureHostDeviceRemoteAction& device_remote_action);
// Used to directly inject the session id into the `RemoteAction`,
// overwriting the protobuf field.
using RemoteAction = mojolpm::media::mojom::VideoCaptureHost_RemoteAction;
const RemoteAction& RemoteActionInjectSessionId(
const RemoteAction& remote_method_action,
const ::base::UnguessableToken& input);
// We register, and therefore use, the devices per `render_process_id`.
// So this gets the appropriate session id.
const base::UnguessableToken& OpenedSessionId(int render_process_id,
uint32_t device_index);
// The proto message describing the test actions to perform.
const raw_ref<const content::fuzzing::video_capture_host::proto::Testcase>
testcase_;
// Apply a reasonable upper-bound on testcase complexity to avoid timeouts.
const int max_action_count_ = 512;
// Apply a reasonable upper-bound on maximum size of action that we will
// deserialize. (This is deliberately slightly larger than max mojo message
// size)
const size_t max_action_size_ = 300 * 1024 * 1024;
// Count of total actions performed in this testcase.
int action_count_ = 0;
// The index of the next sequence of actions to execute.
int next_sequence_idx_ = 0;
// Prerequisite components for the `VideoCaptureHost`.
std::unique_ptr<content::MediaStreamManager> media_stream_manager_;
std::unique_ptr<media::AudioManager> audio_manager_;
std::unique_ptr<media::AudioSystem> audio_system_;
// Indexed by `render_process_id` then `kDeviceDescriptors` index
// See `OpenedSessionId` getter.
std::array<std::array<base::UnguessableToken, kNumDeviceDescriptors>,
kNumRenderProcessIds>
opened_session_ids_;
// Prerequisite state.
content::BrowserTaskEnvironment task_environment_;
content::TestBrowserContext browser_context_;
content::TestContentBrowserClient browser_client_;
SEQUENCE_CHECKER(sequence_checker_);
};
VideoCaptureHostTestcase::VideoCaptureHostTestcase(
const content::fuzzing::video_capture_host::proto::Testcase& testcase)
: testcase_(testcase),
task_environment_(
base::test::TaskEnvironment::MainThreadType::DEFAULT,
base::test::TaskEnvironment::ThreadPoolExecutionMode::ASYNC,
base::test::TaskEnvironment::ThreadingMode::MULTIPLE_THREADS,
content::BrowserTaskEnvironment::REAL_IO_THREAD),
browser_context_(),
browser_client_() {
// VideoCaptureHostTestcase is created on the main thread, but the actions
// that we want to validate the sequencing of take place on the fuzzer
// sequence.
DETACH_FROM_SEQUENCE(sequence_checker_);
SetUp();
for (uint32_t render_process_id = 0; render_process_id < kNumRenderProcessIds;
render_process_id++)
OpenSession(render_process_id,
/*render_frame_id=*/1,
/*requester_id=*/1,
/*page_request_id=*/1);
}
VideoCaptureHostTestcase::~VideoCaptureHostTestcase() {
TearDown();
}
bool VideoCaptureHostTestcase::IsFinished() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return next_sequence_idx_ >= testcase_->sequence_indexes_size();
}
void VideoCaptureHostTestcase::NextAction() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (next_sequence_idx_ < testcase_->sequence_indexes_size()) {
auto sequence_idx = testcase_->sequence_indexes(next_sequence_idx_++);
const auto& sequence =
testcase_->sequences(sequence_idx % testcase_->sequences_size());
for (auto action_idx : sequence.action_indexes()) {
if (!testcase_->actions_size() || ++action_count_ > max_action_count_) {
return;
}
const auto& action =
testcase_->actions(action_idx % testcase_->actions_size());
if (action.ByteSizeLong() > max_action_size_) {
return;
}
switch (action.action_case()) {
case Action::kRunThread: {
if (action.run_thread().id()) {
base::RunLoop run_loop(base::RunLoop::Type::kNestableTasksAllowed);
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE, run_loop.QuitClosure());
run_loop.Run();
} else {
base::RunLoop run_loop(base::RunLoop::Type::kNestableTasksAllowed);
content::GetIOThreadTaskRunner({})->PostTask(
FROM_HERE, run_loop.QuitClosure());
run_loop.Run();
}
} break;
case Action::kNewVideoCaptureHost: {
AddVideoCaptureHost(
action.new_video_capture_host().id(),
action.new_video_capture_host().render_process_id(),
action.new_video_capture_host().routing_id());
} break;
case Action::kVideoCaptureHostDeviceRemoteAction: {
HandleDeviceRemoteAction(
action.video_capture_host_device_remote_action());
} break;
case Action::kVideoCaptureObserverReceiverAction: {
mojolpm::HandleReceiverAction(
action.video_capture_observer_receiver_action());
} break;
case Action::ACTION_NOT_SET:
break;
}
}
}
}
void VideoCaptureHostTestcase::SetUp() {
{
base::RunLoop run_loop{base::RunLoop::Type::kNestableTasksAllowed};
content::GetIOThreadTaskRunner({})->PostTaskAndReply(
FROM_HERE,
base::BindOnce(&VideoCaptureHostTestcase::SetUpOnIOThreadFirst,
base::Unretained(this)),
run_loop.QuitClosure());
run_loop.Run();
}
{
base::RunLoop run_loop{base::RunLoop::Type::kNestableTasksAllowed};
content::GetUIThreadTaskRunner({})->PostTaskAndReply(
FROM_HERE,
base::BindOnce(&VideoCaptureHostTestcase::SetUpOnUIThread,
base::Unretained(this)),
run_loop.QuitClosure());
run_loop.Run();
}
{
base::RunLoop run_loop{base::RunLoop::Type::kNestableTasksAllowed};
content::GetIOThreadTaskRunner({})->PostTaskAndReply(
FROM_HERE,
base::BindOnce(&VideoCaptureHostTestcase::SetUpOnIOThreadSecond,
base::Unretained(this)),
run_loop.QuitClosure());
run_loop.Run();
}
}
void VideoCaptureHostTestcase::SetUpOnIOThreadFirst() {
audio_manager_ = std::make_unique<media::MockAudioManager>(
std::make_unique<media::TestAudioThread>());
audio_system_ =
std::make_unique<media::AudioSystemImpl>(audio_manager_.get());
}
void VideoCaptureHostTestcase::SetUpOnUIThread() {
// Here we specify the devices described by `kDeviceDescriptors`.
// Which tells the `VideoCaptureDeviceFactoryV4L2` what devices we have.
// This factory is then used to setup the `MediaStreamManager`.
std::unique_ptr<media::FakeDeviceProvider> fake_device_provider =
std::make_unique<media::FakeDeviceProvider>();
scoped_refptr<media::FakeV4L2Impl> fake_v4l2_impl =
base::MakeRefCounted<media::FakeV4L2Impl>();
for (const auto& descriptor : kDeviceDescriptors) {
// Note, despite the param name, `device_name` should match `device_id`
fake_v4l2_impl->AddDevice(/*device_name=*/descriptor.device_id,
media::FakeV4L2DeviceConfig(descriptor));
fake_device_provider->AddDevice(descriptor);
}
std::unique_ptr<media::VideoCaptureDeviceFactoryV4L2>
video_capture_device_factory =
std::make_unique<media::VideoCaptureDeviceFactoryV4L2>(
task_environment_.GetMainThreadTaskRunner());
video_capture_device_factory->SetV4L2EnvironmentForTesting(
std::move(fake_v4l2_impl), std::move(fake_device_provider));
// Ensure MediaInternals is created on the UI thread before starting the
// MediaStreamManager instance.
content::MediaInternals::GetInstance();
auto fake_video_capture_provider =
std::make_unique<content::FakeVideoCaptureProvider>(
std::move(video_capture_device_factory));
auto screencapture_video_capture_provider =
content::InProcessVideoCaptureProvider::CreateInstanceForScreenCapture(
base::SingleThreadTaskRunner::GetCurrentDefault());
media_stream_manager_ = std::make_unique<content::MediaStreamManager>(
audio_system_.get(),
std::make_unique<content::VideoCaptureProviderSwitcher>(
std::move(fake_video_capture_provider),
std::move(screencapture_video_capture_provider)));
}
void VideoCaptureHostTestcase::SetUpOnIOThreadSecond() {
media_stream_manager_->UseFakeUIFactoryForTests(base::BindRepeating(
&VideoCaptureHostTestcase::CreateFakeUI, base::Unretained(this)));
}
void VideoCaptureHostTestcase::OpenSession(int render_process_id,
int render_frame_id,
int requester_id,
int page_request_id) {
// We get `salt_and_origin` on the UI Thread, and use it on the IO thread.
content::MediaDeviceSaltAndOrigin salt_and_origin =
content::MediaDeviceSaltAndOrigin::Empty();
{
base::RunLoop run_loop{base::RunLoop::Type::kNestableTasksAllowed};
content::GetUIThreadTaskRunner({})->PostTaskAndReply(
FROM_HERE,
base::BindOnce(&VideoCaptureHostTestcase::OpenSessionOnUIThread,
base::Unretained(this), render_process_id,
render_frame_id, base::Unretained(&salt_and_origin)),
run_loop.QuitClosure());
run_loop.Run();
}
{
base::RunLoop run_loop{base::RunLoop::Type::kNestableTasksAllowed};
content::GetIOThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(&VideoCaptureHostTestcase::OpenSessionOnIOThread,
base::Unretained(this), render_process_id,
render_frame_id, requester_id, page_request_id,
salt_and_origin, run_loop.QuitClosure()));
run_loop.Run();
}
}
void VideoCaptureHostTestcase::OpenSessionOnUIThread(
int render_process_id,
int render_frame_id,
content::MediaDeviceSaltAndOrigin* out_salt_and_origin) {
base::test::TestFuture<const content::MediaDeviceSaltAndOrigin&> future;
content::GetMediaDeviceSaltAndOrigin(
content::GlobalRenderFrameHostId(render_process_id, render_frame_id),
future.GetCallback());
*out_salt_and_origin = future.Get();
}
void VideoCaptureHostTestcase::OpenSessionOnIOThread(
int render_process_id,
int render_frame_id,
int requester_id,
int page_request_id,
const content::MediaDeviceSaltAndOrigin& salt_and_origin,
base::OnceClosure quit_closure) {
// Enumerate video devices.
blink::WebMediaDeviceInfoArray video_devices;
{
base::RunLoop run_loop{base::RunLoop::Type::kNestableTasksAllowed};
content::MediaDevicesManager::BoolDeviceTypes devices_to_enumerate;
devices_to_enumerate[static_cast<size_t>(
MediaDeviceType::kMediaVideoInput)] = true;
media_stream_manager_->media_devices_manager()->EnumerateDevices(
devices_to_enumerate,
base::BindOnce(&VideoCaptureHostTestcase::VideoInputDevicesEnumerated,
base::Unretained(this), run_loop.QuitClosure(),
salt_and_origin.device_id_salt(),
salt_and_origin.origin(), &video_devices));
run_loop.Run();
}
// Open video devices.
for (uint32_t device_index = 0; device_index < video_devices.size();
device_index++) {
base::RunLoop run_loop{base::RunLoop::Type::kNestableTasksAllowed};
media_stream_manager_->OpenDevice(
{render_process_id, render_frame_id}, requester_id, page_request_id,
video_devices[device_index].device_id,
blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE, salt_and_origin,
base::BindOnce(&VideoCaptureHostTestcase::OnDeviceOpened,
base::Unretained(this), run_loop.QuitClosure(),
render_process_id, device_index),
content::MediaStreamManager::DeviceStoppedCallback());
run_loop.Run();
}
std::move(quit_closure).Run();
}
void VideoCaptureHostTestcase::TearDown() {
{
base::RunLoop run_loop{base::RunLoop::Type::kNestableTasksAllowed};
content::GetIOThreadTaskRunner({})->PostTaskAndReply(
FROM_HERE,
base::BindOnce(&VideoCaptureHostTestcase::TearDownOnIOThread,
base::Unretained(this)),
run_loop.QuitClosure());
run_loop.Run();
}
{
base::RunLoop run_loop{base::RunLoop::Type::kNestableTasksAllowed};
content::GetUIThreadTaskRunner({})->PostTaskAndReply(
FROM_HERE,
base::BindOnce(&VideoCaptureHostTestcase::TearDownOnUIThread,
base::Unretained(this)),
run_loop.QuitClosure());
run_loop.Run();
}
}
void VideoCaptureHostTestcase::TearDownOnIOThread() {
audio_manager_->Shutdown();
audio_manager_.reset();
}
void VideoCaptureHostTestcase::TearDownOnUIThread() {
audio_system_.reset();
}
std::unique_ptr<content::FakeMediaStreamUIProxy>
VideoCaptureHostTestcase::CreateFakeUI() {
return std::make_unique<content::FakeMediaStreamUIProxy>(
/*tests_use_fake_render_frame_hosts=*/true);
}
void VideoCaptureHostTestcase::VideoInputDevicesEnumerated(
base::OnceClosure quit_closure,
const std::string& salt,
const url::Origin& security_origin,
blink::WebMediaDeviceInfoArray* out,
const content::MediaDeviceEnumeration& enumeration) {
for (const auto& info :
enumeration[static_cast<size_t>(MediaDeviceType::kMediaVideoInput)]) {
std::string device_id =
content::GetHMACForMediaDeviceID(salt, security_origin, info.device_id);
out->push_back(
blink::WebMediaDeviceInfo(device_id, info.label, std::string()));
}
std::move(quit_closure).Run();
}
void VideoCaptureHostTestcase::OnDeviceOpened(
base::OnceClosure quit_closure,
int render_process_id,
uint32_t device_index,
bool success,
const std::string& label,
const blink::MediaStreamDevice& opened_device) {
if (success) {
opened_session_ids_[render_process_id][device_index] =
opened_device.session_id();
}
std::move(quit_closure).Run();
}
void VideoCaptureHostTestcase::AddVideoCaptureHost(uint32_t id,
uint32_t render_process_id,
uint32_t routing_id) {
mojo::Remote<::media::mojom::VideoCaptureHost> remote;
auto receiver = remote.BindNewPipeAndPassReceiver();
base::RunLoop run_loop(base::RunLoop::Type::kNestableTasksAllowed);
content::GetIOThreadTaskRunner({})->PostTaskAndReply(
FROM_HERE,
base::BindOnce(
&content::VideoCaptureHost::Create,
content::GlobalRenderFrameHostId(render_process_id, routing_id),
media_stream_manager_.get(), std::move(receiver)),
run_loop.QuitClosure());
run_loop.Run();
mojolpm::GetContext()->AddInstance(id, std::move(remote));
}
/* Matches signature of a mojolpm::ToProto implementation, which has not been
* implemented yet, to be replaced once that is done.
* see "mojo/public/mojom/base/unguessable_token.mojom-mojolpm.h"
*/
bool ToProto(const ::base::UnguessableToken& input,
mojolpm::mojo_base::mojom::UnguessableToken& output) {
if (output.has_new_()) {
output.mutable_new_()->set_id(1UL);
output.mutable_new_()->set_m_low(input.GetLowForSerialization());
output.mutable_new_()->set_m_high(input.GetHighForSerialization());
return true;
}
auto allocated_new = std::make_unique<
mojolpm::mojo_base::mojom::UnguessableToken_ProtoStruct>();
allocated_new->set_id(1UL);
allocated_new->set_m_low(input.GetLowForSerialization());
allocated_new->set_m_high(input.GetHighForSerialization());
// passes ownership of `allocated_new`
output.set_allocated_new_(allocated_new.release());
return true;
}
void VideoCaptureHostTestcase::HandleDeviceRemoteAction(
const content::fuzzing::video_capture_host::proto::
VideoCaptureHostDeviceRemoteAction& device_remote_action) {
const base::UnguessableToken& token =
OpenedSessionId(device_remote_action.render_process_id(),
device_remote_action.device_index());
mojolpm::HandleRemoteAction(
RemoteActionInjectSessionId(device_remote_action.remote_action(), token));
}
const VideoCaptureHostTestcase::RemoteAction&
VideoCaptureHostTestcase::RemoteActionInjectSessionId(
const RemoteAction& remote_method_action,
const ::base::UnguessableToken& token) {
// `const_cast` used for performance (could also copy)
RemoteAction& remote_method_action_mutable =
const_cast<RemoteAction&>(remote_method_action);
mojolpm::mojo_base::mojom::UnguessableToken* m_session_id;
switch (remote_method_action_mutable.method_case()) {
case RemoteAction::kMStart:
m_session_id = remote_method_action_mutable.mutable_m_start()
->mutable_m_session_id();
break;
case RemoteAction::kMResume:
m_session_id = remote_method_action_mutable.mutable_m_resume()
->mutable_m_session_id();
break;
case RemoteAction::kMGetDeviceSupportedFormats:
m_session_id =
remote_method_action_mutable.mutable_m_get_device_supported_formats()
->mutable_m_session_id();
break;
case RemoteAction::kMGetDeviceFormatsInUse:
m_session_id =
remote_method_action_mutable.mutable_m_get_device_formats_in_use()
->mutable_m_session_id();
break;
default:
return remote_method_action;
}
if (!ToProto(token, *m_session_id))
return remote_method_action;
return remote_method_action_mutable;
}
const base::UnguessableToken& VideoCaptureHostTestcase::OpenedSessionId(
int render_process_id,
uint32_t device_index) {
return opened_session_ids_[render_process_id % kNumRenderProcessIds]
[device_index % kNumDeviceDescriptors];
}
// Helper function to keep scheduling fuzzer actions on the current runloop
// until the testcase has completed, and then quit the runloop.
void NextAction(VideoCaptureHostTestcase* testcase,
base::RepeatingClosure quit_closure) {
if (!testcase->IsFinished()) {
testcase->NextAction();
GetFuzzerTaskRunner()->PostTask(
FROM_HERE, base::BindOnce(NextAction, base::Unretained(testcase),
std::move(quit_closure)));
} else {
GetFuzzerTaskRunner()->PostTask(FROM_HERE, std::move(quit_closure));
}
}
// Helper function to setup and run the testcase, since we need to do that from
// the fuzzer sequence rather than the main thread.
void RunTestcase(VideoCaptureHostTestcase* testcase) {
mojo::Message message;
auto dispatch_context =
std::make_unique<mojo::internal::MessageDispatchContext>(&message);
mojolpm::GetContext()->StartTestcase();
base::RunLoop fuzzer_run_loop(base::RunLoop::Type::kNestableTasksAllowed);
GetFuzzerTaskRunner()->PostTask(
FROM_HERE, base::BindOnce(NextAction, base::Unretained(testcase),
fuzzer_run_loop.QuitClosure()));
fuzzer_run_loop.Run();
mojolpm::GetContext()->EndTestcase();
}
DEFINE_BINARY_PROTO_FUZZER(
const content::fuzzing::video_capture_host::proto::Testcase&
proto_testcase) {
if (!proto_testcase.actions_size() || !proto_testcase.sequences_size() ||
!proto_testcase.sequence_indexes_size()) {
return;
}
// Make sure that the environment is initialized before we do anything else.
GetEnvironment();
VideoCaptureHostTestcase testcase(proto_testcase);
base::RunLoop ui_run_loop(base::RunLoop::Type::kNestableTasksAllowed);
// Unretained is safe here, because ui_run_loop has to finish before testcase
// goes out of scope.
GetFuzzerTaskRunner()->PostTaskAndReply(
FROM_HERE, base::BindOnce(RunTestcase, base::Unretained(&testcase)),
ui_run_loop.QuitClosure());
ui_run_loop.Run();
}