// DIMESerializer.cpp: implementation of the DIMESerializer class.
//
//
// Copyright (c) 2002 Robert Cunnings. All rights reserved.
//
// You may copy, modify, distribute or publish this code free of charge.
//
// NO WARRANTY: This software is provided AS IS, without warranty of any kind.
//
//////////////////////////////////////////////////////////////////////
//
// DIMESerializer class - implementation of FSM based DIME serializer. A setup
// call to SerializeInit() establishes the record size, and subsequent calls to
// SerializeRecord() pulls one record at a time from the serializer. Finished()
// returns true when serialization is complete. CalcSerialLength()
// may be called after intialization to determine the total DIME package size
// prior to start of serialization.
//  
//////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "DIMESerializer.h"
#include <atlconv.h>

#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif

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

DIMESerializer::DIMESerializer()
{
	m_State = ST_START;
	m_ulCurrentPayload = 0;
	m_ulRecordSize = 0;
	m_pPayloads = NULL;
}

DIMESerializer::~DIMESerializer()
{

}

bool DIMESerializer::Init( unsigned long ulRecordSize,
						   vector<DIMEPayload *> *pPayloads)
{
	if ((ulRecordSize % 4) > 0)
	{
		m_wstrErrorMsg = L"Serializer.Initializer.BadRecordSizeModulo";
		OnEvent(EV_ERROR);
		return false;
	}
	else if (pPayloads == NULL)
	{
		m_wstrErrorMsg = L"Serializer.Initializer.NullPayloadArrayPtr";
		OnEvent(EV_ERROR);
		return false;
	}
	m_pPayloads = pPayloads;
	m_State = ST_START;
	m_ulCurrentPayload = 0;
	m_ulRecordSize = ulRecordSize;

	return true;
}

bool DIMESerializer::SerializeRecord(  char *pszBuffer,
									   unsigned long ulSize,
									   unsigned long *pulBytesWritten)
{
	if (m_pPayloads == NULL)
	{
		m_wstrErrorMsg = L"Serializer.Serialize.Uninitialized";
		OnEvent(EV_ERROR);
		return false;
	}
	if (ulSize < m_ulRecordSize)
	{
		m_wstrErrorMsg = L"Serializer.Serialize.BadBufferSize";
		OnEvent(EV_ERROR);
		return false;
	}
	m_pszOutputBuffer = pszBuffer;
	m_ulOutputBufferSize = ulSize;
	m_ulOutputBytesWritten = 0;
	// generate event
	OnEvent(EV_SERIALIZE_REC);
	*pulBytesWritten = m_ulOutputBytesWritten;
	return true;
}

unsigned long DIMESerializer::CalcSerialLength()
{
	unsigned long ulCalculatedLength = 0;
	vector<DIMEPayload *>::iterator iter;
	for (iter = m_pPayloads->begin(); iter != m_pPayloads->end(); iter++)
	{
		DIMEPayload *pPayload = *iter;
		if (pPayload->OpenForRead() == false)
		{
			return 0;
		}
		// compute nbr of bytes required for header data
		unsigned long ulRecordLength = m_ulRecordSize;
		// compute nbr of bytes required for header data
		unsigned long ulHeaderLengthNeeded = HEADER_SIZE;
		// add length of options field
		unsigned long ulOptionsLength = GetOptionsLength(pPayload, true);
		unsigned long ulOptionsPaddingLength = (ulOptionsLength % 4 == 0) ? 0 : (4 - (ulOptionsLength % 4));
		ulHeaderLengthNeeded += (ulOptionsLength + ulOptionsPaddingLength);
		// add length of type field and padding
		unsigned long ulTypeLength = pPayload->GetType().length();
		unsigned long ulTypePaddingLength = (ulTypeLength % 4 == 0) ? 0 : (4 - (ulTypeLength % 4));
		ulHeaderLengthNeeded += (ulTypeLength + ulTypePaddingLength);
		// add length of id field and padding
		unsigned long ulIdLength = pPayload->GetId().length();
		unsigned long ulIdPaddingLength = (ulIdLength % 4 == 0) ? 0 : (4 - (ulIdLength % 4));
		ulHeaderLengthNeeded += (ulIdLength + ulIdPaddingLength);
		// sanity check
		if (m_ulRecordSize <= (ulHeaderLengthNeeded + 4))
		{
			pPayload->Close();
			return 0;
		}
		// how many bytes in record remain for payload data?
		unsigned long ulDataLengthAvail = m_ulRecordSize - ulHeaderLengthNeeded;
		// how much payload data is there?
		unsigned long ulPayloadLength = pPayload->GetDataLength();
		bool bChunked = true;
		// is payload data length less than that available?
		if (ulPayloadLength < ulDataLengthAvail)
		{
			// adjust record length to fit total payload length
			unsigned long ulDataPaddingLength = (ulPayloadLength % 4 == 0) ? 0 : (4 - (ulPayloadLength % 4));
			ulRecordLength -= (ulDataLengthAvail - (ulPayloadLength + ulDataPaddingLength));
			ulDataLengthAvail = ulPayloadLength;
			bChunked = false;
		}
		// ok, add length
		if (bChunked == true)
		{
			// add prescribed record length to total length
			ulCalculatedLength += m_ulRecordSize;
		}
		else
		{
			// all in one record, done
			ulCalculatedLength += ulRecordLength;
			pPayload->Close();
			continue;
		}
		// subtract actual available data length in 1st record from total payload data length
		ulPayloadLength -= ulDataLengthAvail;
		// recalc length of options field for chunks
		ulOptionsLength = GetOptionsLength(pPayload, false);
		ulOptionsPaddingLength = (ulOptionsLength % 4 == 0) ? 0 : (4 - (ulOptionsLength % 4));
		// recompute data length available per record without id and type fields
		ulDataLengthAvail = m_ulRecordSize - HEADER_SIZE - (ulOptionsLength + ulOptionsPaddingLength);
		// compute number of records needed to carry balance of payload data
		unsigned long ulNumRecords = ulPayloadLength / ulDataLengthAvail;
		// add to total length
		ulCalculatedLength += (ulNumRecords * m_ulRecordSize);
		// last record?
		unsigned long ulRemainder = ulPayloadLength % ulDataLengthAvail;
		if (ulRemainder)
		{
			// compute length of final record
			unsigned long ulPaddingLength = (ulRemainder % 4 == 0) ? 0 : (4 - (ulRemainder % 4));
			// add to total length
			ulCalculatedLength += (HEADER_SIZE + (ulOptionsLength + ulOptionsPaddingLength) + ulRemainder + ulPaddingLength);
		}
		pPayload->Close();
	}
	return ulCalculatedLength;
}

void DIMESerializer::OnEvent(Event e)
{
	Event ev = e;
	while (ev != EV_NULL)
	{
		switch (m_State)
		{
		case ST_START:
			switch (ev)
			{
			case EV_SERIALIZE_REC:
				m_State = ST_OPEN_PAYLOAD;
				ev = ACT_Open_Payload();
				break;
			case EV_ERROR:
				m_State = ST_ERROR;
				ev = ACT_Error();
				break;
			default:
				m_State = ST_ERROR;
				m_wstrErrorMsg = L"Serializer.EventHandler.Start.BadEvent";
				ev = ACT_Error();
				break;
			}
			break;
		case ST_OPEN_PAYLOAD:
			switch (ev)
			{
			case EV_PAYLOAD_OPENED:
				m_State = ST_SERIALIZE_PAYLOAD;
				ev = ACT_Serialize_Payload();
				break;
			case EV_ERROR:
				m_State = ST_ERROR;
				ev = ACT_Error();
				break;
			default:
				m_State = ST_ERROR;
				m_wstrErrorMsg = L"Serializer.EventHandler.OpenPayload.BadEvent";
				ev = ACT_Error();
				break;
			}
			break;
		case ST_SERIALIZE_PAYLOAD:
			switch (ev)
			{
			case EV_SERIALIZE_REC:
				ev = ACT_Serialize_Payload();
				break;
			case EV_PAYLOAD_END:
				m_State = ST_CLOSE_PAYLOAD;
				ev = ACT_Close_Payload();
				break;
			case EV_CHUNK_END:
				m_State = ST_SERIALIZE_CHUNK;
				ev = EV_NULL;
				break;
			case EV_MSG_END:
				m_State = ST_FINISH;
				ev = ACT_Close_Payload();
				break;
			case EV_ERROR:
				m_State = ST_ERROR;
				ev = ACT_Error();
				break;
			default:
				m_State = ST_ERROR;
				m_wstrErrorMsg = L"Serializer.EventHandler.SerializePayload.BadEvent";
				ev = ACT_Error();
				break;
			}
			break;
		case ST_CLOSE_PAYLOAD:
			switch (ev)
			{
			case EV_PAYLOAD_CLOSED:
				m_State = ST_START;
				ev = EV_NULL;
				break;
			case EV_ERROR:
				m_State = ST_ERROR;
				ev = ACT_Error();
				break;
			default:
				m_State = ST_ERROR;
				m_wstrErrorMsg = L"Serializer.EventHandler.ClosePayload.BadEvent";
				ev = ACT_Error();
				break;
			}
			break;
		case ST_SERIALIZE_CHUNK:
			switch (ev)
			{
			case EV_SERIALIZE_REC:
				ev = ACT_Serialize_Chunk();
				break;
			case EV_PAYLOAD_END:
				m_State = ST_CLOSE_PAYLOAD;
				ev = ACT_Close_Payload();
				break;
			case EV_MSG_END:
				m_State = ST_FINISH;
				ev = ACT_Close_Payload();
				break;
			case EV_ERROR:
				m_State = ST_ERROR;
				ev = ACT_Error();
				break;
			default:
				m_State = ST_ERROR;
				m_wstrErrorMsg = L"Serializer.EventHandler.SerializeChunk.BadEvent";
				ev = ACT_Error();
				break;
			}
			break;
		case ST_ERROR:
		case ST_FINISH:
			// consume all events
			ev = EV_NULL;
			break;
		default:
			m_State = ST_ERROR;
			m_wstrErrorMsg = L"Serializer.EventHandler.BadState";
			ev = ACT_Error();
			break;
		}
	}
}

DIMESerializer::Event DIMESerializer::ACT_Serialize_Payload()
{
	DIMEPayload *pPayload = m_pPayloads->at(m_ulCurrentPayload);
	// compute nbr of bytes required for header data
	unsigned long ulHeaderLengthNeeded = HEADER_SIZE;
	// add length of options field
	unsigned long ulOptionsLength = GetOptionsLength(pPayload, true);
	unsigned long ulOptionsPaddingLength = (ulOptionsLength % 4 == 0) ? 0 : (4 - (ulOptionsLength % 4));
	ulHeaderLengthNeeded += (ulOptionsLength + ulOptionsPaddingLength);
	// add length of type field and padding
	unsigned long ulTypeLength = pPayload->GetType().length();
	unsigned long ulTypePaddingLength = (ulTypeLength % 4 == 0) ? 0 : (4 - (ulTypeLength % 4));
	ulHeaderLengthNeeded += (ulTypeLength + ulTypePaddingLength);
	// add length of id field and padding
	unsigned long ulIdLength = pPayload->GetId().length();
	unsigned long ulIdPaddingLength = (ulIdLength % 4 == 0) ? 0 : (4 - (ulIdLength % 4));
	ulHeaderLengthNeeded += (ulIdLength + ulIdPaddingLength);
	// sanity check
	if (m_ulRecordSize <= (ulHeaderLengthNeeded + 4))
	{
		m_wstrErrorMsg = L"Serializer.SerializePayload.RecordSizeTooSmall";
		return EV_ERROR;
	}
	// how many bytes in record remain for payload data?
	unsigned long ulDataLengthAvail = m_ulRecordSize - ulHeaderLengthNeeded;
	// how much payload data is there?
	unsigned long ulPayloadLength = pPayload->GetDataLength();
	bool bChunked = true;
	// is payload data length less than that available?
	if (ulPayloadLength < ulDataLengthAvail)
	{
		// adjust record length to fit total payload length
		ulDataLengthAvail = ulPayloadLength;
		bChunked = false;
	}
	unsigned long ulDataPaddingLength = (ulDataLengthAvail % 4 == 0) ? 0 : (4 - (ulDataLengthAvail % 4));
	// is this the last payload?
	bool bLastPayload = false;
	if ((m_pPayloads->size() - 1) == m_ulCurrentPayload)
	{
		bLastPayload = true;
	}
	Header32 ulHeader;
	// set version
	ulHeader.l = DIME_VERSION;
	if (m_ulCurrentPayload == 0)
	{
		// set message start flag
		ulHeader.l |= MASK_MB_FLAG;
	}
	if (bLastPayload == true && bChunked == false)
	{
		// set message end flag
		ulHeader.l |= MASK_ME_FLAG;
	}
	if (bChunked == true)
	{
		// set chunked flag
		ulHeader.l |= MASK_CF_FLAG;
	}
	// set type_t
	unsigned long ulTemp = pPayload->GetTNF();
	ulHeader.l |= (ulTemp << 20);
	// set options length
	ulHeader.l |= ulOptionsLength;
	// write to buffer
	for (int j = 0; j < 4; j++)
	{
		m_pszOutputBuffer[j] = ulHeader.c[3 - j];
	}
	// set id length field
	ulHeader.l = 0;
	ulHeader.l |= (ulIdLength << 16);
	// set type length field
	ulHeader.l |= ulTypeLength;
	// write to buffer
	for (j = 4; j < 8; j++)
	{
		m_pszOutputBuffer[j] = ulHeader.c[3 - (j - 4)];
	}
	// set data length
	ulHeader.l = ulDataLengthAvail;
	// write to buffer
	for (j = 8; j < 12; j++)
	{
		m_pszOutputBuffer[j] = ulHeader.c[3 - (j - 8)];
	}
	unsigned long ulOffset = 12;
	// write options and padding
	if (ulOptionsLength > 0)
	{
		unsigned long ulSize;
		if (ProcessOptions(m_pszOutputBuffer + ulOffset, ulSize, pPayload, true) == false)
		{
			m_wstrErrorMsg = L"Serializer.Serialize.ProcessOptionsFailed";
			return EV_ERROR;
		}
		ulOffset += ulSize;
		for (unsigned long i = 0; i < ulOptionsPaddingLength; i++)
		{
			m_pszOutputBuffer[ulOffset + i] = 0;
		}
		ulOffset += ulOptionsPaddingLength;
	}
	// write id and padding
	memcpy(m_pszOutputBuffer + ulOffset, pPayload->GetId().c_str(), ulIdLength);
	ulOffset += ulIdLength;
	for (unsigned long i = 0; i < ulIdPaddingLength; i++)
	{
		m_pszOutputBuffer[ulOffset + i] = 0;
	}
	// write type and padding
	ulOffset += ulIdPaddingLength;
	memcpy(m_pszOutputBuffer + ulOffset, pPayload->GetType().c_str(), ulTypeLength);
	ulOffset += ulTypeLength;
	for (i = 0; i < ulTypePaddingLength; i++)
	{
		m_pszOutputBuffer[ulOffset + i] = 0;
	}
	ulOffset += ulTypePaddingLength;
	// now attempt to read block of data of size ulDataLength
	unsigned long ulNbrBytes;
	if (pPayload->Read( &m_pszOutputBuffer[ulOffset],
						ulDataLengthAvail,
						&ulNbrBytes) == false ||
						ulNbrBytes != ulDataLengthAvail)
	{
		m_wstrErrorMsg = L"Serializer.SerializePayload.ReadPayloadFailed";
		return EV_ERROR;
	}
	// padding for data
	ulOffset += ulDataLengthAvail;
	for (i = 0; i < ulDataPaddingLength; i++)
	{
		m_pszOutputBuffer[ulOffset + i] = 0;
	}
	m_ulOutputBytesWritten += (ulOffset + ulDataPaddingLength);
	if (bChunked == true)
	{
		return EV_CHUNK_END;
	}
	else if (bLastPayload == true)
	{
		return EV_MSG_END;
	}
	return EV_PAYLOAD_END;
}

DIMESerializer::Event DIMESerializer::ACT_Serialize_Chunk()
{

	DIMEPayload *pPayload = m_pPayloads->at(m_ulCurrentPayload);
	// compute nbr of bytes required for header data
	unsigned long ulHeaderLengthNeeded = HEADER_SIZE;
	// add length of options field
	unsigned long ulOptionsLength = GetOptionsLength(pPayload, false);;
	unsigned long ulOptionsPaddingLength = (ulOptionsLength % 4 == 0) ? 0 : (4 - (ulOptionsLength % 4));
	ulHeaderLengthNeeded += (ulOptionsLength + ulOptionsPaddingLength);
	// sanity check
	if (m_ulRecordSize <= (ulHeaderLengthNeeded + 4))
	{
		m_wstrErrorMsg = L"Serializer.SerializeChunk.RecordSizeTooSmall";
		return EV_ERROR;
	}
	// now attempt to read block of data of size ulDataLength
	unsigned long ulNbrBytes;
	// how many bytes in record remain for payload data?
	unsigned long ulDataLengthAvail = m_ulRecordSize - ulHeaderLengthNeeded;
	// try to read in that many from the payload
	if (pPayload->Read( &m_pszOutputBuffer[HEADER_SIZE + ulOptionsLength + ulOptionsPaddingLength],
						ulDataLengthAvail,
						&ulNbrBytes) == false)
	{
		m_wstrErrorMsg = L"Serializer.SerializeChunk.ReadPayloadFailed";
		return EV_ERROR;
	}
	bool bLastChunk = false;
	// was space available filled up?
	if (ulNbrBytes < ulDataLengthAvail)
	{
		// adjust to total data length actually read
		ulDataLengthAvail = ulNbrBytes;
		bLastChunk = true;
	}
	unsigned long ulDataPaddingLength = (ulDataLengthAvail % 4 == 0) ? 0 : (4 - (ulDataLengthAvail % 4));
	// is this the last payload?
	bool bLastPayload = false;
	if ((m_pPayloads->size() - 1) == m_ulCurrentPayload)
	{
		bLastPayload = true;
	}
	Header32 ulHeader;
	// set DIME version
	ulHeader.l = DIME_VERSION;
	if (bLastPayload == true && bLastChunk == true)
	{
		// set message end flag
		ulHeader.l |= MASK_ME_FLAG;
	}
	if (bLastChunk == false)
	{
		// set chunked flag
		ulHeader.l |= MASK_CF_FLAG;
	}
	// type_t must be 0 for chunk
	// set options length
	ulHeader.l |= ulOptionsLength;
	// write to buffer
	for (int j = 0; j < 4; j++)
	{
		m_pszOutputBuffer[j] = ulHeader.c[3 - j];
	}
	// id length and type length both zero for chunk
	for (j = 4; j < 8; j++)
	{
		m_pszOutputBuffer[j] = 0;
	}
	// write data length
	ulHeader.l = ulDataLengthAvail;
	for (j = 8; j < 12; j++)
	{
		m_pszOutputBuffer[j] = ulHeader.c[3 - (j - 8)];
	}
	unsigned long ulOffset = HEADER_SIZE;
	// write options and padding
	if (ulOptionsLength > 0)
	{
		unsigned long ulSize;
		if (ProcessOptions(m_pszOutputBuffer + ulOffset, ulSize, pPayload, false) == false)
		{
			m_wstrErrorMsg = L"Serializer.SerializeChunk.ProcessOptionsFailed";
			return EV_ERROR;
		}
		ulOffset += ulSize;
		for (unsigned long i = 0; i < ulOptionsPaddingLength; i++)
		{
			m_pszOutputBuffer[ulOffset + i] = 0;
		}
		ulOffset += ulOptionsPaddingLength;
	}
	// padding for data
	ulOffset += ulDataLengthAvail;
	for (unsigned long i = 0; i < ulDataPaddingLength; i++)
	{
		m_pszOutputBuffer[ulOffset + i] = 0;
	}
	m_ulOutputBytesWritten += (ulOffset + ulDataPaddingLength);
	if (bLastPayload == true && bLastChunk == true)
	{
		return EV_MSG_END;
	}
	if (bLastChunk == true)
	{
		return EV_PAYLOAD_END;
	}
	// more chunks to come, wait for next request
	return EV_NULL;
}

DIMESerializer::Event DIMESerializer::ACT_Open_Payload()
{
	DIMEPayload *pPayload = m_pPayloads->at(m_ulCurrentPayload);
	// open payload for reading
	if (pPayload->OpenForRead() == false)
	{
		m_wstrErrorMsg = L"Serializer.OpenPayload.OpenForReadFailed";
		return EV_ERROR;
	}
	return EV_PAYLOAD_OPENED;
}

DIMESerializer::Event DIMESerializer::ACT_Close_Payload()
{
	DIMEPayload *pPayload = m_pPayloads->at(m_ulCurrentPayload);
	if (pPayload->Close() == false)
	{
		m_wstrErrorMsg = L"Serializer.ClosePayload.CloseFailed";
		return EV_ERROR;
	}
	++m_ulCurrentPayload;
	return EV_PAYLOAD_CLOSED;
}

DIMESerializer::Event DIMESerializer::ACT_Error()
{
    HANDLE  hEventSource;
    LPTSTR  lpszStrings[1];

	USES_CONVERSION;
	char buf[128];
	_snprintf(buf, 127, "Error: %s", OLE2A(m_wstrErrorMsg.c_str()));
    buf[127] = 0;
    lpszStrings[0] = buf;
    hEventSource = RegisterEventSource(NULL, "White Mesa DIME Parser");
    if (hEventSource != NULL)
    {
        ReportEvent(hEventSource, EVENTLOG_INFORMATION_TYPE, 0, 0, NULL, 1, 0, (LPCTSTR*) &lpszStrings[0], NULL);
        DeregisterEventSource(hEventSource);
    }
	return EV_ERR_HANDLED;
}

unsigned long DIMESerializer::GetOptionsLength( DIMEPayload *pPayload,
												bool bIsFirstRecord)
{
	if (bIsFirstRecord == false)
	{
		return 0;
	}
	return pPayload->GetOptionsLength();
}

bool DIMESerializer::ProcessOptions(char *pOptions,
									unsigned long& ulLength,
									DIMEPayload *pPayload,
									bool bIsFirstRecord)
{
	return pPayload->GetOptions((unsigned char *)pOptions, ulLength);
}
