/*
 * Copyright 12/06/05 Sun Microsystems, Inc. All Rights Reserved
 */ 
/*****************************************************************************/
/*        This code is provided for example purposes only and does not       */
/*                 comprise part of the supported product.                   */
/*****************************************************************************/

/*****************************************************************************/
/* tserver								     */
/* 									     */
/* This is an example of a server program using connection mode TLI. The     */
/* program listens for incoming connections.  When notification is received  */
/* of an incoming connection a new transport endpoint is created and the     */
/* call is accepted on this new endpoint.  Once the call is accepted a       */
/* a process is forked to transfer a file (specified in the command line)    */
/* to the client.  The parent process returns to listening for incoming      */
/* calls.  Once the child process has transferred the file it waits for a    */
/* disconnect to be received from the client.				     */
/*									     */
/* This program can be used in conjunction with the tclient and tclient-async*/
/* programs.								     */
/* 									     */
/*****************************************************************************/

/* NP CTE fix for bug 6310987 <PL> 01/Sep/2005 */

#include <stdlib.h>
#include <unistd.h>
#include <strings.h>
#include <tiuser.h>
#include <stropts.h>
#include <fcntl.h>
#include <stdio.h>
#include <signal.h>

#define DLEN 1024
#define EOT 0x04
#define ESC 0x1b
#define DISCONNECT -1

extern int tsap_to_net(char *, char *, char *);
extern void get_addrs(int, char **, char *, char *, char *, char *, char *);

void
Usage()

{
        printf("Usage: tserver <local_nsap> <local_tsel> -f <filename>\n");
        return;
}


/* accept_call() - accepts calls from clients.
 *
 * Unless an internal error occurs only a disconnect received from the remote
 * system can cause the t_accept call to fail.
 *
 * Input -
 *		listen_fd - endpoint where server is currently listening.
 *		call - t_call structure containing details of incoming call.
 *		server_nsap - local nsap for binding.
 *		server_tsel - local tsel for binding.
 *
 * Output -
 *		function returns value of endpoint on which the call will be
 *			handled.
 */

int
accept_call(
		int		listen_fd,
		struct t_call	*call,
		char		*server_nsap,
		char		*server_tsel
	   )
{
	int 		call_fd;
	struct t_bind	*bind, *bound;
	int 		status;

        if ((call_fd = t_open("/dev/otpi", O_RDWR,
                                        (struct t_info *) NULL)) == -1) {
                t_error("t_open failed");
                exit(9);
        }

        /*
         * Allocate the t_bind structure to contain the local address.
         */
 
        if ((bind = (struct t_bind *) t_alloc(call_fd, T_BIND,
                                                        T_ALL)) == NULL) {
                t_error("t_alloc of t_bind structure failed");
                exit(10);
        }
 
        /*
         * Allocate a t_bind structure to contain the bound address.
         */
 
        if ((bound = (struct t_bind *) t_alloc(call_fd, T_BIND,
                                                        T_ALL)) == NULL) {
                t_error("t_alloc of t_bind (bound) structure failed");
                exit(11);
        }

 
        /*
         * Map the local address to the netbuf format.
         */
 
        status = tsap_to_net(bind->addr.buf, server_nsap, server_tsel);
        if (status < 0) {
                fprintf(stderr, "Error mapping server address\n");
                exit(12);
        }

        bind->addr.len = (unsigned int) status;

        /*
         * Bind to the local address.
         */
 
        bind->qlen = 0;
        if (t_bind(call_fd, bind, bound) < 0) {
                t_error("t_bind failed");
                exit(13);
        }
 
        /*
         * Ensure that the correct address was actually bound.
         */
 
        if (bind->addr.len != bound->addr.len ||
                strncmp(bind->addr.buf, bound->addr.buf, bind->addr.len)) {
                fprintf(stderr, "t_bind bound to different address\n");
                exit(14);
        }

	/*
	 * Attempt to accept the call.
	 */
	if (t_accept(listen_fd, call_fd, call) < 0) {

		/*
		 * Check to see if an asynchronous event has occurred.
		 */

		if (t_errno == TLOOK) {

			/* 
			 * Probably a disconnect - otherwise a protocol error.
			 * In any case terminate connection.
			 */

			if (t_close(call_fd) < 0) {
				t_error("t_close failed for call_fd");
				exit(15);
			}

			return(DISCONNECT);
		}
		t_error("t_accept failed");
		exit(16);
	}

	return(call_fd);
}


/* run_server() - fork a process to do data transfer.
 *
 * The function forks a process which transfers a file to the client.
 *
 * Input -
 *		listen_fd - endpoint for listening.
 *		call_fd - endpoint for data transfer.
 *		filename - file to be transferred
 */

void
run_server(
		int listen_fd,
		int call_fd,
		char *filename
	  )

{
	int		nbytes, flags;
	FILE		*fp; 		/* Pointer to file to be transferred */
	char		data_buf[DLEN];
        char   		*send_ptr;
	struct t_discon *discon;

	/*
	 * Fork off process to handle file transfer for this call.
	 */

	switch (fork()) {

	case -1:		/* fork() failed */

		perror("fork failed");
		exit(18);

	default:		/* Parent */

		/*
		 * Close endpoint for handling call since the parent only
		 * performs listens.
 		 */
		if (t_close(call_fd) < 0) {
			t_error("t_close failed for call_fd");
			exit(19);
		}
		return;

	case 0:			/* Child */

		/*
		 * Close the listening endpoint - the child does not listen.
		 */

		if (t_close(listen_fd) < 0) {
			t_error("t_close failed for listen_fd");
			exit(20);
		}

	        /*  
	         * Allocate a t_discon structure for use later. 
	         */ 
 
	        if ((discon = (struct t_discon *) t_alloc(call_fd, T_DIS, 
						T_ALL)) == NULL) {
	                t_error("t_alloc of t_discon failed"); 
	                exit(21); 
	        }

		/*
		 * Open the file to be transferred, and transfer data.
		 */
		if ((fp = fopen(filename, "r")) == NULL) {
			perror("Cannot open file for transfer");
			exit(22);
		}
	
		send_ptr = data_buf + 1;	
		while ((nbytes = fread(send_ptr, 1, 1023, fp)) > 0) {
			/*
			 * When ESC (ASCII 0x1b) is the first character to be sent, one more ESC must be added to the  
			 * beginning of data buffer (it will be subtracted on client side).
                         * This is done to allow unambiguous usage of End of Transfer flag (ESC + EOT).
			 */
			if (*send_ptr == ESC) {
				data_buf[0] = ESC;
   				send_ptr = data_buf;
				nbytes = nbytes + 1;
			}
			

			if (t_snd(call_fd, send_ptr, nbytes, 0) < 0) {
                                        t_error("t_snd failed");
                                        exit(23);
                        }
			send_ptr = data_buf + 1;
		}

		/*
		 * Send end of transmission flags 
		 */

		data_buf[0] = ESC;
		data_buf[1] = EOT;

		if (t_snd(call_fd, data_buf, 2, 0) < 0) {
			t_error("t_snd failed");
			exit(24);
		}

		/*
		 * Wait for disconnect.
		 */

		if (t_rcv(call_fd, data_buf, nbytes, &flags) < 0) {
			if (t_rcvdis(call_fd, discon) < 0) {
				t_error("t_rcvdis failed");
				exit(25);
			}
		}
		else {
			/*
			 * What we received is not a disconnect.  In this
			 * case the server will disconnect since this is an
			 * invalid event.
			 */

			if (t_snddis(call_fd, NULL) < 0) {
				t_error("t_snddis failed");
				exit(26);
			}
		}

		/*
		 * Finished!
		 */

		t_close(call_fd);
		exit(0);
	}
}


/* main() - main part of server program.
 *
 * Endpoint is created and an address bound.  Server waits for incoming calls.
 * Whenever an incoming call is received, accept_call() is called to accept
 * the incoming call, and then, if the call is accepted successfully, 
 * run_server() is called to fork a process to do the data transfer.
 */

main(
     int argc,
     char **argv
    )

{

	int		listen_fd, call_fd;
	int 		nbytes, flags;
	char		data_buf[DLEN];
        char            server_nsap[32], server_tsel[32];
	char		filename[256];
	struct t_bind	*bind, *bound;
	struct t_call	*call;
	int		status;

	/*
	 * Get addressing information, and the name of the file to be
	 * transferred, from the command line.
	 */
	get_addrs(argc, argv, NULL, NULL, server_nsap,
                                                server_tsel, filename);
	if (strlen(filename) == 0)
		strcpy(filename, "/etc/termcap");

	/*
	 * Create the transport endpoint.
	 */

	if ((listen_fd = t_open("/dev/otpi", O_RDWR, 
					(struct t_info *) NULL)) == -1) {
		t_error("t_open failed");
		exit(1);
	}

	/*
	 * Allocate the t_bind structure to contain the local address.
	 */

	if ((bind = (struct t_bind *) t_alloc(listen_fd, T_BIND, 
							T_ALL)) == NULL) {
		t_error("t_alloc of t_bind structure failed");
		exit(2);
	}

	/*
	 * Allocate a t_bind structure to contain the bound address.
	 */

	if ((bound = (struct t_bind *) t_alloc(listen_fd, T_BIND, 
							T_ALL)) == NULL) {
		t_error("t_alloc of t_bind (bound) structure failed");
		exit(3);
	}

	/*
	 * Map the local address to the netbuf format.
	 */

	status = tsap_to_net(bind->addr.buf, server_nsap, server_tsel);
	if (status < 0) {
		fprintf(stderr, "Error mapping server address\n");
		exit(4);
	}

	bind->addr.len = (unsigned int) status;

	/*
	 * Bind to the local address.
	 */

	bind->qlen = 1;
	if (t_bind(listen_fd, bind, bound) < 0) {
		t_error("t_bind failed");
		exit(5);
	}


	/*
	 * Ensure that the correct address was actually bound.
	 */

	if (bind->addr.len != bound->addr.len || 
		strncmp(bind->addr.buf, bound->addr.buf, bind->addr.len)) {
		fprintf(stderr, "t_bind bound to different address\n");
		exit(6);
	}

	/*
	 * Allocate a t_call structure to contain the call details.
	 */

	if ((call = (struct t_call *) t_alloc(listen_fd, T_CALL, 
							T_ALL)) == NULL) {
		t_error("t_alloc of t_call structure failed");
		exit(7);
	}

	/*
	 * Enter infinite loop to wait for incoming connection indications.
	 */

	while (1) {

		printf("Waiting for a connection....\n");
		if (t_listen(listen_fd, call) < 0) {
			t_error("t_listen failed");
			exit(8);
		}
		printf("Incoming call received\n");
		if ((call_fd = accept_call(listen_fd, call, server_nsap, 
				server_tsel)) != DISCONNECT) {
			run_server(listen_fd, call_fd, filename);
		}

		/*
		 * Go back and listen for another call.
		 */
	}
	
	/*
	 * Not reached.
	 */

}
