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

 * 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/.

**************************************************************************** */
// TLVBuffer.cpp: implementation of the CTLVBuffer class.
//
//////////////////////////////////////////////////////////////////////

#include "tlvbuffer.h"
#include <assert.h>

namespace eIDMW
{

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

	char CTLVBuffer::hexChars[] =
		{ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B',
  'C', 'D', 'E', 'F' };

	CTLVBuffer::CTLVBuffer()
	{

	}


	CTLVBuffer::~CTLVBuffer()
	{
		ITMap it;

		//free complete m_oMapTLV
		for (it = m_oMapTLV.begin(); it != m_oMapTLV.end(); ++it)
		{
			CTLV *pTemp = (*it).second;

			if (pTemp != NULL)
			{
				delete pTemp;
			}
		}
		m_oMapTLV.clear();
	}

/** Unstream a multi-TLV-block into m_oMapTLV structure.
  User standard TLV syntax; extra length bytes when the MSB of a length-byte is set to 1.
  Each block should start with a 0-Tag, 0-tag can only be used as first TLV of a block.

 returns: false = invalid data
          true  = OK all done
*/
	bool CTLVBuffer::ParseFileTLV(const unsigned char *pucData,
				      unsigned long ulLen)
	{
		bool bRet = false;

		if (pucData != NULL && ulLen > 0)
		{
			m_oMapTLV.clear();
			bRet = true;
			unsigned long ulIndex = 0;

			while ((ulIndex + 1) < ulLen)	//at least 2 bytes; tag & length
			{
				//--- get & check TAG
				unsigned char ucTag = pucData[ulIndex++];
				unsigned long ulFieldLen = 0;

				//the complete block should start with tag '0' and tag '0' may only be used as first TLV
				if ((ucTag == 0x00 && ulIndex > 2)
				    || (ucTag != 0x00 && ulIndex < 2))
				{
					bRet = false;
					break;
				}
				//--- get Length
//            int iNrBytes = ulLen - ulIndex - 1;     //calculate remaining bytes
				int iNrBytes = ulLen - ulIndex;	//calculate remaining bytes

				if (!TlvDecodeLen(pucData + ulIndex, &iNrBytes, &ulFieldLen))
				{
					bRet = false;
					break;
				}
				ulIndex += iNrBytes;

				if (((ulIndex == ulLen) && (ulFieldLen != 0)) || (ulIndex > ulLen))	//no data-bytes allowed when len=0           
				{
					bRet = false;
					break;
				}

				//--- add the decoded TLV 
				m_oMapTLV[ucTag] =
					new CTLV(ucTag, pucData + ulIndex,
						 ulFieldLen);
				ulIndex += ulFieldLen;
			}
		}
		return bRet;
	}

/** Unstream a multi-TLV-block into m_oMapTLV structure
 Use Fedict TLV syntax; extra length bytes when length-byte = FF
 0-tags can only be used as first TLV of a block
 returns: 0 = invalid data
          1 = OK all done

 */
	int CTLVBuffer::ParseTLV(const unsigned char *pucData,
				 unsigned long ulLen)
	{
		int iRet = 0;

		if (pucData != NULL && ulLen > 0)
		{
			m_oMapTLV.clear();
			iRet = 1;
			unsigned long ulIndex = 0;

			while ((ulIndex + 1) < ulLen)	//at least 2 bytes; tag & length
			{
				//--- get & check tag
				unsigned char ucTag = pucData[ulIndex++];

				// tag '0' may only be used as first TLV
				if (ucTag == 0x00 && ulIndex > 2)
				{
					iRet = 0;
					break;
				}
				//--- get Length
				unsigned long ulFieldLen = pucData[ulIndex];

				while (0xFF == pucData[ulIndex])	//add extra length-bytes
				{
					ulFieldLen += pucData[++ulIndex];
					if ((ulIndex + 1) >= ulLen)
					{
						iRet = 0;
						break;
					}
				}
				++ulIndex;

				//check if enough data available
				if ((ulIndex + ulFieldLen) > ulLen)
				{
					iRet = 0;
					break;
				}
				//get data
				m_oMapTLV[ucTag] =
					new CTLV(ucTag, pucData + ulIndex,
						 ulFieldLen);
				ulIndex += ulFieldLen;
			}
		}
		return iRet;
	}

	CTLV *CTLVBuffer::GetTagData(unsigned char ucTag)
	{
		ITMap it = m_oMapTLV.find(ucTag);

		if (it != m_oMapTLV.end())
		{
			return (*it).second;
		}
		return NULL;
	}

	void CTLVBuffer::FillASCIIData(unsigned char ucTag, char *pData,
				       unsigned long *pulLen)
	{
		CTLV *pTagData = NULL;
		unsigned long ulLength = 0;

		assert(pulLen != NULL);

		if ((NULL != (pTagData = GetTagData(ucTag)))
		    && (pData != NULL))
		{
			if ((*pulLen >= (ulLength = pTagData->GetLength())))
			{
				memcpy(pData, pTagData->GetData(), ulLength);
			}
			//else: we don't chop off data, so either get it all, or nothing
		}
		//tag not present -> *pulLen = 0

		*pulLen = ulLength;
	}

	bool CTLVBuffer::FillUTF8Data(unsigned char ucTag, char *pData,
				      unsigned long *pulLen)
	{
		CTLV *pTagData = NULL;
		unsigned long ulLength = 0;

		assert(pulLen != NULL);

		if (NULL == (pTagData = GetTagData(ucTag))) {
			return false;
		}
		if(pData != NULL)
		{
			if ((*pulLen >= (ulLength = pTagData->GetLength())))
			{
				memcpy(pData, pTagData->GetData(), ulLength);
			}
			//else: we don't chop off data, so either get it all, or nothing
		}
		//tag not present -> *pulLen = 0

		*pulLen = ulLength;
		return true;
	}

	void CTLVBuffer::FillBinaryStringData(unsigned char ucTag,
					      char *pData,
					      unsigned long *pulLen)
	{
		CTLV *pTagData = NULL;
		unsigned long ulLength;

		if ((NULL != (pTagData = GetTagData(ucTag)))
		    && (pData != NULL) && (pulLen != NULL))
		{
			char *pszTemp =
				Hexify(pTagData->GetData(),
				       pTagData->GetLength());
			if (*pulLen >
			    (ulLength = (unsigned long) strlen(pszTemp)))
			{
				memcpy(pData, pszTemp, ulLength + 1);	//copy also terminating 0-char
				*pulLen = ulLength;	//length without 0-char
			} else
				*pulLen = 0;
			delete[]pszTemp;
		}
	}

	void CTLVBuffer::FillLongData(unsigned char ucTag, long *piData)
	{
		CTLV *pTagData = NULL;

		if (NULL != (pTagData = GetTagData(ucTag)))
		{
			char *pszTemp = new char[pTagData->GetLength() + 1];

			memset(pszTemp, 0, pTagData->GetLength() + 1);
			memcpy(pszTemp, pTagData->GetData(),
			       pTagData->GetLength());
			*piData = atol(pszTemp);
			delete[]pszTemp;
		}
	}

	void CTLVBuffer::FillBinaryData(unsigned char ucTag, unsigned char *pData, unsigned long *pulLen
							    /**< in/out: In=max.length, Out=current length */
		)
	{
		CTLV *pTagData = NULL;

		if (NULL != (pTagData = GetTagData(ucTag)))
		{
			unsigned long ulLength = pTagData->GetLength();

			if (*pulLen >= ulLength)	//length data
				memcpy(pData, pTagData->GetData(), ulLength);
			else
				ulLength = 0;
			*pulLen = ulLength;
		}
	}

	bool CTLVBuffer::FillBinaryDataCheck(unsigned char ucTag,
					     unsigned char *pData,
					     unsigned long *pulLen)
	{
		bool bRet = false;
		CTLV *pTagData = NULL;

		if (NULL != (pTagData = GetTagData(ucTag)))
		{
			if (*pulLen >= pTagData->GetLength())
			{
				*pulLen = pTagData->GetLength();
				bRet = true;
			}
			memcpy(pData, pTagData->GetData(), *pulLen);
		} else
			*pulLen = 0;
		return bRet;
	}

//binair to ascii-hex
	char *CTLVBuffer::Hexify(unsigned char *pData, unsigned long ulLen)
	{
		char *pszHex = new char[ulLen * 2 + 1];

		//memset(pszHex, 0, ulLen*2 + 1);
		if (pData != NULL)
		{
			int j = 0;

			for (unsigned long i = 0; i < ulLen; i++)
			{
				pszHex[j++] = hexChars[pData[i] >> 4 & 0x0F];
				pszHex[j++] = hexChars[pData[i] & 0x0F];
			}
			pszHex[j] = 0;
		}
		return pszHex;
	}

	unsigned long CTLVBuffer::GetLengthNeeded()
	{
		unsigned long lRet = 0;
		ITMap it;
		unsigned char ucLenBuffer[5];
		int iBufLen;

		for (it = m_oMapTLV.begin(); it != m_oMapTLV.end(); ++it)
		{
			CTLV *pTemp = (*it).second;

			if (pTemp != NULL)
			{
				memset(ucLenBuffer, 0, sizeof(ucLenBuffer));
				iBufLen = sizeof(ucLenBuffer);
				if (TlvEncodeLen
				    (pTemp->GetLength(), ucLenBuffer,
				     &iBufLen))
				{
					lRet++;	//Tag
					lRet += iBufLen;	//Length
					lRet += pTemp->GetLength();	//Data
				}
			}
		}
		return lRet;
	}

/**********************************************************************************************************
Encode the length in the TLV format    

    Default one length byte.
    The highest bit = 1 means one additional length-byte.
    The lowest 7 bits of each byte are 7 bits of the length-number.

    1 length byte  will encode max 7-bit  numbers, between     00 -       7F  
    2 length bytes will encode max 14-bit numbers, between     80 -     3FFF
    3 length bytes will encode max 21-bit numbers, between    400 -  1F FFFF
    4 length bytes will encode max 28-bit numbers, between 20 000 - FFF FFFF
    
examples:
  len=7B        -> encoding =          7B    
  len=CD        -> encoding =       81 4D
  len=2039      -> encoding =       C0 39 
  len=43ABDA    -> encoding = 82 8E D7 5A
    
 **********************************************************************************************************/
	bool CTLVBuffer::TlvEncodeLen(unsigned long ulLenVal,	//In: length-value to encode in the TLV-format
				      unsigned char *pucBufDest,	//In/Out: Destination buffer with the encoded length-stream
				      int *piBufLen	//In/out: Max buffer length, Out:length of encoded stream
		)
	{
		int iStreamLen = 1;	//length of the stream
		unsigned long ulLenValLoc = 0;	//length
		unsigned char ucExtraByte = 0x00;

		//--- parameter check   
		if (pucBufDest == NULL || piBufLen == NULL)
		{
			return false;
		}
		//--- determinate number of length-bytes
		ulLenValLoc = ulLenVal >> 7;

		while (ulLenValLoc)
		{
			iStreamLen++;
			ulLenValLoc >>= 7;
		}

		if (*piBufLen < iStreamLen)
		{
			return false;
		}
		*piBufLen = iStreamLen;

		//--- write encoded length
		ulLenValLoc = ulLenVal;
		pucBufDest[0] = 0;
		ucExtraByte = 0;
		while (iStreamLen--)
		{
			pucBufDest[iStreamLen] =
				((unsigned char) ulLenValLoc & 0x7F) +
				ucExtraByte;
			ucExtraByte = 0x80;
			ulLenValLoc >>= 7;
		}

		return true;
	}

/**********************************************************************************************************
Decode the length from the TLV format    
   
 **********************************************************************************************************/
	bool CTLVBuffer::TlvDecodeLen(const unsigned char *pucBufSrc,	//In: Source buffer with the encoded length-stream
				      int *piBufLen,	//In/Out: Maximum length of encoded stream, Out:number of length-bytes
				      unsigned long *pulLenVal	//Out: length-value from the TLV stream
		)
	{
		int iStreamLenMax = 0;	//length of the stream

		//--- parameter check   
		if (pucBufSrc == NULL || piBufLen == NULL || (*piBufLen == 0) || pulLenVal == NULL)
		{
			return false;
		}
		//--- decode first byte   
		iStreamLenMax = *piBufLen;
		*piBufLen = 1;
		*pulLenVal = 0x7F & *pucBufSrc;

		//--- handle additional length bytes   
		while (*(pucBufSrc++) & 0x80)
		{
			if (++(*piBufLen) > iStreamLenMax)
			{
				return false;
			}

			*pulLenVal = (*pulLenVal << 7) + (0x7F & *pucBufSrc);
		}
		return true;
	}

	void CTLVBuffer::SetTagData(unsigned char ucTag,
				    const unsigned char *pucData,
				    unsigned long ulLen)
	{

		if (pucData)
		{
			ITMap itr;

			itr = m_oMapTLV.find(ucTag);
			if (itr == m_oMapTLV.end())
			{
				m_oMapTLV[ucTag] =
					new CTLV(ucTag, pucData, ulLen);
			} else
			{
				itr->second->ReplaceData(pucData, ulLen);
			}
		}
	}

	unsigned long CTLVBuffer::Extract(unsigned char *pucData,
					  unsigned long ulLen)
	{

		if (!pucData)
			return 0;

		CByteArray oTempArray;
		unsigned char ucLenBuffer[4] = { 0 };
		int iBufLen = sizeof(ucLenBuffer);
		ITMap it;

		for (it = m_oMapTLV.begin(); it != m_oMapTLV.end(); ++it)
		{

			iBufLen = sizeof(ucLenBuffer);
			memset(ucLenBuffer, 0, sizeof(ucLenBuffer));

			if (TlvEncodeLen
			    (it->second->GetLength(), ucLenBuffer, &iBufLen))
			{
				oTempArray.Append(it->second->GetTag());
				oTempArray.Append(ucLenBuffer, iBufLen);
				oTempArray.Append(it->second->GetData(),
						  it->second->GetLength());
			}
		}

		unsigned long ulSize = oTempArray.Size();

		if (ulLen >= ulSize)
		{
			memcpy(pucData, oTempArray.GetBytes(), ulSize);
			return ulSize;
		}

		return 0;
	}


}				// namespace eIDMW
