/*
 * Copyright (c) 2021 Huawei Device Co., Ltd.
 * 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 "pkg_algorithm.h"
#include "pkg_algo_deflate.h"
#include "pkg_algo_lz4.h"
#include "pkg_stream.h"
#include "pkg_utils.h"
#include "securec.h"

namespace Hpackage {
constexpr uint32_t MAX_BUFFER_SIZE = (4 * 1024 * 1024);

int32_t PkgAlgorithm::ReadData(const PkgStreamPtr inStream, size_t offset, PkgBuffer &buffer,
    size_t &remainSize, size_t &readLen) const
{
    size_t readBytes = 0;
    size_t remainBytes = (remainSize > buffer.length) ? buffer.length : remainSize;
    if (remainBytes != 0) {
        int32_t ret = inStream->Read(buffer, offset, remainBytes, readBytes);
        if (ret != PKG_SUCCESS) {
            PKG_LOGE("fail deflate write data ");
            return ret;
        }
    }
    remainSize -= readBytes;
    readLen = readBytes;
    return PKG_SUCCESS;
}

int32_t PkgAlgorithm::FinalDigest(DigestAlgorithm::DigestAlgorithmPtr algorithm,
    PkgAlgorithmContext &context, bool check) const
{
    if (context.digestMethod == PKG_DIGEST_TYPE_SHA256) {
        PkgBuffer digest(DIGEST_MAX_LEN);
        algorithm->Final(digest);
        if (check && memcmp(digest.buffer, context.digest, DIGEST_MAX_LEN) != 0) {
            return PKG_INVALID_DIGEST;
        }
        if (memcpy_s(context.digest, sizeof(context.digest), digest.buffer, DIGEST_MAX_LEN) != EOK) {
            PKG_LOGE("FinalDigest memcpy failed");
            return PKG_NONE_MEMORY;
        }
    }
    return PKG_SUCCESS;
}

int32_t PkgAlgorithm::Pack(const PkgStreamPtr inStream, const PkgStreamPtr outStream,
    PkgAlgorithmContext &context)
{
    DigestAlgorithm::DigestAlgorithmPtr algorithm = PkgAlgorithmFactory::GetDigestAlgorithm(context.digestMethod);
    if (algorithm == nullptr) {
        PKG_LOGE("Can not get digest algor");
        return PKG_NOT_EXIST_ALGORITHM;
    }
    algorithm->Init();

    PkgBuffer buffer(MAX_BUFFER_SIZE);
    int32_t ret = PKG_SUCCESS;
    size_t srcOffset = context.srcOffset;
    size_t destOffset = context.destOffset;
    size_t remainSize = context.unpackedSize;
    size_t readLen = 0;
    while (remainSize > 0) {
        ret = ReadData(inStream, srcOffset, buffer, remainSize, readLen);
        if (ret != PKG_SUCCESS) {
            PKG_LOGE("Fail read data ");
            break;
        }

        ret = outStream->Write(buffer, readLen, destOffset);
        if (ret != PKG_SUCCESS) {
            PKG_LOGE("Fail write data ");
            break;
        }

        algorithm->Update(buffer, readLen);
        srcOffset += readLen;
        destOffset += readLen;
    }

    ret = FinalDigest(algorithm, context, true);
    if (ret != 0) {
        PKG_LOGE("Check digest fail");
        return ret;
    }
    if (srcOffset - context.srcOffset != context.unpackedSize) {
        PKG_LOGE("original size error %zu %zu", srcOffset, context.unpackedSize);
        return PKG_INVALID_DIGEST;
    }
    context.packedSize = destOffset - context.destOffset;
    return ret;
}

int32_t PkgAlgorithm::Unpack(const PkgStreamPtr inStream, const PkgStreamPtr outStream,
    PkgAlgorithmContext &context)
{
    DigestAlgorithm::DigestAlgorithmPtr algorithm = PkgAlgorithmFactory::GetDigestAlgorithm(context.digestMethod);
    if (algorithm == nullptr) {
        PKG_LOGE("Can not get digest algor");
        return PKG_NOT_EXIST_ALGORITHM;
    }
    algorithm->Init();
    PkgBuffer buffer(nullptr, MAX_BUFFER_SIZE);
    int32_t ret = PKG_SUCCESS;
    size_t srcOffset = context.srcOffset;
    size_t destOffset = context.destOffset;
    size_t remainSize = context.packedSize;
    size_t readLen = 0;
    while (remainSize > 0) {
        ret = ReadData(inStream, srcOffset, buffer, remainSize, readLen);
        if (ret != PKG_SUCCESS) {
            PKG_LOGE("Fail read data ");
            break;
        }

        ret = outStream->Write(buffer, readLen, destOffset);
        if (ret != PKG_SUCCESS) {
            PKG_LOGE("Fail write data ");
            break;
        }

        algorithm->Update(buffer, readLen);
        srcOffset += readLen;
        destOffset += readLen;
    }

    ret = FinalDigest(algorithm, context, true);
    if (ret != 0) {
        PKG_LOGE("Check digest fail");
        return ret;
    }
    if (destOffset - context.destOffset != context.packedSize) {
        PKG_LOGE("original size error %zu %zu", destOffset, context.packedSize);
        return PKG_INVALID_DIGEST;
    }
    context.unpackedSize = srcOffset - context.srcOffset;
    return ret;
}

int32_t PkgAlgorithm::UnpackWithVerify(const PkgStreamPtr inStream, const PkgStreamPtr outStream,
    PkgAlgorithmContext &context, VerifyFunction verifier)
{
    DigestAlgorithm::DigestAlgorithmPtr algorithm = PkgAlgorithmFactory::GetDigestAlgorithm(context.digestMethod);
    if (algorithm == nullptr) {
        PKG_LOGE("Can not get digest algor");
        return PKG_NOT_EXIST_ALGORITHM;
    }
    algorithm->Init();
    PkgBuffer buffer(nullptr, MAX_BUFFER_SIZE);

    int32_t ret = PKG_SUCCESS;
    size_t srcOffset = context.srcOffset;
    size_t destOffset = context.destOffset;
    size_t remainSize = context.packedSize;
    size_t readLen = 0;
    while (remainSize > 0) {
        ret = ReadData(inStream, srcOffset, buffer, remainSize, readLen);
        if (ret != PKG_SUCCESS) {
            PKG_LOGE("Fail read data ");
            break;
        }

        if (verifier != nullptr) {
            ret = verifier(buffer, readLen, destOffset);
            if (ret != PKG_SUCCESS) {
                PKG_LOGE("Fail verify read data");
                break;
            }
        }

        ret = outStream->Write(buffer, readLen, destOffset);
        if (ret != PKG_SUCCESS) {
            PKG_LOGE("Fail write data ");
            break;
        }

        algorithm->Update(buffer, readLen);
        srcOffset += readLen;
        destOffset += readLen;
    }

    ret = FinalDigest(algorithm, context, true);
    if (ret != 0) {
        PKG_LOGE("Check digest fail");
        return ret;
    }
    if (destOffset - context.destOffset != context.packedSize) {
        PKG_LOGE("original size error %zu %zu", destOffset, context.packedSize);
        return PKG_INVALID_DIGEST;
    }
    context.unpackedSize = srcOffset - context.srcOffset;
    return ret;
}

PkgAlgorithm::PkgAlgorithmPtr PkgAlgorithmFactory::GetAlgorithm(const PkgManager::FileInfoPtr config)
{
    if (config == nullptr) {
        PKG_LOGE("Fail to get algorithm ");
        return nullptr;
    }
    switch (config->packMethod) {
        case PKG_COMPRESS_METHOD_ZIP:
        case PKG_COMPRESS_METHOD_GZIP: {
            if (config == nullptr) {
                PKG_LOGE("Invalid param config");
                return nullptr;
            }
            const ZipFileInfo *info = (ZipFileInfo *)config;
            return std::make_shared<PkgAlgoDeflate>(*info);
        }
        case PKG_COMPRESS_METHOD_NONE:
            return std::make_shared<PkgAlgorithm>();
        case PKG_COMPRESS_METHOD_LZ4: {
            if (config == nullptr) {
                PKG_LOGE("Invalid param config");
                return nullptr;
            }
            const Lz4FileInfo *info = (Lz4FileInfo *)config;
            return std::make_shared<PkgAlgorithmLz4>(*info);
        }
        case PKG_COMPRESS_METHOD_LZ4_BLOCK: {
            if (config == nullptr) {
                PKG_LOGE("Invalid param config");
                return nullptr;
            }
            const Lz4FileInfo *info = (Lz4FileInfo *)config;
            return std::make_shared<PkgAlgorithmBlockLz4>(*info);
        }
        default:
            break;
    }
    return nullptr;
}
} // namespace Hpackage
