/*
	PIP - Platform Independent Primitives
	Digest algorithms
	Ivan Pelipenko peri4ko@yandex.ru

	This program is free software: you can redistribute it and/or modify
	it under the terms of the GNU Lesser General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU Lesser General Public License for more details.

	You should have received a copy of the GNU Lesser General Public License
	along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "pidigest_md4_p.h"

template<typename T>
inline T rotate_u(T v, int bits) {
	return (v << bits) | (v >> ((sizeof(T) * 8) - bits));
}

#define F(X, Y, Z) (((X) & (Y)) | ((~(X)) & (Z)))
#define G(X, Y, Z) (((X) & (Y)) | ((X) & (Z)) | ((Y) & (Z)))
#define H(X, Y, Z) ((X) ^ (Y) ^ (Z))

#define MD4ROUND1(a, b, c, d, x, s) \
	a += F(b, c, d) + x;            \
	a = rotate_u(a, s);
#define MD4ROUND2(a, b, c, d, x, s)    \
	a += G(b, c, d) + x + 0x5A827999u; \
	a = rotate_u(a, s);
#define MD4ROUND3(a, b, c, d, x, s)    \
	a += H(b, c, d) + x + 0x6ED9EBA1u; \
	a = rotate_u(a, s);


PIByteArray MD4::md4(const PIByteArray & in) {
	constexpr int part_size = 64;

	uint32_t a              = piChangedEndian(0x01234567u);
	uint32_t b              = piChangedEndian(0x89ABCDEFu);
	uint32_t c              = piChangedEndian(0xFEDCBA98u);
	uint32_t d              = piChangedEndian(0x76543210u);

	uint64_t l              = in.size() * 8;
	PIByteArray msg         = in;
	int add_l               = (part_size - 8) - (msg.size() % (part_size));
	if (add_l <= 0) add_l += part_size;
	msg.append(0x80);
	for (int i = 1; i < add_l; ++i)
		msg.append(0x0);
	msg << l;
	int parts = msg.size_s() / part_size;
	// piCout << l << "add" << add_l << "=" << (in.size() + add_l + sizeof(l)) << msg.size() << parts;
	for (int p = 0; p < parts; ++p) {
		uint32_t * mw = (uint32_t *)msg.data(p * part_size);
		// piCout << PIByteArray(w, 64);
		uint32_t a0   = a;
		uint32_t b0   = b;
		uint32_t c0   = c;
		uint32_t d0   = d;

		MD4ROUND1(a, b, c, d, mw[0], 3);
		MD4ROUND1(d, a, b, c, mw[1], 7);
		MD4ROUND1(c, d, a, b, mw[2], 11);
		MD4ROUND1(b, c, d, a, mw[3], 19);
		MD4ROUND1(a, b, c, d, mw[4], 3);
		MD4ROUND1(d, a, b, c, mw[5], 7);
		MD4ROUND1(c, d, a, b, mw[6], 11);
		MD4ROUND1(b, c, d, a, mw[7], 19);
		MD4ROUND1(a, b, c, d, mw[8], 3);
		MD4ROUND1(d, a, b, c, mw[9], 7);
		MD4ROUND1(c, d, a, b, mw[10], 11);
		MD4ROUND1(b, c, d, a, mw[11], 19);
		MD4ROUND1(a, b, c, d, mw[12], 3);
		MD4ROUND1(d, a, b, c, mw[13], 7);
		MD4ROUND1(c, d, a, b, mw[14], 11);
		MD4ROUND1(b, c, d, a, mw[15], 19);

		MD4ROUND2(a, b, c, d, mw[0], 3);
		MD4ROUND2(d, a, b, c, mw[4], 5);
		MD4ROUND2(c, d, a, b, mw[8], 9);
		MD4ROUND2(b, c, d, a, mw[12], 13);
		MD4ROUND2(a, b, c, d, mw[1], 3);
		MD4ROUND2(d, a, b, c, mw[5], 5);
		MD4ROUND2(c, d, a, b, mw[9], 9);
		MD4ROUND2(b, c, d, a, mw[13], 13);
		MD4ROUND2(a, b, c, d, mw[2], 3);
		MD4ROUND2(d, a, b, c, mw[6], 5);
		MD4ROUND2(c, d, a, b, mw[10], 9);
		MD4ROUND2(b, c, d, a, mw[14], 13);
		MD4ROUND2(a, b, c, d, mw[3], 3);
		MD4ROUND2(d, a, b, c, mw[7], 5);
		MD4ROUND2(c, d, a, b, mw[11], 9);
		MD4ROUND2(b, c, d, a, mw[15], 13);

		MD4ROUND3(a, b, c, d, mw[0], 3);
		MD4ROUND3(d, a, b, c, mw[8], 9);
		MD4ROUND3(c, d, a, b, mw[4], 11);
		MD4ROUND3(b, c, d, a, mw[12], 15);
		MD4ROUND3(a, b, c, d, mw[2], 3);
		MD4ROUND3(d, a, b, c, mw[10], 9);
		MD4ROUND3(c, d, a, b, mw[6], 11);
		MD4ROUND3(b, c, d, a, mw[14], 15);
		MD4ROUND3(a, b, c, d, mw[1], 3);
		MD4ROUND3(d, a, b, c, mw[9], 9);
		MD4ROUND3(c, d, a, b, mw[5], 11);
		MD4ROUND3(b, c, d, a, mw[13], 15);
		MD4ROUND3(a, b, c, d, mw[3], 3);
		MD4ROUND3(d, a, b, c, mw[11], 9);
		MD4ROUND3(c, d, a, b, mw[7], 11);
		MD4ROUND3(b, c, d, a, mw[15], 15);

		a += a0;
		b += b0;
		c += c0;
		d += d0;
	}
	PIByteArray ret;
	ret << a << b << c << d;
	return ret;
}
