/***************************************************************************
                   FHS Control Class (FhsControl.cpp)
                -----------------------------------------

	begin      : 2013/02/26
	copyright  : (C) 2013 Solbox Inc.
	author     : Development 1 Team
					- 2013/02/26 - 1st dadamin
	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 "rc_mngd.h"
#include "JobData.h"
#include "FhsControl.h"
#include "Protocol.h"
#include "UtilFn.h"
#include "Logger.h"

#define PRODUCT_ID_LEN			64
#define MaxSizeOfSID			128
#define OPENDAV_URI_PATH_SIZE	1024
//#define URI_PATH_SIZE			2048

CFhsControl::CFhsControl()
	: m_broadcastfd(-1), m_bcreated(false)
{
	bzero(&m_broadcastAddr,sizeof(m_broadcastAddr));
}

CFhsControl::~CFhsControl()
{
	if (m_broadcastfd > 0)
	{
		::close(m_broadcastfd);
	}
}

unsigned int CFhsControl::ConversionAddr( const char * name )
{
	unsigned int retval;
	struct addrinfo hints, *result;
	int error;

	// 전달받은 정보가 잘못된 경우
	if( (retval = inet_addr(name)) != INADDR_NONE )
		return retval;

	// getaddrinfo() 함수 호출을 위한 Hint 설정
	memset( &hints, 0x00, sizeof(hints));
	hints.ai_family = PF_UNSPEC;
	hints.ai_socktype = SOCK_STREAM;

	// Thread Safe 한 DNS Resolve 처리함수 호출
	// int getaddrinfo(const char *hostname, const char *servname, const struct addrinfo *hints, struct addrinfo **res);
	error = getaddrinfo( name, NULL, &hints, &result );

	// 오류 발생시
	if( error != 0 )
	{
		LOG( LERR, "name[%s] dns resolve fail. getaddrinfo return error [%d][%s]", name, error, gai_strerror(error) );
		return INADDR_NONE;
	}

	struct sockaddr_in * addr = (struct sockaddr_in *)result->ai_addr;
	retval = (unsigned int)(addr->sin_addr.s_addr);

	// DNS Resovle 결과 확인용 코드
	/*
	struct addrinfo *temp;
	for( temp = result; temp; temp = temp->ai_next )
	{
		addr = (struct sockaddr_in *)temp->ai_addr;
		printf ("getaddrinfo result = %s\n",inet_ntoa( addr->sin_addr));
	}
	*/

	// getaddrinfo() 함수에서 생성한 메모리 영역 해제 처리.
	freeaddrinfo(result);

	return retval;

}

bool CFhsControl::Create(const char* rcid, const char * IP, int Port, bool isBroadcast)
{
	bool r = true;

	if( rcid == NULL || strlen(rcid) <= 0 )
	{
		LOG( LERR, "FHS Control Create failed: RCID empty" );
		return false;
	}

	m_rcid = rcid;
	
	if( isBroadcast )
	{
		// case broadcast

		unsigned int hostaddr = ConversionAddr(IP);
		
		if(hostaddr == INADDR_NONE)
			return false;

		m_broadcastAddr.sin_family=AF_INET;
		m_broadcastAddr.sin_addr.s_addr= hostaddr;
		m_broadcastAddr.sin_port=htons(Port);

		m_broadcastfd = ::socket(AF_INET, SOCK_DGRAM, 0);
		if(m_broadcastfd < 0) 
		{
			LOG( LERR, "FHS Control Create failed: %s", strerror(errno) );
			return false;
		}

		int on = 1;
		if (::setsockopt(m_broadcastfd, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on)) < 0) 
		{
			LOG( LERR,  "FHS Control Create failed::BROADCAST: %s", strerror(errno) );
			return false;
		}

		r = true;
	}
	else 
	{
		// case ftsd
		r = m_ftsd.ConnectTarget(string(IP), Port);
	}	
	
	m_bcreated = r;
	return r;
}

bool CFhsControl::IsCreated()
{
	if (!m_bcreated)
	{
		LOG(LERR, "FHS Control not be initialized.");
		return false;
	}

	return true;
}

bool CFhsControl::MkServiceRootDir(CJobDataMap & jobData)
{
	bool r = true;
	
	if (!IsCreated())
	{
		return false;
	}

	if(jobData[KEY_SP_USER_SEQ].empty())
	{
		LOG( LERR,  "User number empty." );
		return false;
	}

	if(jobData[KEY_SP_SVC_TRAN_ID].empty())
	{
		LOG( LERR,  "Service number empty." );
		return false;
	}

	struct tm newtime;
	time_t ltime;
	ltime=time(&ltime);
	localtime_r(&ltime, &newtime);

	MsgHeaderBoardCast header;
	
	snprintf(header.szTransactionID, MaxSizeOfTID, "%2.2d%2.2d%2.2d%2.2d%2.2d%2.2d-0-0-0"
									, newtime.tm_year+1900, newtime.tm_mon+1
									, newtime.tm_mday, newtime.tm_hour
									, newtime.tm_min, newtime.tm_sec);	

	header.ulMode = htonl(BROADCAST_MKDIR_SERVICE_ROOT);
	snprintf(header.szIssuer, MaxSizeOfIssuer, "%s", PROG_NAME);
	snprintf(header.szServiceID, (PRODUCT_ID_LEN - 1), "%s", jobData[KEY_SP_USER_SEQ].c_str() );
	snprintf((header.szServiceID + PRODUCT_ID_LEN),	(PRODUCT_ID_LEN - 1), "%s", jobData[KEY_SP_SVC_TRAN_ID].c_str());
	snprintf(header.szRCID, MaxSizeOfRCID, "%s", m_rcid.c_str());
	header.ulResult = htonl(OK_ALL_PASSED);
	header.ulBodyLength = htonl(0);
	
	size_t len	= sizeof(header);
	socklen_t tolen = sizeof( m_broadcastAddr );
	int nWrite = sendto( m_broadcastfd, (char *) &header, len, 0x00, (struct sockaddr *)&m_broadcastAddr, tolen );

	if( nWrite < 0 ) 
	{
		LOG( LERR, "Send Message(Service Root Make Request) failed[%s] : "
				"rcid=%s,user_seq=%s,tran_id=%s", 
				strerror(errno),m_rcid.c_str(), 
				jobData[KEY_SP_USER_SEQ].c_str(),
				jobData[KEY_SP_SVC_TRAN_ID].c_str());
		return false;
	}

	return r;
}

bool CFhsControl::SendAnonyDir(CJobDataMap & jobData, string targeturi)
{
	bool r = true;

	if (!IsCreated())
	{
		return false;
	}

	if(jobData[KEY_SP_SVC_ID].empty())
	{
		LOG( LERR,  "Service ID empty." );
		return false;
	}

	uint32_t ulMode = 0;
	if(jobData[KEY_JOBSUB_TYPE].compare(JOB_ANONY_DIR_SET) == 0)
	{
		ulMode = BROADCAST_ANONY_SET;
	}
	else if(jobData[KEY_JOBSUB_TYPE].compare(JOB_ANONY_DIR_UNSET) == 0)
	{
		ulMode = BROADCAST_ANONY_UNSET;
	}
	else
	{
		LOG( LERR,  "Unknown Job Type[%s].", jobData[KEY_JOBSUB_TYPE].c_str() );
		return false;
	}

	MsgHeaderBoardCast header;

	struct tm newtime;
	time_t ltime;
	ltime=time(&ltime);
	localtime_r(&ltime, &newtime);

	snprintf(header.szTransactionID, MaxSizeOfTID, "%2.2d%2.2d%2.2d%2.2d%2.2d%2.2d-0-0-0"
		, newtime.tm_year+1900, newtime.tm_mon+1
		, newtime.tm_mday, newtime.tm_hour
		, newtime.tm_min, newtime.tm_sec);

	header.ulMode = htonl(ulMode);

	snprintf(header.szServiceID, MaxSizeOfSID - 1, "%s", jobData[KEY_SP_SVC_ID].c_str() );
	snprintf(header.szRCID, MaxSizeOfRCID, "%s", m_rcid.c_str());
	header.ulResult = htonl(OK_ALL_PASSED);
	header.ulBodyLength = htonl(OPENDAV_URI_PATH_SIZE);

	char szPath[OPENDAV_URI_PATH_SIZE] = {0}; 
	snprintf(szPath, OPENDAV_URI_PATH_SIZE - 1, "%s", targeturi.c_str());

	size_t len = sizeof header + OPENDAV_URI_PATH_SIZE;
	char *psSendBuf = NULL;
	psSendBuf = new char[len];

	if(psSendBuf != NULL)
	{	
		memcpy(psSendBuf, (char *)&header, sizeof header);
		memcpy((psSendBuf + sizeof header), szPath, OPENDAV_URI_PATH_SIZE);

		socklen_t tolen = sizeof( m_broadcastAddr );
		int nWrite = sendto( m_broadcastfd, (char *) psSendBuf, len, 0x00, 
						(struct sockaddr *)&m_broadcastAddr, tolen );

		delete [] psSendBuf;
		if( nWrite < 0 ) 
		{
			LOG( LERR, "Send Message(SendAnonyDir) failed[%s] : "
					"rcid=%s,user_seq=%s,tran_id=%s,uri =%s", 
					strerror(errno),m_rcid.c_str(), 
					jobData[KEY_SP_USER_SEQ].c_str(),
					jobData[KEY_SP_SVC_TRAN_ID].c_str(), szPath);
			return false;
		}
	}
	else 
	{
		LOG( LERR, "Memory allocation failure.[%d]", errno);
		r = false;
	}

	return r;
}


bool CFhsControl::GetFront10MMD5(CJobDataMap & jobData)
{
	bool r = true;

	if (!IsCreated())
	{
		return false;
	}

	unsigned long long nHashChkSize = atoll(jobData[KEY_REQ_HASH_CHECK_SIZE].c_str());

	if( m_ftsd.SendFileCheckRequest( jobData[KEY_FILENAME_HASH], true, nHashChkSize ) == false )
	{
		// Source ftsd 로 전송 실패시
		LOG( LERR, "File Hash Check Request send fail.[filename_hash:%s]", jobData["filename_hash"].c_str());
		return false;
	}

	int nTimeout = 5; // 응답 대기 시간 ( sec )
	int nResult = 0;

	bool bResultSuccess;
	string szErrorMessage;
	string szHashValue;
	unsigned long long nFileSize = 0;
	

	// 루프를 돌면서 요청에 대한 응답을 대기
	while(1)
	{
		// nTimeout 에 지정된 시간동안 응답대기 시도
		// GetFileCheckResult() 함수는 SocketControl.h 파일 참고.
		nResult = m_ftsd.GetFileCheckResult( nTimeout, bResultSuccess, szErrorMessage, nFileSize, szHashValue );

		if( nResult == -1 )
		{			
			// Socket 통신 관련 오류 또는 접속 종료가 발생한 경우.
			// 해당 내역 로깅 및 루프 종료	
			r = false;
			LOG( LERR, "File Hash Check Response wait fail by socket.[%s:%s]"
					,KEY_FILENAME_HASH, jobData[KEY_FILENAME_HASH].c_str());
			break;

		}
		else if( nResult == 0 )
		{
			// 지정된 시간 동안 응답대기 중 처리 결과 정보가 아직 수신되지 않은 경우 => Allive Check 패킷 한번 쏘고 다시 Loop 로

			if( m_ftsd.SendAliveCheck() == false )
			{
				// Alive 전송 실패시 => Socket 종료 및 오류가 발생한 경우임.
				r = false;
				LOG( LERR, "File Hash Check Response wait fail by socket2.[%s:%s]"
							, KEY_FILENAME_HASH, jobData[KEY_FILENAME_HASH].c_str());
				break;
			}

			// 정상적인 경우 다시 응답대기.
			continue;
		}
		else if( nResult == 1 )
		{
			// 요청에 대한 처리결과 정보가 수신된 경우.
			// 해당 정보 로깅처리.

			if( bResultSuccess == true )
			{
				r = true;
				ostringstream strfilesize;
				strfilesize << nFileSize;
				
				jobData[KEY_RES_FILE_SIZE] = strfilesize.str();
				jobData[KEY_RES_FILE_FRONT_10M_MD5] = szHashValue;
				
				_LOG(LDBG, "File Front 10MB MD5 : %s", jobData[KEY_RES_FILE_FRONT_10M_MD5].c_str());
			}
			else
			{
				// 오류 발생시 
				r = false;
				LOG( LERR, "File Hash Check Response error.[%s][%s:%s]",
					szErrorMessage.c_str(), KEY_FILENAME_HASH,jobData[KEY_FILENAME_HASH].c_str());

			}			

			break;	// 응답을 받았으니 응답 대기 루프 종료
		}
		else
		{
			// Replication 요청에 대한 응답패킷이 아닌 경우.
			// 해당 패킷은 무시하고 다시 Loop 로 
			continue;
		}
	}

	return r;
}

bool CFhsControl::GetTotoExtractWithplatform(CJobDataMap & jobData)
{
	bool r = true;

	if (!IsCreated())
	{
		return false;
	}

	if (m_ftsd.SendFileCheckTOTORequest(jobData[KEY_FILENAME_HASH]) == false)
	{
		// Source ftsd 로 전송 실패시
		LOG(LERR, "File Hash Check Request send fail.[filename_hash:%s]", jobData["filename_hash"].c_str());
		return false;
	}

	int nTimeout = 5; // 응답 대기 시간 ( sec )
	int nResult = 0;

	bool bResultSuccess;
	string szErrorMessage;

	// File 추출값 저장 변수
	string szTotoMd5;
	string szChecksum;
	string szDnaCheckKey;	
	unsigned long long nFileSize = 0;

	int ntry = 0;

	// 루프를 돌면서 요청에 대한 응답을 대기
	while (1)
	{
		// nTimeout 에 지정된 시간동안 응답대기 시도
		// GetFileCheckResult() 함수는 SocketControl.h 파일 참고.
		nResult = m_ftsd.GetFileCheckTOTOResult(nTimeout, bResultSuccess, szErrorMessage, nFileSize, szTotoMd5, szChecksum, szDnaCheckKey);

		if (nResult == -1)
		{
			// Socket 통신 관련 오류 또는 접속 종료가 발생한 경우.
			// 해당 내역 로깅 및 루프 종료	
			r = false;
			LOG(LERR, "File Hash Check(Totodisk) Response wait fail by socket.[%s:%s]"
				, KEY_FILENAME_HASH, jobData[KEY_FILENAME_HASH].c_str());
			break;

		}
		else if (nResult == 0)
		{
			// 지정된 시간 동안 응답대기 중 처리 결과 정보가 아직 수신되지 않은 경우 => Allive Check 패킷 한번 쏘고 다시 Loop 로

			if (m_ftsd.SendAliveCheck() == false)
			{
				// Alive 전송 실패시 => Socket 종료 및 오류가 발생한 경우임.
				r = false;
				LOG(LERR, "File Hash Check(Totodisk) Response wait fail by socket2.[%s:%s]"
					, KEY_FILENAME_HASH, jobData[KEY_FILENAME_HASH].c_str());
				break;
			}

			if (++ntry > 3)
			{
				// 3회 재시도 응답 없을 경우 실패처리
				r = false;
				LOG(LERR, "File Hash Check(Totodisk) Response wait timeout.[%s:%s]"
					, KEY_FILENAME_HASH, jobData[KEY_FILENAME_HASH].c_str());
				break;
			}
			// 정상적인 경우 다시 응답대기.
			continue;
		}
		else if (nResult == 1)
		{
			// 요청에 대한 처리결과 정보가 수신된 경우.
			// 해당 정보 로깅처리.

			if (bResultSuccess == true)
			{
				r = true;
				
				ostringstream strfilesize;
				strfilesize << nFileSize;

				jobData[KEY_RES_FILE_SIZE] = strfilesize.str();
				jobData[KEY_RES_FILE_TOTO_MD5] = szTotoMd5;
				jobData[KEY_RES_FILE_TOTO_CHECKSUM] = szChecksum;
				jobData[KEY_RES_FILE_TOTO_DNAKEY] = szDnaCheckKey;

				_LOG(LINF, "TOTO Result file [%s][%s]", jobData[KEY_REQ_URI].c_str(), jobData[KEY_FILENAME_HASH].c_str());
				_LOG(LINF, "TOTO Result FHS      [%s]", jobData[KEY_HOST_NAME].c_str());
				_LOG(LINF, "TOTO Result size     [%s]", jobData[KEY_RES_FILE_SIZE].c_str());
				_LOG(LINF, "TOTO Result md5 hash [%s]", jobData[KEY_RES_FILE_TOTO_MD5].c_str());
				_LOG(LINF, "TOTO Result checksum [%s]", jobData[KEY_RES_FILE_TOTO_CHECKSUM].c_str());
				_LOG(LINF, "TOTO Result dna key  [%s]", jobData[KEY_RES_FILE_TOTO_DNAKEY].c_str());

			}
			else
			{
				// 오류 발생시 
				r = false;
				LOG(LERR, "File Hash Check(Totodisk) Response error.[%s][%s:%s]",
					szErrorMessage.c_str(), KEY_FILENAME_HASH, jobData[KEY_FILENAME_HASH].c_str());

			}

			break;	// 응답을 받았으니 응답 대기 루프 종료
		}
		else
		{
			// Replication 요청에 대한 응답패킷이 아닌 경우.
			// 해당 패킷은 무시하고 다시 Loop 로 
			continue;
		}
	}

	return r;
}

