| /****************************************************************************** |
| * |
| * Copyright (C) 2020 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at: |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| * |
| ***************************************************************************** |
| * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore |
| */ |
| #include <stdio.h> |
| |
| #include <C2Fuzzer.h> |
| |
| using namespace android; |
| |
| class LinearBuffer : public C2Buffer { |
| public: |
| explicit LinearBuffer(const std::shared_ptr<C2LinearBlock>& block) |
| : C2Buffer({block->share(block->offset(), block->size(), ::C2Fence())}) {} |
| |
| explicit LinearBuffer(const std::shared_ptr<C2LinearBlock>& block, size_t size) |
| : C2Buffer({block->share(block->offset(), size, ::C2Fence())}) {} |
| }; |
| |
| /** |
| * Handle Callback functions onWorkDone_nb(), onTripped_nb(), onError_nb() for C2 Components |
| */ |
| struct CodecListener : public C2Component::Listener { |
| public: |
| CodecListener(const std::function<void(std::weak_ptr<C2Component> comp, |
| std::list<std::unique_ptr<C2Work>>& workItems)> |
| fn = nullptr) |
| : callBack(fn) {} |
| virtual void onWorkDone_nb(const std::weak_ptr<C2Component> comp, |
| std::list<std::unique_ptr<C2Work>> workItems) { |
| if (callBack) { |
| callBack(comp, workItems); |
| } |
| } |
| |
| virtual void onTripped_nb(const std::weak_ptr<C2Component> comp, |
| const std::vector<std::shared_ptr<C2SettingResult>> settingResults) { |
| (void)comp; |
| (void)settingResults; |
| } |
| |
| virtual void onError_nb(const std::weak_ptr<C2Component> comp, uint32_t errorCode) { |
| (void)comp; |
| (void)errorCode; |
| } |
| |
| std::function<void(std::weak_ptr<C2Component> comp, |
| std::list<std::unique_ptr<C2Work>>& workItems)> callBack; |
| }; |
| |
| /** |
| * Buffer source implementations to identify a frame and its size |
| */ |
| bool Codec2Fuzzer::BufferSource::searchForMarker() { |
| while (true) { |
| if (isMarker()) { |
| return true; |
| } |
| --mReadIndex; |
| if (mReadIndex > mSize) { |
| break; |
| } |
| } |
| return false; |
| } |
| |
| void Codec2Fuzzer::BufferSource::parse() { |
| bool isFrameAvailable = true; |
| size_t bytesRemaining = mSize; |
| while (isFrameAvailable) { |
| isFrameAvailable = searchForMarker(); |
| if (isFrameAvailable) { |
| size_t location = mReadIndex + kMarkerSize; |
| bool isCSD = isCSDMarker(location); |
| location += kMarkerSuffixSize; |
| uint8_t* framePtr = const_cast<uint8_t*>(&mData[location]); |
| size_t frameSize = bytesRemaining - location; |
| uint32_t flags = 0; |
| if (mFrameList.empty()) { |
| flags |= C2FrameData::FLAG_END_OF_STREAM; |
| } else if (isCSD) { |
| flags |= C2FrameData::FLAG_CODEC_CONFIG; |
| } |
| mFrameList.emplace_back(std::make_tuple(framePtr, frameSize, flags)); |
| bytesRemaining -= (frameSize + kMarkerSize + kMarkerSuffixSize); |
| --mReadIndex; |
| } |
| } |
| if (mFrameList.empty()) { |
| /** |
| * Scenario where input data does not contain the custom frame markers. |
| * Hence feed the entire data as single frame. |
| */ |
| mFrameList.emplace_back( |
| std::make_tuple(const_cast<uint8_t*>(mData), 0, C2FrameData::FLAG_END_OF_STREAM)); |
| mFrameList.emplace_back( |
| std::make_tuple(const_cast<uint8_t*>(mData), mSize, C2FrameData::FLAG_CODEC_CONFIG)); |
| } |
| } |
| |
| FrameData Codec2Fuzzer::BufferSource::getFrame() { |
| FrameData frame = mFrameList.back(); |
| mFrameList.pop_back(); |
| return frame; |
| } |
| |
| void Codec2Fuzzer::handleWorkDone(std::weak_ptr<C2Component> comp, |
| std::list<std::unique_ptr<C2Work>>& workItems) { |
| (void)comp; |
| for (std::unique_ptr<C2Work>& work : workItems) { |
| if (!work->worklets.empty()) { |
| if (work->worklets.front()->output.flags != C2FrameData::FLAG_INCOMPLETE) { |
| mEos = (work->worklets.front()->output.flags & C2FrameData::FLAG_END_OF_STREAM) != 0; |
| work->input.buffers.clear(); |
| work->worklets.clear(); |
| { |
| std::unique_lock<std::mutex> lock(mQueueLock); |
| mWorkQueue.push_back(std::move(work)); |
| mQueueCondition.notify_all(); |
| } |
| if (mEos) { |
| { |
| std::lock_guard<std::mutex> waitForDecodeComplete(mDecodeCompleteMutex); |
| } |
| mConditionalVariable.notify_one(); |
| } |
| } |
| } |
| } |
| } |
| |
| bool Codec2Fuzzer::initDecoder() { |
| std::vector<std::tuple<C2String, C2ComponentFactory::CreateCodec2FactoryFunc, |
| C2ComponentFactory::DestroyCodec2FactoryFunc>> codec2FactoryFunc; |
| |
| codec2FactoryFunc.emplace_back( |
| std::make_tuple(C2COMPONENTNAME, &CreateCodec2Factory, &DestroyCodec2Factory)); |
| |
| std::shared_ptr<C2ComponentStore> componentStore = GetTestComponentStore(codec2FactoryFunc); |
| if (!componentStore) { |
| return false; |
| } |
| |
| std::shared_ptr<C2AllocatorStore> allocatorStore = GetCodec2PlatformAllocatorStore(); |
| if (!allocatorStore) { |
| return false; |
| } |
| |
| c2_status_t status = |
| allocatorStore->fetchAllocator(C2AllocatorStore::DEFAULT_LINEAR, &mLinearAllocator); |
| if (status != C2_OK) { |
| return false; |
| } |
| |
| mLinearPool = std::make_shared<C2PooledBlockPool>(mLinearAllocator, ++mBlockPoolId); |
| if (!mLinearPool) { |
| return false; |
| } |
| |
| for (int32_t i = 0; i < kNumberOfC2WorkItems; ++i) { |
| mWorkQueue.emplace_back(new C2Work); |
| } |
| |
| status = componentStore->createComponent(C2COMPONENTNAME, &mComponent); |
| if (status != C2_OK) { |
| return false; |
| } |
| |
| status = componentStore->createInterface(C2COMPONENTNAME, &mInterface); |
| if (status != C2_OK) { |
| return false; |
| } |
| |
| C2ComponentKindSetting kind; |
| C2ComponentDomainSetting domain; |
| status = mInterface->query_vb({&kind, &domain}, {}, C2_MAY_BLOCK, nullptr); |
| if (status != C2_OK) { |
| return false; |
| } |
| |
| std::vector<C2Param*> configParams; |
| C2StreamPictureSizeInfo::input inputSize(0u, kWidthOfVideo, kHeightOfVideo); |
| C2StreamSampleRateInfo::output sampleRateInfo(0u, kSamplingRateOfAudio); |
| C2StreamChannelCountInfo::output channelCountInfo(0u, kChannelsOfAudio); |
| if (domain.value == DOMAIN_VIDEO) { |
| configParams.push_back(&inputSize); |
| } else if (domain.value == DOMAIN_AUDIO) { |
| configParams.push_back(&sampleRateInfo); |
| configParams.push_back(&channelCountInfo); |
| } |
| |
| mListener.reset(new CodecListener( |
| [this](std::weak_ptr<C2Component> comp, std::list<std::unique_ptr<C2Work>>& workItems) { |
| handleWorkDone(comp, workItems); |
| })); |
| if (!mListener) { |
| return false; |
| } |
| |
| status = mComponent->setListener_vb(mListener, C2_DONT_BLOCK); |
| if (status != C2_OK) { |
| return false; |
| } |
| |
| std::vector<std::unique_ptr<C2SettingResult>> failures; |
| componentStore->config_sm(configParams, &failures); |
| if (failures.size() != 0) { |
| return false; |
| } |
| |
| status = mComponent->start(); |
| if (status != C2_OK) { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| void Codec2Fuzzer::deInitDecoder() { |
| mComponent->stop(); |
| mComponent->reset(); |
| mComponent->release(); |
| mComponent = nullptr; |
| } |
| |
| void Codec2Fuzzer::decodeFrames(const uint8_t* data, size_t size) { |
| static const size_t kPageSize = getpagesize(); |
| std::unique_ptr<BufferSource> bufferSource = std::make_unique<BufferSource>(data, size); |
| if (!bufferSource) { |
| return; |
| } |
| bufferSource->parse(); |
| c2_status_t status = C2_OK; |
| size_t numFrames = 0; |
| int32_t iterationCount = 0; |
| while (!bufferSource->isEos() && ++iterationCount <= kMaxIterations) { |
| uint8_t* frame = nullptr; |
| size_t frameSize = 0; |
| FrameData frameData = bufferSource->getFrame(); |
| frame = std::get<0>(frameData); |
| frameSize = std::get<1>(frameData); |
| |
| std::unique_ptr<C2Work> work; |
| { |
| std::unique_lock<std::mutex> lock(mQueueLock); |
| if (mWorkQueue.empty()) mQueueCondition.wait_for(lock, kC2FuzzerTimeOut); |
| if (!mWorkQueue.empty()) { |
| work.swap(mWorkQueue.front()); |
| mWorkQueue.pop_front(); |
| } else { |
| return; |
| } |
| } |
| |
| work->input.flags = (C2FrameData::flags_t)std::get<2>(frameData); |
| work->input.ordinal.timestamp = 0; |
| work->input.ordinal.frameIndex = ++numFrames; |
| work->input.buffers.clear(); |
| int32_t alignedSize = C2FUZZER_ALIGN(frameSize, kPageSize); |
| |
| std::shared_ptr<C2LinearBlock> block; |
| status = mLinearPool->fetchLinearBlock( |
| alignedSize, {C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE}, &block); |
| if (status != C2_OK || block == nullptr) { |
| return; |
| } |
| |
| C2WriteView view = block->map().get(); |
| if (view.error() != C2_OK) { |
| return; |
| } |
| memcpy(view.base(), frame, frameSize); |
| work->input.buffers.emplace_back(new LinearBuffer(block, frameSize)); |
| work->worklets.clear(); |
| work->worklets.emplace_back(new C2Worklet); |
| |
| std::list<std::unique_ptr<C2Work>> items; |
| items.push_back(std::move(work)); |
| status = mComponent->queue_nb(&items); |
| if (status != C2_OK) { |
| return; |
| } |
| } |
| std::unique_lock<std::mutex> waitForDecodeComplete(mDecodeCompleteMutex); |
| mConditionalVariable.wait_for(waitForDecodeComplete, kC2FuzzerTimeOut, [this] { return mEos; }); |
| std::list<std::unique_ptr<C2Work>> c2flushedWorks; |
| mComponent->flush_sm(C2Component::FLUSH_COMPONENT, &c2flushedWorks); |
| } |
| |
| extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { |
| if (size < 1) { |
| return 0; |
| } |
| Codec2Fuzzer* codec = new Codec2Fuzzer(); |
| if (!codec) { |
| return 0; |
| } |
| if (codec->initDecoder()) { |
| codec->decodeFrames(data, size); |
| } |
| delete codec; |
| return 0; |
| } |