/***************************************************************************
                   Service Class (Service.cpp)
                -----------------------------------------

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

#include <unistd.h>

#define DEFAULT_ACCEPT_WAIT_COUNT	30

#define ACCEPT_TIMEOUT				60

// class CService
CService::CService()
	: m_done(false), m_ipv6(false), m_listenfd(-1), m_ctx(NULL)
{

}
CService::CService(CServiceInfo& info)
	: m_done(false), m_ipv6(false), m_listenfd(-1), m_ctx(NULL), m_info(info)
{

}

CService::~CService()
{
	if(m_ctx)
		SSL_CTX_free(m_ctx);
}

SSL* CService::CerateSSL(int nClientfd)
{
	ostringstream msg;

	SSL* ret = NULL;

	// TCP connection is ready. Do server side SSL.
	// create a new SSL structure for a connection
	// SSL 구조체 생성
	ret = SSL_new(m_ctx);
	if(ret == NULL)
	{
		msg << "Couldn't allocation SSL. [Port : " << m_info.m_port << "]";
		LOGACONSOLE(LERR, msg);
		return NULL;
	}
		
	// connect the SSL object with a file descriptor
	// 연결된 소켓과 SSL과의 연결
	//SSL_set_fd (ret, nClientfd);
	// 가장 중요한 함수, 클라이언트와의 초기 협상과정, 즉 핸드쉐이크 과정을 수행
	//int err = SSL_accept (ret);
	//if( err < 0 )
	//{
	//	msg << "SSL Accept Fail. ["<< err <<"][Port : " << m_info.m_port << "]";
	//	SSL_free(ret);
	//	LOGACONSOLE(LERR, msg);
	//	return NULL;
	//}
	
	return ret;
}

static int password_cb(char *buf,int num,int rwflag,void *userdata)
{
	const char *password = "rootsecurity";
	strncpy(buf, password, num);
	return strlen(buf);
}

SSL_CTX * CService::initializeCTX()
{
	ostringstream msg;

	SSL_METHOD *meth;
	SSL_CTX *ctx = NULL;

	/* Global system initialization*/
	// 기본적인 알고리즘 로드
	SSL_library_init();
	// 모든 에러 스트링 로드
	SSL_load_error_strings();

	/* Create our context*/
	// SSL 버전3 프로토콜 사용
	meth=(SSL_METHOD *)SSLv3_method();
	// SSL 컨텍스트 생성
	ctx=SSL_CTX_new(meth);
	if( ctx == NULL)
	{
		//ERR_print_errors_fp(stderr);
		msg << "Couldn't allocation CTX. [Port : " << m_info.m_port << "]";
		LOGACONSOLE(LERR, msg);
		return NULL;
	}

	// Private Key File Loading 시 요구되는 패스워드를 자동으로 입력한다.
	SSL_CTX_set_default_passwd_cb(ctx,password_cb);

	char ca_path[256];
	/* Load our keys and certificates*/
	// 자신의 인증서를 파일에서 로딩한다.
	snprintf( ca_path, sizeof(ca_path), CMyConfig::GetInstance()->GetSSLCertFile());
	if(!(SSL_CTX_use_certificate_file(ctx,ca_path,SSL_FILETYPE_PEM)))
	{
		msg << "Couldn't read certificate file. [Port : " << m_info.m_port << "]";
		LOGACONSOLE(LERR, msg);
		SSL_CTX_free(ctx);
		return NULL;
	}

	 // 자신의 개인키를 파일에서 로딩한다.
	snprintf( ca_path, sizeof(ca_path), CMyConfig::GetInstance()->GetSSLPrivateFile());
	if(!(SSL_CTX_use_PrivateKey_file(ctx,ca_path,SSL_FILETYPE_PEM)))
	{
		msg << "Couldn't read key file. [Port : " << m_info.m_port << "]";
		LOGACONSOLE(LERR, msg);
		SSL_CTX_free(ctx);
		return NULL;
	}

	// 읽은 인증서와 개인키가 맞는지 확인 한다.
	if( !SSL_CTX_check_private_key(ctx))
	{
		msg << "Private Key does not match the certificate public key. [Port : " << m_info.m_port << "]";
		LOGACONSOLE(LERR, msg);
		SSL_CTX_free(ctx);
		return NULL;
	}

	return ctx;
}

int CService::TCPListen()
{
	ostringstream msg;
#ifdef AF_INET6
	m_listenfd = ::socket( AF_INET6, SOCK_STREAM, 0 );
	if(m_listenfd > 0)
	{
		msg << "Enable IPv6 Socket.";
		LOGACONSOLE(LINF, msg);
		m_ipv6 = true;
	}
#endif // AF_INET6
	if(m_ipv6 == false)
		m_listenfd = ::socket( AF_INET, SOCK_STREAM, 0 );

	if( m_listenfd == -1 )
	{
		msg << "Listen socket create failed.[" << errno << "][" 
			<< strerror(errno)  << "] [Port : " << m_info.m_port << "]";
		LOG( LERR, "%s", msg.str().c_str());
		cerr << msg.str() << endl;
		return -1;
	}

	int result = 0;
	// Socket Port Reuse Option Set
	int opt = 1;
	result = ::setsockopt( m_listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt) );
	if( result != 0 )
	{
		msg << "Listen socket option[SO_REUSEADDR] set failed. [" << errno 
			<< "][" << strerror(errno)  << "][Port : " << m_info.m_port << "]";
		LOGACONSOLE(LERR, msg);
		return -1;
	}

	// Keep Alive Option set
	opt = 1;
	result = ::setsockopt( m_listenfd, SOL_SOCKET, SO_KEEPALIVE, &opt, sizeof(opt) );
	if( result != 0 )
	{
		msg << "Listen socket option[SO_KEEPALIVE] set failed. [" << errno 
			<< "][" << strerror(errno)  << "]";
		LOGACONSOLE(LERR, msg);
		return -1;
	}

	// Recv Timeout 설정
	//  만일 리스너 소켓에 SO_RCVTIMEO를 지정하면, accept(2) 호출시에 지정된 타임아웃 동안 새로운 접속이 없으면 
	//  recv(2)처럼 EAGAIN에러로 리턴된다.
	struct timeval tv_timeo = { ACCEPT_TIMEOUT, 0 };
	result = ::setsockopt( m_listenfd, 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;
	}

#ifdef AF_INET6
	if(m_ipv6)
	{
		struct sockaddr_in6 listenSockAddrv6;
		socklen_t listenSockLen = 0;
		memset(&listenSockAddrv6, 0x00, sizeof(listenSockAddrv6));

		listenSockAddrv6.sin6_family = AF_INET;
		listenSockAddrv6.sin6_flowinfo = 0;
		listenSockAddrv6.sin6_port = htons( m_info.m_port );
		listenSockAddrv6.sin6_addr = in6addr_any;

		listenSockLen = sizeof(listenSockAddrv6);

		// Socket Bind
		result = ::bind( m_listenfd, (struct sockaddr *)&listenSockAddrv6, listenSockLen);
		if( result != 0 )
		{
			msg << "Listen socket bind failed. [" << errno << "][" << strerror(errno)  
				<< "][Port : " << m_info.m_port << "]";
			LOGACONSOLE(LERR, msg);
			return -1;
		}
	}
	else 
#endif  //AF_INET6
	{		
		struct sockaddr_in listenSockAddr;
		socklen_t listenSockLen = 0;
		bzero(&listenSockAddr,sizeof(listenSockAddr));

		listenSockAddr.sin_family = AF_INET;
		listenSockAddr.sin_port = htons( m_info.m_port );
		listenSockAddr.sin_addr.s_addr = htonl( INADDR_ANY );

		listenSockLen = sizeof(listenSockAddr);

		// Socket Bind
		result = ::bind( m_listenfd, (struct sockaddr *)&listenSockAddr, listenSockLen);
		if( result != 0 )
		{
			msg << "Listen socket bind failed. [" << errno << "][" << strerror(errno)  
				<< "][Port : " << m_info.m_port << "]";
			LOGACONSOLE(LERR, msg);
			return -1;
		}
	}

	// Socket Listen
	result = ::listen( m_listenfd, DEFAULT_ACCEPT_WAIT_COUNT );
	if( result != 0 )
	{
		msg << "Listen socket listen failed. [" << errno << "][" << strerror(errno)  
			<< "][Port : " << m_info.m_port << "]";
		LOGACONSOLE(LERR, msg);
		return -1;
	}
	
	return result;
}

int CService::init()
{
	int ret = 0;
	ret = TCPListen();

	if(m_info.m_enableSSL)
	{
		m_ctx = initializeCTX();
		if( m_ctx == NULL )
			return -1;
	}

	return ret;
}

int CService::Accept()
{
	ostringstream msg;

	// 접속 요청을 변수 생성 및 초기화.
	int nClientfd;
	struct sockaddr_in clientSockAddr;
	socklen_t clientSockLen = sizeof(clientSockAddr);

	while(!m_done)
	{
		msg << "Client Waiting... [Port:" << m_info.m_port << "]";
		LOGACONSOLE(LDBG, msg);
		nClientfd = ::accept( m_listenfd, (struct sockaddr *) &clientSockAddr, &clientSockLen );

		if( nClientfd == -1 )
		{
			if( errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK )
			{
				msg << "Service : client accept Warning. [" << errno << "][" 
					<< strerror(errno) << "][Port : " << m_info.m_port << "]";

				LOGACONSOLE(LDBG, msg);
				continue;
			}

			// 오류발생시 해당 내역 로깅처리.
			msg << "Service : client accept failed. [" << errno << "][" 
				<< strerror(errno) << "][Port : " << m_info.m_port << "]";

			LOGACONSOLE(LERR, msg);
		}
		else
		{
			// 정상적인 Client 인 경우 
#ifdef _DEBUG
			cout << "Connection from " <<  nClientfd << "," <<
				clientSockAddr.sin_addr.s_addr << ", Port " << clientSockAddr.sin_port<< endl;
#endif // _DEBUG

			SSL* ssl = NULL;

			if(m_info.m_enableSSL == 1)
			{
				ssl = CerateSSL(nClientfd);
				if(ssl == NULL)
				{
					// error 
					::close( nClientfd );
					continue;
				}
			}

			RunClient(nClientfd, ssl);
		}			
	}

	// exit
	if(m_listenfd >= 0)
	{
		::close(m_listenfd);
		m_listenfd = -1;
	}

	if(m_info.m_enableSSL)
	{
		ERR_remove_state(0);
		EVP_cleanup();
	}

	msg << "Service end.[Port : " << m_info.m_port << ",Type: " 
		<< m_info.m_type << ",SSL:" << m_info.m_enableSSL  << "]";
	LOGACONSOLE(LDBG, msg);
	return 0;
}

int	CService::Acceptv6()
{
	ostringstream msg;
#ifdef AF_INET6
	// 접속 요청을 변수 생성 및 초기화.
	int nClientfd;
	struct sockaddr_in6 clientSockAddr;
	socklen_t clientSockLen = sizeof(clientSockAddr);

	while(!m_done)
	{
		msg << "Client(v6) Waiting... [Port:" << m_info.m_port << "]";
		LOGACONSOLE(LDBG, msg);
		nClientfd = ::accept( m_listenfd, (struct sockaddr *) &clientSockAddr, &clientSockLen );

		if( nClientfd == -1 )
		{
			if( errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK )
			{
				msg << "Service(v6) : client accept Warning. [" << errno << "][" 
					<< strerror(errno) << "][Port : " << m_info.m_port << "]";

				LOGACONSOLE(LDBG, msg);
				continue;
			}

			// 오류발생시 해당 내역 로깅처리.
			msg << "Service(v6) : client accept failed. [" << errno << "][" 
				<< strerror(errno) << "][Port : " << m_info.m_port << "]";

			LOGACONSOLE(LERR, msg);
		}
		else
		{
			// 정상적인 Client 인 경우 
#ifdef _DEBUG
			cout << "Connection(v6) from " <<  nClientfd;
#endif // _DEBUG

			SSL* ssl = NULL;

			if(m_info.m_enableSSL == 1)
			{
				ssl = CerateSSL(nClientfd);
				if(ssl == NULL)
				{
					// error 
					::close( nClientfd );
					continue;
				}
			}

			RunClient(nClientfd, ssl);
		}			
	}

	// exit
	if(m_listenfd >= 0)
	{
		::close(m_listenfd);
		m_listenfd = -1;
	}

	if(m_info.m_enableSSL)
	{
		ERR_remove_state(0);
		EVP_cleanup();
	}

	msg << "Service(v6) end.[Port : " << m_info.m_port << ",Type: " 
		<< m_info.m_type << ",SSL:" << m_info.m_enableSSL  << "]";
	LOGACONSOLE(LDBG, msg);
#endif  //AF_INET6
	return 0;
}

void *CService::entry()
{
	ostringstream msg;
	msg << "Service start.[Port : " << m_info.m_port << ",Type: " 
		<< m_info.m_type << ",SSL:" << m_info.m_enableSSL  << "]";
	LOGACONSOLE(LDBG, msg);

	if(m_ipv6)
		Acceptv6();
	else 
		Accept();


	return 0;
}

void *CService::_entry_func(void *arg) 
{
	void *r = ((CService*)arg)->entry();
	return r;
}

int CService::Start()
{
	// start thread
	int ret = pthread_create(&m_threadid, 0,  _entry_func, (void*)this);
	if (ret != 0) 
	{
		ostringstream msg;		
		msg << "Service Thread create failed.[" << errno << "]";
		LOGACONSOLE(LERR, msg);
		return -1;
	}

	sleep(0);
	return ret;
}

void CService::Stop()
{
	m_done = true;

	if (m_listenfd >= 0) 
	{
		::shutdown(m_listenfd, SHUT_RDWR);
	}

	if( m_threadid > 0 )
	{
		int status = pthread_join(m_threadid, NULL);
		m_threadid = 0;
	}

	if (m_listenfd >= 0) 
	{
		::close(m_listenfd);
		m_listenfd = -1;
	}
	m_done = false;
}

// class CXMLService
CXMLService::CXMLService()
{

}

CXMLService::CXMLService(CServiceInfo& info)
	: CService(info)
{

}

CXMLService::~CXMLService()
{

}

int CXMLService::RunClient(int nClientfd, SSL *ssl /* = NULL */)
{
	ostringstream msg;
	msg << "XML Service start.[port : " << m_info.m_port << ",SSL:"  
		<< m_info.m_enableSSL  << "]";
	LOGACONSOLE(LDBG, msg);

	
	CClient * client = new CClient(nClientfd, ssl);

	if( client->Run(m_info) != 0 )
		delete client;
	
	return 0;
}

// class CSDKService
CSDKService::CSDKService()
{

}

CSDKService::CSDKService(CServiceInfo& info)
	: CService(info)
{

}

CSDKService::~CSDKService()
{

}

int CSDKService::RunClient(int nClientfd, SSL *ssl /* = NULL */)
{
	ostringstream msg;
	msg << "SDK Service start.[port : " << m_info.m_port << ",SSL:"  
		<< m_info.m_enableSSL  << "]";
	LOGACONSOLE(LDBG, msg);

	CClient * client = new CClient(nClientfd, ssl);

	if( client->Run(m_info) != 0 )
		delete client;

	return 0;
}

// class CEtcService
CEtcService::CEtcService()
{

}

CEtcService::CEtcService(CServiceInfo& info)
	: CService(info)
{

}

CEtcService::~CEtcService()
{

}

int CEtcService::RunClient(int nClientfd, SSL *ssl /* = NULL */)
{
	ostringstream msg;
	msg << "Etc Service start.[port : " << m_info.m_port << ",SSL:"  
		<< m_info.m_enableSSL  << "]";
	LOGACONSOLE(LDBG, msg);

	CClient * client = new CClient(nClientfd, ssl);

	if( client->Run(m_info) != 0 )
		delete client;

	return 0;
}

// class CStrService
CStrService::CStrService()
{

}

CStrService::CStrService(CServiceInfo& info)
	: CService(info)
{

}

CStrService::~CStrService()
{

}

int CStrService::RunClient(int nClientfd, SSL *ssl /* = NULL */)
{
	ostringstream msg;
	msg << "Str Service start.[port : " << m_info.m_port << ",SSL:"
		<< m_info.m_enableSSL << "]";
	LOGACONSOLE(LDBG, msg);

	CClient * client = new CClient(nClientfd, ssl);

	if (client->Run(m_info) != 0)
		delete client;

	return 0;
}

// Abstract Factory returning a Service
CService* CSericeFactory::CreateSerice(CServiceInfo &info)
{
	switch(info.m_type)
	{
	case CServiceInfo::XML:
		_LOG( LDBG, "Service Factory [type:XML,Port:%d]", info.m_port );
		return new CXMLService(info);
		break;
	case CServiceInfo::SDK:
		_LOG( LDBG, "Service Factory [type:SDK,Port:%d]", info.m_port );
		return new CSDKService(info);
		break;
	case CServiceInfo::ETC:
		_LOG( LDBG, "Service Factory [type:ETC,Port:%d]", info.m_port );
		return new CEtcService(info);
		break;
	case CServiceInfo::STR:
		_LOG(LDBG, "Service Factory [type:STR,Port:%d]", info.m_port);
		return new CStrService(info);
		break;
	default:
		LOG(LERR, "Service type is unknown.");
		break;
	}

	return NULL;
}
