#include "StdAfx.h"

#ifndef _MFC_VER
#include <QCoreApplication>
#include <QDir>
#endif

#include "StepData.h"
#include "gost_3411_2012_calc.h"

/**@name  Функции расчета хеш-суммы «Стрибог» по ГОСТ 34.11—2012
 * Сделано на основе материалов и исходников статьи https://xakep.ru/2016/07/20/hash-gost-34-11-2012/
 * с убыстрениями почерпнутыми из других источников
 */

#define READ_BUFFER_SIZE 65536
#ifdef __linux__
#define PATH_MAX        4096
#endif


CGOSTHashContext::CGOSTHashContext(int _hash_size)
{
	GOSTHashInit(_hash_size);
}

void CGOSTHashContext::GOSTHashInit(int _hash_size)
{
	unsigned int i;
	memset(this, 0x00, sizeof (CGOSTHashContext));

	hash_size = _hash_size;

	if (hash_size != 256 && hash_size != 512)
		hash_size = DEFAULT_HASH_SIZE;

	for (i = 0; i < 8; i++)
	{
		if (hash_size == 256)
		{
			h.QWORD[i] = 0x0101010101010101ULL;
		}
		else
		{
			h.QWORD[i] = 0x00ULL;
		}
	}
}

void CGOSTHashContext::Cleanup()
{
	memset(this, 0x00, sizeof (CGOSTHashContext));
}

static inline void add512(const union uint512_u *x, const union uint512_u *y, union uint512_u *r)
{
	unsigned int CF;
	unsigned int i;

	CF = 0;
	for (i = 0; i < 8; i++)
	{
		const unsigned long long left = x->QWORD[i];
		unsigned long long sum;

		sum = left + y->QWORD[i] + CF;
		if (sum != left)
			CF = (sum < left);
		r->QWORD[i] = sum;
	}
}

static void g(union uint512_u *h, const union uint512_u *N, const unsigned char *m)
{

	union uint512_u Ki, data;
	unsigned int i;

	XLPS(h, N, (&data));

	/* Starting E() */
	Ki = data;
	XLPS((&Ki), ((const union uint512_u *) &m[0]), (&data));

	for (i = 0; i < 11; i++)
	{
		XLPS((&Ki), (&C[i]), (&Ki));
		XLPS((&Ki), (&data), (&data));
	}

	XLPS((&Ki), (&C[11]), (&Ki));
	X((&Ki), (&data), (&data));
	/* E() done */

	X((&data), h, (&data));
	X((&data), ((const union uint512_u *) &m[0]), h);

}

inline void CGOSTHashContext::GOSTHashStage_2(const unsigned char *data)
{
	union uint512_u m;

	memcpy(&m, data, sizeof(m));
	g(&h, &N, (const unsigned char *)&m);

	add512(&N, &buffer512, &N);
	add512(&Sigma, &m, &Sigma);
}

inline void CGOSTHashContext::GOSTHashStage_3()
{
	ALIGN(16) union uint512_u buf = {{ 0 }};

	buf.QWORD[0] = bufsize << 3;

	if (bufsize < BLOCK_SIZE)
	{
		memset(buffer + bufsize, 0x00, sizeof(buffer) - bufsize);

		buffer[bufsize] = 0x01;
	}

	g(&h, &N, (const unsigned char *) &buffer);

	add512(&N, &buf, &N);
	add512(&Sigma, (const union uint512_u *) &(buffer[0]), &Sigma);

	g(&h, &buffer0, (const unsigned char *) &N);

	g(&h, &buffer0, (const unsigned char *) &Sigma);
	memcpy(&hash, &h, sizeof(uint512_u));
}

void CGOSTHashContext::GOSTHashUpdate(const unsigned char *data, size_t len)
{
	size_t chunksize;

	if (bufsize)
	{
		chunksize = BLOCK_SIZE - bufsize;
		if (chunksize > len)
			chunksize = len;

		memcpy(&buffer[bufsize], data, chunksize);

		bufsize += chunksize;
		len -= chunksize;
		data += chunksize;

		if (bufsize == BLOCK_SIZE)
		{
			GOSTHashStage_2(buffer);

			bufsize = 0;
		}
	}

	while (len > 63)
	{
		GOSTHashStage_2(data);

		data += BLOCK_SIZE;
		len  -= BLOCK_SIZE;
	}

	if (len)
	{
		memcpy(&buffer, data, len);
		bufsize = len;
	}
}

void CGOSTHashContext::GOSTHashFinal()
{
	GOSTHashStage_3();

	bufsize = 0;
}

CString CGOSTHashContext::HashPrint(bool print2console)
{
	CString str1 = _T(""), str2;

	if(print2console)
		printf("%d bit hash sum: \n", hash_size);

	int i, pos_start;
	if (hash_size == 256)
		pos_start = 32;
	else
		pos_start = 0;

	unsigned char *hash_char = (unsigned char *)&(hash.QWORD[0]);

	for(i = pos_start; i < BLOCK_SIZE; i++)
	{
		str2.Format(_T("%02x"), hash_char[i]);
		str1+=str2;
	}

	if(print2console)
	{
		CaplStringAdapter ad(str1);
		printf((LPCSTR)ad);
		printf("\n");
	}
	return str1;
}

// Важно!!! Выделенную этой функцией память освобождать только функцией memfree_align64 !!!
//Выделение памяти с выравниванием по блоку
// для памяти: #define posix_memalign(p, a, s) (((*(p)) = _aligned_malloc((s), (a))), *(p)?0 :errno)
static void *memalloc_align64(const size_t size)
{
	void *p;

#ifdef __linux__
	if (posix_memalign(&p, (size_t) 64, size))
		p=0;
#else
	p = _aligned_malloc(size, 64);
#endif

	return p;
}

static void memfree_align(void *ptr)
{
#ifdef __linux__
	free(ptr);
#else
	// В windows память, выделенную с выравниваением по блокам, надо освобождать особой функцией
	_aligned_free(ptr);
#endif
}


CString GetHashString_Stribog(const char *string, int size,  int _hash_size)
{
	CString out=_T("");

	CGOSTHashContext CTX(_hash_size);

	ALIGN(16) unsigned char *buf;

	buf = (unsigned char *)memalloc_align64(size);
	memcpy(buf, string, size);

	CTX.GOSTHashUpdate(buf, size);

	CTX.GOSTHashFinal();

	memfree_align(buf);

	out = CTX.HashPrint(true);

	CTX.Cleanup();

	return out;
}

bool GetHashFile_Stribog(LPCTSTR file_name, CString& hash, int _hash_size)
{
	unsigned char *buffer;
	size_t len;
	CFile file;

	hash=_T("");

	CGOSTHashContext CTX(_hash_size);

	if (file.Open(file_name, CFile::modeRead | CFile::typeBinary) == FALSE)
	{
		CString cstr1;
		aplGetDescriptionSystemError(GetLastError(), cstr1);
		hash = _T("File error: ") + cstr1 + _T("\n");

		return false;
	}

	buffer = (unsigned char *)memalloc_align64((size_t) READ_BUFFER_SIZE);

	while((len = file.Read(buffer, (size_t) READ_BUFFER_SIZE)))
		CTX.GOSTHashUpdate( buffer, len);

	memfree_align(buffer);

	CTX.GOSTHashFinal();

	file.Close();

	hash = CTX.HashPrint(false);

	CTX.Cleanup();

	return true;
}



bool AFX_EXT_API CalculateFilesHashes(CStringArray &list_params, CString &out_xml, int hash_size,
									  LPCTSTR _version, LPCTSTR _start_dir, bool checkTime, bool printInfo)
{
	CString cstr1, cstr_param, cstr_name, cstr_relname, cstr_full_name;
	CString version = _version, start_dir = _start_dir;

	CFileFind ff;
	COleDateTime odt = COleDateTime::GetCurrentTime();

	out_xml = _T("<!-- Apl Hash table file -->\n");
	cstr1.Format(_T("<FilesHashTable hash_size=\"%i\" method=\"Stribog (GOST 34.11-2012)\" >\n"), hash_size);
	out_xml += cstr1;
	if(version != _T(""))
		out_xml += _T("\t<BuildVersion>") + version + _T("</BuildVersion>\n");

	if(start_dir == _T(""))
	{
#ifdef _MFC_VER
		TCHAR dir[2048];
		GetCurrentDirectory( 2048, dir);
		start_dir = dir;
#else
		start_dir = QS2CS(QDir::currentPath());
#ifndef __linux__
		// QDir::currentPath() под виндой выдает путь с неправильными линуксовми слешами
		start_dir.Replace(aplMissedDirRazd, aplDirRazd);
#endif
#endif
	}

	start_dir.Replace(aplMissedDirRazd, aplDirRazd);

	if(start_dir.Right(1) != aplDirRazd)
		start_dir+=aplDirRazd;

	if(printInfo)
	{
		cstr1 = _T("Working path: ") + start_dir;
		aplMessagePrint(cstr1);
	}

	CaplStrMap list_calculated_files;
	CStringArray list_founded;

	for(int i=0; i<list_params.GetSize(); i++ )
	{
		CString path_delta = _T("");

		cstr_param = list_params[i];

		if(printInfo)
		{
			cstr1 = _T("Processing param '") + cstr_param + _T("'...");
			aplMessagePrint(cstr1);
		}
		else
			printf(".");

#ifndef _MFC_VER
		QCoreApplication::processEvents();
#endif

		if(cstr_param.Find(aplMissedDirRazd)!=-1)
		{
			cstr_param.Replace(aplMissedDirRazd, aplDirRazd);
			cstr1.Format(_T("Invalid delimiters '%c' were replaced with the correct ones '%c'"), aplMissedDirRazd, aplDirRazd);
			if(!printInfo)printf("\n");
			aplMessagePrint(cstr1, MB_ICONWARNING);
		}

		int dir_razd_first = cstr_param.Find(aplDirRazd);
		if(dir_razd_first!=-1 && dir_razd_first!=0)
		{
			int dir_razd_last = cstr_param.ReverseFind(aplDirRazd);
			path_delta = cstr_param.Left(dir_razd_last+1);
		}

		list_founded.RemoveAll();
		if(ff.FindFile(start_dir + cstr_param))
		{
			while(ff.FindNextFile())
			{
				if(ff.IsDots()) continue;
				if(ff.IsDirectory( )) continue;

				list_founded.Add(ff.GetFilePath());
			}
		}

		if(cstr_param.Right(4) == _T(".exe") || cstr_param.Right(4) == _T(".dll"))
		{
			// костыль для того, чтобы в списке не указывать два комплекта с exe/dll для винды и без расширения/so для астры
			if(ff.FindFile(start_dir + cstr_param.Left(cstr_param.GetLength() - 3)))
			{
				while(ff.FindNextFile())
				{
					if(ff.IsDots()) continue;
					if(ff.IsDirectory( )) continue;

					list_founded.Add(ff.GetFilePath());
				}
			}
			if(ff.FindFile(start_dir + _T("lib") + cstr_param.Left(cstr_param.GetLength() - 4) + _T(".so")))
			{
				while(ff.FindNextFile())
				{
					if(ff.IsDots()) continue;
					if(ff.IsDirectory( )) continue;

					list_founded.Add(ff.GetFilePath());
				}
			}
		}

		if(list_founded.GetSize()==0)
		{
			if(printInfo)
			{
				cstr1 = _T("Files not found for param '") + cstr_param + _T("'!\n");
				aplMessagePrint(cstr1, MB_ICONWARNING);
			}
		}
		else
		{
			if(printInfo)
			{
				cstr1.Format(_T(" Found %i files\n"), list_founded.GetSize());
				CaplStringAdapter ad(cstr1);
				printf((LPCSTR)ad);
			}

			for(int j=0; j<list_founded.GetSize(); j++)
			{
				cstr_full_name = list_founded.GetAt(j);

				cstr_name = cstr_full_name.Mid(cstr_full_name.ReverseFind(aplDirRazd)+1);
				cstr_relname = path_delta + cstr_name;

				if(list_calculated_files.Find(cstr_relname)!=-1)
				{
					if(printInfo)
					{
						cstr1 = _T("File '") + cstr_relname + _T("' has already been processed.");
						aplMessagePrint(cstr1);
					}
					continue;
				}
				list_calculated_files.Add((LPCTSTR)cstr_relname, (INT_PTR)0);


				if(printInfo)
				{
					cstr1 = _T("Processing file '") + cstr_relname + _T("' (full path '") + cstr_full_name + _T("')...");
					aplMessagePrint(cstr1);
				}
				else
					printf(".");

#ifndef _MFC_VER
				QCoreApplication::processEvents();
#endif

				COleDateTime odt1 = COleDateTime::GetCurrentTime();
				if(GetHashFile_Stribog(cstr_full_name, cstr1, hash_size))
				{
					out_xml += _T("\t<FileHashValue>\n");
					out_xml += _T("\t\t<File>") + cstr_relname + _T("</File>\n");
					out_xml += _T("\t\t<Hash>") + cstr1 + _T("</Hash>\n");
					if(checkTime)
					{
						COleDateTimeSpan odts = COleDateTime::GetCurrentTime() - odt1;
						out_xml += _T("\t\t<ProcessedTime>") + odts.Format(_T("%H:%M:%S")) + _T("</ProcessedTime>\n");
					}
					out_xml += _T("\t</FileHashValue>\n");
					if(printInfo)
					{
#ifdef _DEBUG
						aplMessagePrint(cstr1);
						printf("\n");
#endif
						printf(" Ready!\n");
					}
				}
				else
				{
					CString cstr2 = _T(" Calculation error for file ") + cstr_full_name + _T(": ") + cstr1 + _T("\n");
					aplMessagePrint(cstr2, MB_ICONERROR);
					out_xml = cstr2;
					return false;
				}
			}
		}
	}

	COleDateTimeSpan odts = COleDateTime::GetCurrentTime() - odt;
	out_xml += _T("\t<ProcessingInfo>\n\t\t<StartTime>") + odt.Format(_T("%d.%m.%Y %H:%M:%S")) + _T("</StartTime>\n");
	out_xml += _T("\t\t<ProcessedTime>") + odts.Format(_T("%H:%M:%S")) + _T("</ProcessedTime>\n\t</ProcessingInfo>\n");

	out_xml += _T("</FilesHashTable>");

	return true;
}
