// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/modules/webcodecs/audio_encoder.h"
#include <string>
#include "base/run_loop.h"
#include "base/test/scoped_feature_list.h"
#include "media/base/media_switches.h"
#include "media/media_buildflags.h"
#include "media/mojo/buildflags.h"
#include "media/mojo/mojom/audio_encoder.mojom.h"
#include "media/mojo/mojom/interface_factory.mojom.h"
#include "media/mojo/services/mojo_audio_encoder_service.h"
#include "testing/libfuzzer/proto/lpm_interface.h"
#include "third_party/blink/public/common/thread_safe_browser_interface_broker_proxy.h"
#include "third_party/blink/public/platform/scheduler/test/renderer_scheduler_test_support.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_audio_decoder_config.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_audio_encoder_init.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_encoded_audio_chunk_output_callback.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_webcodecs_error_callback.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/frame/settings.h"
#include "third_party/blink/renderer/core/testing/dummy_page_holder.h"
#include "third_party/blink/renderer/modules/webcodecs/encoded_audio_chunk.h"
#include "third_party/blink/renderer/modules/webcodecs/fuzzer_inputs.pb.h"
#include "third_party/blink/renderer/modules/webcodecs/fuzzer_utils.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h"
#include "third_party/blink/renderer/platform/bindings/script_state.h"
#include "third_party/blink/renderer/platform/heap/persistent.h"
#include "third_party/blink/renderer/platform/testing/blink_fuzzer_test_support.h"
#include "third_party/blink/renderer/platform/testing/task_environment.h"
#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
#include "third_party/blink/renderer/platform/wtf/vector.h"
#include "third_party/protobuf/src/google/protobuf/text_format.h"
#if BUILDFLAG(IS_WIN) && BUILDFLAG(USE_PROPRIETARY_CODECS)
#include "base/win/scoped_com_initializer.h"
#include "media/gpu/windows/mf_audio_encoder.h"
#define HAS_AAC_ENCODER 1
#endif
#if BUILDFLAG(IS_MAC) && BUILDFLAG(USE_PROPRIETARY_CODECS)
#include "media/filters/mac/audio_toolbox_audio_encoder.h"
#define HAS_AAC_ENCODER 1
#endif
#if HAS_AAC_ENCODER
namespace {
// Other end of remote InterfaceFactory requested by AudioEncoder. Used
// to create real media::mojom::AudioEncoders.
class TestInterfaceFactory : public media::mojom::InterfaceFactory {
public:
TestInterfaceFactory() = default;
~TestInterfaceFactory() override = default;
void BindRequest(mojo::ScopedMessagePipeHandle handle) {
receiver_.Bind(mojo::PendingReceiver<media::mojom::InterfaceFactory>(
std::move(handle)));
// Each AudioEncoder instance will try to open a connection to this
// factory, so we must clean up after each one is destroyed.
receiver_.set_disconnect_handler(WTF::BindOnce(
&TestInterfaceFactory::OnConnectionError, base::Unretained(this)));
}
void OnConnectionError() { receiver_.reset(); }
// Implement this one interface from mojom::InterfaceFactory.
void CreateAudioEncoder(
mojo::PendingReceiver<media::mojom::AudioEncoder> receiver) override {
// While we'd like to use the real GpuMojoMediaFactory here, it requires
// quite a bit more of scaffolding to setup and isn't really needed.
#if BUILDFLAG(IS_MAC)
auto platform_audio_encoder =
std::make_unique<media::AudioToolboxAudioEncoder>();
#elif BUILDFLAG(IS_WIN)
CHECK(com_initializer_.Succeeded());
auto platform_audio_encoder = std::make_unique<media::MFAudioEncoder>(
blink::scheduler::GetSequencedTaskRunnerForTesting());
#else
#error "Unknown platform encoder."
#endif
audio_encoder_receivers_.Add(
std::make_unique<media::MojoAudioEncoderService>(
std::move(platform_audio_encoder)),
std::move(receiver));
}
// Stub out other mojom::InterfaceFactory interfaces.
void CreateVideoDecoder(
mojo::PendingReceiver<media::mojom::VideoDecoder> receiver,
mojo::PendingRemote<media::stable::mojom::StableVideoDecoder>
dst_video_decoder) override {}
void CreateAudioDecoder(
mojo::PendingReceiver<media::mojom::AudioDecoder> receiver) override {}
void CreateDefaultRenderer(
const std::string& audio_device_id,
mojo::PendingReceiver<media::mojom::Renderer> receiver) override {}
#if BUILDFLAG(ENABLE_CAST_RENDERER)
void CreateCastRenderer(
const base::UnguessableToken& overlay_plane_id,
mojo::PendingReceiver<media::mojom::Renderer> receiver) override {}
#endif
#if BUILDFLAG(IS_ANDROID)
void CreateMediaPlayerRenderer(
mojo::PendingRemote<media::mojom::MediaPlayerRendererClientExtension>
client_extension_remote,
mojo::PendingReceiver<media::mojom::Renderer> receiver,
mojo::PendingReceiver<media::mojom::MediaPlayerRendererExtension>
renderer_extension_receiver) override {}
void CreateFlingingRenderer(
const std::string& presentation_id,
mojo::PendingRemote<media::mojom::FlingingRendererClientExtension>
client_extension,
mojo::PendingReceiver<media::mojom::Renderer> receiver) override {}
#endif // BUILDFLAG(IS_ANDROID)
void CreateCdm(const media::CdmConfig& cdm_config,
CreateCdmCallback callback) override {
std::move(callback).Run(mojo::NullRemote(), nullptr,
media::CreateCdmStatus::kCdmNotSupported);
}
#if BUILDFLAG(IS_WIN)
void CreateMediaFoundationRenderer(
mojo::PendingRemote<media::mojom::MediaLog> media_log_remote,
mojo::PendingReceiver<media::mojom::Renderer> receiver,
mojo::PendingReceiver<media::mojom::MediaFoundationRendererExtension>
renderer_extension_receiver,
mojo::PendingRemote<
::media::mojom::MediaFoundationRendererClientExtension>
client_extension_remote) override {}
#endif // BUILDFLAG(IS_WIN)
private:
#if BUILDFLAG(IS_WIN)
base::win::ScopedCOMInitializer com_initializer_;
#endif // BUILDFLAG(IS_WIN)
// media::MojoCdmServiceContext cdm_service_context_;
mojo::Receiver<media::mojom::InterfaceFactory> receiver_{this};
mojo::UniqueReceiverSet<media::mojom::AudioEncoder> audio_encoder_receivers_;
};
} // namespace
#endif // HAS_AAC_ENCODER
namespace blink {
DEFINE_TEXT_PROTO_FUZZER(
const wc_fuzzer::AudioEncoderApiInvocationSequence& proto) {
static BlinkFuzzerTestSupport test_support = BlinkFuzzerTestSupport();
test::TaskEnvironment task_environment;
auto page_holder = std::make_unique<DummyPageHolder>();
page_holder->GetFrame().GetSettings()->SetScriptEnabled(true);
#if HAS_AAC_ENCODER
base::test::ScopedFeatureList platform_aac(media::kPlatformAudioEncoder);
static const bool kSetTestBinder = []() {
auto interface_factory = std::make_unique<TestInterfaceFactory>();
return Platform::Current()
->GetBrowserInterfaceBroker()
->SetBinderForTesting(
media::mojom::InterfaceFactory::Name_,
WTF::BindRepeating(&TestInterfaceFactory::BindRequest,
base::Owned(std::move(interface_factory))));
}();
CHECK(kSetTestBinder) << "Failed to register media interface binder.";
#endif
//
// NOTE: GC objects that need to survive iterations of the loop below
// must be Persistent<>!
//
// GC may be triggered by the RunLoop().RunUntilIdle() below, which will GC
// raw pointers on the stack. This is not required in production code because
// GC typically runs at the top of the stack, or is conservative enough to
// keep stack pointers alive.
//
// Scoping Persistent<> refs so GC can collect these at the end.
Persistent<ScriptState> script_state =
ToScriptStateForMainWorld(&page_holder->GetFrame());
ScriptState::Scope scope(script_state);
Persistent<ScriptFunction> error_function =
MakeGarbageCollected<ScriptFunction>(
script_state, MakeGarbageCollected<FakeFunction>("error"));
Persistent<V8WebCodecsErrorCallback> error_callback =
V8WebCodecsErrorCallback::Create(error_function->V8Function());
Persistent<ScriptFunction> output_function =
MakeGarbageCollected<ScriptFunction>(
script_state, MakeGarbageCollected<FakeFunction>("output"));
Persistent<V8EncodedAudioChunkOutputCallback> output_callback =
V8EncodedAudioChunkOutputCallback::Create(output_function->V8Function());
Persistent<AudioEncoderInit> audio_encoder_init =
MakeGarbageCollected<AudioEncoderInit>();
audio_encoder_init->setError(error_callback);
audio_encoder_init->setOutput(output_callback);
Persistent<AudioEncoder> audio_encoder = AudioEncoder::Create(
script_state, audio_encoder_init, IGNORE_EXCEPTION_FOR_TESTING);
if (audio_encoder) {
for (auto& invocation : proto.invocations()) {
switch (invocation.Api_case()) {
case wc_fuzzer::AudioEncoderApiInvocation::kConfigure: {
AudioEncoderConfig* config =
MakeAudioEncoderConfig(invocation.configure());
// Use the same config to fuzz isConfigSupported().
AudioEncoder::isConfigSupported(script_state, config,
IGNORE_EXCEPTION_FOR_TESTING);
audio_encoder->configure(config, IGNORE_EXCEPTION_FOR_TESTING);
break;
}
case wc_fuzzer::AudioEncoderApiInvocation::kEncode: {
AudioData* data =
MakeAudioData(script_state, invocation.encode().data());
if (!data) {
return;
}
audio_encoder->encode(data, IGNORE_EXCEPTION_FOR_TESTING);
break;
}
case wc_fuzzer::AudioEncoderApiInvocation::kFlush: {
// TODO(https://crbug.com/1119253): Fuzz whether to await resolution
// of the flush promise.
audio_encoder->flush(IGNORE_EXCEPTION_FOR_TESTING);
break;
}
case wc_fuzzer::AudioEncoderApiInvocation::kReset:
audio_encoder->reset(IGNORE_EXCEPTION_FOR_TESTING);
break;
case wc_fuzzer::AudioEncoderApiInvocation::kClose:
audio_encoder->close(IGNORE_EXCEPTION_FOR_TESTING);
break;
case wc_fuzzer::AudioEncoderApiInvocation::API_NOT_SET:
break;
}
// Give other tasks a chance to run (e.g. calling our output callback).
base::RunLoop().RunUntilIdle();
}
}
}
} // namespace blink