// Chuck Benz, Hollis, NH   Copyright (c)2005
// http://asics.chuckbenz.com
//
// The information and description contained herein is the
// property of Chuck Benz.
//
// This information may only be used under license from
// Chuck Benz Asic & FPGA Design, or it's assignees.  Any 
// other use or modification is prohibited
//
// $Id: sit_cli.c,v 1.5 2005/07/22 00:00:11 cbenz Exp cbenz $
//

// sit_cli.c - simulation interconnection technology client
// This program connects to a local simulation through named
// pipes, and to a remote copy of this program through sockets
// (either directly or through a server) - that remote program
// being connected to it's local simulation.

// We are using 40 byte packets that are terminated with a \n on
// the named pipes.  We'll map those pretty much directly to the
// socket for the initial prototype.  Note that the IO with the
// named pipes are constrained to use just 64 printable characters
// because of verilog's constraints on how $write can be used.
// We use code points 0x30-0x4f and 0x5d-0x7c.  Actually, so far
// we've stuck to 0x30-0x4f.  Oh, and we're using 0x20 (space).

#ifdef __sun__
/* needed to get SIOCGIFADDR define */
#define BSD_COMP
#define SIOCGIFHWADDR SIOCGIFADDR
#define ifr_hwaddr ifr_addr
#endif

#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <sys/time.h>
#include <time.h>
#include <string.h>
#include <unistd.h>
#include <net/if.h>             // struct ifreq
#include <sys/ioctl.h>          // ioctl() and SIOCGIFHWADDR

#define MAXLEN        1024
#define MAXFD         31
#define fdsnull       (fd_set *) 0

struct charbuf {
  char buff[MAXLEN] ;
  int length ;
  struct charbuf * next ;
} ;


main (argc, argv)
     int    argc ;
     char * argv[] ;
{
  int                   blank_sockfd ;
  int                   sockfd ;
  struct sockaddr_in    serv_addr, cli_addr ;
  int                   clilen ;
  int                   dbg_lvl = 0 ;
  fd_set                socketVector, tempVect ;
  static struct timeval        delaytime ;
  int                   selret ;
  int                   val = 1 ;
  char                  buff[MAXLEN] ;
  int                   buff_size ;
  int                   i ;
  int                   ifile_opened = 0 ;
  int                   loop = 0 ;
  int                   currtime ;
  int                   rerun = 0 ;
  char                  * spawn_cmd ;
  int serv_tcp_port = 0xCBAF ;

  char ifilename[80] ;
  char ofilename[80] ;
  int  listener = 0 ;
  char partner[80] ;
  register struct hostent *servptr ;

  int  ifile, ofile ;
  int  difile, dofile ;
  int  debugfiles = 0 ;
  int  sock_eof = 0 ;
  int  pipe_eof = 0 ;

  spawn_cmd = NULL ;

  strcpy (ifilename, "sit_pipei") ;
  strcpy (ofilename, "sit_pipeo") ;
  strcpy (partner, "localhost");

  for (i=1; i<argc; i++) {
    if (0 == strcmp(argv[i], "-n")) {
      if (strlen(argv[++i]) < 78) {
	strcpy (ifilename, argv[i]) ;
	strcpy (ofilename, argv[i]) ;
	strcat (ifilename, "i") ;
	strcat (ofilename, "o") ;
      }
      else {
	printf ("error: filename length limited to 78 characters.\n") ;
	exit (-1) ;
      }
    }
    else if (0 == strcmp(argv[i], "-D")) {
      if (sscanf(argv[++i], "%d", &dbg_lvl) < 1) {
	printf ("error: -D must be followed by an integer debug level\n");
        exit (-1) ;
      }
    }
    else if (0 == strcmp(argv[i], "-p")) {
      if (sscanf(argv[++i], "%d", &serv_tcp_port) < 1) {
	fprintf (stderr,
		 "error: -p must be followed by an integer port number\n");
	exit (-1) ;
      }
    }
    else if (0 == strcmp(argv[i], "-sockloop")) {
      loop = 1 ;
    }
    else if (0 == strcmp(argv[i], "-pipeloop")) {
      loop = 2 ;
    }
    else if (0 == strcmp(argv[i], "-debugfiles")) {
      debugfiles = 1 ;
    }
    else if (0 == strcmp(argv[i], "-rerun")) {
      rerun = 1 ;
    }
    else if (0 == strcmp(argv[i], "-c")) {
      spawn_cmd = argv[++i] ;
      if (dbg_lvl > 3)
	fprintf (stderr, "will spawn command \"%s\" when connection is made\n",
		 spawn_cmd) ;
    }
    else if (0 == strcmp(argv[i], "-D")) {
      if (sscanf(argv[++i], "%d", &dbg_lvl) < 1) {
	printf ("error: -D must be followed by an integer debug level\n");
        exit (-1) ;
      }
    }
    else {  // it's either -L or partner name
      if (0 == strcmp(argv[i], "-L")) 
	listener = 1 ;
      else {
	if (strlen(argv[i]) < 79) 
	  strcpy (partner, argv[i]) ;
	else {
	printf ("error: partner name length limited to 79 characters.\n") ;
	exit (-1) ;
	}
      }
    }
  } // end of for loop for walking through argv/argc

  if (debugfiles) {
    if ((difile = open ("sit_debugi", O_WRONLY | O_CREAT, 00660)) < 0) {
      fprintf (stderr, "sit_cli: error opening sit_debugi, errno %d\n",
		errno) ;
      exit (-1);
    }
    if ((dofile = open ("sit_debugo", O_WRONLY | O_CREAT, 00660)) < 0) {
      fprintf (stderr, "sit_cli: error opening sit_debugo, errno %d\n",
	       errno) ;
      exit (-1);
    }
  }

  if (dbg_lvl > 9) 
    fprintf (stderr, "filename %s, loop %d, partner %s, listen %d\n",
	     ifilename, loop, partner, listener) ;

  // Set up our sockets, then set up our named pipes.
  // Then use select to listen in both directions and xfer data.

  if (loop != 2) {
    bzero ((char *) & serv_addr, sizeof(serv_addr));
    serv_addr.sin_family      = AF_INET ;
    serv_addr.sin_port        = htons(serv_tcp_port);

    if (listener) {
      if ((blank_sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
	printf ("sit_cli: can't open socket to listen, errno %d.\n", errno);
	exit(-1) ;
      }
      if (setsockopt(blank_sockfd, SOL_SOCKET, 
		     SO_REUSEADDR, &val, sizeof(val)) <0)
	if (dbg_lvl > 1)
	  printf ("warning: setsockopt with SO_REUSEADDR returned error %d\n",
		  errno) ;

      serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);

      if (bind(blank_sockfd, 
	       (struct sockaddr *) &serv_addr, sizeof(serv_addr)) <0) {
	printf ("sit_cli: couldn't bind local address/port, errno %d.\n", 
		errno);
	exit (-1) ;
      }

      listen (blank_sockfd, 5) ;
    }
    else {  // connect to partner
      if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
	printf ("sit_cli: can't open stream socket");
	exit(-1) ;
      }

      servptr = gethostbyname(partner) ;
      if (servptr == NULL) {
	printf ("gethostbyname error for partner %s\n", partner) ;
	exit(-1) ;
      }
      serv_addr.sin_addr = * (struct in_addr *) servptr->h_addr ;
      printf (" IP address %s\n", inet_ntoa(serv_addr.sin_addr)) ;
    
      if (connect(sockfd, 
		  (struct sockaddr *) &serv_addr, sizeof(serv_addr)) <0){
	printf ("sit_cli: couldn't connect to server.\n") ;
	exit (-1) ;
      }
      else
	printf ("connected to partner\n");
    }

    if (listener) {
      clilen = sizeof (cli_addr) ;
      printf ("listening for connection\n");
      sockfd = accept(blank_sockfd, (struct sockaddr *) &cli_addr, &clilen) ;
      // once I get a connection, stop listening by closing blank_sockfd
      close (blank_sockfd) ;
      printf ("accepted connection from %s\n",
	      inet_ntoa(cli_addr.sin_addr)) ;
    }
  }

  if (spawn_cmd != NULL) {
    fprintf (stderr, "running command: %s\n", spawn_cmd) ;
    system (spawn_cmd) ;
  }


  if (loop != 1) {
    // Open xmit fifo as read, don't wait.  But then do wait on opening
    // rcv fifo for write (FIFO construct requires wait for open/write).

    if ((ofile = open (ofilename, O_RDONLY | O_NDELAY)) < 0) {
      fprintf (stderr, "sit_cli: error opening xmit file/fifo\n") ;
      exit (-1);
    }
    if (dbg_lvl > 5)
      fprintf (stderr, "sit_cli: opened xmit file/fifo %s\n", ofilename) ;

#ifndef __CYGWIN__
    if ((ifile = open (ifilename, O_WRONLY | O_CREAT), 00660) < 0) {
      fprintf (stderr, "sit_cli: error opening rcv file/fifo %s, errno %d\n",
	       ifilename, errno) ;
      exit (-1);
    }
    if (dbg_lvl > 5)
      fprintf (stderr, "sit_cli: opened rcv file/fifo %s\n", ifilename) ;
    ifile_opened = 1 ;
#endif
  }
  else // socket loopback, so claim that ifile is opened...
    ifile_opened = 1 ;

  // So now the socket is open and both pipes are open... (unless loopback)
  // Now wait for either the socket (rd) or the ofile pipe to be ready.
  // For prototype, assume that writes won't block significantly.
  // But eventually, should allow that.

  if ((dbg_lvl > 5))
    fprintf (stderr, "loop %d, ifileopened %d\n", loop, ifile_opened) ;

  while (1) {
    FD_ZERO(&socketVector) ;
    if ((loop != 1) && ! pipe_eof)
      FD_SET(ofile, &socketVector) ;
    if (ifile_opened && (loop != 2) && ! sock_eof) {
      FD_SET(sockfd, &socketVector) ;
      if (dbg_lvl > 25)  printf ("1.");
    }
    if (dbg_lvl > 25)
      printf ("waiting for select\n") ;
    selret = select (MAXFD, &socketVector, fdsnull, 
		     fdsnull, (struct timeval *) 0) ;
    if (dbg_lvl > 25)
      printf ("select returned %d\n", selret) ;
    if (selret <0) {
      printf ("ERR: select returned %d, errno %d.\n", selret, errno) ;
      exit(-1) ;
    }
    if (FD_ISSET(ofile, &socketVector)) {
#ifdef __CYGWIN__
      if ( ! ifile_opened) {
	if ((ifile = open (ifilename, O_WRONLY | O_CREAT), 00660) < 0) {
	  fprintf (stderr, 
		   "sit_cli: error opening rcv file/fifo %s, errno %d\n",
		   ifilename, errno) ;
	  exit (-1);
	}
	if (dbg_lvl > 5)
	  fprintf (stderr, "sit_cli: opened rcv file/fifo %s\n", ifilename) ;
	ifile_opened = 1 ;
      }
#endif
      buff_size = read (ofile, buff, MAXLEN) ;
      if (dbg_lvl > 9) {
	printf ("sit_cli got %d bytes\n", buff_size) ;
	for (i=0;i<buff_size;i++)
	  putchar(buff[i]);
      }
      if (buff_size == 0) {
	printf ("EOF on local rd pipe\n");
	// send EOS to remote sim
	if (loop != 2)
	  write (sockfd, " 30\n", 4) ;
	else if (loop == 2)
	  write (ifile, " 30\n", 4) ;
	pipe_eof = 1 ;
	if (sock_eof || (loop == 2))  { // we're already waiting...
	  if (rerun) {
	    if (dbg_lvl > 2) 
	      fprintf (stderr, "exiting, re-running\n") ;
	    execvp (argv[0], argv) ;
	  }
	  exit (0) ;
	}
      }
      else if (loop != 2) {
	write (sockfd, buff, buff_size) ;
	if (debugfiles) write (dofile, buff, buff_size) ;
      }
      else
	write (ifile, buff, buff_size) ;
    }
    if (loop != 2) {
      if (FD_ISSET(sockfd, &socketVector)) {
	buff_size = read (sockfd, buff, MAXLEN) ;
	if ((buff_size == 0) || (buff[1] == '3')) {
	  printf ("EOF/EOS on reading socket\n");
	  // send EOS to local sim
	  if (loop == 0) {
	    write (ifile, " 30\n", 4) ;
	    close (ifile) ;
	  }
	  sock_eof = 1 ;
	  close (sockfd) ;
	  if (pipe_eof || (loop > 0)) {
	    if (rerun) {
	      if (dbg_lvl > 2) 
		fprintf (stderr, "exiting, re-running\n") ;
	      execvp (argv[0], argv) ;
	    }
	    exit (0) ;
	  }
	}
	else if (loop == 1)
	  write (sockfd, buff, buff_size) ;
	else {
	  write (ifile, buff, buff_size) ;
	  if (debugfiles) write (difile, buff, buff_size) ;
	}
      }
    }
  }
}

// $Log: sit_cli.c,v $
// Revision 1.5  2005/07/22 00:00:11  cbenz
// *** empty log message ***
//
// Revision 1.4  2005/07/18 20:12:20  cbenz
// when I added pipe loopback, I broke socket loopback - fix that.
//
// Revision 1.3  2005/07/14 20:04:50  cbenz
// Add loopback support, attempt cygwin support,
// but cygwin only works with other cygwin apps.
//
//
