/**    
    @file     fabind.c 
    @date     2008/10/08
    @author   오재경 freefrug@falinux.com  FALinux.Co.,Ltd.
    @brief    fa-bind 드라이버를 사용하기 위한 API 를 구현한다..
              2009/06/10(오재경) fanet 에 포함하기 위해 수정
    
    @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 <util.h>
#include <fabind.h>
#include <fa-bind-ioctl.h>

char desc_fabind[] = "falinux fa-bind ver 0.2.1";

/// fabind 개별 구조체
typedef struct {

	int  port;
	int  group;
	
	int  need_ack;          // 응답요구가 필요한것
	ioctl_bind_msg_t ack_info;
	
} fabind_priv_t;


/// @{
/// @brief  로컬 함수 리스트 
int    fabind_post_message_simple( int fd, int port , unsigned char *buf, int count                    ); /// 일반적인 메세지 전송
int    fabind_post_message       ( int fd, int port , unsigned char *buf, int count, msg_howto_t howto ); /// 일반적인 메세지 전송
int    fabind_group_message      ( int fd, int group, unsigned char *buf, int count );                    /// 그룹 메세지 전송(방송도 포함)
int    fabind_recv_message       ( int fd, unsigned char *buf, int count, ioctl_bind_msg_t *info       ); /// 메세지 수신
int    fabind_request_response   ( int fd, int port , unsigned char *tx_buf, unsigned char *rx_buf, int tx_cnt, int rx_cnt ); /// 응답 대기형 메세지 전송
int    fabind_send_ack           ( int fd, int port , unsigned char *buf, int count                    ); /// ack 메세지 전송
/// @}



//------------------------------------------------------------------------------
/** @brief   fabind 드라이버 열고 객체를 초기화한다.
	@param   port              나의 포트
	@param   group             나의 그룹
	@param   recv_queue_count  내가 받을 큐의 개수
	@retrun  obj  폴객체 포인터
*///----------------------------------------------------------------------------
poll_obj_t  *fabind_open( int port, int group, int recv_queue_count )
{
	fabind_priv_t         *fabind;
	poll_obj_t            *obj;
	ioctl_set_bind_info_t  info;
	int    dev;
	int    rtn, major = 0;
	char   nodfile[256];
	
	
	major = find_dev_major( FABIND_DEV_NAME );
	if ( 0 > major ) 
	{
		printf( "can not find device %s\n", FABIND_DEV_NAME );
		return NULL;	
	}

	sprintf( nodfile, "/dev/%s", FABIND_DEV_NAME );
	dev = dev_open( nodfile, major, 0 );
	if ( 0 > dev )
	{
		return NULL;	
	}
	//printf( "open major=%d\n", major );
	
	info.port  = port;
	info.group = group;
	info.recv_queue_count = recv_queue_count;
	
	rtn = ioctl( dev, IOCTL_SET_BIND_INFO, &info );	
	if ( rtn < 0 )
	{
		printf( "fbind) unable to open port-%d\n", port );
		close(dev);
		return NULL;
	}
	    
	// fabind 만의 정보를 설정한다.
	fabind = (fabind_priv_t *)malloc( sizeof(fabind_priv_t) );
	memset( (void *)fabind, 0, sizeof(fabind_priv_t) );
	
	fabind->port          = port;
	fabind->group         = group;
	fabind->need_ack = 0;

	// 폴매니져에 등록한다.
	obj = poll_add( dev );
	obj->type = STYP_FABIND;
	obj->priv = (void *)fabind;
	
	return obj;
}
//------------------------------------------------------------------------------
/** @brief    fabind 객체를 해제한다.
    @param    obj  폴객체 포인터
*///----------------------------------------------------------------------------
void fabind_close( poll_obj_t *obj )
{
	close( obj->fd );
	
	if ( obj->priv )
	{
		free( obj->priv );
	}
	
	poll_delete( obj );
}
//------------------------------------------------------------------------------
/** @brief    fabind 폴객체를 포트번호로 찾는다.
    @param    port 포트번호
    @return   obj  폴객체 포인터
*///----------------------------------------------------------------------------
poll_obj_t *fabind_get_byport( int port )
{
	poll_obj_t *obj;
	fabind_priv_t *fabind;
	int  idx, count;
	
	count = poll_count();
	
	for(idx=0; idx<count; idx++)
	{
		obj = poll_get_obj( idx );
		fabind = (fabind_priv_t *)obj->priv;  
		if ( fabind )
		{
			if ( port == fabind->port )
			{
				return obj;
			}
		}
	}
	
	return NULL;
}
//------------------------------------------------------------------------------
/** @brief    fabind 를 통해 데이타를 전송한다.
    @param    obj       폴객체 포인터
    @param    port      상대편 포트번호
    @param    buf       전송버퍼
    @param    len       버퍼의 길이
    @return   전송한 데이타 개수
*///----------------------------------------------------------------------------
int  fabind_write( poll_obj_t *obj, int port, char *buf, int len )
{
	fabind_priv_t *fabind;
	
	fabind = (fabind_priv_t *)obj->priv;

	if ( FABIND_LAST_RECV_PORT == port )
	{
		port = fabind->ack_info.port;
	}

	if ( 0 > port )
	{
		printf( "fabind) invalid port=%d\n", port );
		return -1;
	}

	// 이전 데이타에서 응답이 필요한 데이타가 들어왔다면
	if ( fabind->need_ack )
	{
		fabind->need_ack = 0;
		return fabind_send_ack( obj->fd, port, buf, len );
	}
	else
	{
		return fabind_post_message( obj->fd, port , buf, len, SR_TYPE_NORMAL );
	}
}
//------------------------------------------------------------------------------
/** @brief    fabind 를 통해 데이타를 전송한다.
    @param    obj          폴객체 포인터
    @param    port         상대편 포트번호
    @param    buf          전송버퍼
    @param    len          버퍼의 길이
    @param    timeout_sec  전송 대기시간
                           0보다 작으면 무한대로 대기하여 전송한다
    @return   전송한 데이타 개수
*///----------------------------------------------------------------------------
int  fabind_write_timeout( poll_obj_t *obj, int port, char *buf, int len, int timeout_sec )
{
	int             rtn;
	unsigned int    end_sec;
	
	end_sec = get_cur_sec() + timeout_sec;
	
	while(1)	
	{
		rtn = fabind_post_message( obj->fd, port , buf, len, SR_TYPE_NORMAL );  
		if ( 0 < rtn ) return rtn;
			
		if ( rtn != -EAGAIN ) break;
			
		if ( 0 > timeout_sec ) continue;
		
		if ( end_sec < get_cur_sec() ) break;
	} 
	
	printf( "fbind) unable to send-timeout (port=%d)\n", port );
	return -1;
}
//------------------------------------------------------------------------------
/** @brief    fabind 를 통해 데이타를 읽는다.
    @param    obj       폴객체 포인터
    @param    buf       읽기버퍼
    @param    len       버퍼의 길이
    @param    need_ack  현재의 버퍼가 응답이 필요한것인지의 여부를 알려주는 포인터
    @return   읽은 데이타 개수
    @remark   need_ack_port 가 0 이상이면 곧바로 fabind_write() 함수로 응답을 보내주어야 한다.
*///----------------------------------------------------------------------------
int  fabind_read( poll_obj_t *obj, char *buf, int len, int *need_ack )
{
	fabind_priv_t *fabind;
	int  rdcnt;

	fabind = (fabind_priv_t *)obj->priv; 
	*need_ack = 0;

	// 데이타를 읽는다.
	rdcnt = fabind_recv_message( obj->fd, buf, len, &fabind->ack_info );

	if ( 0 < rdcnt )
	{
		if ( (fabind->ack_info.sr_type&SR_TYPE_REQ_ACK) == SR_TYPE_REQ_ACK )
		{
			*need_ack = 1;
		}
	}

	fabind->need_ack = *need_ack;	

	return rdcnt;
}
//------------------------------------------------------------------------------
/** @brief   fabind 를 통해 데이타를 전송한 후 응답을 받는다.
	@retrun  받은 데이타 개수
*///----------------------------------------------------------------------------
int  fabind_write_and_response( poll_obj_t *obj, int port, char *tx_buf, char *rx_buf, int tx_cnt, int rx_cnt ) 
{
	fabind_priv_t *fabind;

	fabind = (fabind_priv_t *)obj->priv;

	// 내가 점유한 포트에는 응답요구 전송을 하지 않는다.
	if ( fabind->port == port ) return 0;
	
	return fabind_request_response( obj->fd, port, tx_buf, rx_buf, tx_cnt, rx_cnt );
}
//------------------------------------------------------------------------------
/** @brief   fabind 를 통해 그룹단위로 데이타를 전송한다.
	@retrun  받은 데이타 개수
*///----------------------------------------------------------------------------
int  fabind_write_group( poll_obj_t *obj, int grp_port, char *buf, int len )
{
	return fabind_group_message( obj->fd, grp_port, buf, len );
}
//------------------------------------------------------------------------------
/** @brief   fabind 를 통해 들어온 포트번호를 얻는다.
	@retrun  받은 데이타 개수
*///----------------------------------------------------------------------------
int  fabind_get_recv_port( poll_obj_t *obj )
{
	fabind_priv_t *fabind;
	fabind = (fabind_priv_t *)obj->priv; 

	return fabind->ack_info.port; 
}




//____개별 api 함수_____________________________________________________________


//------------------------------------------------------------------------------
/** @brief   단순하게 메세지 보내기
	@param   fd       fa-bind 디바이스 핸들
	@param   port     상대편 포트번호
	@param   buf      버퍼
	@param   count    버퍼의 개수
	@retrun  전송된 개수
*///----------------------------------------------------------------------------
int  fabind_post_message_simple( int fd, int port, unsigned char *buf, int count )
{
	ioctl_bind_msg_t msg;
	int  rtn;
	
	msg.port     = port;
	msg.group    = -1;
	msg.sr_type  = SR_TYPE_NORMAL;
	msg.count    = count;
	msg.buf      = buf;
	
	rtn = ioctl( fd, IOCTL_BIND_SEND, &msg );

	if ( rtn < 0 ) 
	{
		printf( "fbind) unable to send (port=%d)\n", port );
		return -errno; 
	}
	return rtn;
}
//------------------------------------------------------------------------------
/** @brief   메세지 보내기
	@param   fd       fa-bind 디바이스 핸들
	@param   port     상대편 포트번호
	@param   buf      버퍼
	@param   count    버퍼의 개수
	@param   howto    전송의 방법
	                  SR_TYPE_NORMAL, SR_TYPE_EXPRESS, SR_TYPE_EMERENCY
	@retrun  전송된 개수
*///----------------------------------------------------------------------------
int  fabind_post_message( int fd, int port , unsigned char *buf, int count, msg_howto_t howto )
{
	ioctl_bind_msg_t msg;
	int  rtn;
	
	msg.port     = port;
	msg.group    = -1;
	msg.sr_type  = howto;
	msg.count    = count;
	msg.buf      = buf;
	
	rtn = ioctl( fd, IOCTL_BIND_SEND, &msg );

	if ( rtn < 0 )
	{
		 if ( errno != EAGAIN ) printf( "fbind) unable to send (port=%d)  rtn=%d\n", port, rtn );
		 return -errno; 
	}
	return rtn;
}
//------------------------------------------------------------------------------
/** @brief   메세지 받아오기
	@param   fd       fa-bind 디바이스 핸들
	@param   buf      버퍼
	@param   count    버퍼의 개수
	@param   info     상대편 정보 구조체 포인터
	@retrun  받은 데이타 개수
	@remark  
*///----------------------------------------------------------------------------
int  fabind_recv_message( int fd, unsigned char *buf, int count, ioctl_bind_msg_t *info )
{
	ioctl_bind_msg_t bind_msg, *msg = &bind_msg;
	int  rtn;
	
	if (info) msg = info;
	
	msg->port    = -1;
	msg->group   = -1;
	msg->count   = count;
	msg->buf     = buf;
	
	rtn = ioctl( fd, IOCTL_BIND_RECV, msg );
	
	if ( rtn <= 0 ) printf( "fbind) unable to recv\n" );
	return rtn;
}
//------------------------------------------------------------------------------
/** @brief   메세지를 전송한 후 응답을 받는다.
	@param   fd       fa-bind 디바이스 핸들
	@param   port     상대편 포트번호
	@param   tx_buf   전송 버퍼
	@param   rx_buf   수신 버퍼
	@param   tx_cnt   전송버퍼의 개수
	@param   rx_cnt   수신버퍼의 개수
	@retrun  받은 데이타 개수
*///----------------------------------------------------------------------------
int  fabind_request_response( int fd, int port , 
          unsigned char *tx_buf, unsigned char *rx_buf, int tx_cnt, int rx_cnt )
{
	ioctl_bind_msg_t msg;
	int  rtn;
	
	msg.port     = port;
	msg.group    = -1;
	msg.sr_type  = SR_TYPE_EMERENCY;
	msg.count    = tx_cnt;
	msg.buf      = tx_buf;
	
	rtn = ioctl( fd, IOCTL_BIND_SEND_REQ, &msg );   // 송신 (blocking)

	if ( rtn <= 0 )
	{
		printf( "fbind) unable to send-req port=%d\n", port );
		return -1;
	}
	
	msg.port    = -1;
	msg.group   = -1;
	msg.count   = rx_cnt;
	msg.buf     = rx_buf;
	
	rtn = ioctl( fd, IOCTL_BIND_RECV_ACK, &msg );   // 수신
	return rtn;
}
//------------------------------------------------------------------------------
/** @brief   그룹별 메세지 전송
	@param   fd       fa-bind 디바이스 핸들
	@param   group    전송할 그룹번호
	@param   buf      버퍼
	@param   count    버퍼의 개수
	@retrun  전송된 목적지의 개수
*///----------------------------------------------------------------------------
int  fabind_group_message( int fd, int group, unsigned char *buf, int count )
{
	ioctl_bind_msg_t msg;
	int  rtn;
	
	msg.port     = -1;
	msg.group    = group;
	msg.sr_type  = SR_TYPE_NORMAL;
	msg.count    = count;
	msg.buf      = buf;
	
	rtn = ioctl( fd, IOCTL_BIND_SEND_GROUP, &msg );

	if ( rtn <= 0 ) printf( "fbind) unable to send-grp grp=%d\n", group );
	return rtn;
}
//------------------------------------------------------------------------------
/** @brief   요구 메세지에 대한 응답 전송
	@param   fd       fa-bind 디바이스 핸들
	@param   port     응답을 보낼 포트
	@param   buf      버퍼
	@param   count    버퍼의 개수
	@retrun  전송된 목적지의 개수
*///----------------------------------------------------------------------------
int  fabind_send_ack( int fd, int port , unsigned char *buf, int count )
{
	ioctl_bind_msg_t msg;
	int  rtn;
	
	msg.port     = port;
	msg.group    = -1;
	msg.sr_type  = -1;
	msg.count    = count;
	msg.buf      = buf;
	
	rtn = ioctl( fd, IOCTL_BIND_SEND_ACK, &msg );

	if ( rtn <= 0 ) printf( "fbind) unable to send-ack port=%d\n", port );
	return rtn;
}