/*
   Copyright 2005-2010 Jakub Kruszona-Zawadzki, Gemius SA
   Copyright 2013-2014 EditShare
   Copyright 2013-2015 Skytechnology sp. z o.o.
   Copyright 2023      Leil Storage OÜ


   SaunaFS is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, version 3.

   SaunaFS 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 General Public License for more details.

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

#include "common/platform.h"
#include "common/md5.h"

#include <inttypes.h>
#include <string.h>

#define S11 7
#define S12 12
#define S13 17
#define S14 22
#define S21 5
#define S22 9
#define S23 14
#define S24 20
#define S31 4
#define S32 11
#define S33 16
#define S34 23
#define S41 6
#define S42 10
#define S43 15
#define S44 21

static const uint8_t padding[64] = {
	0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};

#define F(x, y, z) (((x) & (y)) | ((~x) & (z)))
#define G(x, y, z) (((x) & (z)) | ((y) & (~z)))
#define H(x, y, z) ((x) ^ (y) ^ (z))
#define I(x, y, z) ((y) ^ ((x) | (~z)))

#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32-(n))))

#define FF(a, b, c, d, x, s, ac) { \
 (a) += F ((b), (c), (d)) + (x) + (uint32_t)(ac); \
 (a) = ROTATE_LEFT ((a), (s)); \
 (a) += (b); \
  }
#define GG(a, b, c, d, x, s, ac) { \
 (a) += G ((b), (c), (d)) + (x) + (uint32_t)(ac); \
 (a) = ROTATE_LEFT ((a), (s)); \
 (a) += (b); \
  }
#define HH(a, b, c, d, x, s, ac) { \
 (a) += H ((b), (c), (d)) + (x) + (uint32_t)(ac); \
 (a) = ROTATE_LEFT ((a), (s)); \
 (a) += (b); \
  }
#define II(a, b, c, d, x, s, ac) { \
 (a) += I ((b), (c), (d)) + (x) + (uint32_t)(ac); \
 (a) = ROTATE_LEFT ((a), (s)); \
 (a) += (b); \
  }

static void md5_encode (uint8_t *output,const uint32_t *input,uint32_t len) {
	uint32_t i,j;

	for (i=0,j=0 ; i<len ; i++,j+=4) {
		output[j] = (uint8_t)(input[i]&0xff);
		output[j+1] = (uint8_t)((input[i]>>8)&0xff);
		output[j+2] = (uint8_t)((input[i]>>16)&0xff);
		output[j+3] = (uint8_t)((input[i]>>24)&0xff);
	}
}

static void md5_decode (uint32_t *output,const uint8_t *input,uint32_t len) {
	uint32_t i,j;

	for (i=0,j=0 ; i<len ; i++,j+=4) {
		output[i] = ((uint32_t)input[j]) | (((uint32_t)input[j+1])<<8) | (((uint32_t)input[j+2])<<16) | (((uint32_t)input[j+3])<<24);
	}
}

static void md5_transform (uint32_t state[4],const uint8_t block[64]) {
	uint32_t a=state[0];
	uint32_t b=state[1];
	uint32_t c=state[2];
	uint32_t d=state[3];
	uint32_t x[kDefaultMd5DigestSize];

	md5_decode(x,block,kDefaultMd5DigestSize);

	FF (a, b, c, d, x[ 0], S11, 0xd76aa478); /* 1 */
	FF (d, a, b, c, x[ 1], S12, 0xe8c7b756); /* 2 */
	FF (c, d, a, b, x[ 2], S13, 0x242070db); /* 3 */
	FF (b, c, d, a, x[ 3], S14, 0xc1bdceee); /* 4 */
	FF (a, b, c, d, x[ 4], S11, 0xf57c0faf); /* 5 */
	FF (d, a, b, c, x[ 5], S12, 0x4787c62a); /* 6 */
	FF (c, d, a, b, x[ 6], S13, 0xa8304613); /* 7 */
	FF (b, c, d, a, x[ 7], S14, 0xfd469501); /* 8 */
	FF (a, b, c, d, x[ 8], S11, 0x698098d8); /* 9 */
	FF (d, a, b, c, x[ 9], S12, 0x8b44f7af); /* 10 */
	FF (c, d, a, b, x[10], S13, 0xffff5bb1); /* 11 */
	FF (b, c, d, a, x[11], S14, 0x895cd7be); /* 12 */
	FF (a, b, c, d, x[12], S11, 0x6b901122); /* 13 */
	FF (d, a, b, c, x[13], S12, 0xfd987193); /* 14 */
	FF (c, d, a, b, x[14], S13, 0xa679438e); /* 15 */
	FF (b, c, d, a, x[15], S14, 0x49b40821); /* 16 */

	GG (a, b, c, d, x[ 1], S21, 0xf61e2562); /* 17 */
	GG (d, a, b, c, x[ 6], S22, 0xc040b340); /* 18 */
	GG (c, d, a, b, x[11], S23, 0x265e5a51); /* 19 */
	GG (b, c, d, a, x[ 0], S24, 0xe9b6c7aa); /* 20 */
	GG (a, b, c, d, x[ 5], S21, 0xd62f105d); /* 21 */
	GG (d, a, b, c, x[10], S22,  0x2441453); /* 22 */
	GG (c, d, a, b, x[15], S23, 0xd8a1e681); /* 23 */
	GG (b, c, d, a, x[ 4], S24, 0xe7d3fbc8); /* 24 */
	GG (a, b, c, d, x[ 9], S21, 0x21e1cde6); /* 25 */
	GG (d, a, b, c, x[14], S22, 0xc33707d6); /* 26 */
	GG (c, d, a, b, x[ 3], S23, 0xf4d50d87); /* 27 */
	GG (b, c, d, a, x[ 8], S24, 0x455a14ed); /* 28 */
	GG (a, b, c, d, x[13], S21, 0xa9e3e905); /* 29 */
	GG (d, a, b, c, x[ 2], S22, 0xfcefa3f8); /* 30 */
	GG (c, d, a, b, x[ 7], S23, 0x676f02d9); /* 31 */
	GG (b, c, d, a, x[12], S24, 0x8d2a4c8a); /* 32 */

	HH (a, b, c, d, x[ 5], S31, 0xfffa3942); /* 33 */
	HH (d, a, b, c, x[ 8], S32, 0x8771f681); /* 34 */
	HH (c, d, a, b, x[11], S33, 0x6d9d6122); /* 35 */
	HH (b, c, d, a, x[14], S34, 0xfde5380c); /* 36 */
	HH (a, b, c, d, x[ 1], S31, 0xa4beea44); /* 37 */
	HH (d, a, b, c, x[ 4], S32, 0x4bdecfa9); /* 38 */
	HH (c, d, a, b, x[ 7], S33, 0xf6bb4b60); /* 39 */
	HH (b, c, d, a, x[10], S34, 0xbebfbc70); /* 40 */
	HH (a, b, c, d, x[13], S31, 0x289b7ec6); /* 41 */
	HH (d, a, b, c, x[ 0], S32, 0xeaa127fa); /* 42 */
	HH (c, d, a, b, x[ 3], S33, 0xd4ef3085); /* 43 */
	HH (b, c, d, a, x[ 6], S34,  0x4881d05); /* 44 */
	HH (a, b, c, d, x[ 9], S31, 0xd9d4d039); /* 45 */
	HH (d, a, b, c, x[12], S32, 0xe6db99e5); /* 46 */
	HH (c, d, a, b, x[15], S33, 0x1fa27cf8); /* 47 */
	HH (b, c, d, a, x[ 2], S34, 0xc4ac5665); /* 48 */

	II (a, b, c, d, x[ 0], S41, 0xf4292244); /* 49 */
	II (d, a, b, c, x[ 7], S42, 0x432aff97); /* 50 */
	II (c, d, a, b, x[14], S43, 0xab9423a7); /* 51 */
	II (b, c, d, a, x[ 5], S44, 0xfc93a039); /* 52 */
	II (a, b, c, d, x[12], S41, 0x655b59c3); /* 53 */
	II (d, a, b, c, x[ 3], S42, 0x8f0ccc92); /* 54 */
	II (c, d, a, b, x[10], S43, 0xffeff47d); /* 55 */
	II (b, c, d, a, x[ 1], S44, 0x85845dd1); /* 56 */
	II (a, b, c, d, x[ 8], S41, 0x6fa87e4f); /* 57 */
	II (d, a, b, c, x[15], S42, 0xfe2ce6e0); /* 58 */
	II (c, d, a, b, x[ 6], S43, 0xa3014314); /* 59 */
	II (b, c, d, a, x[13], S44, 0x4e0811a1); /* 60 */
	II (a, b, c, d, x[ 4], S41, 0xf7537e82); /* 61 */
	II (d, a, b, c, x[11], S42, 0xbd3af235); /* 62 */
	II (c, d, a, b, x[ 2], S43, 0x2ad7d2bb); /* 63 */
	II (b, c, d, a, x[ 9], S44, 0xeb86d391); /* 64 */

	state[0] += a;
	state[1] += b;
	state[2] += c;
	state[3] += d;

	memset((char*)x,0,sizeof(x));
}



void md5_init (md5ctx *ctx) {
	ctx->count[0] = ctx->count[1] = 0;
	ctx->state[0] = 0x67452301U;
	ctx->state[1] = 0xefcdab89U;
	ctx->state[2] = 0x98badcfeU;
	ctx->state[3] = 0x10325476U;
}

void md5_update (md5ctx *ctx,const uint8_t *buff,uint32_t leng) {
	uint32_t i,indx,partleng;

	indx = (uint32_t)((ctx->count[0]>>3)&0x3F);

	if ((ctx->count[0]+=((uint32_t)leng<<3)) < ((uint32_t)leng<<3)) {
		ctx->count[1]++;
	}
	ctx->count[1] += ((uint32_t)leng>>29);

	partleng = 64-indx;

	if (leng>=partleng) {
		memcpy((char*)(ctx->buffer+indx),(const char*)buff,partleng);
		md5_transform(ctx->state,ctx->buffer);

		for (i=partleng ; i+63<leng ; i+=64) {
			md5_transform(ctx->state,buff+i);
		}
		indx = 0;
	} else {
		i = 0;
	}

	memcpy((char*)(ctx->buffer+indx),(const char*)(buff+i),leng-i);
}

void md5_final(uint8_t digest[kDefaultMd5DigestSize], md5ctx *ctx) {
	uint8_t bits[8];
	uint32_t indx,padleng;

	md5_encode(bits,ctx->count,2);
	indx = (uint32_t)((ctx->count[0]>>3)&0x3f);
	padleng = (indx<56)?(56-indx):(120-indx);
	md5_update (ctx,padding,padleng);
	md5_update(ctx,bits,8);
	md5_encode(digest,ctx->state,4);
	memset((char*)ctx,0,sizeof(md5ctx));
}

std::array<uint8_t, kDefaultMd5DigestSize> md5_challenge_response(
    const std::array<uint8_t, 32> &challenge, std::string data) {
	md5ctx ctx;
	md5_init(&ctx);
	md5_update(&ctx, challenge.data(), kDefaultMd5DigestSize);
	md5_update(&ctx, (uint8_t*)(data.c_str()), data.size());
	md5_update(&ctx, challenge.data() + kDefaultMd5DigestSize, kDefaultMd5DigestSize);
	std::array<uint8_t, kDefaultMd5DigestSize> digest;
	md5_final(digest.data(), &ctx);
	return digest;
}

int md5_parse(std::vector<uint8_t> &password_digest, const char *in_md5_data) {
	password_digest.resize(kDefaultMd5DigestSize);
	const uint8_t *p = (const uint8_t *)in_md5_data;
	for (size_t i = 0; i < kDefaultMd5DigestSize; ++i) {
		if (*p >= '0' && *p <= '9') {
			password_digest[i] = (*p - '0') << 4;
		} else if (*p >= 'a' && *p <= 'f') {
			password_digest[i] = (*p - 'a' + 10) << 4;
		} else if (*p >= 'A' && *p <= 'F') {
			password_digest[i] = (*p - 'A' + 10) << 4;
		} else {
			return -1;
		}
		p++;
		if (*p >= '0' && *p <= '9') {
			password_digest[i] += (*p - '0');
		} else if (*p >= 'a' && *p <= 'f') {
			password_digest[i] += (*p - 'a' + 10);
		} else if (* p>= 'A' && *p <= 'F') {
			password_digest[i] += (*p - 'A' + 10);
		} else {
			return -1;
		}
		p++;
	}
	if (*p) {
		return -1;
	}
	return 0;
}
