/* The DIVE software is covered by a license. The use of the software */
/* represents acceptance of the terms and conditions in the license.  */
/* ****************************************************************** */
/* Copyright (c) 1994, Swedish Institute of Computer Science          */

/*--------------------------------------------------------------------------
  File:      divesh.c
  Project:   DIVE
  Copyright: SICS
  Author:    Emmanuel Frécon, emmanuel@sics.se
  Version:   $Id: divesh.c,v 1.2 1996/04/01 11:47:02 anneli Exp $
  --------------------------------------------------------------------------*/

/***********
** This code implements the simplest example using the DIVE Client
** Interface.  It opens a connexion with a DIVE server and either
** reads a script to be executed on the server, either offers an
** interactive prompt to the user.
************/

#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <unistd.h>
#include <sys/time.h>
#include <errno.h>
#include <signal.h>
#include <sys/signal.h>

extern char *optarg; /* For getopt(...)  */

/* DCI Framing Protocol over TCP*/
#define DCI_MSGTYPE_ANSWER (0)
#define DCI_MSGTYPE_ERROR (1)
#define DCI_MSGTYPE_EVENT (2)

#define DCI_MAGIC (710420)

/* Useful constants */
#ifndef TRUE
#define TRUE (1)
#define FALSE (0)
#endif /* TRUE */

/* Default values */
#define DEFAULT_HOST "localhost"
#define DEFAULT_PORT (7120)
#define INPUT_BUFFER_LEN (1024)


/* Connection with the DIVE server */
#define NO_CONNEXION (-1)
static int dive_server = NO_CONNEXION;
static char interactive_prompt[256] = {0, };


/***********
** Set up a connection to an (extern) server and return the
** connected socket.  Subsequent calls must use this socket to
** listen or write messages to. Return NO_CONNEXION on error
************/
static int
tcp_connect_server(char *host, int port)
{
  int sock;
  struct sockaddr_in server;
  struct hostent *hp, *gethostbyname();

  server.sin_family = AF_INET;
  hp = gethostbyname(host);
  if (hp == 0){
    fprintf(stderr, "%s is an unknown host\n", host); 
    return NO_CONNEXION;
  }
  memcpy((void *)&server.sin_addr, (void *)hp->h_addr_list[0], hp->h_length);
  server.sin_port = htons(port);

  sock = socket(AF_INET, SOCK_STREAM, 0);
  if (sock<0){
    fprintf(stderr, "Unable to open stream socket\n");
    return NO_CONNEXION;
  }

  if (connect(sock,(struct sockaddr *)&server, sizeof (server)) < 0){
    if (ECONNREFUSED == errno) {
      fprintf(stderr, "Connexion refused by host %s\n", host);
      close(sock);
    }
    return NO_CONNEXION;
  }
  return(sock);
}


/***************
** Write a frame to the given socket. The input buffer must be a valid
** C string.
****************/
static int
tcp_frame_write(int socket, int type, char *msg)
{
  struct {
    unsigned long magic;
    unsigned long nbyte;
    unsigned long type;
  } header;
  int sent;
  int len;

  len = strlen(msg);

  /* Send Header */
  header.magic = htonl(DCI_MAGIC);
  header.nbyte = htonl(len);
  header.type = htonl(type);
  sent = write(socket, (char *)&header, sizeof(header));

  if (sent<sizeof(header))
    return FALSE;

  /* Send Buffer */
  sent = write(socket, msg, len);

  return sent==len;
}


/***************
** Read a frame from the given socket and return an allocated buffer
** containing it, NULL in error cases. The allocated buffer is a valid
** C string (i.e. terminated by a '\0').
****************/
static char *
tcp_frame_read(int socket, int *msgtype)
{
  unsigned long magic, nbyte, type;
  int rcount;
  char *buffer;

  /* Magic Number */
  rcount = read(socket, (void *)&magic, sizeof(magic));
  if (rcount!=sizeof(magic))
    return NULL;

  magic = ntohl(magic);
  if (magic!=DCI_MAGIC)
    return NULL;

  /* Length */
  rcount = read(socket, (void *)&nbyte, sizeof(nbyte));
  if (rcount != sizeof(nbyte))
    return NULL;
  nbyte = ntohl(nbyte);

  /* Type */
  rcount = read(socket, (void *)&type, sizeof(type));
  if (rcount != sizeof(type))
    return NULL;
  type = ntohl(type);
  *msgtype = type;

  /* Message */
  buffer = (char *)malloc(nbyte+1);
  rcount = read(socket, buffer, nbyte);
  buffer[rcount] = '\0';

  return buffer;
}


/************
** Infinitely waits for input coming on <input>, communicating with
** the DIVE server on <dive_server>. The incoming messages from the
** DIVE server are written on stdout or stderr, depending on the
** channel they belong to, if the input file is stdin, otherwise, only
** errors are written on stderr.
*************/
static void
input_loop(FILE *input)
{
  int finished = FALSE;

  while (!finished) {
    fd_set readfds;
    int ready;

    FD_ZERO(&readfds);
    FD_SET(fileno(input), &readfds);
    FD_SET(dive_server, &readfds);
    /* Wait for something to come */
    if (input==stdin)
      printf("%s", interactive_prompt); fflush(stdout);
    ready = select(FD_SETSIZE, &readfds, NULL, NULL, NULL);
    if (FD_ISSET(dive_server, &readfds)) {
      char *msg;
      int msgtype;

      msg = tcp_frame_read(dive_server, &msgtype);
      if (msg) {
	switch (msgtype) {
	case DCI_MSGTYPE_ANSWER:
	  if (input==stdin)
	    printf("%s\n", msg);
	  break;
	case DCI_MSGTYPE_ERROR:
	  fprintf(stderr, "%s\n", msg);
	  break;
	case DCI_MSGTYPE_EVENT:
	  if (input==stdin)
	    printf(">EVENT: %s<\n", msg);
	  break;
	}
	free(msg);
      } else {
	fprintf(stderr, "Connexion closed by host.\n");
	dive_server = NO_CONNEXION;
	finished = TRUE;
      }
    } else if (FD_ISSET(fileno(input), &readfds)) {
      int c;
      int len = 0;
      char input_buf[INPUT_BUFFER_LEN];

      memset(input_buf, 0, INPUT_BUFFER_LEN);
      do {
	c = fgetc(input);
	if (c==EOF) {
	  finished = TRUE;
	  if (input==stdin)
	    fprintf(stderr, "Can no longer read from stdin.\n");
	} else {
	  input_buf[len++] = c;
	  if (c=='\n') {
	    int type = 0;
	    
	    if (!tcp_frame_write(dive_server, type, input_buf)) {
	      finished = TRUE;
	      dive_server = NO_CONNEXION;
	      fprintf(stderr, "Connexion closed by host.\n");
	    }
	  }
	}
      } while (!finished && c!='\n');
    }
  }
}


/************
** Close connexion with the DIVE server
*************/
void close_connexion(int reason)
{
  if (dive_server!=NO_CONNEXION)
    close(dive_server);
  printf("\n\n\tBye...\n\n");
  exit(0);
}


/************
** Main Loop:
** Parse the arguments on the command line, opening a connexion with
** the requested (or default) host on the requested (or default)
** port. If a file was given, it is read and sent to the DIVE server
** as a script and we then waits for the user to interrupt. If no file
** was given, commands are read from stdin after a prompt, until the
** user interrupts.
*************/
void
main(int argc, char *argv[])
{
  int c;
  char *host = NULL;
  int port = DEFAULT_PORT;
  char *file = NULL;
  FILE *input;

  /* Parse Command Line */
  while ((c = getopt(argc, argv, "h:p:f:")) != EOF) {
    switch (c) {
    case 'h':
      host = (char *)strdup(optarg);
      break;
    case 'p':
      port = atoi(optarg);
      break;
    case 'f':
      file = (char *)strdup(optarg);
      break;
    }
  }

  /* Deciding which host we are going to communicate with */
  if (!host)
    host = (char *)strdup(DEFAULT_HOST);
  sprintf(interactive_prompt, "Dive - %s> ", host);

  /* Opening the input file */
  if (file) {
    input = fopen(file, "r");
    if (!input) {
      fprintf(stderr, "%f not found\n", file);
      exit(0);
    }
  } else {
    input = stdin;
  }
      
  /* Opening connexion with the DIVE server */
  dive_server = tcp_connect_server(host, port);
  if (dive_server == NO_CONNEXION) {
    exit(1);
    free(host);
  } else {
    printf("Connexion with %s established.\n", host);
    free(host);
  }

  /* Wait on stdin and send commands */
  signal(SIGINT, close_connexion);
  input_loop(input);

  /* Close input file */
  if (file) {
    fclose(input);
    free(file);
    if (dive_server!=NO_CONNEXION) {
      while (TRUE) {
      }
    }
  }

  /* Close connexion or waits for USER interrupt */
  close_connexion(0);
}

