
 /* This program is free software. It comes without any warranty, to
 * the extent permitted by applicable law. You can redistribute it
 * and/or modify it under the terms of the Do What The Fuck You Want
 * To Public License, Version 2, as published by Sam Hocevar. See
 * http://sam.zoy.org/wtfpl/COPYING for more details. */

// Copyright 2008 Mathias Gottschlag
// For an explanation of this code, look at http://www.faqs.org/rfcs/rfc1321.html
// Sorry for the magic numbers.

#include "md5.h"
#include "logger.h"

#include <string.h>
#include <stdio.h>

unsigned int T[64] = {
0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee,
0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501,
0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be,
0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821,

0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa,
0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8,
0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed,
0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a,

0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c,
0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70,
0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x4881d05,
0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665,

0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039,
0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1,
0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1,
0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391
};

static int leftrotate(unsigned int x, unsigned int c)
{
	return (x << c) | (x >> (32 - c));
}

md5_tmp_t md5_initialize(void)
{
	md5_tmp_t md5;
	md5.md5.h[0] = 0x67452301;
	md5.md5.h[1] = 0xEFCDAB89;
	md5.md5.h[2] = 0x98BADCFE;
	md5.md5.h[3] = 0x10325476;
	md5.size = 0;
	return md5;
}
// Helper functions
static inline int F(int x, int y, int z)
{
	return (x & y) | (~x & z);
}
static inline int G(int x, int y, int z)
{
	return (x & z) | (y & ~z);
}
static inline int H(int x, int y, int z)
{
	return x ^ y ^ z;
}
static inline int I(int x, int y, int z)
{
	return y ^ (x | (~z));
}
void md5_process_block(md5_tmp_t* md5)
{
	unsigned int *data = (unsigned int*)md5->block;
	unsigned int a = md5->md5.h[0];
	unsigned int b = md5->md5.h[1];
	unsigned int c = md5->md5.h[2];
	unsigned int d = md5->md5.h[3];

	#define ROUND(A, B, C, D, k, s, i) A = B + leftrotate(A + F(B,C,D) + data[k] + T[i], s);
	ROUND(a, b, c, d, 0, 7, 0);
	ROUND(d, a, b, c, 1, 12, 1);
	ROUND(c, d, a, b, 2, 17, 2);
	ROUND(b, c, d, a, 3, 22, 3);
	ROUND(a, b, c, d, 4, 7, 4);
	ROUND(d, a, b, c, 5, 12, 5);
	ROUND(c, d, a, b, 6, 17, 6);
	ROUND(b, c, d, a, 7, 22, 7);
	ROUND(a, b, c, d, 8, 7, 8);
	ROUND(d, a, b, c, 9, 12, 9);
	ROUND(c, d, a, b, 10, 17, 10);
	ROUND(b, c, d, a, 11, 22, 11);
	ROUND(a, b, c, d, 12, 7, 12);
	ROUND(d, a, b, c, 13, 12, 13);
	ROUND(c, d, a, b, 14, 17, 14);
	ROUND(b, c, d, a, 15, 22, 15);

	#undef ROUND
	#define ROUND(A, B, C, D, k, s, i) A = B + leftrotate(A + G(B,C,D) + data[k] + T[i], s);
	ROUND(a, b, c, d, 1, 5, 16);
	ROUND(d, a, b, c, 6, 9, 17);
	ROUND(c, d, a, b, 11, 14, 18);
	ROUND(b, c, d, a, 0, 20, 19);
	ROUND(a, b, c, d, 5, 5, 20);
	ROUND(d, a, b, c, 10, 9, 21);
	ROUND(c, d, a, b, 15, 14, 22);
	ROUND(b, c, d, a, 4, 20, 23);
	ROUND(a, b, c, d, 9, 5, 24);
	ROUND(d, a, b, c, 14, 9, 25);
	ROUND(c, d, a, b, 3, 14, 26);
	ROUND(b, c, d, a, 8, 20, 27);
	ROUND(a, b, c, d, 13, 5, 28);
	ROUND(d, a, b, c, 2, 9, 29);
	ROUND(c, d, a, b, 7, 14, 30);
	ROUND(b, c, d, a, 12, 20, 31);

	#undef ROUND
	#define ROUND(A, B, C, D, k, s, i) A = B + leftrotate(A + H(B,C,D) + data[k] + T[i], s);
	ROUND(a, b, c, d, 5, 4, 32);
	ROUND(d, a, b, c, 8, 11, 33);
	ROUND(c, d, a, b, 11, 16, 34);
	ROUND(b, c, d, a, 14, 23, 35);
	ROUND(a, b, c, d, 1, 4, 36);
	ROUND(d, a, b, c, 4, 11, 37);
	ROUND(c, d, a, b, 7, 16, 38);
	ROUND(b, c, d, a, 10, 23, 39);
	ROUND(a, b, c, d, 13, 4, 40);
	ROUND(d, a, b, c, 0, 11, 41);
	ROUND(c, d, a, b, 3, 16, 42);
	ROUND(b, c, d, a, 6, 23, 43);
	ROUND(a, b, c, d, 9, 4, 44);
	ROUND(d, a, b, c, 12, 11, 45);
	ROUND(c, d, a, b, 15, 16, 46);
	ROUND(b, c, d, a, 2, 23, 47);

	#undef ROUND
	#define ROUND(A, B, C, D, k, s, i) A = B + leftrotate(A + I(B,C,D) + data[k] + T[i], s);
	ROUND(a, b, c, d, 0, 6, 48);
	ROUND(d, a, b, c, 7, 10, 49);
	ROUND(c, d, a, b, 14, 15, 50);
	ROUND(b, c, d, a, 5, 21, 51);
	ROUND(a, b, c, d, 12, 6, 52);
	ROUND(d, a, b, c, 3, 10, 53);
	ROUND(c, d, a, b, 10, 15, 54);
	ROUND(b, c, d, a, 1, 21, 55);
	ROUND(a, b, c, d, 8, 6, 56);
	ROUND(d, a, b, c, 15, 10, 57);
	ROUND(c, d, a, b, 6, 15, 58);
	ROUND(b, c, d, a, 13, 21, 59);
	ROUND(a, b, c, d, 4, 6, 60);
	ROUND(d, a, b, c, 11, 10, 61);
	ROUND(c, d, a, b, 2, 15, 62);
	ROUND(b, c, d, a, 9, 21, 63);

	md5->md5.h[0] += a;
	md5->md5.h[1] += b;
	md5->md5.h[2] += c;
	md5->md5.h[3] += d;
}
void md5_process_data(md5_tmp_t* md5, const void* data, int length)
{
	if (((md5->size % 64) + length) <= 64)
	{
		// No md5_process_block needed
		memcpy(md5->block + (md5->size % 64), data, length);
		md5->size += length;
		return;
	}
	int written = 0;
	// First block
	if (md5->size % 64)
	{
		memcpy(md5->block + (md5->size % 64), data, 64 - (md5->size % 64));
		written += 64 - (md5->size % 64);
		md5->size += 64 - (md5->size % 64);
		md5_process_block(md5);
	}
	// Other blocks
	while ((length - written) >= 64)
	{
		memcpy(md5->block, (char*)data + written, 64);
		written += 64;
		md5->size += 64;
		md5_process_block(md5);
	}
	// Last part
	memcpy(md5->block, (char*)data + written, length - written);
	md5->size += length - written;
}
md5_t md5_finish(md5_tmp_t* md5)
{
	// Add padding and create sum of remaining data
	unsigned char offset = md5->size % 64;
	if (offset > 55)
	{
		md5->block[offset] = 0x80;
		memset(md5->block + offset + 1, 0, 64 - (offset + 1));
		md5_process_block(md5);
		memset(md5->block, 0, 56);
		*((unsigned long long*)&md5->block[56]) = (unsigned long long)md5->size * 8;
		md5_process_block(md5);
	}
	else
	{
		md5->block[offset] = 0x80;
		memset(md5->block + offset + 1, 0, 64 - (offset + 1));
		*((unsigned long long*)&md5->block[56]) = (unsigned long long)md5->size * 8;
		md5_process_block(md5);
	}
	return md5->md5;
}

md5_t md5_process(const void* data, unsigned int length)
{
	md5_tmp_t md5 = md5_initialize();
	md5_process_data(&md5, data, length);
	return md5_finish(&md5);
}

void md5_to_string(md5_t md5, char* s)
{
	sprintf(s, "%08x%08x%08x%08x", ntohl(md5.h[0]), ntohl(md5.h[1]), ntohl(md5.h[2]), ntohl(md5.h[3]));
}

int md5_process_file(md5_t *md5, const char* filename)
{
	FILE* file = fopen(filename, "rb");
	if (!file)
	{
		return -1;
	}
	
	md5_tmp_t filemd5 = md5_initialize();
	char buffer[1024];
	// Create checksum
	while (!feof(file))
	{
		int blocksize = fread(buffer, 1, 1024, file);
		if (blocksize == -1)
		{
			fclose(file);
			return -1;
		}
		if (blocksize == 0) break;
		md5_process_data(&filemd5, buffer, blocksize);
	}
	*md5 = md5_finish(&filemd5);
	fclose(file);
	return 0;
}

