//
//  File:   hzIpServer.h
//
//  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.
//
#ifndef hzIpServer_h
#define hzIpServer_h
#include <unistd.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <openssl/ssl.h>
#include <sys/epoll.h>
#include "hzTmplList.h"
#include "hzTmplVect.h"
#include "hzTmplMapS.h"
#include "hzChain.h"
#include "hzIpaddr.h"
/*
**  Definitions
*/
enum    hzSvrStatus
{
    //  Category:   Internet
    //
    //  Server status
    SERVER_OFFLINE,         //  Server offline or otherwise not accepting connections
    SERVER_ONLINE,          //  Server operating normally
    SERVER_SHUTDOWN         //  Server continues to operate but won't accept new connections
} ;
enum    hzCliStatus
{
    //  Category:   Internet
    //
    //  Client status
    CLIENT_STATE_NONE   = 0,        //  Client is created in this state
    CLIENT_INITIALIZED  = 0x0001,   //  Client accepted but no data in as yet
    CLIENT_HELLO        = 0x0002,   //  Client has been sent a hello
    CLIENT_READING      = 0x0004,   //  Client can read
    CLIENT_READ_WHOLE   = 0x0008,   //  A whole message has been read (may be more to come)
    CLIENT_HANGUP       = 0x0010,   //  An epoll HANGUP condition has been detected
    CLIENT_TERMINATION  = 0x0020,   //  The client has sent a 0-byte formal termination packet
    CLIENT_WRITING      = 0x0040,   //  A response has been formulated and is in the process of being sent to the client
    CLIENT_WRITE_WHOLE  = 0x0080,   //  The response has been written to the clinet
    CLIENT_BAD          = 0x0100,   //  Server has deemed the client to be bad and will not send a response
    CLIENT_SSL_ACCEPTED = 0x0200    //  Client has an initialized and accepted SSL session
} ;
enum    hzTcpCode
{
    //  Category:   Internet
    //
    //  TCP session return codes to direct if session is to be terminated or kept alive
    TCP_TERMINATE,          //  Terminate connection
    TCP_KEEPALIVE,          //  Keep connection alive for next transmission even if current transmission has completed
    TCP_INCOMPLETE,         //  Keep connection alive as transmission is incomplete
    TCP_INVALID             //  Message processing failed for syntax/protocol reasons. Connection to terminate
} ;
#define HZMAX_SERVNAMLEN    64      //  Max domain name?
#define HZMAX_IPADDRLEN     16      //  IPV-4 Address length
/*
**  The ListeningSocket class
*/
#define HZ_LISTEN_SECURE    0x01    //  Connections under the listening socket must use SSL
#define HZ_LISTEN_INTERNET  0x02    //  Connections under the listening socket must use AF_INET (access from outside) else use AF_UNIX (local clients only)
#define HZ_LISTEN_TCP       0x04    //  Connections under the listening socket must use TCP
#define HZ_LISTEN_HTTP      0x08    //  Connections under the listening socket must use HTTP
#define HZ_LISTEN_UDP       0x10    //  Connections under the listening socket must use UDP
#define HZ_LISTEN_SESSION   0x20    //  Connections under the listening socket must use separate thread function
class   hzIpConnex ;
class   hzIpServer ;
class   hzHttpEvent ;
class   hzIpListen
{
    //  Category:   Internet
    //
    //  hzIpListen is a hzIpServer support class. It holds a listening socket and port, various control parameters and pointers to the OnIngress(), OnConnect() and OnDisconn()
    //  functions used to handle client connections.
    hzIpServer* m_pServer ;             //  Applicable hzIpServer instance
    hzLogger*   m_pLog ;                //  Log channel
    SOCKADDRIN  m_Address ;             //  IP Addres of socket
    uint32_t    m_nSocket ;             //  The actual listening socket
    uint32_t    m_nTimeout ;            //  Timeout appllied to all connections to this port
    uint16_t    m_nPort ;               //  Port number server listens on
    uint16_t    m_nMaxConnections ;     //  Max number of simultaneous TCP connections on this port
    uint16_t    m_nCurConnections ;     //  Current number of simultaneous TCP connections on this port
    uint16_t    m_bOpflags ;            //  Operational flags (HZ_LISTEN_SECURE | HZ_LISTEN_INTERNET | HZ_LISTEN_UDP)
public:
    hzTcpCode   (*m_OnIngress)(hzChain& Input, hzIpConnex* pCx) ;   //  To be called as soon as input exists.
    hzTcpCode   (*m_OnConnect)(hzIpConnex* pCx) ;                   //  Only for protocols in which the server sends the first message (server hello).
    hzTcpCode   (*m_OnDisconn)(hzIpConnex* pCx) ;                   //  Only provided if extra tidying up is needed on dissconnection.
    void*       m_appFn ;                                           //  Cast to the specific callback function (eg HTTP)
    hzIpListen  (hzLogger* plog)
    {
        m_nPort = 0 ;
        m_nSocket = 0 ;
        m_nTimeout = 0 ;
        m_nMaxConnections = 0 ;
        m_nCurConnections = 0 ;
        m_OnIngress = 0 ;
        m_OnConnect = 0 ;
        m_appFn = 0 ;
        m_pLog = plog ;
        m_bOpflags = 0 ;
    }
    ~hzIpListen (void)
    {
    }
    //  Initialization
    hzEcode Init
    (
        hzIpServer* pServer,                                //  Applicable hzIpServer instance
        hzTcpCode   (*OnIngress)(hzChain&, hzIpConnex*),    //  App's packet handler function
        hzTcpCode   (*OnConnect)(hzIpConnex*),              //  App's server hello func (if used)
        hzTcpCode   (*OnDisconn)(hzIpConnex*),              //  App's server hello func (if used)
        uint32_t    nTimeout,                               //  Timeout applied to all connections on port
        uint32_t    nPort,                                  //  Port application will listen on
        uint32_t    nMaxClients,                            //  Max number of simultaneous clients
        uint32_t    bOpflags                                //  Operational flags (HZ_LISTEN_SECURE | HZ_LISTEN_INTERNET | HZ_LISTEN_UDP)
    ) ;
    hzEcode Activate    (void) ;
    //  Get functions
    hzIpServer* GetServer           (void) const    { return m_pServer ; }
    hzLogger*   GetLogger           (void) const    { return m_pLog ; }
    uint32_t    GetPort             (void) const    { return m_nPort ; }
    uint32_t    GetSocket           (void) const    { return m_nSocket ; }
    uint32_t    GetTimeout          (void) const    { return m_nTimeout ; }
    uint32_t    GetMaxConnections   (void) const    { return m_nMaxConnections ; }
    uint32_t    GetCurConnections   (void) const    { return m_nCurConnections ; }
    bool        UseUDP              (void) const    { return m_bOpflags & HZ_LISTEN_UDP ? true : false ; }
    bool        UseSSL              (void) const    { return m_bOpflags & HZ_LISTEN_SECURE ? true : false ; }
    uint16_t    Opflags             (void) const    { return m_bOpflags ; }
} ;
/*
**  Connection Info class
*/
class   hzProcInfo
{
    //  Category:   System
    //
    //  The hzProcInfo or 'process data' class is used to pass process information in cases where client connections are handled in a separate thread. The thread handler function
    //  must nessesarily have a single void* argument. In order to supply the client IP address, client socket, and any SSL structure to the thread function, this info is wrapped
    //  in a hzProcInfo instance and a pointer to this is passed instead.
 
    SSL*        m_pSSL ;            //  SSL session info (if applicable)
    hzIpaddr    m_Ipa ;             //  IP address
    uint32_t    m_Socket ;          //  The client socket
public:
    hzProcInfo  (void)
    {
        m_pSSL = 0 ;
        m_Socket = 0 ; 
    }
    hzProcInfo  (SSL* pSSL, uint32_t nSock, hzIpaddr ipa)
    {
        m_pSSL = pSSL ;
        m_Socket = nSock ;
        m_Ipa = ipa ;
    }
    ~hzProcInfo (void)
    {
        if (m_pSSL)
            SSL_free(m_pSSL) ;
        close(m_Socket) ;
    }
    void    SetParams   (SSL* pSSL, uint32_t nSock, hzIpaddr ipa)
    {
        m_pSSL = pSSL ;
        m_Socket = nSock ;
        m_Ipa = ipa ;
    }
    hzIpaddr&   Ipaddr  (void)  { return m_Ipa ; }
    uint32_t    Socket  (void)  { return m_Socket ; }
} ;
class   hzIpConnInfo
{
    //  Category:   Internet
    //
    //  The hzIpConnInfo class is the pure virtual base class for any form of session class. The session classes are application specific devices that maintain state.
public:
    virtual ~hzIpConnInfo   (void)  {}
} ;
/*
**  Conections (Inbound and outbound)
*/
class   hzPktQue
{
    //  Category:   Internet
    //
    //  hzPktQue is a hzIpConnex support class, which queues instances of hzPacket for outgoing responses. hzPktQue is conceptually similar to hzChain, except that the unit of data
    //  is a packet, rather than a byte. While both hzPktQue and hzChain are always appended and read from the begining, with hzPktQue, all packets may be partially filled and the
    //  write and iteration processes are assumed to be ongoing, with packets being removed from the start of the chain after they have been processed.
public:
    hzPacket*   m_pStart ;              //  The first IP packet in the que
    hzPacket*   m_pFinal ;              //  The last IP packet in the que
    uint32_t    m_nSize ;               //  Current size in bytes
    uint32_t    m_nSeq ;                //  Packet sequence
    hzPktQue    (void)
    {
        m_pStart = m_pFinal = 0 ;
        m_nSize = m_nSeq = 0 ;
    }
    uint32_t    Size    (void) const    { return m_nSize ; }
    hzPacket*   Peek    (void) const    { return m_pStart ; }
    hzEcode     Push    (hzPacket& pkt) ;
    hzEcode     Push    (const hzChain& Z) ;
    void        Pull    (void) ;
    void        Clear   (void) ;
} ;
class   hzIpConnex
{
    //  Category:   Internet
    //
    //  Generic client connection
    hzChain         m_Input ;           //  Incomming message chain
    hzChain::Iter   m_MsgStart ;        //  For iteration of pipelined requests
    hzLogger*       m_pLog ;            //  Log channel
    hzIpConnInfo*   m_pInfo ;           //  Connection specific information
    hzPktQue        m_Outgoing ;        //  Outgoing message stream
    uint64_t        m_ConnExpires ;     //  Nanosecond Epoch expiry
    uint64_t        m_nsAccepted ;      //  Nanosecond Epoch connection accepted
    uint64_t        m_nsRecvBeg ;       //  Nanosecond Epoch first packet of message
    uint64_t        m_nsRecvEnd ;       //  Nanosecond Epoch request considered complete
    uint64_t        m_nsSendBeg ;       //  Nanosecond Epoch response transmission began
    uint64_t        m_nsSendEnd ;       //  Nanosecond Epoch response transmission ended
    SOCKADDRIN      m_CliAddr ;         //  IP Addres of client socket
    socklen_t       m_nCliLen ;         //  Length of socket address
    hzIpaddr        m_ClientIP ;        //  IP address of client
    uint32_t        m_nSock ;           //  Client socket
    uint32_t        m_nMsgno ;          //  Event/Message number
    uint32_t        m_nGlitch ;         //  Extent of incomplete write
    uint32_t        m_nStart ;          //  Start position of current incomming message within chain
    uint32_t        m_nTotalIn ;        //  Total size of outgoing response
    uint32_t        m_nTotalOut ;       //  Total size of outgoing response
    uint32_t        m_nExpected ;       //  Expected size of incomming request
    uint32_t        m_bState ;          //  Client state
    uint16_t        m_nPort ;           //  Incomimg port
    uint16_t        m_bListen ;         //  Operational flags from listening socket (HZ_LISTEN_SECURE | HZ_LISTEN_INTERNET | HZ_LISTEN_UDP)
    bool            m_bInitSSL ;        //  If there is an SSL conection, has it been accepted and set up?
public:
    //  hzChain     m_Track ;               //  Used to report on progress during the processing of a client request. Committed to logfile on completion or when connection terminated.
    void*       m_appFn ;               //  Application message event handler
    void*       m_pEventHdl ;           //  HTTP Event instance
    SSL*        m_pSSL ;                //  SSL session info
    hzIpConnex* m_pProxy ;              //  Proxy connection
    bool        m_bAcceptSSL ;          //  SSL has been accepted
    hzTcpCode   (*m_OnIngress)(hzChain& Input, hzIpConnex* pCx) ;   //  Required: Function to handle messages comming in on the specific port.
    hzTcpCode   (*m_OnConnect)(hzIpConnex* pCx) ;                   //  Optional: Called on connection e.g. Server hello.
    hzTcpCode   (*m_OnDisconn)(hzIpConnex* pCx) ;                   //  Optional: Called on disconnection for any tidying up.
    //  Constructor and destructor
    hzIpConnex  (hzLogger* pLog) ;
    ~hzIpConnex (void) ;
    //  Init functions
    hzEcode Initialize  (hzIpListen* pLS, SSL* pSSL, hzIpaddr ipa, uint32_t cliSock, uint32_t cliPort, uint32_t eventNo) ;
    void    SetSocket   (uint32_t nSock)    { m_nSock = nSock ; }
    void    Oxygen  (void)  { m_ConnExpires = RealtimeNano() ; m_ConnExpires += 25000000000 ; } //  Adds 25 seconds to the time to live
    void    Hypoxia (void)  { m_ConnExpires = 0 ; }                                             //  Expires the connection
    void    Terminate   (void) ;    //  Terminate connection
    //  Set functions
    void    SetInfo (hzIpConnInfo* pInfo)   { m_pInfo = pInfo ; }
    //  Get functions
    hzIpConnInfo*   GetInfo (void) const    { return m_pInfo ; }
    hzLogger*   GetLogger   (void) const    { return m_pLog ; }
    hzChain&    InputZone   (void)          { return m_Input ; }
    hzIpaddr    ClientIP    (void) const    { return m_ClientIP ; }
    uint64_t    Expires     (void) const    { return m_ConnExpires ; }
    uint64_t    TimeAcpt    (void) const    { return m_nsRecvBeg - m_nsAccepted ; }
    uint64_t    TimeRecv    (void) const    { return m_nsRecvEnd - m_nsRecvBeg ; }
    uint64_t    TimeProc    (void) const    { return m_nsSendBeg - m_nsRecvEnd ; }
    uint64_t    TimeXmit    (void) const    { return m_nsSendEnd - m_nsSendBeg ; }
    uint32_t    EventNo     (void) const    { return m_nMsgno ; }
    uint32_t    CliSocket   (void) const    { return m_nSock ; }
    uint32_t    CliPort     (void) const    { return m_nPort ; }
    uint32_t    SizeIn      (void) const    { return m_Input.Size() - m_nStart ; }
    uint32_t    TotalIn     (void) const    { return m_nTotalIn ; }
    uint32_t    TotalOut    (void) const    { return m_nTotalOut ; }
    bool        IsVirgin    (void) const    { return m_bState == CLIENT_INITIALIZED ; }
    bool        IsCliTerm   (void) const    { return m_bState & CLIENT_TERMINATION ; }
    bool        IsCliBad    (void) const    { return m_bState & CLIENT_BAD ; }
    bool        _isxmit     (void) const    { return m_Outgoing.m_pStart ? true : false ; }
    //  Operational functions
    int32_t     Recv        (hzPacket& Buf) ;
    hzEcode     SendData    (const hzChain& Hdr, const hzChain& Body) ;
    hzEcode     SendData    (const hzChain& Z) ;
    void        SendKill    (void) ;
    int32_t     _xmit       (hzPacket& buf) ;
    //  Message size expectations
    void        ExpectSize  (uint32_t nBytes)   { m_nExpected = nBytes ; }
    uint32_t    ExpectSize  (void)  { return m_nExpected ; }
    bool        MsgComplete (void)  { return m_nExpected && m_Input.Size() >= (m_nExpected + m_nStart) ? true : false ; }
    bool    MsgReady    (void)
    {
        //  If a message has an expected size this condition will only be true if the input so far has reached or exceeded this size. In the common
        //  scenario where an incoming message has a header stating the length, the expected size starts at zero and this function returns true. The
        //  input is then provisionally processed and this will establish the expected size. This function will then not return true until the whole
        //  message in in.
        if (!m_nExpected)
            return true ;
        if (m_Input.Size() >= (m_nExpected + m_nStart))
            return true ;
        return false ;
    }
} ;
/*
**  The SERVER itself
*/
#define MAXEVENTS   100
class   hzIpServer
{
    //  Category:   Internet
    //
    //  General purpose server class.
    hzList<hzIpListen*> m_LS ;          //  Listening sockets
    //hzVect<hzIpConnex*>   m_Inbound ;     //  Currently connected clients
    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*>    m_mapLS ;       //  Listening sockets map
    struct epoll_event  m_arEvents[MAXEVENTS] ;     //  Epoll event array
    hzLogger*   m_pLog ;                //  Log channel to use for events
    hzLogger*   m_pStats ;              //  Log channel to use for stats
    socklen_t   m_nCliLen ;             //  Length of client
    uint32_t    m_nMaxClients ;         //  Maximum number of simultaneous connections.
    uint32_t    m_nCurrentClients ;     //  Current number of connected clients.
    uint32_t    m_nTimeout ;            //  Timeout for select
    uint32_t    m_eError ;              //  Error code
    uint32_t    m_nMaxSocket ;          //  Highest socket for the select function
    uint32_t    m_nLoop ;               //  Number of time round epoll loop (run time total)
    bool        m_bActive ;             //  Socket to start listening
    bool        m_bShutdown ;           //  Socket to stop listening
    /*
    **  Private constructor for singleton method
    */
    hzIpServer  (void)
    {
        m_pLog = 0 ;
        m_nMaxClients = 0 ;
        m_nCurrentClients = 0 ;
        m_bActive = false ;
        m_bShutdown = false ;
        m_nMaxSocket = 0 ;
        m_nTimeout = 30 ;
    }
    hzEcode _nonblock   (uint32_t nSock) ;
public:
    int32_t     s_epollSocket ;         //  The epoll 'master' socket
    static  hzIpServer* GetInstance (hzLogger* pLogger) ;
    ~hzIpServer (void)  {}
    void    SetTimeout  (uint32_t Timeout)  { m_nTimeout = Timeout ; }
    void    SetLogger   (hzLogger* pLog)    { m_pLog = pLog ; }
    void    SetStats    (hzLogger* pStats)  { m_pStats = pStats ; }
    //  Adds a TCP listening socket for invoking a user defined function that handles general client connections
    hzEcode AddPortTCP  (   hzTcpCode   (*OnIngress)(hzChain&, hzIpConnex*),
                            hzTcpCode   (*OnConnect)(hzIpConnex*),
                            hzTcpCode   (*OnDisconn)(hzIpConnex*),
                            uint32_t    nTimeout,
                            uint32_t    nPort,
                            uint32_t    nMaxClients,
                            bool        bSecure = false ) ;
    //  Adds a listening socket for HTTP connections. HTTP is a special case in which applications specify an OnHtpRq function to process complete HTTP requests
    //  rather than an OnIngress function. The OnIngress function is in-built and extracts HTTP requests from the incomming data.
    hzEcode AddPortHTTP (   hzTcpCode   (*OnHttpReq)(hzHttpEvent*),
                            uint32_t    nTimeout,
                            uint32_t    nPort,
                            uint32_t    nMaxClients,
                            bool        bSecure = false ) ;
    //  Adds a UDP socket. Note there cannot be an OnConnect function in the normal sense as UDP has no connections. However applications will need to ...
    hzEcode AddPortUDP  (   hzTcpCode   (*OnIngress)(hzChain&, hzIpConnex*),
                            hzTcpCode   (*OnConnect)(hzIpConnex*),
                            hzTcpCode   (*OnDisconn)(hzIpConnex*),
                            uint32_t    nTimeout,
                            uint32_t    nPort,
                            uint32_t    nMaxClients,
                            bool        bSecure = false ) ;
    //  followed by a single call to Activate sets all ports to listen
    hzEcode Activate    (void) ;
    //  Proxy connections
    hzEcode ProxyTo     (hzIpConnex* pConn, uint32_t nPort) ;
    //  Serve (standard mode)
    void    Serve   (void) ;
    //  If you need a multi-threaded server, you must use thread specialization and the ServeX() function. This listens, accepts new client connections and reads incoming requests,
    //  but does not call a request handler function. Instead client requests are placed in a lock free queue from where they are drawn by ServeRequests() which must be called from
    //  a separate thread (created by means of a thread entry function and a pthread_create() call). You may set up several such threads as required. Then in yet another thread,
    //  ServeResponses() is called. This is nessesary as ServeX(), unlike Serve(), does not write to client sockets. Instead ServeReponses() does this job by drawing the responses
    //  from a queue fed by ServeRequests calls to the Handler passed in AddPort().
    void    ServeRequests   (void) ;
    void    ServeResponses  (void) ;
    void    ServeX  (void) ;
    void    Halt    (void)  { m_bShutdown = true ; }
} ;
/*
**  Prototypes
*/
hzEcode     SetupHost       (void) ;
hzEcode     InitDomainSSL   (const char* pvtKey, const char* sslCert, const char* sslCA, const char* domain) ;
hzEcode     InitServerSSL   (const char* pvtKey, const char* sslCert, const char* sslCA) ;
/*
**  Globals
*/
extern  hzMapS  <hzIpaddr,hzIpinfo> _hzGlobal_StatusIP ;    //  Black and white listed IP addresses
extern  hzString    _hzGlobal_Hostname ;        //  String form of actual hostname of this server
extern  hzString    _hzGlobal_HostIP ;          //  String form of assigned IP address of this server
extern  hzIpaddr    _hzGlobal_localhost ;       //  Always the default IP address 127.0.0.1
extern  hzIpaddr    _hzGlobal_livehost ;        //  Assigned IP address of this server
#endif  //  hzIpServer_h