#include "stdafx.h"
#include "hmac_sha_base64.h"
#include "openssl/sha.h"

#include <boost/archive/iterators/binary_from_base64.hpp>
#include <boost/archive/iterators/base64_from_binary.hpp>
#include <boost/archive/iterators/transform_width.hpp>
#include <boost/algorithm/string.hpp>

using namespace core;
using namespace tools;

uint8_t *coding=(uint8_t*)"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";                   //52...63

enum
{
    UNABLE_TO_OPEN_INPUT_FILE,
    UNABLE_TO_OPEN_OUTPUT_FILE,
    UNABLE_TO_CREATE_OUTPUTBUFFER
};

uint8_t base64::lobyte(uint16_t w)
{
    return ((uint8_t)(((uint32_t)(w)) & 0xff));
}

uint8_t base64::hibyte(uint16_t w)
{
    return ((uint8_t)((((uint32_t)(w)) >> 8) & 0xff));
}

uint16_t base64::loword(uint32_t l)
{
    return ((uint16_t)(((uint32_t)(l)) & 0xffff));
}

uint16_t base64::hiword(uint32_t l)
{
    return ((uint16_t)((((uint32_t)(l)) >> 16) & 0xffff));
}

int32_t fchr(uint8_t c)
{
    int32_t i=0;
    for (i=0;i < 64;i++) if (coding[i]==c) return (i);
    return (-1);
}

int32_t base64::base64_decode(uint8_t *source,int32_t length, uint8_t *dst)
{
    int32_t temp =0;
    int32_t i=0;
    int32_t size =0;
    int32_t cursor = 0;

    for (;i<length/4;)
    {
        int32_t zeroes;
        int32_t j;
        cursor = 0;
        zeroes = 0;

        *((uint8_t*)&cursor+3) = source[i*4];
        *((uint8_t*)&cursor+2) = source[i*4+1];
        *((uint8_t*)&cursor+1) = source[i*4+2];
        *((uint8_t*)&cursor) = source[i*4+3];
        for (j =0 ;j <4; j++)
        {
            if (*((uint8_t*)&cursor +j) == '=')
            {
                *((uint8_t*)&cursor +j) =0;
                zeroes++;
                continue;
            }
            temp  = fchr( *((uint8_t*)&cursor +j) );
            temp <<=6*j;
            cursor &= ~(0xFF << j*8);
            cursor ^= temp;
        }
        for (j = 0; j<3; j++)
        {
            dst[i*3+j] = *((uint8_t*)&cursor+2);
            cursor <<=8;
            size++;
        }
        i++;
        size-=zeroes;
    }
    return size;
}

int32_t base64::base64_encode(uint8_t* source, int32_t length, uint8_t* dst)
{
    uint32_t cursor =0x0;
    int32_t i=0,size =0, j =0,ostatok = (length%3);

    for (;i<length - (length%3);)
    {
        *((uint8_t*)&cursor) = source[i+2];
        *((uint8_t*)&cursor+1) = source[i+1];
        *((uint8_t*)&cursor+2) = source[i];
        i+=3;

        //shift
        for (j =0; j<4; j++ )
        {
            uint8_t zna4; // delete after debug
            cursor <<=6;
            zna4 = core::tools::base64::hibyte(core::tools::base64::hiword(cursor));
            dst[size+j] = coding[zna4];
            cursor &= 0x0FFFFFF;
        }
        size +=j;
    }
    if (ostatok)
    {
        cursor =0;
        if (ostatok>1)
        {
            *((uint8_t*)&cursor) = source[i+1];
            *((uint8_t*)&cursor+1) = source[i];
        }
        else 
            *((uint8_t*)&cursor) = source[i];

        for (i = 0; i<ostatok+1; i++)
        {
            cursor<<=6;
            if (ostatok == 1)
            {
                dst[size+i] = coding[core::tools::base64::hibyte(core::tools::base64::loword(cursor))];
                cursor &= 0xFF;
            }
            else
            {
                dst[size+i] = coding[core::tools::base64::lobyte(core::tools::base64::hiword(cursor))];
                cursor &= 0xFFFF;
            }

        }
        size +=i;
        for (j=0; j < 4 - i;j++)
        {
            dst[size] = '=';
            size++;
        }
    }
    return (size);
}	


std::string base64::hmac_base64(std::vector<uint8_t>& data, std::vector<uint8_t>& secret)
{
    const unsigned SHA_BLOCKSIZE = 64;

    uint8_t* text = &data[0];
    uint32_t textlen = (uint32_t)data.size();
    uint8_t* key = &secret[0];
    uint32_t keylen = (uint32_t)secret.size();

#define MIR_SHA256_HASH_SIZE 32

    unsigned char mdkey[MIR_SHA256_HASH_SIZE];
    unsigned char k_ipad[SHA_BLOCKSIZE], k_opad[SHA_BLOCKSIZE];

    SHA256_CTX ctx;

    uint8_t res[MIR_SHA256_HASH_SIZE];

    if ( keylen > SHA_BLOCKSIZE )
    {
        SHA256_Init( &ctx );
        SHA256_Update( &ctx, key, keylen );
        SHA256_Final( mdkey, &ctx );

        keylen = 32;
        key = mdkey;
    }

    memcpy( k_ipad, key, keylen );
    memcpy( k_opad, key, keylen );
    memset( k_ipad+keylen, 0x36, SHA_BLOCKSIZE - keylen );
    memset( k_opad+keylen, 0x5c, SHA_BLOCKSIZE - keylen );

    for ( uint32_t i = 0; i < keylen; i++ ) 
    {
        k_ipad[i] ^= 0x36;
        k_opad[i] ^= 0x5c;
    }

    SHA256_Init( &ctx );
    SHA256_Update( &ctx, k_ipad, SHA_BLOCKSIZE );
    SHA256_Update( &ctx, text, (int32_t) textlen );
    SHA256_Final( res, &ctx );

    SHA256_Init( &ctx );
    SHA256_Update( &ctx, k_opad,SHA_BLOCKSIZE );
    SHA256_Update( &ctx, res, (int32_t)MIR_SHA256_HASH_SIZE );
    SHA256_Final( res, &ctx );

    std::vector<uint8_t> temp_buffer(MIR_SHA256_HASH_SIZE*2);
    int32_t encoded = core::tools::base64::base64_encode((uint8_t*) res, MIR_SHA256_HASH_SIZE, &temp_buffer[0]);

    return std::string((char*) &temp_buffer[0], encoded);
}

using namespace boost::archive::iterators;

std::string base64::decode64(const std::string& val)
{
    typedef transform_width<binary_from_base64<std::string::const_iterator>, 8, 6> it;
    return boost::algorithm::trim_right_copy_if(std::string(it(std::begin(val)), it(std::end(val))), [](char c) {
        return c == '\0';
    });
}

std::string base64::encode64(const std::string& val)
{
    typedef base64_from_binary<transform_width<std::string::const_iterator, 6, 8>> it;
    auto tmp = std::string(it(std::begin(val)), it(std::end(val)));
    return tmp.append((3 - val.size() % 3) % 3, '=');
}