/***********************************************************************************
 *                                                                                 *
 *      Modul obsahuje hlavni stavovy automat tunelu a pomocne funkce              *
 *      jez tvori pomocna vlakna pro vymenu dat mezi rozhranimi.                   *
 *                                                                                 *
 ***********************************************************************************/

#include <unistd.h>
#include <malloc.h>
#include <string.h>
#include <sys/socket.h>
#include <resolv.h>
#include <iostream>
#include <openssl/bio.h> // BIO objects for I/O
#include <openssl/ssl.h> // SSL and SSL_CTX for SSL connections
#include <openssl/err.h> // Error reporting
#include <openssl/rand.h>
#include <stdio.h> // If you don't know what this is for stop reading now.
#include "sslServer.h"
#include "telnet.h"
#include <pthread.h>
#include <semaphore.h>
#include <signal.h>
#include <time.h>
#define FAIL    -1

sslServer *mySSL = 0;
Telnet * myTelnet = 0;


/***********************************************************************************
 *                                                                                 *
 *     Funkce je hlavnim prvkem vlakna pro presun dat mezi Telnetem a SSL rozhranim*
 *                                                                                 *
 ***********************************************************************************/
void* TelToSSLRoutine(void* input)
{
  /* Nasledujici struktury slouzi pro uchovani casovych informaci pro podminene cekani na signal */
  struct timespec timeToWait;
  struct timeval now;

  /* Vlakno bezi v nekonecne smycce */
  while(1)
    {
      /* Uzamkneme mutex pro cekani na data z telnetu */
      pthread_mutex_lock(myTelnet->GetReaderLock());

      /* Dokud Opakuj dokud nejsou prijata data z telnetu */
      while(myTelnet->AvailableData()==0)
        {
          /* Zjisti systemovy cas */
          gettimeofday(&now,NULL);

          /* Pridej 3.1 sekundy */
          timeToWait.tv_sec = now.tv_sec+3;
          timeToWait.tv_nsec = (now.tv_usec+100)*1000UL;

          /* Zahaj cekani na signal o prijatych datech */
          pthread_cond_timedwait(myTelnet->GetReaderCond(), myTelnet->GetReaderLock(), &timeToWait);

          /* Pokud neni pripojen telnet nebo SSL */
          if (myTelnet->isConnected() == false || mySSL->CheckConnectionState() == false )
            {
              /* Odemkni zamek pro podminku */
              pthread_mutex_unlock(myTelnet->GetReaderLock());

              /* Info, ktere rozhrani je odpojeno */
              if (myTelnet->isConnected()== false)
                cout << "Telnet disconnected in TelToSSL\r";
              else
                cout << "SSL disconnected in TelToSSL\r";
              return 0;
            }
        }

      /* Odemkneme zamek pro cteni z telnetu (neni potreba ho mit zamknuty) */
      pthread_mutex_unlock(myTelnet->GetReaderLock());

      /* Zjistime stav spojeni SSL. Pokud je pripojen */
      if (mySSL->CheckConnectionState())
        {
          /* Nacteme data z Telnetu a odesleme je pres SSL */
          if (mySSL->WriteData(myTelnet->ReadData())==false)
            {
              /* Pokud zapis selhal, ukoncime vlakno */
              cout << "TelToSSL write failed\r";
              return 0;
            }
        }
    }
}


/***************************************************************************
 *      Funkce pro realizaci vlakna, jez presunuje data z ERD do Telnetu   *
 *                                                                         *
 ***************************************************************************/
void* SSLToTelRoutine(void* input)
{
  /* Struktury pro uchovani casovych informaci*/
  struct timespec timeToWait;
  struct timeval now;

  /* Vlakno bezi v nekonecne smycce */
  while(1)
    {
      /* Uzamkneme zamek na strane SSL */
      pthread_mutex_lock(mySSL->GetReaderLock());

      /* Dokud nejsou prijata data z SSL, opakuj... */
      while(mySSL->AvailableData()==0)
        {
          /* Ziskani systemoveho casu a pridani 3.1s*/
          gettimeofday(&now,NULL);
          timeToWait.tv_sec = now.tv_sec+3;
          timeToWait.tv_nsec = (now.tv_usec+10000)*1000UL;

          /* Casove podminene cekani na signal o prijatych datech z SSL*/
          pthread_cond_timedwait(mySSL->GetReaderCond(), mySSL->GetReaderLock(), &timeToWait);

          /* Pokud je pripojeni na obou stranach v poradku */
          if (mySSL->CheckConnectionState() == false || myTelnet->isConnected() == false )
            {
              /* Odemkneme zamek */
              pthread_mutex_unlock(mySSL->GetReaderLock());

              /* Zjistime, ktera strana selhala */
              if (myTelnet->isConnected()== false)
                cout << "Telnet disconnected in SSLToTel\r";
              else
                cout << "SSL disconnected in SSLToTel\r";
              return 0;
            }
        }

      /* Odemkneme zamek */
      pthread_mutex_unlock(mySSL->GetReaderLock());

      /* Pokud je pripojeni v poradku */
      if (myTelnet->isConnected())
        {
          /* Odesleme data z SSL do Telnetu */
          if (myTelnet->WriteData(mySSL->ReadData())<0)
            {
              cout << "SSLToTel write failed\r";
              return 0;
            }
        }
    }
}






/*---------------------------------------------------------------------*/
/*--- main - create SSL socket server.                              ---*/
/*---------------------------------------------------------------------*/
int main(int count, char *strings[])
{
  /* Deklarace obou nezbytnych vlaken */
  pthread_t TelToSSL, SSLToTel;

  /* Nasledujici struktury slouzi pro uchovani casovych informaci pro podminene cekani na signal */
  struct timespec timeToWait;
  struct timeval now;


  /* Podle poctu parametru rozhodneme o chovani pri inicializaci */
    switch(count)
    {
    case 1:
      mySSL = new sslServer(1025,"./certs/","1234");
      break;
    case 2:
      mySSL = new sslServer(atoi(strings[1]),"./certs/","1234");
      break;
    default:
      cout << "Chybne parametry! Spravna syntaxe: tunnel port.";
      return -1;

    }
    /* Vytvoreni instance sslServer pro ovladani strany s ERD */


  /* Program bezi v nekonecne smycce */
  while(1)
    {

      /* Uzamkneme zamek pro cekani na signal o pripojeni */
      pthread_mutex_lock(mySSL->GetConnectionLock());

      /* Pokud neni pripojeni aktivni, cekej na signal */
      while(mySSL->CheckConnectionState()== false)
        {
          pthread_cond_wait(mySSL->GetConnectionCond(),mySSL->GetConnectionLock());
        }


      printf("Connected.\r");
      /* Ted by mela byt strana se SSL aktivni, takze vytvorime lokalni spojeni s Telnetem */
      myTelnet = new Telnet("127.0.0.1",23);

      /* Odemkneme zamek spojeni, kontrolu spojeni zaridi odesilaci vlakna */
      pthread_mutex_unlock(mySSL->GetConnectionLock());

      /* Vlakno pro realizaci spojeni Telnet => SSL */
      pthread_create(&TelToSSL,NULL,&TelToSSLRoutine,NULL);

      /* Vlakno pro realizaci spojeni SSL => Telnet */
      pthread_create(&SSLToTel,NULL,&SSLToTelRoutine,NULL);

      /*Opet uzamkneme zamek spojeni */
      pthread_mutex_lock(mySSL->GetConnectionLock());

      /* Cekani na odpojeni ze strany SSL*/
      while(mySSL->CheckConnectionState()== true && myTelnet->isConnected() == true)
        {
          //dpthread_cond_wait(mySSL->GetConnectionCond(),mySSL->GetConnectionLock());


          /* Zjisti systemovy cas */
          gettimeofday(&now,NULL);

          /* Pridej 3.1 sekundy */
          timeToWait.tv_sec = now.tv_sec+3;
          timeToWait.tv_nsec = (now.tv_usec+100)*1000UL;

          /* Zahaj cekani na signal o prijatych datech */
          pthread_cond_timedwait(mySSL->GetConnectionCond(),mySSL->GetConnectionLock(), &timeToWait);

        }
      /*if (!myTelnet->isConnected())
        {
          mySSL->WriteData("Spojeni bylo lokalne ukonceno!\r");
          mySSL->WriteData("Opakujte pripojeni.");

        }*/
      printf("Connection lost.\n");

      pthread_mutex_unlock(mySSL->GetConnectionLock());

      /* Cekani na ukonceni obou vlaken */
      pthread_join(TelToSSL,0);
      pthread_join(SSLToTel,0);
      printf("Correctly stopped.\r\n");

      /* Ukonceni spojeni s Telnetem. */
      delete myTelnet;
      //delete mySSL;

    }
}

