/**    
    @file     udp.c
    @date     2009-03-19
    @author   오재경 freefrug@falinux.com
    @brief    udp 를 사용한 통신을 담당한다.

    @modify   
              2010-08-18 (장길석) mingw와 함께 사용할 수 있는 코드 추가
    @todo    
    @bug     
    @remark   
    
    @warning 
*/
//
//  저작권    에프에이리눅스(주)
//            외부공개 금지
//
//----------------------------------------------------------------------------
#define EMBEDDED_LINUX                                          // 이렇게 처리하지 않으면 EClipse에서 C 영역이 회색 바탕이 됨

#ifdef MS_WIN32
    #undef EMBEDDED_LINUX
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/types.h>

#ifdef EMBEDDED_LINUX

    #include <sys/un.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>

#else

    #include <windows.h>
    #include <winsock2.h>

#endif



#include <pollmng.h>
#include <udp.h>

char desc_udp[] = "falinux udp ver 0.2.0";


/// udp 개별 구조체
typedef struct {
	
	int   port;
	int   im_server;
	struct sockaddr_in addr;
} udp_priv_t;

static poll_obj_t  *tmp_udp_poll = NULL;  // poll 관리객체없이 전송함수를 위해


//------------------------------------------------------------------------------
/** @brief    udp 소켓을 서버형태로 open 한다.
    @param    port  포트번호
    @return   poll_obj_t 형태의 포인터
*///----------------------------------------------------------------------------

#ifdef EMBEDDED_LINUX

poll_obj_t  *udp_open_server( int port )
{
	struct sockaddr_in addr_svr;
	poll_obj_t *obj;
	udp_priv_t *udp;
	int  option;
	int  sockfd;
	
	sockfd = socket( PF_INET, SOCK_DGRAM, 0 );
	if ( sockfd < 0 )
	{
		perror( "udp open error:" );
		return NULL;	
	}

	// TIME-WAIT 상태에 있는 소켓에 할당되어 있는 IP 주소와 포트를 바로 사용할 수 있도록
	// SO_REUSEADDR 의 옵션 값을 TRUE 로
	option = 1;                                                         
	setsockopt( sockfd, SOL_SOCKET, SO_REUSEADDR, &option, sizeof(option));

	// 소켓을 시스템에 연결한다.
	bzero( &addr_svr, sizeof(struct sockaddr_in) );
	addr_svr.sin_family      = AF_INET;
	addr_svr.sin_addr.s_addr = htonl( INADDR_ANY );
	addr_svr.sin_port        = htons( port );
	
	if( bind( sockfd, (struct sockaddr *)&addr_svr, sizeof(struct sockaddr_in) ) < 0 )
	{
		perror("udp bind error :"); 
	    close( sockfd );
	    return NULL;
	}
	
	// udp 만의 정보를 설정한다.
	udp = (udp_priv_t *)malloc( sizeof(udp_priv_t) );
	udp->im_server  = 1;
	udp->port       = port;

	obj             = poll_add( sockfd );
	obj->type       = STYP_UDP;
	obj->priv       = (void *)udp;

	tmp_udp_poll    = obj;
	
	return obj;
}

#else

poll_obj_t  *udp_open_server( int port )
{
    SOCKET      hSock;
    SOCKADDR_IN sckAddr;

    poll_obj_t *obj;
    udp_priv_t *udp;
    char        option;

    hSock = socket( PF_INET, SOCK_DGRAM, 0 );
    if ( INVALID_SOCKET == hSock){
        perror( "udp open error:");
        return NULL;
    }

    // TIME-WAIT 상태에 있는 소켓에 할당되어 있는 IP 주소와 포트를 바로 사용할 수 있도록
    // SO_REUSEADDR 의 옵션 값을 TRUE 로
    option = 1;
    setsockopt( hSock, SOL_SOCKET, SO_REUSEADDR, &option, sizeof(option));

    // 소켓을 시스템에 연결한다.
    memset( &sckAddr, 0, sizeof( sckAddr));
    sckAddr.sin_family      = AF_INET;
    sckAddr.sin_addr.s_addr = htonl( INADDR_ANY );
    sckAddr.sin_port        = htons( port );

    if( SOCKET_ERROR == bind( hSock, ( SOCKADDR *)&sckAddr, sizeof( sckAddr) ))
    {
        perror("udp bind error");
        closesocket( hSock );
        return NULL;
    }

    // udp 만의 정보를 설정한다.
    udp = (udp_priv_t *)malloc( sizeof(udp_priv_t) );
    udp->im_server  = 1;
    udp->port       = port;

    fd_t  pSock     = malloc( sizeof( SOCKET));
   *( SOCKET*)pSock = hSock;
    obj             = poll_add( pSock);
    obj->type       = STYP_UDP;
    obj->priv       = (void *)udp;

    tmp_udp_poll    = obj;

    return obj;
}

#endif


//------------------------------------------------------------------------------
/** @brief    udp 소켓을 클라이언트 형태로 open 한다.
    @return   poll_obj_t 형태의 포인터
*///----------------------------------------------------------------------------

#ifdef EMBEDDED_LINUX

poll_obj_t  *udp_open_client( void )
{
	poll_obj_t *obj;
	udp_priv_t *udp;
	int sockfd;
	
	sockfd = socket( PF_INET, SOCK_DGRAM, 0 );
	if ( sockfd < 0 )
	{
		perror( "udp open error:" );
		return NULL;	
	}

	// udp 만의 정보를 설정한다.
	udp = (udp_priv_t *)malloc( sizeof(udp_priv_t) );
	udp->im_server = 0;
	udp->port      = -1;

	obj             = poll_add( sockfd );
	obj->type       = STYP_UDP;
	obj->priv       = (void *)udp;
	
	tmp_udp_poll    = obj;
	
	return obj;
}

#else

poll_obj_t  *udp_open_client( void )
{
    SOCKET      hSock;

    poll_obj_t *obj;
    udp_priv_t *udp;

    hSock = socket( PF_INET, SOCK_DGRAM, 0 );
    if ( INVALID_SOCKET == hSock){
        perror( "udp open error:");
        return NULL;
    }

    // udp 만의 정보를 설정한다.
    udp = (udp_priv_t *)malloc( sizeof(udp_priv_t) );
    udp->im_server  = FALSE;
    udp->port       = -1;

    fd_t  pSock     = malloc( sizeof( SOCKET));
   *( SOCKET*)pSock = hSock;
    obj             = poll_add( pSock);
    obj->type       = STYP_UDP;
    obj->priv       = (void *)udp;

    tmp_udp_poll    = obj;

    return obj;
}

#endif

//------------------------------------------------------------------------------
/** @brief    udp 소켓을 close 한다.
    @param    obj  폴객체 포인터
*///----------------------------------------------------------------------------
void udp_close( poll_obj_t *obj )
{
#ifdef EMBEDDED_LINUX

	close( obj->fd );
	
#else

	closesocket( *( ( SOCKET *)obj->fd));
	free( obj->fd);                                             // SOCKET * 를 해제한다.

#endif

	if ( obj->priv )
	{
		free( obj->priv );
	}
	
	poll_delete( obj );
	
	if ( tmp_udp_poll == obj ) tmp_udp_poll = NULL;
}
//------------------------------------------------------------------------------
/** @brief    udp 폴객체를 포트번호로 찾는다.
    @param    port 포트번호
    @return   obj  폴객체 포인터
*///----------------------------------------------------------------------------
poll_obj_t  *udp_get_byport( int port )
{
	poll_obj_t *obj;
	udp_priv_t *udp;
	int  idx, count;
	
	count = poll_count();
	
	for(idx=0; idx<count; idx++)
	{
		obj = poll_get_obj( idx );
		udp = (udp_priv_t *)obj->priv;  
		if ( udp )
		{
			if ( udp->port == port )
			{
				return obj;
			}
		}
	}
	
	return NULL;
}
//------------------------------------------------------------------------------
/** @brief    자료를 송신한 클라이언트로 데에터를 재 전송한다
    @param    obj     폴객체 포인터
    @param    len     버퍼의 길이
    @return   전송된 데이타 개수
*///----------------------------------------------------------------------------
int  udp_echo( poll_obj_t *obj, char *buf, int len )
{
	struct sockaddr_in *paddr;
	udp_priv_t *udp;
	int			wrcnt;

	udp   = (udp_priv_t *)(obj->priv);
	paddr = &(udp->addr);

#ifdef EMBEDDED_LINUX

	wrcnt = sendto( obj->fd, buf, len, 0, (struct sockaddr *)paddr, sizeof( struct sockaddr_in) );

#else
	SOCKET  hSock   = *( SOCKET *)obj->fd;

    wrcnt = sendto( hSock, buf, len, 0, (struct sockaddr *)paddr, sizeof( struct sockaddr_in) );

#endif

	if ( 0 > wrcnt )
	{
		perror( "udp send error:" );
	}

	return wrcnt;
}
//------------------------------------------------------------------------------
/** @brief    udp 소켓을 통해 데이타를 전송한다.
    @param    obj     폴객체 포인터
    @param    host    상대방 IP 나 호스트이름 문자열포인터
    @param    port    상대방 포트번호
    @param    buf     전송버퍼
    @param    len     버퍼의 길이
    @return   전송한 데이타 개수
*///----------------------------------------------------------------------------
int  udp_write( poll_obj_t *obj, char *host, int port, char *buf, int len )
{
	struct sockaddr_in udp_addr;
	int  wrcnt;
	
	// 상대편 주소를 설정한다.
	memset( &udp_addr, 0, sizeof( struct sockaddr_in) );
	udp_addr.sin_family      = AF_INET;
	udp_addr.sin_addr.s_addr = inet_addr( host );
	udp_addr.sin_port        = htons( port );
	
	// 전송한다.

#ifdef EMBEDDED_LINUX

	wrcnt = sendto( obj->fd, buf, len, 0, (struct sockaddr *)&udp_addr, sizeof(udp_addr) );

#else
	SOCKET  hSock   = *( SOCKET *)obj->fd;

    wrcnt = sendto( hSock, buf, len, 0, (struct sockaddr *)&udp_addr, sizeof(udp_addr) );

#endif

	if ( 0 > wrcnt )
	{
		perror( "udp send error:" );
	}
	
	return wrcnt;
}
//------------------------------------------------------------------------------
/** @brief    udp 소켓을 통해 데이타를 읽는다.
    @param    obj     폴객체 포인터
    @param    len     버퍼의 길이
    @return   전송된 데이타 개수
*///----------------------------------------------------------------------------
int  udp_read( poll_obj_t *obj, char *buf, int len )
{
	struct sockaddr_in *paddr;
	udp_priv_t *udp;
	int  addr_len;
	int  rdcnt;
	
	udp   = (udp_priv_t *)(obj->priv);
	paddr = &(udp->addr);
	
	// 데이타를 읽는다.
	addr_len = sizeof( struct sockaddr_in);

#ifdef EMBEDDED_LINUX

	rdcnt = recvfrom( obj->fd, buf, len, 0, (struct sockaddr *)paddr, (socklen_t *)&addr_len );
	
#else
	SOCKET  hSock   = *( SOCKET *)obj->fd;

    rdcnt = recvfrom( hSock, buf, len, 0, ( SOCKADDR *)paddr, &addr_len);

#endif

	if ( 0 > rdcnt )
	{
		perror( "udp recv error:" );
	}
	//printf( "host=%s port=%d\n", inet_ntoa( udp->addr.sin_addr ), udp->addr.sin_port );		
	
	return rdcnt;
}
//------------------------------------------------------------------------------
/** @brief    udp 소켓으로 마지막으로 수신한 호스트를 찾는다.
    @param    obj   폴객체 포인터
    @param    host  상대편 호스트를 돌려줄 문자열 포인터
    @param    port  상대편 호스트가 사용한 포트번호를 돌려줄 정수형 포인터
    @return   의미없음
*///----------------------------------------------------------------------------
int  udp_get_remote_host( poll_obj_t *obj, char *host, int *port )
{
	udp_priv_t *udp;
	
	udp   = (udp_priv_t *)(obj->priv);
	
	strcpy( host, inet_ntoa( udp->addr.sin_addr ) );
	*port = udp->addr.sin_port;

	return 0;
}
//------------------------------------------------------------------------------
/** @brief    poll 객체없이 udp 소켓을 통해 데이타를 전송한다.
    @param    host    상대방 IP 나 호스트이름 문자열포인터
    @param    port    상대방 포트번호
    @param    buf     전송버퍼
    @param    len     버퍼의 길이
    @return   전송한 데이타 개수
    @remark   udp_open_server(), udp_open_client() 함수는 한번 호출되어야 한다.
*///----------------------------------------------------------------------------
int  udp_write_simple( char *host, int port, char *buf, int len )
{
	if ( tmp_udp_poll )
	{
		return udp_write( tmp_udp_poll, host, port, buf, len );
	}

	return -1;
}
//------------------------------------------------------------------------------
/** @brief    poll 객체없이 udp 소켓을 통해 데이타를 읽는다.
    @param    buf  전송버퍼
    @param    len  버퍼의 길이
    @return   전송된 데이타 개수
    @remark   udp_open_server(), udp_open_client() 함수는 한번 호출되어야 한다.
*///----------------------------------------------------------------------------
int  udp_read_simple( char *buf, int len )
{
	if ( tmp_udp_poll )
	{
		return udp_read( tmp_udp_poll, buf, len );
	}

	return -1;
}