#include "ChaCha20.h"

/**
   This code is licensed under the MCGSI Public License
   Copyright 2018 Jeff Becker

   Kovri go write your own code

 */

#if LEGACY_OPENSSL
namespace i2p
{
namespace crypto
{
namespace chacha
{
constexpr int rounds = 20;
constexpr std::size_t blocksize = 64;

void u32t8le(uint32_t v, uint8_t * p) 
{
    p[0] = v & 0xff;
    p[1] = (v >> 8) & 0xff;
    p[2] = (v >> 16) & 0xff;
    p[3] = (v >> 24) & 0xff;
}

uint32_t u8t32le(const uint8_t * p) 
{
    uint32_t value = p[3];

    value = (value << 8) | p[2];
    value = (value << 8) | p[1];
    value = (value << 8) | p[0];

    return value;
}

uint32_t rotl32(uint32_t x, int n) 
{
    return x << n | (x >> (-n & 31));
}

void quarterround(uint32_t *x, int a, int b, int c, int d) 
{
    x[a] += x[b]; x[d] = rotl32(x[d] ^ x[a], 16);
    x[c] += x[d]; x[b] = rotl32(x[b] ^ x[c], 12);
    x[a] += x[b]; x[d] = rotl32(x[d] ^ x[a],  8);
    x[c] += x[d]; x[b] = rotl32(x[b] ^ x[c],  7);
}

    struct State_t
    {
      State_t() {};
      State_t(State_t &&) = delete;
      
      State_t & operator += (const State_t & other)
      {
        for(int i = 0; i < 16; i++)
            data[i] += other.data[i];
        return *this;
      }

      void Copy(const State_t & other)
      {
           memcpy(data, other.data, sizeof(uint32_t) * 16);
      }
      uint32_t data[16];
    };

    struct Block_t
    {
      Block_t() {};
      Block_t(Block_t &&) = delete;

      uint8_t data[blocksize];

      void operator << (const State_t & st)
      {
        int i;
        for (i = 0; i < 16; i++) 
            u32t8le(st.data[i], data + (i << 2));
      }
    };

void block(const State_t &input, Block_t & block, int rounds)
{
    int i;
    State_t x;
    x.Copy(input);

    for (i = rounds; i > 0; i -= 2) 
    {
        quarterround(x.data, 0, 4,  8, 12);
        quarterround(x.data, 1, 5,  9, 13);
        quarterround(x.data, 2, 6, 10, 14);
        quarterround(x.data, 3, 7, 11, 15);
        quarterround(x.data, 0, 5, 10, 15);
        quarterround(x.data, 1, 6, 11, 12);
        quarterround(x.data, 2, 7,  8, 13);
        quarterround(x.data, 3, 4,  9, 14);
    }
    x += input;
    block << x;

}
} // namespace chacha





void chacha20(uint8_t * buf, size_t sz, const uint8_t * nonce, const uint8_t * key, uint32_t counter)
{
    chacha::State_t state;
    chacha::Block_t block;
    size_t i, j;

    state.data[0] = 0x61707865;
    state.data[1] = 0x3320646e;
    state.data[2] = 0x79622d32;
    state.data[3] = 0x6b206574;

    for (i = 0; i < 8; i++) 
        state.data[4 + i] = chacha::u8t32le(key + i * 4);
    

    state.data[12] = counter;

    for (i = 0; i < 3; i++) 
        state.data[13 + i] = chacha::u8t32le(nonce + i * 4);

    
    for (i = 0; i < sz; i += chacha::blocksize) 
    {
        chacha::block(state, block, chacha::rounds);
        state.data[12]++;
        for (j = i; j < i + chacha::blocksize; j++) 
        {
            if (j >= sz) break;
            buf[j] ^= block.data[j - i];
        }
    }

}

}
}
#endif