/*
....[@@@..[@@@..............[@.................. MUD++ is a written from
....[@..[@..[@..[@..[@..[@@@@@....[@......[@.... scratch multi-user swords and
....[@..[@..[@..[@..[@..[@..[@..[@@@@@..[@@@@@.. sorcery game written in C++.
....[@......[@..[@..[@..[@..[@....[@......[@.... This server is an ongoing
....[@......[@..[@@@@@..[@@@@@.................. development project.  All 
................................................ contributions are welcome. 
....Copyright(C).1995.Melvin.Smith.............. Enjoy. 
------------------------------------------------------------------------------
Melvin Smith (aka Fusion)         msmith@falcon.mercer.peachnet.edu 
MUD++ development mailing list    mudpp-list@spice.com
------------------------------------------------------------------------------
socket.cc
*/

#include <ctype.h>
#include "socket.h"


// This is the beginning of redesigning the Server and all IO classes
// The Socket class should be in the Stream class hierarchy but as
// I have the hierarchy now, it is not easy to insert Socket since
// Sockets can't be memory mapped so the IStream class wont work with it.
// The Read(), Write() functions are the same as Server:: functions and
// will replace them when Socket class is done. The Server will then
// have a slightly different role.

/*
Structures describing an Internet socket address
from sys/in.h

INADDR_ANY is 0.0.0.0 which means accept all incoming
#define INADDR_ANY ((unsigned long int) 0x00000000)

struct in_addr
{
	__u32 s_addr;
};

struct sockaddr_in
{
	short int sin_family;          Address family
	unsigned short int sin_port;   Port number ( network byte order )
	struct in_addr sin_addr;       IP address

	unsigned char __pad[ XXX ];    Pads to size of sockaddr
};
*/



Socket::Socket( int port )
{
	int size = sizeof( sockaddr_in ); 

	if( ( sock = socket( AF_INET, SOCK_STREAM, 0 ) ) == -1 )
	{
		err = SOCK_NOT_CREATED;
		return;
	}

	if( fcntl( sock, F_SETFL, O_NONBLOCK ) == -1 )
	{
		err = SOCK_FCNTL_ERR;
		return;
	}

	if( ( ::getsockname( sock, ( sockaddr * )this, &size ) ) == -1 )
	{
		// err = SOCK_CANT_GET_NAME;
		return;
	}

	strcpy( ip, "0.0.0.0" );
	sin_port = htons( port );
	sin_family = AF_INET;
	sin_addr.s_addr = htonl( INADDR_ANY );

	err = 0;
}


Socket::Socket( char * address, int port )
{
	strcpy( ip, address );
	sin_port = htons( port );
	sin_family = AF_INET;

	if( isdigit( *address ) )
	{
		if( (long)( sin_addr.s_addr = inet_addr( address ) ) == -1 )
		{
			perror( "inet_addr" );
			err = SOCK_BAD_ADDRESS;
			return;
		}
	}
	else
	{
		struct hostent *he;
		if( !( he = ::gethostbyname( address ) ) )
		{
			perror( "gethostbyname" );
			err = SOCK_UNKNOWN_HOST;
			return;
		}

		memcpy( (char*)&sin_addr, he->h_addr, sizeof( sin_addr ) );
	}

	if( ( sock = ::socket( AF_INET, SOCK_STREAM, 0 ) ) == -1 )
	{
		perror( "socket" );
		err = SOCK_NOT_CREATED;
		return;
	}

	if( fcntl( sock, F_SETFL, O_NONBLOCK ) == -1 )
	{
		perror( "fcntl" );
		err = SOCK_FCNTL_ERR;
		return;
	}

	err = 0;
}


// This is for recreating a socket on a live TCP connection after a
// reboot.
Socket::Socket( char *address, int port, int desc )
{
	sock = desc;
	sin_port = htons( port );
	sin_family = AF_INET;
	
	if( !*address || !strcmp( address, "0.0.0.0" ) )
	{
		strcpy( ip, "0.0.0.0" );
		return;
	}

	strcpy( ip, address );

	if( isdigit( *ip ) )
	{
		if( (long)( sin_addr.s_addr = inet_addr( ip ) ) == -1 )
		{
			perror( "inet_addr" );
			err = SOCK_BAD_ADDRESS;
			return;
		}
	}
	else
	{
		struct hostent *he;
		if( !( he = ::gethostbyname( address ) ) )
		{
			perror( "gethostbyname" );
			err = SOCK_UNKNOWN_HOST;
			return;
		}

		memcpy( (char*)&sin_addr, he->h_addr, sizeof( sin_addr ) );
	}
}


int Socket::listen()
{
	if( ( ::bind( sock, ( sockaddr * )this, sizeof( sockaddr_in  ) ) ) == -1 )
	{
		// err = SOCK_CANT_BIND;
		return -1;
	}

	if( ( ::listen( sock, 5 ) ) == -1 )
	{
		// err = SOCK_CANT_LISTEN;
		return -1;
	}

	return 0;
}


Socket * Socket::accept()
{
	Socket *newSock = new Socket;
	int desc;
	int size = sizeof( sockaddr_in );

	if( ( desc = ::accept( sock, ( sockaddr * ) newSock, &size ) ) == -1 )
	{
		// err = SOCK_CANT_ACCEPT;
		return 0;
	}

	newSock->setDesc( desc );

	if( newSock->getFamily() != AF_INET )
	{
		// err = SOCK_INVALID_ADDRESS;
		newSock->close();
		delete newSock;
		return 0;
	}

	if( newSock->noDelay() == -1 )
	{
		// err = SOCK_CANT_SET_NODELAY;
		newSock->close();
		delete newSock;
		return 0;
	}

	// Now get info for new connection
	if( newSock->getPeerName() == -1 )
	{
		// err = SOCK_CANT_GET_PEER_NAME;
		delete newSock;
		return 0;
	}

	if( newSock->resolveIP() == -1 )
	{
		delete newSock;
		return 0;
	}

	return newSock;
}


int Socket::resolveIP()
{
	hostent * he = ::gethostbyaddr( (char *) &sin_addr,
								sizeof( sin_addr ), AF_INET );

	if( he )
		strcpy( ip, he->h_name );
	else
	{
		unsigned long addr = ntohl( sin_addr.s_addr );
		if( (long) addr == -1 )
		{
			*ip = '\0';
			return -1;
		}

		sprintf( ip, "%ld.%ld.%ld.%ld", ( addr >> 24 ) & 0x000000ff,
					( addr >> 16 ) & 0x000000ff,
					( addr >> 8  ) & 0x000000ff,
					( addr       ) & 0x000000ff  );
	}

	return 0;
}


int Socket::getPeerName()
{
	int size = sizeof( sockaddr );
	if( ::getpeername( sock, ( sockaddr * ) this, &size ) == -1 )
	{
		// err = SOCK_CANT_GET_PEER_NAME
		return -1;
	}

	return 0;
}


int Socket::reuseAddr()
{
	int i;
	if( ::setsockopt( sock, SOL_SOCKET, SO_REUSEADDR, (char *)&i, sizeof(i) ) == -1 )
	{
		err = SOCK_SOCKOPT_ERR;
		return -1;
	}

	return 0;
}


int Socket::noDelay()
{
	if( fcntl( sock, F_SETFL, FNDELAY ) == -1 )
	{
		err = SOCK_FCNTL_ERR;
		return -1;
	}

	return 0;
}


int Socket::nonBlock()
{
	if( fcntl( sock, F_SETFL, O_NONBLOCK ) == -1 )
	{
		err = SOCK_FCNTL_ERR;
		return -1;
	}

	return 0;
}


int Socket::connect()
{
	if( ::connect( sock, (sockaddr *)this, sizeof( sockaddr ) ) )
	{
		if( errno != EINPROGRESS )
		{
			err = SOCK_NOT_CONNECTED;
			return -1;
		}
	}

	return 0;
}


int Socket::write( const char * buf )
{
	return write( buf, strlen( buf ) );
}


int Socket::write( const char * buf, int size )
{
	int error;
	int bytes = 0;
	int max_write = 1024;

	if( size < max_write )
		max_write = size;

	while( 1 )
	{
		if( ( error = ::write( sock, ( buf + bytes ), max_write ) ) != -1 )
		{
			bytes += error;
			if( bytes >= size )
				return bytes;
			else
				max_write = size - bytes;
			continue;
		}
		else
		{
			if( errno == EAGAIN )
				continue;
			else
				return -1;
		}
	}
}


int Socket::read( char * buf, int max )
{
	int bytes = 0;
	int error;

	while( 1 )
	{
		if( ( error = ::read( sock, buf, ( max - bytes ) ) ) > 0 )
		{
			bytes += error;
			*(buf + bytes) = '\0';
		}
		else if( !error )
		{
			*( buf + bytes ) = '\0';
			err = SOCK_EOF;
			return bytes;
		}
		else
		{
			if( errno == EAGAIN )
			{
				*(buf + bytes ) = '\0';
				return bytes;
			}
			else if( bytes > 0 )
			{
				*(buf + bytes ) = '\0';
				return bytes;
			}

			return -1;
		}
	}
}


int Socket::echoOff()
{
	return write( REQ_ECHO_OFF, 3 );
}

int Socket::echoOn()
{
	return write( REQ_ECHO_ON, 3 );
}