// ------------------------------- //
// -------- Start of File -------- //
// ------------------------------- //
// ----------------------------------------------------------- // 
// C++ Source Code File Name: m_thread.cpp
// C++ Compiler Used: MSVC, GCC
// Produced By: DataReel Software Development Team
// File Creation Date: 10/17/2001
// Date Last Modified: 06/17/2016
// Copyright (c) 2001-2024 DataReel Software Development
// ----------------------------------------------------------- // 
// ------------- Program Description and Details ------------- // 
// ----------------------------------------------------------- // 
/*
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
 
This library 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
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  
USA

Multi-threaded server framework using the gxSSL and gxThread 
libraries.
*/
// ----------------------------------------------------------- // 
#include "gxdlcode.h"

#if defined (__USE_ANSI_CPP__) // Use the ANSI Standard C++ library
#include <iostream>
#else // Use the old iostream library by default
#include <iostream.h>
#endif // __USE_ANSI_CPP__

#include <string.h>
#include "m_thread.h"

// --------------------------------------------------------------
// Globals variable initialzation
// --------------------------------------------------------------
ServerConfig ServerConfigSruct;
ServerConfig *servercfg = &ServerConfigSruct;
// --------------------------------------------------------------

void ClientSocket_t::FreeSSL() 
{
  if(openssl) {
    openssl->ReleaseCTX(); // Do not free Context by ~gxSSL() call
    delete openssl; 
    openssl = 0;
  }
}

int CheckSocketError(gxSocket *s, const char *mesg, int report_error)
// Test the socket for an error condition and report the message
// if the "report_error" variable is true. The "mesg" string is used
// display a message with the reported error. Returns true if an
// error was detected for false of no errors where detected. 
{
  if(s->GetSocketError() == gxSOCKET_NO_ERROR) {
    // No socket errors reported
    return 0;
  }
  if(report_error) { // Reporting the error to an output device
    if(mesg) {
      PrintMessage(mesg, "\n", s->SocketExceptionMessage());
    }
    else {
      PrintMessage(s->SocketExceptionMessage());
    }
  }
  return 1;
}

int CheckThreadError(gxThread_t *thread, const char *mesg, int report_error)
// Test the thread for an error condition and report the message
// if the "report_error" variable is true. The "mesg" string is used
// display a message with the reported error. Returns true if an
// error was detected for false of no errors where detected. 
{
  if(thread->GetThreadError() == gxTHREAD_NO_ERROR) {
    // No thread errors reported
    return 0;
  }
  if(report_error) { // Reporting the error to an output device
    if(mesg) {
      PrintMessage(mesg, "\n", thread->ThreadExceptionMessage()); 
    }
    else {
      PrintMessage(thread->ThreadExceptionMessage());
    }
  }
  return 1;
}

void ReportError(const char *s1, const char *s2, const char *s3)
// Thread safe error reporting function.
{
  PrintMessage(s1, s2, s3);
}

void PrintMessage(const char *s1, const char *s2, const char *s3)
// Thread safe write function that will not allow access to
// the critical section until the write operation is complete.
{
  servercfg->display_lock.MutexLock();

  // Ensure that all threads have finished writing to the device
  int num_try = 0;
  while(servercfg->display_is_locked != 0) {
    // Block this thread from its own execution if a another thread
    // is writing to the device
    if(++num_try < DISPLAY_THREAD_RETRIES) {
      servercfg->display_cond.ConditionWait(&servercfg->display_lock);
    }
    else {
      return; // Could not write string to the device
    }
  }

  // Tell other threads to wait until write is complete
  servercfg->display_is_locked = 1; 

  // ********** Enter Critical Section ******************* //
  GXSTD::cout << "\n";
  if(s1) GXSTD::cout << s1;
  if(s2) GXSTD::cout << s2;
  if(s3) GXSTD::cout << s3;
  GXSTD::cout << "\n";
  GXSTD::cout.flush(); // Flush the ostream buffer to the stdio
  // ********** Leave Critical Section ******************* //

  // Tell other threads that this write is complete
  servercfg->display_is_locked = 0; 
 
  // Wake up the next thread waiting on this condition
  servercfg->display_cond.ConditionSignal();
  servercfg->display_lock.MutexUnlock();
}

ServerConfig::ServerConfig()
{
  port = 80;
  accept_clients = 1;
  display_is_locked = 0;
  client_request_pool = new thrPool;
}

ServerConfig::~ServerConfig()
{
  if(client_request_pool) delete client_request_pool;
}

int ServerThread::InitServer(int max_connections)
// Initialize the server. Returns a non-zero value if any
// errors ocurr.
{
  if(InitSocketLibrary() == 0) {
    if(InitSocket(SOCK_STREAM, servercfg->port) < 0) return 1;
  }
  else {
    return 1;
  }

  int val = 1;
  if(SetSockOpt(gxsocket, SOL_SOCKET, SO_REUSEADDR,
		&val, sizeof(val)) < 0) {
    CheckSocketError((gxSocket *)this, "Error initializing server"); 
    Close();
    return 1;
  }

  // Bind the name to the socket
  if(Bind() < 0) {
    CheckSocketError((gxSocket *)this, "Error initializing server"); 
    Close();
    return 1;
  }
  
  // Listen for connections with a specified backlog
  if(Listen(max_connections) < 0) {
    CheckSocketError((gxSocket *)this, "Error initializing server");
    Close();
    return 1;
  }

  return 0;
}

void *ServerThread::ThreadEntryRoutine(gxThread_t *thread)
{
  while(servercfg->accept_clients) { // Loop accepting client connections

    // Block the server until a client requests service
    if(Accept() < 0) {
      PrintMessage("Error accepting client connections");
      return 0;
    }

    // NOTE: Getting client info for statistical purposes only
    char client_name[gxsMAX_NAME_LEN]; int r_port = -1;
    GetClientInfo(client_name, r_port);
    PrintMessage("Received client request from ", client_name);

    ClientSocket_t *s = new ClientSocket_t;
    if(!s) {
      ReportError("A fatal memory allocation error occurred\n \
Shutting down the server");
      CloseSocket();
      return (void *)0;
    }
    
    SSL_CTX_set_cipher_list(openssl.GetCTX(), "ALL");

    // Record the file descriptor assigned by the Operating System
    s->client_socket = GetRemoteSocket();

    s->openssl = new gxSSL(openssl.GetCTX());
    if(!s->openssl) {
      PrintMessage("A fatal memory allocation error occurred\n \
Shutting down the server");
      delete s;
      return (void *)0;
    }

    // Destroy all the client threads that have exited
    RebuildThreadPool(servercfg->client_request_pool);

    // Create thread per cleint
    gxThread_t *rthread = \
      request_thread.CreateThread(servercfg->client_request_pool, (void *)s);
    if(CheckThreadError(rthread, "Error starting client request thread")) {
      delete s;
      return (void *)0;
    }
  }

  return (void *)0;
}

void ServerThread::ThreadExitRoutine(gxThread_t *thread)
// Thread exit function used to close the server thread.
{
  CloseSocket(); // Close the server side socket
}

void ServerThread::ThreadCleanupHandler(gxThread_t *thread)
// Thread cleanup handler used in the event that the thread is
// canceled.
{
  CloseSocket(); // Close the server side socket
}

void *ClientRequestThread::ThreadEntryRoutine(gxThread_t *thread)
{
  // Extract the client socket from the thread parameter
  ClientSocket_t *s = (ClientSocket_t *)thread->GetThreadParm();
  
  // Process the client request
  HandleClientRequest(s);

  return (void *)0;
}

void ClientRequestThread::ThreadExitRoutine(gxThread_t *thread)
// Thread exit function used to close the client thread.
{
  // Extract the client socket from the thread parameter
  ClientSocket_t *s = (ClientSocket_t *)thread->GetThreadParm();

  // Close the client socket and free its resources
  s->openssl->CloseSSLSocket();
  Close(s->client_socket);
  delete s;
}

void ClientRequestThread::ThreadCleanupHandler(gxThread_t *thread)
// Thread cleanup handler used in the event that the thread is
// canceled.
{
  // Extract the client socket from the thread parameter
  ClientSocket_t *s = (ClientSocket_t *)thread->GetThreadParm();

  // Close the client socket and free its resources
  s->openssl->CloseSSLSocket();
  Close(s->client_socket);
  delete s;
}

void ClientRequestThread::HandleClientRequest(ClientSocket_t *s)
// Function used to handle a client request.
{
  if(s->openssl->OpenSSLSocket(s->client_socket) != gxSSL_NO_ERROR) {
    PrintMessage("Error opening SSL socket");
    PrintMessage(s->openssl->SSLErrorMessage());
    socket_error = gxSOCKET_SSL_ERROR;
    return;
  }
  
  if(s->openssl->AcceptSSLSocket() != gxSSL_NO_ERROR) {
    PrintMessage("Error accepting SSL socket");
    PrintMessage(s->openssl->SSLErrorMessage());
    socket_error = gxSOCKET_SSL_ERROR;
    gxString err;
    gxSSL::GlobalSSLError(err);
    PrintMessage(err.c_str());
    return;
  }

  // NOTE: The demo processes a single HTTP GET request per client
  const int rxbuf = 1024;
  char request[rxbuf];

  // Block until the client sends some data
  int rv = s->openssl->RawReadSSLSocket(request, rxbuf);

  // A socket error occurred
  if(rv <= 0) {
    PrintMessage("Error reading SSL socket");
    PrintMessage(s->openssl->SSLErrorMessage());
    socket_error = gxSOCKET_RECEIVE_ERROR;
    return;
  }

  request[rv] = 0; // Null terminate the request string

  // At this point we need to parse the client request and process it.
  // For simplicity sake these operations have been omitted.
  // ... (parsing operation)
  // ... (processing operation)

  // Simply send a test page (with a header) back to the client.
  const char *test_page = "HTTP/1.0 200 OK\r\nServer: gxThread\r\nConnection: \
close\r\n\r\n<HTML>\n<HEAD>\n<TITLE> gxThread Test Page </TITLE>\n</HEAD>\
<BODY><CENTER><H1>gxThread Test Page</H1></CENTER><HR><PRE>Response from the \
multi-threaded HTTP server</PRE><HR><CENTER>End of document</CENTER>\n</BODY>\
\n</HTML>";

  // Blocking send that will not return until all bytes are written  
  //  Send(s->client_socket, test_page, strlen(test_page));
  //  CheckSocketError((gxSocket *)this, "Error sending page to client");

  rv = s->openssl->SendSSLSocket(test_page, strlen(test_page));
  if(s->openssl->HasError()) {
    if(s->openssl->GetSSLError() == gxSSL_DISCONNECTED_ERROR) {
      PrintMessage("Error writing to SSL socket");
      PrintMessage(s->openssl->SSLErrorMessage());
      socket_error = gxSOCKET_DISCONNECTED_ERROR; 
    }
    else {
      PrintMessage("Error writing to SSL socket");
      PrintMessage(s->openssl->SSLErrorMessage());
      socket_error = gxSOCKET_TRANSMIT_ERROR; 
    }
  }
  if(rv != (int)strlen(test_page)) {
    PrintMessage("Error writing to SSL socket");
    PrintMessage(s->openssl->SSLErrorMessage());
    socket_error = gxSOCKET_TRANSMIT_ERROR; 
  }
  CheckSocketError((gxSocket *)this, "Error sending page to client");
}
// ----------------------------------------------------------- // 
// ------------------------------- //
// --------- End of File --------- //
// ------------------------------- //