/**    
    @file     ipc_call.c
    @date     2008-06-30
    @author   박진호 jhpark@falinux.com
    @brief    프로세스간의 명령 전달을 구현한다.

    @modify   
    @todo    
    @bug     
    @remark   
    
    @warning 
*/
//
//  저작권    에프에이리눅스(주)
//            외부공개 금지
//
//----------------------------------------------------------------------------

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/signal.h>
#include <sys/ioctl.h>
#include <sys/poll.h> 
#include <time.h>
#include <errno.h>

#include <pollmng.h>

#include <fabind.h>
#include <uds.h>
#include <udp.h>

#include <ipc_call.h>

/// @brief  전역 변수 선언
//------------------------------------------------------------------------------
/// @}

char ipc_call_desc[]   = "ipc_call ver 0.3.0";

#define 				FABIND_QUEUE_COUNT		1024

/// fabind 개별 구조체
typedef struct {
	unsigned long 		id;						/// 
	unsigned long 		port;					/// 통신을 위한 PORT 
	unsigned long 		group;					/// 구룹 처리를 위한 GROUP ID
	         long 		method_type;			/// 통신 처리 방법 ( FABIND, UDS, UDP )
	unsigned long 		level;					/// LEVEL 값
	unsigned long 		time_out;				/// TIME OUT 값
	
	poll_obj_t  		*poll_obj;				/// 폴 관련 obj
	ipc_call_func_t     *func;                  /// 데이타 수신시 호출하는 사용자 콜백함수
	ipc_call_packet_t   recv_packet;            /// 데이타 수신 패켓

} ipc_priv_t;

/// IPC_CALL 의 변수를 관리하는 구조체
static ipc_priv_t  ipc_priv;

int    ipc_call_poll_in( poll_obj_t *obj);


int			ipc_call_method				( unsigned long method	);

//------------------------------------------------------------------------------
/** @brief    IPC_CALL 에러 상태를 출력 한다.
    @param    err err 넘버
*///----------------------------------------------------------------------------
void ipc_call_print_error				( int err	) 									
{
	if ( 0 > err )
	{
		switch( err )
		{
		case IPC_CALL_E_AGAIN     	:	printf("ipc_call) Error Again\n");		break;
		case IPC_CALL_E_TIME_OUT  	:	printf("ipc_call) Error Timeout\n");  	break;  
		case IPC_CALL_E_BUSY      	:	printf("ipc_call) Error Busy\n");  		break;  
		case IPC_CALL_E_NO_ID     	:	printf("ipc_call) Error No id\n");  	break;  
		}
	}
}

//------------------------------------------------------------------------------
/** @brief    IPC_CALL 을 생성한다.
    @param    id   IPC의 ID.
    @return   0  성공
			  -1 실패
*///----------------------------------------------------------------------------
int			ipc_call_create				( unsigned long id  	) 									
{	
	poll_obj_t  		*obj;

	memset( (void *)&ipc_priv, 0, sizeof(ipc_priv_t) );

	ipc_priv.id        		= id;                      		
	ipc_priv.port 	   		= IPC_CALL_GET_PORT(id) ;  
	ipc_priv.group     		= IPC_CALL_GET_GROUP(id);  
	ipc_priv.method_type 	= IPC_CALL_METHOD_NONE;     
	                        
	// 디폴트로 UDS로 열고 다른것은 타입을 변경할때 처리 하도록 한다.
	return ipc_call_method( IPC_CALL_METHOD_UDS );
}

//------------------------------------------------------------------------------
/** @brief    IPC_CALL 을 해제한다.
    @param    id IPC의 ID.
    @return   0 성공
			  -1 실패
*///----------------------------------------------------------------------------
int	ipc_call_free( unsigned long id )
{	
	poll_obj_t  		*obj;

	if ( ipc_priv.id != id )		return IPC_CALL_E_NO_ID;

	obj = ipc_priv.poll_obj;

	switch( ipc_priv.method_type )
	{	
	case 	IPC_CALL_METHOD_NONE	 : 									break;
	case 	IPC_CALL_METHOD_FABIND	 : 	fabind_close( obj );			break;
	case 	IPC_CALL_METHOD_UDS		 :	uds_close( obj );				break;
	case 	IPC_CALL_METHOD_UDP		 :	udp_close( obj );				break;
	default                          : 	printf("Not Match Method\n");	break;
	}

	return IPC_CALL_OK;	
}

//------------------------------------------------------------------------------
/** @brief    IPC_CALL 의 수행 방식을 정한다.
    @param    method 메시지 전송 방법
    @return   0 성공
			  -1 실패
*///----------------------------------------------------------------------------                       	                                							
int			ipc_call_method				( unsigned long method	)
{
	poll_obj_t  		*obj = NULL;

	if ( ipc_priv.method_type == method )		return IPC_CALL_OK;

	ipc_call_free( ipc_priv.id );

	switch( method )
	{
	case 	IPC_CALL_METHOD_FABIND	 : 
	{	
		obj = fabind_open( ipc_priv.port, ipc_priv.group, FABIND_QUEUE_COUNT);
		if (  NULL == obj )
		{ 
			return IPC_CALL_E_BUSY;
		}
		ipc_priv.method_type = IPC_CALL_METHOD_FABIND;
	}
	break;
	case 	IPC_CALL_METHOD_UDS		 :
	{ 
		char dst_uds[1024];
		sprintf( dst_uds, IPC_CALL_UDS_NAME_FORMAT, ipc_priv.port );
		obj = uds_open_server( dst_uds );
		if ( NULL == obj )
		{ 
			return IPC_CALL_E_BUSY;
		}			
		 
		ipc_priv.method_type = IPC_CALL_METHOD_UDS;						
	}
	break;
	case 	IPC_CALL_METHOD_UDP		 :
	{	
		obj = udp_open_server( ipc_priv.port );
		if ( NULL == obj )
		{ 
			return IPC_CALL_E_BUSY;
		}			
		ipc_priv.method_type = IPC_CALL_METHOD_UDP;	 
	}
	break;
	default : printf("Not Match METHOD\n"); break;
	}

	if( NULL != obj )	
	{
		obj->on_poll_in = ipc_call_poll_in;
		ipc_priv.poll_obj = obj;
	}

	return IPC_CALL_OK;	
}
 
//------------------------------------------------------------------------------
/** @brief    IPC_CALL 의 레벨을 정한다.
    @param    level 전송 레벨을 정한다.
    @return   0 성공
			  -1 실패
*///----------------------------------------------------------------------------                                    	                                							
int			ipc_call_level				( unsigned long level	)
{
	ipc_priv.level = level;
	return 0;	
}


//------------------------------------------------------------------------------
/** @brief    IPC_CALL 의 디폴트 레벨을 설정한다.
    @param    level    디폴트 전송 레벨을 정한다.	
    @return   0 성공
			  -1 실패
*///----------------------------------------------------------------------------           
int			ipc_call_level_default		( unsigned long level	)
{
	ipc_priv.level = level;
	return 0;	
}

//------------------------------------------------------------------------------
/** @brief    IPC_CALL 의 시간 초과를 설정한다.
    @param    msec    timeout될 시간을 정한다.
    @return   0 성공
			  -1 실패
*///----------------------------------------------------------------------------                                               	                                							
int			ipc_call_timeout			( unsigned long msec	)
{
	ipc_priv.time_out = msec;
	return 0;	
}

//------------------------------------------------------------------------------
/** @brief    IPC_CALL 의 디폴트 시간 초과를 정한다.
    @param    msec    timeout될 디폴트 시간을 정한다.
    @return   0 성공
			  -1 실패
*///----------------------------------------------------------------------------   
int			ipc_call_timeout_default	( unsigned long msec	)
{
	ipc_priv.time_out = msec;
	return 0;
}


//------------------------------------------------------------------------------
/** @brief    POLLIN을 위한 함수 포인터
    @param    obj poll_obj 
    @return   0 성공
			  -1 실패
*///----------------------------------------------------------------------------                                       	
int    ipc_call_poll_in( poll_obj_t *obj)
{
	ipc_call_packet_t *packet;
	int	   ack, rsize = 0;

	packet = &(ipc_priv.recv_packet);

	switch( ipc_priv.method_type )
	{
	case 	IPC_CALL_METHOD_FABIND		: rsize = fabind_read( obj, (void *)packet, sizeof(ipc_call_packet_t), &ack );  break;
	case 	IPC_CALL_METHOD_UDS		 	: rsize = uds_read( obj,  (void *)packet, sizeof(ipc_call_packet_t) ); 		  break;
	case 	IPC_CALL_METHOD_UDP		 	: rsize = udp_read( obj,  (void *)packet, sizeof(ipc_call_packet_t) );		  break;
	default                       		: printf("Not Match Method\n");  					      break;
	}
	
#if 0
	{
		int i;
		printf("rsize:%d ======================\n", rsize);
		for( i = 0; i < packet->size; i++)
		{
			if ( i%10 == 0 ) printf("\n");
			printf("%X ", packet->buf[i]);
		}
		printf("\n=============================\n");
	}
#endif

	if ( ( 0 < rsize ) && ipc_priv.func )
	{
		ipc_call_func_t read_func;

		read_func = ipc_priv.func;	
		read_func( packet->src_id, packet->msg_type, packet->buf, packet->size );	
	}

	return POLL_EVENTED;
}

//------------------------------------------------------------------------------
/** @brief    IPC_CALL 의 읽기에 대한 이벤트를 지정한다.
    @param    func    read시 호출될 함수 포인터를 설정 한다.
    @return   0 성공
			  -1 실패
*///----------------------------------------------------------------------------                                       	
int	ipc_call_on_read			( ipc_call_func_t *func )
{
	ipc_priv.func = func;	
	return 0;
}

//------------------------------------------------------------------------------
/** @brief    IPC_CALL 의 수신된 버퍼의 내용을 BIN 형식으로 읽는다.
    @param    msec    timeout될 디폴트 시간을 정한다.
    @return   0 성공
			  -1 실패
	@remark   이 함수는 필요가 없을것 같다. 판단필요
*///----------------------------------------------------------------------------                                       	
int			ipc_call_read				( void *buf, int len ) 
{
	poll_obj_t  		*obj;
		
	int			rsize = 0;
	int 		ack;
	char 		rbuf[1024];
	
	memset( rbuf, 0x00, sizeof(rbuf) );
	
	obj = ipc_priv.poll_obj;
		
	switch( ipc_priv.method_type )
	{
	case 	IPC_CALL_METHOD_FABIND	 : rsize = fabind_read( obj, rbuf, len, &ack );	break;
	case 	IPC_CALL_METHOD_UDS		 : rsize = uds_read( obj, rbuf, len ); 			break;
	case 	IPC_CALL_METHOD_UDP		 : rsize = udp_read( obj, rbuf, len );			break;
	default                          : printf("Not Match METHOD\n");  				break;
	}

	memcpy( buf, rbuf, rsize );
	
	return 0;
}


//------------------------------------------------------------------------------
/** @brief     IPC_CALL 의 BIN 형식으로 데이터를 전송한다.
    @param    msec    timeout될 디폴트 시간을 정한다.
    @return   0 성공
			  -1 실패
*///----------------------------------------------------------------------------                                      	
int			ipc_call_send			( unsigned long dest, void *buf, int len, unsigned long msg_type )
{
	poll_obj_t         *obj;
	ipc_call_packet_t   packet;
	int                 ret = 0;
	
//	printf( "IPC_CALL_SEND DATA[%s] LEN=%d\n", buf, len );

	if ( 0 >= len ) return IPC_CALL_E_EMPTY;
	if ( len > IPC_CALL_BUF_LEN ) len = IPC_CALL_BUF_LEN;

	obj = ipc_priv.poll_obj;

	packet.dst_id   = dest;
	packet.src_id   = ipc_priv.id;
	packet.msg_type = msg_type;
	packet.size     = len;
	
	memcpy( packet.buf, buf, len);			
	
	switch( ipc_priv.method_type )
	{
	case IPC_CALL_METHOD_FABIND	 : 
		{
			ret = fabind_write( obj, packet.dst_id, (char *)(&packet), IPC_CALL_PACKET_HEADER_LEN + len );
		}
		break;
		
	case IPC_CALL_METHOD_UDS		 : 
		{
			char dst_uds[1024];
			//memset(dst_uds, 0x00, sizeof(dst_uds));
			//sprintf(dst_uds, "%s%ld", IPC_UDS_NAME_PREFIX, dest );
			sprintf( dst_uds, IPC_CALL_UDS_NAME_FORMAT, IPC_CALL_GET_PORT(dest) );
			ret = uds_write( obj, dst_uds, (char *)(&packet), IPC_CALL_PACKET_HEADER_LEN + len );     
		}
		break;
		
	case IPC_CALL_METHOD_UDP		 : 
		{
			char host[1024];
			//memset(host, 0x00, sizeof(host) );
			sprintf(host, "%s", "127.0.0.1" );
			ret = udp_write( obj, host, IPC_CALL_GET_PORT(dest), (char *)(&packet), IPC_CALL_PACKET_HEADER_LEN + len );
		}
		break;
		
	default                          : 
		{
			printf("Not Match Method\n");  
		}
		break;
	}
	
	if ( 0 >= ret  )
	{
		// todo	
		printf("ipc Send Fail\n");  
		return -1;
	}
	
	return 0;
}


//------------------------------------------------------------------------------
/** @brief     IPC_CALL 의 XML 형식으로 데이터를 전송한다.
    @param    msec    timeout될 디폴트 시간을 정한다.
    @return   0 성공
			  -1 실패
*///----------------------------------------------------------------------------   
int			ipc_call_send_xml			( unsigned long dest, char *buf, int len )
{
	return ipc_call_send( dest, buf, len, IPC_CALL_MSG_TYPE_XML );

}

//------------------------------------------------------------------------------
/** @brief     IPC_CALL 의 ASC 형식으로 데이터를 전송한다.
    @param    msec    timeout될 디폴트 시간을 정한다.
    @return   0 성공
			  -1 실패
*///----------------------------------------------------------------------------  
int			ipc_call_send_asc			( unsigned long dest, char *buf, int len )
{
	return ipc_call_send( dest, buf, len, IPC_CALL_MSG_TYPE_ASC );
}

//------------------------------------------------------------------------------
/** @brief     IPC_CALL 의 BIN 형식으로 데이터를 전송하고 수신한다.
    @param    msec    timeout될 디폴트 시간을 정한다.
    @return   양수  성공
			  음수  실패
*///----------------------------------------------------------------------------                                                	
int			ipc_call_rpc_raw		( unsigned long dest, void *tbuf, int tlen , void *rbuf, int rlen , unsigned long msg_type )
{
	int ret = 0;
	int event_ret = 0;

	ipc_call_packet_t packet;
	poll_obj_t *obj;

	packet.dst_id   = dest;
	packet.src_id   = ipc_priv.id;
	packet.msg_type = msg_type;
	packet.size     = tlen;
	memcpy( packet.buf, tbuf, tlen);			
				
	obj = ipc_priv.poll_obj;			
				
	switch( ipc_priv.method_type )
	{
	case IPC_CALL_METHOD_FABIND : 
		{
			ret = fabind_write_and_response( obj, packet.dst_id, tbuf, rbuf, tlen, rlen );
		}
		break;

	case IPC_CALL_METHOD_UDS : 
		{
			char dst_uds[1024];
			sprintf( dst_uds, IPC_CALL_UDS_NAME_FORMAT, IPC_CALL_GET_PORT(dest) );

			ret = uds_write( obj, dst_uds, (char *)(&packet), IPC_CALL_PACKET_HEADER_LEN + tlen );
			event_ret = poll_do_one( obj->fd, POLLIN, ipc_priv.time_out );		// POLL 이벤트가 올때까지 대기 한다. ( TIME OUT )이면 빠져 나간다.

			if ( event_ret != POLL_EVENTED  ) 
			{
				// 이벤트가 정상적으로 발생 할때 까지 무한 대기 한다.
				while(1)
				{
					ret = uds_write( obj, dst_uds, (char *)(&packet), IPC_CALL_PACKET_HEADER_LEN + tlen );

					if ( ( event_ret == POLL_TIME_OUT ) && ( ipc_priv.time_out == IPC_CALL_TIMEOUT_UNLIMIT ) )
					{
						event_ret = poll_do_one( obj->fd, POLLIN, ipc_priv.time_out );		// POLL 이벤트가 올때까지 대기 한다. ( TIME OUT )이면 빠져 나간다.
					}

					if ( event_ret == POLL_EVENTED  ) 
					{
						break;
					}
				}
			}
			ret = uds_read( obj, rbuf, rlen );        

		}
		break;

	case IPC_CALL_METHOD_UDP : 
		{
			char host[1024];
			memset(host, 0x00, sizeof(host) );
			sprintf(host, "%s", "127.0.0.1");

			udp_write( obj, host, packet.dst_id, (char *)(&packet), tlen );
			poll_do_one( obj->fd, POLLIN, ipc_priv.time_out );		// POLL 이벤트가 올때까지 대기 한다.  ( TIME OUT )이면 빠져 나간다.

			ret = udp_read( obj, rbuf, rlen );        

		}
		break;

	default : 
		{
			printf("Not Match METHOD\n");  
		}
		break;
	}	
	
	if ( ret < 0 ) ipc_call_print_error( ret );						
		
	return ret;
}

//------------------------------------------------------------------------------
/** @brief     IPC_CALL 의 BIN 형식으로 데이터를 전송하고 수신한다.
    @param    msec    timeout될 디폴트 시간을 정한다.
    @return   양수  성공
			  음수  실패
*///----------------------------------------------------------------------------                                                	
int	ipc_call_rpc( unsigned long dest, void *tbuf, int tlen , void *rbuf, int rlen )
{
	return ipc_call_rpc_raw( dest, tbuf, tlen , rbuf, rlen , IPC_CALL_MSG_TYPE_BIN );
}

//------------------------------------------------------------------------------
/** @brief     IPC_CALL 의 XML 형식으로 데이터를 전송하고 수신한다.
    @param    msec    timeout될 디폴트 시간을 정한다.
    @return   양수  성공
			  음수  실패
*///----------------------------------------------------------------------------         
int	ipc_call_rpc_xml( unsigned long dest, void *tbuf, int tlen , void *rbuf, int rlen )
{
	return ipc_call_rpc_raw( dest, tbuf, tlen , rbuf, rlen , IPC_CALL_MSG_TYPE_XML );

}

//------------------------------------------------------------------------------
/** @brief     IPC_CALL 의 ASC 형식으로 데이터를 전송하고 수신한다.
    @param    msec    timeout될 디폴트 시간을 정한다.
    @return   양수 성공
			  음수 실패
*///----------------------------------------------------------------------------         
int	ipc_call_rpc_asc( unsigned long dest, void *tbuf, int tlen , void *rbuf, int rlen )
{
	return ipc_call_rpc_raw( dest, tbuf, tlen , rbuf, rlen , IPC_CALL_MSG_TYPE_ASC );
}