/***************************************************************************
                   Socket Control Class (SocketControl.cpp)
                -----------------------------------------

	begin      : 2013/02/20
	copyright  : (C) 2013 Solbox Inc.
	author     : Development 1 Team
					- 2013/02/20 - 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 "SocketControl.h"
#include "Logger.h"
#include "UtilFn.h"

#define  SOCKET_TIMEOUT		5

CSocketControl::CSocketControl()
	: CBaseSocket( SOCKET_NOT_VALID ), m_ssl(NULL)
{

}

CSocketControl::CSocketControl( const int & socket, SSL * ssl)
	: CBaseSocket( socket ), m_ssl(ssl)
{

}

CSocketControl::~CSocketControl()
{

}

int CSocketControl::SetOption()
{
	if( m_sock == SOCKET_NOT_VALID )
		return -1;

	int result = 0;
	/* Time wait 방지 */
	struct linger   ling; 

	ling.l_onoff = 1; 
	ling.l_linger = 10;      /* 0 for abortive disconnect */ 

	result =  setsockopt(m_sock, SOL_SOCKET, SO_LINGER, &ling, sizeof(ling));
	if( result != 0 )
	{
		int errorNum = errno;
		LOG( LERR, "SO_LINGER set error.[%d][%s]", errorNum, strerror(errorNum));
		return -1;
	}

	struct timeval tv_timeo = { SOCKET_TIMEOUT, 0 };
	/* Recv Timeout 설정. */
	result = setsockopt( m_sock, SOL_SOCKET, SO_RCVTIMEO, &tv_timeo, sizeof(tv_timeo));
	if( result != 0 )
	{
		int errorNum = errno;
		LOG( LERR, "SO_RCVTIMEO set error.[%d][%s]", errorNum, strerror(errorNum));
		return -1;
	}

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

	return 0;
}

ssize_t CSocketControl::ReadnSSL( void *vptr, size_t n )
{
	ssize_t nread = 0;
	size_t nleft = n;
	char * ptr = (char *)vptr;

	while(nleft > 0)
	{
		if((nread = SSL_read(m_ssl, ptr, nleft)) < 0)
		{
			int error_code = SSL_get_error(m_ssl, nread);
			LOG( LDBG, "SSL read error code [%d]", error_code);

			switch( error_code)
			{
				char tempBuffer[1024];
				memset(tempBuffer, 0x00, sizeof(tempBuffer));

			case SSL_ERROR_NONE:
			case SSL_ERROR_ZERO_RETURN:
			case SSL_ERROR_WANT_READ:
			case SSL_ERROR_WANT_WRITE:
				nread = 0;
				LOG( LWAR, "SSL read sleep [%d]", error_code);
				solusleep(50000);
				break;
			case SSL_ERROR_WANT_CONNECT:
			case SSL_ERROR_WANT_ACCEPT:
			case SSL_ERROR_WANT_X509_LOOKUP:
			case SSL_ERROR_SYSCALL:
			case SSL_ERROR_SSL:
				ERR_error_string_n(ERR_get_error(), tempBuffer, sizeof(tempBuffer));
				LOG( LERR, "SSL Read Error :[%s]", tempBuffer);
				return -1;
			}
		}
		else if(nread == 0)
		{
			LOG(LWAR, "SSL read  return 0.[%zd]", nread);
			return 0;
		}

		nleft -= nread;
		ptr += nread;
	}

	return n;/* ?? */
}

ssize_t CSocketControl::WritenSSL(void *vptr, size_t n)
{
	size_t nleft;
	ssize_t nwritten;

	const char * ptr = (const char *)vptr;
	nleft = n;

	int tryCount = 0;

	while(nleft > 0)
	{
		//fprintf(stderr, "[%s] ssl write before nleft[%d]\n", __func__, nleft);
		nwritten = SSL_write( m_ssl, ptr, nleft);
		//fprintf(stderr, "[%s] ssl write after n[%d]\n", __func__, nwritten);


		if( nwritten < 0 )
		{
			int error_code = SSL_get_error(m_ssl, nwritten);
			LOG( LDBG, "SSL Write error code [%d]", error_code);

			switch( error_code )
			{
				char tempBuffer[1024];

				memset(tempBuffer, 0x00, sizeof(tempBuffer));

			case SSL_ERROR_NONE:
			case SSL_ERROR_ZERO_RETURN:
			case SSL_ERROR_WANT_READ:
			case SSL_ERROR_WANT_WRITE:
				nwritten = 0;
				++tryCount;
				LOG( LWAR, "SSL Write sleep [%d]", error_code);
				solusleep(50000);
				break;
			case SSL_ERROR_WANT_CONNECT:
			case SSL_ERROR_WANT_ACCEPT:
			case SSL_ERROR_WANT_X509_LOOKUP:
			case SSL_ERROR_SYSCALL:
			case SSL_ERROR_SSL:
				ERR_error_string_n(ERR_get_error(), tempBuffer, sizeof(tempBuffer));
				LOG( LERR, "SSL Write Error [%s]", tempBuffer);
				return -1;
			}
		}
		else if( nwritten == 0)
		{
			return 0;
		}

		if( tryCount > 60 )
		{
			LOG( LERR, "SSL Write Try Count Over !!!");
			return -1;
		}

		nleft -= nwritten;
		ptr += nwritten;
	}

	return n;
}

ssize_t CSocketControl::ReadData(void *vptr, size_t n)
{
	ssize_t nret = 0;
	
	if(m_ssl)
		nret = ReadnSSL(vptr, n);
	else 
		nret = ReadNTimeout(vptr, n);

	ostringstream msg; 
	msg << "Socket ReadData() result.[" << nret << "]";
	LOGACONSOLE( LDBG, msg);

	return nret;
}

ssize_t CSocketControl::ReadDataEx(void *vptr, size_t n)
{
	ssize_t nret = 0;

	if (m_ssl)
		nret = ReadSSL(vptr, n);
	else
		nret = ReadTimeout(vptr, n);

	ostringstream msg;
	msg << "Socket ReadDataEx() result.[" << nret << "]";
	LOGACONSOLE(LDBG, msg);

	return nret;
}

ssize_t CSocketControl::ReadTimeout(void *vptr, size_t n, int timeout)
{
	if (IsValidSocket() == false)
		return 0;

	ssize_t nRead = 0;
	char * ptr = (char *)vptr;

	struct timeval timeOver;
	int result;

	fd_set selectFds;
	FD_ZERO(&selectFds);


	timeOver.tv_sec = timeout;
	timeOver.tv_usec = 0;
	FD_SET(m_sock, &selectFds);

	do {
		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, n)) < 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;
				}
			}

		}
		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;
		}
	} while (false);
	
	return nRead;
}

ssize_t CSocketControl::ReadSSL(void *vptr, size_t n)
{
	ssize_t nread = 0;
	char * ptr = (char *)vptr;

	if ((nread = SSL_read(m_ssl, ptr, n)) < 0)
	{
		int error_code = SSL_get_error(m_ssl, nread);
		LOG(LDBG, "SSL read error code [%d]", error_code);

		switch (error_code)
		{
			char tempBuffer[1024];
			memset(tempBuffer, 0x00, sizeof(tempBuffer));

		case SSL_ERROR_NONE:
		case SSL_ERROR_ZERO_RETURN:
		case SSL_ERROR_WANT_READ:
		case SSL_ERROR_WANT_WRITE:
			nread = 0;
			LOG(LWAR, "SSL read sleep [%d]", error_code);
			solusleep(50000);
			break;
		case SSL_ERROR_WANT_CONNECT:
		case SSL_ERROR_WANT_ACCEPT:
		case SSL_ERROR_WANT_X509_LOOKUP:
		case SSL_ERROR_SYSCALL:
		case SSL_ERROR_SSL:
			ERR_error_string_n(ERR_get_error(), tempBuffer, sizeof(tempBuffer));
			LOG(LERR, "SSL Read Error :[%s]", tempBuffer);
			return -1;
		}
	}
	else if (nread == 0)
	{
		LOG(LWAR, "SSL read  return 0.[%zd]", nread);
		return 0;
	}

	return nread;/* ?? */
}

ssize_t CSocketControl::WriteData(void *vptr, size_t n)
{
	ssize_t nret = 0;

	if(m_ssl)
		nret = WritenSSL(vptr, n);
	else 
		nret = WriteN(vptr, n);

	ostringstream msg; 
	msg << "Socket WriteData() result.[" << nret << "]";
	LOGACONSOLE( LDBG, msg);

	return nret;
}

string CSocketControl::GetMyIPString()
{
	string r;
	union {
		struct sockaddr s;
		struct sockaddr_in v4;
#ifdef AF_INET6
		struct sockaddr_in6 v6;
#endif // AF_INET6
	} u;

	if (m_sock == SOCKET_NOT_VALID) return r;


	socklen_t slen = sizeof(u);


	if (getsockname(m_sock, &u.s, &slen) == 0)
	{
#ifdef AF_INET6
		if (u.s.sa_family == AF_INET6)
		{
			socklen_t clientSockLen;
			char tempBuffer[INET6_ADDRSTRLEN] = {0};

			clientSockLen = sizeof(u.v6);

			if (inet_ntop(AF_INET6, (void *)&u.v6.sin6_addr, tempBuffer, sizeof(tempBuffer)) != NULL)
			{
				r = tempBuffer;
			}


		} else 
#endif // AF_INET6
		if (u.s.sa_family == AF_INET)
		{
			socklen_t clientSockLen;
			char tempBuffer[INET_ADDRSTRLEN] = {0};

			clientSockLen = sizeof(&u.v4);

			if (inet_ntop(AF_INET, (void *)& u.v4.sin_addr, tempBuffer, sizeof(tempBuffer)) != NULL)
			{
				r = tempBuffer;
			}

		}
		else
		{
			LOG( LERR, "My IP String : UNKNOWN??");
		}
	}

	LOG( LDBG, "My IP String : %s",r.c_str());

	return r;
}
