
/*
	highscore-server.c -- The hiscore server using TCP/IP stream.
	~yas/dsys/highscore/tcp/highscore-server.c
*/

#include <stdio.h>	/* stderr, fprintf() */
#include <stdlib.h>	/* strtol() */
#include <unistd.h>	/* close() */
#include <string.h>	/* memcpy() */
#include <sys/types.h>	/* socket() */
#include <sys/socket.h>	/* socket() */
#include <netinet/in.h>	/* struct sockaddr_in, INADDR_ANY */
#include <netdb.h>	/* getnameinfo() */
#include "highscore-tcp.h"
#include "marshaling-burffer.h"

/* From Coins System Program */
extern	void tcp_sockaddr_print( int com );
extern	void tcp_peeraddr_print( int com );
extern	void sockaddr_print( struct sockaddr *addrp, socklen_t addr_len );
extern int  tcp_acc_port( int portno, int ip_version );

/* Hiscore data in memory */
static score_record_t hiscore_records[HIGHSCORE_MAX_RECORDS];
static int	      hiscore_nelements;

static void hiscore_server( int portno, int ip_version );
static void hiscore_receive_request_and_send_reply( int com );
static void print_my_host_port( int portno );
static int insert_score( score_record_t records[], int len, int nelement,
			 int score, char *user );
static int find_posision( score_record_t records[], int len, int nelement,
			  int score );

static void usage( char *comname ) {
	fprintf(stderr,"Usage: %% %s portno ip\n", comname);
	exit( 1 );
}

int main( int argc, char *argv[], char *envp[] ) {
	int portno, ip_version;

        if( !(argc == 2 || argc==3) ) {
		fprintf(stderr,"Usage: %s portno {ipversion}\n",argv[0] );
		exit( 1 );
	}
	portno = strtol( argv[1],0,10 );
	if( argc == 3 )
		ip_version = strtol( argv[2],0,10 );
	else
		ip_version = 46; /* Both IPv4 and IPv6 by default */
	hiscore_server( portno, ip_version );
}

static void hiscore_server( int portno, int ip_version ) {
    int acc,com ;
	acc = tcp_acc_port( portno, ip_version );
	if( acc<0 )
	    exit( -1 );
	print_my_host_port( portno );
	tcp_sockaddr_print( acc );
	printf("(To stop this server, Press ^C)\n");
	while( 1 ) {
	    if( (com = accept( acc,0,0 )) < 0 ) {
		perror("accept");
		exit( -1 );
	    }
	    tcp_peeraddr_print( com );
	    hiscore_receive_request_and_send_reply( com );
	}
}

static void hiscore_receive_request_and_send_reply( int com ) {
    marbuf_t request, reply;
    int cmd;
	marbuf_init( &request,HISCORE_PROTO_MAX_MESSAGE_SIZE );
	marbuf_init( &reply,HISCORE_PROTO_MAX_MESSAGE_SIZE );
	if( marbuf_receive_message( &request, com ) < 0 ) {
	    perror("read");
	    goto error0;
	}
	
	if( !marbuf_unmarshal_int( &request, &cmd ) ) {
	    perror("request_msg cmd");
	    goto error0;
	}
	switch( cmd ) {
	case HISCORE_PROTO_PUT_SCORE:
	{
	    int score ; char name[HIGHSCORE_NAME_LEN];
	    if( !marbuf_unmarshal_int( &request, &score ) )
		goto error1;
	    if( !marbuf_unmarshal_byte_array( &request, name, HIGHSCORE_NAME_LEN ) )
		goto error1;
	    hiscore_nelements = insert_score( hiscore_records, HIGHSCORE_MAX_RECORDS,
					      hiscore_nelements, score, name );
	    if( !marbuf_marshal_int( &reply, HISCORE_PROTO_OK ) )
		goto error1;
	    break;
	}
	case HISCORE_PROTO_GET_HISCORE:
	{
	    int len,i,n ; 
	    if( !marbuf_unmarshal_int( &request, &len ) )
		goto error1;
	    if( len > HIGHSCORE_MAX_RECORDS )
		len = HIGHSCORE_MAX_RECORDS;
	    n = len < hiscore_nelements ? len : hiscore_nelements ;
	    /* return n and hiscore_records[0..n-1] to client. */
	    if( !marbuf_marshal_int( &reply, n ) )
		goto error1;
	    for( i=0 ; i<n; i++ )
	    {
	        if( !marbuf_marshal_int( &reply,hiscore_records[i].score ) )
		    goto error1;
	        if( !marbuf_marshal_byte_array(
			&reply,hiscore_records[i].name,HIGHSCORE_NAME_LEN ) )
		    goto error1;
	    }
	     break;
	 }
	default:
	    marbuf_marshal_int( &reply, HISCORE_PROTO_NO_COMMAND );
	    break;
	}
	marbuf_send_message( &reply, com );

error0:	marbuf_final( &request );
	marbuf_final( &reply );
	close( com );
	return;

error1:	marbuf_marshal_int( &reply, HISCORE_PROTO_MARSHAL_ERROR );
	marbuf_send_message( &reply, com );
	goto error0;
}

static void print_my_host_port( int portno ) {
    char hostname[100] ;
	gethostname( hostname,sizeof(hostname) );
	hostname[99] = 0 ;
	printf("run client %s %d \n",hostname, portno );
}

static int insert_score( score_record_t records[], int len, int nelement,
			 int score, char *user ) {
    int pos, copy_records;
    	pos  = find_posision( records, len, nelement, score );
	if( pos < 0 )
	    return( -1 );
	if( nelement == len )
	    copy_records = nelement - pos - 1;
	else
	    copy_records = nelement - pos ;
	memcpy( &records[pos+1], &records[pos],
		copy_records*sizeof(score_record_t) );
	memset( &records[pos], 0, sizeof(score_record_t) );
	records[pos].score = score ;
	snprintf( records[pos].name, HIGHSCORE_NAME_LEN, "%s", user );
	return( nelement == len ? nelement : nelement+1 );
}

static int find_posision( score_record_t records[], int len, int nelement,
			  int score ) {
    score_record_t *pos;
    int i;
    	for( i=0; i<nelement; i++ ) {
	    if( records[i].score <= score )
		return( i );
	}
	return( len == nelement ? -1 : nelement );
}

/* Coins System Program */
#define	BUFFERSIZE	1024

void
tcp_sockaddr_print( int com )
{
	struct sockaddr_storage addr ;
	socklen_t addr_len ; /* MacOSX: __uint32_t */

	addr_len = sizeof( addr );
    	if( getsockname( com, (struct sockaddr *)&addr, &addr_len  )<0 )
	{
		perror("tcp_peeraddr_print");
		return;
	}
    	printf("[%d] accepting (fd==%d) to ",getpid(),com );
	sockaddr_print( (struct sockaddr *)&addr, addr_len );
	printf("\n");
}

void
tcp_peeraddr_print( int com )
{
	struct sockaddr_storage addr ;
	socklen_t addr_len ; /* MacOSX: __uint32_t */

	addr_len = sizeof( addr );
    	if( getpeername( com, (struct sockaddr *)&addr, &addr_len  )<0 )
	{
		perror("tcp_peeraddr_print");
		return;
	}
    	printf("[%d] connection (fd==%d) from ",getpid(),com );
	sockaddr_print( (struct sockaddr *)&addr, addr_len );
	printf("\n");
}

void
sockaddr_print( struct sockaddr *addrp, socklen_t addr_len )
{
	char host[BUFFERSIZE] ;
	char port[BUFFERSIZE] ;

	if( getnameinfo(addrp, addr_len, host, sizeof(host),
			port, sizeof(port), NI_NUMERICHOST|NI_NUMERICSERV)<0 )
		return;
	if( addrp->sa_family == PF_INET )
		printf("%s:%s", host, port );
	else
		printf("[%s]:%s", host, port );
}

#define PORTNO_BUFSIZE 30

int
tcp_acc_port( int portno, int ip_version )
{
	struct addrinfo hints, *ai;
	char portno_str[PORTNO_BUFSIZE];
	int err, s, on, pf;

    	switch( ip_version )
	{
	case 4:
		pf = PF_INET;
		break;
	case 6:
		pf = PF_INET6;
		break;
	case 0:
	case 64:
	case 46:
		pf = 0;
		break;
	default:
		fprintf(stderr,"bad IP version: %d.  4 or 6 is allowed.\n",
			ip_version );
		goto error0;
	}
	snprintf( portno_str,sizeof(portno_str),"%d",portno );
	memset( &hints, 0, sizeof(hints) );
	ai = NULL;
	hints.ai_family   = pf ;
	hints.ai_flags    = AI_PASSIVE;
	hints.ai_socktype = SOCK_STREAM ;
	if( (err = getaddrinfo( NULL, portno_str, &hints, &ai )) )
	{
		fprintf(stderr,"bad portno %d? (%s)\n",portno,
			gai_strerror(err) );
		goto error0;
	}
	if( (s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol)) < 0 )
	{
		perror("socket");
		goto error1;
	}

#ifdef	IPV6_V6ONLY
	if( ai->ai_family == PF_INET6 && ip_version == 6 )
	{
		on = 1;
		if( setsockopt(s,IPPROTO_IPV6, IPV6_V6ONLY,&on,sizeof(on)) < 0 )
		{
			perror("setsockopt(,,IPV6_V6ONLY)");
			goto error1;
		}
	}
#endif	/*IPV6_V6ONLY*/

	if( bind(s,ai->ai_addr,ai->ai_addrlen) < 0 )
	{
		perror("bind");
		fprintf(stderr,"Port number %d\n", portno );
		goto error2;
	}
	on = 1;
	if( setsockopt( s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on) ) < 0 )
	{
		perror("setsockopt(,,SO_REUSEADDR)");
		goto error2;
	}
	if( listen( s, 5 ) < 0 )
	{
		perror("listen");
		goto error2;
	}
	freeaddrinfo( ai );
	return( s );

error2:
	close( s );	
error1:
	freeaddrinfo( ai );
error0:
	return( -1 );
}
