#include "WorkerRecvDavListener.h"

#include "Logger.h"
#include "ProcessRename.h"
#include "ProcessConfig.h"
#include "FixedQueue.h"
#include "ProtocolOpendavListener.h"
#include "ReportLog.h"

#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <pthread.h>
#include <errno.h>
#include <string.h>
#include <arpa/inet.h>
#include <sqlite3.h>


// 수신 처리를 담당할 Thread 의 갯수
#define MAX_THREAD_RECEIVER           10

// opendav-listener 로 부터 전달받은 Data 를 파싱 thread 에 전달시 사용할 Queue 의 최대 갯수
#define MAX_DATA_QUEUE_COUNT		3000

// opendav-listener 가 전송한 서비스 통계 정보를 수신하기 위한 buffer size
// header 352byte + int 4byte + body 272byte => 628 byte
// 따라서 향후 확장으로 고려하여 구형 rc_statd(3.4 이전) 9216 Byte  -> 1024 Byte 로 변경함.
#define MAX_SOCKET_BUFFER_SIZE      1024    

// sqlite SQLITE_BUSY 잦은 발생으로 INSERT 지연 발생, 이로 인해 CCDB 통계 누락 발생 (#21440)
// - 이를 해결하기 위해 lock timeout 값을 3 sec -> 0.6 sec 로 변경 처리 
// - 이와 더불어 Lock 발생시 재시도 횟수를 30 -> 60 으로 변경 처리
#define SQLITE_DB_LOCK_TIMEOUT       600    // 세션 중에 SQLITE_BUSY 상황 발생 문제 해결을 위해 DB Lock 관련 timeout 설정값. ( 0.6 sec )
#define SQLITE_DB_LOCK_MAX_RETRY      60    // SQLITE_BUSY 발생시 재시도 최대 횟수



// FHS 의 opendav-listener 로 부터 서비스 관련 통계 (Network, 세션등의 통계 등)정보를 수신하기 위한 UDP socket...
// Main.cpp 에 전역변수로 정의 되어 있다.
extern int g_sockDavListenerRecv;

// FHS fimngd 가 전송하는 서비스별 Network 통계 정보 수신을 위한 TCP Socket
// Main.cpp 에 전역변수로 정의 되어 있다.
extern int g_sockServiceStatRecv;


// Data 수신부와 이를 파싱하여 처리할 Thread 객체간에 Data 전달을 위한 Queue 객체 생성
CFixedQueue * g_pRecvDataQueue = NULL;




// 프로세스 종료 전 처리할 종료 관련 각 작업을 일괄로 처리하기 위한 함수.
void ReadyToExitWorkerRecvDavListener()
{
	// open 된 socket 관련 close 처리
	if( g_sockDavListenerRecv != -1 )
		close( g_sockDavListenerRecv );
	
	// Queue 객체 해제
	if( g_pRecvDataQueue != NULL )
	{
		CFixedQueue * temp = g_pRecvDataQueue;

		g_pRecvDataQueue = NULL;
		delete temp;		
	}
	
	// Logger 객체 종료 처리.
	CLogger::Exit();

	return;
}


// Worker RecvDavListener 프로세스에 대한 대한 종료 처리 수신시.
static void SignalWorkerRecvDavListenerTerminate( int nSignalNumber )
{
	// Signal Number 에 따른 로깅처리.
	if( nSignalNumber == SIGTERM )
	{
		_LOG( LINF, "[RecvDavListener:%d] Process exit by SIGTERM signal. Good Bye..", getpid() );
	}
	else
	{
		_LOG( LWAR, "[RecvDavListener:%d] Process exit by abnormal signal[%d], Good Bye..", getpid(), nSignalNumber );
	}

	ReadyToExitWorkerRecvDavListener();

	// 종료전 잠시 대기
	struct timespec sleep;
	sleep.tv_sec  = 0;
	sleep.tv_nsec = 500000000;  // 0.5 sec
	nanosleep( &sleep, NULL );
	
	exit( EXIT_SUCCESS );
}

// Worker::RecvDavListener Process 의 signal 처리기...
void SetWorkerRecvDavListenerSignalHandler()
{
	sigset_t set;
	struct sigaction act;

	memset( &act, 0x00, sizeof( act ) );
	sigfillset( &set );
	sigprocmask( SIG_SETMASK, &set, NULL );
	sigfillset( &act.sa_mask );

	// 무시 처리 signal
	act.sa_handler = SIG_IGN;
	sigaction( SIGPIPE, &act, NULL );	/* desciptor 오류 발생시 Process가 죽는 것은 방지하기 위하여 설정 */
	sigaction( SIGHUP, &act, NULL );	/* process를 기동시킨 관리자의 로그아웃시, 또는 config reload 등의 처리 signal*/
	sigaction( SIGINT, &act, NULL );	/* ^C 키를 누른 경우 받는 신호 => demon 으로 기동되기 땜시 이 신호 못받음 */
	sigaction( SIGQUIT, &act, NULL );	/* 키보드에 의한  Abort 신호 처리 => ? */

	// Worker 프로세스는 자식 프로세스가 존재하지 않으므로 그냥 무시처리함.
	act.sa_handler = SIG_IGN;
	sigaction( SIGCHLD, &act, NULL );

	/* 각종 에러나 사용자의 종료 신호 처리 */
	act.sa_handler = SignalWorkerRecvDavListenerTerminate;
	sigaction( SIGTERM, &act, NULL );	/* kill -TERM 에 의한 프로세스 종료시 */

	// 나머지 신호는 default 처리.

	sigemptyset( &set );                  /* 신호 처리기 처리 설정 위한 블록 해제 */
	sigprocmask( SIG_SETMASK, &set, NULL );
}


// 340.0@278 형식의 서비스 ID 정보를 user_seq, svc_seq 정보를 파싱처리하기 위한 함수.
int ParseServiceId( const char * org, char * szUserSeq, char * szSvcSeq )
{
	const char * p = org;

	// 앞 부분인 svc_seq 부분 추출...
	while( *p != '.' && *p != '@' && *p != '\0' )
		p++;

	if( *p == '\0' )
		return -1;

	strncpy( szSvcSeq, org, ( p - org ) );
	*( szSvcSeq + ( p - org ) ) = '\0';


	if( *p == '.' )
	{
		// 중간 부분인 class id 는 안 쓰므로 제외하고...
		while( *p != '@' && *p != '\0' )
			p++;

		if( *p == '\0' )
			return -1;
	}

	// @ 뒤에 있는 마지막 user_seq 부분을 추출한다.	
	p = p + 1;

	if( *p == '\0' )
		return -1;

	strcpy( szUserSeq, p );


	return 0;
}


// 전역 Queue 에 저장된 opendav listener network 통계 수신 data 를 파싱, 저장 처리를 담당하는 Thread
void * WorkerRecvDavListenerSaveThread( void * arg )
{
	// 메인 함수에서 이 스레드가 종료되기를 기다리지 않게 분리
	pthread_detach( pthread_self() );
	
	_LOG( LINF, "[RecvDavListener:%d] SaveThread: start...", getpid() );
	
	
	// Queue pop data 저장 관련 변수 초기화.
	int pop_result;
	char szTempBuffer[MAX_SOCKET_BUFFER_SIZE];

	struct opendav_message_header * stHeader = ( struct opendav_message_header * )szTempBuffer;
	// Header 와 Body 사이 4Byte int 값 존재함.
	struct opendav_message_body   * stBody = ( struct opendav_message_body * )( szTempBuffer + sizeof( int ) + sizeof( struct opendav_message_header ) );

	// sqlite 관련 변수 초기화
	sqlite3      * db = NULL;
	sqlite3_stmt * stmt = NULL;

	int db_result;
	int nRetryCount;

	const char * insert_sql = "INSERT INTO network_stat( "
		"    timestamp, user_seq, svc_seq, fhs, insert_time, send_flag, down_traffic, up_traffic, "
		"    down_content_size, up_content_size ) "
		"VALUES ( ?, ?, ?, ?, datetime('now', 'localtime'), 0, ?, ?, ?, ? ) ";
	
	// 루프 내에서 파싱된 정보를 저장하기 위한 변수
	char szUserSeq[128];	// 파싱된 user_seq 정보 저장 변수
	char szSvcSeq[128];     // 파싱된 svc_seq 정보 저장 변수


	// 본 Thread 는 main 에서 통계  정보를 수신한 후 동작해야 하므로... 약간 기다린 후 동작시킨다.
	sleep( 3 );


	// sqlite 는 centos 5.4 에 설치된 기본 버전을 사용한다. ( 3.3.6 version )
	// 해당 버전에서는 최신 _v2 함수를 지원하지 않는다.
	// 기동시 db 파일이 없는 경우... -> open 명령에 의해 db 파일이 생성됨..(open 성공).. 하지만 prepare 에서 table 이 없다는 오류가 발생함.
	// 동작 중에 db 파일이 삭제되는 경우... -> Query 가 모두 정상적으로 수행됨... 따라서 감지가 안됨...

	// 본 sqlite3_open 함수에서는 오류가 발생하지 않는다.. 왜냐하면.. 해당 파일이 없을 경우.. 해당 파일을 0 size 파일로 강제로 생성하기 때문임.
	// 따라서 하위 오류 코드는 그냥 형식상 존재하는 코드임.

	db_result = sqlite3_open( CProcessConfig::GetInstance()->GetLocalDbFileName(), &db );
	if( db_result != SQLITE_OK )
	{
		LOG( LERR, "[RecvDavListener:%d] SaveThread: sqlite3 open failed.[%s][%d][%s]", getpid(), CProcessConfig::GetInstance()->GetLocalDbFileName(), db_result, sqlite3_errmsg( db ) );
		sqlite3_close( db );
		db = NULL;
	}
	else
	{
		// DB File 이 open 된 경우...
		nRetryCount = 0;

		// bind 관련 Query 를 수행할 stmt 객체 생성.
		// - 만약 sqlite DB File 이 사전에 생성되어 있지 않은 경우...  위의  open 함수에서 0 파일을 생성되어 정상적으로 open 처리되지만.. 
		//   sqlite3_prepare 함수에서 table 이 생성되어 있지 않으므로 오류가 발생함.
		//   따라서 loop 중에 재시도하도록 한다.
		db_result = sqlite3_prepare( db, insert_sql, strlen( insert_sql ), &stmt, NULL );

		// sqlite3_prepare() 함수 실행시 SQLITE_BUSY 오류가 발생할 수 있으므로.. 재시도 로직을 추가한다.
		while( db_result == SQLITE_BUSY  && nRetryCount < SQLITE_DB_LOCK_MAX_RETRY )
		{
			// sqlite 에서 SQLITE_BUSY 반환 관련 lock 대기 timeout 을 설정.
			sqlite3_busy_timeout( db, SQLITE_DB_LOCK_TIMEOUT );
			++nRetryCount;

			LOG( LWAR, "[RecvDavListener:%d] SaveThread: sqlite3 prepare SQLITE_BUSY, Retry[%d]", getpid(), nRetryCount );

			db_result = sqlite3_prepare( db, insert_sql, strlen( insert_sql ), &stmt, NULL );
		}

		if( db_result != SQLITE_OK )
		{
			LOG( LERR, "[RecvDavListener:%d] SaveThread: sqlite3 prepare failed.[%d][%s]", getpid(), db_result, sqlite3_errmsg( db ) );
			sqlite3_close( db );
			db = NULL;
			stmt = NULL;
		}
	}

	// 루프를 돌면서... Queue 에 저장된 수신 정보를 POP -> 파싱하여.. sqlite DB 에 저장한다.
	// g_pRecvDataQueue 값은 프로세스 종료 처리시 NULL 로 변경된다.. 따라서 본 Thread 가 종료될 수 있도록 처리한다.
	while( g_pRecvDataQueue != NULL )
	{
		// Queue 에서 Data 를 Pop 한다..
		// Queue 객체 자체에서 인자값을 전달한 버퍼를 memset 하므로.. 따로 수행하지 않는다.
		pop_result = g_pRecvDataQueue->Pop( szTempBuffer, MAX_SOCKET_BUFFER_SIZE );
		
		if( pop_result == 0 ) // Pop 성공시..
		{
			/*
			_LOG( LDBG, "[RecvDavListener:%d] SaveThread: header [%s][0x%04x][%s][%s][%s][%d][%d]", getpid()
			, stHeader->szTransactionID, ntohl(stHeader->ulMode), stHeader->szIssuer, stHeader->szServiceID, stHeader->szRCID
			, ntohl(stHeader->ulResult), ntohl(stHeader->ulBodyLength) );
			*/


			// 해당 값은 opendav-listener 가 보내는 서비스의 index 순서 정보를 기록하는 값으로써... n 번째 data 라는 의미임.
			// - 해당 패킷에 n 개의 data 가 저장된다는 의미는 아니며...
			// - 1개 패킷에는 무조건 1개 정보만 저장하여 전송 처리함.
			int num = *( (int *)( szTempBuffer + sizeof( struct opendav_message_header ) ) );

			/*
			_LOG( LDBG, "[RecvDavListener:%d] SaveThread: body [%d] [%s][%s][%d] [%lu][%lu][%lu][%lu][%lu][%lu] "
				  "[%u][%u][%u][%u] [%u][%u][%u][%u][%u][%u][%u][%u][%u][%u]"
				  , getpid()
				  , num
				  , stBody->szServiceID, stBody->szRCID, stBody->timestamp

				  , stBody->nst.traffic[0], stBody->nst.traffic[1]
				  , stBody->nst.content[0], stBody->nst.content[1]
				  , stBody->nst.tc_traffic[0], stBody->nst.tc_traffic[1]

				  , stBody->sst.concurrent[0], stBody->sst.concurrent[1]
				  , stBody->sst.saved_concurrent[0], stBody->sst.saved_concurrent[1]

				  , stBody->sst.success[0], stBody->sst.success[1]
				  , stBody->sst.fail_not_auth[0], stBody->sst.fail_not_auth[1]
				  , stBody->sst.fail_illegal_request[0], stBody->sst.fail_illegal_request[1]
				  , stBody->sst.fail_timeout[0], stBody->sst.fail_timeout[1]
				  , stBody->sst.fail_disconnected[0], stBody->sst.fail_disconnected[1]
				  );
			*/
			
			_LOG( LDBG, "[RecvDavListener:%d] SaveThread: num[%d]th stamp[%u] svc[%s] traffic[u:%lu][d:%lu] size[u:%lu][d:%lu]"
				  , getpid()
				  , num
				  , stBody->timestamp, stBody->szServiceID
				  , stBody->nst.traffic[1], stBody->nst.traffic[0]
				  , stBody->nst.content[1], stBody->nst.content[0]				  
				  );
			
			// 통계 정보 중.. DB 에 저장할 값이 모두 0 인 통계 정보는 불필요하므로... DB 에 입력처리 하지 않는다.
			// traffic = 0 (content size 도 자동으로 0이므로 ) 일 경우 불필요한 data 이므로 버린다.
			if( stBody->nst.traffic[0] == 0 && stBody->nst.traffic[1] == 0 )
				continue;
			
			// sqlite open 이 되지 않은 경우.. 재시도 처리 
			if( db == NULL )
			{
				_LOG( LINF, "[RecvDavListener:%d] SaveThread: sqlite3 open retry.[%s]"
					  , getpid(), CProcessConfig::GetInstance()->GetLocalDbFileName() );

				db_result = sqlite3_open( CProcessConfig::GetInstance()->GetLocalDbFileName(), &db );
				if( db_result != SQLITE_OK )
				{
					LOG( LERR, "[RecvDavListener:%d] SaveThread: sqlite3 open failed2.[%s][%d][%s]"
						 , getpid(), CProcessConfig::GetInstance()->GetLocalDbFileName(), db_result, sqlite3_errmsg( db ) );

					sqlite3_close( db );
					db = NULL;
				}
				else
				{
					nRetryCount = 0;

					// bind 관련 Query 를 수행할 stmt 객체 생성.
					db_result = sqlite3_prepare( db, insert_sql, strlen( insert_sql ), &stmt, NULL );

					// sqlite3_prepare() 함수 실행시 SQLITE_BUSY 오류가 발생할 수 있으므로.. 재시도 로직을 추가한다.
					while( db_result == SQLITE_BUSY  && nRetryCount < SQLITE_DB_LOCK_MAX_RETRY )
					{
						// sqlite 에서 SQLITE_BUSY 반환 관련 lock 대기 timeout 을 설정.
						sqlite3_busy_timeout( db, SQLITE_DB_LOCK_TIMEOUT );
						++nRetryCount;

						LOG( LWAR, "[RecvDavListener:%d] SaveThread: sqlite3 prepare SQLITE_BUSY, Retry[%d]", getpid(), nRetryCount );

						db_result = sqlite3_prepare( db, insert_sql, strlen( insert_sql ), &stmt, NULL );
					}

					if( db_result != SQLITE_OK )
					{
						LOG( LERR, "[RecvDavListener:%d] SaveThread: sqlite3 prepare failed2.[%d][%s]"
							 , getpid(), db_result, sqlite3_errmsg( db ) );

						sqlite3_close( db );
						db = NULL;
						stmt = NULL;
					}
				}
			}

			// sqlite 접속 불가시
			// - 위에서 접속 불가 발생시.. 해당 Data 는 처리 방법이 없으므로.. 버린다.
			// - 만약 잠시 대기 후 재시도할 경우... data 가 밀려... 최신 data 가 입력이 밀리고.. 이로 인해 traffic 제어 등에 문제가 발생할 수 있다.
			

			// sqlite 접속이 정상적으로 된 경우...
			if( stmt != NULL )
			{
				// pop 된 정보 중 stBody->szServiceID 정보는 348.0@207 형식으로 표시됨.
				// 이 중 user_seq 는@ 다음에 오는 207 , svc_seq 는 348 이므로... 파싱 처리한다.
				if( strlen( stBody->szServiceID ) == 0 )
				{
					// 서비스 정보가 없는 경우.. 에러 마킹하고 정보를 버린다.
					LOG( LWAR, "[RecvDavListener:%d] SaveThread: service_id length 0. save failed.[%d][%s]", getpid(), stBody->timestamp, stHeader->szIssuer );
					continue;
				}
				else
				{
					memset( szUserSeq, 0x00, sizeof( szUserSeq ) );
					memset( szUserSeq, 0x00, sizeof( szSvcSeq ) );

					// 전달 받은 stBody->szServiceID 정보(348.0@207 형식) 를 파싱 처리함.
					if( ParseServiceId( stBody->szServiceID, szUserSeq, szSvcSeq ) != 0 )
					{
						LOG( LWAR, "[RecvDavListener:%d] SaveThread: service_id parse error. save failed.[%d][%s][%s]"
							 , getpid(), stBody->timestamp, stHeader->szIssuer, stBody->szServiceID );

						continue;
					}
				}

				// insert query 에 변수 bind 처리. ( bind 값을 1부터 시작 )
				// text binding 에 사용되는 변수는 sqlite3_step() 함수 호출되기 전까지 변경이 없는 변수들이므로... SQLITE_TRANSIENT 대신 SQLITE_STATIC 을 사용한다.

				sqlite3_bind_int( stmt, 1, stBody->timestamp );
				sqlite3_bind_text( stmt, 2, szUserSeq, strlen( szUserSeq ), SQLITE_STATIC );
				sqlite3_bind_text( stmt, 3, szSvcSeq, strlen( szSvcSeq ), SQLITE_STATIC );
				sqlite3_bind_text( stmt, 4, stHeader->szIssuer, strlen( stHeader->szIssuer ), SQLITE_STATIC );

				sqlite3_bind_int64( stmt, 5, stBody->nst.traffic[0] );
				sqlite3_bind_int64( stmt, 6, stBody->nst.traffic[1] );
				sqlite3_bind_int64( stmt, 7, stBody->nst.content[0] );
				sqlite3_bind_int64( stmt, 8, stBody->nst.content[1] );				

				// insert query 수행.
				db_result = sqlite3_step( stmt );
				nRetryCount = 0;

				// 만약 BUSY 상태로 인해 오류가 발생하면..지정된 횟수 만큼 재시도
				while( db_result == SQLITE_BUSY  && nRetryCount < SQLITE_DB_LOCK_MAX_RETRY )
				{
					sqlite3_busy_timeout( db, SQLITE_DB_LOCK_TIMEOUT );
					++nRetryCount;

					LOG( LWAR, "[RecvDavListener:%d] SaveThread: sqlite3 step SQLITE_BUSY, Retry[%d]", getpid(), nRetryCount );

					// query 재시도
					db_result = sqlite3_step( stmt );
				}


				if( db_result != SQLITE_DONE )
				{
					LOG( LERR, "[RecvDavListener:%d] SaveThread: sqlite3 step error.[%d][%s]", getpid(), db_result, sqlite3_errmsg( db ) );

					// 만약 오류 코드가 SQLITE_ERROR, SQLITE_INTERNAL, SQLITE_EMPTY, SQLITE_SCHEMA 등이라면.... DB에 문제 있는 것으로 보고.. db 객체를 초기화한다.
					if( db_result == SQLITE_ERROR || db_result == SQLITE_INTERNAL || db_result == SQLITE_EMPTY || db_result == SQLITE_SCHEMA )
					{
						LOG( LERR, "[RecvDavListener:%d] SaveThread: sqlite db force closed", getpid() );

						sqlite3_finalize( stmt );
						sqlite3_close( db );

						db = NULL;
						stmt = NULL;
					}
					else
					{
						// Query 수행 오류가 발생된 경우... 해당 값은 포기하고... 다음 prepared 문을 재사용하기 위해 reset 처리한다.
						sqlite3_reset( stmt );
					}
				}
				else
				{
					// Query 수행된 경우.. prepared 문을 재사용하기 위해 reset 처리한다.
					sqlite3_reset( stmt );
				}
			}
		}
		else if( pop_result == 1 )  // Queue 가 empty 인 경우..
		{
			// pop 할 data 가 없으므로.. 잠시 쉰다.. ( 1 sec 정도 )
			//_LOG( LDBG, "[RecvDavListener:%d] SaveThread: queue empty. sleep", getpid());
			
			// usleep 대신 sleep, nanosleep 으로 변경 처리.
			//usleep( 1000000 );
			sleep( 1 );

		}
		else	// 나머지 상황은 오류..
		{
			LOG( LERR, "[RecvDavListener:%d] SaveThread: queue pop unknown error.[%d]", getpid(), pop_result );
		}
		

	} // while
	

	// Thread 종료 관련 처리.
	return NULL;
}


// Worker::RecvDavListener 에서 각 opendav-listener 가 전송한 network 통계 정보를 수신처리하기 위한 Thread.
void * WorkerRecvDavListenerClientThread( void * arg )
{
	// 메인 함수에서 이 스레드가 종료되기를 기다리지 않게 분리
	pthread_detach( pthread_self() );

	_LOG( LINF, "[RecvDavListener:%d:%lu] client recv thread start...", getpid(), pthread_self() );
	
	// opendav-listener 로 부터 서비스 통계 정보 수신을 위한 각 변수 초기화
	struct sockaddr_in clientSocketAddr;
	socklen_t clientSocketAddrLen = sizeof( clientSocketAddr );

	char szClientAddress[INET_ADDRSTRLEN];
	memset( szClientAddress, 0x00, sizeof( szClientAddress ) );

	char szSocketBuffer[MAX_SOCKET_BUFFER_SIZE];
	int nRead;
	int nResult;


	// opendav-listener 로 부터 서비스 통계 관련 정보 수신 대기
	while( 1 )
	{
		nRead = recvfrom( g_sockDavListenerRecv, &szSocketBuffer, MAX_SOCKET_BUFFER_SIZE, 
						  0, ( struct sockaddr * )&clientSocketAddr, &clientSocketAddrLen );

		// 수신 오류 발생시
		if( nRead < 0 )
		{
			LOG( LWAR, "[RecvDavListener:%d] socket recvfrom error[%d][%s]", getpid(), errno, strerror( errno ) );
			continue;
		}

		if( inet_ntop( AF_INET, &( clientSocketAddr.sin_addr ), szClientAddress, sizeof( szClientAddress ) ) == NULL )
		{
			LOG( LERR, "[RecvDavListener:%d] inet_ntop error[%d][%s]", getpid(), errno, strerror( errno ) );
		}

		// 로그 기록 목적으로 로깅처리
		_LOG( LDBG, "[RecvDavListener:%d:%lu] opendav-listener msg recv. client[%s] len[%d]", getpid(), pthread_self(), szClientAddress, nRead );


		// 1. 동일 RC 에서 전달된 메시지인지 확인한다.
		struct opendav_message_header * stHeader = ( struct opendav_message_header * )szSocketBuffer;
		if( strcasecmp( stHeader->szRCID, CProcessConfig::GetInstance()->GetRcId() ) != 0 )
		{
			// 다른 RC 로 부터 통계 정보를 수신한 경우... 수신 Data 는 그냥 버린다.
			LOG( LWAR, "[RecvDavListener:%d] not match RCID[%s][%s]", getpid(), stHeader->szRCID, szClientAddress );

			// 다른 RC 의 FHS opendav-listener 로부터 서비스 통계 정보를 수신받은 경우...
			// 해당 FHS 의 opendav-listener 설정이 잘못된 상태이므로...
			// report 로그 상에 해당 내역을 기록한다.
			CReportLog reportLog;
			std::string strErrorMessage;
			strErrorMessage.clear();

			if( reportLog.Init( CProcessConfig::GetInstance()->GetLogPath() ) == false )
			{
				reportLog.GetLastError( strErrorMessage );
				LOG( LERR, "[RecvDavListener:%d] report log init failed.[%s]", getpid(), strErrorMessage.c_str() );
			}
			else
			{
				reportLog.Write( "WAR", "[%s][opendav-listener stats RCID not match. check FHS setting. [%s][%s][recv: %s, local: %s]]"
								 , PROG_NAME, szClientAddress, stHeader->szIssuer, stHeader->szRCID, CProcessConfig::GetInstance()->GetRcId() );
			}


			continue;
		}

		// Debug 목적으로 Data 파싱 처리 후 출력...
		_LOG( LDBG, "[RecvDavListener:%d:%lu] FHS [%s][0x%04x][%s][%s][%d][%d]", getpid(), pthread_self()
			  , stHeader->szIssuer, ntohl( stHeader->ulMode ), stHeader->szServiceID, stHeader->szRCID
			  , ntohl( stHeader->ulResult ), ntohl( stHeader->ulBodyLength ) );

		// 2. 기존 rc_statd(3.4 이전) 소스 코드를 참조하여...
		// 수신된 메시시 값 중 ntohl(stHeader->ulMode) == MSG_MODE_OPENDAV_RCSTAT_NETSTAT ( 0x0110 ) 인 경우에만 처리..
		// 그외 메시지에 대해서는 Invalid Protocol 로 처리한다.
		if( ntohl( stHeader->ulMode ) != MSG_MODE_OPENDAV_RCSTAT_NETSTAT )   // #define MSG_MODE_OPENDAV_RCSTAT_NETSTAT  0x0110
		{
			LOG( LWAR, "[RecvDavListener:%d] Invalid protocol received.[0x%04x][%s]", getpid(), ntohl( stHeader->ulMode ), szClientAddress );
		}
		else
		{
			// Data 를 Queue 에 저장.
			if( g_pRecvDataQueue != NULL )
			{
				nResult = g_pRecvDataQueue->Push( szSocketBuffer, nRead );

				// Push 처리시 반환값
				// -1 : 전달 인자값에 NULL 존재
				// -2 : Queue 생성시 설정한 Data 크기보다 더 큰 nRead 값을 전달한 경우.
				//  1 : Queue 생성시 설정한 최대 Queue 갯수를 초과한 경우..
				//  0 : 정상 처리
				if( nResult != 0 )
				{
					LOG( LWAR, "[RecvDavListener:%d] recv queue push error[%d]", getpid(), nResult );
				}
			}
		}

	} // while(1)

	
	// Thread 종료 관련 처리.
	return NULL;
}


// Worker::RecvDavListener 프로세스의 Main 역활을 수행하기 위한 함수.
int WorkerRecvDavListenerMain()
{
	// 전역 변수로 설정된 socket 중 해당 worker 에서 사용하지 않는 socket 은 close 처리한다.	
	if( g_sockServiceStatRecv != -1 )
	{
		close( g_sockServiceStatRecv );
		g_sockServiceStatRecv = -1;
	}
	
	// Process Rename..
	set_ps_display( PROG_NAME": RecvDavListener", false );
	
	// Set Signal Handler
	SetWorkerRecvDavListenerSignalHandler();


	// 사용할 객체 초기화 처리
	
	// Data 전달을 담당할 Queue 객체 생성	
	g_pRecvDataQueue = new CFixedQueue( MAX_SOCKET_BUFFER_SIZE, MAX_DATA_QUEUE_COUNT );
	if( g_pRecvDataQueue == NULL )
	{
		// 객체 생성 실패시
		LOG( LERR, "[RecvDavListener:%d] data queue create failed. process exit", getpid() );

		ReadyToExitWorkerRecvDavListener();
		return EXIT_FAILURE;
	}
	
	// Queue 로 전달된 Data 를 파싱하여 local DB 에 저장 처리를 담당할 Thread 를 생성한다.
	pthread_t workerSaveThreadId;
	if( pthread_create( &workerSaveThreadId, NULL, WorkerRecvDavListenerSaveThread, NULL ) != 0 )
	{
		int errorNum = errno;
		LOG( LERR, "[RecvDavListener:%d] save thread create failed[%d][%s]. process exit", getpid(), errorNum, strerror( errorNum ) );

		ReadyToExitWorkerRecvDavListener();
		return EXIT_FAILURE;
	}	

	// opendav-listener 로 부터 UDP 통계 정보를 수신 처리할 Thread 생성 처리.
	pthread_t workerReceiveThreadId;
	for( int i = 0; i < MAX_THREAD_RECEIVER; i++ )
	{
		if( pthread_create( &workerReceiveThreadId, NULL, WorkerRecvDavListenerClientThread, NULL ) != 0 )
		{
			int errorNum = errno;
			LOG( LERR, "[RecvDavListener:%d] receive thread create failed[%d][%s]. process exit", getpid(), errorNum, strerror( errorNum ) );

			ReadyToExitWorkerRecvDavListener();
			return EXIT_FAILURE;
		}
	}

	sleep( 2 );
	_LOG( LINF, "[RecvDavListener:%d] receive thread count[%d] create success.", getpid(), MAX_THREAD_RECEIVER );
		
		
	// 대기
	while( 1 )
	{
		// 할일 없어서.. 시그널 수신까지 대기 한다.
		pause();
	}
	

	// 종료시 정리 작업 수행
	ReadyToExitWorkerRecvDavListener();
	return EXIT_SUCCESS;
}
