#include "firmware_signature.h"

#include <contrib/libs/openssl/include/openssl/crypto.h>
#include <contrib/libs/openssl/include/openssl/evp.h>
#include <contrib/libs/openssl/include/openssl/hmac.h>

#include <library/cpp/openssl/holders/hmac.h>

#include <maps/libs/common/include/exception.h>

#include <util/string/hex.h>

#include <maps/libs/log8/include/log8.h>

namespace maps::fw_updater::fw_signature {

namespace {


bool isSignatureCorrect(const HmacSha1Digest& correctSignature, const HmacSha1Digest& requestSignature)
{
    return !CRYPTO_memcmp(correctSignature.cbegin(), requestSignature.cbegin(), HMAC_SHA1_SIZE);
}

std::optional<HmacSha1Digest> tryUnhexSignature(const std::string& hex)
{
    if (hex.size() != 2 * HMAC_SHA1_SIZE) {
        WARN() << "Wrong signature size (" << hex.size() << ")";
        return std::nullopt;
    }
    try {
        HmacSha1Digest result;
        HexDecode(hex.c_str(), hex.size(), result.begin());
        return result;
    } catch (const yexception&) {
        return std::nullopt;
    }
}

}; // namespace

HmacSha1Digest hmacSha1(const std::string& signatureKey, const std::vector<std::string>& messageChunks)
{
    NOpenSSL::THmacCtx hmacCtx;
    ASSERT(HMAC_Init_ex(hmacCtx, signatureKey.data(), signatureKey.size(), EVP_sha1(), nullptr));

    for (const auto& chunk : messageChunks) {
        ASSERT(HMAC_Update(hmacCtx,
                           reinterpret_cast<const unsigned char*>(chunk.c_str()),
                           chunk.size()));
    }

    HmacSha1Digest correctSignature;
    ASSERT(HMAC_Final(hmacCtx, correctSignature.begin(), nullptr));

    return correctSignature;
}

bool verifyFirmwareSignatureImpl(const std::string& firmwareSignature,
                                 const std::string& userAgent,
                                 const std::string& host,
                                 const std::string& requestMethod,
                                 const std::string& pathAndQuery,
                                 const std::string& requestBody,
                                 const std::string& signatureKey)
{
    const auto requestSignature = tryUnhexSignature(firmwareSignature);
    if (!requestSignature) {
        return false;
    }

    const auto correctSignature = hmacSha1(
        signatureKey,
        {userAgent, host, requestMethod, pathAndQuery, requestBody}
    );

    return isSignatureCorrect(correctSignature, *requestSignature);
}

}; // namespace maps::fw_updater::fw_signature
