/*
 * Copyright 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.
 */

#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <vndk/hardware_buffer.h>

#include "InputFrame.h"
#include "PixelFormatUtils.h"
#include "PixelStreamManager.h"
#include "gmock/gmock-matchers.h"
#include "Fuzz.pb.h"
#include "src/libfuzzer/libfuzzer_macro.h"

namespace android {
namespace automotive {
namespace computepipe {
namespace runner {
namespace stream_manager {
namespace {

InputFrame convertToInputFrame(const fuzz::proto::Frame &frame) {
    uint32_t height = frame.height();
    uint32_t width = frame.width();
    uint32_t stride = frame.stride();
    PixelFormat pixelFormat = static_cast<PixelFormat>(frame.format());
    const uint8_t* data = reinterpret_cast<const uint8_t*>(frame.buffer().c_str());
    return InputFrame(height, width, pixelFormat, stride, data);
}

int setFrameDataTest(const fuzz::proto::PixelMemHandleFuzzerInput& input) {
    int bufferId = 10;
    int streamId = 1;
    uint64_t timestamp = 100;

    PixelMemHandle memHandle(bufferId, streamId, AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN);
    InputFrame oldInputFrame = convertToInputFrame(input.frames().Get(0));
    memHandle.setFrameData(timestamp, oldInputFrame);

    // overwrite frame data with different format and dimensions
    InputFrame newInputFrame = convertToInputFrame(input.frames().Get(1));
    memHandle.setFrameData(timestamp, newInputFrame);

    AHardwareBuffer_Desc desc;
    AHardwareBuffer* buffer = memHandle.getHardwareBuffer();
    AHardwareBuffer_describe(buffer, &desc);
    return 0;
}

bool isValid(const fuzz::proto::PixelMemHandleFuzzerInput& input) {
    for (auto& frame : input.frames()) {
        uint64_t height = frame.height();
        uint64_t width = frame.width();
        uint64_t stride = frame.stride();
        uint64_t size = frame.buffer().size();
        if (stride > width * height) {
            return false;
        }

        if (height * width != size) {
            return false;
        }

        if (height * width == 0) {
            return false;
        }
    }

    return true;
}

// generate guided and mutated frame data for fuzzing
DEFINE_PROTO_FUZZER(const fuzz::proto::PixelMemHandleFuzzerInput& input) {
    static PostProcessorRegistration reg = {
            [](const fuzz::proto::PixelMemHandleFuzzerInput* input, unsigned int seed) {
                if (input->frames().size() < 2) {
                    return;
                }

                if (!isValid(*input)) {
                    return;
                }

                setFrameDataTest(*input);
            }
    };
}

}  // namespace
}  // namespace stream_manager
}  // namespace runner
}  // namespace computepipe
}  // namespace automotive
}  // namespace android
