#include "ProcessSocketControl.h"

#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <sys/sendfile.h>


// 본 Class 에서 내부적으로 사용할 Packet 수신 결과 매크로 정의
// 본 정의값은 GetPacketHeader() 함수의 결과값임.
#define PACKET_HEADER_RECEIVE_SUCCESS			 1	// Packet Header 정보 수신 정상 처리
#define PACKET_HEADER_RECEIVE_TIMEOUT			 0	// Packet Header 정보 수신시 Timeout 발생한 경우
#define PACKET_HEADER_RECEIVE_SOCK_CLOSED		-1	// Packet Header 정보 수신시 socket 이 Close 된 경우
#define PACKET_HEADER_RECEIVE_INVALID_PROTOCOL	-2	// Packet Header 정보 수신시 잘못된 Packet 형식인 경우
#define PACKET_HEADER_RECEIVE_READ_ERROR		-3	// Packet Header 정보 수신시 시스템 함수 오류 발생한 경우


// 생성자.
CProcessSocketControl::CProcessSocketControl()
	: CBaseSocket( SOCKET_NOT_VALID )
	  , m_nPacketHeaderLen ( sizeof(m_packetHeader))
	  , m_nPacketDataLen( 0 )
{
}

// 생성자.
// socket [in] 처리할 socket descriptor
CProcessSocketControl::CProcessSocketControl( const int& socket )
	: CBaseSocket( socket )
	  , m_nPacketHeaderLen ( sizeof(m_packetHeader))
	  , m_nPacketDataLen( 0 )
{
}

// 소멸자
CProcessSocketControl::~CProcessSocketControl()
{
	// 소멸자 Socket 명시적 Close 처리.
	Close();
}

// 전달받은 Target 으로 Socket 접속을 수행
// @param szTarget [in] 접속 대상 Host name 또는 IP
// @param nPort	[in] 접속 Port
// @return 접속 성공시 true, 실패시 false 반환. ( 오류 내역은 로깅처리됨.)
bool CProcessSocketControl::ConnectTarget( const std::string& szTarget, int nPort )
{
	return Connect( szTarget, nPort );
}

// Packet 의 Header 정보를 수신하여 각 정보를 전달받은 참조변수로 반환.
// @param bRequest [out] Request 요청인지 여부
// @param bFromClient [out] Client 로 부터 전달된 Command 인지 여부
// @param command [out] Command 정보
// @return 1 : Data 수신 성공시
//         0 : Timeout 발생시
//        -1 : 접속 종료시
int CProcessSocketControl::GetHeaderInfo( bool& bRequest, bool& bFromClient, char& command )
{
	if( IsValidSocket() == false )
		return -1;

	// Packet Header 정보 수신처리.
	// Socket 응답에 대한 대기상태( select) 에서 응답이 들어온 경우 이를 바로 처리하기 위해
	// Timeout 시간을 Default 에서 3 sec 로 변경처리함.
	int nResult = GetPacketHeader(3);

	// Header Data 수신 실패시
	if( nResult != PACKET_HEADER_RECEIVE_SUCCESS )
	{
		if( nResult == PACKET_HEADER_RECEIVE_TIMEOUT )	// Timeout 발생시
		{
			if( IsValidSocket() == false )	// socket 종료 여부를 다시 한번 확인
				return -1;
			else
				return 0;
		}
		else
		{
			// 잘못된 프로토콜 형식인 경우
			if( nResult == PACKET_HEADER_RECEIVE_INVALID_PROTOCOL )
			{
				// Socket 강제 종료 처리. 
				LOG( LERR, "Not valid protocol header. Connection closed by forced.");
				Close();
			}

			return -1;
		}
	}

	// Data 수신 성공시
	// Type 값 확인
	if( m_packetHeader.type == HEADER_TYPE_REQUEST )
		bRequest = true;

	// Client 으로 부터 전송된 패킷인 경우.	
	if( m_packetHeader.command[0] != NOT_CLIENT_COMMAND )
	{
		bFromClient = true;
		command = m_packetHeader.command[0];
	}
	else
	{
		// 내부 Process 간 통신인 경우.
		bFromClient = false;
		command = m_packetHeader.command[1];
	}

	return 1;
}

// Packet 의 Header 정보 중 현재 전달받은 Header 의 Result 값을 반환
// @param result [out] result 4 Byte 부분의 값을 저장하기 위한 참조변수
// @return result 4 Byte 부분 중 첫번째 Byte 값을 반환.
char CProcessSocketControl::GetHeaderResult()
{
	return m_packetHeader.result[0];
}

void CProcessSocketControl::GetHeaderResult( std::vector<char>& result )
{
	result.clear();
	result.push_back( m_packetHeader.result[0] );
	result.push_back( m_packetHeader.result[1] );
	result.push_back( m_packetHeader.result[2] );
	result.push_back( m_packetHeader.result[3] );
}


// Packet Header 정보를 Log 파일에 Logging 처리
void CProcessSocketControl::PrintHeaderToLog()
{
	_LOG( LINF, "------------------------------------" );
	_LOG( LINF, "stx         [%02x]", m_packetHeader.stx );
	_LOG( LINF, "type        [%02x]", m_packetHeader.type );
	_LOG( LINF, "command     [%02x][%02x][%02x][%02x]", m_packetHeader.command[0], m_packetHeader.command[1], m_packetHeader.command[2], m_packetHeader.command[3] );
	_LOG( LINF, "result      [%02x][%02x][%02x][%02x]", m_packetHeader.result[0], m_packetHeader.result[1], m_packetHeader.result[2], m_packetHeader.result[3] );
	_LOG( LINF, "data_length [%u]", m_nPacketDataLen );
	_LOG( LINF, "extend_code [%02x][%02x]", m_packetHeader.extend_code[0], m_packetHeader.extend_code[1] );
	_LOG( LINF, "------------------------------------" );
}


// Alive Check 를 위한 패킷 전송 => 해당 패킷에 대한 응답은 필요없으므로 수신시 무시할 것.
// @return 전송 성공시 true, 실패시 false 반환.
bool CProcessSocketControl::SendAliveCheck()
{
	if( IsValidSocket() == false )
		return false;

	// 정보 요청을 위한 Packet Header 생성.
	struct RcSyncdPacketHeader stHeader;
	memset( &stHeader, 0x00, sizeof( struct RcSyncdPacketHeader ) );

	stHeader.stx        = HEADER_STX_CODE;
	stHeader.type       = HEADER_TYPE_REQUEST;
	stHeader.command[0] = COMMON_ALIVE_CHECK;

	// Packet Header 정보 전송
	if( WriteN( &stHeader, m_nPacketHeaderLen ) != m_nPacketHeaderLen )
		return false;

	return true;
}

// Client 로 부터 수신된 Sync 요청 Data body 수신 처리.
bool CProcessSocketControl::RecvBodySyncFromClient( CReqMasterData& request )
{
	if( IsValidSocket() == false )
		return false;

	// Body 부분 수신 처리.	
	unsigned int nLength;
	
	// sync_type
	if( GetPacketData( 4 ) == false )
	{
		LOG( LERR, "sync_type get failed." );
		return false;
	}
	request.sync_type = (SYNC_TYPE::SYNC_TYPE)GetDataToUInt( m_tempBuffer, true );
	
	// start_time
	if( GetPacketData( 4 ) == false )
	{
		LOG( LERR, "start_time size get failed." );
		return false;
	}
	nLength = GetDataToUInt( m_tempBuffer, true );
	GetPacketData( nLength, request.start_time );
		
	// end_time
	if( GetPacketData( 4 ) == false )
	{
		LOG( LERR, "end_time size get failed." );
		return false;
	}
	nLength = GetDataToUInt( m_tempBuffer, true );
	GetPacketData( nLength, request.end_time );
	
	// one_service
	if( GetPacketData( 4 ) == false )
	{
		LOG( LERR, "one_service size get failed." );
		return false;
	}
	nLength = GetDataToUInt( m_tempBuffer, true );
	GetPacketData( nLength, request.one_service );
		
	return true;
}

// Client 에게 Sync 요청에 대한 결과 정보 전송 처리
bool CProcessSocketControl::SendResultSyncToClient( bool bSuccess, std::string& strMessage )
{
	if( IsValidSocket() == false )
		return false;

	// 정보 전달을 위한 Packet Header 생성.
	struct RcSyncdPacketHeader stHeader;
	memset( &stHeader, 0x00, sizeof( struct RcSyncdPacketHeader ) );

	stHeader.stx        = HEADER_STX_CODE;
	stHeader.type       = HEADER_TYPE_RESPONSE;
	stHeader.command[0] = CLIENT_COMMAND_SYNC;

	// 성공, 실패 여부 설정
	if( bSuccess == true )
		stHeader.result[0] = HEADER_RESULT_SUCCESS;	
	else
		stHeader.result[0] = HEADER_RESULT_ERROR;

	// Data body length
	stHeader.data_length = htonl( strMessage.size() );
	
	// Packet Header 정보 전송
	if( WriteN( &stHeader, m_nPacketHeaderLen ) != m_nPacketHeaderLen )
	{
		LOG( LERR, "reponse [CLIENT_COMMAND_SYNC] send fail to client" );
		return false;
	}
	else
	{
		// message 부분이 존재하는 경우.. 전송 처리.
		if( strMessage.empty() == false )
		{
			WriteN( strMessage.c_str(), strMessage.size() );
		}
	}

	return true;
}


// [Master Mode] Slave rc_syncd 로 content 정보 목록 요청.
bool CProcessSocketControl::SendRequestContentListToSlave( CReqSlaveData& data )
{
	if( IsValidSocket() == false )
		return false;
	
	// 정보 전달을 위한 Packet Header 생성.
	struct RcSyncdPacketHeader stHeader;
	memset(&stHeader, 0x00, sizeof(struct RcSyncdPacketHeader));

	stHeader.stx		= HEADER_STX_CODE;
	stHeader.type		= HEADER_TYPE_REQUEST;
	stHeader.command[0] = NOT_CLIENT_COMMAND;
	stHeader.command[1] = PROCESS_CONTENT_LIST;

	// Data 부분
	// sync_type  : int  (network_byte order)
	// start_time : string ( 4 + string.size() )
	// end_time   : string ( 4 + string.size() )
	// master     : string ( 4 + string.size() )
	// slave      : string ( 4 + string.size() )

	// 전체 크기 계산.
	unsigned int nTemp = 0;
	nTemp = 4;                               // sync_type
	nTemp += ( 4 + data.start_time.size() ); // start_time
	nTemp += ( 4 + data.end_time.size() );   // end_time
	nTemp += ( 4 + data.master.size() );     // master
	nTemp += ( 4 + data.slave.size() );      // slave

	stHeader.data_length = htonl( nTemp );

	// Packet Header 정보 전송
	if( WriteN( &stHeader, m_nPacketHeaderLen ) != m_nPacketHeaderLen )
	{
		LOG( LERR, "request[PROCESS_CONTENT_LIST] send fail to slave rc_syncd" );
		return false;
	}
	else
	{
		// Data 부분 전송

		// 1. sync_type
		nTemp = htonl( data.sync_type );
		WriteN( &nTemp, 4 );
		// 2. start_time
		SendString( data.start_time );
		// 3. end_time
		SendString( data.end_time );
		// 4. master
		SendString( data.master );
		// 5. slave
		SendString( data.slave );
	}

	return true;	
}

// Master rc_syncd 에서 전송한 Content 목록 요청의 Data body 수신 처리.
bool CProcessSocketControl::RecvBodyContentListFromMaster( CReqSlaveData& request )
{
	if( IsValidSocket() == false )
		return false;

	// Body 부분 수신 처리.
	unsigned int nLength;

	// sync_type
	if( GetPacketData( 4 ) == false )
	{
		LOG( LERR, "sync_type get failed." );
		return false;
	}
	request.sync_type = ( SYNC_TYPE::SYNC_TYPE )GetDataToUInt( m_tempBuffer, true );

	// start_time
	if( GetPacketData( 4 ) == false )
	{
		LOG( LERR, "start_time size get failed." );
		return false;
	}
	nLength = GetDataToUInt( m_tempBuffer, true );
	GetPacketData( nLength, request.start_time );
	
	// end_time
	if( GetPacketData( 4 ) == false )
	{
		LOG( LERR, "end_time size get failed." );
		return false;
	}
	nLength = GetDataToUInt( m_tempBuffer, true );
	GetPacketData( nLength, request.end_time );

	// master
	if( GetPacketData( 4 ) == false )
	{
		LOG( LERR, "master size get failed." );
		return false;
	}
	nLength = GetDataToUInt( m_tempBuffer, true );
	GetPacketData( nLength, request.master );

	// slave
	if( GetPacketData( 4 ) == false )
	{
		LOG( LERR, "slave size get failed." );
		return false;
	}
	nLength = GetDataToUInt( m_tempBuffer, true );
	GetPacketData( nLength, request.slave );
	
	return true;
}

// [Slave] Content 정보 목록 추출 실패시 Master rc_syncd 로 관련 오류 내역을 전달한다.
bool CProcessSocketControl::SendErrorContentListToMaster( std::string& strMessage )
{
	if( IsValidSocket() == false )
		return false;

	// 정보 전달을 위한 Packet Header 생성.
	struct RcSyncdPacketHeader stHeader;
	memset( &stHeader, 0x00, sizeof( struct RcSyncdPacketHeader ) );

	stHeader.stx = HEADER_STX_CODE;
	stHeader.type = HEADER_TYPE_RESPONSE;
	stHeader.command[0] = NOT_CLIENT_COMMAND;
	stHeader.command[1] = PROCESS_CONTENT_LIST;
	stHeader.result[0] = HEADER_RESULT_ERROR;

	// Data body length
	stHeader.data_length = htonl( strMessage.size() );

	// Packet Header 정보 전송
	if( WriteN( &stHeader, m_nPacketHeaderLen ) != m_nPacketHeaderLen )
	{
		LOG( LERR, "reponse [PROCESS_CONTENT_LIST] send fail to master" );
		return false;
	}
	else
	{
		// message 부분이 존재하는 경우.. 전송 처리.
		if( strMessage.empty() == false )
		{
			WriteN( strMessage.c_str(), strMessage.size() );
		}
	}

	return true;
}

// [Slave] Master rc_syncd 로 추출된 Content 정보 목록을 전달한다.
bool CProcessSocketControl::SendResultContentListToMaster( int fd, unsigned long long nFileSize )
{
	if( IsValidSocket() == false )
		return false;

	// 본 Packet 은 Header + 파일 Data 로 구성해야 하지만...
	// File Size 가 엄청 클 수가 있으므로.. packet header 의 data_length 항목에 저장이 불가능.
	// 따라서 다음과 같이 처리한다.
	// PacketHeader + 8Byte ( FileSize 항목) + 실제 파일 내역.
	// => packet header data_length 값은 file size 항목만 계산 처리. (8)
	// => 실제 파일 크기까지는 포함시키지 않는다.
	
	// 정보 전달을 위한 Packet Header 생성.
	struct RcSyncdPacketHeader stHeader;
	memset( &stHeader, 0x00, sizeof( struct RcSyncdPacketHeader ) );

	stHeader.stx        = HEADER_STX_CODE;
	stHeader.type       = HEADER_TYPE_RESPONSE;
	stHeader.command[0] = NOT_CLIENT_COMMAND;
	stHeader.command[1] = PROCESS_CONTENT_LIST;
	stHeader.result[0]  = HEADER_RESULT_SUCCESS;

	// Data body length
	stHeader.data_length = htonl( 8 );  // FileSize 항목인 8Byte 로 고정처리.
	
	// Packet Header 정보 전송
	if( WriteN( &stHeader, m_nPacketHeaderLen ) != m_nPacketHeaderLen )
	{
		LOG( LERR, "reponse [PROCESS_CONTENT_LIST] send fail to master" );
		return false;
	}
	else
	{
		// File Size 항목 전송 ( 엔디안 변환하지 않고 그냥 전송 처리 = unsigned long long 변환하기 귀찮음.)
		WriteN( &nFileSize, 8 );

		// File 크기가 0보다 큰 경우.. 실제 파일 내용 전송.
		if( nFileSize > 0 )
		{
			// Linux 의 경우 sendfile 함수가 2G 까지만 전송 가능하므로....
			// 루프로 전송 처리가 되도록 처리한다.
			off_t   offset           = 0;
			size_t  total_bytes_sent = 0;
			ssize_t bytes_sent;
			
			while( total_bytes_sent < nFileSize )
			{
				if( ( bytes_sent = sendfile( m_sock, fd, &offset, nFileSize - total_bytes_sent ) ) <= 0 )
				{
					int errorNum = errno;
					if( errorNum == EINTR || errorNum == EAGAIN )
					{
						// Interrupted system call/try again
						// Just skip to the top of the loop and try again 
						continue;
					}
					else
					{
						// 전송 실패 발생시... 
						LOG( LERR, "sendfile function error. [%d][%s]", errorNum, strerror( errorNum ) );
						return false;
					}
				}

				total_bytes_sent += bytes_sent;
			}

			// 전송이 완료된 경우... 실제 전송할 크기와 전송 완료된 크기를 비교, 확인한다.
			if( ( unsigned long long )total_bytes_sent != nFileSize )
			{
				LOG( LERR, "sendfile error. file size[%llu] send size[%lu]", nFileSize, total_bytes_sent );
				return false;
			}
		}
	}

	return true;
}

// [Master] Slave rc_syncd 에서 전송한 content 목록 정보 처리 결과를 수신하기 위한 함수.
int CProcessSocketControl::RecvResultContentListFromSlave( int nTimeout, bool& bSuccess, std::string& strSaveFileName, std::string& strErrorMessage )
{
	if( IsValidSocket() == false )
		return -1;

	// 실제.. 통신 client 이므로.. alive packet 을 수신할 일이 없지만.. 혹시 발생할 수 있으므로...
	// 방어 처리.

	int nRead;
	bool isAlivePacket;

	do
	{
		isAlivePacket = false;

		// Socket 으로 부터 Packet Header 부분 수신.
		// 수신된 정보는 멤버변수에 저장처리.
		nRead = ReadNTimeout( &m_packetHeader, m_nPacketHeaderLen, nTimeout );
		
		// nRead  0: Socket Closed
		//       -1: error 
		//       -2: Timeout 이므로
		if( nRead == -2 )
			return 0;		// Timeout 반환.
		else if( nRead == 0 || nRead == -1 )
			return -1;		// Socket Close 또는 오류 발생시

		// STX code 검사.
		if( m_packetHeader.stx != HEADER_STX_CODE )
		{
			LOG( LERR, "Not valid stx code." );

			return -1;
		}
		
		// Data Length 부분 값을 멤버 변수에 저장처리.
		m_nPacketDataLen = ntohl( m_packetHeader.data_length );

		// Alive Check Packet 인 경우 해당 패킷은 무시처리 후 다시 수신 처리한다.
		if( m_packetHeader.type          == HEADER_TYPE_REQUEST
			&& m_packetHeader.command[0] == COMMON_ALIVE_CHECK
			&& m_nPacketDataLen == 0 )
		{
			isAlivePacket = true;
		}

	} while( isAlivePacket == true );

	
	// Alive Packet 이 아닌 경우 해당 Packet 에 대한 응답패킷인지 검사.
	if( m_packetHeader.type != HEADER_TYPE_RESPONSE 
		|| m_packetHeader.command[0] != NOT_CLIENT_COMMAND 
		|| m_packetHeader.command[1] != PROCESS_CONTENT_LIST )
	{
		// 다른 패킷이 들어온 경우

		// ** 정석대로라면 그냥 냅둬어 수신부에서 처리해야 하지만
		// 현재 수신 처리부가 없어 당장 문제가 생길수 있으므로 
		// 데이터 부분까지 수신하여 임시버퍼에 저장처리 해 놓는다. 
		GetPacketData( m_nPacketDataLen );
		return 2;
	}

	// PROCESS_CONTENT_LIST 요청에 대한 응답 패킷인 경우...

	// 성공, 실패 여부 판단 및 이에 따른 body 부분 파싱 처리.
	if( m_packetHeader.result[0] != HEADER_RESULT_SUCCESS )
	{
		// 실패 정보 수신시..
		bSuccess = false;
		// 오류 메시지 정보 수신 처리
		if( m_nPacketDataLen > 0 )
		{
			if( GetPacketData( m_nPacketDataLen, strErrorMessage ) == false )
			{
				LOG( LERR, "data body receive failed." );
				return -1;
			}
		}
	}
	else
	{
		// 처리 결과가 성공인 경우....
		// 8 Byte 의 파일 크기 정보 와 실제 파일 내용을 수신해야 함.		
		if( GetPacketData( m_nPacketDataLen ) == false )
		{
			LOG( LERR, "file length body receive failed." );
			return -1;
		}		
		BYTE * pPos = m_tempBuffer;
		unsigned long long nFileSize;
		nFileSize = GetDataToUInt64( pPos );

		// 파일 관련 처리.		
		// - 파일 크기가 0 인 경우도 정상이므로.. 0 파일 생성 처리해야 함.
		// - 파일 크기만큼 실제 파일 정보 수신 실패시.. 해당 파일은 잘못된 파일이므로.. 삭제 처리해야 함.
		
		// File Descriptor 생성.
		FILE * pFile = NULL;
		pFile = fopen( strSaveFileName.c_str(), "w" );
		if( pFile == NULL )
		{
			// 파일 Open 실패시 오류로 처리
			int errorNum = errno;			
			char tempBuffer[1024];
			snprintf( tempBuffer, sizeof( tempBuffer ) - 1, "File open failed to write.[%s] [%d][%s]", strSaveFileName.c_str(), errorNum, strerror( errorNum ) );
						
			strErrorMessage.append( tempBuffer );
			bSuccess = false;

			LOG( LERR, "%s", strErrorMessage.c_str() );

			// 통신상에서 파일 관련 data 가 남아 있어 이를 수신해야 하지만....
			// 파일 open 이 실패했으므로.. 불필요한 정보임.. => 그냥 socket 을 close 처리한다.
			Close();
		}
		else
		{
			// File Open 성공시... 
			// => 파일 수신 처리.

			if( ReceiveToFile( pFile, nFileSize, strErrorMessage ) == false )
			{
				// 파일 수신 처리 실패시....
				// - 파일 삭제 처리
				// - socket 종료 처리 ( 더이상 사용하지 못하도록 )
				fclose( pFile );
				Close();
				unlink( strSaveFileName.c_str() );

				bSuccess = false;

				LOG( LERR, "Content list result file receive failed.[%s][%llu]", strSaveFileName.c_str(), nFileSize );
			}
			else
			{
				// 파일 수신 처리 성공시...
				fclose( pFile );
				bSuccess = true;
				
				_LOG( LDBG, "Content List result receive OK.[%s][%llu]", strSaveFileName.c_str(), nFileSize );
			}
		}
	}
	
	return 1;
}

// [Master] Slave rc_syncd 에 sync 작업 수행을 요청한 후 그 결과를 수신.
// - 본 함수는 data 와 파일을 원격 rc_syncd 에 전달하여 원격 rc_syncd 가 SYNC MODE 로 동작하도록 한다.
//   그리고.. 원격 rc_syncd 의 execute 전까지의 처리 결과를 수신하여 [out] 항목에 그 결과를 저장처리한다.
bool CProcessSocketControl::SendRequestExecuteSyncToSlave( CReqSyncData& data, int fd, unsigned long long nFileSize, std::string& strMessage )
{
	if( IsValidSocket() == false )
	{
		strMessage = "connection not vaild.";
		return false;
	}

	// 정보 전달을 위한 Packet Header 생성.
	struct RcSyncdPacketHeader stHeader;
	memset( &stHeader, 0x00, sizeof( struct RcSyncdPacketHeader ) );

	stHeader.stx        = HEADER_STX_CODE;
	stHeader.type       = HEADER_TYPE_REQUEST;
	stHeader.command[0] = NOT_CLIENT_COMMAND;
	stHeader.command[1] = PROCESS_CONTENT_SYNC;

	// Data 부분
	// sync_type  : int  (network_byte order)
	// start_time : string ( 4 + string.size() )
	// end_time   : string ( 4 + string.size() )
	// master     : string ( 4 + string.size() )
	// slave      : string ( 4 + string.size() )
	// nFileSize  : 8 Byte	---------->본 항목까지만 header data_length 에 포함시킨다.
	// sync 파일 

	// 전체 크기 계산.
	unsigned int nTemp = 0;
	nTemp = 4;                               // sync_type
	nTemp += ( 4 + data.start_time.size() ); // start_time
	nTemp += ( 4 + data.end_time.size() );   // end_time
	nTemp += ( 4 + data.master.size() );     // master
	nTemp += ( 4 + data.slave.size() );      // slave
	nTemp += 8;                              // nFileSize

	stHeader.data_length = htonl( nTemp );

	// Packet Header 정보 전송
	if( WriteN( &stHeader, m_nPacketHeaderLen ) != m_nPacketHeaderLen )
	{
		strMessage = "header send fail to slave rc_syncd";
		LOG( LERR, "request [PROCESS_CONTENT_SYNC] send fail to slave rc_syncd" );
		return false;
	}

	// Body 부분 전송
	// sync_type
	nTemp = htonl( data.sync_type );
	WriteN( &nTemp, 4 );
	// start_time
	SendString( data.start_time );
	// end_time
	SendString( data.end_time );
	// master
	SendString( data.master );
	// slave
	SendString( data.slave );
	// nFileSize
	WriteN( &nFileSize, 8 );

	// 파일 내용 전송.
	if( nFileSize > 0 )
	{
		// Linux 의 경우 sendfile 함수가 2G 까지만 전송 가능하므로....
		// 루프로 전송 처리가 되도록 처리한다.
		off_t   offset           = 0;
		size_t  total_bytes_sent = 0;
		ssize_t bytes_sent;

		while( total_bytes_sent < nFileSize )
		{
			if( ( bytes_sent = sendfile( m_sock, fd, &offset, nFileSize - total_bytes_sent ) ) <= 0 )
			{
				int errorNum = errno;
				if( errorNum == EINTR || errorNum == EAGAIN )
				{
					// Interrupted system call/try again
					// Just skip to the top of the loop and try again 
					continue;
				}
				else
				{
					// 전송 실패 발생시... 
					strMessage = "file send fail to slave rc_syncd";
					LOG( LERR, "sendfile function error. [%d][%s]", errorNum, strerror( errorNum ) );
					
					return false;
				}
			}

			total_bytes_sent += bytes_sent;
		}

		// 전송이 완료된 경우... 실제 전송할 크기와 전송 완료된 크기를 비교, 확인한다.
		if( ( unsigned long long )total_bytes_sent != nFileSize )
		{
			LOG( LERR, "sendfile error. file size[%llu] send size[%lu]", nFileSize, total_bytes_sent );
			strMessage = "file send fail to slave rc_syncd. file size not match.";
			return false;
		}
	}

	// 이제 응답 대기...
	// - 통신간 Alive Packet 은 전송하지 않는다.
	// - 최대 15 분까지 기다려도 응답이 없는 경우.. 오류로 처리한다.
	
	int nTimeout = 60 * 15; // 최대 15분간 대기
	int nRead;

	// Socket 으로 부터 Packet Header 부분 수신.
	// 수신된 정보는 멤버변수에 저장처리.
	nRead = ReadNTimeout( &m_packetHeader, m_nPacketHeaderLen, nTimeout );

	// nRead  0: Socket Closed
	//       -1: error 
	//       -2: Timeout 이므로
	if( nRead == -2 )
	{
		// Timeout 발생시
		LOG( LERR, "slave rc_sycnd reponse wait. but timeout[%d sec] occured.", nTimeout );
		strMessage = "slave rc_syncd reponse timeout[15 min] occured.";
		return false;
	}		
	else if( nRead == 0 || nRead == -1 )
	{
		// Socket Close 또는 오류 발생시
		LOG( LERR, "slave rc_sycnd reponse wait. but socket closed or error [%d]", nRead );
		strMessage = "slave rc_syncd session disconnected.";
		return false;
	}

	// STX code 검사.
	if( m_packetHeader.stx != HEADER_STX_CODE )
	{
		LOG( LERR, "Not valid stx code." );
		PrintHeaderToLog();

		strMessage = "slave rc_syncd packet header error.";
		return false;
	}

	// Data Length 부분 값을 멤버 변수에 저장처리.
	m_nPacketDataLen = ntohl( m_packetHeader.data_length );
	
	// 해당 Packet 에 대한 응답패킷인지 검사.
	if( m_packetHeader.type          != HEADER_TYPE_RESPONSE
		|| m_packetHeader.command[0] != NOT_CLIENT_COMMAND
		|| m_packetHeader.command[1] != PROCESS_CONTENT_SYNC )
	{
		// 다른 패킷이 들어온 경우

		// ** 정석대로라면 그냥 냅둬어 수신부에서 처리해야 하지만
		// 현재 수신 처리부가 없어 당장 문제가 생길수 있으므로 
		// 데이터 부분까지 수신하여 임시버퍼에 저장처리 해 놓는다. 
		GetPacketData( m_nPacketDataLen );
		
		strMessage = "slave rc_syncd communication flow error.";
		return false;
	}

	// Data 부분이 존재하는 경우.. 수신 처리.
	if( m_nPacketDataLen > 0 )
	{
		if( GetPacketData( m_nPacketDataLen, strMessage ) == false )
		{
			LOG( LERR, "data body receive failed." );
			strMessage = "data body receive failed.";
			return false;
		}
	}

	// 성공 실패 여부 확인
	if( m_packetHeader.result[0] != HEADER_RESULT_SUCCESS )
	{
		_LOG( LDBG, "Execute sync result received. error[%s]", strMessage.c_str());
		return false;
	}
	else
	{
		_LOG( LDBG, "Execute sync result received. success.");
		return true;
	}
}


// Master rc_syncd 에서 전송한 sync 실행 요청 관련 data body 수신 처리.
// - 파일 관련 부분은 수신하지 않고.. 아래 함수(RecvFileExecuteSyncFromMaster)를 통해 따로 수신 처리해야 함.
bool CProcessSocketControl::RecvBodyExecuteSyncFromMaster( CReqSyncData& request )
{
	if( IsValidSocket() == false )
		return false;

	// Body 부분 수신 처리.
	unsigned int nLength;

	// sync_type
	if( GetPacketData( 4 ) == false )
	{
		LOG( LERR, "sync_type get failed." );
		return false;
	}
	request.sync_type = ( SYNC_TYPE::SYNC_TYPE )GetDataToUInt( m_tempBuffer, true );

	// start_time
	if( GetPacketData( 4 ) == false )
	{
		LOG( LERR, "start_time size get failed." );
		return false;
	}
	nLength = GetDataToUInt( m_tempBuffer, true );
	GetPacketData( nLength, request.start_time );

	// end_time
	if( GetPacketData( 4 ) == false )
	{
		LOG( LERR, "end_time size get failed." );
		return false;
	}
	nLength = GetDataToUInt( m_tempBuffer, true );
	GetPacketData( nLength, request.end_time );

	// master
	if( GetPacketData( 4 ) == false )
	{
		LOG( LERR, "master size get failed." );
		return false;
	}
	nLength = GetDataToUInt( m_tempBuffer, true );
	GetPacketData( nLength, request.master );

	// slave
	if( GetPacketData( 4 ) == false )
	{
		LOG( LERR, "slave size get failed." );
		return false;
	}
	nLength = GetDataToUInt( m_tempBuffer, true );
	GetPacketData( nLength, request.slave );
		
	return true;
}


// Master rc_syncd 에서 전송한 sync 실행 요청 관련 sync file 관련 부분을 수신 처리.
// @param strSaveFileName [in] 저장 파일명
// @param strErrorMessage [out] return 값이 false 일 경우. 오류 메시지 저장.
bool CProcessSocketControl::RecvFileExecuteSyncFromMaster( std::string& strSaveFileName, std::string& strErrorMessage )
{
	if( IsValidSocket() == false )
		return false;

	// 8 Byte 의 파일 크기 정보 수신
	if( GetPacketData( 8 ) == false )
	{
		LOG( LERR, "sync file length body receive failed." );
		return false;
	}
	BYTE * pPos = m_tempBuffer;
	unsigned long long nFileSize;
	nFileSize = GetDataToUInt64( pPos );

	// 파일 관련 처리.		
	// - 파일 크기가 0 인 경우도 정상이므로.. 0 파일 생성 처리해야 함.
	// - 파일 크기만큼 실제 파일 정보 수신 실패시.. 해당 파일은 잘못된 파일이므로.. 삭제 처리해야 함.

	bool bSuccess;

	// File Descriptor 생성.
	FILE * pFile = NULL;
	pFile = fopen( strSaveFileName.c_str(), "w" );
	if( pFile == NULL )
	{
		// 파일 Open 실패시 오류로 처리
		int errorNum = errno;
		char tempBuffer[1024];
		snprintf( tempBuffer, sizeof( tempBuffer ) - 1, "File open failed to sync write.[%s] [%d][%s]", strSaveFileName.c_str(), errorNum, strerror( errorNum ) );

		strErrorMessage.append( tempBuffer );
		LOG( LERR, "%s", strErrorMessage.c_str() );

		// 통신상에서 파일 관련 data 가 남아 있어 이를 수신해야 하지만....
		// 파일 open 이 실패했으므로.. 불필요한 정보임.. => 그냥 socket 을 close 처리한다.
		Close();

		bSuccess = false;
	}
	else
	{
		// File Open 성공시... 
		// => 파일 수신 처리.
		if( ReceiveToFile( pFile, nFileSize, strErrorMessage ) == false )
		{
			// 파일 수신 처리 실패시....
			// - 파일 삭제 처리
			// - socket 종료 처리 ( 더이상 사용하지 못하도록 )
			fclose( pFile );
			Close();
			unlink( strSaveFileName.c_str() );

			bSuccess = false;

			LOG( LERR, "Sync request file receive failed.[%s][%llu]", strSaveFileName.c_str(), nFileSize );
		}
		else
		{
			// 파일 수신 처리 성공시...
			fclose( pFile );
			bSuccess = true;

			_LOG( LDBG, "Sync request file receive OK.[%s][%llu]", strSaveFileName.c_str(), nFileSize );
		}
	}
	
	return bSuccess;
}

// [Sync] Master 에게 Sync Execute 요청에 대한 응답을 전송 처리
bool CProcessSocketControl::SendResultExecuteSyncToMaster( bool bSuccess, std::string& strMessage )
{
	if( IsValidSocket() == false )
		return false;

	struct RcSyncdPacketHeader stHeader;
	memset( &stHeader, 0x00, sizeof( struct RcSyncdPacketHeader ) );

	stHeader.stx        = HEADER_STX_CODE;
	stHeader.type       = HEADER_TYPE_RESPONSE;
	stHeader.command[0] = NOT_CLIENT_COMMAND;
	stHeader.command[1] = PROCESS_CONTENT_SYNC;

	// 성공, 실패 여부 설정
	if( bSuccess == true )
		stHeader.result[0] = HEADER_RESULT_SUCCESS;
	else
		stHeader.result[0] = HEADER_RESULT_ERROR;

	// Data body length
	stHeader.data_length = htonl( strMessage.size() );

	// Packet Header 정보 전송
	if( WriteN( &stHeader, m_nPacketHeaderLen ) != m_nPacketHeaderLen )
	{
		LOG( LERR, "reponse [CLIENT_COMMAND_SYNC] send fail to client" );
		return false;
	}
	else
	{
		// message 부분이 존재하는 경우.. 전송 처리.
		if( strMessage.empty() == false )
		{
			WriteN( strMessage.c_str(), strMessage.size() );
		}
	}

	return true;
}


///////////////////////////////////////////////////////////////////////////////////////
// Packet Header 부분의 수신 처리를 위한 함수.
// @param timeout [in] 대기시간.
// @retrun 
//         1 : PACKET_HEADER_RECEIVE_SUCCESS          : 성공시           
//         0 : PACKET_HEADER_RECEIVE_TIMEOUT          : Timeout 발생시
//        -1 : PACKET_HEADER_RECEIVE_SOCK_CLOSED      : socket 종료된 경우
//        -2 : PACKET_HEADER_RECEIVE_INVALID_PROTOCOL : 잘못된 Packet 형식인 경우
//        -3 : PACKET_HEADER_RECEIVE_READ_ERROR       : 기타 오류
int CProcessSocketControl::GetPacketHeader( int timeout )
{
	if( IsValidSocket() == false )
	{
		LOG( LERR, "IsValidSocket return false.");

		// 유효하지 않은 socket 이므로 -1 을 반환
		// return false;
		return PACKET_HEADER_RECEIVE_SOCK_CLOSED;
	}

	int nRead;
	bool isAlivePacket;
	do
	{
		isAlivePacket = false;

		// Socket 으로 부터 Packet Header 부분 수신.
		// 수신된 정보는 멤버변수에 저장처리.
		// ReadNTimeout() 반환값은 read 된 데이터의 크기. 0: fd closed, -1: 오류 , -2: Timeout
		nRead = ReadNTimeout( &m_packetHeader, m_nPacketHeaderLen, timeout );

		// 정상적으로 Data 를 수신한 경우
		if( nRead > 0 )
		{
			// Packet 형식 코드 중 STX code 검사.
			if( m_packetHeader.stx != HEADER_STX_CODE )
			{
				LOG( LERR, "Not valid STX code ");
				PrintHeaderToLog();

				// 잘못된 Packet 형식인 경우 -2 값을 반환
				//return false;
				return PACKET_HEADER_RECEIVE_INVALID_PROTOCOL;
			}

			// Data Length 부분 값을 멤버 변수에 저장처리.
			m_nPacketDataLen = ntohl( m_packetHeader.data_length );

			// Alive Check Packet 인 경우 해당 패킷은 무시처리 후 다시 수신 처리한다.
			if( m_packetHeader.type == HEADER_TYPE_REQUEST
					&& m_packetHeader.command[0] == COMMON_ALIVE_CHECK 
					&& m_nPacketDataLen == 0 )
			{
				isAlivePacket = true;
			}

		}
		else
		{
			// Socket 이 종료된 경우
			if( nRead == 0 )
			{				
				//LOG( LINF, "Connection already closed.");
				Close();	// 확실하게 종료 처리 한 후

				// Connection 이 종료된 경우 -1 을 반환
				return PACKET_HEADER_RECEIVE_SOCK_CLOSED;
			}
			else if( nRead == -1 )	// read 함수 관련 오류가 발생한 경우
			{
				LOG( LERR, "Socket read() function error. Connection closed by forced.");				
				Close();	// 확실하게 종료 처리 한 후

				// 오류 발생시 -3 을 반환
				return PACKET_HEADER_RECEIVE_READ_ERROR;

			}
			else if( nRead == -2 )	// Timeout 이 발생한 경우
			{
				LOG( LDBG, "Socket read() timeout [%d]", timeout);

				// Timeout 발생시에는 0 을 반환
				return PACKET_HEADER_RECEIVE_TIMEOUT;
			}
		}

	} while( isAlivePacket == true );


	return PACKET_HEADER_RECEIVE_SUCCESS;
}

// socket 에서 지정된 크기만큼의 데이터를 읽어 출력변수에 저장처리.
// @param size [in] read 할 데이터 크기
// @param value [out] 읽은 데이터를 저장할 string 변수
// @return On success return true, otherwise return false.
bool CProcessSocketControl::GetPacketData( unsigned int& size, std::string& value )
{
	if( size == 0 )
	{
		value.clear();
		return true;
	}
	
	// 1. 수신할 크기가 임시버퍼보다 큰 경우 새로운 버퍼를 생성한다.
	BYTE * pBuffer = NULL;
	bool bNewBufferCreated = false;
	if( size > DEFAULT_SOCKET_TEMP_BUFFER_SIZE )
	{
		pBuffer = new BYTE[size];	// 신규 메모리 할당.
		bNewBufferCreated = true;
	}
	else
		pBuffer = m_tempBuffer;

	// 2. 데이터 수신 처리.
	int nRead = ReadNTimeout( pBuffer, size );
	
	// 오류 발생시
	if( nRead <= 0 )
	{
		// 신규로 생성된 버퍼인 경우 메모리 해제 처리.
		if( bNewBufferCreated == true )
			delete [] pBuffer;

		return false;
	}

	// 3. 수신 데이터 저장 처리 - 기존 데이터 뒤에 붙여 준다=> 이게 사용하기 편함.
	value.append( (char *)pBuffer, size );

	// 신규로 생성된 버퍼인 경우 메모리 해제 처리.
	if( bNewBufferCreated == true )
		delete [] pBuffer;

	return true;
}

// socket 에서 지정된 크기만큼의 데이터를 읽어 내부 임시버퍼인 m_tempBuffer 에 저장처리.
// @param size [in] read 할 데이터 크기
// @return On success return true, otherwise return false.
bool CProcessSocketControl::GetPacketData( unsigned int size )
{
	if( size == 0 )
		return true;
	
	// 1. 수신할 크기가 임시버퍼보다 큰 경우 오류 반환.
	if( size > DEFAULT_SOCKET_TEMP_BUFFER_SIZE )
		return false;

	// 2. 데이터 수신 처리.
	int nRead = ReadNTimeout( m_tempBuffer, size );
	
	// 오류 발생시
	if( nRead <= 0 )
		return false;

	// 3. 정상인 경우는 ReadNTimeout 함수가 무조건 요청 크기만큼 read 처리하므로 정상수신임.
	return true;
}

// 전달받은 데이터를 unsigned int 형으로 변환처리 및 Endian 변환
unsigned int CProcessSocketControl::GetDataToUInt( BYTE * pValue, bool bConvertEndian )
{
	BYTE tempBuffer[4];
	unsigned int result;

	memcpy(tempBuffer, pValue, 4);
	unsigned int * pInt = (unsigned int *)tempBuffer;

	// Endian 변환처리.
	if( bConvertEndian == true )
	{
		result = ntohl( *pInt );
	}
	else
		result = *pInt;

	return result;
}

// pValue 에 저장된 데이터를 unsigned long long (64Byte) 형으로 변환처리.
unsigned long long CProcessSocketControl::GetDataToUInt64( BYTE * pValue )
{
	unsigned long long * pResult;
	BYTE tempBuffer[8];
	memcpy(tempBuffer, pValue, 8);

	pResult = (unsigned long long *)tempBuffer;

	return  *pResult;
}

// 전달받은 Directory 정보를 확인하여 존재하지 않을 경우 생성처리.
bool CProcessSocketControl::MakeDirectory( const std::string& szFullPath )
{
	if( szFullPath.size() <= 0 )
		return false;

	struct stat dirStat;

	// 디렉토리 정보 검사.
	// 해당 이름을 가진 Directory 또는 파일이 존재하고
	if( lstat ( szFullPath.c_str(), &dirStat ) == 0)
	{
		// 해당 정보가 Directory 인 경우
		if( S_ISDIR( dirStat.st_mode ) != 0 )
			return true;
	}

	// 해당 디렉토리가 존재하지 않는 경우 무조건 생성 처리.
	char tempBuffer[1024];		
	sprintf( tempBuffer, "/bin/mkdir -p -m0777 %s", szFullPath.c_str() );
	system(tempBuffer);

	return true;
}

// Socket 으로 부터 수신받은 데이터를 파일에 Write 처리한다.
// 만약 File Write 처리시 오류가 발생하는 경우 Socket Data 의 정상적인 처리를 위해
// Socket 데이터 수신만 처리하고 오류를 반환한다.
// @returnFile Write 가 오류 없이 정상적으로 처리된 경우 true, 오류발생시 false 및 오류메시지 반환
bool CProcessSocketControl::ReceiveToFile( FILE * pFile, unsigned long long nFileSize, std::string& errorMessage )
{
	if( IsValidSocket() == false )
		return false;
	
	// 0 Byte 크기의 파일인 경우 그냥 SUCCESS 처리.
	if( nFileSize == 0 )
		return true;

	char tempBuffer[FILE_DATA_BUFFER_SIZE];
	int onceReadSize;
	int nRead;
	unsigned long long nLeft = nFileSize;
	bool bFileWriteSuccess = true;

	int nLockResult;

		
	// 동일 파일에 대한 동시 Write 처리를 방어하기 위해 file lock 개념을 추가한다.
	int fd = fileno( pFile );
	nLockResult = lockf( fd, F_TLOCK, 0 );	// 파일 전체에 대해 lock 시도.

	if( nLockResult == -1 )
	{
		int errorNum = errno;
		if( errorNum == EAGAIN )
		{
			// 이미 Lock 이 걸려 있는 상태인 경우... 5 sec 후 재시도 처리..
			// 만약 재시도가 실패하는 경우.. 실패로 반환.
			LOG( LWAR, "File lock 1th fail. 5 sec sleep and retry [%d][%s]", errorNum, strerror(errorNum));

			sleep(5);
			nLockResult = lockf( fd, F_TLOCK, 0 );	// 파일 전체에 대해 lock 재시도.

			if( nLockResult == -1 )
			{
				errorNum = errno;

				// Lock 재시도 실패시.. 오류 반환.
				LOG( LERR, "File lock fail. file receive fail [%d][%s]", errorNum, strerror(errorNum));

				errorMessage = "File data receive error [file lock fail]";
				return false;
			}
		}
		else
		{
			// 이미 Lock 상태로 인한 lock 처리 실패가 아닌 경우.
			LOG( LERR, "File lock fail by system error. file receive fail [%d][%s]", errorNum, strerror(errorNum));
			errorMessage = "File data receive error [file lock fail]";
			return false;
		}
	}
	
	while( nLeft > 0 )
	{
		// 한번에 읽을 용량 계산.
		if( nLeft > FILE_DATA_BUFFER_SIZE )
			onceReadSize = FILE_DATA_BUFFER_SIZE;
		else
			onceReadSize = nLeft;

		// Socket Read
		if( (nRead = read( m_sock, tempBuffer, onceReadSize )) < 0 )
		{
			int errorNum = errno;
			if( errorNum == EINTR || errorNum == EAGAIN || errorNum == EWOULDBLOCK )
			{
				nRead = 0;
				continue;
			}
			else if( errorNum == ECONNRESET )
			{
				m_bConnected = false;
				errorMessage = "File receive error [connection closed].";

				lockf( fd, F_ULOCK, 0 );
				return false;
			}
			else
			{
				errorMessage = "File data receive error [socket read error]";
				LOG( LERR, "Socket read error.[%d][%s]", errorNum, strerror(errorNum));

				lockf( fd, F_ULOCK, 0 );
				return false;
			}
		}
		else if( nRead == 0 )  // Connection Closed..
		{
			m_bConnected = false;
			errorMessage = "File receive error [connection closed].";

			lockf( fd, F_ULOCK, 0 );
			return false;
		}
		
		// 정상적인 경우.
		// File Write 시도
		if( bFileWriteSuccess == true )
		{
			if( (int)fwrite( tempBuffer, 1, nRead, pFile ) != nRead )
			{
				// file write 실패시.
				int errorNum = errno;
				LOG( LERR, "File write to local error.[%d][%s]", errorNum, strerror(errorNum));

				// TODO 소켓을 읽어 전송받은 데이터를 날려버려야 할까요..?
				errorMessage = "File write failed from receive data";
				bFileWriteSuccess = false;
			}
		}

		nLeft -= nRead;
	}

	// file Lock 해제 처리.
	lockf( fd, F_ULOCK, 0 );

	// File Write 가 오류 없이 정상적으로 처리된 경우 true
	// 오류발생시 false 반환처리.
	if( bFileWriteSuccess == true )
		return true;
	else
		return false;
}


// Packet 정보 중 String 정보를 전송하기 위한 함수.
bool CProcessSocketControl::SendString( const std::string& strData )
{
	if( IsValidSocket() == false )
		return false;

	// 크기값을 저장하기 위한 임시 변수.
	unsigned int nSize = 0;

	// String Data 전송
	nSize = htonl( strData.size() );
	WriteN( &nSize, 4 );
	if( strData.size() > 0 )
		WriteN( strData.c_str(), strData.size() );

	return true;
}
