#include <stdint.h>

#define LENGTH_OF_BLOCK 8

void xxtea_Encrypt(uint32_t * data, uint8_t dataLength, const uint32_t key[4]);
void xxtea_Decrypt(uint32_t * data, uint8_t dataLength, const uint32_t key[4]);

/* The data packets are encrypted using the xxtea algorithm. */
void Encrypt(uint32_t * data, uint8_t dataLength, uint64_t salt, const uint32_t key[4])
{
	/* This function assumes that the dataLength is a multiple of the length of the
	 * salt (8)
	 *
	 * The data is encrypted using cipher block chaining:
	 * (https://upload.wikimedia.org/wikipedia/commons/8/80/CBC_encryption.svg)
	 */

	uint8_t i;
	uint8_t * previousCipherBlock = (uint8_t*) &salt;
	uint8_t * currentPlaintextBlock = (uint8_t*) data;

	for (i = 0; i < dataLength/LENGTH_OF_BLOCK; i++)
	{
		/* XOR of current plain text block and previous cipher block */
		//for (j = 0; j < LENGTH_OF_BLOCK; j++)
		//{
		//	currentPlaintextBlock[j] ^= previousCipherBlock[j];
		//}
		*((uint64_t*) currentPlaintextBlock) ^= *((uint64_t*) previousCipherBlock);

		/* Encrypt the block */
		xxtea_Encrypt((uint32_t*) currentPlaintextBlock, LENGTH_OF_BLOCK, key);

		/* Setup for next block */
		previousCipherBlock = currentPlaintextBlock;
		currentPlaintextBlock += LENGTH_OF_BLOCK;
	}

}


void Decrypt(uint32_t * data, uint8_t dataLength, uint64_t salt, const uint32_t key[4])
{
	/* This function assumes that the dataLength is a multiple of the length of the
	 * salt (8)
	 *
	 * The data is encrypted using cipher block chaining:
	 * (https://upload.wikimedia.org/wikipedia/commons/2/2a/CBC_decryption.svg)
	 */

	/* The data is decrypted from back to front as front to back cannot be done in
	 * place
	 */
	uint8_t i;
	uint8_t * previousCipherBlock = (uint8_t*) data + dataLength - 2 * LENGTH_OF_BLOCK;
	uint8_t * currentCipherBlock = (uint8_t*) data + dataLength - LENGTH_OF_BLOCK;

	for (i = dataLength/LENGTH_OF_BLOCK; i > 0; i--)
	{
		/* Decrypt the block */
		xxtea_Decrypt((uint32_t*) currentCipherBlock, LENGTH_OF_BLOCK, key);

		/* XOR of the decrypted block with cipher block in front of it */
		*((uint64_t*) currentCipherBlock) ^= *((uint64_t*) previousCipherBlock);

		/* Setup for next block */
		currentCipherBlock = previousCipherBlock;
		if (previousCipherBlock != (uint8_t*) data + LENGTH_OF_BLOCK)
		{
			previousCipherBlock -= LENGTH_OF_BLOCK;
		}
		else
		{
			previousCipherBlock = (uint8_t*) &salt;
		}
	}
}

void xxtea_Encrypt(uint32_t * data, uint8_t dataLength, const uint32_t key[4])
{
    uint32_t sum = 0, z, y, e;
    uint8_t i = 6 + 52/(dataLength/4), r;
    const int16_t n = dataLength / 4 - 1;

    z = data[n]; // left neighbour for the first round
    do {
        // cycle
        sum += 0x9e3779b9ul;
        e = (sum >> 2) & 3;
        for (r = 0; r <= n; r++) {
            // round
            y = data[(r+1) % (n + 1)]; // right neighbour
            data[r] += ((z>>5 ^ y<<2) + (y>>3 ^ z<<4)) ^ ((sum^y) + (key[(r^e) & 3] ^ z));
            z = data[r]; // left neighbour for the next round
        }
    } while (--i);
}

void xxtea_Decrypt(uint32_t * data, uint8_t dataLength, const uint32_t key[4])
{
    uint32_t sum, z, y, e;
    int16_t i = 6 + 52/(dataLength/4), r;
    const int16_t n = dataLength / 4;

    sum = (uint32_t) ((uint64_t) (6 + 52/(dataLength/4)) * 0x9e3779b9ul);
    y = data[0];
    do {
        // cycle
        e = sum >> 2;
        for (r = n-1; r >= 0; --r) {
            // round
            z = data[(r+n-1) % n];
            data[r] -= ((z>>5 ^ y<<2) + (y>>3 ^ z<<4)) ^ ((sum^y) + (key[(r^e) & 3] ^ z));
            y = data[r];
        }
        sum -= 0x9e3779b9;
    } while (--i);
}
