#include "BaseSocket.h"


#include <sys/select.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>


#define SOCKET_NOT_VALID		-1
#define MAX_SEND_RETRY_COUNT	10	// Socket send 실패시 최대 재전송 시도 횟수.


/// @brief 생성자.
/// @param socket [in] 처리할 socket descriptor
CBaseSocket::CBaseSocket( const int& socket )
	: m_sock( socket )
{
	if( m_sock < 0 )
	{
		m_sock = SOCKET_NOT_VALID;
		m_bConnected = false;
	}
	else
	{
		m_bConnected = true;
	}
}

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

/// @brief 소켓의 Close 처리를 수행함.
void CBaseSocket::Close()
{
	m_bConnected = false;

	if( m_sock != SOCKET_NOT_VALID )
	{
		close( m_sock );
		m_sock = SOCKET_NOT_VALID;
	}
}

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

	unsigned int nHost = ConversionAddr( szTarget.c_str() );
	if( nHost == INADDR_NONE )
	{
		LOG( LERR, "target host name resolve fail. [%s]->INADDR_NONE", szTarget.c_str());
		return false;
	}

	struct sockaddr_in stTargetAddr;

	m_sock = socket(AF_INET, SOCK_STREAM, 0);
	if( m_sock == -1 )
	{
		int errorNum = errno;

		LOG( LERR, "socket create failed.[%d][%s] Target:[%s]", errorNum, strerror(errorNum), szTarget.c_str() );

		m_sock = SOCKET_NOT_VALID;
		return false;
	}

	stTargetAddr.sin_family = AF_INET;
	stTargetAddr.sin_addr.s_addr = nHost;
	stTargetAddr.sin_port = htons( nPort );

	// NEW 2012-05-18 huibong
	// Connnection 종료시 많은 TIME_WAIT 상태 발생으로 인해 .. 이를 제거하기 위해 SO_LINGER 옵션을 설정처리한다.
	struct linger opt_linger;
	opt_linger.l_onoff  = 1; /* LINGER ON */

	// CHG 2014-05-27 huibong
	// 아래와 같이 No Wait 로 처리할 경우... Server 역활 수행 중 Close 신호를 받지 못하는 경우가 발생 가능함.
	// 따라서 5 sec 정도 대기하도록 수정 처리한다.
	//opt_linger.l_linger = 0; /* No Wait => 0 for abortive disconnect */
	opt_linger.l_linger = 5; // 5 sec 대기

	int result = setsockopt( m_sock, SOL_SOCKET, SO_LINGER, &opt_linger, sizeof(opt_linger));
	if( result != 0 )
	{
		int errorNum = errno;
		LOG( LERR, "setsockopt func SO_LINGER set fail.[%d][%s]", errorNum, strerror(errorNum));

	}

	/* Send Timeout 설정. */
	struct timeval tv_timeo = { 5, 0 };
	
	result = setsockopt( m_sock, SOL_SOCKET, SO_SNDTIMEO, &tv_timeo, sizeof(tv_timeo));
	if( result != 0 )
	{
		int errorNum = errno;
		LOG( LERR, "setsockopt func SO_SNDTIMEO set fail.[%d][%s]", errorNum, strerror(errorNum));

	}

	if(connect(m_sock, (struct sockaddr *)&stTargetAddr, sizeof(stTargetAddr)) < 0 )
	{
		int errorNum = errno;
		LOG( LERR, "connect fail.[%s][%d] [%d][%s]", szTarget.c_str(), nPort, errorNum, strerror( errorNum ) );

		Close();
		return false;
	}
	else
	{
		m_bConnected = true;
		return true;
	}
}


/// @brief 멤버 변수인 m_sock 이 유효하고 연결된 상태인 경우 true 반환.
/// @return socket이 유효하지 않거나 연결이 끊어지 경우 false 반환, 그외에는 true 반환.
bool CBaseSocket::IsValidSocket()
{
	if( m_sock != SOCKET_NOT_VALID && m_bConnected == true )
		return true;
	else
		return false;
}

/// @brief m_sock 으로부터 지정된 size 만큼 데이터 read 를 시도 ( read 함수와 동일 )
/// @param vptr [out] Read 된 데이터를 저장하기 위한 변수에 대한 포인터.
/// @param size [in] read 하고자 하는 데이터의 크기.
/// @return read 된 데이터의 크기. 0: fd closed, -1: 오류 
ssize_t CBaseSocket::Read( void * vptr, size_t size )
{
	if( IsValidSocket() == false )
		return 0;

	ssize_t nRead = 0;
	while( (nRead = read( m_sock, vptr, size )) < 0 )
	{
		int errorNum = errno;
		if( errorNum == EINTR || errorNum == EAGAIN || errorNum == EWOULDBLOCK )
		{
			nRead = 0;
			continue;
		}
		else if( errorNum == ECONNRESET || errorNum == EPIPE ) 
		{
			// 2010-07-23 BUG huibong 잘못된 대입연산자를 비교연산자로 수정.
			// 2015-08-28 CHG huibong EPIPE(32) 오류 발생시.. 연결 종료로 처리되도록 수정
			m_bConnected = false;
			return 0;
		}
		else
		{
			LOG( LERR, "read func fail.[%d][%s]", errorNum, strerror(errorNum) );
			return -1;
		}
	}

	return nRead;
}

/// @brief m_sock 로부터 지정된 size 만큼 read 를 시도, \n
///< 만약 지정된 크기만큼 데이터가 존재하지 않을 경우 해당 크기만큼 데이터를 read 할때까지 대기.
/// @param vptr [out] Read 된 데이터를 저장하기 위한 변수에 대한 포인터.
/// @param size [in] read 하고자 하는 데이터의 크기.
/// @return read 된 데이터의 크기. 0: fd closed, -1: 오류 
ssize_t CBaseSocket::ReadN( void * vptr, size_t size )
{
	if( IsValidSocket() == false )
		return 0;

	ssize_t nRead = 0;
	size_t  nLeft = size;
	char * ptr = (char *)vptr;
	
	while( nLeft > 0 )
	{
		if( (nRead = read( m_sock, ptr, nLeft )) < 0 )
		{
			int errorNum = errno;
			if( errorNum == EINTR || errorNum == EAGAIN || errorNum == EWOULDBLOCK )
			{
				nRead = 0;
				continue;
			}
			else if( errorNum == ECONNRESET || errorNum == EPIPE )
			{
				// 2015-08-28 CHG huibong EPIPE(32) 오류 발생시.. 연결 종료로 처리되도록 수정
				m_bConnected = false;
				return 0;
			}
			else
			{
				LOG( LERR, "read func fail.[%d][%s]", errorNum, strerror(errorNum) );
				return -1;
			}
		}
		else if( nRead == 0 )
		{
			m_bConnected = false;
			return 0;
		}

		nLeft -= nRead;
		ptr   += nRead;
	}

	return size;
}

/// @brief m_sock 로부터 지정된 size 만큼 read 를 시도, \n
///< 만약 지정된 크기만큼 데이터가 존재하지 않을 경우 해당 크기만큼 데이터를 read 할때까지 대기. \n
///< 또는 지정된 Timeout 값 동안 read 를 하지 못하는 경우 오류 처리.
/// @param vptr [out] Read 된 데이터를 저장하기 위한 변수에 대한 포인터.
/// @param size [in] read 하고자 하는 데이터의 크기.
/// @param timeout [in] Timeout value (sec)
/// @return read 된 데이터의 크기. 0: fd closed, -1: 오류 , -2: Timeout
ssize_t CBaseSocket::ReadNTimeout( void * vptr, size_t size, int timeout )
{
	if( IsValidSocket() == false )
		return 0;

	ssize_t nRead = 0;
	size_t  nLeft = size;
	char * ptr    = (char *)vptr;
	
	struct timeval timeOver;
	int result;

	fd_set selectFds;
	FD_ZERO( &selectFds );

	while( nLeft > 0 )
	{
		timeOver.tv_sec  = timeout;
		timeOver.tv_usec = 0;
		FD_SET( m_sock, &selectFds );

		result = select( m_sock+1, &selectFds, (fd_set *)NULL, (fd_set *)NULL, &timeOver );

		if( result > 0 )
		{
			if( FD_ISSET( m_sock, &selectFds ))
			{
				if( (nRead = read( m_sock, ptr, nLeft) ) < 0 )
				{
					int errorNum = errno;
					if(errorNum == EINTR || errorNum == EAGAIN || errorNum == EWOULDBLOCK)
					{
						nRead = 0;
						continue;
					}
					else if( errorNum == ECONNRESET || errorNum == EPIPE )
					{
						// 2015-08-28 CHG huibong EPIPE(32) 오류 발생시.. 연결 종료로 처리되도록 수정
						m_bConnected = false;
						return 0;
					}
					else
					{
						LOG( LERR, "read func fail.[%d][%s]", errorNum, strerror(errorNum) );
						return -1;
					}
				}
				else if( nRead == 0 )
				{
					m_bConnected = false;
					return 0;
				}

				nLeft -= nRead;
				ptr   += nRead;
			}

		}
		else if( result == 0 ) // Timeout
		{
			//LOG( LDBG, "read timeout");
			return -2;
		}
		else
		{
			int errorNum = errno;
			LOG( LERR, "select func error.[%d][%s]", errorNum, strerror(errorNum) );
			
			return -1;
		}
	}

	return size;
}

/// @brief m_sock 로부터 지정된 size 만큼 read 를 시도, \n
///< 만약 지정된 크기만큼 데이터가 존재하지 않을 경우 해당 크기만큼 데이터를 read 할때까지 대기. \n
///< 또는 지정된 Timeout 값 동안 read 를 하지 못하는 경우 오류 처리.
///< 본 함수는 Socket 상에 이미 Data 가 존재하는 경우에만 사용.
/// @param vptr [out] Read 된 데이터를 저장하기 위한 변수에 대한 포인터.
/// @param size [in] read 하고자 하는 데이터의 크기.
/// @param timeout [in] Timeout value (sec)
/// @return read 된 데이터의 크기. 0: fd closed, -1: 오류, -2: Timeout
ssize_t CBaseSocket::ReadNTimeout2( void * vptr, size_t size, int timeout )
{
	if( IsValidSocket() == false )
		return 0;

	ssize_t nRead = 0;
	size_t  nLeft = size;
	char * ptr    = (char *)vptr;
	int errorNum;

	// 우선은 읽기 시도.
	if( (nRead = read( m_sock, ptr, nLeft) ) < 0 )
	{
		errorNum = errno;
		if( errorNum == EINTR || errorNum == EAGAIN || errorNum == EWOULDBLOCK )
		{
			nRead = 0;	
		}
		else if( errorNum == ECONNRESET || errorNum == EPIPE )
		{
			// 2015-08-28 CHG huibong EPIPE(32) 오류 발생시.. 연결 종료로 처리되도록 수정

			m_bConnected = false;
			return 0;
		}
		else
		{
			LOG( LERR, "read func fail.[%d][%s]", errorNum, strerror(errorNum) );
			return -1;
		}
	}
	else if( nRead == 0 )
	{
		m_bConnected = false;
		return 0;
	}

	nLeft -= nRead;
	ptr   += nRead;

	// 만약 더 받을 데이터가 존재한다면.
	if( nLeft > 0 )
	{
		struct timeval timeOver;
		int result;

		fd_set selectFds;
		FD_ZERO( &selectFds );

		while( nLeft > 0 )
		{
			timeOver.tv_sec  = timeout;
			timeOver.tv_usec = 0;
			FD_SET( m_sock, &selectFds );

			result = select( m_sock+1, &selectFds, (fd_set *)NULL, (fd_set *)NULL, &timeOver );

			if( result > 0 )
			{
				if( FD_ISSET( m_sock, &selectFds ))
				{
					if( (nRead = read( m_sock, ptr, nLeft) ) < 0 )
					{
						errorNum = errno;
						if(errorNum == EINTR || errorNum == EAGAIN || errorNum == EWOULDBLOCK)
						{
							nRead = 0;
							continue;
						}
						else if( errorNum == ECONNRESET || errorNum == EPIPE )
						{
							// 2015-08-28 CHG huibong EPIPE(32) 오류 발생시.. 연결 종료로 처리되도록 수정
							m_bConnected = false;
							return 0;
						}
						else
						{
							LOG( LERR, "read func fail.[%d][%s]", errorNum, strerror(errorNum) );
							return -1;
						}
					}
					else if( nRead == 0 )
					{
						m_bConnected = false;
						return 0;
					}

					nLeft -= nRead;
					ptr   += nRead;
				}
			}
			else if( result == 0 ) // Timeout
			{
				//LOG( LDBG, "read timeout");
				return -2;
			}
			else
			{
				errorNum = errno;
				LOG( LERR, "select func error.[%d][%s]", errorNum, strerror(errorNum) );

				return -1;
			}
		}
	}

	return size;
}

/// @brief m_sock 으로 지정된 크기만큼 vptr 의 데이터를 전송 시도.\n
///< Send Timeout 옵션 설정으로 Write Timeout 설정 가능. \n
///< 지정된 횟수만큼 재전송 실패시 오류로 처리함.
/// @param vptr [in] 전달할 데이터를 저장한 변수에 대한 포인터.
/// @param size [in] write 하고자 하는 데이터의 크기.
/// @return write 된 데이터의 크기. 0: fd closed, -1: 오류 
ssize_t CBaseSocket::WriteN( const void * vptr, size_t size )
{
	if( IsValidSocket() == false )
		return 0;

	size_t nLeft;
	ssize_t nWrite;

	const char * ptr = (const char *)vptr;
	nLeft = size;

	int nTryCount = 0;

	while( nLeft > 0 )
	{
		if( (nWrite = send( m_sock, ptr, nLeft, 0 )) < 0 )
		{
			int errorNum = errno;

			if( errorNum == EINTR || errorNum == EAGAIN || errorNum == EWOULDBLOCK )
			{
				nWrite = 0;
				++nTryCount;
			}
			else if( errorNum == ECONNRESET || errorNum == EPIPE )
			{
				// 2015-08-28 CHG huibong EPIPE(32) 오류 발생시.. 연결 종료로 처리되도록 수정
				m_bConnected = false;
				return 0;
			}
			else
			{
				LOG( LERR, "send func fail.[%d][%s]", errorNum, strerror(errorNum));

				return -1;
			}
		}
		else if( nWrite == 0 )
		{
			m_bConnected = false;
			return 0;
		}

		if( nTryCount > MAX_SEND_RETRY_COUNT )
		{
			LOG( LERR, "send func failure due to exceeding count of retry[%d/%d]", nTryCount, MAX_SEND_RETRY_COUNT );
			return 0 ;
		}

		nLeft -= nWrite;
		ptr   += nWrite;
	}

	return size;
}

// CHG 2011-05-03 huibong
// gethostbyname() 함수가 Thread Safe 하지 않기 때문에
// DNS resolve 처리시 잘못된 정보를 반환할 가능성이 존재
// 이에 따라 본 함수를 수정처리함.
/*
unsigned int CBaseSocket::ConversionAddr( const char * name )
{
	struct hostent *he;
	int max;
	unsigned int retval;

	if ((retval = inet_addr(name)) != INADDR_NONE)
		return retval;

	he = gethostbyname(name);
	if (he == NULL) 
		return INADDR_NONE;

	for (max = 0; he->h_addr_list[max]; max++) ;
	if (max == 1) 
		return *((unsigned int *)(he->h_addr_list[0]));
	else 
		return *((unsigned int *)(he->h_addr_list[random() % max]));
}
*/

unsigned int CBaseSocket::ConversionAddr( const char * name )
{
	unsigned int retval;
	struct addrinfo hints;
	struct addrinfo *result = NULL;
	int error;


	// 전달받은 정보가 IP 주소인 경우...
	// - DNS resolve 할 필요 없이 변환시킨 값을 그대로 사용한다.
	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);

	// CHG 2014-11-14 huibong
	// getaddrinfo 호출시 종종 EAI_NONAME 오류가 반환됨. (#20783)
	// - 따라서 오류 반환시 재시도하도록 기능 추가 
	// CHG 2015-07-15 huibong
	// usleep 의 multi thread 상에서 block 발생 가능
	// - nanosleep 을 사용토록 변경 처리
	for( int count = 0 ; count < 3; count++ )
	{
		error = getaddrinfo( name, NULL, &hints, &result );

		// 오류 발생시
		if( error != 0 )
		{
			if( result != NULL)
			{
				freeaddrinfo(result);
				result = NULL;
			}

			// 잠시 대기 후 재시도 처리
			struct timespec sleep;
			sleep.tv_sec = 0;
			sleep.tv_nsec = 500000000;   // 0.5 sec
			nanosleep( &sleep, NULL );
		}
		else
		{
			// 정상 처리된 경우...
			// - loop 탈출
			break;
		}
	}

	// 최종 오류 발생시
	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;

}

