/* 
  Copyright 2023 Thorsten Alteholz <libcontra@alteholz.eu>

  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 3 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 (see the file COPYING included with this
  distribution); if not, write to the Free Software Foundation, Inc.,
  59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

/*!
*************************************************************************
*
* \file test_server.c
*
* \author Thorsten Alteholz <libcontra@alteholz.eu>
*
* \brief this is the server for testing
*
* This server contains all functions that are within the wrapper 
* library. It shall produce the output for all server functions.
*
*
* \addtogroup libcontratest
* The group of the test stuff
* @{
************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <getopt.h>
#include <pthread.h>
#include <stdbool.h>
#include <libgen.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#define __USE_MISC 1 /*!< Google said this has to be set here */
#include <arpa/inet.h>
#include <netdb.h>
#include <sys/socket.h>
#include <strings.h>
#include <libcontra_log.h>
#include <libcontra.h>
#include "test-common.h"

pthread_mutex_t libcontra_lock; /*!< mutex for output locks in different functions */
bool	libcontra_logStdio=true; /*!< during the tests we want to capture output on stdio */
int	libcontra_loglevel=LOGLEVEL_MAX; /*!< during the tests we want to log everything */
FILE*	libcontra_file=NULL; /*!< variable needed to be able to use libcontra */

/* global variables for server tests */
#define PORT 54321 /*!< default port that the test server listens on */
#define SA struct sockaddr  /*!< shortcut */
struct sockaddr_in servaddr; /*!< global variable for local test server address */
int globalSockfd; /*!< global variable for socket that can be used in each test */
int globalConnfd; /*!< global variable for connection socket that can be used in each test */


static const struct option long_options[] =
    {
        { "testnumber", required_argument,       0, 't' },
//        { "BBB", no_argument,       0, 'b' },
//        { "CCC", required_argument, 0, 'c' },
//        { "DDD", no_argument,       0, 'd' },
//        { "EEE", no_argument,       0, 0   },
//        { "FFF", required_argument, 0, 0   },
        0
    };

/*!
*************************************************************************
*
* \fn void setSocketOptions(int sockfd)
*
* \author Thorsten Alteholz <libcontra@alteholz.eu>
*
* \brief set default options of socket
* 
* at the moment the only default option that is set is SO_REUSEADDR
*
************************************************************************/
void setSocketOptions(int sockfd)
{
  const int enable = 1;
  if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)) < 0)
      libcontra_log(LOGLEVEL_ERROR, "E: could not setsockopt(v(%i) -> SO_REUSEADDR  (%s:v(%d))",sockfd, basename(__FILE__), __LINE__);
}

/*!
*************************************************************************
*
* \fn bool do_test_0_1(int testcase)
*
* \author Thorsten Alteholz <libcontra@alteholz.eu>
*
* \brief test feature 1 of testcase 0
*
* \return rc value of test -> always true
************************************************************************/
bool do_test_0_1(int testcase)
{
  bool result=true;

  libcontra_log(LOGLEVEL_INFO, "I: ...... doing test %02i 1: %5s (%5s)",testcase, bool2str(result), bool2str(true));
  return result;
}

/*!
*************************************************************************
*
* \fn bool do_test_0_2(int testcase)
*
* \author Thorsten Alteholz <libcontra@alteholz.eu>
*
* \brief test feature 2 of testcase 0
*
* \return rc value of test = always false
************************************************************************/
bool do_test_0_2(int testcase)
{
  bool result=false;

  libcontra_log(LOGLEVEL_INFO, "I: ...... doing test %02i 2: %5s (%5s)",testcase, bool2str(result), bool2str(false));
  return result;
}

/*!
*************************************************************************
*
* \fn bool do_test_0_3(int testcase)
*
* \author Thorsten Alteholz <libcontra@alteholz.eu>
*
* \brief test feature 3 of testcase 0
* 
* produce log output for binary data, need to be compared
* with template, so return value is always true
*
* \return rc value of test = always true
************************************************************************/
bool do_test_0_3(int testcase)
{
  bool result=true;
  
  char *data1="01234567";             /* less than a line */
  char *data2="01234567899876543210"; /* a bit more than a line */
  char data3[257];
  int i;
  
  for (i=0;i<256;i++) data3[i]=i; 
  data3[256]=0; data3[0]=1;
  
  libcontra_log_data(LOGLEVEL_MAX, data1, strlen(data1));
  libcontra_log_data(LOGLEVEL_MAX, data2, strlen(data2));
  libcontra_log_data(LOGLEVEL_MAX, data3, strlen(data3));

  libcontra_log(LOGLEVEL_INFO, "I: ...... doing test %02i 3: %5s (%5s)",testcase, bool2str(result), bool2str(false));
  return result;
}

/*!
*************************************************************************
*
* \fn bool do_test_0(int testcase)
*
* \author Thorsten Alteholz <libcontra@alteholz.eu>
*
* \brief testcase 0, server internal test
*
* \return rc value of all tests from testcase 0
************************************************************************/
bool do_test_0(int testcase)
{
  bool result=true;

  result=result & do_test_0_1(testcase); /* result of test: true */
  result=result & !do_test_0_2(testcase); /* result of test: false */
  result=result & do_test_0_3(testcase); /* result of test: false */
  libcontra_log(LOGLEVEL_INFO, "I: ... test %02i: %5s (%5s)",testcase, bool2str(result), bool2str(true));

  return result;
}

/*!
*************************************************************************
*
* \fn bool do_test_1_2(int testcase)
*
* \author Thorsten Alteholz <libcontra@alteholz.eu>
*
* \brief test feature 1 of testcase 2; socket(AF_INET6)
*
* \return rc value of test
************************************************************************/
int do_test_1_2(int testcase)
{
  int result=0;
  bool resultBool;
  
  result=socket(AF_INET6, SOCK_STREAM, 0);
  resultBool=socket>0;
  libcontra_log(LOGLEVEL_INFO, "I: ...... doing test %02i 2: v(%i) %5s (%5s) (%s:v(%d))",testcase, result, bool2str(resultBool), bool2str(true), basename(__FILE__), __LINE__);
  return result;
}

/*!
*************************************************************************
*
* \fn bool do_test_1_1(int testcase)
*
* \author Thorsten Alteholz <libcontra@alteholz.eu>
*
* \brief test feature 1 of testcase 1; socket(AF_INET)
*
* \return rc value of test
************************************************************************/
int do_test_1_1(int testcase)
{
  int result=0;
  bool resultBool;
  
  result=socket(AF_INET, SOCK_STREAM, 0);
  resultBool=socket>0;
  libcontra_log(LOGLEVEL_INFO, "I: ...... doing test %02i 1: v(%i) %5s (%5s) (%s:v(%d))",testcase, result, bool2str(resultBool), bool2str(true), basename(__FILE__), __LINE__);
  return result;
}


/*!
*************************************************************************
*
* \fn bool do_test_1(int testcase)
*
* \author Thorsten Alteholz <libcontra@alteholz.eu>
*
* \brief testcase 1, socket test
*
* \return rc value of all tests from testcase 0
************************************************************************/
bool do_test_1(int testcase)
{
  bool result=true;

  result=result & (do_test_1_1(testcase)>0); /* result of test: true */
  result=result & (do_test_1_2(testcase)>0); /* result of test: true */
  libcontra_log(LOGLEVEL_INFO, "I: ... test %02i: %5s (%5s)",testcase, bool2str(result), bool2str(true));

  return result;
}

/*!
*************************************************************************
*
* \fn int do_test_2_1(int testcase, int sockfd)
*
* \author Thorsten Alteholz <libcontra@alteholz.eu>
*
* \brief test feature 1 of testcase 2; bind()
*
* \return rc value of test
************************************************************************/
int do_test_2_1(int testcase, int sockfd)
{
  int result=0;
  bool resultBool;
  
  // assign IP, PORT
  bzero(&servaddr, sizeof(servaddr));   
  servaddr.sin_family = AF_INET;
  servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
  servaddr.sin_port = htons(PORT+testcase);

  result=bind(sockfd, (SA*)&servaddr, sizeof(servaddr));
  
  resultBool=result==0;
  
  libcontra_log(LOGLEVEL_INFO, "I: ...... doing test %02i 1: v(%i) %5s (%5s) (%s:v(%d))",testcase, result, bool2str(resultBool), bool2str(true), basename(__FILE__), __LINE__);
  return result;
}


/*!
*************************************************************************
*
* \fn bool do_test_2(int testcase)
*
* \author Thorsten Alteholz <libcontra@alteholz.eu>
*
* \brief testcase 2, bind test
*
* we use the global variable globalSockfd
*
* \return rc value of all tests from testcase 0
************************************************************************/
bool do_test_2(int testcase)
{
  bool result=true;
  int bindrc, sockfd;
  int i;
  
  /* get AF_INET socket with function from previous test */
  sockfd=do_test_1_1(testcase);
  if (sockfd == -1) {
    libcontra_log(LOGLEVEL_ERROR, "E: ... test %02i: could not create socket %i -> %s (%s:v(%d))", testcase, errno, strerror(errno), basename(__FILE__), __LINE__);
    return false;
  } else {
    libcontra_log(LOGLEVEL_INFO, "I: ... test %02i: socket created (%s:v(%d))", testcase, basename(__FILE__), __LINE__);
  }

  bindrc=do_test_2_1(testcase, sockfd);
  result=bindrc==0;

  close(sockfd); // we no longer need the socket
  
  libcontra_log(LOGLEVEL_INFO, "I: ... test %02i: %5s (%5s)",testcase, bool2str(result), bool2str(true));

  return result;
}

/*!
*************************************************************************
*
* \fn bool do_test_3_1(int testcase, int sockfd)
*
* \author Thorsten Alteholz <libcontra@alteholz.eu>
*
* \brief test feature 1 of testcase 3; listen()
*
* \return rc value of test
************************************************************************/
int do_test_3_1(int testcase, int sockfd)
{
  int result=0;
  bool resultBool;
  

  result=listen(sockfd, 0);
  
  resultBool=result==0;
  
  libcontra_log(LOGLEVEL_INFO, "I: ...... doing test %02i 1: v(%i) %5s (%5s) (%s:v(%d))",testcase, result, bool2str(resultBool), bool2str(true), basename(__FILE__), __LINE__);
  return result;
}

/*!
*************************************************************************
*
* \fn bool do_test_3(int testcase)
*
* \author Thorsten Alteholz <libcontra@alteholz.eu>
*
* \brief testcase 3, listen() test
*
* \return rc value of all tests from testcase 3
************************************************************************/
bool do_test_3(int testcase)
{
  bool result=true;
  int bindrc, listenrc;

  /* get AF_INET socket with function from previous test */
  globalSockfd=do_test_1_1(testcase);
  if (globalSockfd == -1) {
    libcontra_log(LOGLEVEL_ERROR, "E: ... test %02i: could not create socket %i -> %s (%s:v(%d))", testcase, errno, strerror(errno), basename(__FILE__), __LINE__);
    return false;
  } else {
    libcontra_log(LOGLEVEL_INFO, "I: ... test %02i: socket v(%i) created (%s:v(%d))", testcase, globalSockfd, basename(__FILE__), __LINE__);
  }
  setSocketOptions(globalSockfd);
  
  bindrc=do_test_2_1(testcase, globalSockfd);
  if (bindrc != 0) {
    libcontra_log(LOGLEVEL_ERROR, "E: ... test %02i: could not bind to socket: %i -> %s (%s:v(%d))", testcase, errno, strerror(errno), basename(__FILE__), __LINE__);
    return false;
  } else {
    libcontra_log(LOGLEVEL_INFO, "I: ... test %02i: bind done (%s:v(%d))", testcase, basename(__FILE__), __LINE__);
  }
  
  listenrc=do_test_3_1(testcase, globalSockfd);
  result=listenrc==0;
  
  //we do need this socket in do_test_4: close(globalSockfd);
  
  libcontra_log(LOGLEVEL_INFO, "I: ... test %02i: %5s (%5s)",testcase, bool2str(result), bool2str(true));

  return result;
}

/*!
*************************************************************************
*
* \fn bool do_test_4_1(int testcase, int sockfd)
*
* \author Thorsten Alteholz <libcontra@alteholz.eu>
*
* \brief test feature 1 of testcase 4; accept()
*
* \return rc value of test
************************************************************************/
int do_test_4_1(int testcase, int sockfd)
{
  int connfd=0;
  unsigned int len;
  bool resultBool;
  struct sockaddr_in cli;

  connfd=accept(sockfd, (SA*)&cli, &len);
  if (connfd < 0) {
    libcontra_log(LOGLEVEL_ERROR, "E: ... test %02i: could not accept socket: %i -> %s (%s:v(%d))", testcase, errno, strerror(errno), basename(__FILE__), __LINE__);
  }
  resultBool=connfd>=0;

  libcontra_log(LOGLEVEL_INFO, "I: ...... doing test %02i 1: v(%i) %5s (%5s) (%s:v(%d))",testcase, connfd, bool2str(resultBool), bool2str(true), basename(__FILE__), __LINE__);
  return connfd;
}

/*!
*************************************************************************
*
* \fn bool do_test_4(int testcase)
*
* \author Thorsten Alteholz <libcontra@alteholz.eu>
*
* \brief testcase 4, accept() test
*
* \return rc value of all tests from testcase 4
************************************************************************/
bool do_test_4(int testcase)
{
  bool result=true;
  int len;

  globalConnfd=do_test_4_1(testcase, globalSockfd); /* this test returns a connection socket, that can be used later on */

  result=globalConnfd>=0;

  libcontra_log(LOGLEVEL_INFO, "I: ... test %02i: %5s (%5s)",testcase, bool2str(result), bool2str(true));

  return result;
}

/*!
*************************************************************************
*
* \fn bool do_test_5_1(int testcase, int sockfd)
*
* \author Thorsten Alteholz <libcontra@alteholz.eu>
*
* \brief test feature 1 of testcase 5; recv()
*
* \return rc value of test
************************************************************************/
int do_test_5_1(int testcase, int connfd)
{
  unsigned int len;
  char buffer[TEST_RECV_BUFFER_LEN];
  bool resultBool;
  ssize_t receivedBytes;

  receivedBytes=recv(connfd, &buffer, TEST_RECV_BUFFER_LEN, 0);
  if (receivedBytes != TEST_SEND_MESSAGE_LEN) {
    libcontra_log(LOGLEVEL_ERROR, "E: ... test %02i: did not receive all byte: %li of %i (%s:v(%d))", testcase, receivedBytes, TEST_SEND_MESSAGE_LEN, basename(__FILE__), __LINE__);
  }
  resultBool=receivedBytes == TEST_SEND_MESSAGE_LEN;

  libcontra_log(LOGLEVEL_INFO, "I: ...... doing test %02i 1: v(%li) %5s (%5s) (%s:v(%d))",testcase, receivedBytes, bool2str(resultBool), bool2str(true), basename(__FILE__), __LINE__);
  return connfd;
}

/*!
*************************************************************************
*
* \fn bool do_test_5(int testcase)
*
* \author Thorsten Alteholz <libcontra@alteholz.eu>
*
* \brief testcase 5, send()+recv() test
*
* \return rc value of all tests from testcase 5
************************************************************************/
bool do_test_5(int testcase)
{
  bool result=true;
  int len;

  /* do_test_4 sets globalConnfd that we can use here to receive data */
  result=do_test_5_1(testcase, globalConnfd); /* this test returns a connection socket, that can be user later on */

  libcontra_log(LOGLEVEL_INFO, "I: ... test %02i: %5s (%5s)",testcase, bool2str(result), bool2str(true));

  return result;
}

/*!
*************************************************************************
*
* \fn bool do_test_6(int testcase)
*
* \author Thorsten Alteholz <libcontra@alteholz.eu>
*
* \brief testcase 6, setsockopt() + getsockopt()
*
* we use the global variable globalSockfd
*
* \return rc value of all tests from testcase 0
************************************************************************/
bool do_test_6(int testcase)
{
  bool result=true;
  int bindrc, sockfd;
  int i, value;
  socklen_t socklen; 
  
  /* get AF_INET socket with function from previous test */
  sockfd=do_test_1_1(testcase);
  if (sockfd == -1) {
    libcontra_log(LOGLEVEL_ERROR, "E: ... test %02i: could not create socket %i -> %s (%s:v(%d))", testcase, errno, strerror(errno), basename(__FILE__), __LINE__);
    return false;
  } else {
    libcontra_log(LOGLEVEL_INFO, "I: ... test %02i: socket created (%s:v(%d))", testcase, basename(__FILE__), __LINE__);
  }

  /* we have a sockt, now do some stuff with it */
  result=getsockopt ( sockfd , IPPROTO_TCP , TCP_MAXSEG , ( char* ) &value , &socklen ) ==  0;

  close(sockfd); // we no longer need the socket
  
  libcontra_log(LOGLEVEL_INFO, "I: ... test %02i: %5s (%5s)",testcase, bool2str(result), bool2str(true));

  return result;
}

/*!
*************************************************************************
*
* \fn bool do_test_7(int testcase)
*
* \author Thorsten Alteholz <libcontra@alteholz.eu>
*
* \brief testcase 7, gethostbyname() + gethostbyaddr()
*
* we use the global variable globalSockfd
*
* \return rc value of all tests from testcase 7
************************************************************************/
bool do_test_7(int testcase)
{
  bool result=true;
  int bindrc, sockfd;
  int i, value;
  socklen_t socklen; 
  const char *name="www.yahoo.com"; // they have several addresses and aliases
  struct hostent* rchostent;
  struct in_addr addr;
  struct in6_addr addr6;
  
  rchostent=gethostbyname(name);
  result=rchostent!=NULL;
  result=result & (strcmp(rchostent->h_name,"new-fp-shed.wg1.b.yahoo.com")==0); //TODO: is this an universal test?
  //TODO add more checks from rchostent struct
  
  inet_aton("87.248.100.216", &addr);
  rchostent=gethostbyaddr(&addr, sizeof(addr), AF_INET);
  result=result & (rchostent!=NULL);
  result=result & (strcmp(rchostent->h_name,"media-router-fp74.prod.media.vip.ir2.yahoo.com")==0); //TODO: is this an universal test?

  inet_pton(AF_INET6, "2a00:1288:110:c305::1:8000", &addr6);
  rchostent=gethostbyaddr(&addr6, sizeof(addr6), AF_INET6);
  result=result & (rchostent!=NULL);
  result=result & (strcmp(rchostent->h_name,"media-router-fp73.prod.media.vip.ir2.yahoo.com")==0); //TODO: is this an universal test?
  
  libcontra_log(LOGLEVEL_INFO, "I: ... test %02i: %5s (%5s)",testcase, bool2str(result), bool2str(true));

  return result;
}

/*!
*************************************************************************
*
* \fn bool do_test_8(int testcase)
*
* \author Thorsten Alteholz <libcontra@alteholz.eu>
*
* \brief testcase 8, reeaddrinfo() + getaddrinfo()
*
* we use the global variable globalSockfd
*
* \return rc value of all tests from testcase 8
************************************************************************/
bool do_test_8(int testcase)
{
  bool result=true;
  int rc;
  socklen_t socklen; 
  const char *nodename="www.yahoo.com";
  const char *srvname="www.yahoo.com";
  struct addrinfo *hints = NULL;
  struct addrinfo *res;

  
  rc=getaddrinfo(nodename, srvname, hints, &res);
  result=rc==0;
  
  
  libcontra_log(LOGLEVEL_INFO, "I: ... test %02i: %5s (%5s)",testcase, bool2str(result), bool2str(true));

  return result;
}

/*!
*************************************************************************
*
* \fn int main(int argc, char **argv)
*
* \author Thorsten Alteholz <libcontra@alteholz.eu>
*
* \brief main function of server
*
* @param[in] argc number of parameters
* @param[in] argv value of parameters
* 
* \return rc value of all tests
************************************************************************/

int main(int argc, char **argv)
{
 int result, index, testnumber=0;
 bool testresult=false;
 
 libcontra_log(LOGLEVEL_INFO, "I: start test_server");
 libcontra_log(LOGLEVEL_INFO, "I: ... parsing parameter");
 while(1) {
  result = getopt_long(argc, argv, "t:", long_options, &index);
  if (result == -1) break;
  switch (result) {
    case 't':
      testnumber=atoi(optarg);
      break;
    default: /* unknown */
      break;;
  }
 }
 libcontra_log(LOGLEVEL_INFO, "I: ... doing test %02i",testnumber);

 switch (testnumber) {
   case 0:
     testresult=do_test_0(0);
     break;
   case 1:
     testresult=do_test_1(1);
     break;
   case 2:
     testresult=do_test_2(2);
     break;
   case 3:
     testresult=do_test_3(3);
     break;
   case 4:
     /*
      *this is the same as testcase 3 ...
      */
     testresult=do_test_3(4);
     /*
      * ... but with a client actually connecting
      *     we are using globalSockfd here, so we can use that later
      */
     testresult=testresult & do_test_4(4);
     break;
   case 5:
     /*
      *this is the same as testcase 4 ...
      */
     testresult=do_test_3(5);
     /*
      * ... but with a client actually sending stuff
      *     we are using globalSockfd here, so we can use that later
      */
     testresult=testresult & do_test_4(5);
     testresult=testresult & do_test_5(5);
     break;
   case 6:
     testresult=do_test_6(6);
     break;
   case 7:
     testresult=do_test_7(7);
     break;
   case 8:
     testresult=do_test_8(8);
     break;
   default:
     break;
 }
 libcontra_log(LOGLEVEL_INFO, "I: end test_server: %s", bool2str(testresult));

 if (testresult) return 0;
 else return 1;
}




/*! @} */

