/*----------------------------------------------------------------------------
;
; MD5 hash generator -- Paul Houle (paulhoule.com) 4/29/2010
;
; Non-time critical C logic.  All API entry points are here.
; See phmd5.h for documentation.
;
;---------------------------------------------------------------------------*/

#include <string.h>
#include "phmd5.h"

// Prototype external core routine (95%+ of the time is spent there).
extern void Phmd5DoBlocks(unsigned char *hash, char *pdata, unsigned dbytes);
extern void Phmd5DoBlocksZero(unsigned char *hash, unsigned dbytes);

// First call -- initialize pmd5 structure for use.
void Phmd5Begin(PHMD5 *pmd5) {
	unsigned __int32 *uhash = (unsigned __int32 *) pmd5->hash;

	uhash[0] = 0x67452301;				// init hash per rfc1321
	uhash[1] = 0xEFCDAB89;
	uhash[2] = 0x98BADCFE;
	uhash[3] = 0x10325476;

	pmd5->totbyt = 0;					// init count of data bytes processed
}

// Last call -- after this, pmd5->hash holds final MD5 hash.
void Phmd5End(PHMD5 *pmd5) {
	char pad[72];						// pad buffer (worst case is 72 bytes)
	unsigned padc;						// size of needed pad (9-72 bytes)

	padc = 64 - ((unsigned) pmd5->totbyt & 63); // pad to 64-byte boundary
	if (padc < 9) padc += 64;			// add a block if we need more room
	memset(pad, 0, padc);				// clear entire pad area
	pad[0] = 0x80;						// place input stream terminator
										// place 64-bit input data bit count
	*(unsigned __int64 *) &pad[padc - 8] = pmd5->totbyt << 3;
	Phmd5Process(pmd5, pad, padc);		// process the pad
}

// Work done here -- call for as many input blocks that need to be processed.
// pdata points to the input data, bytecnt is pdata size (0..n bytes).
// See phmd5.h regarding how to use this optimally.
void Phmd5Process(PHMD5 *pmd5, char *pdata, unsigned bytecnt) {
	unsigned resid = (unsigned) pmd5->totbyt;

	pmd5->totbyt += bytecnt;			// update total bytes processed

	resid &= 63;						// count of bytes now in pmd5->buf

	// This block handles the case of residual data in pmd5->buf.
	// After this block pmd5->buf is empty (except perhaps on exit).

	if (resid) {						// if residual exists,
		unsigned cb = 64 - resid;
		if (cb > bytecnt) cb = bytecnt;
		memcpy(pmd5->buf + resid, pdata, cb);
		pdata += cb;
		bytecnt -= cb;
		if (resid + cb < 64) return;
		Phmd5DoBlocks(pmd5->hash, pmd5->buf, 64);
	}

	// This block processes input data in-place, if the data is dword
	// aligned and in 64-byte chunks.

	if (bytecnt & ~63 && ((unsigned) pdata & 3) == 0) {
		Phmd5DoBlocks(pmd5->hash, pdata, bytecnt & ~63);
		pdata += bytecnt & ~63;
		bytecnt &= 63;
	}

	while (bytecnt) {					// handle residual/non-aligned data
		unsigned cb = 64 > bytecnt ? bytecnt : 64;
		memcpy(pmd5->buf, pdata, cb);
		pdata += cb;
		bytecnt -= cb;
		if (cb < 64) return;
		Phmd5DoBlocks(pmd5->hash, pmd5->buf, 64);
	};
}

// Added by Yutaka Sawada for PAR2's padding null bytes at the last of each file.
void Phmd5ProcessZero(PHMD5 *pmd5, unsigned bytecnt) {
	unsigned resid = (unsigned) pmd5->totbyt;

	pmd5->totbyt += bytecnt;			// update total bytes processed

	resid &= 63;						// count of bytes now in pmd5->buf

	// This block handles the case of residual data in pmd5->buf.
	// After this block pmd5->buf is empty (except perhaps on exit).

	if (resid) {						// if residual exists,
		unsigned cb = 64 - resid;
		if (cb > bytecnt) cb = bytecnt;
		memset(pmd5->buf + resid, 0, cb);
		bytecnt -= cb;
		if (resid + cb < 64) return;
		Phmd5DoBlocks(pmd5->hash, pmd5->buf, 64);
	}

	// This block processes input data in-place, if the data is dword
	// aligned and in 64-byte chunks.

	if (bytecnt & ~63) {
		Phmd5DoBlocksZero(pmd5->hash, bytecnt & ~63);
		bytecnt &= 63;
	}

	while (bytecnt) {					// handle residual/non-aligned data
		unsigned cb = 64 > bytecnt ? bytecnt : 64;
		memset(pmd5->buf, 0, cb);
		bytecnt -= cb;
		if (cb < 64) return;
		Phmd5DoBlocks(pmd5->hash, pmd5->buf, 64);
	};
}
