/**
   @file

   This program tests the low-level block transfer and authentication
   functions.

*/

/*
  Copyright (c) Andreas Hofmeier
  (www.an-h.de, www.an-h.de.vu, www.lgut.uni-bremen.de/an-h/)
  
  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful, but
  WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/


#include <stdio.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <signal.h>
#include "libcomm.h"
#include <unistd.h>


/** Port on which the server is listening and the client trys to
    connect. */
#define PORT 1235
/** How much random bytes should be transfered? */
#define bufsize 8192
/** The ID which identifies the thread of the block_call()
    function. This value can be coosen arbitrary and is passwd to all
    called functions. */
#define threadid 1234
//#define bufsize 8

/** test0001sockts: see test0001sockts.c.
*/

main(int argc, char *argv[]) {
  // PID of the child process
  int fpid;
  int iport = PORT;

  printf("running %s...\n", argv[0]);

  printf("  trying blocked read mode... ");
  fflush(stdout);
  if (!(fpid = fork())) {
    server_test_program(iport, 0);
  }

  client_test_program("localhost", iport++, 0);

  // kill server terst programm
  kill(fpid, 9);


  printf("  trying non-blocked read mode... ");
  fflush(stdout);
  if (!(fpid = fork())) {
    server_test_program(iport, 1);
  }
  client_test_program("localhost", iport++, 0);

  // kill server terst programm
  kill(fpid, 9);

  printf("  trying function-call read mode... ");
  fflush(stdout);
  if (!(fpid = fork())) {
    server_test_program(iport, 2);
  }
  client_test_program("localhost", iport++, 0);

  // kill server terst programm
  kill(fpid, 9);

  printf("  trying accept-call and function-call read mode... ");
  fflush(stdout);
  server_test_program(iport, 3);
  sleep(2);
  client_test_program("localhost", iport++, 0);

  printf("  testing authentification... ");
  fflush(stdout);
  if (!(fpid = fork())) {
    server_test_program(iport, 4);
  }

  client_test_program("localhost", iport++, 1);

  // kill server terst programm
  kill(fpid, 9);


  exit(0);
}


/** test0001sockts: see test0001sockts.c, Client Test Programm.

   The client test program generates a block of n*(bufsize) random
   bytes, sents theses bytes to the server, receive a block from
   server, invert it and compare it with generated block.

   BUGS: Receives only one block. If not all data ready and function
   resv() do noct block, data get lost. Test fails.

   @param server (char *) contains the server name

   @param port (int) specifies the port on the server

   @param auth (int) 0: normale test; 1: do authentification before
   run test.

*/
client_test_program(char *server, int port, int auth) {
  // **************************************************************
  // *** Client Test Programm

  int i;

  // FD of socket
  int sock;

  char buf[bufsize], buf2[bufsize];
  int type, type2, rsize;

  // wait one second, give the fork enough time to bind and listen
  // the socket
  sleep(1);

  if (block_random(buf, bufsize) == NULL) {
    fail("cannot generate random numbers", 1);
  }
  if (block_random((char *) &type, 2) == NULL) {
    fail("cannot generate random numbers", 1);
  }
  
  // Connect to server
  if ((sock = socket_connect(server, port)) < 0) {
    fail("Cannot connect.", 1);    
  }

  sleep(3);

  if (auth) {
    struct AUTHINFO *local, *remote;

    char a[] = "remote";
    char b[] = "loginname54321";

    //    if (socket_md5auth(sock, NULL, &b, &local, &remote) < 0) {
    if (socket_md5auth(sock, (char *) &a, NULL, &local, &remote) < 0) {
      fail("authentifications failed!", 1);    
    } else {
      printf("(client OK [%s:%s]) ", local -> name, local -> passwd);
      fflush(stdout);
    }
  }

  // send datablock to server
  block_send(sock, type, buf, bufsize);

  // receive datablock from server
  block_receive(sock, &type2, buf2, &rsize, bufsize, false);
  if (rsize != bufsize) {
    fprintf(stderr, "WARNING: received less data from server than was" \
	    " send.\n(%d:%d)\n", rsize, bufsize);
  }

  close(sock);

  // invert the bits of the local datablock and
  // compare it with the result from the server
  // should be the same.
  for (i = 0; i < bufsize; i++) {
    if (~buf2[i] != buf[i]) {
      fail("some errors occure during the data transfer...", 1);
    }
  }
  printf("OK.\n");
  fflush(stdout);
  sleep(1);
}


/**
   testfunction which ist called from block_call() if a
   message/datablock has received.
*/
block_call_do_test(int fd, int id, unsigned int type, char *buf,
		   unsigned int size, int term) {
  int i;

  if (id != threadid) {
    fprintf(stderr, "** WARNING: Thread-ID was not stored correctly!");
  }

  // invert the bits of the whole datablock
  for (i = 0; i < size; i++) {
    buf[i] = ~buf[i];
  }

  //  printf("block_call_do_test()");
  fflush(stdout);

  // send inverted datablock to client
  block_send(fd, type, buf, size);
  close(fd);
}


/**
   testfunction which ist called from block_call() if a
   the connection terminates.
*/
block_call_term_test(int fd, int id) {
  int i;

  if (id != threadid) {
    fprintf(stderr, "** WARNING: Thread-ID was not stored correctly!");
  }

  printf("(server: connection terminated.)\n");
  fflush(stdout);
}


/**
   testfunction which ist called from socket_accept() if someone has
   connected.
*/
socket_accept_do_test(int fd, int id, char *pip,
		      struct sockaddr_in their_addr, int term) {
  block_call(fd, id, false, 
	     (void *) block_call_do_test,
	     (void *) block_call_term_test);
}


/** test0001sockts: see test0001sockts.c, Server Test Programm.

   The server test program binds a port and waits for connections. If
   someone connects it reads n*(bufsize) bytes, invert all bits of
   these bytes and send all back.

   BUGS: Receives only one block. If not all data ready and function
   resv() do noct block, data get lost. Test fails.

   @param port (int) specify the port to bind.

   @param mode (int) select the mode of receiving a message/datablock:
   0: blocked mode, use block_receive(); 1: poll mode, poll with
   block_receive_poll(); 2: call function block_call_do_test() if a
   block is received, use block_call(); 3: call
   socket_accept_do_test() if someone has connected. This function
   calls block_call() which does the same like in 2, use
   socket_accept(); 4: testing authenication by using
   socket_md5auth(). After this do the same as in 0.
*/
server_test_program(int port, int mode) {
  // **************************************************************
  // *** Server Test Program

  // buffer for storing data.
  char *buf;

  // FD of socket which is bounded to the port
  int sockport;
  // FD of socket
  int sock;

  /* connector's address information */
  struct sockaddr_in their_addr;
  int sin_size;

    
  // Bind port
  if ((sockport = socket_bind(port, 10)) < 0) {
    fail("Cannot bind port.", 1);
  }

  if (mode == 3) {
    socket_accept(sockport, threadid, (void *) socket_accept_do_test);
    return;
  }

  // accept connection
  sin_size = sizeof(struct sockaddr_in);
  if ((sock = accept(sockport, (struct sockaddr *) &their_addr,
		     &sin_size)) != -1) {
    int i, size, type;
    char *pard = inet_ntoa(their_addr.sin_addr);
    //    fprintf(stdout, "  got connection from %s\n", pard);
    
    
    // receive datablock from client
    switch (mode) {
    case 0:
      // blocking function
      buf = block_receive(sock, &type, NULL, &size, 0, false);
      if (buf == NULL) {
	fprintf(stderr, "Can't receive block from client...\n");
	exit(1);
      }
      break;
    case 1:
      // polling function
      i = 0;
      do {
	usleep(2L);
	i++;
	buf = block_receive_poll(sock, &type, NULL, &size, 0, false);
      } while (buf == (char *) 1L);
      if (buf == NULL) {
	fprintf(stderr, "Error during receivion occured...\n");
	exit(1);
      }
      printf("(%d polls) ", i);
      fflush(stdout);
      break;
    case 2:
      // function call
      block_call(sock, threadid, false, (void *) block_call_do_test,
		 (void *) block_call_term_test);
      sleep(500); // simulate the running of the "normal" program...
      return;
      break;
    case 4:
      // authentification with blocking function
      {
	struct AUTHINFO *local, *remote;
	char a[] = "loginname12345";

	if (socket_md5auth(sock, NULL, (char *) &a, &local, &remote) == 0) {
	  printf("(server OK [%s:%s]) ", local -> name, local -> passwd);
	  fflush(stdout);
	}
	buf = block_receive(sock, &type, NULL, &size, 0, false);
	if (buf == NULL) {
	  fprintf(stderr, "Can't receive block from client...\n");
	  exit(1);
	}
      }
      break;
    }

    // invert the bits of the whole datablock
    for (i = 0; i < size; i++) {
      buf[i] = ~buf[i];
    }

    // send inverted datablock to client
    block_send(sock, type, buf, size);
    close(sock);
  }
  close(sockport);
  exit(0);
}  


