BareGit
#include "secret_token.h"

#include <algorithm>
#include <array>
#include <cstddef>
#include <string>

namespace
{

constexpr std::array<char, 16> HEX = {
    '0', '1', '2', '3', '4', '5', '6', '7',
    '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};

mw::E<std::string> decodeToken(const std::string& token)
{
    if(token.size() != 64)
    {
        return std::unexpected(mw::runtimeError("Invalid token"));
    }
    std::string bytes(32, '\0');
    for(std::size_t index = 0; index < bytes.size(); ++index)
    {
        const auto nibble = [](char value) -> int
        {
            if(value >= '0' && value <= '9')
            {
                return value - '0';
            }
            if(value >= 'a' && value <= 'f')
            {
                return value - 'a' + 10;
            }
            return -1;
        };
        const int high = nibble(token[index * 2]);
        const int low = nibble(token[index * 2 + 1]);
        if(high < 0 || low < 0)
        {
            return std::unexpected(mw::runtimeError("Invalid token"));
        }
        bytes[index] = static_cast<char>((high << 4) | low);
    }
    return bytes;
}

} // namespace

mw::E<SecretToken> generateSecretToken(mw::CryptoInterface& crypto)
{
    auto random = crypto.randomBytes(32);
    if(!random)
    {
        return std::unexpected(std::move(random.error()));
    }
    std::string bytes;
    bytes.reserve(random->size());
    std::string value;
    value.reserve(random->size() * 2);
    for(std::byte byte : *random)
    {
        const unsigned int number = std::to_integer<unsigned int>(byte);
        bytes.push_back(static_cast<char>(number));
        value.push_back(HEX[number >> 4]);
        value.push_back(HEX[number & 0x0f]);
    }
    auto hash = mw::SHA256Hasher().hashToBytes(bytes);
    if(!hash)
    {
        return std::unexpected(std::move(hash.error()));
    }
    return SecretToken{std::move(value), std::move(*hash)};
}

mw::E<std::vector<unsigned char>> hashSecretToken(const std::string& token)
{
    auto bytes = decodeToken(token);
    if(!bytes)
    {
        return std::unexpected(std::move(bytes.error()));
    }
    return mw::SHA256Hasher().hashToBytes(*bytes);
}

bool constantTimeEqual(const std::string& first, const std::string& second)
{
    const std::size_t length = std::max(first.size(), second.size());
    std::size_t difference = first.size() ^ second.size();
    for(std::size_t index = 0; index < length; ++index)
    {
        const unsigned char left = index < first.size() ? first[index] : 0;
        const unsigned char right = index < second.size() ? second[index] : 0;
        difference |= static_cast<std::size_t>(left ^ right);
    }
    return difference == 0;
}