#include "SocketFimngd.h"

#include "Logger.h"
#include "ProcessConfig.h"

#include <arpa/inet.h>
#include <string.h>

#include <iterator>


// 생성자
// param [in] clientSocket client 와 통신 연결된 socket.

CSocketFimngd::CSocketFimngd( int clientSocket )
	: CBaseSocket( clientSocket )	
	, m_iPacketHeaderLen( sizeof( m_packetHeader ) )
	, m_uPacketDataLen( 0 )
{
	
}

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


// Packet Header 부분의 수신 처리를 위한 함수
// param command [out] 수신된 command code		
// param timeout [in] 대기시간.
// retrun 0 : timeout
//        1 : 성공
//       -1 : socket 오류 또는 정의된 Data 가 아닌 경우.
int CSocketFimngd::GetPacketHeader( char & command, int timeout )
{
	if( IsValidSocket() == false )
		return -1;

	// Socket 으로 부터 Packet Header 부분 수신.
	// 수신된 정보는 멤버변수에 저장처리.
	/// read 된 데이터의 크기. 0: fd closed, -1: 오류, -2: Timeout
	int nRead = ReadNTimeout( &m_packetHeader, m_iPacketHeaderLen, timeout );
	if( nRead == -2 )
	{
		// timeout 발생시
		return 0;
	}
	else if( nRead <= 0 )
	{
		// timeout 을 제외한 그 밖의 오류 발생시
		return -1;
	}
	else
	{
		// 정상 Data 수신시.

		// STX code 검사.
		if( m_packetHeader.stx != HEADER_STX_CODE_FIMNGD )
		{
			LOG( LERR, "Not valid stx code." );
			PrintHeaderToLog();
			return -1;
		}

		// Type Code 검사
		if( m_packetHeader.type != HEADER_TYPE_REQUEST )
		{
			LOG( LERR, "Not valid type code." );
			PrintHeaderToLog();
			return -1;
		}
		

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

		// command code 정보를 출력 param 에 저장 처리.
		// 통신 정의상 1th 항목만 사용.
		command = m_packetHeader.command[1];

		return 1;
	}
}

// Packet Header 정보를 Log 파일에 Logging 처리 ( Debug 처리를 위한 함수)
void CSocketFimngd::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_uPacketDataLen );
	_LOG( LINF, "protocol_version [%02x]", m_packetHeader.protocol_version );
	_LOG( LINF, "extend_code      [%02x]", m_packetHeader.extend_code );
	_LOG( LINF, "------------------------------------" );
}

// COMMAND_SERVICE_NETWORK_STAT 요청 관련 Body 수신 처리.
// - protocol version 에 따른 처리 필요.
// param vecNetworkStat [out] 수신된 각 서비스별 network 통계 정보 저장
// return false socket 오류, 해당 protocol version 미지원, RC ID 불일치 등 비정상 상황.
//        그외 true
bool CSocketFimngd::GetPacketBodyNetworkStat( std::vector< struct ServiceNetworkStat > & vecStat )
{
	std::string strErrorMessage;
	
	// 지원 가능한 protocol version 인지 검사.
	if( m_packetHeader.protocol_version > PARSE_MAX_PROTOCOL_VERSION_SERVICE_NETWORK )
	{
		// 2016-05-27 (#27106)
		// fimngd 와의 통신 Protocol 버전을 확인하여
		// 0x01 이상인 경우.. fimngd로 오류 내역 및 메시지 전송 처리
		if( m_packetHeader.protocol_version > 0x00 )
		{
			strErrorMessage = "Not support protocol version.";
			SendResultNetworkStat( false, strErrorMessage );
		}
		
		// 파싱 불가능한 버전인 경우...
		LOG( LERR, "Not support protocol version. [COMMAND_SERVICE_NETWORK_STAT] support[%02x] request[%02x]."
			 , PARSE_MAX_PROTOCOL_VERSION_SERVICE_NETWORK, m_packetHeader.protocol_version );
		
		PrintHeaderToLog();
		return false;
	}
	
	int nRead = 0;
	
	// body header 정보 수신 처리.
	PacketDataServiceNetworkStatHeader stHeader;
	memset( &stHeader, 0x00, sizeof( struct PacketDataServiceNetworkStatHeader ) );

	nRead = ReadNTimeout( &stHeader, sizeof( struct PacketDataServiceNetworkStatHeader ), 2 );
	if( nRead == -2 )
	{
		// timeout 발생시
		// - 지정된 시간 동안 Body Header 읽지 못했으므로. 오류로 처리
		LOG( LERR, "fimngd network stat body header[%lu] read timeout.", sizeof( struct PacketDataServiceNetworkStatHeader ) );
		
		// 2016-05-27 (#27106)
		// fimngd 와의 통신 Protocol 버전을 확인하여
		// 0x01 이상인 경우.. fimngd로 오류 내역 및 메시지 전송 처리
		if( m_packetHeader.protocol_version > 0x00 )
		{
			strErrorMessage = "network stat data recv timeout.";
			SendResultNetworkStat( false, strErrorMessage );
		}

		return false;
	}
	else if( nRead <= 0 )
	{
		// timeout 을 제외한 그 밖의 오류 발생시
		LOG( LERR, "fimngd network stat body read error[%d]", nRead );

		// 2016-05-27 (#27106)
		// fimngd 와의 통신 Protocol 버전을 확인하여
		// 0x01 이상인 경우.. fimngd로 오류 내역 및 메시지 전송 처리
		if( m_packetHeader.protocol_version > 0x00 )
		{
			strErrorMessage = "network stat data recv error.";
			SendResultNetworkStat( false, strErrorMessage );
		}

		return false;
	}

	// body header 수신이 정상인 경우...	
	// 1. RC ID 정보 비교 (대소문자 무시)	
	if( strcasecmp( CProcessConfig::GetInstance()->GetRcId(), stHeader.rcid ) != 0 )
	{
		LOG( LERR, "fimngd network stat error. RC ID not match. request [%s][%s]", stHeader.rcid, stHeader.hostname );

		// 2016-05-27 (#27106)
		// fimngd 와의 통신 Protocol 버전을 확인하여
		// 0x01 이상인 경우.. fimngd로 오류 내역 및 메시지 전송 처리
		if( m_packetHeader.protocol_version > 0x00 )
		{
			strErrorMessage = "network stat data RC ID mismatch.";
			SendResultNetworkStat( false, strErrorMessage );
		}
		
		return false;
	}

	// 2. 서비스 count 정보 변환 처리.
	unsigned int uServiceCount = ntohl( stHeader.service_count );
	
	
	// protocol version 별 파싱 처리.

	// 2016-05-27 (#27106)
	// Protocol 버전 0x00, 0x01 의 Data 구조는 동일함
	// 다만 Protocol 버전이  0x01인 경우.. fimngd로 오류 내역 및 메시지 전송 처리하도록 기능 추가함.
	if( m_packetHeader.protocol_version == 0x01 || m_packetHeader.protocol_version == 0x00 )
	{
		// 0x00, 0x01 버전이 동일한 Data 구조체이므로 동시 지원.
		// 서비스별 Network stat 정보 수신 처리를 담당할 구조체 정의 및 초기화.
		struct PacketDataServiceNetworkStat_V00 stBody;
		memset( &stBody, 0x00, sizeof( struct PacketDataServiceNetworkStat_V00 ) );
		

		// Body 부분 크기가.. 수신 처리할 구조체 크기와 동일한지 체크...
		// - 안 할 경우.... body 크기가 저장 구조체 크기보다 큰 경우.. 
		// - 본 모듈이 memory overflow 로 죽으므로... 방어코드 적용.
		if( m_uPacketDataLen != sizeof( stBody ) * uServiceCount + sizeof( struct PacketDataServiceNetworkStatHeader ) )
		{
			if( m_packetHeader.protocol_version > 0x00 )
			{
				strErrorMessage = "network stat body size invalid.";
				SendResultNetworkStat( false, strErrorMessage );
			}

			LOG( LERR, "Invalid body size. [COMMAND_SERVICE_NETWORK_STAT] version[%02x] real[%zu] packet header[%u]"
				 , m_packetHeader.protocol_version
				 , sizeof( stBody ) * uServiceCount + sizeof( struct PacketDataServiceNetworkStatHeader )
				 , m_uPacketDataLen );

			PrintHeaderToLog();

			return false;
		}

		struct ServiceNetworkStat stStat;

		// uServiceCount 만큼 루프를 돌면서.. 통계 정보를 수신처리한 후.. output 인자에 저장처리한다.
		for( unsigned int i = 0; i < uServiceCount; ++i )
		{
			nRead = ReadNTimeout( &stBody, sizeof( struct PacketDataServiceNetworkStat_V00 ), 1 );
			if( nRead == -2 )
			{
				// timeout 발생시
				// - 지정된 시간 동안 Header 에 기록된 Body 크기를 읽지 못했으므로. 오류로 처리
				if( m_packetHeader.protocol_version > 0x00 )
				{
					strErrorMessage = "network stat data body recv timeout.";
					SendResultNetworkStat( false, strErrorMessage );
				}
				
				LOG( LERR, "fimngd network stat[%u/%u] body[%lu] read timeout."
					 , i + 1, uServiceCount, sizeof( struct PacketDataServiceNetworkStat_V00 ) );

				return false;
			}
			else if( nRead <= 0 )
			{
				// timeout 을 제외한 그 밖의 오류 발생시
				if( m_packetHeader.protocol_version > 0x00 )
				{
					strErrorMessage = "network stat data body recv error.";
					SendResultNetworkStat( false, strErrorMessage );
				}
				
				LOG( LERR, "fimngd network stat[%u/%u] body read error[%d]", i + 1, uServiceCount, nRead );
				return false;
			}
			else
			{
				// 정상적으로 수신처리한 경우....
				// output 인자에 저장 처리.				
				memset( &stStat, 0x00, sizeof( struct ServiceNetworkStat ) );

				// header 에서 수신된 hostname 정보 복사.
				strncpy( stStat.hostname, stHeader.hostname, FIMNGD_HOST_NAME_MAX_LENGTH );

				// 수신된 통계 정보 복사
				// - 향후 구조가 변경될 수 있어.. 개별 copy 처리함.
				stStat.timestamp         = stBody.timestamp;
				stStat.user_seq          = stBody.user_seq;
				stStat.svc_seq           = stBody.svc_seq;
				stStat.down_content_size = stBody.down_content_size;
				stStat.up_content_size   = stBody.up_content_size;
				stStat.down_traffic      = stBody.down_traffic;
				stStat.up_traffic        = stBody.up_traffic;

				// output vector 에 저장 처리.
				vecStat.push_back( stStat );
			}
		}

		// 2016-05-27 (#27106)
		// fimngd 와의 통신 Protocol 버전을 확인하여
		// 0x01 이상인 경우.. fimngd로 Data 수신 처리 성공 메시지 전송.
		// 만약 fimngd 로 성공 메시지 전송이 실패할 경우....
		// - fimngd 는 다시 재전송 시도 하므로.. 실패로 처리해야 한다.
		if( m_packetHeader.protocol_version > 0x00 )
		{
			strErrorMessage.clear();

			// fimngd 로 성공 응답코드 전송 실패시...
			if( SendResultNetworkStat( true, strErrorMessage ) == false )
			{
				// fimngd 가 재전송을 수행하므로.. 이번 수신 data 는 버린다.
				_LOG( LINF, "fimngd network stat reponse send fail. recv network stat data throw.. protocol[%02x].", m_packetHeader.protocol_version );
				return false;
			}
		}
		
		// 수신된 service network 통계 정보가 존재하는 경우만 true 반환.
		// - data 도 없는데.. 왜 header 를 보내 -> 이거는 false 로 처리.
		if( uServiceCount > 0 )
			return true;
		else
			return false;
	}
	else
	{
		// 해당 버전에 대한 파싱 코드가 아직 없는 경우...

		// 2016-05-27 (#27106)
		// fimngd 와의 통신 Protocol 버전을 확인하여
		// 0x01 이상인 경우.. fimngd로 오류 내역 및 메시지 전송 처리
		if( m_packetHeader.protocol_version > 0x00 )
		{
			strErrorMessage = "Not support protocol version parsing.";
			SendResultNetworkStat( false, strErrorMessage );
		}

		LOG( LERR, "Not support protocol version parsing. [COMMAND_SERVICE_NETWORK_STAT] request[%02x].", m_packetHeader.protocol_version );
				
		return false;
	}
}



// NEW 2016-05-27 huibong
// - #27106 에 의해 
// - fimngd 로 부터 Network 통계 수신시 Protocol version 0x01 이상이면.. 처리 결과를 전송하도록 기능 추가
// - bSuccess = true  : 성공인 경우.. Packet header 에 성공으로 표시하여 전달. ( 성공시 추가 메시지는 전달 안함)
//            = false : 실패인 경우.. Packet header 에 실패로 표시하고.. 전달할 메시지가 있는 경우.. 해당 정보까지 전달. 
bool CSocketFimngd::SendResultNetworkStat( bool bSuccess, std::string& strMessage )
{
	if( IsValidSocket() == false )
		return false;

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

	stHeader.stx        = HEADER_STX_CODE_FIMNGD;
	stHeader.type       = HEADER_TYPE_RESPONSE;
	
	// command code 정보를 출력 param 에 저장 처리.
	// 통신 정의상 1th 항목만 사용.
	stHeader.command[1] = COMMAND_SERVICE_NETWORK_STAT;
	
	// 성공, 실패 여부 및 전송할 Body Size 정보 설정
	if( bSuccess == true )
	{
		stHeader.result[0] = HEADER_RESULT_SUCCESS;

		// 성공시에는 메시지 전송 안함.
		stHeader.data_length = htonl( 0 );
	}
	else
	{
		stHeader.result[0] = HEADER_RESULT_ERROR;

		// 실패시에만 메시지 전송한다.
		stHeader.data_length = htonl( strMessage.size() );
	}

	// Protocol version
	// - 수신된 protocol version 과 같도록
	stHeader.protocol_version = m_packetHeader.protocol_version;
	
	// Packet Header 정보 전송
	if( WriteN( &stHeader, m_iPacketHeaderLen ) != m_iPacketHeaderLen )
	{
		LOG( LERR, "reponse [fimngd network stat] send fail to client" );
		return false;
	}
	else
	{
		// 오류 발생 상황이고.. message 부분이 존재하는 경우.. 전송 처리.
		if( bSuccess == false && strMessage.empty() == false )
		{
			WriteN( strMessage.c_str(), strMessage.size() );
		}
	}

	return true;
}
