/*
 * Copyright (c) 2001-2003 Shiman Associates Inc. All Rights Reserved.
 * 
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use, copy,
 * modify, merge, publish, distribute, sublicense, and/or sell copies
 * of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
# include <sys/socket.h>
# include <sys/un.h>
# include <sys/stat.h>

#ifdef RTP_HAVE_SYS_FILIO
#include <sys/filio.h>
#endif /* RTP_HAVE_SYS_FILIO */
#include <errno.h>
#include "rtpdefines.h"
#include "rtperror.h"
#include "rtp_transport.h"


static int _set_stream_socket_buffers(RTPSOCKET rtpsock, RTPSOCKET rtcpsock);
static int _create_local_sockets(int session_type, RTPSOCKET* rtpsock,
				 RTPSOCKET* rtcpsock);

/* Accepts a TCP or UNIX connection request on a supplied pair of
   listening sockets. */
int
rtp_transport_stream_accept(int session_type,
			    RTPSOCKET rtplisten,
			    RTPSOCKET rtcplisten,
			    RTPSOCKET* rtpsock,
			    RTPSOCKET* rtcpsock,
			    struct sockaddr* rtp_peer_addr, 
			    struct sockaddr* rtcp_peer_addr, 
			    int* retval_syserror) 
{
    int err;
    socklen_t addrlen;
	
    if (!rtp_peer_addr) return ERR_RTP_NULLPTR;
    if (!rtcp_peer_addr) return ERR_RTP_NULLPTR;

    /* set the size of the addr structure */
    switch (session_type)
    {
    case RTP_SESSTYPE_TCP:
	addrlen = sizeof (struct sockaddr_in);
	break;
    case RTP_SESSTYPE_UNIX:
	addrlen = sizeof (struct sockaddr_un);
	break;
    default:
	break;
    }
    
    /* accept the connection request on the RTP socket*/
    *rtpsock = accept(rtplisten, rtp_peer_addr, &addrlen);
    if (*rtpsock == RTPSOCKERR)
    {
#ifdef _POSIX_SOURCE
	*retval_syserror = errno;
#endif	
	return ERR_RTP_CANTCREATESOCKET;
    }
	
    /* accept the connection request on the RTCP socket */
    *rtcpsock = accept(rtcplisten, rtcp_peer_addr, &addrlen);
    if (*rtcpsock == RTPSOCKERR)
    {
#ifdef _POSIX_SOURCE
	*retval_syserror = errno;
#endif	
	return ERR_RTP_CANTCREATESOCKET;
    }
	
    err = _set_stream_socket_buffers(*rtpsock, *rtcpsock);
    if (err < 0) return err;

    return 0;
}

/* Tries to connect to a TCP or UNIX endpoint */
int
rtp_transport_stream_connect(int session_type, RTPSOCKET* rtpsock,
			     RTPSOCKET* rtcpsock,
			     struct sockaddr* rtp_peer_addr,
			     struct sockaddr* rtcp_peer_addr, 
			     int* retval_syserror)
{
    int err;
    
    /* Create the sockets */
    err = _create_local_sockets(session_type, rtpsock, rtcpsock);
    if (err < 0) return err;

    /* set the buffer sizes */
    err = _set_stream_socket_buffers(*rtpsock, *rtcpsock);
    if (err < 0) return err;

    /* Connect to the peer */
    if ( session_type == RTP_SESSTYPE_TCP ) /* TCP */
    {
	if ( connect(*rtpsock, rtp_peer_addr, sizeof(struct sockaddr_in)) != 0)
	{
	    RTPCLOSESOCKET(*rtpsock);
	    RTPCLOSESOCKET(*rtcpsock);
#ifdef _POSIX_SOURCE
	    *retval_syserror = errno;
#endif	
	    return ERR_RTP_CANTBINDSOCKET;
	}
	if ( connect(*rtcpsock, rtcp_peer_addr, sizeof(struct sockaddr_in)) != 0)
	{
	    RTPCLOSESOCKET(*rtpsock);
	    RTPCLOSESOCKET(*rtcpsock);
#ifdef _POSIX_SOURCE
	    *retval_syserror = errno;
#endif	
	    return ERR_RTP_CANTBINDSOCKET;
	}
    }
    else
    {
	if ( connect(*rtpsock, rtp_peer_addr, sizeof(struct sockaddr_un)) != 0)
	{
	    RTPCLOSESOCKET(*rtpsock);
	    RTPCLOSESOCKET(*rtcpsock);
#ifdef _POSIX_SOURCE
	    *retval_syserror = errno;
#endif	
	    return ERR_RTP_CANTBINDSOCKET;
	}
	if ( connect(*rtcpsock, rtcp_peer_addr, sizeof(struct sockaddr_un)) != 0)
	{
	    RTPCLOSESOCKET(*rtpsock);
	    RTPCLOSESOCKET(*rtcpsock);
#ifdef _POSIX_SOURCE
	    *retval_syserror = errno;
#endif	
	    return ERR_RTP_CANTBINDSOCKET;
	}
    }
	
    return 0;
}

int
rtp_transport_stream_listen(int session_type,
			    RTPSOCKET* rtpsock,
			    RTPSOCKET* rtcpsock,
			    struct sockaddr* rtp_local_addr,
			    struct sockaddr* rtcp_local_addr)
{
    int                  err, tmp;
    int                  rtp_length = 0;
    int                  rtcp_length = 0;
    struct sockaddr_in*  addr_in;
    struct sockaddr_un*  addr_un;

    /* This is TCP, we don't have to conform to RFC 1889's odd/even
       port rule.  Not checking. */
	
    /* Create the sockets */
    err = _create_local_sockets(session_type, rtpsock, rtcpsock);
    if (err < 0) return err;

    /* set socket buffer sizes */
    err = _set_stream_socket_buffers(*rtpsock, *rtcpsock);
    if (err < 0) return err;

    /* set the reusable flag, so we can listen again, even while
       TIME_WAIT is active. */
    tmp = 1;
    setsockopt(*rtpsock,SOL_SOCKET,SO_REUSEADDR, &tmp, sizeof tmp);
    setsockopt(*rtcpsock,SOL_SOCKET,SO_REUSEADDR, &tmp, sizeof tmp);

    /*** setup RTP/RTCP listening sockets ******************************/
    /**** first, do some checking */
    if (session_type == RTP_SESSTYPE_TCP)
    {
	rtp_length = sizeof(struct sockaddr_in);
	addr_in = (struct sockaddr_in*)rtp_local_addr;
	if (addr_in->sin_family != AF_INET) 
	    return ERR_RTP_CANTBINDSOCKET;

	rtcp_length = sizeof(struct sockaddr_in);
	addr_in = (struct sockaddr_in*)rtcp_local_addr;
	if (addr_in->sin_family != AF_INET) 
	    return ERR_RTP_CANTBINDSOCKET;

    }
    
    if (session_type == RTP_SESSTYPE_UNIX)
    {
	addr_un = (struct sockaddr_un*)rtp_local_addr;
	rtp_length = sizeof(addr_un->sun_family) +
	    strlen(addr_un->sun_path) + 1;
	if (addr_un->sun_family != AF_UNIX) 
	    return ERR_RTP_CANTBINDSOCKET;
	
	/* delete the socket if it's already there */
	unlink(addr_un->sun_path);
	
	addr_un = (struct sockaddr_un*)rtcp_local_addr;
	rtcp_length = sizeof(addr_un->sun_family) +
	    strlen(addr_un->sun_path) + 1;
	if (addr_un->sun_family != AF_UNIX) 
	    return ERR_RTP_CANTBINDSOCKET;

	/* delete the socket if it's already there */
	unlink(addr_un->sun_path);
	
    }

    /**** now bind them  ***********/
    if (bind( *rtpsock, (struct sockaddr *)rtp_local_addr, rtp_length) < 0 )
    {
	RTPCLOSESOCKET(*rtpsock);
	RTPCLOSESOCKET(*rtcpsock);
	return ERR_RTP_CANTBINDSOCKET;
    }
    if (bind( *rtcpsock, (struct sockaddr *)rtcp_local_addr, rtcp_length) < 0)
    {
	RTPCLOSESOCKET(*rtpsock);
	RTPCLOSESOCKET(*rtcpsock);
	return ERR_RTP_CANTBINDSOCKET;
    }

    if (session_type == RTP_SESSTYPE_UNIX)
    {
	addr_un = (struct sockaddr_un*)rtp_local_addr;
	chmod(addr_un->sun_path, 0777);
	addr_un = (struct sockaddr_un*)rtcp_local_addr;
	chmod(addr_un->sun_path, 0777);
    }

    if ( listen(*rtpsock, RTP_LISTEN_BACKLOG) < 0)
	return ERR_RTP_CANTLISTEN;
    if ( listen(*rtcpsock, RTP_LISTEN_BACKLOG) < 0)
	return ERR_RTP_CANTLISTEN;

    return 0;
}


/***** STATIC FUNCTIONS ***************************************************/

int
_set_stream_socket_buffers(RTPSOCKET rtpsock, RTPSOCKET rtcpsock)
{
    int size;
    /* Set socket receive and send buffers */
    size = RTP_RECEIVEBUFFERSIZE;
    if (setsockopt(rtpsock,SOL_SOCKET,SO_RCVBUF, &size,sizeof(int)) != 0)
	return ERR_RTP_CANTSETSOCKETBUFFER;

    size = RTP_RECEIVEBUFFERSIZE;
    if (setsockopt(rtcpsock,SOL_SOCKET,SO_RCVBUF, &size,sizeof(int)) != 0)
	return ERR_RTP_CANTSETSOCKETBUFFER;

    size = RTP_SENDBUFFERSIZE;
    if (setsockopt(rtpsock,SOL_SOCKET,SO_SNDBUF, &size,sizeof(int)) != 0)
	return ERR_RTP_CANTSETSOCKETBUFFER;

    size = RTP_SENDBUFFERSIZE;
    if (setsockopt(rtcpsock,SOL_SOCKET,SO_SNDBUF, &size,sizeof(int)) != 0)
	return ERR_RTP_CANTSETSOCKETBUFFER;
    return 0;
}

int
_set_reuseaddr(RTPSOCKET rtpsock, RTPSOCKET rtcpsock)
{
    return 0;
}

int
_create_local_sockets(int session_type, RTPSOCKET* rtpsock,
			  RTPSOCKET* rtcpsock)
{
    if ( (session_type != RTP_SESSTYPE_TCP) && (session_type != RTP_SESSTYPE_UNIX) )
       return ERR_RTP_CANTCREATESOCKET;

    /* Create the sockets */
    if (session_type == RTP_SESSTYPE_TCP)
    {
	*rtpsock = socket(AF_INET,SOCK_STREAM,0);
	if (*rtpsock == RTPSOCKERR)
	    return ERR_RTP_CANTCREATESOCKET;
	*rtcpsock = socket(AF_INET,SOCK_STREAM,0);
	if (*rtcpsock == RTPSOCKERR)
	    return ERR_RTP_CANTCREATESOCKET;
    }
    else /* it's UNIX */
    {
	*rtpsock = socket(AF_UNIX,SOCK_STREAM,0);
	if (*rtpsock == RTPSOCKERR)
	    return ERR_RTP_CANTCREATESOCKET;
	*rtcpsock = socket(AF_UNIX,SOCK_STREAM,0);
	if (*rtcpsock == RTPSOCKERR)
	    return ERR_RTP_CANTCREATESOCKET;
    }

    return 0;
}
