//
//  File:   hzIpServer.cpp
//
//  Legal Notice:   This file is part of the HadronZoo C++ Class Library. Copyright 2025 HadronZoo Project (http://www.hadronzoo.com)
//
//  The HadronZoo C++ Class 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 3 of the License, or any later version.
//
//  The HadronZoo C++ Class 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 the HadronZoo C++ Class Library. If not, see http://www.gnu.org/licenses.
//
//
//  Implementation of HadronZoo server and client socket classes
//
#include <cstdio>
#include <fstream>
#include <iostream>
#include <unistd.h>
#include <netdb.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <sys/epoll.h>
#include <signal.h>
#include <pthread.h>
#include "hzChars.h"
#include "hzMimetype.h"
#include "hzTextproc.h"
#include "hzDirectory.h"
#include "hzHttpServer.h"
#include "hzIpServer.h"
#include "hzProcess.h"
using namespace std ;
/*
**  Definitions
*/
class   _hz_SSL_Regime
{
    //  SSL context plus the private key, SSL certificate and CA certificate, for a given domain, subdomain or for server as a whole
public:
    SSL_CTX*    m_svrCTX ;          //  SSL context (for domain name or server)
    hzString    m_Domain ;          //  Domain name
    hzString    m_sslPvtKey ;       //  SSL Private Key
    hzString    m_sslCert ;         //  SSL Certificate
    hzString    m_sslCertCA ;       //  SSL CA Certificate
    _hz_SSL_Regime  ()  { m_svrCTX = 0 ; }
} ;
/*
**  Variables
*/
global  hzMapS  <hzIpaddr,hzIpinfo> _hzGlobal_StatusIP ;    //  Black and white listed IP addresses
global  hzString    _hzGlobal_Hostname ;                    //  This server hostname
global  hzString    _hzGlobal_HostIP ;                      //  This server IP address
global  hzIpaddr    _hzGlobal_livehost ;                    //  This server IP address (copy)
static  hzMapS  <hzString,_hz_SSL_Regime*>  s_mapSSLDoms ;  //  Map of all applicable SSL server contexts
static  _hz_SSL_Regime*     s_SSL_svrRegime ;               //  Default SSL context and certs for whole server
static  hzIpServer*         s_pTheOneAndOnlyServer = 0 ;    //  The singleton server instance
//static    SSL_CTX*            s_svrCTX = 0 ;                  //  SSL CTX structure
static  const SSL_METHOD*   s_svrMeth = 0 ;                 //  SSL method
//  static  int32_t (*verify_callback)(int, X509_STORE_CTX*) ;  //  SSL callback function
static  hzPacket*   s_pTcpBuffer_freelist = 0 ;             //  Freelist of IP packet holders
static  ofstream    s_status_ip_os ;        //  Black and white list stream
static  hzString    s_status_ip_fname ;     //  Blacklist file path
static  hzString    s_str_hangup = "EPOLL HANGUP" ;         //  Epoll hangup error message
static  hzString    s_str_error = "EPOLL ERROR" ;           //  Epoll general error message
//static    int32_t     s_epollSocket ;                         //  The epoll 'master' socket
/*
**  System Init Functions
*/
hzEcode SetupHost   (void)
{
    //  Category:   Internet Server
    //
    //  Determine the hostname of this server. This is called as part of the initialization sequence of a server application, if not also a client application.
    //  It should generally not be called mid way though. The hostname could change during runtime, but in a live server environment proving an online service,
    //  performing such a change is absurd.
    //
    //  The function calls the standard functions gethostname() and gethostbyname(). The former to establish the hostname and set _hzGlobal_Hostname. The latter
    //  to establish the physical server IP address and set _hzGlobal_HostIP.
    //
    //  Although it is intended that this function be called as part of program initialization, there is still a need to take account of the condition in which
    //  the DNS advises a retry (h_errno set to DNS_TRY_AGAIN). This is because it should not be assumed the program is to be started manually. This function
    //  will repeat the call to gethostbyname() for a maximum of 10 times before failing.
    //
    //  The application should check that the hostname is not set to localhost if it is to provide an online service.
    //
    //  Arguments:  None. The function sets two global parameters, both of type
    //              hzString, namely _hzGlobal_Hostname and _hzGlobal_HostIP
    //
    //  Returns:    E_NODATA    If the call to gethostname fails
    //              E_NOTFOUND  If the call to gethostbyname does not return a host
    //              E_DNS_RETRY If the number of retries reaches 10
    //              E_OK        If the hostname established
    _hzfunc("SetupHost") ;
    struct hostent*     pHost ;     //  Host entry
    struct sockaddr_in  SvrAddr ;   //  Server address
    uint32_t    nTries ;            //  Number of DNS tries
    char        buf [256] ;         //  Buffer for gethostname function
    if (gethostname(buf, 256))
    {
        hzerr(E_NODATA, "System call 'gethostname' failed. Cannot establish hostname") ;
        return E_NODATA ;
    }
    _hzGlobal_Hostname = buf ;
    for (nTries = 0 ; nTries < 10 ; nTries++)
    {
        pHost = gethostbyname(*_hzGlobal_Hostname) ;
        if (pHost)
            break ;
        if (h_errno == TRY_AGAIN)
            continue ;
        hzerr(E_NOTFOUND, "Could not lookup the hostname") ;
        return E_NOTFOUND ;
    }
    if (nTries == 10)
    {
        hzerr(E_DNS_RETRY, "Could not lookup the hostname") ;
        return E_DNS_RETRY ;
    }
    memset(&SvrAddr, 0, sizeof(SvrAddr)) ;
    SvrAddr.sin_family = AF_INET ;
    memcpy(&SvrAddr.sin_addr, pHost->h_addr, pHost->h_length) ;
    _hzGlobal_HostIP = inet_ntoa(SvrAddr.sin_addr) ;
    _hzGlobal_livehost = _hzGlobal_HostIP ;
    return E_OK ;
}
hzIpServer* hzIpServer::GetInstance (hzLogger* pLogger)
{
    //  Purpose:    Creates a single instance of a hzIpServer with the Singleton method of calling a private constructor.
    //
    //  Arguments:  1)  pLogger The logger for the server (this is compulsory)
    //
    //  Returns:    Pointer to the hzIpServer instance. The same instance is returned however many times this function is called.
    //              NULL if no logger is provided
    _hzfunc("hzIpServer::GetInstance") ;
    hzIpServer* pSvr = 0 ;  //  TCP server
    if (!pLogger)
    {
        hzerr(E_NOINIT, "No logfile supplied") ;
        return 0 ;
    }
    if (s_pTheOneAndOnlyServer)
    {
        //pSvr = dynamic_cast<hzIpServer*>(s_pTheOneAndOnlyServer) ;
        pSvr = s_pTheOneAndOnlyServer ;
        if (!pSvr)
        {
            hzerr(E_CONFLICT, "Application is attempting to allocate a TCP Server when a UDP Server is already allocated") ;
            return 0 ;
        }
        hzerr(E_CONFLICT, "Application is attempting to allocate more than one server instance") ;
    }
    else
    {
        pSvr = new hzIpServer() ;
        pSvr->m_pLog = pLogger ;
        s_pTheOneAndOnlyServer = pSvr ;
    }
    return pSvr ;
}
/*
**  Packet Allocation Functions
*/
hzPacket*   _tcpbufAlloc    (void)
{
    //  Allocate a hzPacket instance to service a TCP client connection. Allocation is from a freelist regime.
    //
    //  Arguments:  None
    //  Returns:    Pointer to a hzPacket instance
    hzPacket*   pTB ;   //  Target hzPacket pointer
    if (!s_pTcpBuffer_freelist)
        pTB = new hzPacket() ;
    else
    {
        pTB = s_pTcpBuffer_freelist ;
        s_pTcpBuffer_freelist = s_pTcpBuffer_freelist->next ;
        pTB->next = 0 ;
        pTB->m_size = 0 ;
    }
    return pTB ;
}
void    _tcpbufDeleteOne    (hzPacket* pTB)
{
    //  Delete a single hzPacket instance after the data held in it has been successfully sent to the client.
    //
    //  Arguments:  1)  pTB Pointer to hzPacket instance
    //
    //  Returns:    None
    pTB->next = s_pTcpBuffer_freelist ;
    s_pTcpBuffer_freelist = pTB ;
}
void    _tcpbufDeleteAll    (hzPacket* first, hzPacket* last)
{
    //  Delete a series of hzPacket instances. This is called when the outgoing message to a connected client is complete or has been abandoned.
    //
    //  Arguments:  1)  first   Start of series
    //              2)  last    End of series
    //
    //  Returns:    None
    last->next = s_pTcpBuffer_freelist ;
    s_pTcpBuffer_freelist = first ;
}
int32_t SNI_Callback    (SSL *pSSL, int *al, void *arg)
{
    //  Category:   Internet Server
    //
    //  This is provided as a function pointer for use by SSL_accept() function, to establish which certificate to send to the clinet, on the basis of the domain name sought by the
    //  client.
    _hzfunc("SNI_Callback") ;
    _hz_SSL_Regime* pSSL_Regime ;   //  SSL Server context
    hzString        svrName ;   //  Server (domain) name requested by connecting client
    int32_t         type ;      //  Connection type?
    type = SSL_get_servername_type(pSSL);
    svrName = SSL_get_servername(pSSL, type) ;
    if (!svrName)
    {
        threadLog("Server name req by client: NULL (type %d)\n", type) ;
        return SSL_TLSEXT_ERR_OK ;
    }
    threadLog("Server name req by client: %s (type %d)\n", *svrName, type) ;
    pSSL_Regime = s_mapSSLDoms[svrName] ;
    if (!pSSL_Regime)
    {
        threadLog("Server name %s not found\n", *svrName) ;
        return SSL_TLSEXT_ERR_OK ;
    }
    if (pSSL_Regime == s_SSL_svrRegime)
    {
        threadLog("Selecting default\n") ;
        return SSL_TLSEXT_ERR_OK ;
    }
    //  Swap the CTX
    threadLog("Swaping CTX to %s\n", *pSSL_Regime->m_Domain) ;
    SSL_set_tlsext_host_name(pSSL, *pSSL_Regime->m_Domain);
    SSL_set_SSL_CTX(pSSL, pSSL_Regime->m_svrCTX);
    return SSL_TLSEXT_ERR_OK ;
}
hzEcode InitDomainSSL   (const char* pvtKey, const char* sslCert, const char* sslCA, const char* domain)
{
    //  Category:   Internet Server
    //
    //  Initializes SSL for a single domain. The domain name and full paths for the private key, SSL certificate and CA certificate, must be supplied. The scope of the certificates
    //  is set by the domain argument as follows:-
    //
    //      1)  The certificate is assumed to a single domain wildcard if it is a domain name preceded by an asterisk and a period
    //      2)  The certificate is assumed to single domain if only a domain name is submitted. The domain name must specify the exact subdomain.
    //
    //  Arguments:  1)  pvtKey          Domain Private Key
    //              2)  sslCert         Domain Certificate
    //              3)  sslCA           Domain CA Certificate
    //              4)  domain          Domain name
    //
    //  Returns:    E_INITDUP   If SSL has already been initialized
    //              E_ARGUMENT  If either the private key, the server SSL certificate or the certificate authority are not supplied
    //              E_INITFAIL  If any of the SSL functions return an error
    //              E_OK        If the SSL is set up OK
    _hzfunc("InitDomainSSL") ;
    _hz_SSL_Regime* pSSL_Regime ;       //  SSL Server context
    hzString        S ;             //  Temp string
    int32_t         sys_rc ;        //  System return value
    //  Check args
    if (!pvtKey || !sslCert || !sslCA || !domain)
        return hzerr(E_ARGUMENT, "Domain %s: SSL params missing [PvtKey=%s][Cert=%s][CertCA=%s]", domain, pvtKey, sslCert, sslCA) ;
    threadLog("Domain %s: SSL params [PvtKey=%s][Cert=%s][CertCA=%s]\n", domain, pvtKey, sslCert, sslCA) ;
    //  Do server SSL init
    if (!s_svrMeth)
    {
        sys_rc = SSL_library_init() ;
        threadLog("Returned from SSL_library_init with %d\n", sys_rc) ;
        SSL_load_error_strings();   
        sys_rc = OpenSSL_add_ssl_algorithms();
        threadLog("Returned from OpenSSL_add_ssl_algorithms with %d\n", sys_rc) ;
        s_svrMeth = SSLv23_server_method() ;
        if (!s_svrMeth)
            return hzerr(E_INITFAIL, "Failed to allocate SSLv23 Server Method (errno %d)\n", errno) ;
    }
    //  Create domain or server context container
    S = domain ;
    if (s_mapSSLDoms.Exists(S))
        return hzerr(E_INITDUP, "SSL Init called already for domain %s", domain) ;
    pSSL_Regime = new _hz_SSL_Regime() ;
    pSSL_Regime->m_Domain = S ;
    s_mapSSLDoms.Insert(S, pSSL_Regime) ;
    //  Create the SSL context and set callback
    pSSL_Regime->m_svrCTX = SSL_CTX_new(s_svrMeth) ;
    if (!pSSL_Regime->m_svrCTX)
        return hzerr(E_INITFAIL, "Failed to allocate SSL Server Context for % (errno %d)\n", *pSSL_Regime->m_Domain, errno) ;
    SSL_CTX_set_tlsext_servername_callback(pSSL_Regime->m_svrCTX, &SNI_Callback) ;
    //  Load server certificate into the SSL context
    sys_rc = SSL_CTX_use_certificate_file(pSSL_Regime->m_svrCTX, sslCert, SSL_FILETYPE_PEM) ;
    if (sys_rc <= 0)
        return hzerr(E_INITFAIL, "No SSL certificate. File %s Returned %d, errno %d", sslCert, sys_rc, errno) ;
    threadLog("Returned from SSL_CTX_use_certificate_file with %d\n", sys_rc) ;
    sys_rc = SSL_CTX_load_verify_locations(pSSL_Regime->m_svrCTX, sslCA, NULL) ;
    if (sys_rc <= 0)
        return hzerr(E_INITFAIL, "No SSL CA certificate. File %s Returned %d, errno %d", sslCA, sys_rc, errno) ;
    threadLog("Returned from SSL_CTX_load_verify_locations with %d\n", sys_rc) ;
 
    //  Load the server private-key into the SSL context
    sys_rc = SSL_CTX_use_PrivateKey_file(pSSL_Regime->m_svrCTX, pvtKey, SSL_FILETYPE_PEM) ;
    if (sys_rc <= 0)
        return hzerr(E_INITFAIL, "No SSL private key. File %s Error %d", pvtKey, sys_rc) ;
    threadLog("Returned from SSL_CTX_use_PrivateKey_file with %d\n", sys_rc) ;
    //  Check if the server certificate and private-key matches
    if (!(sys_rc = SSL_CTX_check_private_key(pSSL_Regime->m_svrCTX)))
        return hzerr(E_INITFAIL, "Private key does not match the certificate public key") ;
    threadLog("Returned from SSL_CTX_check_private_key with %d\n", sys_rc) ;
    return E_OK ;
}
hzEcode InitServerSSL   (const char* pvtKey, const char* sslCert, const char* sslCA)
{
    //  Category:   Internet Server
    //
    //  Initializes SSL for the whole server. If a multi-domain certificate is supplied, this will be used to create the default SSL regime. Otherwise the default SSL regime is set
    //  to that of the first domain SSL regime in the map of domain SSL regimes. It cannot be NULL as it is used to create the client connection SSL structure (created by SSL_new).
    //
    //  Arguments:  1)  pvtKey          Server Private Key
    //              2)  sslCert         Server Certificate
    //              3)  sslCA           Server CA Certificate
    //
    //  Returns:    E_INITDUP   If server SSL has already been initialized
    //              E_ARGUMENT  If either the private key, the server SSL certificate or the certificate authority are supplied, but not all
    //              E_INITFAIL  If any of the SSL functions return an error
    //              E_OK        If the SSL is set up OK
    _hzfunc("InitServerSSL") ;
    hzString    S ;                 //  Temp string
    int32_t     sys_rc ;            //  System return value
    //  Check we have not already set the default server SSL regime
    if (s_SSL_svrRegime)
        return hzerr(E_INITDUP, "SSL Init called already for whole server") ;
    //  Do server SSL init
    if (!s_svrMeth)
    {
        sys_rc = SSL_library_init() ;
        threadLog("Returned from SSL_library_init with %d\n", sys_rc) ;
        SSL_load_error_strings();   
        sys_rc = OpenSSL_add_ssl_algorithms();
        threadLog("Returned from OpenSSL_add_ssl_algorithms with %d\n", sys_rc) ;
        s_svrMeth = SSLv23_server_method() ;
        if (!s_svrMeth)
            return hzerr(E_INITFAIL, "Failed to allocate SSLv23 Server Method (errno %d)\n", errno) ;
    }
    //  Check args
    if ((pvtKey || sslCert || sslCA) && (!pvtKey || !sslCert || !sslCA))
        return hzerr(E_ARGUMENT, "Server SSL params incomplete set [PvtKey=%s][Cert=%s][CertCA=%s]", pvtKey, sslCert, sslCA) ;
    if (!pvtKey || !sslCert || !sslCA)
    {
        s_SSL_svrRegime = s_mapSSLDoms.GetObj(0) ;
        if (!s_SSL_svrRegime)
            return hzerr(E_ARGUMENT, "Server SSL - No domain SSL regimes found [PvtKey=%s][Cert=%s][CertCA=%s]", pvtKey, sslCert, sslCA) ;
        return E_OK ;
    }
    threadLog("SSL params [PvtKey=%s][Cert=%s][CertCA=%s]\n", pvtKey, sslCert, sslCA) ;
    //  Create domain or server context container
    s_SSL_svrRegime = new _hz_SSL_Regime() ;
    s_SSL_svrRegime->m_Domain = "default" ;
    //  Create the SSL context and set callback
    s_SSL_svrRegime->m_svrCTX = SSL_CTX_new(s_svrMeth) ;
    if (!s_SSL_svrRegime->m_svrCTX)
        return hzerr(E_INITFAIL, "Failed to allocate SSL Server Context for % (errno %d)\n", *s_SSL_svrRegime->m_Domain, errno) ;
    SSL_CTX_set_tlsext_servername_callback(s_SSL_svrRegime->m_svrCTX, &SNI_Callback) ;
    //  Load server certificate into the SSL context
    sys_rc = SSL_CTX_use_certificate_file(s_SSL_svrRegime->m_svrCTX, sslCert, SSL_FILETYPE_PEM) ;
    if (sys_rc <= 0)
        return hzerr(E_INITFAIL, "No SSL certificate. File %s Returned %d, errno %d", sslCert, sys_rc, errno) ;
    threadLog("Returned from SSL_CTX_use_certificate_file with %d\n", sys_rc) ;
    sys_rc = SSL_CTX_load_verify_locations(s_SSL_svrRegime->m_svrCTX, sslCA, NULL) ;
    if (sys_rc <= 0)
        return hzerr(E_INITFAIL, "No SSL CA certificate. File %s Returned %d, errno %d", sslCA, sys_rc, errno) ;
    threadLog("Returned from SSL_CTX_load_verify_locations with %d\n", sys_rc) ;
 
    //  Load the server private-key into the SSL context
    sys_rc = SSL_CTX_use_PrivateKey_file(s_SSL_svrRegime->m_svrCTX, pvtKey, SSL_FILETYPE_PEM) ;
    if (sys_rc <= 0)
        return hzerr(E_INITFAIL, "No SSL private key. File %s Error %d", pvtKey, sys_rc) ;
    threadLog("Returned from SSL_CTX_use_PrivateKey_file with %d\n", sys_rc) ;
    //  Check if the server certificate and private-key matches
    if (!(sys_rc = SSL_CTX_check_private_key(s_SSL_svrRegime->m_svrCTX)))
        return hzerr(E_INITFAIL, "Private key does not match the certificate public key") ;
    threadLog("Returned from SSL_CTX_check_private_key with %d\n", sys_rc) ;
    return E_OK ;
}
hzTcpCode   HandleHttpMsg   (hzChain& Input, hzIpConnex* pCx)
{
    //  Category:   Internet Server
    //
    //  While no assumptions can be made about incoming messages within the hzIpServer regime itself, for HTTP it is expedient to ensure that messages (HTTP requests), are complete
    //  before being passed to the applicable callback function (by default hdsApp::ProcHTTP).
    //
    //  This is acheived by creating a hzHttpEvent instance for the connection and calling hzHttpEvent::ProcessEvent on the data recieved so far. If this sets a flag indicating the
    //  message does constitute a complete HTTP request, then the callback function is invoked.
    //
    //  Arguments:  1)  Input   The input chain (maintained by the hzIpServer instance)
    //              2)  pCx     The TCP connection the message is being received on
    //
    //  Returns:    A hzTcpCode to indicate keep-alive or terminate.
    //
    //  Notes:      This function is itself a callback function that is called every time a packet arrives on a port deemed to be using
    //              the HTTP protocol. It is defined as static to prevent it being called directly by the application.
    _hzfunc("HandleHttpMsg") ;
    hzTcpCode   (*fnOnHttpEvent)(hzHttpEvent*) ;    //  HTTP event handle
    hzHttpEvent*    pE ;        //  The HTTP event message
    hzTcpCode       tcp_rc ;    //  TCP return code
    if (!pCx->m_pEventHdl)
        hzexit(E_MEMORY, "Could not allocate a HTTP Event") ;
    pE = (hzHttpEvent*) pCx->m_pEventHdl ;
    //  Know message complete - don't keep the pointer
    if (pCx->MsgComplete())
        pCx->m_pEventHdl = 0 ;
    //  First process message to see if it is complete. This also populates the hzHttpEvent instance
    if (pE->ProcessEvent(Input) != E_OK)
    {
        pCx->GetLogger()->Log("Deleting failed HTTP event input\n") ;
        Input.Clear() ;
        return TCP_TERMINATE ;
    }
    if (!pE->MsgComplete())
    {
        //  Know message incomplete - set the pointer
        pCx->m_pEventHdl = pE ;
        pCx->ExpectSize(pE->ExpectSize()) ;
        return TCP_INCOMPLETE ;
    }
    //  Call the applcation's function
    if (pCx->m_appFn)
    {
        fnOnHttpEvent = (hzTcpCode(*)(hzHttpEvent*)) pCx->m_appFn ;
        tcp_rc = fnOnHttpEvent(pE) ;
    }
    Input.Clear() ;
    return tcp_rc ;
}
hzEcode InitIpInfo  (const hzString& dataDir)
{
    //  Category:   Internet Server
    //
    //  Reads in BOTH the blacklist AND whitelist file
    _hzfunc(__func__) ;
    ifstream    is ;            //  For reading the lists
    hzIpinfo    ipi ;           //  IP block info
    hzIpaddr    ipa ;           //  IP address
    uint32_t    nLine ;         //  Line number
    char        buf [24] ;      //  Line buffer
    hzEcode     rc = E_OK ;     //  Return code
    if (!dataDir)
        return E_ARGUMENT ;
    rc = AssertDir(dataDir, 0666) ;
    if (rc != E_OK)
        return rc ;
    s_status_ip_fname = dataDir + "/status_ip.ips" ;
    rc = TestFile(s_status_ip_fname) ;
    if (rc == E_OK)
    {
        is.open(*s_status_ip_fname) ;
        if (is.fail())
            return hzerr(E_OPENFAIL, "Cannot open file %s\n", *s_status_ip_fname) ;
        for (nLine = 1 ;; nLine++)
        {
            is.getline(buf, 23) ;
            if (!is.gcount())
                break ;
            ipa = buf ;
            if (!ipa)
                return hzerr(E_FORMAT, "Line %d of file %s, is not a valid IP address", nLine, *s_status_ip_fname) ;
            _hzGlobal_StatusIP.Insert(ipa, ipi) ;
        }
        is.close() ;
        is.clear() ;
        threadLog("Loaded %d IP address in WHITELIST\n", _hzGlobal_StatusIP.Count()) ;
    }
    else
    {
        if (rc != E_NOTFOUND && rc != E_NODATA)
            return hzerr(rc, "File error %s\n", *s_status_ip_fname) ;
    }
    rc = E_OK ;
    //  Now open the file for writing new entries
    s_status_ip_os.open(*s_status_ip_fname, ios::app) ;
    if (s_status_ip_os.fail())
        return hzerr(E_OPENFAIL, "Cannot open %s for writing", *s_status_ip_fname) ;
    return rc ;
}
void    SetStatusIP (hzIpaddr ipa, hzIpStatus reason, uint32_t nDelay)
{
    //  Category:   Internet Server
    //
    //  Set reason for noting the IP address. Add the IP address to _hzGlobal_StatusIP if not already present.
    _hzfunc(__func__) ;
    hzIpinfo    ipi ;       //  IP test blocker
    if (ipa == IPADDR_BAD || ipa == IPADDR_NULL || ipa == IPADDR_LOCAL)
        return ;
    threadLog("Setting Status of IP %s\n", *ipa) ;
    if (!_hzGlobal_StatusIP.Exists(ipa))
    {
        s_status_ip_os << ipa << "\n" ;
        if (!s_status_ip_os.fail())
            s_status_ip_os.flush() ;
        ipi.m_nSince = 1 ;
        ipi.m_nTotal = 1 ;
        ipi.m_bInfo |= (reason & 0xff) ;
        if (reason & HZ_IPSTATUS_BLACK)
            ipi.m_tBlack = time(0) ;
        if (reason & HZ_IPSTATUS_WHITE)
            ipi.m_tWhite = time(0) ;
        _hzGlobal_StatusIP.Insert(ipa, ipi) ;
    }
    else
    {
        ipi = _hzGlobal_StatusIP[ipa] ;
        //  If status has changed, note this in the file
        if ((ipi.m_bInfo | reason) != ipi.m_bInfo)
        {
            ipi.m_bInfo |= reason ;
            if (reason & HZ_IPSTATUS_BLACK)
                ipi.m_tBlack = nDelay ? nDelay + time(0) : 0 ;
            if (reason & HZ_IPSTATUS_WHITE)
                ipi.m_tWhite = nDelay ? nDelay + time(0) : 0 ;
        }
        s_status_ip_os << ipa << "\n" ;
        if (s_status_ip_os.fail())
            threadLog("BLOCKED IP ADDR NOT RECORDED %s\n", *ipa) ;
        else
            s_status_ip_os.flush() ;
    }
    //note.Printf("IP ADDR BLOCKED %s %u times\n", *ipa.Str(), _hzGlobal_Blacklist[ipa].m_Count++) ;
    //_hzGlobal_Blacklist[IpTest].m_Count++ ;
}
hzIpStatus  GetStatusIP (hzIpaddr ipa)
{
    //  Category:   Internet Server
    //
    //  Retrieve the record for the IP address.
    _hzfunc(__func__) ;
    hzIpinfo    ipi ;       //  IP test blocker
    uint32_t    now ;       //  Epoch time
    uint32_t    ips ;       //  IP status as it currently applies
    ips = (uint32_t) HZ_IPSTATUS_NULL ;
    if (!_hzGlobal_StatusIP.Exists(ipa))
        return HZ_IPSTATUS_NULL ;
    //  We have info on the IP address, but does it still apply?
    now = time(0) ;
    ipi = _hzGlobal_StatusIP[ipa] ;
    if (ipi.m_bInfo & HZ_IPSTATUS_WHITE && (!ipi.m_tWhite || ipi.m_tWhite > now))
        ips |= HZ_IPSTATUS_WHITE ;
    if (ipi.m_bInfo & HZ_IPSTATUS_BLACK && (!ipi.m_tWhite || ipi.m_tBlack > now))
        ips |= HZ_IPSTATUS_BLACK ;
    return (hzIpStatus) ips ;
}
/*
**  The hzIpListen class members
*/
hzEcode hzIpListen::Init    (   hzIpServer* pServer,
                                hzTcpCode   (*OnIngress)(hzChain&, hzIpConnex*),
                                hzTcpCode   (*OnConnect)(hzIpConnex*),
                                hzTcpCode   (*OnDisconn)(hzIpConnex*),
                                uint32_t    nTimeout,
                                uint32_t    nPort,
                                uint32_t    nMaxConnections,
                                uint32_t    bOpflags    )
{
    //  Purpose:    Initialize a listening socket.
    //
    //  Arguments:  1)  OnIngress       The Application Specific 'OnIngress' function - called each time client sends in data.
    //              2)  OnConnect       The Application Specific 'Server Hello' function - only where protocol requires server to speak first.
    //              3)  OnSession       Application Specific Session Handler function - only where client sessions are handled in a separate thread.
    //              4)  nTimeout        Timeout in seconds.
    //              5)  nPort           The port number to listen on.
    //              6)  nMaxConnections Max number of simulataneous connections to this port.
    //              7)  bSecure         Use SSL
    //
    //  Returns:    E_NOINIT    If the SSL server method is not set up (SSL applications only)
    //              E_OK        If the listening socket is set up.
    _hzfunc("hzIpListen::Init") ;
    if (!pServer)
        return hzerr(E_ARGUMENT, "No hzIpServer supplied") ;
    m_pServer = pServer ;
    if (OnIngress)  m_OnIngress = OnIngress ;
    if (OnConnect)  m_OnConnect = OnConnect ;
    if (OnDisconn)  m_OnDisconn = OnDisconn ;
    //if (OnSession)    m_OnSession = OnSession ;
    m_nTimeout = nTimeout ;
    m_nPort = nPort ;
    m_nMaxConnections = nMaxConnections ;
    if (bOpflags & HZ_LISTEN_SECURE)
    {
        if (!s_svrMeth)
        {
            hzerr(E_NOINIT, "No SSL initialization") ;
            return E_NOINIT ;
        }
    }
    m_bOpflags = bOpflags ;
    return E_OK ;
}
hzEcode hzIpListen::Activate    (void)
{
    //  Purpose:    Acivate listening socket. This creates a sock and then binds it to a port.
    //
    //  Arguments:  None.
    //
    //  Returns:    E_NOSOCKET  If listening socket bound to port,
    //              E_OK        If operation successful
    _hzfunc("hzIpListen::Activate") ;
    uint32_t    nTries ;        //  Number of attempts to bind (give up after 5)
    int32_t     sys_rc ;        //  Return from bind call
    //  Allocate and bind a listening socket
    if (m_bOpflags & HZ_LISTEN_UDP)
    {
        if ((m_nSocket = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
        {
            hzerr(E_NOSOCKET, "Could not create UDP server socket (errno=%d)", errno) ;
            return E_NOSOCKET ;
        }
    }
    else
    {
        if ((m_nSocket = socket(AF_INET, SOCK_STREAM, 0)) < 0) 
        {
            hzerr(E_NOSOCKET, "Could not create TCP server socket (errno=%d)", errno) ;
            return E_NOSOCKET ;
        }
    }
    memset(&m_Address, 0, sizeof(SOCKADDRIN)) ;
    m_Address.sin_family = AF_INET ;
    m_Address.sin_addr.s_addr = htonl(INADDR_ANY) ;
    m_Address.sin_port = htons(m_nPort) ;
    for (nTries = 1 ; nTries <= 5 ; nTries++)
    {
        sys_rc = bind(m_nSocket, (SOCKADDR*) &m_Address, sizeof(m_Address)) ;
        if (sys_rc < 0)
        {
            hzwarn(E_NOSOCKET, "Server could not bind to port %d (attempt %d)", m_nPort, nTries) ;
            if (close(m_nSocket) < 0)
            {
                hzerr(E_CORRUPT, "Could not close socket %d (port %d) after bind_fail. Errno=%d", m_nSocket, m_nPort, errno) ;
                return E_CORRUPT ;
            }
            if (nTries == 5)
                return E_NOSOCKET ;
            sleep(5) ;
            continue ;
        }
        break ;
    }
    if (!(m_bOpflags & HZ_LISTEN_UDP))
    {
        if (listen(m_nSocket, 5) < 0)
        {
            hzwarn(E_NOSOCKET, "Server listening socket (sock %d) is not listening on port %d. Errno=%d", m_nSocket, m_nPort, errno) ;
            if (close(m_nSocket) < 0)
                hzwarn(E_CORRUPT, "Could not close socket %d after listen_fail. Errno=%d", m_nSocket, errno) ;
            return E_NOSOCKET ;
        }
    }
    return E_OK ;
}
/*
**  hzIpConnex members
*/
hzIpConnex::hzIpConnex  (hzLogger* pLog)
{
    m_ConnExpires = m_nsAccepted = m_nsRecvBeg = m_nsRecvEnd = m_nsSendBeg = m_nsSendEnd = RealtimeNano() ;
    m_pInfo = 0 ;
    m_nSock = 0 ;
    m_nPort = 0 ;
    m_nMsgno = 0 ;
    m_nGlitch = 0 ;
    m_nStart = 0 ;
    m_nExpected = 0 ;
    m_pEventHdl = 0 ;
    m_pLog = pLog ;
    m_pSSL = 0 ;
    m_bState = CLIENT_STATE_NONE ;
    m_bAcceptSSL = false ;
    m_pProxy = 0 ;
    //memset(m_ipbuf, 0, 44) ;
}
hzIpConnex::~hzIpConnex (void)
{
    if (m_nSock)
    {
        //  if (_hzGlobal_Debug & HZ_DEBUG_SERVER)
        //      threadLog("NOTE: Connection under Destruction still has a socket %d\n", m_nSock) ;
        close(m_nSock) ;
        m_nSock = 0 ;
    }
    if (m_pSSL)         SSL_free(m_pSSL) ;
    if (m_pInfo)        delete m_pInfo ;
    if (m_pEventHdl)    delete (hzHttpEvent*) m_pEventHdl ;
}
hzEcode hzIpConnex::Initialize  (hzIpListen* pLS, SSL* pSSL, hzIpaddr ipa, uint32_t cliSock, uint32_t cliPort, uint32_t eventNo)
{
    //  Initialize a TCP client connection (hzIpConnex instance). This is called from within the select and the epoll server functions and in general, server
    //  programs based on HadronZoo would use one of the server functions and so have no need to call this function directly.
    //
    //  Arguments:  1)  Sock    The socket file descriptor
    //              2)  nEvent  The event number (usually Nth connection)
    //              3)  pLS     Pointer to the listening socket (hzIpListen instance)
    //              4)  pSSL    The SSL connection (if applicable)
    //              5)  cpIpa   The text representation of the client IP address
    //
    //  Returns:    E_ARGUMENT  If either the socket or the pointer to the listening socket is not supplied
    //              E_DUPLICATE If the connection has already got a socket
    //              E_OK        If the TCP connection is initialized
    _hzfunc("hzIpConnex::Initialize") ;
    if (m_nSock)    return hzerr(E_DUPLICATE, "Attempt to init connection to socket %d - already has socket %d", cliSock, m_nSock) ;
    if (!pLS)       return hzerr(E_ARGUMENT, "No listening socket class instance provided") ;
    m_nsAccepted = RealtimeNano() ;
    m_ClientIP = ipa ;
    m_nSock = cliSock ;
    m_nPort = cliPort ;
    m_nMsgno = eventNo ;
    m_OnIngress = pLS->m_OnIngress ;
    m_OnConnect = pLS->m_OnConnect ;
    m_appFn = pLS->m_appFn ;
    m_pSSL = pSSL ;
    m_pLog = pLS->GetLogger() ;
    Oxygen() ;
    m_bState = CLIENT_INITIALIZED ;
    if (pLS->Opflags() & HZ_LISTEN_HTTP)
        m_pEventHdl = (void*) new hzHttpEvent(m_Input, this) ;
    return E_OK ;
}
/*
hzEcode hzIpConnex::FinalizeSSL (void)  //hzIpListen* pLS, SSL* pSSL, hzIpaddr ipa, uint32_t cliSock, uint32_t cliPort, uint32_t eventNo)
{
}
*/
void    hzIpConnex::Terminate   (void)
{
    //  This is called within ServeEpollST or ServeEpollMT in the event of an epoll error being detected. The origin of the epoll error would be such
    //  as connection reset by peer, or a broken pipe.
    //
    //  The remedial action is to remove any remaining outgoing data as this has no chance of being delivered to the client. However the socket itself
    //  is not closed immeadiately. This is left open for a limited period so that it is not assigned to another connection.
    //
    //  Arguments:  None
    //  Returns:    None
    _hzfunc("hzIpConnex::Terminate") ;
    struct epoll_event  epEv ;      //  Epoll event for depricated connection
    hzXDate             now ;       //  Time now
    m_Input.Clear() ;
    m_nExpected = 0 ;
    if (m_Outgoing.Size())
        m_Outgoing.Clear() ;
    if (!m_nSock)
        m_pLog->Log("No socket - Has Terminate already been called?\n") ;
    else
    {
        if (epoll_ctl(s_pTheOneAndOnlyServer->s_epollSocket, EPOLL_CTL_DEL, m_nSock, &epEv) < 0)
            m_pLog->Log("EPOLL ERROR: Could not del client connection handler on sock %d/%d. Error=%s\n", m_nSock, m_nPort, strerror(errno)) ;
        if (close(m_nSock) < 0)
            m_pLog->Log("Could not close socket %d for event %d on IP %s. Errno=%s", m_nSock, m_nMsgno, *m_ClientIP, strerror(errno)) ;
        else
            m_pLog->Log("Socket %d closed\n", m_nSock) ;
        m_nSock = 0 ;
    }
    /*
    if (m_Track.Size())
    {
        now.SysDateTime() ;
        m_pLog->Out("SESSION TRACE: Terminated at %s\n[\n", now.Txt(FMT_TIME_USEC)) ;
        m_pLog->Out(m_Track) ;
        m_pLog->Out("TRACE END:\n]\n\n") ;
    }
    */
}
hzEcode hzPktQue::Push  (const hzChain& Z)
{
    //  Send a response to the client. This function should always be called by applications rather than writing to the client socket directly as this
    //  ensures proper multiplexing between clients.
    //
    //  Arguments:  1)  Z   Output chain. Response to be sent to the client.
    //
    //  Returns:    E_SENDFAIL  If the connection is in an error state
    //              E_OK        If supplied hzChain is accepted for the response,
    _hzfunc("hzPktQue::Push") ;
    hzChain::BlkIter    bi ;    //  Block iterator
    //uint32_t  sofar = 0 ;     //  Bytes so far
    hzEcode     rc ;            //  Return code
    //  if (m_eState == HZCONNEX_FAIL)
    //      return E_SENDFAIL ;
    //m_nSize = Z.Size() ;
    if (Z.Size())
    {
        for (bi = Z ; bi.Data() ; bi.Advance())
        {
            if (!m_pFinal)
                m_pFinal = m_pStart = _tcpbufAlloc() ;
            else
            {
                m_pFinal->next = _tcpbufAlloc() ;
                m_pFinal = m_pFinal->next ;
            }
            //  m_pFinal->m_msgId = m_nMsgno ;
            memcpy(m_pFinal->m_data, bi.Data(), bi.Size()) ;
            m_pFinal->m_size = bi.Size() ;
            m_nSize += bi.Size() ;
            //sofar += m_pFinal->m_size ;
            m_pFinal->m_seq = ++m_nSeq ;
        }
    }
    return E_OK ;
}
void    hzPktQue::Pull  (void)
{
    //  Remove the first block in the packet queue
    //
    //  Arguments:  None
    //  Returns:    None
    _hzfunc("hzPktQue::Pull") ;
    hzPacket*   pkt ;       //  Temp packet pointer
    pkt = m_pStart ;
    if (pkt)
    {
        m_nSize -= pkt->m_size ;
        m_pStart = m_pStart->next ;
        _tcpbufDeleteOne(pkt) ;
        if (!m_pStart)
        {
            m_pFinal = 0 ;
            m_nSize = 0 ;
        }
    }
}
void    hzPktQue::Clear (void)
{
    //  Remove all packets in packet queue
    //
    //  Arguments:  None
    //  Returns:    None
    _hzfunc("hzPktQue::Clear") ;
    if (m_pFinal)
    {
        m_pFinal->next = s_pTcpBuffer_freelist ;
        s_pTcpBuffer_freelist = m_pStart ;
    }
    m_pStart = m_pFinal = 0 ;
    m_nSize = 0 ;
}
hzEcode hzIpConnex::SendData    (const hzChain& Hdr, const hzChain& Body)
{
    //  Send a response to the client as a separate header and message body. This approach means a cost of two partial IP packets instead of one, but
    //  it saves having to concatenate the two chains which is a heavy copying operation.
    //
    //  Note that this function should always be called by applications rather than writing to the client socket directly as this ensures proper multiplexing
    //  between clients.
    //
    //  Arguments:  1)  Hdr     Header of outgoing response
    //              2)  Body    Body of outgoing response
    //
    //  Returns:    E_OK if hzChain is accepted for the response,
    //              False otherwise.
    _hzfunc("hzIpConnex::SendData(1)") ;
    struct epoll_event  epEventNew ;        //  Epoll event for new connections
    if (!Hdr.Size() && !Body.Size())
    {
        m_pLog->Log("No data queued for output\n") ;
        return E_NODATA ;
    }
    if (Hdr.Size() && Body.Size())
    {
        m_Outgoing.Push(Hdr) ;
        m_Outgoing.Push(Body) ;
    }
    m_nTotalOut = Hdr.Size() + Body.Size() ;
    m_nsSendBeg = RealtimeNano() ;
    epEventNew.data.fd = m_nSock ;
    epEventNew.events = EPOLLIN | EPOLLOUT | EPOLLET ;
    if (epoll_ctl(s_pTheOneAndOnlyServer->s_epollSocket, EPOLL_CTL_MOD, m_nSock, &epEventNew) < 0)
    {
        m_pLog->Log("EPOLL ERROR: Could not add client connection write handler on sock %d/%d. Error=%s\n", m_nSock, m_nPort, strerror(errno)) ;
        if (close(m_nSock) < 0)
            m_pLog->Log("NOTE: Could not close socket %d after epoll error. errno=%d\n", m_nSock, errno) ;
    }
    return E_OK ;
}
hzEcode hzIpConnex::SendData    (const hzChain& Z)
{
    //  Send a response to the client. This function should always be called by applications rather than writing to the client socket directly as this
    //  ensures proper multiplexing between clients.
    //
    //  Arguments:  1)  Z   Output chain. Response to be sent to the client.
    //
    //  Returns:    E_SENDFAIL  If the connection is in an error state
    //              E_OK        If supplied hzChain is accepted for the response,
    _hzfunc("hzIpConnex::SendData(2)") ;
    struct epoll_event  epEventNew ;        //  Epoll event for new connections
    m_nTotalOut = Z.Size() ;
    if (!Z.Size())
    {
        m_pLog->Log("No data queued for output\n") ;
        return E_NODATA ;
    }
    if (Z.Size())
        m_Outgoing.Push(Z) ;
    m_nsSendBeg = RealtimeNano() ;
    epEventNew.data.fd = m_nSock ;
    epEventNew.events = EPOLLIN | EPOLLOUT | EPOLLET ;
    if (epoll_ctl(s_pTheOneAndOnlyServer->s_epollSocket, EPOLL_CTL_MOD, m_nSock, &epEventNew) < 0)
    {
        m_pLog->Log("EPOLL ERROR: Could not add client connection write handler on sock %d/%d. Error=%s\n", m_nSock, m_nPort, strerror(errno)) ;
        if (close(m_nSock) < 0)
            m_pLog->Log("NOTE: Could not close socket %d after epoll error. errno=%d\n", m_nSock, errno) ;
    }
    return E_OK ;
}
void    hzIpConnex::SendKill    (void)
{
    //  Sent by connection handler in response to illegal message. The status is set to CLIENT_BAD but the socket is still made ready for epoll write events. As soon as the socket
    //  is available for writes however, the connection is terminated and deleted. The client recieves no response to their request.
    //
    //  Arguments:  None
    //  Returns:    None
    _hzfunc("hzIpConnex::SendKill") ;
    struct epoll_event  epEventDead ;       //  Epoll event for depricated connection
    m_Input.Clear() ;
    m_nExpected = 0 ;
    if (m_Outgoing.Size())
        m_Outgoing.Clear() ;
    m_nsSendBeg = RealtimeNano() ;
    m_bState |= CLIENT_BAD ;
    epEventDead.data.fd = m_nSock ;
    epEventDead.events = EPOLLOUT | EPOLLET ;
    if (epoll_ctl(s_pTheOneAndOnlyServer->s_epollSocket, EPOLL_CTL_MOD, m_nSock, &epEventDead) < 0)
        m_pLog->Log("%s: EPOLL ERROR: Could not add client connection write handler on sock %d/%d. Error=%s\n", *_fn, m_nSock, m_nPort, strerror(errno)) ;
    else
        m_pLog->Log("%s: BAD CLIENT: Connection killed by app. Sock %d/%d\n", *_fn, m_nSock, m_nPort) ;
    //  if (close(m_nSock) < 0)
    //      m_Track.Printf("%s: NOTE: Could not close socket %d after epoll error. errno=%d\n", *_fn, m_nSock, errno) ;
}
int32_t hzIpConnex::Recv    (hzPacket& tbuf)
{
    //  Category:   Internet Server
    //
    //  Receive a packet of data from the client socket and append it to the input chain.
    //
    //  Arguments:  1)  tbuf        The recepticle (buffer) for reading the socket.
    //
    //  Returns:    Total number of bytes read
    //
    //  Scope:      This function should not be called by the application!
    _hzfunc("hzIpConnex::Recv") ;
    int32_t     nRecv ;     //  Bytes recieved
    if (!m_Input.Size())
        m_nsRecvBeg = RealtimeNano() ;
    if (m_pSSL)
        nRecv = SSL_read(m_pSSL, tbuf.m_data, HZ_MAXPACKET) ;
    else
        nRecv = recv(m_nSock, tbuf.m_data, HZ_MAXPACKET, 0) ;
    if (!nRecv)
        m_bState |= CLIENT_TERMINATION ;
    if (nRecv > 0)
    {
        m_nTotalIn += nRecv ;
        m_bState |= CLIENT_READING ;
        m_Input.Append(tbuf.m_data, nRecv) ;
    }
    m_nsRecvEnd = RealtimeNano() ;
    return nRecv ;
}
int32_t hzIpConnex::_xmit   (hzPacket& tbuf)
{
    //  Category:   Internet Server
    //
    //  In the epoll loop, this function is called for each client connection with a write event on the socket (the socket becomes available for writing). If no output is pending,
    //  this function does nothing and returns. This approach serves to multiplex output so that no socket is left hanging during large downloads.
    //
    //  The term 'output' depends on the perspective. There are three possible scenarios as follows:-
    //
    //      - The connection is that of a direct client of this service, with no proxy. In this case the connection m_Input member is populated by read events the output will be the response to an earlier request.
    //
    //      - The connection is that of a client of this service, but has a proxy connection to another service. In this case the output will be the request from the client, to be
    //      passed to the other service.
    //
    //      - The connection is the proxy connection to another service. In this case the input from epoll read events, is actually the response from the other service - which must
    //      be passed to the client.
    //
    //  Note that in the epoll loop, read events always add data to the m_Input member of the applicable connection
    //  In the direct case, this function will write data to the client socket until either the socket write buffer is full, or the outgoing message is exhausted. In the has 
    //
    //  Note: This function is private to the hzIpConnex class so cannot be called directly by an application. It is called only when _isxmit() returns true to
    //  indicate there is outgoing data to transmit.
    //
    //  Arguments:  1)  tbuf    The hzPacket supplied by the caller to contain data to write to the socket.
    //
    //  Returns:    -1      If the write operation failed
    //              >0      If the write operation is delayed (errno either a EAGAIN or WOULDBLOCK)
    //              0       If the write operation completely wrote the outgoing message
    _hzfunc("hzIpConnex::_xmit") ;
    hzPacket*   pTB ;       //  Packet buffer block
    int32_t     nSend ;     //  Bytes to write
    int32_t     nSent ;     //  Bytes actually written
    /*
    **  Write out outgoing packets until exhauted or we get an EWOULDBLOCK
    */
    for (; m_Outgoing.Size() ;)
    {
        pTB = m_Outgoing.Peek() ;
        nSend = pTB->m_size - m_nGlitch ;
        if (m_pSSL)
            nSent = SSL_write(m_pSSL, pTB->m_data + m_nGlitch, nSend) ;
        else
            nSent = write(m_nSock, pTB->m_data + m_nGlitch, nSend) ;
        if (nSent < 0)
        {
            if (errno == EAGAIN || errno == EWOULDBLOCK)
            {
                m_pLog->Log("WBLOCK: Client %d IP %s Event %d Sock %d/%d TOTAL OUT %d (pkt %d posn %d)\n",
                    m_nMsgno, *m_ClientIP, pTB->m_msgId, m_nSock, m_nPort, m_nTotalOut, pTB->m_size, pTB->m_seq) ;
                return errno ;
            }
            m_pLog->Log("FAILED: Client %d IP %s Event %d Sock %d/%d TOTAL OUT %d (pkt %d posn %d) Error=%s\n",
                m_nMsgno, *m_ClientIP, pTB->m_msgId, m_nSock, m_nPort, m_nTotalOut, pTB->m_size, pTB->m_seq, strerror(errno)) ;
            return -1 ;
        }
        if (nSent != nSend)
        {
            m_nGlitch += nSent ;
            m_pLog->Log("GLITCH: Client %d IP %s Event %d Sock %d/%d Sent only %d of %d: TOTAL OUT %d (glitch %d pkt %d posn %d)\n",
                m_nMsgno, *m_ClientIP, pTB->m_msgId, m_nSock, m_nPort, nSent, nSend, m_nTotalOut, m_nGlitch, pTB->m_size, pTB->m_seq) ;
            return 1 ;
        }
        m_nGlitch = 0 ;
        if (!pTB->next)
        {
            m_nsSendEnd = RealtimeNano() ;
            m_pLog->Log("COMPLETE 1: Client Event %d Msg %d Sock %d Port %d Bytes (%d/%d) Times: recv %lu proc %lu xmit %lu so total (%lu ns)\n",
                m_nMsgno,
                pTB->m_seq,
                m_nSock,
                m_nPort,
                SizeIn(),
                TotalOut(),
                TimeRecv(),
                TimeProc(),
                TimeXmit(),
                TimeRecv() + TimeProc() + TimeXmit()) ;
            m_Outgoing.Pull() ;
            break ;
        }
        if (pTB->next->m_msgId != pTB->m_msgId)
        {
            m_nsSendEnd = RealtimeNano() ;
            m_pLog->Log("COMPLETE 2: Client Event %d Msg %d Sock %d Port %d Bytes IN (%d/%d) Times: recv %lu proc %lu xmit %lu so total (%lu ns)\n",
                m_nMsgno,
                pTB->m_seq,
                m_nSock,
                m_nPort,
                SizeIn(),
                TotalOut(),
                TimeRecv(),
                TimeProc(),
                TimeXmit(),
                TimeRecv() + TimeProc() + TimeXmit()) ;
        }
        m_Outgoing.Pull() ;
    }
    return 0 ;
}
/*
**  hzIpServer members
*/
hzEcode hzIpServer::AddPortTCP  (   hzTcpCode   (*OnIngress)(hzChain&, hzIpConnex*),
                                    hzTcpCode   (*OnConnect)(hzIpConnex*),
                                    hzTcpCode   (*OnDisconn)(hzIpConnex*),
                                    uint32_t    nTimeout,
                                    uint32_t    nPort,
                                    uint32_t    nMaxClients,
                                    bool        bSecure )
{
    //  Category:   Internet Server
    //
    //  This function adds a listening socket operating on the assigned port, to a server. The client connections (hzIpConnex instances) call an
    //  application specific function each time data comes in on the port. Nothing is assumed about the incoming data so it is nessesary that the
    //  application specific function determines if the data forms a complete message or not and acts accordingly.
    //
    //  Arguments:  1)  OnIngress       Application Specific 'Do Something' function to be called each time data has come in from a connected client.
    //              2)  OnConnect       Application Specific 'Server Hello' function - only required if the protocol used dictates that the server
    //                                  speaks first.
    //              3)  nTimeout        Timeout for connections to the port in seconds
    //              4)  nPort           Listening port number
    //              5)  nMaxClients     Max number of connections on the port (before server blocks listening socket)
    //              6)  bSecure         Flag to indicate SSL
    //
    //  Returns:    E_ARGUMENT  If the 'OnIngress' function pointer is null.
    //              E_RANGE     If the port number is out of range
    //              E_INITDUP   If there is already a listening socket on the supplied port number
    //              E_MEMORY    If the is insufficient memory to allocate the hzIpListen for the port
    //              E_INITFIAL  If the hzIpListen could not be initialized
    //              E_OK        If the operation was successful.
    _hzfunc("hzIpServer::AddPort") ;
    if (!OnIngress)
    {
        hzerr(E_ARGUMENT, "Each listening port must have a 'OnIngress' handler") ;
        return E_ARGUMENT ;
    }
    if (nPort < 1 || nPort > 65535)
    {
        hzerr(E_RANGE, "Port %d out of range", nPort) ;
        return E_RANGE ;
    }
    //  Check that we are not already using port
    hzList<hzIpListen*>::Iter   I ;     //  Listening socket iterator
    hzIpListen* pLS ;           //  Listening socket
    uint32_t    opflags ;       //  Operational flags
    for (I = m_LS ; I.Valid() ; I++)
    {
        pLS = I.Element() ;
        if (pLS->GetPort() == nPort)
        {
            hzerr(E_INITDUP, "Socket already initialized and listening on port %d\n", pLS->GetPort()) ;
            return E_INITDUP ;
        }
    }
    if (!(pLS = new hzIpListen(m_pLog)))
    {
        hzerr(E_MEMORY, "SERIOUS ERROR - Could not allocate an LS for server") ;
        return E_MEMORY ;
    }
    opflags = HZ_LISTEN_TCP ;
    if (bSecure)
        opflags |= HZ_LISTEN_SECURE ;
    if (pLS->Init(this, OnIngress, OnConnect, OnDisconn, nTimeout, nPort, nMaxClients, opflags) != E_OK)
    {
        hzerr(E_INITFAIL, "SERIOUS ERROR - Could not initialize an LS for server") ;
        return E_INITFAIL ;
    }
    if (m_LS.Add(pLS) != E_OK)
    {
        hzerr(E_INITFAIL, "SERIOUS ERROR - Could not insert an LS for server") ;
        return E_INITFAIL ;
    }
    return E_OK ;
}
hzEcode hzIpServer::AddPortHTTP (   hzTcpCode   (*OnHttpReq)(hzHttpEvent*),
                                    uint32_t    nTimeout,
                                    uint32_t    nPort,
                                    uint32_t    nMaxClients,
                                    bool        bSecure )
{
    //  Category:   Internet Server
    //
    //  Purpose:    Add a listening port, customized for HTTP, to the singleton hzIpServer instance. This will allow the application
    //              to act as a web server.
    //
    //  Arguments:  1)  OnHttpReq       Application Specific 'Do Something' function called each time a complete HTTP request has come from a connected client. 
    //              2)  nTimeout        Timeout in seconds.
    //              3)  nPort           Port number
    //              4)  nMaxClients     Max number of connections on the port (before server blocks listening socket)
    //              5)  bSecure         Flag to indicate SSL
    //
    //  Returns:    E_ARGUMENT  If the 'HandleHttpEvent' function pointer is null.
    //              E_RANGE     If the port number is out of range
    //              E_INITDUP   If there is already a listening socket on the supplied port number
    //              E_MEMORY    If the is insufficient memory to allocate the hzIpListen for the port
    //              E_INITFIAL  If the hzIpListen could not be initialized
    //              E_OK        If the operation was successful.
    _hzfunc("hzIpServer::AddPortHTTP") ;
    if (!OnHttpReq)
    {
        hzerr(E_ARGUMENT, "Each listening HTTP port must have a 'HandleHttpEvent' handler") ;
        return E_ARGUMENT ;
    }
    if (nPort < 1 || nPort > 65535)
    {
        hzerr(E_RANGE, "Port %d out of range", nPort) ;
        return E_INITDUP ;
    }
    hzList<hzIpListen*>::Iter   I ;     //  Listening socket iterator
    hzIpListen* pLS ;           //  Listening socket
    uint32_t    opflags ;       //  Operational flags
    //  Check that we are not already using port
    for (I = m_LS ; I.Valid() ; I++)
    {
        pLS = I.Element() ;
        if (pLS->GetPort() == nPort)
        {
            hzerr(E_INITDUP, "Socket already initialized and listening on port %d\n", pLS->GetPort()) ;
            return E_INITDUP ;
        }
    }
    if (!(pLS = new hzIpListen(m_pLog)))
    {
        hzerr(E_MEMORY, "SERIOUS ERROR - Could not allocate an LS for server") ;
        return E_MEMORY ;
    }
    opflags = HZ_LISTEN_HTTP ;
    if (bSecure)
        opflags |= HZ_LISTEN_SECURE ;
    if (pLS->Init(this, HandleHttpMsg, 0, 0, nTimeout, nPort, nMaxClients, opflags) != E_OK)
    {
        hzerr(E_INITFAIL, "SERIOUS ERROR - Could not initialize an LS for server") ;
        return E_INITFAIL ;
    }
    pLS->m_appFn = (void*) OnHttpReq ;
    if (m_LS.Add(pLS) != E_OK)
    {
        hzerr(E_INITFAIL, "SERIOUS ERROR - Could not insert an LS for server") ;
        return E_INITFAIL ;
    }
    return E_OK ;
}
hzEcode hzIpServer::AddPortUDP  (   hzTcpCode   (*OnIngress)(hzChain&, hzIpConnex*),
                                    hzTcpCode   (*OnConnect)(hzIpConnex*),
                                    hzTcpCode   (*OnDisconn)(hzIpConnex*),
                                    uint32_t    nTimeout,
                                    uint32_t    nPort,
                                    uint32_t    nMaxClients,
                                    bool        bSecure )
{
    //  Category:   Internet Server
    //
    //  This function adds a listening socket operating on the assigned port, to a server. The client connections (hzIpConnex instances) call an
    //  application specific function each time data comes in on the port. Nothing is assumed about the incoming data so it is nessesary that the
    //  application specific function determines if the data forms a complete message or not and acts accordingly.
    //
    //  Arguments:  1)  OnIngress       Application Specific 'Do Something' function to be called each time data has come in from a connected client.
    //              2)  OnConnect       Application Specific 'Server Hello' function - only required if the protocol used dictates that the server
    //                                  speaks first.
    //              3)  nTimeout        Timeout for connections to the port in seconds
    //              4)  nPort           Listening port number
    //              5)  nMaxClients     Max number of connections on the port (before server blocks listening socket)
    //              6)  bSecure         Flag to indicate SSL
    //
    //  Returns:    E_ARGUMENT  If the 'OnIngress' function pointer is null.
    //              E_RANGE     If the port number is out of range
    //              E_INITDUP   If there is already a listening socket on the supplied port number
    //              E_MEMORY    If the is insufficient memory to allocate the hzIpListen for the port
    //              E_INITFIAL  If the hzIpListen could not be initialized
    //              E_OK        If the operation was successful.
    _hzfunc("hzIpServer::AddUdpPort") ;
    if (!OnIngress)
    {
        hzerr(E_ARGUMENT, "Each listening port must have a 'OnIngress' handler") ;
        return E_ARGUMENT ;
    }
    if (nPort < 1 || nPort > 65535)
    {
        hzerr(E_RANGE, "Port %d out of range", nPort) ;
        return E_RANGE ;
    }
    //  Check that we are not already using port
    hzList<hzIpListen*>::Iter   I ;     //  Listening socket iterator
    hzIpListen* pLS ;           //  Listening socket
    uint32_t    opflags ;       //  Operational flags
    for (I = m_LS ; I.Valid() ; I++)
    {
        pLS = I.Element() ;
        if (pLS->GetPort() == nPort)
        {
            hzerr(E_INITDUP, "Socket already initialized and listening on port %d\n", pLS->GetPort()) ;
            return E_INITDUP ;
        }
    }
    if (!(pLS = new hzIpListen(m_pLog)))
        hzexit(E_MEMORY, "SERIOUS ERROR - Could not allocate an LS for server") ;
    opflags = HZ_LISTEN_UDP ;
    if (bSecure)
        opflags |= HZ_LISTEN_SECURE ;
    if (pLS->Init(this, OnIngress, OnConnect, OnDisconn, nTimeout, nPort, nMaxClients, opflags) != E_OK)
    {
        hzerr(E_INITFAIL, "SERIOUS ERROR - Could not initialize an LS for server") ;
        return E_INITFAIL ;
    }
    if (m_LS.Add(pLS) != E_OK)
    {
        hzerr(E_INITFAIL, "SERIOUS ERROR - Could not insert an LS for server") ;
        return E_INITFAIL ;
    }
    return E_OK ;
}
hzEcode hzIpServer::Activate    (void)
{
    //  Purpose:    Activates all listening sockets added to the server by AddPort(). This must only be called once.
    //
    //  Arguments:  None
    //
    //  Returns:    E_INITDUP   If this has already been called
    //              E_INITFAIL  If one or more listening sockets fail to activate
    //              E_OK        If the operation was successful.
    _hzfunc("hzIpServer::Activate") ;
    if (m_bActive)
    {
        hzerr(E_INITDUP, "All ports already active") ;
        return E_INITDUP ;
    }
    hzList<hzIpListen*>::Iter   I ;     //  Listening socket iterator
    hzIpListen* pLS ;           //  Listening socket
    for (I = m_LS ; I.Valid() ; I++)
    {
        pLS = I.Element() ;
        //  Activate port
        if (pLS->Activate() != E_OK)
        {
            hzerr(E_INITFAIL, "Could not activate listening port (sock %d)", pLS->GetPort()) ;
            return E_INITFAIL ;
        }
    }
    m_bActive = true ;
    return E_OK ;
}
/*
**  Slow loris defence
*/
#if 0
struct  _loris
{
    //  Recording of IP address of client and port client used to connect
    hzIpaddr    ip ;        //  IP address of rogue client
    uint32_t    port ;      //  Port rogue client connected on
    _loris  ()  { port = 0 ; }
    bool    operator<   (const _loris op) const
    {
        if (port < op.port) return true ;
        if (port > op.port) return false ;
        if (ip > op.ip)     return false ;
        return true ;
    }
    bool    operator>   (const _loris op) const
    {
        if (port > op.port) return true ;
        if (port < op.port) return false ;
        if (ip < op.ip)     return false ;
        return true ;
    }
} ;
hzMapS<_loris,uint32_t> s_black ;   //  Monitor for slow lorris attacks
static  bool    _ipcheck    (const char* addr, uint32_t port)
{
    _loris      I ;     //  Client IP address and connected port
    uint32_t    t ;     //  Time of last connect
    uint32_t    x ;     //  Time before next connection allowed
    I.ip = addr ;
    I.port = port ;
    t = time(0) ;
    if (s_black.Exists(I))
    {
        x = s_black[I] ;
        x -= 15 ;
        return x <= t ? true : false ;
    }
    s_black.Insert(I,t) ;
    return false ;
}
#endif
/*
**  SECTION X:  Epoll Method (Single Threaded)
*/
//#define MAXEVENTS 100
hzIpConnex* currCC[2000] ;      //  Connected clients
hzEcode hzIpServer::_nonblock   (uint32_t nSock)
{
    //  Make the socket non-blocking (to be handled by epoll)
    _hzfunc("hzIpServer::_nonblock") ;
    int32_t     flags ;     //  Returned from fcntl()
    flags = fcntl(nSock, F_GETFL, 0) ;
    if (flags == -1)
    {
        m_pLog->Log("Loop %u: NOTE: Could not make client socket %d non blocking (case 1)\n", m_nLoop, nSock) ;
        if (close(nSock) < 0)
            m_pLog->Log("Loop %u: NOTE: Could not close socket %d after non_block_fail(1) errno=%d\n", m_nLoop, nSock, errno) ;
        return E_NOSOCKET ;
    }
    flags |= O_NONBLOCK ;
    if (fcntl(nSock, F_SETFL, flags) == -1)
    {
        m_pLog->Log("Loop %u: NOTE: Could not make client socket %d non blocking (case 2)\n", m_nLoop, nSock) ;
        if (close(nSock) < 0)
            m_pLog->Log("Loop %u: NOTE: Could not close socket %d after non_block_fail(2) errno=%d\n", m_nLoop, nSock, errno) ;
        return E_NOSOCKET ;
    }
    return E_OK ;
}
hzEcode hzIpServer::ProxyTo (hzIpConnex* pConn, uint32_t nPort)
{
    //  Set up a proxy connection to be associated with the supplied connection. The proxy connection is then placed in the epoll event queue.
    //
    //      - The supplied connection must be valid
    //      - If the other connection does not exist it will be created
    //
    //  Please note that connections have three possible types as follows:-
    //
    //      Direct:     The hzIpConnex is that of a direct client of this service, with no proxy. In this case the hzIpConnex m_Input member receives the client requests (populated
    //                  by epoll read events). The m_Outgoing member is populated by processing the request (i.e. it is the response). The contents of m_Outgoing are written to the
    //                  client socket by the _xmit function.
    //
    //      Has Proxy:  The hzIpConnex is that of a client of this service, but has a proxy hzIpConnex to another service. Once the proxy hzIpConnex is established, the epoll reads
    //                  are redirected. Instead of populating the m_Input member of the client hzIpConnex instance, client requests arriving via epoll reads populate the m_Outgoing
    //                  member of the proxy hzIpConnex instance. The _xmit function as applied to the proxy hzIpConnex instance, then transmits the request to the other service.
    //
    //      Is Proxy:   The hzIpConnex is the proxy, which is the mirror image of the has-proxy case. The input (the response from the other service), is redirected to populate the
    //                  m_Outgoing member of the client hzIpConnex. The _xmit function as applied to the client hzIpConnex instance, then transmits the response to the client.
    //
    //  This function creates the proxy hzIpConnex instance, forms the association with the client hzIpConnex, and copies across any data received so far.
    _hzfunc("hzIpServer::ProxyTo") ;
    SOCKADDRIN          svrAddr ;       //  Server address
    HOSTENT*            pHost ;         //  External service host
    struct epoll_event  epEventNew ;    //  Epoll event for new connections
    struct timeval      tv ;            //  Timeout structure
    hzIpConnex*         pExt ;          //  External connection
    hzXDate             now ;           //  Time now (used to log connections)
    hzString            hostname ;      //  External service hostname
    int32_t             nSock ;         //  External connection socket
    hzEcode             rc ;            //  Return code
    if (!pConn)
        return hzerr(E_ARGUMENT, "No connection") ;
    if (pConn->m_pProxy)
        pExt = pConn->m_pProxy ;
    else
    {
        //  Get the hostname
        hostname = "127.0.0.1" ;
        if (!(pHost = gethostbyname(*hostname)))
        {
            threadLog("Unknown Host [%s]\n", *hostname) ;
            return E_HOSTFAIL ;
        }
        //  Create the socket
        memset(&svrAddr, 0, sizeof(svrAddr)) ;
        svrAddr.sin_family = AF_INET ;
        memcpy(&svrAddr.sin_addr, pHost->h_addr, pHost->h_length) ;
        svrAddr.sin_port = htons(nPort) ;
        //  Connect
        if ((nSock = socket(AF_INET, SOCK_STREAM, 0)) < 0)
            return hzerr(E_NOSOCKET, "Could not create socket (returns %d, errno %d)", nSock, errno) ;
        //  Connect stage
        if (connect(nSock, (struct sockaddr *) &svrAddr, sizeof(svrAddr)) < 0)
            return hzerr(E_HOSTFAIL, "Could not connect to %s on port %d (errno=%d)", *hostname, nPort, errno) ;
        //  Set timeouts and non-blocking
        rc = _nonblock(nSock) ;
        if (rc != E_OK)
            return rc ;
        tv.tv_sec = 10 ;
        tv.tv_usec = 0 ;
        if (setsockopt(nSock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) < 0)
            return E_HOSTFAIL ;
        //  Add the external connection to the epoll loop
        epEventNew.data.fd = nSock ;
        epEventNew.events = EPOLLIN | EPOLLOUT ;    //| EPOLLET ;
        if (epoll_ctl(s_epollSocket, EPOLL_CTL_ADD, nSock, &epEventNew) < 0)
        {
            m_pLog->Log("Loop %u: EPOLL ERROR: Could not add client connection handler on sock %d/%d. Error=%s\n", m_nLoop, nSock, pConn->CliPort(), strerror(errno)) ;
            if (close(nSock) < 0)
                m_pLog->Log("NOTE: Could not close socket %d after memory allocation failure. errno=%d\n", nSock, errno) ;
            m_bShutdown = true ;
            return E_HOSTFAIL ;
        }
        //  Initialize and oxygenate the connection
        pExt = pConn->m_pProxy = new hzIpConnex(m_pLog) ;
        pExt->m_pProxy = pConn ;
        currCC[nSock] = pExt ;
        pExt->SetSocket(nSock) ;
        pExt->Oxygen() ;
        now.SysDateTime() ;
        m_pLog->Log("%s: Loop %u: NEW Proxy on socket %d/%d from %d:%d\n", now.Txt(FMT_TIME_USEC), m_nLoop, nSock, pExt->CliPort(), pConn->CliSocket(), pConn->CliPort()) ;
    }
    //  Now copy the data that has come into the original connection, to the output of the client connection
    pExt->SendData(pConn->InputZone()) ;
    pConn->InputZone().Clear() ;
    return E_OK ;
}
void    hzIpServer::Serve   (void)
{
    //  Category:   Internet Server
    //
    //  Epoll server in edge-triggered mode. In this mode, all client connections are non-blocking and notification that a socket has data available for reaading, is only given at
    //  the point the data arrives. Consequently, sockets must always be read until no more data is available.
    //
    //  Arguments:  None
    //  Returns:    None
    //
    //  Note:       Call only once for all listening sockets! This function will not return until the server is shut down
    _hzfunc("hzIpServer::Serve") ;
    //hzMapS    <hzIpaddr,hzIpConnex*>  udpClients ;    //  Map of UDP clients by IP address
    //hzMapS    <uint32_t,hzIpConnex*>  ConnInError ;   //  Map of TCP clients in error
    //hzMapS    <uint32_t,hzIpListen*>  Listen ;        //  Listening sockets map
    hzList  <hzIpListen*>::Iter     I ;             //  Listening sockets iterator
    //struct epoll_event    m_arEvents[MAXEVENTS] ;     //  Epoll event array
    struct epoll_event  epEventNew ;                //  Epoll event for new connections
    hzPacket        tbuf ;              //  Fixed buffer for single IP packet
    //SOCKADDR      cliAddr ;           //  Client address
    SOCKADDRIN      cliAddrIn ;         //  Client address
    socklen_t       cliLen ;            //  Client address length
    hzIpListen*     pLS ;               //  Listening socket
    hzIpConnex*     pCC ;               //  Connected client
    SSL*            pUDP_SSL ;          //  SSL session (if applicable)
    hzProcInfo      proc_data ;         //  Process data (Client socket & IP address, must be destructed by the called thread function)
    hzIpinfo*       ipi ;               //  IP address for testing against black/white lists
    hzChain         errMsg ;            //  For building error message for logs
    hzXDate         now ;               //  Time now (used to log connections)
    pthread_attr_t  tattr ;             //  Thread attribute (to support thread invokation)
    timeval         tv ;                //  Time limit for epoll call
    //uint64_t      nsThen ;            //  Before the wait
    //uint64_t      nsNow ;             //  After the wait
    hzIpaddr        ipa ;               //  IP address
    uint32_t        nCliSeq ;           //  Client connection id allocator
    //uint32_t      nLoop ;             //  Number of times round the epoll event loop
    uint32_t        nSlot ;             //  Connections iterator
    uint32_t        nEpollEvents ;      //  Result of the epoll call
    uint32_t        nError ;            //  Errno of the epoll call
    uint32_t        cSock ;             //  Client socket (from accept)
    uint32_t        cPort ;             //  Client socket (from accept)
    //uint32_t      prc ;               //  Return from pthread_create
    uint32_t        nBannedAttempts ;   //  Connection attempts by blocked IP address
    int32_t         flags ;             //  Flags to make socket non-blocking
    int32_t         nRecv ;             //  No bytes read from client socket after epoll (in one read)
    int32_t         nRecvTotal ;        //  No bytes read from client socket after epoll (in a series of reads)
    int32_t         sys_rc ;            //  Return from system library calls
    int32_t         sys_err ;           //  Derived error e.g. SSL_get_error()
    int32_t         xmitState ;         //  Return from _xmit() call
    //int32_t           nTries ;            //  Attempts to call SSL_accept()
    bool            bCloseSSL ;         //  Need to close underlying connection because of a non-recoverable SSL_accept failure
    hzTcpCode       trc ;               //  Return code
    char            ipbuf[44] ;         //  Client IP address textform buffer
    //  Pre-define thread attributes
    pthread_attr_init(&tattr) ;
    pthread_attr_setdetachstate(&tattr, PTHREAD_CREATE_DETACHED) ;
    //  Init connected client regime
    for (nSlot = 0 ; nSlot < 1024 ; nSlot++)
        currCC[nSlot] = 0 ;
    cliLen = sizeof(cliAddrIn) ;
    //  Create epoll socket and and listening sockets to the controller
    s_epollSocket = epoll_create1(0) ;
    if (s_epollSocket == -1)
        Fatal("Could not create epoll socket\n") ;
    m_pLog->Log("Epoll socket is %d\n", s_epollSocket) ;
    for (I = m_LS ; I.Valid() ; I++)
    {
        pLS = I.Element() ;
        epEventNew.data.fd = pLS->GetSocket() ;
        epEventNew.events = EPOLLIN ;
        if (epoll_ctl(s_epollSocket, EPOLL_CTL_ADD, pLS->GetSocket(), &epEventNew) < 0)
            Fatal("Could not add listening socket %d to the epoll controller\n", pLS->GetSocket()) ;
        m_pLog->Log("Added listening socket is %d\n", pLS->GetSocket()) ;
        m_mapLS.Insert(pLS->GetSocket(), pLS) ;
    }
    /*
    **  Main loop - waiting for events
    */
    m_nLoop = nCliSeq = nBannedAttempts = 0 ;
    for (;;)
    {
        //  Check for shutdown condition
        if (m_bShutdown)
        {
            //  Check for outstanding connections
            for (nSlot = 0 ; nSlot < 1024 ; nSlot++)
            {
                if (currCC[nSlot])
                    break ;
            }
            if (nSlot == 1024)
                break ;
        }
        //  Check for expired connections
        for (nSlot = 0 ; nSlot < ConnInError.Count() ; nSlot++)
        {
            pCC = ConnInError.GetObj(nSlot) ;
        }
        //  Wait for events
        //nsThen = RealtimeNano() ;
        nEpollEvents = epoll_wait(s_epollSocket, m_arEvents, MAXEVENTS, 60000) ;
        //nsNow = RealtimeNano() ;
        m_nLoop++ ;
        if (!nEpollEvents)
            m_pLog->Log("Loop %u No action\n", m_nLoop) ;
        //  else
        //      m_pLog->Log("Loop %u: Waited %lu nanoseconds for %d events\n", m_nLoop, nsNow - nsThen, nEpollEvents) ;
        for (nSlot = 0 ; nSlot < nEpollEvents ; nSlot++)
        {
            /*
            **  Check for hangups and other connection errors
            */
            if (m_arEvents[nSlot].events & EPOLLHUP)
            {
                //  Hangup signal
                cSock = m_arEvents[nSlot].data.fd ;
                pCC = currCC[cSock] ;
                nError = 0 ;
                if (!pCC)               { m_bShutdown = true ; m_pLog->Log("Loop %u slot %u: HANGUP CORRUPT Sock %d No connector\n", m_nLoop, nSlot, cSock) ; continue ; }
                if (!pCC->CliSocket())  { m_bShutdown = true ; m_pLog->Log("Loop %u slot %u: HANGUP CORRUPT Sock %d Connector defunct\n", m_nLoop, nSlot, cSock) ; continue ; }
                if (getsockopt(cSock, SOL_SOCKET, SO_ERROR, (void *)&nError, &cliLen) == 0)
                    m_pLog->Log("Loop %u slot %u: HANGUP on socket %d (%s)\n", m_nLoop, nSlot, cSock, strerror(nError)) ;
                else
                    m_pLog->Log("Loop %u slot %u: HANGUP on socket %d (no details)\n", m_nLoop, nSlot, cSock) ;
                //  Remove the connection
                pCC->Terminate() ;
                delete pCC ;
                currCC[cSock] = 0 ;
                continue ;
            }
            if (m_arEvents[nSlot].events & EPOLLERR)
            {
                //  An error has occured on this fd, or the socket is not ready for reading
                cSock = m_arEvents[nSlot].data.fd ;
                pCC = currCC[cSock] ;
                nError = 0 ;
                if (!pCC)               { m_bShutdown = true ; m_pLog->Log("Loop %u slot %u: EPOLLERR CORRUPT Sock %d No connector\n", m_nLoop, nSlot, cSock) ; continue ; }
                if (!pCC->CliSocket())  { m_bShutdown = true ; m_pLog->Log("Loop %u slot %u: EPOLLERR CORRUPT Sock %d Connector defunct\n", m_nLoop, nSlot, cSock) ; continue ; }
                if (getsockopt(cSock, SOL_SOCKET, SO_ERROR, (void *)&nError, &cliLen) == 0)
                    m_pLog->Log("Loop %u slot %u: EPOLLERR on socket %d (%s)\n", m_nLoop, nSlot, cSock, strerror(nError)) ;
                else
                    m_pLog->Log("Loop %u slot %u: EPOLLERR on socket %d (no details)\n", m_nLoop, nSlot, cSock) ;
                //  Remove the connection
                pCC->Terminate() ;
                delete pCC ;
                currCC[cSock] = 0 ;
                continue ;
            }
            /*
            **  Events on listening sockets
            */
            if (m_mapLS.Exists(m_arEvents[nSlot].data.fd))
            {
                //  We have a notification on the listening socket so either a new incoming connection or if the listening socket is UDP, an incoming UDP packet.
                pLS = m_mapLS[m_arEvents[nSlot].data.fd] ;
                pUDP_SSL = 0 ;
                if (pLS->UseUDP())
                {
                    //  Listening socket expects UDP clients
                    cSock = pLS->GetSocket() ;
                    flags = fcntl(pLS->GetSocket(), F_GETFL, 0) ;
                    if (flags == -1)
                    {
                        m_pLog->Log("UDP: Loop %u: NOTE: Could not make client socket %d non blocking (case 1)\n", m_nLoop, pLS->GetSocket()) ;
                        if (close(pLS->GetSocket()) < 0)
                            m_pLog->Log("Loop %u: NOTE: Could not close socket %d after non_block_fail(1) errno=%d\n", m_nLoop, pLS->GetSocket(), errno) ;
                        continue ;
                    }
                    flags |= O_NONBLOCK ;
                    if (fcntl(pLS->GetSocket(), F_SETFL, flags) == -1)
                    {
                        m_pLog->Log("UDP: Loop %u: NOTE: Could not make client socket %d non blocking (case 2)\n", m_nLoop, pLS->GetSocket()) ;
                        if (close(pLS->GetSocket()) < 0)
                            m_pLog->Log("Loop %u: NOTE: Could not close socket %d after non_block_fail(2) errno=%d\n", m_nLoop, pLS->GetSocket(), errno) ;
                        continue ;
                    }
                    if ((nRecv = recvfrom(pLS->GetSocket(), tbuf.m_data, HZ_MAXPACKET, 0, (SOCKADDR*) &cliAddrIn, &cliLen)) < 0)
                    {
                        m_pLog->Log("UDP: Loop %u: NOTE: Could not read from UDP client\n", m_nLoop) ;
                        continue ;
                    }
                    //  Get client IP address
                    ipa = (uint32_t) cliAddrIn.sin_addr.s_addr ;
                    cPort = ntohs(cliAddrIn.sin_port) ;
                    tbuf.m_data[nRecv] = 0 ;
                    pCC = udpClients[ipa] ;
                    if (!pCC)
                    {
                        pCC = new hzIpConnex(m_pLog) ;
                        udpClients.Insert(ipa, pCC) ;
                        //  Initialize and oxygenate the connection
                        pCC->Initialize(pLS, pUDP_SSL, ipa, cSock, cPort, nCliSeq++) ;
                        if (pCC->m_OnConnect)
                            pCC->m_OnConnect(pCC) ;
                    }
                    m_pLog->Out("Received UDP message %s from port %d internet address %s\n", tbuf.m_data, cPort, *ipa) ;
                    if (pCC->MsgReady())
                        trc = pCC->m_OnIngress(pCC->InputZone(), pCC) ;
                    else
                        trc = TCP_INCOMPLETE ;
                    switch (trc)
                    {
                    case TCP_TERMINATE:
                        if (pCC->_isxmit())
                        {
                            //  If outgoing data not complete, transmit some more.
                            xmitState = pCC->_xmit(tbuf) ;
                            if (xmitState < 0)
                            {
                                m_pLog->Log("UDP: Loop %u slot %u: TCP_TERMINATE: Write error - Sock %d/%d to be removed\n", m_nLoop, nSlot, cSock, pCC->CliPort()) ;
                                pCC->Terminate() ;
                                udpClients.Delete(ipa) ;
                                delete pCC ;
                                break ;
                            }
                            if (xmitState > 0)
                            {
                                //  We have EAGAIN or EWOULDBLOCK and so now we make epoll entry to look for write event (when ocket becomes writable)
                                m_pLog->Log("UDP: Loop %u slot %u: TCP_TERMINATE: Write delay - Sock %d /%d, looking for write event\n",
                                    m_nLoop, nSlot, cSock, pCC->CliPort()) ;
                                break ;
                            }
                        }
    
                        //  No more outgoing data so close
                        m_pLog->Log("UDP: Loop %u slot %u: TCP_TERMINATE: Normal termination, sock %d/%d to be removed\n", m_nLoop, nSlot, cSock, pCC->CliPort()) ;
                        pCC->Terminate() ;
                        udpClients.Delete(ipa) ;
                        delete pCC ;
                        break ;
                    case TCP_KEEPALIVE:
                        m_pLog->Log("UDP: Client %d SERVER - data processed, awaiting further messages\n", pCC->EventNo()) ;
                        pCC->Oxygen() ;
                        if (pCC->_isxmit())
                        {
                            xmitState = pCC->_xmit(tbuf) ;
                            if (xmitState < 0)
                            {
                                m_pLog->Log("UDP: Loop %u slot %u: TCP_KEEPALIVE: Write error, sock %d/%d to be removed\n", m_nLoop, nSlot, cSock, pCC->CliPort()) ;
                                pCC->Terminate() ;
                                udpClients.Delete(ipa) ;
                                delete pCC ;
                                break ;
                            }
                            if (xmitState > 0)
                            {
                                //  We have EAGAIN or EWOULDBLOCK and so now we make epoll entry to look for write event (when ocket becomes writable)
                                m_pLog->Log("UDP: Loop %u slot %u: TCP_KEEPALIVE: Write delay - Sock %d/%d retained\n", m_nLoop, nSlot, cSock, pCC->CliPort()) ;
                                break ;
                            }
                            //  We have xmitState of 0 which means the message was written in full to the socket
                            m_pLog->Log("UDP: Loop %u slot %u: TCP_KEEPALIVE: Write OK, sock %d/%d retained\n", m_nLoop, nSlot, cSock, pCC->CliPort()) ;
                        }
                        if (pCC->IsCliTerm())
                        {
                            //  No longer any incoming requests and as we have finished writing, close
                            m_pLog->Log("UDP: Loop %u loop %u: TCP_KEEPALIVE: Client done so sock %d/%d to be removed\n", m_nLoop, nSlot, cSock, pCC->CliPort()) ;
                            pCC->Terminate() ;
                            udpClients.Delete(ipa) ;
                            delete pCC ;
                            break ;
                        }
                        break ;
                    case TCP_INCOMPLETE:
                        m_pLog->Log("UDP: Client %d SERVER - data incomplete\n", pCC->EventNo()) ;
                        pCC->Oxygen() ;
                        break ;
                    case TCP_INVALID:       //  The message processor cannot dechipher the message. Kill the connection
                        if (m_pLog)
                            m_pLog->Log("UDP: Loop %u: Sock %d/%d INVALID TCP code\n", m_nLoop, cSock, pCC->CliPort()) ;
                        pCC->Terminate() ;
                        udpClients.Delete(ipa) ;
                        delete pCC ;
                        break ;
                    }
                    continue ;
                    //  End of UDP section
                }
                /*
                **  Handle new TCP client connections
                */
                //  Accept the client no matter what, then if there is a problem close the connection.
                cSock = accept(pLS->GetSocket(), (SOCKADDR*) &cliAddrIn, &cliLen) ;
                if (cSock < 0)
                {
                    m_pLog->Log("Loop %u: NOTE: Listening socket %d ignored client on port %d\n", m_nLoop, pLS->GetSocket(), pLS->GetPort()) ;
                    if (errno && errno != EAGAIN && errno != EWOULDBLOCK)
                    {
                        m_bShutdown = true ;
                        m_pLog->Log("Loop %u: SHUTDOWN: Listening socket %d failure: Port %d (errno=%d)\n", m_nLoop, pLS->GetSocket(), pLS->GetPort(), errno) ;
                    }
                    continue ;
                }
                //  Get client IP address and port
                inet_ntop(AF_INET, &cliAddrIn.sin_addr, ipbuf, 16) ;
                ipa = ipbuf ;
                cPort = ntohs(cliAddrIn.sin_port) ;
                //  Check if client IP is blocked. Note this does not work when connections are coming via a local proxy service as the IP address will always be 127.0.0.1
                if (_hzGlobal_StatusIP.Exists(ipa))
                {
                    ipi = &(_hzGlobal_StatusIP[ipa]) ;
                    if (!(ipi->m_bInfo & HZ_IPSTATUS_WHITE) || ipi->m_tWhite < time(0))
                    {
                        if (!(ipi->m_nSince%100))
                            m_pLog->Log("BLOCKED IP %s reaches %u attempts\n", *ipa, ipi->m_nSince) ;
                        if (close(cSock) < 0)
                            m_pLog->Log("ERROR: Could not close socket %d after blocked IP address detected. errno=%d\n", cSock, errno) ;
                        ipi->m_nSince++ ;
                        ipi->m_nTotal++ ;
                        nBannedAttempts++ ;
                        if (!(nBannedAttempts%10000))
                            m_pLog->Log("BLOCKED IP TOTAL reaches %u attempts\n", nBannedAttempts) ;
                        continue ;
                    }
                }
                if (_hzGlobal_Debug & HZ_DEBUG_SERVER)
                    m_pLog->Log("Loop %u: Accepted connection (1): socket %d/%d host %s port %d\n", m_nLoop, cSock, cPort, ipbuf, pLS->GetPort()) ;
                /*
                **  Allocate the connected client object
                */
                //  Check that slot in the connected client array (as indicated by the client socket), is actually free
                pCC = currCC[cSock] ;
                if (pCC)
                {
                    m_pLog->Log("Loop %u slot %u: CORRUPT: Existing client connection handler on sock %d/%d.\n", m_nLoop, nSlot, cSock, pCC->CliPort()) ;
                    if (close(cSock) < 0)
                        m_pLog->Log("NOTE: Could not close socket %d after memory allocation failure. errno=%d\n", cSock, errno) ;
                    m_bShutdown = true ;
                    continue ;
                }
                //  Allocate the connected client object
                pCC = new hzIpConnex(m_pLog) ;
                if (!pCC)
                {
                    m_pLog->Log("ERROR: No memory for client %s on socket %d. Closing conection\n", ipbuf, cSock) ;
                    if (close(cSock) < 0)
                        m_pLog->Log("NOTE: Could not close socket %d after memory allocation failure. errno=%d\n", cSock, errno) ;
                    m_bShutdown = true ;
                    continue ;
                }
                if (_nonblock(cSock) != E_OK)
                    continue ;
                if (pLS->UseSSL())
                {
                    //  Set timeout to 0.1 secs.
                    tv.tv_sec = 0 ;
                    tv.tv_usec = 100000 ;
                    if (setsockopt(cSock, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)) < 0)
                    {
                        m_pLog->Log("Loop %u: NOTE: Could not set send socket options\n", m_nLoop) ;
                        if (close(cSock) < 0)
                            m_pLog->Log("Loop %u: NOTE: Could not close socket %d after setop_send errno=%d\n", m_nLoop, cSock, errno) ;
                        continue ;
                    }
                    if (setsockopt(cSock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) < 0)
                    {
                        m_pLog->Log("Loop %u: NOTE: Could not set recv socket options\n", m_nLoop) ;
                        if (close(cSock) < 0)
                            m_pLog->Log("Loop %u: NOTE: Could not close socket %d after setop_recv errno=%d\n", cSock, errno) ;
                        continue ;
                    }
                    //  Create a new SSL session instance
                    m_pLog->Log("Loop %u: NOTE: SSL instance. Client %s:%d\n", m_nLoop, ipbuf, pLS->GetPort()) ;
                    pCC->m_pSSL = SSL_new(s_SSL_svrRegime->m_svrCTX) ;  //s_svrCTX) ;
                    if (!pCC->m_pSSL)
                    {
                        m_pLog->Log("Loop %u: NOTE: Failed to allocate an SSL instance on port %d\n", m_nLoop, pLS->GetPort()) ;
                        if (close(cSock) < 0)
                        {
                            m_pLog->Log("NOTE: Could not close socket %d after memory allocation failure. errno=%d\n", cSock, errno) ;
                            m_bShutdown = true ;
                        }
                        continue ;
                    }
                    m_pLog->Log("Loop %u: Allocated SSL instance on port %d\n", m_nLoop, pLS->GetPort()) ;
                    //  Set the SSL session socket
                    SSL_set_accept_state(pCC->m_pSSL) ;
                    sys_rc = SSL_set_fd(pCC->m_pSSL, cSock) ;
                    errMsg.Clear() ;
                    errMsg.Printf("Loop %u: SSL_set_fd %d - ", m_nLoop, sys_rc) ;
                    bCloseSSL = false ;
                    sys_rc = SSL_accept(pCC->m_pSSL) ;
                    if (sys_rc >= 1)
                    {
                        pCC->m_bAcceptSSL = false ;
                        errMsg << "SSL_accept OK\n" ;
                    }
                    else
                    {
                        pCC->m_bAcceptSSL = true ;
                        sys_err = SSL_get_error(pCC->m_pSSL, sys_rc) ;
                        errMsg.Printf("Failed to accept SSL client port %d sock %d ret %d err=%d ", pLS->GetPort(), cSock, sys_rc, sys_err) ;
                        switch  (sys_err)
                        {
                        case SSL_ERROR_ZERO_RETURN:             errMsg << "SSL_ERROR_ZERO_RETURN\n" ;   bCloseSSL = true ;  break ;
                        case SSL_ERROR_WANT_READ:               errMsg << "SSL_ERROR_WANT_READ\n" ;             break ;
                        case SSL_ERROR_WANT_WRITE:              errMsg << "SSL_ERROR_WANT_WRITE\n" ;            break ;
                        case SSL_ERROR_WANT_CONNECT:            errMsg << "SSL_ERROR_WANT_CONNECT\n" ;          break ;
                        case SSL_ERROR_WANT_ACCEPT:             errMsg << "SSL_ERROR_WANT_ACCEPT\n" ;           break ;
                        case SSL_ERROR_WANT_X509_LOOKUP:        errMsg << "SSL_ERROR_WANT_ACCEPT\n" ;           break ;
                        case SSL_ERROR_WANT_ASYNC:              errMsg << "SSL_ERROR_WANT_ASYNC\n" ;            break ;
                        case SSL_ERROR_WANT_ASYNC_JOB:          errMsg << "SSL_ERROR_WANT_ASYNC_JOB\n" ;        break ;
                        case SSL_ERROR_WANT_CLIENT_HELLO_CB:    errMsg << "SSL_ERROR_WANT_CLIENT_HELLO_CB\n" ;  break ;
                        case SSL_ERROR_SYSCALL:                 errMsg << "SSL_ERROR_SYSCALL\n" ;   bCloseSSL = true ;  break ;
                        case SSL_ERROR_SSL:                     errMsg << "SSL_ERROR_SSL\n" ;       bCloseSSL = true ;  break ;
                        }
                    }
                    m_pLog->Log(errMsg) ;
                    if (bCloseSSL)
                    {
                        SSL_free(pCC->m_pSSL) ;
                        if (close(cSock) < 0)
                        {
                            m_pLog->Log("Loop %u: NOTE: Could not close socket %d after SSL no_alloc errno=%d\n", m_nLoop, cSock, errno) ;
                            m_bShutdown = true ;
                        }
                        continue ;
                    }
                }
                //  Deal with the case where we have too many connections or we are shutting down.
                if (m_bShutdown || pLS->GetCurConnections() >= pLS->GetMaxConnections())
                {
                    m_pLog->Log("Loop %u: NOTE: System too busy: Curr %d Max %d stat %d\n", m_nLoop, pLS->GetCurConnections(), m_nMaxClients, m_bShutdown ? 1 : 0) ;
                    if (close(cSock) < 0)
                        m_pLog->Log("Loop %u: NOTE: Could not close socket %d after sys_too_busy errno=%d\n", m_nLoop, cSock, errno) ;
                    continue ;
                }
                //  Client is already connected. Do we allow further connections?
                /*
                if (_ipcheck(ipbuf, pLS->GetPort()))
                {
                    m_pLog->Log("Loop %u: NOTE: Lorris Attack from IP %s port %d\n", ipbuf, pLS->GetPort()) ;
                    if (close(cSock) < 0)
                        m_pLog->Log("Loop %u: NOTE: Could not close socket %d after lorris_attack errno=%d\n", m_nLoop, cSock, errno) ;
                    continue ;
                }
                */
                //  All is well so set socket options
                tv.tv_sec = 20 ;
                tv.tv_usec = 0 ;
                if (setsockopt(cSock, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)) < 0)
                {
                    m_pLog->Log("Loop %u: NOTE: Could not set send socket options\n", m_nLoop) ;
                    if (close(cSock) < 0)
                        m_pLog->Log("Loop %u: NOTE: Could not close socket %d after setop_send errno=%d\n", m_nLoop, cSock, errno) ;
                    continue ;
                }
                if (setsockopt(cSock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) < 0)
                {
                    m_pLog->Log("Loop %u: NOTE: Could not set recv socket options\n", m_nLoop) ;
                    if (close(cSock) < 0)
                        m_pLog->Log("Loop %u: NOTE: Could not close socket %d after setop_recv errno=%d\n", cSock, errno) ;
                    continue ;
                }
                //  Add new client socket to the epoll control
                epEventNew.data.fd = cSock ;
                epEventNew.events = EPOLLIN ;
                if (epoll_ctl(s_epollSocket, EPOLL_CTL_ADD, cSock, &epEventNew) < 0)
                {
                    m_pLog->Log("Loop %u slot %u: EPOLL ERROR: Could not add client connection handler on sock %d/%d. Error=%s\n",
                        m_nLoop, nSlot, cSock, pCC->CliPort(), strerror(errno)) ;
                    if (close(cSock) < 0)
                        m_pLog->Log("NOTE: Could not close socket %d after memory allocation failure. errno=%d\n", cSock, errno) ;
                    m_bShutdown = true ;
                    delete pCC ;
                    continue ;
                }
                //  Initialize and oxygenate the connection
                currCC[cSock] = pCC ;
                pCC->Initialize(pLS, pCC->m_pSSL, ipa, cSock, cPort, nCliSeq++) ;
                if (pCC->m_OnConnect)
                    pCC->m_OnConnect(pCC) ;
                now.SysDateTime() ;
                m_pLog->Log("Loop %u slot %u: NEW Connection on socket %d/%d from %s:%u\n", m_nLoop, nSlot, cSock, pCC->CliPort(), ipbuf, pLS->GetPort()) ;
                continue ;
                //  End of listening socket handling.
            }
            /*
            **  WRITE Events
            */
            if (m_arEvents[nSlot].events & EPOLLOUT)
            {
                //  Subsequent to a call to hzIpConnex::SendData to place a response in the output queue and to modify the epoll entry for the connection so the socket is monitored
                //  for writability, we now have notification that the socket is writable.
                cSock = m_arEvents[nSlot].data.fd ;
                pCC = currCC[cSock] ;
                if (!pCC)
                {
                    m_pLog->Log("Loop %u slot %u: CORRUPT: Write event on socket %d/%d but no connector!\n", m_nLoop, nSlot, cSock, pCC->CliPort()) ;
                    m_bShutdown = true ;
                    continue ;
                }
                if (cSock != pCC->CliSocket())
                {
                    m_pLog->Log("Loop %u slot %u: CORRUPT: Write event on sock %d/%d but attached client object has socket of %d\n",
                        m_nLoop, nSlot, cSock, pCC->CliPort(), pCC->CliSocket()) ;
                    m_bShutdown = true ;
                    continue ;
                }
                if (pCC->IsCliBad())
                {
                    m_pLog->Log("Loop %u slot %u: BAD Client, connection on socket %d/%d to be removed\n", m_nLoop, nSlot, cSock, pCC->CliPort()) ;
                    pCC->Terminate() ;
                    delete pCC ;
                    currCC[cSock] = 0 ;
                    continue ;
                }
                if (pCC->m_bAcceptSSL)
                {
                    //  Not Complete - Call SSL_accept() again
                    //  SSL_set_accept_state(pCC->m_pSSL) ;
                    //  sys_rc = SSL_set_fd(pCC->m_pSSL, cSock) ;
                    //  m_pLog->Log("Subsequent SSL_set_fd returns %d\n", sys_rc) ;
                    sys_rc = SSL_accept(pCC->m_pSSL) ;
                    if (sys_rc > 0)
                    {
                        pCC->m_bAcceptSSL = false ;
                        m_pLog->Log("Subsequent SSL_accept OK %d\n", sys_rc) ;
                    }
                    else
                    {
                        sys_err = SSL_get_error(pCC->m_pSSL, sys_rc) ;
                        m_pLog->Log("Loop %u: NOTE: Subsequent Failed to accept SSL client port %d sock %d ret %d err=%d\n", m_nLoop, pLS->GetPort(), cSock, sys_rc, sys_err) ;
                        switch  (sys_err)
                        {
                        case SSL_ERROR_ZERO_RETURN:             bCloseSSL = true ;  m_pLog->Log("SSL_ERROR_ZERO_RETURN\n") ;            break ;
                        case SSL_ERROR_WANT_READ:               bCloseSSL = false ; m_pLog->Log("SSL_ERROR_WANT_READ\n") ;              break ;
                        case SSL_ERROR_WANT_WRITE:              bCloseSSL = false ; m_pLog->Log("SSL_ERROR_WANT_WRITE\n") ;             break ;
                        case SSL_ERROR_WANT_CONNECT:            bCloseSSL = false ; m_pLog->Log("SSL_ERROR_WANT_CONNECT\n") ;           break ;
                        case SSL_ERROR_WANT_ACCEPT:             bCloseSSL = false ; m_pLog->Log("SSL_ERROR_WANT_ACCEPT\n") ;            break ;
                        case SSL_ERROR_WANT_X509_LOOKUP:        bCloseSSL = false ; m_pLog->Log("SSL_ERROR_WANT_ACCEPT\n") ;            break ;
                        case SSL_ERROR_WANT_ASYNC:              bCloseSSL = false ; m_pLog->Log("SSL_ERROR_WANT_ASYNC\n") ;             break ;
                        case SSL_ERROR_WANT_ASYNC_JOB:          bCloseSSL = false ; m_pLog->Log("SSL_ERROR_WANT_ASYNC_JOB\n") ;         break ;
                        case SSL_ERROR_WANT_CLIENT_HELLO_CB:    bCloseSSL = false ; m_pLog->Log("SSL_ERROR_WANT_CLIENT_HELLO_CB\n") ;   break ;
                        case SSL_ERROR_SYSCALL:                 bCloseSSL = true ;  m_pLog->Log("SSL_ERROR_SYSCALL\n") ;                break ;
                        case SSL_ERROR_SSL:                     bCloseSSL = true ;  m_pLog->Log("SSL_ERROR_SSL\n") ;                    break ;
                        }
                        if (bCloseSSL)
                        {
                            //SSL_free(pCC->m_pSSL) ;
                            pCC->Terminate() ;
                            delete pCC ;
                            currCC[cSock] = 0 ;
                            //  if (close(cSock) < 0)
                            //  {
                            //      m_pLog->Log("Loop %u: NOTE: Could not close socket %d after SSL no_alloc errno=%d\n", m_nLoop, cSock, errno) ;
                            //      m_bShutdown = true ;
                            //  }
                        }
                        continue ;
                    }
                }
                if (!pCC->_isxmit() && _hzGlobal_Debug & HZ_DEBUG_SERVER)
                    m_pLog->Log("Loop %u slot %u: Write event with nothing socket %d/%d\n", m_nLoop, nSlot, cSock, pCC->CliPort()) ;
                if (pCC->_isxmit())
                {
                    //m_Track.Printf("Loop %u slot %u: Write event with output on socket %d/%d\n", m_nLoop, nSlot, cSock, pCC->CliPort()) ;
                    xmitState = pCC->_xmit(tbuf) ;
                    if (xmitState < 0)
                    {
                        m_pLog->Log("Loop %u slot %u: EPOLLOUT Write error, connection on socket %d/%d to be removed\n", m_nLoop, nSlot, cSock, pCC->CliPort()) ;
                        pCC->Terminate() ;
                        delete pCC ;
                        currCC[cSock] = 0 ;
                        continue ;
                    }
                }
            }
            /*
            **  READ Events
            */
            if (m_arEvents[nSlot].events & EPOLLIN)
            {
                //  At this point we have data on the socket to be read
                cSock = m_arEvents[nSlot].data.fd ;
                pCC = currCC[cSock] ;
                if (!pCC)
                {
                    m_pLog->Log("Loop %u: CORRUPT: Read event on socket %d/%d but no connector!\n", m_nLoop, cSock, pCC->CliPort()) ;
                    m_bShutdown = true ;
                    continue ;
                }
                if (cSock != pCC->CliSocket())
                {
                    m_pLog->Log("Loop %u slot %u: CORRUPT: Read event on sock %d/%d but attached client object has socket of %d\n",
                        m_nLoop, nSlot, cSock, pCC->CliPort(), pCC->CliSocket()) ;
                    m_bShutdown = true ;
                    continue ;
                }
                //  Test if client connection is using SSL and if so, is the SSL_accept() process complete?
                if (pCC->m_bAcceptSSL)
                {
                    //  Not Complete - Call SSL_accept() again
                    bCloseSSL = false ;
                    sys_rc = SSL_accept(pCC->m_pSSL) ;
                    if (sys_rc > 0)
                    {
                        //pCC->m_bAcceptSSL = false ;
                        m_pLog->Log("Subsequent W SSL_accept OK %d\n", sys_rc) ;
                    }
                    else
                    {
                        sys_err = SSL_get_error(pCC->m_pSSL, sys_rc) ;
                        m_pLog->Log("Loop %u: NOTE: Subsequent W Failed to accept SSL client port %d sock %d ret %d err=%d\n", m_nLoop, pLS->GetPort(), cSock, sys_rc, sys_err) ;
                        switch  (sys_err)
                        {
                        case SSL_ERROR_ZERO_RETURN:             bCloseSSL = true ;  m_pLog->Log("SSL_ERROR_ZERO_RETURN\n") ;            break ;
                        case SSL_ERROR_WANT_READ:               bCloseSSL = false ; m_pLog->Log("SSL_ERROR_WANT_READ\n") ;              break ;
                        case SSL_ERROR_WANT_WRITE:              bCloseSSL = false ; m_pLog->Log("SSL_ERROR_WANT_WRITE\n") ;             break ;
                        case SSL_ERROR_WANT_CONNECT:            bCloseSSL = false ; m_pLog->Log("SSL_ERROR_WANT_CONNECT\n") ;           break ;
                        case SSL_ERROR_WANT_ACCEPT:             bCloseSSL = false ; m_pLog->Log("SSL_ERROR_WANT_ACCEPT\n") ;            break ;
                        case SSL_ERROR_WANT_X509_LOOKUP:        bCloseSSL = false ; m_pLog->Log("SSL_ERROR_WANT_ACCEPT\n") ;            break ;
                        case SSL_ERROR_WANT_ASYNC:              bCloseSSL = false ; m_pLog->Log("SSL_ERROR_WANT_ASYNC\n") ;             break ;
                        case SSL_ERROR_WANT_ASYNC_JOB:          bCloseSSL = false ; m_pLog->Log("SSL_ERROR_WANT_ASYNC_JOB\n") ;         break ;
                        case SSL_ERROR_WANT_CLIENT_HELLO_CB:    bCloseSSL = false ; m_pLog->Log("SSL_ERROR_WANT_CLIENT_HELLO_CB\n") ;   break ;
                        case SSL_ERROR_SYSCALL:                 bCloseSSL = true ;  m_pLog->Log("SSL_ERROR_SYSCALL\n") ;                break ;
                        case SSL_ERROR_SSL:                     bCloseSSL = true ;  m_pLog->Log("SSL_ERROR_SSL\n") ;                    break ;
                        }
                        if (bCloseSSL)
                        {
                            pCC->Terminate() ;
                            delete pCC ;
                            currCC[cSock] = 0 ;
                        }
                        continue ;
                    }
                }
                //  Now because we are in edge-triggered mode, continue reading from the socket until we get -1
                for (nRecvTotal = 0 ;; nRecvTotal += nRecv)
                {
                    nRecv = pCC->Recv(tbuf) ;
                    if (nRecv <= 0)
                        break ;
                }
                if (pCC->m_bAcceptSSL)
                {
                    pCC->m_bAcceptSSL = false ;
                    if (!nRecvTotal)
                        continue ;
                }
                if (_hzGlobal_Debug & HZ_DEBUG_SERVER)
                {
                    if (!pCC->IsCliTerm())
                        m_pLog->Log("Loop %u slot %u: Client LIVE sock %d/%d. Recv %d Have %d\n", m_nLoop, nSlot, cSock, pCC->CliPort(), nRecvTotal, pCC->SizeIn()) ;
                    else
                        m_pLog->Log("Loop %u slot %u: Client DONE sock %d/%d. Revc %d Have %d\n", m_nLoop, nSlot, cSock, pCC->CliPort(), nRecvTotal, pCC->SizeIn()) ;
                }
                //  Data to be processed has come in
                if (!nRecvTotal)
                {
                    m_pLog->Log("Loop %u slot %u: Read event with zero data on socket %d - disconnecting\n", m_nLoop, nSlot, cSock) ;
                    if (!pCC->_isxmit())
                    {
                        //  Nothing has come in and nothing is due to go out so clear off the connection
                        m_pLog->Log("Loop %u slot %u: Terminating unused connection on socket %d\n", m_nLoop, nSlot, cSock) ;
                        pCC->Terminate() ;
                        delete pCC ;
                        currCC[cSock] = 0 ;
                    }
                }
                else
                {
                    if (pCC->m_pProxy)
                    {
                    threadLog("Sending back response from proxy\n") ;
                        pCC->m_pProxy->SendData(pCC->InputZone()) ;
                        pCC->InputZone().Clear() ;
                        continue ;
                    }
                    if (pCC->MsgReady())
                        trc = pCC->m_OnIngress(pCC->InputZone(), pCC) ;
                    else
                    {
                        m_pLog->Log("Loop %u slot %u: NOTE: Client message not yet complete on socket %d/%d\n", m_nLoop, nSlot, cSock, pCC->CliPort()) ;
                        trc = TCP_INCOMPLETE ;
                    }
                    switch (trc)
                    {
                    case TCP_TERMINATE: //  Directive is to close after outgoing message is complete
                        if (pCC->_isxmit())
                        {
                            //  If outgoing data not complete, transmit some more.
                            xmitState = pCC->_xmit(tbuf) ;
                            if (xmitState < 0)
                            {
                                m_pLog->Log("Loop %u slot %u: TCP_TERMINATE: Write error - Sock %d/%d doomed\n", m_nLoop, nSlot, cSock, pCC->CliPort()) ;
                                pCC->Terminate() ;
                                delete pCC ;
                                currCC[cSock] = 0 ;
                                break ;
                            }
                        }
                        if (!pCC->_isxmit())
                        {
                            //  No longer any outgoing data so close
                            m_pLog->Log("Loop %u slot %u: TCP_TERMINATE: Normal termination, sock %d/%d to be removed\n", m_nLoop, nSlot, cSock, pCC->CliPort()) ;
                            pCC->Terminate() ;
                            delete pCC ;
                            currCC[cSock] = 0 ;
                            break ;
                        }
                        m_pLog->Log("Loop %u slot %u: TCP_TERMINATE: Write delay - Sock %d/%d\n", m_nLoop, nSlot, cSock, pCC->CliPort()) ;
                        break ;
                    case TCP_KEEPALIVE: //  Directive is to keep open after outgoing message is complete
                        if (pCC->_isxmit())
                        {
                            xmitState = pCC->_xmit(tbuf) ;
                            if (xmitState < 0)
                            {
                                m_pLog->Log("Loop %u slot %u: TCP_KEEPALIVE: Write error, sock %d/%d doomed\n", m_nLoop, nSlot, cSock, pCC->CliPort()) ;
                                pCC->Terminate() ;
                                delete pCC ;
                                currCC[cSock] = 0 ;
                                break ;
                            }
                            if (xmitState > 0)
                            {
                                //  We have EAGAIN or EWOULDBLOCK and so now we make epoll entry to look for write event (when ocket becomes writable)
                                m_pLog->Log("Loop %u slot %u: TCP_KEEPALIVE: Write delay - Sock %d/%d\n", m_nLoop, nSlot, cSock, pCC->CliPort()) ;
                            }
                        }
                        if (pCC->IsCliTerm())
                        {
                            //  No longer any outgoing data so close
                            m_pLog->Log("Loop %u slot %u: TCP_KEEPALIVE: Client terminated so sock %d/%d to be removed\n", m_nLoop, nSlot, cSock, pCC->CliPort()) ;
                            pCC->Terminate() ;
                            delete pCC ;
                            currCC[cSock] = 0 ;
                            break ;
                        }
                        pCC->Oxygen() ;
                        break ;
                    case TCP_INCOMPLETE:    //  The message processor has indicated it is still waiting for more imput
                        pCC->Oxygen() ;
                        break ;
                    case TCP_INVALID:       //  The message processor cannot dechipher the message. Kill the connection
                        m_pLog->Log("Loop %u: Sock %d/%d INVALID TCP code\n", m_nLoop, cSock, pCC->CliPort()) ;
                        pCC->Terminate() ;
                        delete pCC ;
                        currCC[cSock] = 0 ;
                        break ;
                    }
                }
            }
        }
    }
    threadLog("SHUTDOWN COMPLETE\n") ;
}
/*
**  SECTION X:  Epoll Method (Multi-threaded)
*/
static  hzDiode s_queRequests ;         //  Lock free que for passing clients from the input thread to the request processor thread
static  hzDiode s_queResponses ;        //  Lock free que for passing clients from the request processor thread to output thread
static  pthread_mutex_t s_request_mutex = PTHREAD_MUTEX_INITIALIZER ;   //  Request mutex for server threads
static  pthread_cond_t  s_request_cond = PTHREAD_COND_INITIALIZER ;     //  Request condition for server threads
static  pthread_mutex_t s_response_mutex = PTHREAD_MUTEX_INITIALIZER ;  //  Response mutex for server threads
static  pthread_cond_t  s_response_cond = PTHREAD_COND_INITIALIZER ;    //  Response condition for server threads
static  uint32_t    s_nReqThreads ;         //  Number of request server threads
void    hzIpServer::ServeRequests   (void)
{
    //  Calculate number of request server thread as this will serve as the divisor
    //
    //  Arguments:  None
    //  Returns:    None
    _hzfunc("hzIpServer::ServeRequests") ;
    hzPacket    tbuf ;      //  Fixed buffer for single IP packet
    hzIpConnex* pCC ;       //  Connected client
    hzLogger*   pLog ;      //  Separate logger
    hzTcpCode   rc ;        //  Return code from event handler
    pLog = GetThreadLogger() ;
    pthread_mutex_lock(&s_request_mutex);
    s_nReqThreads++ ;
    pthread_mutex_unlock(&s_request_mutex);
    for (; !m_bShutdown ;)
    {
        pthread_mutex_lock(&s_request_mutex);
        pthread_cond_wait(&s_request_cond, &s_request_mutex);
        for (;;)
        {
            pCC = (hzIpConnex*) s_queRequests.Pull() ;
            if (!pCC)
                break ;
            //  Handle the request
            rc = pCC->m_OnIngress(pCC->InputZone(), pCC) ;
            switch  (rc)
            {
            case TCP_TERMINATE:     pCC->Hypoxia() ;
                                    s_queResponses.Push(pCC) ;
                                    pthread_cond_signal(&s_response_cond) ;
                                    break ;
            case TCP_KEEPALIVE:     pCC->Oxygen() ;
                                    s_queResponses.Push(pCC) ;
                                    pthread_cond_signal(&s_response_cond) ;
                                    break ;
            case TCP_INCOMPLETE:    pCC->Oxygen() ;
                                    pLog->Log("Client %d (sock %d): Data incomplete\n", pCC->EventNo(), pCC->CliSocket()) ;
                                    break ;
            }
        }
        pthread_mutex_unlock(&s_request_mutex);
    }
}
void    hzIpServer::ServeResponses  (void)
{
    _hzfunc("hzIpServer::ServeResponses") ;
    hzList<hzIpConnex*>     lc ;    //  List of connections with outgoing matter remaining
    hzList<hzIpConnex*>::Iter   ic ;    //  List iterator of connections with outgoing matter remaining
    hzPacket        tbuf ;              //  Fixed buffer for single IP packet
    hzIpConnex* pCC ;               //  Connected client
    for (; !m_bShutdown ;)
    {
        pthread_mutex_lock(&s_response_mutex);
        pthread_cond_wait(&s_response_cond, &s_response_mutex);
        for (;;)
        {
            pCC = (hzIpConnex*) s_queResponses.Pull() ;
            if (pCC)
                lc.Add(pCC) ;
            if (!lc.Count())
                break ;
            for (ic = lc ; ic.Valid() ;)
            {
                pCC = ic.Element() ;
                //  Write out
                if (pCC->_isxmit())
                {
                    pCC->_xmit(tbuf) ;
                    if (!pCC->_isxmit())
                    {
                        ic.Delete() ;
                        continue ;
                    }
                }
                ic++ ;
            }
        }
        pthread_mutex_unlock(&s_response_mutex);
    }
}
void    hzIpServer::ServeX  (void)
{
    //  Category:   Internet Server
    //
    //  Act as server based on the epoll method. This function is strickly for use in multi threaded servers. In the epoll method, the epoll socket
    //  (the one which activates the polling) is allocated and used to set up the controlling array of sockets. This array includes listening sockets
    //  as well as the sockets of connected clients.
    //
    //  Arguments:  None
    //
    //  Returns:    None
    //
    //  Note:       Call only once for all listening sockets! This function will not return until the server is shut down
    _hzfunc("hzIpServer::ServeX") ;
    //  Here we stay in a loop consiting of three stages:-
    //  1)  Setup all array of all active entities for the epoll system call
    //  2)  Call epoll to wait for something to come in.
    //  3)  Go through all active entries to see which one recieved a message
    hzMapS<uint32_t,hzIpListen*>    ls ;    //  Listening sockets map
    hzMapS<uint32_t,hzIpConnex*>    allCC ; //  Connected clients
    hzMapS<uint32_t,hzIpConnex*>    duff ;  //  Failed clients
    hzList<hzIpListen*>::Iter       I ;     //  Listening sockets iterator
    //struct epoll_event    m_arEvents[100] ;       //  Array of epoll events
    struct epoll_event  epEventNew ;        //  Epoll event for new connections
    //struct epoll_event    epEventDead ;       //  Epoll event for dead connections
    hzPacket        tbuf ;                  //  Fixed buffer for single IP packet
    SOCKADDR        cliAddr ;               //  Client address
    SOCKADDRIN      cliAddrIn ;             //  Client address
    socklen_t       cliLen ;                //  Client address length
    hzIpListen*     pLS ;                   //  Listening socket
    hzIpConnex*     pCC ;                       //  Connected client
    //hzProcInfo*       pPD ;                   //  Process data (Client socket & IP address, must be destructed by the called thread function)
    SSL*            pSSL ;                  //  SSL session (if applicable)
    //X509*         client_cert ;           //  Client certificate
    //char*         clicert_str ;           //  Client certificate string
    //const char*       errstr ;                //  Either 'hangup' or 'error'
    timeval         tv ;                    //  Time limit for epoll call
    pthread_attr_t  tattr ;                 //  Thread attribute (to support thread invokation)
    //pthread_t     tid ;                   //  Needed for multi-threading
    //uint64_t      then ;                  //  Before the wait
    //uint64_t      now ;                   //  After the wait
    uint64_t        nsThen ;                //  Before the wait
    uint64_t        nsNow ;                 //  After the wait
    hzIpaddr        ipa ;                   //  IP address
    uint32_t        nBannedAttempts ;       //  Total connection attempts by banned IP addresses
    uint32_t        nCliSeq = 1 ;           //  Client connection id allocator
    uint32_t        nX ;                    //  Dead/failed connections counter
    //uint32_t      nLoop ;                 //  Number of times round the epoll event loop
    uint32_t        nSlot ;                 //  Connections iterator
    uint32_t        nError ;                //  Errno of the epoll call
    uint32_t        cSock ;                 //  Client socket (from accept)
    uint32_t        cPort ;                 //  Client socket (from accept)
    int32_t         aSock ;                 //  Client socket (validated)
    //int32_t           epollSocket ;           //  The epoll 'master' socket
    int32_t         flags ;                 //  Flags to mak socket non-blocking
    int32_t         nC ;                    //  Connections iterator
    int32_t         nRecv ;                 //  No bytes read from client socket after epoll
    int32_t         nEpollEvents ;          //  Result of the epoll call
    int32_t         sys_rc ;                //  Return from system library calls
    hzEcode         err ;                   //  Returns by called functions
    char            ipbuf[44] ;             //  Client IP address textform buffer
    //ls.SetDefaultObj(0) ;
    //allCC.SetDefaultObj(0) ;
    //  Pre-define thread attributes
    pthread_attr_init(&tattr) ;
    pthread_attr_setdetachstate(&tattr, PTHREAD_CREATE_DETACHED) ;
    //  Create epoll socket and and listening sockets to the controller
    s_epollSocket = epoll_create1(0) ;
    if (s_epollSocket == -1)
        Fatal("Could not create epoll socket\n") ;
    m_pLog->Log("Epoll socket is %d\n", s_epollSocket) ;
    for (I = m_LS ; I.Valid() ; I++)
    {
        pLS = I.Element() ;
        epEventNew.data.fd = pLS->GetSocket() ;
        epEventNew.events = EPOLLIN ;
        if (epoll_ctl(s_epollSocket, EPOLL_CTL_ADD, pLS->GetSocket(), &epEventNew) < 0)
            Fatal("Could not add listening socket %d to the epoll controller\n", pLS->GetSocket()) ;
        m_pLog->Log("Added listening socket is %d\n", pLS->GetSocket()) ;
        ls.Insert(pLS->GetSocket(), pLS) ;
    }
    //  Main loop - waiting for events
    m_nLoop = nCliSeq = nBannedAttempts = 0 ;
    for (;;)
    {
        nsThen = RealtimeNano() ;
        nEpollEvents = epoll_wait(s_epollSocket, m_arEvents, MAXEVENTS, 30000) ;
        nsNow = RealtimeNano() ;
        for (nC = 0 ; nC < nEpollEvents ; nC++)
        {
            if (_hzGlobal_Debug & HZ_DEBUG_SERVER)
            {
                if (nSlot)
                    m_pLog->Log("Loop %u slot %u: Event on socket %d\n", m_nLoop, nSlot, m_arEvents[nSlot].data.fd) ;
                else
                    m_pLog->Log("Loop %u: Waited %lu nanoseconds for %d events: 1st sock %u\n", m_nLoop, nsNow - nsThen, nEpollEvents, m_arEvents[0].data.fd) ;
            }
            //  Hangup?
            if (m_arEvents[nSlot].events & EPOLLHUP)
            {
                //  Hangup signal
                cSock = m_arEvents[nSlot].data.fd ;
                pCC = currCC[cSock] ;
                nError = 0 ;
                if (!pCC)               { m_bShutdown = true ; m_pLog->Log("Loop %u slot %u: HANGUP CORRUPT Sock %d No connector\n", m_nLoop, nSlot, cSock) ; continue ; }
                if (!pCC->CliSocket())  { m_bShutdown = true ; m_pLog->Log("Loop %u slot %u: HANGUP CORRUPT Sock %d Connector defunct\n", m_nLoop, nSlot, cSock) ; continue ; }
                if (getsockopt(cSock, SOL_SOCKET, SO_ERROR, (void *)&nError, &cliLen) == 0)
                    m_pLog->Log("Loop %u slot %u: HANGUP on socket %d (%s)\n", m_nLoop, nSlot, cSock, strerror(nError)) ;
                else
                    m_pLog->Log("Loop %u slot %u: HANGUP on socket %d (no details)\n", m_nLoop, nSlot, cSock) ;
            }
            //  General connection error?
            if (m_arEvents[nSlot].events & EPOLLERR)
            {
                //  An error has occured on this fd, or the socket is not ready for reading
                cSock = m_arEvents[nSlot].data.fd ;
                pCC = currCC[cSock] ;
                nError = 0 ;
                if (!pCC)               { m_bShutdown = true ; m_pLog->Log("Loop %u slot %u: EPOLLERR CORRUPT Sock %d No connector\n", m_nLoop, nSlot, cSock) ; continue ; }
                if (!pCC->CliSocket())  { m_bShutdown = true ; m_pLog->Log("Loop %u slot %u: EPOLLERR CORRUPT Sock %d Connector defunct\n", m_nLoop, nSlot, cSock) ; continue ; }
                if (getsockopt(cSock, SOL_SOCKET, SO_ERROR, (void *)&nError, &cliLen) == 0)
                    m_pLog->Log("Loop %u slot %u: EPOLLERR on socket %d (%s)\n", m_nLoop, nSlot, cSock, strerror(nError)) ;
                else
                    m_pLog->Log("Loop %u slot %u: EPOLLERR on socket %d (no details)\n", m_nLoop, nSlot, cSock) ;
            }
            //  Listening socket event?
            if (ls.Exists(m_arEvents[nC].data.fd))
            {
                //  We have a notification on the listening socket, which means one or more incoming connections.
                //  Firstly accept the client no matter what. If there is a problem we will imeadiately close the connection.
                pLS = ls[m_arEvents[nC].data.fd] ;
                pSSL = 0 ;
                cliLen = sizeof(SOCKADDRIN) ;
                if ((aSock = accept(pLS->GetSocket(), &cliAddr, &cliLen)) < 0)
                {
                    m_pLog->Log("Listening socket %d will not accept client on port %d\n", aSock, pLS->GetPort()) ;
                    continue ;
                }
                //  Make the incoming socket non-blocking and add it to the list of fds to monitor.
                cSock = aSock ;
                flags = fcntl(cSock, F_GETFL, 0) ;
                if (flags == -1)
                    Fatal("Could not make client socket %d non blocking (case 1)\n", cSock) ;
                flags |= O_NONBLOCK ;
                if (fcntl(cSock, F_SETFL, flags) == -1)
                    Fatal("Could not make client socket %d non blocking (case 2)\n", cSock) ;
                //  Add client socker to the epoll control
                epEventNew.data.fd = cSock ;
                epEventNew.events = EPOLLIN | EPOLLET ;
                if (epoll_ctl(s_epollSocket, EPOLL_CTL_ADD, cSock, &epEventNew) < 0)
                    Fatal("Could not add client socket %d to control\n", cSock) ;
                //  Get the IP address
                inet_ntop(AF_INET, &cliAddrIn.sin_addr, ipbuf, 16) ;
                ipa = ipbuf ;
                //  Deal with the case where we have too many connections or we are shutting down.
                if (m_bShutdown || (pLS->GetCurConnections() >= pLS->GetMaxConnections()))
                {
                    m_pLog->Log("NOTE: System too busy: Curr %d Max %d stat %d\n", m_nCurrentClients, m_nMaxClients, m_bShutdown ? 1 : 0) ;
                    if (close(cSock) < 0)
                        m_pLog->Log("NOTE: Could not close socket %d after sys_too_busy\n", cSock) ;
                    continue ;
                }
                //  Client is already connected. Do we allow further connections?
                /*
                if (_ipcheck(ipbuf, pLS->GetPort()))
                {
                    m_pLog->Log("Lorris Attack from IP %s port %d\n", ipbuf, pLS->GetPort()) ;
                    if (close(cSock) < 0)
                        m_pLog->Log("NOTE: Could not close socket %d after lorris_attack\n", cSock) ;
                    continue ;
                }
                */
                //  All is well so set socket options
                tv.tv_sec = 1 ;
                tv.tv_usec = 0 ;
                if (setsockopt(cSock, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)) < 0)
                    { m_pLog->Log("Could not set send socket options\n") ; continue ; }
                if (setsockopt(cSock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) < 0)
                    { m_pLog->Log("Could not set recv socket options\n") ; continue ; }
                //  Do we have SSL on top of connection?
                if (pLS->UseSSL())
                {
                    //  Create a new SSL session instance
                    //pSSL = SSL_new(s_svrCTX) ;
                    pSSL = SSL_new(s_SSL_svrRegime->m_svrCTX) ;
                    if (!pSSL)
                        { m_pLog->Log("Failed to allocate an SSL instance on port %d\n", pLS->GetPort()) ; continue ; }
                    m_pLog->Log("Allocated SSL instance on port %d\n", pLS->GetPort()) ;
                    //  Set the SSL session socket
                    SSL_set_fd(pSSL, cSock) ;
                    sys_rc = SSL_accept(pSSL) ;
                    if (sys_rc <= 0)
                    {
                        m_pLog->Log("NOTE: Failed to accept SSL client on port %d socket %d (error=%d,%d)\n",
                            pLS->GetPort(), cSock, sys_rc, SSL_get_error(pSSL, sys_rc)) ;
                        SSL_free(pSSL) ;
                        if (close(cSock) < 0)
                            m_pLog->Log("NOTE: Could not close socket %d after SSL fail_accept\n", cSock) ;
                        m_pLog->Log("Closed socket\n") ;
                        continue ;
                    }
                    m_pLog->Log("SSL connection using %s\n", SSL_get_cipher(pSSL)) ;
                    /*
                    if (verify_callback)
                    {
                        //  Get the client's certificate (optional)
                        client_cert = SSL_get_peer_certificate(pSSL);
                        if (client_cert != NULL)
                        {
                            clicert_str = X509_NAME_oneline(X509_get_subject_name(client_cert), 0, 0);
                            if (!clicert_str)
                                { m_pLog->Log("No Client Certificate found\n") ; continue ; }
                            m_pLog->Log("Subject of Client Cert: %s\n", clicert_str) ;
                            free(clicert_str) ;
                            clicert_str = X509_NAME_oneline(X509_get_issuer_name(client_cert), 0, 0);
                            if (!clicert_str)
                                { m_pLog->Log("No Client Cert Issuer found\n") ; continue ; }
                            m_pLog->Log("Issuer of Client Cert: %s\n", clicert_str) ;
                            free(clicert_str) ;
                            X509_free(client_cert);
                        }
                        else
                            printf("The SSL client does not have certificate.\n");
                    }
                    */
                }
                //  If the listening socket is for a session handler, deal with this here
#if 0
                if (pLS->m_OnSession)
                {
                    //  Call the session handler in it's own thread
                    pPD = new hzProcInfo(pSSL, cSock, ipa) ;
                    if (pthread_create(&tid, &tattr, pLS->m_OnSession, pPD) == 0)
                        m_pLog->Log("Thread %u accepted connection from %s on socket %d\n", tid, ipbuf, cSock) ;
                    else
                    {
                        m_pLog->Log("NOTE: Could not create thread for client %s on socket %d. Closing conection\n", ipbuf, cSock) ;
                        if (close(cSock) < 0)
                            m_pLog->Log("NOTE: Could not close socket %d after fail_thread_create\n", cSock) ;
                        delete pPD ;
                    }
                    continue ;
                }
#endif
                /*
                **  Allocate the connected client object
                */
                pCC = allCC[cSock] ;
                if (!pCC)
                {
                    pCC = new hzIpConnex(m_pLog) ;
                    if (!pCC)
                        hzexit(E_MEMORY, "No memory for new client connection\n") ;
                    allCC.Insert(cSock, pCC) ;
                }
                m_pLog->Log("New client connection on socket %d\n", cSock) ;
                //  Initialize and oxygenate the connection
                pCC->Initialize(pLS, pSSL, ipa, cSock, cPort, nCliSeq++) ;
                if (pCC->m_OnConnect)
                {
                    if (_hzGlobal_Debug & HZ_DEBUG_SERVER)
                        m_pLog->Log("Client %d (sock %d): Issuing server hello\n", pCC->EventNo(), pCC->CliSocket()) ;
                    pCC->m_OnConnect(pCC) ;
                }
                continue ;
            }
            //  Deal with connected clients here.
            if (m_arEvents[nC].events & EPOLLOUT)
            {
                //  Should not get this as we are not asking for these notifications
                cSock = m_arEvents[nC].data.fd ;
                m_pLog->Log("WHAT??? write event on socket %d\n", cSock) ;
                continue ;
            }
            if (m_arEvents[nC].events & EPOLLIN)
            {
                //  At this point we have date on the socket to be read, at least in theory
                cSock = m_arEvents[nC].data.fd ;
                pCC = allCC[cSock] ;
                if (!pCC)
                {
                    m_pLog->Log("WHAT??? read event on socket %d but no connector!\n", cSock) ;
                    continue ;
                }
                if (pCC->IsCliTerm())
                    m_pLog->Log("NOTE: Client %d (sock %d) has a read event after sending 0 byte packet\n", pCC->EventNo(), pCC->CliSocket()) ;
                //  Now because we are in edge-triggered mode, continue reading from the socket until we get -1
                //  m_pLog->Log("Client %d (sock %d): Got %d bytes\n", pCC->EventNo(), pCC->CliSocket(), nRecv) ;
                for (;;)
                {
                    nRecv = pCC->Recv(tbuf) ;
                    m_pLog->Log("Client %d (sock %d): Got %d bytes\n", pCC->EventNo(), pCC->CliSocket(), nRecv) ;
                    if (nRecv <= 0)
                        break ;
                }
                if (!pCC->SizeIn())
                {
                    m_pLog->Log("Client %d (sock %d): Final read - NO DATA\n", pCC->EventNo(), pCC->CliSocket()) ;
                    if (nsNow > pCC->Expires())
                    {
                        m_pLog->Log("Client %d (sock %d): Final read - NO DATA deleted\n", pCC->EventNo(), pCC->CliSocket()) ;
                        pCC->Terminate() ;
                    }
                }
                else
                {
                    //  Data to be processed has come in
                    //  if (_hzGlobal_Debug & HZ_DEBUG_SERVER)
                    //  {
                    //      m_pLog->Log("Client %d (sock %d): Triggered with %d bytes\n[\n", pCC->EventNo(), pCC->CliSocket(), pCC->SizeIn()) ;
                    //      m_pLog->Out(pCC->InputZone()) ;
                    //      m_pLog->Out("]\n") ;
                    //  }
                    s_queRequests.Push(pCC) ;
                    pthread_cond_signal(&s_request_cond) ;
                }
            }
        }
        //  Kill off any inactive and out of date keep-alive connections - but only if there is a delay between polls
        if ((nsNow - nsThen) > 100000000)
        {
            if (_hzGlobal_Debug & HZ_DEBUG_SERVER)
                m_pLog->Log("Purging ...\n") ;
            for (nX = 0 ; nX < allCC.Count() ; nX++)
            {
                pCC = allCC.GetObj(nX) ;
                if (!pCC)
                    continue ;
                if (!pCC->CliSocket())
                    continue ;
                if (pCC->_isxmit())
                {
                    err = pCC->_xmit(tbuf) ;
                    continue ;
                }
                /*
                if (pCC->State() == HZCONNEX_FAIL)
                {
                    if (now < pCC->Expires())
                        continue ;
                    
                    //pCC->StateDone() ;
                    pCC->SetState(*_fn, HZCONNEX_DONE) ;
                    if (m_pLog)
                        m_pLog->Log("Client (ev %d sock %d state %d) vgn %d: Failed, removed\n",
                            pCC->EventNo(), pCC->CliSocket(), pCC->State(), pCC->IsVirgin() ? 1 : 0) ;
                    pCC->Terminate() ;
                    continue ;
                }
                */
                //  No incoming data or connection expired?
                if (!pCC->SizeIn() && (nsNow > pCC->Expires()))
                {
                    //pCC->StateDone() ;
                    //pCC->SetState(*_fn, HZCONNEX_DONE) ;
                    //m_pLog->Log("Client (ev %d sock %d state %d) vgn %d: Inactive, removed\n",
                    //  pCC->EventNo(), pCC->CliSocket(), pCC->State(), pCC->IsVirgin() ? 1 : 0) ;
                    m_pLog->Log("Client (ev %d sock %d) vgn %d: Inactive, removed\n", pCC->EventNo(), pCC->CliSocket(), pCC->IsVirgin() ? 1 : 0) ;
                    pCC->Terminate() ;
                }
            }
        }
    }
    threadLog("Shutdown\n") ;
}