/***************************************************************************
                   Interface Processor Class
                -----------------------------------------

	begin      : 2013/09/16
	copyright  : (C) 2005 Solbox Inc.
	author     : Development 1 Team
					- 2013/09/16
	email      : dev1@solbox.com
	version    : 3.2.0

	CopyRight(C) 2005 Solbox Inc. All Rights reserved.
	Redistribution and use in source and binary forms, with or with out
	modification, are not permitted in outside of Solbox Inc.
***************************************************************************/

#include "InterfaceProcessor.h"
#include "Logger.h"
#include "protocol.h"
#include "global.h"
#include "md5.h"
#include "Configs.h"

#define AuthPrime 137897

#define MaxSizeOfRcvBuffer  1024	

#define MD_CTX MD5_CTX
#define MDInit MD5Init
#define MDUpdate MD5Update
#define MDFinal MD5Final

void getHashedStr(char *pszPlain, char *pszHashed)
{
	unsigned char szDigest[16];
	char szHexBuffer[3];
	string szResult;

	MD_CTX md5Context;
	MDInit( &md5Context );

	MDUpdate( &md5Context, (unsigned char*)pszPlain, ::strlen(pszPlain));

	MDFinal( szDigest, &md5Context );
	// 해시값을 hex_digest 값으로 바꿔준다.
	for(int i = 0; i<16 ; i++ )
	{
		memset( szHexBuffer, 0x00, sizeof( szHexBuffer ) );
		sprintf( szHexBuffer, "%02x", szDigest[i] );
		szResult.append( (char*)szHexBuffer);
	}
	
	::snprintf(pszHashed, 33, "%s", szResult.c_str());
}

CInterfaceProcessor::CInterfaceProcessor()
	: CBaseSocket( SOCKET_NOT_VALID )
{

}

CInterfaceProcessor::CInterfaceProcessor(int clientsd, string szClientIP,
	CQueue<CLoginInfo> *pQueueLoginInfo, CServiceInfoUpdater *pobjServiceInfoUpdater)
	: CBaseSocket( clientsd ), m_szClientIP(szClientIP)
	,m_pQueueLoginInfo(pQueueLoginInfo), m_pobjServiceInfoUpdater(pobjServiceInfoUpdater)
{
	m_responseBody = NULL;
}

CInterfaceProcessor::~CInterfaceProcessor()
{
	LOG(LDEV, "~CInterfaceProcessor ");
	if ( m_responseBody != NULL )
		delete m_responseBody;
	Close();
}

int CInterfaceProcessor::Respose_cctimed(char *_pszRcvdReq ) 
{
	int nWrite = 0;
	time_sync_t current_time;
	time_sync_t *pAuthVal = NULL;
    
	pAuthVal = (time_sync_t *)_pszRcvdReq;
	if((ntohl(pAuthVal->sync_info) % AuthPrime) > 0) {
		LOG( LERR, "<Time Request> AuthPrime value is not valid.[%d]", ntohl(pAuthVal->sync_info) % AuthPrime );
		return -2;
	}
    
	current_time.sync_info = htonl(time(NULL));
	nWrite = WriteN((char *)&current_time, sizeof(current_time));
	if(nWrite <= 0) {
		LOG( LERR, "<Time Request> Socket Write Error. (errmsg: %s) " , strerror(errno) );
		return -3;
	}

	return 1;
}

void *CInterfaceProcessor::_entry_func(void *arg) 
{
	CInterfaceProcessor* pObject = reinterpret_cast<CInterfaceProcessor *>(arg);
	pthread_detach(pthread_self());
	MsgHeader *msgHeader;
	int nResult;

	while ( 1 ) 
	{
		// 헤더 부분을 읽는다.
		// 단, 본 프로그램은 cc_timed와 cc_tsd 역할을 모두 수행하는데도 불구하고...
		// 가cc_timed 과거에 개발될때 Header가 명확히 정의되지 않고 개발되었다.
		// 읽기 요청했을 때 readsize가 

		// buffer size는 Header 만큼버퍼 생성
		char recvBuffer[sizeof(MsgHeader)];
		// 버퍼 초기화
		memset(recvBuffer, 0x00, sizeof(MsgHeader));

		// 2차 읽기 크기
		int nReadCnt = 0;
		// 1차 읽기 시도.
		int nRead = pObject->Read(recvBuffer, sizeof(MsgHeader));
		if( nRead == sizeof(time_sync_t) )
		{
			_LOG( LINF, "<Time Request> Client IP : %s.", pObject->m_szClientIP.c_str());

			//cc_timed의 요청이면...
			pObject->Respose_cctimed(recvBuffer);

			break;
		}
		else
		{
			if( nRead <= 0 )
			{
				//읽기 실패 또는 관제를 위한 연결로 간주하고 중단.
				// close() 처리함.
				break;
			}
			else
			{
				//cc_timed의 요청이 아니면..
				_LOG(LDBG, "Request of Service Info. Client IP : %s.", pObject->m_szClientIP.c_str());

				// MsgHeader를 추가로 읽을 필요가 있는지를 확인한다.
				if( nRead != sizeof(MsgHeader) )
				{
					
					// 2차 읽기 시도... 
					nReadCnt = pObject->ReadNTimeout((recvBuffer+nRead), (sizeof(MsgHeader) - nRead));

					// nRead  0: Socket Closed, -1: error , -2: Timeout 이므로
					if( nReadCnt <= 0 )
					{
						LOG(LERR, "Header read failed.");
						break;
					}
				}

				msgHeader = (MsgHeader* )&recvBuffer;
			}
		}

		// process body
		//
		nResult = pObject->CheckRequestMode(msgHeader);
		// 잘못된 요청일 경우
		if( nResult == -1 ) 
		{
			// 지원되지 않는 요청을 한 경우는 response를 줄 필요 없이 종료한다.
			break;
		}

		// 요청에 대한 응답 구조체를 생성한다.
		nResult = pObject->ProcessingJob(msgHeader);
		// 잘못된 요청일 경우
		if( nResult == -1 ) 
		{
			// 지원되지 않는 요청을 한 경우는 response를 줄 필요 없이 종료한다.
			break;
		}

		// 결과를 보내준다.
		// 내부에서 모두 만들어 줌.
		pObject->SendResult(nResult, msgHeader);
		break;
	}

	if(pObject)
	{
		delete pObject;
	}

	return 0;
}

int CInterfaceProcessor::Run()
{
	pthread_t workerthread = 0;

	// start thread
	int ret = pthread_create(&workerthread, 0,  _entry_func, (void*)this);
	if (ret != 0) 
	{
		ostringstream msg;		
		msg << "Client Thread create failed.[" << errno << "]";
		_LOG(LINF, "%s", msg.str().c_str());
		return -1;
	}

	sleep(0);

	return ret;
}

int CInterfaceProcessor::CheckRequestMode(MsgHeader *msgHeader)
{
	LOG(LDEV, "-----MsgHeader --------------");
	LOG(LDEV, "!m_msgHeader->m_szTID		: %s", msgHeader->m_szTID);
	LOG(LDEV, "!m_msgHeader->m_ulMode		: %x", ntohl(msgHeader->m_ulMode));
	LOG(LDEV, "!m_msgHeader->m_szIssuer		: %s", msgHeader->m_szIssuer);
	LOG(LDEV, "!m_msgHeader->m_szServiceID	: %s", msgHeader->m_szServiceID);
	LOG(LDEV, "!m_msgHeader->m_ulResult		: %d", ntohl(msgHeader->m_ulResult));
	LOG(LDEV, "!m_msgHeader->m_ulBodyLength	: %d", ntohl(msgHeader->m_ulBodyLength));
	LOG(LDEV, "!-----MsgHeader END-----------");

	switch(ntohl(msgHeader->m_ulMode)) 
	{
			case MODE_SRV_LST_2ND_REQUEST :
				return 0;
				break;
			case MODE_SRV_LST_REQUEST :
			case MODE_USER_DATA_MODIFIER_REQ :
			case MODE_APACHE_ERROR_LOG_REQ :
			case MODE_APACHE_ACCESS_LOG_REQ :
			case MODE_DEFAULT :
			case MODE_SRV_TRAFFIC_REQUEST :
				_LOG(LINF, "Currently, the protocol is not supported. code : %x Client IP : %s", ntohl(msgHeader->m_ulMode), m_szClientIP.c_str());
				break;
			default :
				_LOG(LINF, "Invalid request. Client IP : %s", m_szClientIP.c_str());
				break;
	}
	return -1;
}

bool CInterfaceProcessor::ParseUserInfo(const char *pszRcvd, user_auth_t *pUserAuthInfo)
{
	int nFlag = 0;
	int nElement = 0;
	char szTmpBuffer[52];
	if((pszRcvd == NULL) || (pUserAuthInfo == NULL)) {
		return false;
	}

	while(*pszRcvd != '\0') {
		if(*pszRcvd != CODE_ESC) {
			if(*pszRcvd != '\0') {
				szTmpBuffer[nFlag++] = *pszRcvd++;
			}
		} else {
			szTmpBuffer[nFlag] = '\0';
			if(nElement == 0) {
				::snprintf(pUserAuthInfo->szUserID, sizeof(pUserAuthInfo->szUserID), "%s", szTmpBuffer);
			} else if (nElement == 1) {
				::snprintf(pUserAuthInfo->szHashed, sizeof(pUserAuthInfo->szHashed), "%s", szTmpBuffer);
			} 
			::memset(szTmpBuffer, 0, 52);
			*pszRcvd++;
			nFlag = 0;	
			nElement++;
		}
	}
	szTmpBuffer[nFlag] = '\0';
	pUserAuthInfo->reqTime = (time_t)::atol(szTmpBuffer);
	return true;
}

int CInterfaceProcessor::ProcessingJob(MsgHeader *msgHeader)
{
	int nReadCnt = 0;
	// body의 내용은 user_auth_t 구조체의 내용들을 포함한다.
	user_auth_t stUserAuthInfo;
	char recvBuffer[MaxSizeOfRcvBuffer];
	char szUserVerInfo[MaxSizeOfRcvBuffer];
	char szHashedUserInfo[MaxSizeOfRcvBuffer];
	::memset(recvBuffer, 0, MaxSizeOfRcvBuffer);

	// 1. body 정보를 읽는다.
	nReadCnt = ReadNTimeout(recvBuffer, ntohl(msgHeader->m_ulBodyLength));

	// nRead  0: Socket Closed, -1: error , -2: Timeout 이므로
	if( nReadCnt <= 0 )
	{
		LOG(LERR, "Body read failed.");
		return -1;
	}

	if ( ParseUserInfo(recvBuffer, &stUserAuthInfo) == false )
	{
		LOG(LERR, "Body Parsing failed.");
		return -1;
	}

	LOG(LDEV, "-----stUserAuthInfo --------------");
	LOG(LDEV, "!sp_user_id		: %s", stUserAuthInfo.szUserID);
	LOG(LDEV, "!sp_hash_value	: %s (%d)", stUserAuthInfo.szHashed,(int) strlen(stUserAuthInfo.szHashed));
	LOG(LDEV, "!reqTime		: %ld", stUserAuthInfo.reqTime);
	LOG(LDEV, "!-----stUserAuthInfo END-----------");

	// 2. 고객 ID를 이용해 user_seq 값을 얻는다.
	CCustomerInfo objCustomerInfo;
	if( m_pobjServiceInfoUpdater->GetUserServiceData(stUserAuthInfo.szUserID, objCustomerInfo) == false )
	{
		LOG(LERR, "Not Found User's(ID:%s) Info: ", stUserAuthInfo.szUserID);
		return PROTO_FAILURE_AUTH;
	}
	// 사용자 아이디를 저장해둔다.
	m_szUserID = objCustomerInfo.m_szUserID;

	LOG(LDEV, "xxx666_m_szUserSeq :%s", objCustomerInfo.m_szUserSeq.c_str());
	LOG(LDEV, "xxx666_m_szUserID :%s", objCustomerInfo.m_szUserID.c_str());
	LOG(LDEV, "xxx666_m_szPassWord :%s", objCustomerInfo.m_szPassWord.c_str());

	// 3. hash 고객에 해당하는 패스워드를 가지고 md5 hash를 생성한다.
	::snprintf(szUserVerInfo, sizeof(szUserVerInfo), "%s%s%ld", stUserAuthInfo.szUserID
		                                                          , objCustomerInfo.m_szPassWord.c_str()
																  , stUserAuthInfo.reqTime);

	getHashedStr(szUserVerInfo, szHashedUserInfo);
	LOG(LDEV, "Hashed value11: %s", szHashedUserInfo);
	LOG(LDEV, "Hashed value22: %s", stUserAuthInfo.szHashed);

	// 4. 요청된 hash와 생성된 hash로 auth를 체크한다.
	if( strcmp(stUserAuthInfo.szHashed, szHashedUserInfo) != 0 )
	{
		LOG(LERR, "Do not match. Pass word.");
		return PROTO_FAILURE_AUTH;
	}
	// 5. 본 클래스의 멤버 변수인
	//    m_responseHeader와 m_responseBody를 생성한다.
	if( MakeResponseMessage(msgHeader, &objCustomerInfo.mapServiceInfo)  == false )
	{
		LOG(LERR, "Failed to create response messages.");
		return PROTO_INTERNAL_ERROR;
	}
	LOG(LDEV, "xxxxxxxx22");
	
	return PROTO_SUCCESS;
}

// 응답메세지의 구조는 아래와 같다.
// [서비스개수][서비스개수*sizeof(sp_service_info)]
// 버퍼는 위의 크기 +1 을 할당해야한다.
bool CInterfaceProcessor::MakeResponseMessage(MsgHeader *pmsgHeader, CServiceInfoMap *pmapServiceInfo)
{
	uint32_t nCount = 0;
	uint32_t nhtonl= 0;
	uint32_t nBodyPos = 0;
	// 1. body 생성을 한다.
	// 1-1. response body의 크기만큼 메모리할당을 한다.
	nCount = (uint32_t)pmapServiceInfo->size();
	m_nBodyLen = nCount * sizeof(sp_service_info) + sizeof(uint32_t) + 5;
	m_responseBody = new char[m_nBodyLen];
	LOG(LDEV, "xxxxxxxxxxxxxxxxxxxx11 xxxxx %d", m_nBodyLen);
	memset(m_responseBody, 0x00, nCount * sizeof(sp_service_info) + sizeof(uint32_t) + 5);

	// 1-2. 서비스 개수를 맨앞에 4바이트에 기록한다.
	nhtonl = htonl(nCount);
	LOG(LDEV, "Service Count: %d", nCount);
	memcpy(m_responseBody, (char *)&nhtonl, sizeof(uint32_t));
	nBodyPos += sizeof(uint32_t);

	for (CServiceInfoMap::iterator it=pmapServiceInfo->begin(); it!=pmapServiceInfo->end(); ++it)
	{
		LOG(LDEV, "ServiceSeq :%s", it->first.c_str());

		CServiceInfo objServiceInfo = it->second;
		LOG(LDBG, "m_szServiceID :%s", objServiceInfo.m_szServiceID.c_str());
		
		sp_service_info stServiceInfo;
		::memset(&stServiceInfo, 0x00, sizeof(sp_service_info));

		snprintf(stServiceInfo.sp_svc_id,  MaxSizeOfSvcID,  "%s", (const char *)objServiceInfo.m_szServiceID.c_str());
		snprintf(stServiceInfo.svc_rcts_url,  MaxSizeOfRCURL,  "%s", (const char *)objServiceInfo.m_szRctsDomain.c_str());
		if( strcmp(pmsgHeader->m_szIssuer, ISSUER_SP_CONSOLE) == 0 || strcmp(pmsgHeader->m_szIssuer, ISSUER_SP_CONSOLE3) == 0 )
		{
			snprintf(stServiceInfo.vol_url,  MaxSizeOfRCURL,  "%s", (const char *)objServiceInfo.m_szRcDomain.c_str());	
		}
		else
		{
			snprintf(stServiceInfo.vol_url,  MaxSizeOfRCURL,  "%s", (const char *)objServiceInfo.m_szServiceDomain.c_str());
		}

		LOG(LDEV, "ServiceID :%s", stServiceInfo.sp_svc_id);
		LOG(LDEV, "rcts domain :%s", stServiceInfo.svc_rcts_url);
		LOG(LDEV, "Volume(rc) domain :%s", stServiceInfo.vol_url);
		// 1-3. body에 추가한 후 
		memcpy((m_responseBody + nBodyPos), (char *)&stServiceInfo, sizeof(sp_service_info));
		nBodyPos += sizeof(sp_service_info);
	}
	return true;
}

void CInterfaceProcessor::SendResult(int nCode, MsgHeader *pmsgHeader)
{
	//nCode가 성공이 아니면 에러코드를 넣어서 결과를 주고..
	// 성공이면 body를 만들어서 헤더와 함께 response를 준다.

	// cc_tsd response를 위한 헤더 구조체
	MsgHeader responseHeader;

	// 2. header 생성을 한다.
	::snprintf(responseHeader.m_szTID, MaxSizeOfTID, "%s", pmsgHeader->m_szTID);
	responseHeader.m_ulMode = htonl(MODE_SRV_LST_2ND_RESPONSE);
	::snprintf(responseHeader.m_szIssuer, MaxSizeOfIssuer, "%s", ISSUER_SERVER);
	::snprintf(responseHeader.m_szServiceID, MaxSizeOfServiceID, "%s", pmsgHeader->m_szServiceID);
	responseHeader.m_ulResult = htonl(nCode);
	if( nCode == PROTO_SUCCESS )
		responseHeader.m_ulBodyLength = htonl(m_nBodyLen);
	//	responseHeader.m_ulBodyLength = htonl(sizeof(m_responseBody));
	else
		responseHeader.m_ulBodyLength = htonl(0);
	if( WriteN(&responseHeader, sizeof(MsgHeader)) != sizeof(MsgHeader) )
	{
		LOG( LERR, "Header data write failed.");
		return;
	}

	// 성공이 아니면 body를 보낼필요가 없다.
	if( nCode != PROTO_SUCCESS )
	{
		return;
	}
	
	// 3. header와 body를 socket wirte한다.
	if( WriteN(m_responseBody, m_nBodyLen) != m_nBodyLen )
	{
		LOG( LERR, "Body data write failed.");
		return;
	}
	if ( m_responseBody != NULL )
	{
		delete m_responseBody;
		m_responseBody = NULL;
	}

	_LOG( LDBG, "Send Response Message.");

	_LOG( LINF, "ISSUER : %s, Client IP : %s",pmsgHeader->m_szIssuer, m_szClientIP.c_str());

	// LOGIN_INFO_INSERT 기능이 on인 경우만 queue에 등록한다.
	if( CMyConfig::GetInstance()->GetLoginInfo() == false)
	{
		return;
	}
	// 4. Login info 를 생성한다.
	CLoginInfo objLoginInfo;
	objLoginInfo.m_szUserID = m_szUserID;
	objLoginInfo.m_szIssuer = pmsgHeader->m_szIssuer;
	objLoginInfo.m_szClientIP = m_szClientIP;
	// 현재 시각 설정 
	char szTime[40];
	time_t ltime = time(0);;

	struct tm *pTm = NULL;
	pTm = ::localtime((time_t *)&ltime);
	::memset(szTime, 0, sizeof(szTime));
	::snprintf(szTime, 40, "%4.4d-%2.2d-%2.2d %2.2d:%2.2d:%2.2d",
						pTm->tm_year+1900, pTm->tm_mon +1, pTm->tm_mday,
						pTm->tm_hour,  pTm->tm_min,  pTm->tm_sec);

	LOG( LDEV, "m_szUserID : %s", objLoginInfo.m_szUserID.c_str());
	LOG( LDEV, "m_szIssuer : %s", objLoginInfo.m_szIssuer.c_str());
	LOG( LDEV, "m_szClientIP : %s", objLoginInfo.m_szClientIP.c_str());
	LOG( LDEV, "time : %s", szTime);

	objLoginInfo.m_szLoginDate = szTime;

	m_pQueueLoginInfo->Push(objLoginInfo);
}
