/**********************************************************\
|                                                          |
| xxtea.c                                                  |
|                                                          |
| XXTEA encryption algorithm library for C.                |
|                                                          |
| Encryption Algorithm Authors:                            |
|      David J. Wheeler                                    |
|      Roger M. Needham                                    |
|                                                          |
| Code Authors: Chen fei <cf850118@163.com>                |
|               Ma Bingyao <mabingyao@gmail.com>           |
| LastModified: Feb 7, 2016                                |
|                                                          |
\**********************************************************/


#include "xxtea.h"

#include <string.h>
#if defined(_MSC_VER) && _MSC_VER < 1600
typedef unsigned __int8 uint8_t;
typedef unsigned __int32 uint32_t;
#else
#if defined(__FreeBSD__) && __FreeBSD__ < 5
/* FreeBSD 4 doesn't have stdint.h file */
#include <inttypes.h>
#else
#include <stdint.h>
#endif
#endif

#include <sys/types.h> /* This will likely define BYTE_ORDER */

#ifndef BYTE_ORDER
#if (BSD >= 199103)
# include <machine/endian.h>
#else
#if defined(linux) || defined(__linux__)
# include <endian.h>
#else
#define LITTLE_ENDIAN   1234    /* least-significant byte first (vax, pc) */
#define BIG_ENDIAN  4321    /* most-significant byte first (IBM, net) */
#define PDP_ENDIAN  3412    /* LSB first in word, MSW first in long (pdp)*/

#if defined(__i386__) || defined(__x86_64__) || defined(__amd64__) || \
   defined(vax) || defined(ns32000) || defined(sun386) || \
   defined(MIPSEL) || defined(_MIPSEL) || defined(BIT_ZERO_ON_RIGHT) || \
   defined(__alpha__) || defined(__alpha)
#define BYTE_ORDER    LITTLE_ENDIAN
#endif

#if defined(sel) || defined(pyr) || defined(mc68000) || defined(sparc) || \
    defined(is68k) || defined(tahoe) || defined(ibm032) || defined(ibm370) || \
    defined(MIPSEB) || defined(_MIPSEB) || defined(_IBMR2) || defined(DGUX) ||\
    defined(apollo) || defined(__convex__) || defined(_CRAY) || \
    defined(__hppa) || defined(__hp9000) || \
    defined(__hp9000s300) || defined(__hp9000s700) || \
    defined (BIT_ZERO_ON_LEFT) || defined(m68k) || defined(__sparc)
#define BYTE_ORDER  BIG_ENDIAN
#endif
#endif /* linux */
#endif /* BSD */
#endif /* BYTE_ORDER */

#ifndef BYTE_ORDER
#ifdef __BYTE_ORDER
#if defined(__LITTLE_ENDIAN) && defined(__BIG_ENDIAN)
#ifndef LITTLE_ENDIAN
#define LITTLE_ENDIAN __LITTLE_ENDIAN
#endif
#ifndef BIG_ENDIAN
#define BIG_ENDIAN __BIG_ENDIAN
#endif
#if (__BYTE_ORDER == __LITTLE_ENDIAN)
#define BYTE_ORDER LITTLE_ENDIAN
#else
#define BYTE_ORDER BIG_ENDIAN
#endif
#endif
#endif
#endif

#define MX (((z >> 5) ^ (y << 2)) + ((y >> 3) ^ (z << 4))) ^ ((sum ^ y) + (key[(p & 3) ^ e] ^ z))
#define DELTA 0x9e3779b9

#define FIXED_KEY \
    size_t i;\
    uint8_t fixed_key[16];\
    memcpy(fixed_key, key, 16);\
    for (i = 0; (i < 16) && (fixed_key[i] != 0); ++i);\
    for (++i; i < 16; ++i) fixed_key[i] = 0;\


static uint32_t * xxtea_to_uint_array(const uint8_t * data, size_t len, int inc_len, size_t * out_len) {
	uint32_t* out;
#if !(defined(BYTE_ORDER) && (BYTE_ORDER == LITTLE_ENDIAN))
	size_t i;
#endif
	size_t n;

	n = (((len & 3) == 0) ? (len >> 2) : ((len >> 2) + 1));

	if (inc_len) {
		out = (uint32_t*)calloc(n + 1, sizeof(uint32_t));
		if (!out) return NULL;
		out[n] = (uint32_t)len;
		*out_len = n + 1;
	}
	else {
		out = (uint32_t*)calloc(n, sizeof(uint32_t));
		if (!out) return NULL;
		*out_len = n;
	}
#if defined(BYTE_ORDER) && (BYTE_ORDER == LITTLE_ENDIAN)
	memcpy(out, data, len);
#else
	for (i = 0; i < len; ++i) {
		out[i >> 2] |= (uint32_t)data[i] << ((i & 3) << 3);
	}
#endif

	return out;
}

static uint8_t* xxtea_to_ubyte_array(const uint32_t * data, size_t len, int inc_len, size_t * out_len) {
	uint8_t* out;
#if !(defined(BYTE_ORDER) && (BYTE_ORDER == LITTLE_ENDIAN))
	size_t i;
#endif
	size_t m, n;

	n = len << 2;

	if (inc_len) {
		m = data[len - 1];
		n -= 4;
		if ((m < n - 3) || (m > n)) return NULL;
		n = m;
	}

	out = (uint8_t*)malloc(n + 1);

#if defined(BYTE_ORDER) && (BYTE_ORDER == LITTLE_ENDIAN)
	memcpy(out, data, n);
#else
	for (i = 0; i < n; ++i) {
		out[i] = (uint8_t)(data[i >> 2] >> ((i & 3) << 3));
	}
#endif

	out[n] = '\0';
	*out_len = n;

	return out;
}

static uint32_t* xxtea_uint_encrypt(uint32_t * data, size_t len, uint32_t * key) {
	uint32_t n = (uint32_t)len - 1;
	uint32_t z = data[n], y, p, q = 6 + 52 / (n + 1), sum = 0, e;

	if (n < 1) return data;

	while (0 < q--) {
		sum += DELTA;
		e = sum >> 2 & 3;

		for (p = 0; p < n; p++) {
			y = data[p + 1];
			z = data[p] += MX;
		}

		y = data[0];
		z = data[n] += MX;
	}

	return data;
}

static uint32_t* xxtea_uint_decrypt(uint32_t * data, size_t len, uint32_t * key) {
	uint32_t n = (uint32_t)len - 1;
	uint32_t z, y = data[0], p, q = 6 + 52 / (n + 1), sum = q * DELTA, e;

	if (n < 1) return data;

	while (sum != 0) {
		e = sum >> 2 & 3;

		for (p = n; p > 0; p--) {
			z = data[p - 1];
			y = data[p] -= MX;
		}

		z = data[n];
		y = data[0] -= MX;
		sum -= DELTA;
	}

	return data;
}

static uint8_t* xxtea_ubyte_encrypt(const uint8_t * data, size_t len, const uint8_t * key, size_t * out_len) {
	uint8_t* out;
	uint32_t* data_array, * key_array;
	size_t data_len, key_len;

	if (!len) return NULL;

	data_array = xxtea_to_uint_array(data, len, 1, &data_len);
	if (!data_array) return NULL;

	key_array = xxtea_to_uint_array(key, 16, 0, &key_len);
	if (!key_array) {
		free(data_array);
		return NULL;
	}

	out = xxtea_to_ubyte_array(xxtea_uint_encrypt(data_array, data_len, key_array), data_len, 0, out_len);

	free(data_array);
	free(key_array);

	return out;
}

static uint8_t* xxtea_ubyte_decrypt(const uint8_t * data, size_t len, const uint8_t * key, size_t * out_len) {
	uint8_t* out;
	uint32_t* data_array, * key_array;
	size_t data_len, key_len;

	if (!len) return NULL;

	data_array = xxtea_to_uint_array(data, len, 0, &data_len);
	if (!data_array) return NULL;

	key_array = xxtea_to_uint_array(key, 16, 0, &key_len);
	if (!key_array) {
		free(data_array);
		return NULL;
	}

	out = xxtea_to_ubyte_array(xxtea_uint_decrypt(data_array, data_len, key_array), data_len, 1, out_len);

	free(data_array);
	free(key_array);

	return out;
}

// public functions

void* xxtea_encrypt(const void* data, size_t len, const void* key, size_t * out_len) {
	FIXED_KEY
		return xxtea_ubyte_encrypt((const uint8_t*)data, len, fixed_key, out_len);
}

void* xxtea_decrypt(const void* data, size_t len, const void* key, size_t * out_len) {
	FIXED_KEY
		return xxtea_ubyte_decrypt((const uint8_t*)data, len, fixed_key, out_len);
}