chromium/content/test/fuzzer/video_capture_host_mojolpm_fuzzer.cc

// 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();
}