
/* ****************************************************************************

 * eID Middleware Project.
 * Copyright (C) 2008-2010 FedICT.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License version
 * 3.0 as published by the Free Software Foundation.
 *
 * This software 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 software; if not, see
 * http://www.gnu.org/licenses/.

**************************************************************************** */
#include <stdlib.h>
#include <string.h>
#include <iostream>
#include <exception>

#include "bytearray.h"
#include "util.h"
#include "mwexception.h"
#include "eiderrors.h"

const static unsigned long EXTRA_INCREMENT_LEN = 10;

using namespace std;

/***************** ByteArray **************************/

namespace eIDMW
{

	CByteArray::CByteArray(unsigned long ulCapacity) :m_pucData(NULL),
		m_ulSize(0), m_ulCapacity(ulCapacity), m_bMallocError(false)
	{
	}

	//copy mem into object
	CByteArray::CByteArray(const unsigned char * pucData, unsigned long ulSize, unsigned long ulCapacity)
	{
		MakeArray(pucData, ulSize, ulCapacity);
	}

	//copy object into new object
	CByteArray::CByteArray(const CByteArray & oByteArray)
	{
		MakeArray(oByteArray.GetBytes(), oByteArray.Size());
	}

	//assign data to object
	//?! put move logic in makeArray
	CByteArray & CByteArray::operator = (const CByteArray &oByteArray)
	{
		if (&oByteArray != this)	//only action needed if both are not the same object
		{
			if (m_pucData == NULL)
				MakeArray(oByteArray.GetBytes(), oByteArray.Size());	//create new array with new data if nothing exist yet
			else if (m_ulCapacity >= oByteArray.Size())
			{
				m_ulSize = oByteArray.Size();							//array large enough; copy new data in existing array 
				memcpy(m_pucData, oByteArray.GetBytes(), m_ulSize);
				m_bMallocError = false;
			}
			else {
				m_ulCapacity = oByteArray.Size();						//array too small, create new one
				free(m_pucData);										//ip 13/08/07
				if (m_ulCapacity == 0)
					m_ulCapacity = EXTRA_INCREMENT_LEN;
				m_pucData = static_cast<unsigned char *>(malloc(m_ulCapacity));
				if (m_pucData == NULL)
					m_bMallocError = true;
				else
				{
					m_ulSize = m_ulCapacity;
					memcpy(m_pucData, oByteArray.GetBytes(), m_ulSize);
					m_bMallocError = false;
				}
			}
		}

		return *this;
	}

	CByteArray & CByteArray::operator = (CByteArray &&oByteArray)
	{
		if(this != &oByteArray) {
			if(m_pucData != NULL) {
				SecureClearContents();
			}
			this->m_pucData = oByteArray.m_pucData;
			oByteArray.m_pucData = NULL;
			this->m_ulSize = oByteArray.m_ulSize;
			oByteArray.m_ulSize = 0;
			this->m_ulCapacity = oByteArray.m_ulCapacity;
			oByteArray.m_ulCapacity = 0;
			this->m_bMallocError = oByteArray.m_bMallocError;
			oByteArray.m_bMallocError = false;
		}
		return *this;
	}

	static inline bool IsHexDigit(char c)
	{
		return ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'));
	}

	static inline unsigned char Hex2Byte(char cHex)
	{
		if (cHex >= '0' && cHex <= '9')
			return (unsigned char)(cHex - '0');
		if (cHex >= 'A' && cHex <= 'F')
			return (unsigned char)(cHex - 'A' + 10);
		return (unsigned char)(cHex - 'a' + 10);
	}

	CByteArray::CByteArray(const std::string & csData, bool bIsHex)
	{
		if (!bIsHex)
		{
			const unsigned char *data = reinterpret_cast<const unsigned char *>(csData.c_str());
			MakeArray(data, static_cast<unsigned int>(csData.length()));
		}
		else
		{
			const char *csHexData = csData.c_str();
			unsigned long ulHexLen = (int)csData.size();
			m_ulCapacity = ulHexLen / 2;
			MakeArray(NULL, 0, m_ulCapacity);
			if (!m_bMallocError)
			{
				unsigned char uc = 0;
				bool bSecondHexDigit = true;
				for (unsigned long i = 0; i < ulHexLen; i++)
				{
					if (IsHexDigit(csHexData[i]))
					{
						uc = 16 * uc + Hex2Byte(csHexData[i]);
						bSecondHexDigit = !bSecondHexDigit;
						if (bSecondHexDigit)
							m_pucData[m_ulSize++] = uc;
					}
				}
			}
		}
	}

	// CByteArray::~CByteArray()
	CByteArray::~CByteArray()
	{
		SecureClearContents();
	}

	unsigned long CByteArray::Size() const
	{
		if (m_bMallocError)
			throw CMWEXCEPTION(EIDMW_ERR_MEMORY);

		return m_ulSize;
	}

	unsigned char CByteArray::GetByte(unsigned long ulIndex) const
	{
		if (m_bMallocError)
			throw CMWEXCEPTION(EIDMW_ERR_MEMORY);

		if (ulIndex >= m_ulSize)
			throw CMWEXCEPTION(EIDMW_ERR_PARAM_RANGE);

		return m_pucData[ulIndex];
	}

	unsigned long CByteArray::GetLong(unsigned long ulIndex) const
	{
		if (m_bMallocError)
			throw CMWEXCEPTION(EIDMW_ERR_MEMORY);

		if (ulIndex + sizeof(unsigned long) > m_ulSize)
			throw CMWEXCEPTION(EIDMW_ERR_PARAM_RANGE);

		unsigned long ulLong = 0;

		for (unsigned int i = 0; i < sizeof(unsigned long); i++)
			ulLong = (ulLong * 256) + m_pucData[ulIndex++];

		return ulLong;
	}

	void CByteArray::SetByte(unsigned char ucByte, unsigned long ulIndex)
	{
		if (m_bMallocError)
			throw CMWEXCEPTION(EIDMW_ERR_MEMORY);

		if (ulIndex >= m_ulSize)
			throw CMWEXCEPTION(EIDMW_ERR_PARAM_RANGE);

		m_pucData[ulIndex] = ucByte;
	}

	// unsigned char *CByteArray::GetBytes()
	unsigned char *CByteArray::GetBytes()
	{
		if (m_bMallocError)
			throw CMWEXCEPTION(EIDMW_ERR_MEMORY);

		return m_pucData;
	}

	// const unsigned char *CByteArray::GetBytes() const
	const unsigned char *CByteArray::GetBytes() const
	{
		if (m_bMallocError)
			throw CMWEXCEPTION(EIDMW_ERR_MEMORY);

		return m_ulSize == 0 ? NULL : m_pucData;
	}

	CByteArray CByteArray::GetBytes(unsigned long ulOffset, unsigned long ulLen) const
	{
		if (m_bMallocError)
			throw CMWEXCEPTION(EIDMW_ERR_MEMORY);

		if (ulOffset >= m_ulSize)
			throw CMWEXCEPTION(EIDMW_ERR_PARAM_RANGE);

		if (ulLen == 0xFFFFFFFF || ulOffset + ulLen > m_ulSize)
			ulLen = m_ulSize - ulOffset;

		return CByteArray(&m_pucData[ulOffset], ulLen);
	}

	void CByteArray::Append(unsigned char ucByte)
	{
		Append(&ucByte, sizeof(char));
	}

	//Other formalism for the Append(unsigned char ucByte)
	CByteArray & CByteArray::operator +=(const unsigned char ucByte)
	{
		Append(ucByte);
		return *this;
	}

	void CByteArray::AppendLong(unsigned long ulLong)
	{
		unsigned char tucLong[sizeof(unsigned long)];
		for (int i = sizeof(unsigned long) - 1; i >= 0; --i)
		{
			tucLong[i] = static_cast<unsigned char> (ulLong % 256);
			ulLong /= 256;
		}

		Append(tucLong, sizeof(tucLong));
	}

	void CByteArray::Append(const CByteArray & oByteArray)
	{
		Append(oByteArray.GetBytes(), oByteArray.Size());
	}
	//Other formalism for the Append(const CByteArray & oByteArray)
	CByteArray & CByteArray::operator +=(const CByteArray & oByteArray)
	{
		Append(oByteArray);
		return *this;
	}

	void CByteArray::Append(const unsigned char * pucData, unsigned long ulSize)
	{
		unsigned char* pucOldData;
		if (m_bMallocError)
			throw CMWEXCEPTION(EIDMW_ERR_MEMORY);

		if (pucData != NULL && ulSize != 0)		//add only if there is something to add
		{
			if (m_ulSize + ulSize > m_ulCapacity || m_pucData == NULL)
			{
				m_ulCapacity = m_ulSize + ulSize + EXTRA_INCREMENT_LEN;
				pucOldData = m_pucData;
				m_pucData = static_cast<unsigned char *>(realloc(m_pucData, m_ulCapacity));
				if (m_pucData == NULL)
				{
					m_bMallocError = true;
					if(pucOldData != NULL)
						free(pucOldData);
					throw CMWEXCEPTION(EIDMW_ERR_MEMORY);
				}
			}

			memcpy(m_pucData + m_ulSize, pucData, ulSize);
			m_ulSize += ulSize;
		}
	}

	// void CByteArray::Append()
	void CByteArray::Append(const std::string scData)
	{
		Append((const unsigned char *)(scData.c_str()), static_cast<unsigned long>(scData.length()));
	}
	//Other formalism for the Append(const std::string scData)
	CByteArray & CByteArray::operator += (const std::string scData)
	{
		Append(scData);
		return *this;
	}

	// void CByteArray::Append()
	void CByteArray::AppendString(const std::string scData)
	{
		unsigned long length = static_cast <unsigned long>(scData.length());
		this->AppendLong(length);
		this->Append(scData);
	}

	// void CByteArray::Chop()
	void CByteArray::Chop(unsigned long ulSize)
	{
		if (m_bMallocError)
			throw CMWEXCEPTION(EIDMW_ERR_MEMORY);

		if (ulSize <= m_ulSize)
			m_ulSize -= ulSize;
		else
			m_ulSize = 0;
	}

	// void CByteArray::TrimRight
	void CByteArray::TrimRight(unsigned char ucByte)
	{
		if (m_bMallocError)
			throw CMWEXCEPTION(EIDMW_ERR_MEMORY);

		while (m_ulSize > 0 && m_pucData[m_ulSize - 1] == ucByte)
		{
			m_ulSize--;
		}
	}

	// void CByteArray::ClearContents()
	void CByteArray::ClearContents()
	{
		if (m_pucData)
		{
			free(m_pucData);
			m_pucData = NULL;
		}
		m_ulSize = 0;
		m_ulCapacity = 0;
	}

	// void CByteArray::SecureClearContents() set content value to zero's before freeing
	void CByteArray::SecureClearContents()
	{
		if (m_pucData)
		{
			memset(m_pucData, 0, m_ulSize);
			free(m_pucData);
			m_pucData = NULL;
		}
		m_ulSize = 0;
		m_ulCapacity = 0;
	}

	bool CByteArray::Equals(const CByteArray & oByteArray) const
	{
		if (m_bMallocError)
			throw CMWEXCEPTION(EIDMW_ERR_MEMORY);

		if (m_ulSize == 0 && oByteArray.Size() == 0)
			return true;

		if (m_pucData == NULL)
			throw CMWEXCEPTION(EIDMW_ERR_CHECK);

		return m_ulSize == oByteArray.Size() &&
			memcmp(m_pucData, oByteArray.GetBytes(), m_ulSize) == 0;
	}

	static inline char ToHex(unsigned int uc)
	{
		return static_cast<char>(uc <= 9 ? '0' + uc : 'A' - 10 + uc);
	}

	static inline void AddHexChar(char *csBuf, unsigned long & ulOffset, bool bAddSpace, unsigned char uc)
	{
		csBuf[ulOffset++] = ToHex(uc / 16);
		csBuf[ulOffset++] = ToHex(uc % 16);
		if (bAddSpace)
			csBuf[ulOffset++] = ' ';
	}

	std::wstring CByteArray::ToWString(bool bAddSpace, bool bOneLine,
		unsigned long ulOffset, unsigned long ulLen) const
	{
		return utilStringWiden(ToString(bAddSpace, bOneLine, ulOffset, ulLen));
	}

	std::string CByteArray::ToString(bool bAddSpace, bool bOneLine,
		unsigned long ulOffset, unsigned long ulLen) const
	{
		if (m_bMallocError)
			throw CMWEXCEPTION(EIDMW_ERR_MEMORY);

		if (ulLen == 0xFFFFFFFF || ulOffset + ulLen > m_ulSize)
			ulLen = m_ulSize - ulOffset;

		if (ulOffset >= m_ulSize || ulLen == 0)
			return std::string("");

		char csBuf[3 * 16 + 10];
		unsigned long ulBufOffset = 0;
		unsigned long i;

		unsigned char *pTmp = m_pucData + ulOffset;

		if (bOneLine)
		{
			unsigned long ulEnd = (ulLen >= 10 ? 10 : ulLen);

			for (i = 0; i < ulEnd; i++)
				AddHexChar(csBuf, ulBufOffset, bAddSpace, pTmp[i]);
			if (ulLen > 16)
			{
				csBuf[ulBufOffset++] = '.';
				csBuf[ulBufOffset++] = '.';
				if (bAddSpace)
					csBuf[ulBufOffset++] = ' ';
				i = ulLen - 5;
			}
			for (; i < ulLen; i++)
				AddHexChar(csBuf, ulBufOffset, bAddSpace, pTmp[i]);
			if (bAddSpace)
				csBuf[ulBufOffset - 1] = '\0';
			else
				csBuf[ulBufOffset] = '\0';

			return std::string(csBuf);
		}
		else
		{
			std::string csRet("\n");

			for (i = 0; i < ulLen; i += 16)
			{
				unsigned long ulEnd = ulLen - i > 16 ? 16 : ulLen - i;
				ulBufOffset = 0;
				csBuf[ulBufOffset++] = '\t';
				for (unsigned long j = 0; j < ulEnd; j++)
					AddHexChar(csBuf, ulBufOffset, bAddSpace, pTmp[j]);
				pTmp += ulEnd;
				csBuf[ulBufOffset++] = '\n';
				csBuf[ulBufOffset] = '\0';
				csRet.append(csBuf);
			}

			return csRet;
		}
	}
	//copy supplied memory into new allocated memory
	//?? capacity is not in steps of 10, but takes new data-len
	void CByteArray::MakeArray(const unsigned char *pucData,	//returns allocated memory
		unsigned long ulSize,
		unsigned long ulCapacity)
	{
		m_ulCapacity = ulCapacity < ulSize ? ulSize : ulCapacity;	//take largest value of both: available memory
		m_ulSize = ulSize;	//effictively used memory
		m_bMallocError = false;

		if (m_ulCapacity == 0)
			m_ulCapacity = EXTRA_INCREMENT_LEN;
		m_pucData = static_cast<unsigned char *>(malloc(m_ulCapacity));
		if (m_pucData == NULL)
		{
			m_ulSize = 0;
			m_bMallocError = true;
		}
		else if (pucData != NULL)
		{
			memcpy(m_pucData, pucData, m_ulSize);
		}
	}

	void CByteArray::Replace(unsigned char ucByteSrc, unsigned char ucByteDest)
	{

		unsigned char *src = m_pucData;
		for (unsigned long i = 0; i < m_ulSize; ++i)
		{
			if (*src == ucByteSrc)
				*src = ucByteDest;
			src++;
		}
	}

}				// namespace eidMW
