/*
 * 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>

// Accepts a TCP or UNIX connection request on a supplied pair of
// listening sockets.
int rtp_transport_stream_accept(int new_session_type,
				RTPSOCKET rtplisten,
				RTPSOCKET rtcplisten,
				RTPSOCKET* rtpsock,
				RTPSOCKET* rtcpsock,
				struct sockaddr* peer_addr,
				int localipaddress)
{
	int size;
	RTPSOCKLENTYPE socklen;
	struct sockaddr     addr_junk;
	struct sockaddr_in* addr_in;
	
	if (socketsopened)
		return ERR_RTP_CONNALREADYCREATED;
		
	if (!peer_addr) return ERR_RTP_NULLPTR;

	session_type = new_session_type;
	
	/* accept the connection request on the RTP socket*/
	*rtpsock = accept(rtplisten, peer_addr, 0);
	if (*rtpsock == RTPSOCKERR)
	    return ERR_RTP_CANTCREATESOCKET;
	
	/* accept the connection request on the RTCP socket */
	*rtcpsock = accept(rtcplisten, &addr_junk, 0);
	if (*rtcpsock == RTPSOCKERR)
	    return ERR_RTP_CANTCREATESOCKET;
	
	/* Set socket receive and send buffers */
	size = RTP_RECEIVEBUFFERSIZE;
	if (setsockopt(*rtpsock,SOL_SOCKET,SO_RCVBUF,(const char *)&size,sizeof(int)) != 0)
		return ERR_RTP_CANTSETSOCKETBUFFER;
	size = RTP_RECEIVEBUFFERSIZE;
	if (setsockopt(*rtcpsock,SOL_SOCKET,SO_RCVBUF,(const char *)&size,sizeof(int)) != 0)
		return ERR_RTP_CANTSETSOCKETBUFFER;
	size = RTP_SENDBUFFERSIZE;
	if (setsockopt(*rtpsock,SOL_SOCKET,SO_SNDBUF,(const char *)&size,sizeof(int)) != 0)
		return ERR_RTP_CANTSETSOCKETBUFFER;
	size = RTP_SENDBUFFERSIZE;
	if (setsockopt(*rtcpsock,SOL_SOCKET,SO_SNDBUF,(const char *)&size,sizeof(int)) != 0)
		return ERR_RTP_CANTSETSOCKETBUFFER;

	/* Get the local IP address if this is a TCP connection*/
	if (new_session_type == RTP_SESSTYPE_TCP)
	{
	    if (localipaddress == 0) // user didn't supply an IP address
	    {
		localip = CalcLocalIP();
		if (localip == 0)
		{
		    RTPCLOSESOCKET(*rtpsock);
		    RTPCLOSESOCKET(*rtcpsock);
		    return ERR_RTP_CANTGETLOCALIP;
		}
	    }
	    else // user specified the local IP address
		localip = localipaddress;
	}

	return 0;
}

// Accepts a XClientMessage connection
int RTPConnection::Create_from_XClientMessage(int local_windowid, int
					      remote_windowid)
{
    // TODO
    return 0;
}

// Tries to connect to a TCP or UNIX endpoint
int RTPConnection::Create_connect_to(int new_session_type,
				     struct sockaddr* rtp_addr,
				     struct sockaddr* rtcp_addr,
				     int localipaddress) 
{
	int size;
	RTPSOCKLENTYPE socklen;

	if (socketsopened)
		return ERR_RTP_CONNALREADYCREATED;
	if ( (new_session_type != RTP_SESSTYPE_TCP)
	     && (new_session_type != RTP_SESSTYPE_UNIX) )
	    return ERR_RTP_CANTCREATESOCKET;
	
	/* Create the sockets */
	if (new_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
	{
	    rtpsock = socket(PF_UNIX,SOCK_STREAM,0);
	    if (rtpsock == RTPSOCKERR)
		return ERR_RTP_CANTCREATESOCKET;
	    rtcpsock = socket(PF_UNIX,SOCK_STREAM,0);
	    if (rtcpsock == RTPSOCKERR)
		return ERR_RTP_CANTCREATESOCKET;
	}

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

	/* Connect to the peer */
	if ( new_session_type == RTP_SESSTYPE_TCP )
	{
	    if ( connect(rtpsock, rtp_addr, sizeof(struct sockaddr_in)) != 0)
	    {
		RTPCLOSESOCKET(rtpsock);
		RTPCLOSESOCKET(rtcpsock);
		return ERR_RTP_CANTBINDSOCKET;
	    }
	    if ( connect(rtcpsock, rtcp_addr, sizeof(struct sockaddr_in)) != 0)
	    {
		RTPCLOSESOCKET(rtpsock);
		RTPCLOSESOCKET(rtcpsock);
		return ERR_RTP_CANTBINDSOCKET;
	    }
	}
	else
	{
	    if ( connect(rtpsock, rtp_addr, sizeof(struct sockaddr_un)) != 0)
	    {
		RTPCLOSESOCKET(rtpsock);
		RTPCLOSESOCKET(rtcpsock);
		return ERR_RTP_CANTBINDSOCKET;
	    }
	    if ( connect(rtcpsock, rtcp_addr, sizeof(struct sockaddr_un)) != 0)
	    {
		RTPCLOSESOCKET(rtpsock);
		RTPCLOSESOCKET(rtcpsock);
		return ERR_RTP_CANTBINDSOCKET;
	    }
	}
	
	if (new_session_type == RTP_SESSTYPE_TCP)
	{
	    /* Get the1 local IP address */
	    if (localipaddress == 0) // user didn't supply an IP address
	    {
		localip = CalcLocalIP();
		if (localip == 0)
		{
		    RTPCLOSESOCKET(rtpsock);
		    RTPCLOSESOCKET(rtcpsock);
		    RTPCLOSESOCKET(sendsock);
		    return ERR_RTP_CANTGETLOCALIP;
		}
	    }
	    else // user specified the local IP address
		localip = localipaddress;
	}

	socketsopened = true;

	session_type = new_session_type;
	session_state = RTP_SESS_STATE_CONNECTED;

	return 0;
}

int RTPConnection::Create_TCP_listener(int pbase, int localipaddress)
{
	int size;
	RTPSOCKLENTYPE socklen;

	if (socketsopened)
		return ERR_RTP_CONNALREADYCREATED;
		
	/* Check if the localportbase is even, conforming to RFC 1889 */
	
	if (pbase%2 != 0)
		return ERR_RTP_PORTBASENOTEVEN;
	
	/* Create the sockets */

	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;

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

	/* Bind the sockets */
	
	struct sockaddr_in addr;

	addr.sin_family = AF_INET;
	addr.sin_port = htons(pbase);
	addr.sin_addr.s_addr = htonl(INADDR_ANY);
	if (bind(rtpsock,(struct sockaddr *)&addr,sizeof(struct sockaddr)) != 0)
	{
		RTPCLOSESOCKET(rtpsock);
		RTPCLOSESOCKET(rtcpsock);
		return ERR_RTP_CANTBINDSOCKET;
	}

	addr.sin_family = AF_INET;
	addr.sin_port = htons(pbase+1);
	addr.sin_addr.s_addr = htonl(INADDR_ANY);
	if (bind(rtcpsock,(struct sockaddr *)&addr,sizeof(struct sockaddr)) != 0)
	{
		RTPCLOSESOCKET(rtpsock);
		RTPCLOSESOCKET(rtcpsock);
		return ERR_RTP_CANTBINDSOCKET;
	}

	/* Get the local IP address */
	if (localipaddress == 0) // user didn't supply an IP address
	{
		localip = CalcLocalIP();
		if (localip == 0)
		{
			RTPCLOSESOCKET(rtpsock);
			RTPCLOSESOCKET(rtcpsock);
			RTPCLOSESOCKET(sendsock);
			return ERR_RTP_CANTGETLOCALIP;
		}
	}
	else // user specified the local IP address
		localip = localipaddress;

	socketsopened = true;
	session_type = RTP_SESSTYPE_TCP;
	session_state = RTP_SESS_STATE_LISTENING;
	portbase = pbase;
	
	return 0;
}

int RTPConnection::Create_UNIX_listener(char* rtp_path, char* rtcp_path)
{
         int size;
	 RTPSOCKLENTYPE socklen;
	 
	 if (socketsopened)
	     return ERR_RTP_CONNALREADYCREATED;
 
	/* Create the sockets */

	rtpsock = socket(PF_UNIX,SOCK_STREAM,0);
	if (rtpsock == RTPSOCKERR)
		return ERR_RTP_CANTCREATESOCKET;
	rtcpsock = socket(PF_UNIX,SOCK_STREAM,0);
	if (rtcpsock == RTPSOCKERR)
		return ERR_RTP_CANTCREATESOCKET;

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

	/* Bind the sockets */
	
	struct sockaddr_un addr;
	
	addr.sun_family = PF_UNIX;
	strncpy(addr.sun_path, rtp_path, RTP_UNIX_PATH_MAX);
	addr.sun_path[RTP_UNIX_PATH_MAX-1] = 0; // force null termination
	if (bind(rtpsock,(struct sockaddr *)&addr,sizeof(struct sockaddr)) != 0)
	{
		RTPCLOSESOCKET(rtpsock);
		RTPCLOSESOCKET(rtcpsock);
		return ERR_RTP_CANTBINDSOCKET;
	}

	addr.sun_family = PF_UNIX;
	strncpy(addr.sun_path, rtcp_path, RTP_UNIX_PATH_MAX);
	addr.sun_path[RTP_UNIX_PATH_MAX-1] = 0; // force null termination
	if (bind(rtcpsock,(struct sockaddr *)&addr,sizeof(struct sockaddr)) != 0)
	{
		RTPCLOSESOCKET(rtpsock);
		RTPCLOSESOCKET(rtcpsock);
		return ERR_RTP_CANTBINDSOCKET;
	}

	socketsopened = true;
	session_type = RTP_SESSTYPE_UNIX;
	session_state = RTP_SESS_STATE_LISTENING;
	portbase = 0;

	return 0;
}
