blob: cb6acc047ccad4513d9e048914fc6c20160d6247 [file] [log] [blame]
// Copyright 2019 The Chromium OS 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 <fcntl.h>
#include <base/bind.h>
#include <base/bind_helpers.h>
#include <base/files/file_descriptor_watcher_posix.h>
#include <base/files/file_util.h>
#include <base/logging.h>
#include <base/macros.h>
#include <base/memory/weak_ptr.h>
#include <base/memory/writable_shared_memory_region.h>
#include <base/posix/eintr_wrapper.h>
#include <base/threading/thread.h>
#include "arc/vm/libvda/decode/test/decode_unittest_common.h"
#include "arc/vm/libvda/libvda_decode.h"
namespace {
// Maximum supported output buffers is 32 as set here:
// https://codesearch.chromium.org/chromium/src/components/arc/video_accelerator/gpu_arc_video_decode_accelerator.cc?rcl=df1fede89a832a708df47f329e265bb3ff3366e3&l=49
constexpr size_t kMaxNumOutputBuffers = 32;
// Maximum number of planes per output buffer.
// TODO(alexlau): Increase this limit?
constexpr size_t kMaxPlanes = 4;
} // namespace
class Environment {
public:
Environment() {
logging::SetMinLogLevel(logging::LOGGING_FATAL); // Disable logging.
}
Environment(const Environment&) = delete;
Environment& operator=(const Environment&) = delete;
};
class ReadEventThread {
public:
explicit ReadEventThread(int fd) : fd_(fd), thread_("ReadEventThread") {
CHECK(thread_.StartWithOptions(
base::Thread::Options(base::MessagePumpType::IO, 0)));
thread_.task_runner()->PostTask(
FROM_HERE, base::BindOnce(&ReadEventThread::StartWatching,
base::Unretained(this)));
}
ReadEventThread(const ReadEventThread&) = delete;
ReadEventThread& operator=(const ReadEventThread&) = delete;
~ReadEventThread() {
thread_.task_runner()->PostTask(
FROM_HERE,
base::BindOnce(&ReadEventThread::StopWatching, base::Unretained(this)));
}
private:
void StartWatching() {
event_pipe_fd_controller_ = base::FileDescriptorWatcher::WatchReadable(
fd_, base::BindRepeating(&ReadEventThread::OnEventPipeReadable,
weak_factory_.GetWeakPtr()));
}
void StopWatching() { event_pipe_fd_controller_.reset(); }
void OnEventPipeReadable() {
// Read the events to make sure the pipe buffer doesn't become
// completely full.
// TODO(alexlau): Should these events be parsed and responded to?
vda_event_t event;
if (!base::ReadFromFD(fd_, reinterpret_cast<char*>(&event),
sizeof(vda_event_t))) {
LOG(ERROR) << "Failed to read from event pipe.";
return;
}
switch (event.event_type) {
case PICTURE_READY: {
const picture_ready_event_data_t& picture_ready =
event.event_data.picture_ready;
LOG(INFO) << "Received PICTURE_READY event with"
<< " picture_buffer_id=" << picture_ready.picture_buffer_id
<< " bitstream_id=" << picture_ready.bitstream_id
<< " crop_left=" << picture_ready.crop_left
<< " crop_top=" << picture_ready.crop_top
<< " crop_right=" << picture_ready.crop_right
<< " crop_bottom=" << picture_ready.crop_bottom;
break;
}
case PROVIDE_PICTURE_BUFFERS: {
const provide_picture_buffers_event_data_t& provide_picture_buffers =
event.event_data.provide_picture_buffers;
LOG(INFO) << "Received PROVIDE_PICTURE_BUFFERS event with"
<< " min_num_buffers="
<< provide_picture_buffers.min_num_buffers
<< " width=" << provide_picture_buffers.width
<< " height=" << provide_picture_buffers.height
<< " visible_rect_left="
<< provide_picture_buffers.visible_rect_left
<< " visible_rect_top="
<< provide_picture_buffers.visible_rect_top
<< " visible_rect_right="
<< provide_picture_buffers.visible_rect_right
<< " visible_rect_bottom="
<< provide_picture_buffers.visible_rect_bottom;
break;
}
case NOTIFY_END_OF_BITSTREAM_BUFFER:
LOG(INFO) << "Received NOTIFY_END_OF_BITSTREAM_BUFFER event with"
<< " bitstream_id=" << event.event_data.bitstream_id;
break;
case NOTIFY_ERROR:
LOG(INFO) << "Received NOTIFY_ERROR event with result="
<< event.event_data.result;
break;
case RESET_RESPONSE:
LOG(INFO) << "Received RESET_RESPONSE event with result="
<< event.event_data.result;
break;
case FLUSH_RESPONSE:
LOG(INFO) << "Received FLUSH_RESPONSE event with result="
<< event.event_data.result;
break;
default:
LOG(ERROR) << "Received unknown event type " << event.event_type;
break;
}
}
// Event pipe FD. SessionManager's |impl| retains ownership and will close it
// when the decode session is stopped.
int fd_;
base::Thread thread_;
std::unique_ptr<base::FileDescriptorWatcher::Controller>
event_pipe_fd_controller_;
base::WeakPtrFactory<ReadEventThread> weak_factory_{this};
};
class ScopedSession {
public:
ScopedSession()
: impl_(SetupImpl(GAVDA)),
session_(SetupSession(impl_, H264PROFILE_MIN)) {}
ScopedSession(const ScopedSession&) = delete;
ScopedSession& operator=(const ScopedSession&) = delete;
~ScopedSession() { DestroyImpl(); }
void RecreateImplAndSession() {
DestroyImpl();
impl_ = SetupImpl(GAVDA);
RecreateSession();
}
void RecreateSession() {
DestroySession();
session_ = SetupSession(impl_, H264PROFILE_MIN);
event_thread_ = std::make_unique<ReadEventThread>(session_->event_pipe_fd);
}
void* impl() { return impl_.get(); }
void* ctx() { return session_.get()->ctx; }
private:
void DestroySession() {
if (session_) {
event_thread_.reset();
session_.reset();
}
}
void DestroyImpl() {
DestroySession();
if (impl_) {
impl_.reset();
}
}
ImplPtr impl_;
SessionPtr session_;
std::unique_ptr<ReadEventThread> event_thread_;
};
class DataReader {
public:
DataReader(const uint8_t* data, size_t size)
: data_(data), size_(size), index_(0) {}
DataReader(const DataReader&) = delete;
DataReader& operator=(const DataReader&) = delete;
uint8_t GetUint8() {
if (index_ >= size_) {
index_ = 0;
}
return data_[index_++];
}
uint32_t GetUint32() {
// We don't care about endianness here, so ordering of the
// retrieved bytes can be the same for both, as long as the
// 32 bit integer is populated.
uint32_t value = 0;
for (int i = 0; i < 4; ++i) {
value = (value << 8) | GetUint8();
}
return value;
}
int32_t GetInt32() {
uint32_t value = GetUint32();
int32_t* int_ptr = reinterpret_cast<int32_t*>(&value);
return *int_ptr;
}
size_t GetSize() {
static_assert(sizeof(size_t) >= 4,
"size_t is unexpectedly smaller than 32 bits.");
return static_cast<size_t>(GetUint32());
}
private:
const uint8_t* data_;
size_t size_;
size_t index_;
};
enum class VdaCommand : uint8_t {
RECREATE_IMPL,
RECREATE_SESSION,
GET_CAPABILITIES,
DECODE,
SET_OUTPUT_BUFFER_COUNT,
USE_OUTPUT_BUFFER,
FLUSH,
RESET,
COMMAND_MAX = RESET
};
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
static Environment env;
// |session_manager| is a static variable responsible for setting up the VDA
// session, such that the same connection can be used across different
// invocations of LLVMFuzzerTestOneInput.
static ScopedSession session_manager;
if (!size)
return 0;
DataReader reader(data, size);
VdaCommand command = static_cast<VdaCommand>(
reader.GetUint8() % (static_cast<uint8_t>(VdaCommand::COMMAND_MAX) + 1));
LOG(INFO) << "Command: " << static_cast<uint8_t>(command);
switch (command) {
case VdaCommand::RECREATE_IMPL:
session_manager.RecreateImplAndSession();
break;
case VdaCommand::RECREATE_SESSION:
session_manager.RecreateSession();
break;
case VdaCommand::GET_CAPABILITIES:
get_vda_capabilities(session_manager.impl());
break;
case VdaCommand::DECODE: {
const int32_t bitstream_id = reader.GetInt32();
const bool no_allocate_shm = reader.GetUint8() % 2 == 0;
uint32_t offset = reader.GetUint32();
uint32_t bytes_used = reader.GetUint32();
int fd;
if (no_allocate_shm) {
fd = HANDLE_EINTR(open("/dev/urandom", O_RDONLY));
} else {
// When allocating, try not to run out of memory.
offset %= 4096;
bytes_used %= 4096;
const size_t data_size = offset + bytes_used;
base::WritableSharedMemoryRegion shm_region =
base::WritableSharedMemoryRegion::Create(data_size);
base::WritableSharedMemoryMapping shm_mapping = shm_region.Map();
base::ScopedFD random_fd(HANDLE_EINTR(open("/dev/urandom", O_RDONLY)));
HANDLE_EINTR(read(random_fd.get(), shm_mapping.memory(), data_size));
base::subtle::PlatformSharedMemoryRegion platform_shm =
base::WritableSharedMemoryRegion::TakeHandleForSerialization(
std::move(shm_region));
base::subtle::PlatformSharedMemoryRegion::ScopedPlatformHandle handle =
platform_shm.PassPlatformHandle();
fd = handle.fd.release();
}
// Ownership of the FD is passed. We don't hold the fd out of the output
// buffer as we're not interested in the buffer content when fuzzing.
vda_decode(session_manager.ctx(), bitstream_id, fd, offset, bytes_used);
break;
}
case VdaCommand::SET_OUTPUT_BUFFER_COUNT: {
size_t num_output_buffers = reader.GetSize();
num_output_buffers = num_output_buffers % (kMaxNumOutputBuffers + 1);
vda_set_output_buffer_count(session_manager.ctx(), num_output_buffers);
break;
}
case VdaCommand::USE_OUTPUT_BUFFER: {
const int32_t picture_buffer_id = reader.GetInt32();
const vda_pixel_format_t format = static_cast<vda_pixel_format_t>(
reader.GetUint32() % (PIXEL_FORMAT_MAX + 1));
// TODO(alexlau): Consider passing a real dmabuf?
const int fd = HANDLE_EINTR(open("/dev/urandom", O_RDWR));
const size_t num_planes = reader.GetSize() % (kMaxPlanes + 1);
std::vector<video_frame_plane_t> planes(num_planes);
for (auto& plane : planes) {
plane.offset = reader.GetInt32();
plane.stride = reader.GetInt32();
}
// Ownership of the FD is passed.
vda_use_output_buffer(session_manager.ctx(), picture_buffer_id, format,
fd, num_planes, planes.data());
break;
}
case VdaCommand::FLUSH: {
vda_flush(session_manager.ctx());
break;
}
case VdaCommand::RESET: {
vda_reset(session_manager.ctx());
break;
}
default:
NOTREACHED();
}
return 0;
}