#include "SocketCsagentd.h"

#include "Logger.h"

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


// 생성자
// param [in] clientSocket client 와 통신 연결된 socket.
// param [in] szRcid       RC ID 정보.
CSocketCsagentd::CSocketCsagentd( int clientSocket, const char * szRcid )
	: CBaseSocket( clientSocket )	
	, m_iPacketHeaderLen( sizeof( m_packetHeader ) )
	, m_uPacketDataLen( 0 )
{
	m_strRcId = szRcid;
}

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


// 수신된 Data 를 전달받은 인자에 설정하여 반환 처리.
// param [out] strHostname 장비의 hostname 정보.
// param [out] pData       수신된 data
// param [in]  timeout	   대기 timeout (sec)		
// return  -1 : Socket 오류 또는 잘못된 Data 수신시 ( RC ID 가 다른 경우 포함)
//          0 : timeout 발생시
//          1 : 정상
int CSocketCsagentd::GetSystemInfo( std::string & strHostname, struct SystemInfo * pData, int timeout )
{
	// Header 정보를 수신
	int nResult = GetPacketHeader( timeout );

	if( nResult == -1 )
	{
		// Socket 끊김 또는 정의되지 않은 Data 수신시.
		return -1;
	}
	else if( nResult == 0 )
	{
		// timeout 발생시
		return 0;
	}

	//_LOG( LDBG, "system info packet header OK. body size[%u]", m_uPacketDataLen );


	// Header Data 정상 수신시... 
	// - command code 정보 확인
	if( m_packetHeader.command[0] != COMMAND_TRANSFER_SYSTEM )
	{
		// 미정의된 command code 인 경우
		LOG( LERR, "Not defined command code." );
		PrintHeaderToLog();
		return -1;
	}
	else
	{
		// system 상태 정보를 수신한 경우....
		// - 수신된 protocol_version 정보를 확인하여 파싱 가능한지 검사.
		if( m_packetHeader.protocol_version > PARSE_MAX_PROTOCOL_VERSION_SYSTEM )
		{
			// 파싱 불가능한 통신 규격인 경우.....
			LOG( LERR, "Not support protocol version. [COMMAND_TRANSFER_SYSTEM] support[%02x] request[%02x]."
					, PARSE_MAX_PROTOCOL_VERSION_SYSTEM, m_packetHeader.protocol_version );

			PrintHeaderToLog();
			return -1;
		}

		// Data 부분 수신 처리.
		if( GetPacketBodySystem( strHostname, pData ) == false )
		{
			// body 수신 처리시 오류 발생			
			return -1;
		}

		return 1;
	}
}

// 수신된 Data 를 전달받은 인자에 설정하여 반환 처리.
// param [out] strHostname      장비의 hostname 정보.
// param [out] iServiceStat     수신된 data
// param [out] iStorageMaxUsage 수신된 data
// param [in]  timeout	   대기 timeout (sec)		
// return  -1 : Socket 오류 또는 잘못된 Data 수신시 ( RC ID 가 다른 경우 포함)
//          0 : timeout 발생시
//          1 : 정상
int CSocketCsagentd::GetServiceInfo( std::string & strHostname, int & iServiceStat, int & iStorageMaxUsage, int timeout )
{
	// Header 정보를 수신
	int nResult = GetPacketHeader( timeout );

	if( nResult == -1 )
	{
		// Socket 끊김 또는 정의되지 않은 Data 수신시.
		return -1;
	}
	else if( nResult == 0 )
	{
		// timeout 발생시
		return 0;
	}

	//_LOG( LDBG, "check info packet header OK. body size[%u]", m_uPacketDataLen );
	

	// Header Data 정상 수신시... 
	// - command code 정보 확인
	if( m_packetHeader.command[0] != COMMAND_TRANSFER_SERVICE )
	{
		// 미정의된 command code 인 경우
		LOG( LERR, "Not defined command code." );
		PrintHeaderToLog();
		return -1;
	}
	else
	{
		// system 상태 정보를 수신한 경우....
		// - 수신된 protocol_version 정보를 확인하여 파싱 가능한지 검사.
		if( m_packetHeader.protocol_version > PARSE_MAX_PROTOCOL_VERSION_SERVICE )
		{
			// 파싱 불가능한 통신 규격인 경우.....
			LOG( LERR, "Not support protocol version. [COMMAND_TRANSFER_SERVICE] support[%02x] request[%02x]."
				 , PARSE_MAX_PROTOCOL_VERSION_SERVICE, m_packetHeader.protocol_version );

			PrintHeaderToLog();
			return -1;
		}

		// Data 부분 수신 처리.
		if( GetPacketBodyService( strHostname, iServiceStat, iStorageMaxUsage ) == false )
		{
			// body 수신 처리시 오류 발생			
			return -1;
		}

		return 1;
	}
}



// Packet Header 정보를 Log 파일에 Logging 처리 ( Debug 처리를 위한 함수)
void CSocketCsagentd::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, "------------------------------------" );
}


// Packet Header 부분의 수신 처리를 위한 함수
// param timeout [in] 대기시간.
// retrun 0 : timeout
//        1 : 성공
//       -1 : socket 오류 또는 정의된 Data 가 아닌 경우.
int CSocketCsagentd::GetPacketHeader( 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_CSAGENTD )
		{
			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 );

		return 1;
	}
}

// COMMAND_TRANSFER_SYSTEM 요청 관련 Packet Body 부분을 수신 처리
bool CSocketCsagentd::GetPacketBodySystem( std::string & strHostname, struct SystemInfo * pData )
{
	// 현재는 protocol 버전이 0x00 1개이므로.... 본 함수에 코딩...
	// 향후 버전이 추가되면... 버전별 파싱 함수 따로 분리하는 것이 좋을 듯.

	// protocol_version == 0x00 인 경우.	
	if( m_packetHeader.protocol_version == 0x00 )
	{
		// 수신된 Body 를 저장할 변수 생성.
		struct PacketDataSystem_V00 stBody;

		// Body 부분 수신 처리.
		int nRead = ReadNTimeout( &stBody, m_uPacketDataLen, 15 );
		if( nRead == -2 )
		{
			// timeout 발생시
			// - 지정된 시간 동안 Header 에 기록된 Body 크기를 읽지 못했으므로. 오류로 처리
			LOG( LERR, "system info body[%u] read timeout.", m_uPacketDataLen );
			return false;
		}
		else if( nRead <= 0 )
		{
			// timeout 을 제외한 그 밖의 오류 발생시
			LOG( LERR, "system info body read error[%d]", nRead );
			return false;
		}
		else
		{
			// Body 부분 정상 수신시...
			// 1. RC ID 정보 비교 후. (대소문자 무시)
			// 2. 정상인 경우..수신된 Data 를 파싱하여 인자로 전달받은 data 에 저장 처리.
			if( strcasecmp( m_strRcId.c_str(), stBody.rcid ) != 0 )
			{
				LOG( LERR, "system info body error. RC ID[%s] not match. request[%s]"
					 , m_strRcId.c_str(), stBody.rcid );
				return false;
			}

			// 0x00 버전에 대한 data 파싱.
			strHostname = stBody.hostname;

			pData->load_avg[0] = stBody.load_avg[0];
			pData->load_avg[1] = stBody.load_avg[1];
			pData->load_avg[2] = stBody.load_avg[2];

			pData->memory_total = stBody.memory_total;
			pData->memory_free  = stBody.memory_free;
			pData->memory_usage = stBody.memory_usage;

			pData->swap_total = stBody.swap_total;
			pData->swap_used  = stBody.swap_used;
			pData->swap_usage = stBody.swap_usage;

			pData->storage_total     = stBody.storage_total;
			pData->storage_avail     = stBody.storage_avail;
			pData->storage_max_usage = stBody.storage_max_usage;

			pData->disk_max_queue = stBody.disk_max_queue;
			pData->disk_max_busy  = stBody.disk_max_busy;

			pData->network_packet_in   = stBody.network_packet_in;
			pData->network_packet_out  = stBody.network_packet_out;
			pData->network_traffic_in  = stBody.network_traffic_in;
			pData->network_traffic_out = stBody.network_traffic_out;

			pData->tcp_total        = stBody.tcp_total;
			pData->tcp_80           = stBody.tcp_80;
			pData->tcp_80_establish = stBody.tcp_80_establish;
			
			return true;
		}
	}

	return false;
}


// COMMAND_TRANSFER_SERVICE 요청 관련 Packet Body 부분을 수신 처리
bool CSocketCsagentd::GetPacketBodyService( std::string & strHostname, int & iServiceStat, int & iStorageMaxUsage )
{
	// 현재는 protocol 버전이 0x00 1개이므로.... 본 함수에 코딩...
	// 향후 버전이 추가되면... 버전별 파싱 함수 따로 분리하는 것이 좋을 듯.

	// protocol_version == 0x00 인 경우.	
	if( m_packetHeader.protocol_version == 0x00 )
	{
		// 수신된 Body 를 저장할 변수 생성.
		struct PacketDataService_V00 stBody;

		// Body 부분 수신 처리.
		int nRead = ReadNTimeout( &stBody, m_uPacketDataLen, 15 );
		if( nRead == -2 )
		{
			// timeout 발생시
			// - 지정된 시간 동안 Header 에 기록된 Body 크기를 읽지 못했으므로. 오류로 처리
			LOG( LERR, "service info body[%u] read timeout.", m_uPacketDataLen );
			return false;
		}
		else if( nRead <= 0 )
		{
			// timeout 을 제외한 그 밖의 오류 발생시
			LOG( LERR, "service info body read error[%d]", nRead );
			return false;
		}
		else
		{
			// Body 부분 정상 수신시...
			// 1. RC ID 정보 비교 후. (대소문자 무시)
			// 2. 정상인 경우..수신된 Data 를 파싱하여 인자로 전달받은 data 에 저장 처리.
			if( strcasecmp( m_strRcId.c_str(), stBody.rcid ) != 0 )
			{
				LOG( LERR, "service info body error. RC ID[%s] not match. request[%s]"
					 , m_strRcId.c_str(), stBody.rcid );
				return false;
			}

			// 0x00 버전에 대한 data 파싱.
			strHostname = stBody.hostname;

			iServiceStat     = stBody.service_stat;
			iStorageMaxUsage = stBody.storage_max_usage;


			return true;
		}
	}

	return false;
}

