/* FotonSoft socket functions for FotonCom II under LINUX,WIN32                       *
 * FAIL-SAFE for strncpy , strncat , snprintf                                         *
 * Last modification: 2001.06.07 no ez igy itt nem igaz, de nem erek ra dokumentalni  */

#include <stdio.h>
#include <unistd.h>
#include <errno.h>

#include "socket.h"
#include "thread.h"
#include "log.h"

FCII_SOCKET *FCII_Sockets=NULL;
DWORD FCII_SocketNum=0;
FOHANDLE FCII_SocketMutex=NULL;

int maximum_pending_listens = 50;

void FSSOCKET_GetLocalIPAddress( char *ipaddress ) {		/* megmondja a local IP cimet (valoszinu 127.0.0.1 lesz mindig) */
	HOSTENT	*hostentry;
	HOSTENT *hostent_ptr;
	char tempbuf[2048];
	int herr;

	char	hostname_buffer[256];
	gethostname( hostname_buffer , 256);
	hostentry=(HOSTENT *)malloc( sizeof( HOSTENT ));
	if (gethostbyname_r( hostname_buffer , hostentry , tempbuf , 2048 , &hostent_ptr , &herr )) {
		free( hostentry );
		hostentry = NULL;
	}
	if (hostentry) {
		memset( ipaddress , 0 , 16 );
		/* SL020521 - FIXWIN */
		snprintf( ipaddress , 16 , "%u.%u.%u.%u",
			(unsigned char)hostentry->h_addr_list[0][0],
			(unsigned char)hostentry->h_addr_list[0][1],
			(unsigned char)hostentry->h_addr_list[0][2],
			(unsigned char)hostentry->h_addr_list[0][3] );
	}
	else
		strncpy( ipaddress, "0.0.0.0" , 15 );
	if (hostentry)
		free( hostentry );
}

void FSSOCKET_GetIPAddress( struct in_addr sockaddr, char *ipaddress ) {		/* IP-cimme konvertalja a sockaddr strukturat */
	memset( ipaddress , 0 ,16 );
	strncpy( ipaddress , inet_ntoa( sockaddr ) , 15 );
}
void FSSOCKET_GetExactSocketError( char *errormessage , DWORD length ) {		/* socket error beszedesbe */
	DWORD errnum;
	errnum = errno;
	switch (errnum) {
		case EBADF: snprintf( errormessage, length , "(SOCKET) not a valid socket descriptor...");break;
		case EINVAL: snprintf( errormessage, length , "(SOCKET) the socket is already bound to an address...");break;
		case EACCES: snprintf( errormessage , length, "(SOCKET) the address is protected...");break;
		case ENOTSOCK: snprintf( errormessage, length, "(SOCKET) descriptor is not a socket descriptor...");break;
		default: snprintf( errormessage, length , "(SOCKET unknown socket error(%08x:%d), append sources at fs_socket.c line:%d...",(unsigned int)errnum,(int)errnum,__LINE__);
	}
}

BOOL FSSOCKET_Initialize( void ) {	/* socket inicializalasa (es ssl, ha be van forditva) */
	LogMessage( "(SOCKET) layer initialized..." );
	FCII_SocketMutex = FoCreateMutex( NULL , FALSE , NULL );
	return TRUE;
}

BOOL FSSOCKET_Deinitialize( void ) {	/* socket alrendszer deinicializalasa (es ssl, ha be van forditva) */
	if (!FCII_SocketMutex)
		return FALSE;
	LogMessage( "(SOCKET) layer deinitialized...");
	FoWaitForSingleObject( FCII_SocketMutex , INFINITE );
	FoCloseHandle( FCII_SocketMutex );
	FCII_SocketMutex = NULL;
	return TRUE;
}

SOCKET FSSOCKET_Listen( char *ipaddress, WORD port , char *parameters) {
	/* adott port adott ip-jen letrehoz egy hallgatozo socket-et es ezt adja vissza (server oldal) */
	SOCKET		newsocket;
	char errormessage[256];
	SOCKADDR_IN	sin;
	int		n;

	newsocket = socket( AF_INET , SOCK_STREAM , IPPROTO_TCP );	/* socket letrehozasa */
	if ( newsocket == INVALID_SOCKET ) {
		LogMessage( "(SOCKET) socket() allocation failed...");
		FSSOCKET_GetExactSocketError( errormessage , 255 );
		LogMessage( errormessage );
		return -1;
	}
	sin.sin_family = AF_INET;
	sin.sin_addr.s_addr = INADDR_ANY;
	sin.sin_port = htons( port );

	n = 1;
	if ( setsockopt( newsocket , SOL_SOCKET , SO_REUSEADDR , (char *) &n , sizeof(n)) == SOCKET_ERROR ) {	/* ujrahasznositas bekapcs */
		LogMessage( "(SOCKET) setsockopt( REUSEADDR ) failed...");
		memset( errormessage , 0 , 256 );
		FSSOCKET_GetExactSocketError( errormessage , 255 );
		LogMessage( errormessage  );
		closesocket( newsocket );
		return -1;
	}
	if ( bind( newsocket , (LPSOCKADDR) &sin , sizeof( sin ) ) == SOCKET_ERROR ) {		/* socket bindolasa */
		LogMessage( "(SOCKET) bind() of socket failed...");
		FSSOCKET_GetExactSocketError( errormessage , 255 );
		LogMessage( errormessage );
		return -1;
	}
	if ( listen( newsocket , maximum_pending_listens ) == SOCKET_ERROR ) {			/* listen indul */
		LogMessage( "(SOCKET) listen() of socket failed...");
		FSSOCKET_GetExactSocketError( errormessage , 255 );
		LogMessage( errormessage );
		return -1;
	}
	return newsocket;
}

SOCKET FSSOCKET_Accept( SOCKET listensocket , SOCKADDR *client_address , char *parameters ) {
	/* ha a listen sikerult, akkor az accept az, aki egy beerkezo connectionnek (a kliensek)
	 * kioszt ad egy socketet */
	unsigned int addrlen;
	SOCKET accept_result;
	SOCKADDR_IN *in_addr;
	char errormsg[2048];
	char inet_address[32];
	addrlen = sizeof( SOCKADDR );				/* SL010521: ez kellett, kulonben a win32 visszater WSAEFAULT-tal */
	accept_result = accept( listensocket , client_address , &addrlen );
	if (accept_result == INVALID_SOCKET) {
		memset( errormsg , 0 , 2048 );
		snprintf( errormsg , 2047 , "(SOCKET) accept of socket(%d) failed..." , listensocket );
		LogMessage( errormsg );
		FSSOCKET_GetExactSocketError( errormsg , 2047  );
		LogMessage( errormsg );
		return INVALID_SOCKET;
	}
	/* getting IP address */
	in_addr = (SOCKADDR_IN *)client_address;
	memset( inet_address , 0 , 32 );
	strncpy( inet_address , inet_ntoa( in_addr->sin_addr ), 31);
	FSSOCKET_InsertSocket( accept_result );
	return accept_result;
}

int FSSOCKET_HardClose( SOCKET socket , char *parameters ) {
	/* bunko bezaras, ugy leszakitja a gyereket a vonalrol,
	 * mint a havibaj a mehnyalkahartyat */
	return shutdown( socket , 2 );

}

int FSSOCKET_LightClose( SOCKET socket , char *parameters ) {
	/* ovatoasabb csukja be, elotte beallitja, hogy varja meg meg a bufferben levo adatot */
	/* igy nagyobb esellyen indul a paciens arra, hogy tudjon rola, hogy megszakadt */
	struct linger on_close_linger;
	on_close_linger.l_onoff = 0;
	on_close_linger.l_linger = 0;
	setsockopt( socket , SOL_SOCKET , SO_LINGER, (const char *) &on_close_linger , sizeof( on_close_linger ) );
	return closesocket( socket );
}

int FSSOCKET_Close( SOCKET socket , char *parameters ) {	/* bezaras hibauzenetekkel */
	int retval;
	struct linger on_close_linger;
	char errormsg[2048];
	on_close_linger.l_onoff = 0;
	on_close_linger.l_linger = 0;
	setsockopt( socket , SOL_SOCKET , SO_LINGER , (const char *) &on_close_linger , sizeof( on_close_linger ) );
	retval = closesocket( socket );
	if (retval == SOCKET_ERROR ) {
		memset( errormsg , 0, 2048 );
		snprintf( errormsg , 2047, "(SOCKET) closesocket(%d) failed..." , socket );
		LogMessage( errormsg );
		FSSOCKET_GetExactSocketError( errormsg , 2047 );
		LogMessage( errormsg );
	}
	return retval;
}

BOOL FSSOCKET_InsertSocket( SOCKET socket ) {
	/* egy socket felvetele a sajat listankba,
	 * a timeouthoz szukseges, egyedi azonositot veszi fel */
	DWORD i;
	FoWaitForSingleObject( FCII_SocketMutex , INFINITE );
	for (i=0;i<FCII_SocketNum;i++)
		if (FCII_Sockets[i].socket==socket) {
			FoReleaseMutex( FCII_SocketMutex );
			return FALSE;
		}
	FCII_Sockets=(FCII_SOCKET *)realloc( FCII_Sockets , sizeof( FCII_SOCKET ) * (FCII_SocketNum+1) );
	memset( &FCII_Sockets[FCII_SocketNum] , 0 , sizeof( FCII_SOCKET ));
	FCII_Sockets[FCII_SocketNum].socket = socket;
	FCII_SocketNum++;
	FoReleaseMutex( FCII_SocketMutex );
	return FALSE;
}

BOOL FSSOCKET_DeleteSocket( SOCKET socket ) {
	/* egy socket torlese a listabol */
	DWORD i;
	FoWaitForSingleObject( FCII_SocketMutex , INFINITE );
	for (i=0;i<FCII_SocketNum;i++)
		if (FCII_Sockets[i].socket==socket) {
			memmove( &FCII_Sockets[i] , &FCII_Sockets[i+1] , sizeof( FCII_SOCKET ) * (FCII_SocketNum-i-1 ) );
			if (FCII_SocketNum-1)
				FCII_Sockets=(FCII_SOCKET *)realloc( FCII_Sockets , sizeof( FCII_SOCKET ) * (FCII_SocketNum-1 ) );
			else {
				free( FCII_Sockets );
				FCII_Sockets=NULL;
			}
			FCII_SocketNum--;
			FoReleaseMutex( FCII_SocketMutex );
			return TRUE;
		}
	FoReleaseMutex( FCII_SocketMutex );
	return FALSE;
}

BOOL FSSOCKET_IsStringIpAddress( char *string ) {
	/* a string, amit megadtunk valos IP?
	 * ezt rabizzuk az oprendszerre, ha nem tudja kovertalni, akkor nem az */
	return (inet_addr( string )!=INADDR_NONE);
}

DWORD FSSOCKET_ResolveIPFromHostname( char *hostname ) {
	/* IP-cim feloldasa a hosntname-bol:  www.fotonsoft.hu ==>195.70.37.170 */
	struct hostent *host;
	DWORD retval;
	struct hostent *host_ptr;
	char tempbuf[2048];
	int herr;
	host = (struct hostent *)malloc( sizeof( struct hostent ));
	if (!gethostbyname_r(hostname,host,tempbuf,2048,&host_ptr,&herr)) {
		free( host );
		host=NULL;
	}
	if (!host) {
		return 0x00000000;
	}
	retval = *((DWORD *)host->h_addr_list[0]);
	if (host)
		free( host );
	return retval;
}

DWORD FSSOCKET_ResolveIPFromIPString( char *ipstring ) {	/* ip konvertalasa stringbol */
	return inet_addr( ipstring );
}

SOCKET FSSOCKET_Connect( char *ipaddress , WORD port , char *parameters ) {
	/* socket connection letrehozasa ipaddress:port cimre */
	/* http-proxy feature */
	char errormsg[2048];
	SOCKET newsocket;
	SOCKADDR_IN sin;
	SOCKET retsocket;
	int option;
	char *tempstring;

	/* initialize http proxy settings */
	newsocket = socket( AF_INET , SOCK_STREAM , IPPROTO_TCP );
	if ( newsocket == INVALID_SOCKET ) {
		LogMessage( "(SOCKET) socket() allocation failed...");
		memset( errormsg , 0 , 2048 );
		FSSOCKET_GetExactSocketError( errormsg , 2047  );
		LogMessage( errormsg );
		return -1;
	}
	sin.sin_family = AF_INET;
	sin.sin_addr.s_addr = INADDR_ANY;
	sin.sin_port = 0;
	option = 1;

	if (setsockopt( newsocket , SOL_SOCKET , SO_REUSEADDR , (char *)&option , sizeof( option ) )==SOCKET_ERROR) {
		LogMessage( "(SOCKET) setsockopt( REUSEADDR ) failed...");
		FSSOCKET_GetExactSocketError( errormsg , 2047 );
		LogMessage( errormsg );
		return -1;
	}
	if (bind( newsocket , (LPSOCKADDR) &sin , sizeof( sin ))==SOCKET_ERROR) {
		LogMessage( "(SOCKET) bind() failed...");
		FSSOCKET_GetExactSocketError( errormsg , 2047  );
		LogMessage( errormsg );
		return -1;
	}
	
	sin.sin_family = AF_INET;
	/* direct socket connect */
	if (FSSOCKET_IsStringIpAddress( ipaddress ))
		sin.sin_addr.s_addr = FSSOCKET_ResolveIPFromIPString( ipaddress );
	else
		sin.sin_addr.s_addr = FSSOCKET_ResolveIPFromHostname( ipaddress );
	sin.sin_port = htons( port );

	retsocket = connect( newsocket , (SOCKADDR *) &sin , sizeof( SOCKADDR ) );
	if (retsocket == INVALID_SOCKET ) {
		DWORD errnum;
		char tempstring[1024];
		errnum = errno;
		sprintf( tempstring , "(SOCKET) connect() failed...(error:%d) may the server not listening?" , (int)errnum );
		closesocket( newsocket ); /* SL011016 - a socket() altal foglaltat felszabaditjuk */
		LogMessage( tempstring );
		FSSOCKET_GetExactSocketError( errormsg , 2047 );
		LogMessage( errormsg );
		return INVALID_SOCKET;
	}
	FSSOCKET_InsertSocket( newsocket );
	return newsocket;
}

char *FSSOCKET_ReceiveStringSimple( SOCKET socket ) {
	char *retval;
	char cur_byte;
	DWORD bytesrecv;
	DWORD retlength;
	BOOL exit;
	char errormessage[2048];
	
	retval = NULL;
	retlength=0;
	exit=FALSE;
	while (!exit) {
		bytesrecv = recv( socket , &cur_byte , 1 , 0 );
		if (((int)bytesrecv) == SOCKET_ERROR ) {
			memset( errormessage , 0 , 2048 );
			snprintf( errormessage , 2047 , "(SOCKET) cannot receive the message on socket %d" , socket );
			LogMessage( errormessage );
			FSSOCKET_GetExactSocketError( errormessage , 2047 );
			LogMessage( errormessage );
			if (retval) free( retval );
			return NULL;
		}
		if (!bytesrecv) {
			memset( errormessage , 0 , 2048 );
			snprintf( errormessage , 2047 , "(SOCKET) recv 0 bytes on socket %d" , socket );
			LogMessage( errormessage );
			if (retval) free( retval );
			return NULL;
		}
		if (cur_byte=='\n') {
			exit=TRUE;
			continue;
		}
		retval = (char *)realloc( retval , retlength + 2 );
		retval[retlength]=cur_byte;
		retlength++;
	}
	if (retval)
		retval[retlength]=0;
	return retval;
}

BOOL FSSOCKET_SendStreamSimple( SOCKET socket , UCHAR *message , DWORD messagesize ,char *parameters ) {
	/* szimpla socket-en message kuldese (messagesize hosszu) */
	char errormessage[2048];
	DWORD bytesleft;
	DWORD bytessent;
	DWORD allbytessent;

	bytesleft = messagesize;
	allbytessent=0;
	memset( errormessage , 0 , 2048 );
	while (bytesleft) {
		bytessent = send( socket , message + allbytessent , bytesleft , 0 );
		if (((int)bytessent) == SOCKET_ERROR) {
			memset( errormessage , 0 , 2048 );
			snprintf( errormessage , 2047 , "(SOCKET) can not send the message on socket %d" , socket );
			LogMessage( errormessage );
			FSSOCKET_GetExactSocketError( errormessage , 2047 );
			LogMessage( errormessage );
			return FALSE;
		}
		if (!bytessent) {
			memset( errormessage , 0 , 2048 );
			snprintf( errormessage , 2047 , "(SOCKET) sent 0 bytes on socket %d" , socket );
			LogMessage( errormessage );
			return FALSE;
		}
		bytesleft-=bytessent;
		allbytessent+=bytessent;
	}
	return TRUE;
}

	
BOOL FSSOCKET_SendStream( SOCKET socket , UCHAR *message , DWORD messagesize , char *parameters ) {
	/* globalizalt kuldes */
	/* a 2 tipusu (szimpla/SSL) kuldes kozul valaszt, parameter es ssl-jelenlet alapjan */
	return FSSOCKET_SendStreamSimple( socket , message , messagesize , parameters );
}

BOOL FSSOCKET_ReceiveStreamSimple( SOCKET socket , UCHAR *message , DWORD messagesize , char *parameters ) {
	/* simpla socket fogadas (message-be messagesize meretut) */
	DWORD bytesleft;
	DWORD bytesrecv;
	DWORD allbytesrecv;
	char errormessage[2048];

	bytesleft = messagesize;
	allbytesrecv=0;
	memset( errormessage , 0 , 2048 );
	while (bytesleft) {
		bytesrecv = recv( socket , message + allbytesrecv , bytesleft , 0 );
		if (((int)bytesrecv) == SOCKET_ERROR ) {
			memset( errormessage , 0 , 2048 );
			snprintf( errormessage , 2047 , "(SOCKET cannot receive the message on socket %d" , socket );
			LogMessage( errormessage );
			FSSOCKET_GetExactSocketError( errormessage , 2047 );
			LogMessage( errormessage );
			return FALSE;
		}
		if (!bytesrecv) {
			memset( errormessage , 0 , 2048 );
			snprintf( errormessage , 2047 , "(SOCKET) recv 0 bytes on socket %d" , socket );
			LogMessage( errormessage );
			return FALSE;
		}
		bytesleft-=bytesrecv;
		allbytesrecv+=bytesrecv;
	}
	return TRUE;
}

BOOL FSSOCKET_ReceiveStream( SOCKET socket , UCHAR *message , DWORD messagesize , char *parameters ) {
	/* globalizalt fogadas */
	/* a 2 tipusu (szimpla/SSL) fogadas kozul valaszt, parameter es ssl-jelenlet alapjan */
	return FSSOCKET_ReceiveStreamSimple( socket , message , messagesize , parameters );
}
