//
// 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