//
// File: hzUdpClient.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.
//
//
// Implimentation of the generic UDP client class, hzUdpClient.
//
#include <iostream>
#include <stdarg.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/socket.h>
#include "hzProcess.h"
#include "hzUdpClient.h"
hzEcode hzUdpClient::Connect (const hzString& Hostname, uint32_t nPort, bool bLocal)
{
// There is of course, no such thing as a UDP connection in the same way there are TCP connections. However the host must still be looked up and a socket
// obtained before one can send and receive data so in terms of function call sequence, the two forms of communication are conceptually similar.
//
// It is the role of this function to do the host lookup and obtain the datagram socket.
//
// Arguments: 1) Hostname The server name or IP address (as hzString)
// 2) nPort The port number
// 3) bLocal Optional local flag (if host is this machine)
//
// Returns: E_ARGUMENT If either the hostname or port is not specified.
// E_DNS_NOHOST If the domain does not exist
// E_DNS_NODATA If the domain exists but not a service
// E_DNS_FAILED If the domain has invalid settings
// E_DNS_RETRY If the DNS is busy
// E_NOSOCKET If the socket could not be created or timeouts set
// E_OK If host established and socket obtained
_hzfunc("hzUdpClient::Connect") ;
timeval tv ; // Socket timer
tv.tv_sec = 20 ;
tv.tv_usec = 0 ;
/*
** Initialize
*/
m_Hostname = Hostname ;
m_nPort = nPort ;
if (!Hostname)
{
hzerr(E_ARGUMENT, "Hostname not set") ;
return E_ARGUMENT ;
}
m_pHost = gethostbyname(*m_Hostname) ;
if (!m_pHost)
{
if (h_errno == TRY_AGAIN) return E_DNS_RETRY ;
if (h_errno == HOST_NOT_FOUND) return E_DNS_NOHOST ;
if (h_errno == NO_RECOVERY) return E_DNS_FAILED ;
if (h_errno == NO_DATA || h_errno == NO_ADDRESS)
return E_DNS_NODATA ;
hzerr(E_DNS_FAILED, "Could not resolve hostname") ;
return E_DNS_FAILED ;
}
if (m_nPort == 0)
{
hzerr(E_ARGUMENT, "Port not set") ;
return E_ARGUMENT ;
}
m_SvrLen = sizeof(m_SvrAddr) ;
/*
** Set up socket
*/
memset(&m_SvrAddr, 0, sizeof(m_SvrAddr)) ;
m_SvrAddr.sin_family = AF_INET ;
memcpy(&m_SvrAddr.sin_addr, m_pHost->h_addr, m_pHost->h_length) ;
m_SvrAddr.sin_port = htons(m_nPort) ;
if ((m_nSock = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
{
hzerr(E_NOSOCKET, "Could not create client socket") ;
return E_NOSOCKET ;
}
if (setsockopt(m_nSock, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)) < 0)
{
hzerr(E_NOSOCKET, "Could not set send timeout on UDP client socket") ;
return E_NOSOCKET ;
}
if (setsockopt(m_nSock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) < 0)
{
hzerr(E_NOSOCKET, "Could not set recv timeout on UDP client socket") ;
return E_NOSOCKET ;
}
return E_OK ;
}
hzEcode hzUdpClient::SendPkt (hzPacket* pData, uint32_t nLen)
{
// Purpose: Write buffer content to a socket
//
// Arguments: 1) pData The buffer (void*)
// 2) nLen Number of bytes to send
//
// Returns: E_NOSOCKET If no socket has been set by Connect()
// E_ARGUMENT If the data is not provided
// E_RANGE If the number of bytes to send exceeds packet size (1460 bytes)
// E_SENDFAIL If the send operation fails. In this event the connection is closed.
// E_OK If the operation is successfull.
_hzfunc("hzUdpClient::Send(void*,uint32_t)") ;
if (m_nSock == 0)
return E_NOSOCKET ;
if (!pData)
{
hzerr(E_ARGUMENT, "Nothing to send") ;
return E_ARGUMENT ;
}
if (nLen > HZ_MAXPACKET)
{
hzerr(E_RANGE, "Length of message must be between 1 and 1460 bytes") ;
return E_RANGE ;
}
// send a message
if (sendto(m_nSock, pData->m_data, nLen, 0, (struct sockaddr*) &m_SvrAddr, m_SvrLen) < 0)
{
close(m_nSock) ;
m_nSock = 0 ;
hzerr(E_SENDFAIL, "Could not send to host (%s) on port %d", *m_Hostname, m_nPort) ;
return E_SENDFAIL ;
}
threadLog("Client sock %d sends msg of %d bytes [%s]\n", m_nSock, nLen, pData->m_data) ;
return E_OK ;
}
hzEcode hzUdpClient::SendChain (hzChain& C)
{
// Write supplied chain content to a socket
//
// Arguments: 1) C The chain to send. No size indicator is needed as whole chain is sent.
//
// Returns: E_NOSOCKET If the connection is not open
// E_NODATA If the supplied chain contains no data
// E_SENDFAIL If the send operation fails. In this event the connection is closed.
// E_OK If the operation is successfull.
_hzfunc("hzUdpClient::Send(hzChain&)") ;
chIter ci ; // To iterate input chain
char* i ; // To populate buffer
uint32_t nSend ; // Bytes in buffer to send
uint32_t nSofar ; // Bytes sent so far
if (m_nSock == 0)
return E_NOSOCKET ;
if (!C.Size())
return E_NODATA ;
// Init
ci = C ;
nSofar = 0 ;
// Read from chain to populate rest of buffer with start of message
for (nSend = 0, i = m_pack.m_data ; !ci.eof() && nSend < HZ_MAXPACKET ; *i = *ci, i++, nSend++, ci++) ;
nSofar = nSend ;
// Send buffer content to socket
if (sendto(m_nSock, m_pack.m_data, nSend, 0, (struct sockaddr*) &m_SvrAddr, m_SvrLen) < 0)
{
close(m_nSock) ;
m_nSock = 0 ;
hzerr(E_SENDFAIL, "Could not send to host (%s) on port %d", *m_Hostname, m_nPort) ;
return E_SENDFAIL ;
}
// Repeat read and send steps for rest of message
for (; nSofar < C.Size() ;)
{
for (nSend = 0, i = m_pack.m_data ; !ci.eof() && nSend < HZ_MAXPACKET ; *i = *ci, nSend++, i++, ci++) ;
nSofar += nSend ;
if (!nSend)
break ;
if (sendto(m_nSock, m_pack.m_data, nSend, 0, (struct sockaddr*) &m_SvrAddr, m_SvrLen) < 0)
{
close(m_nSock) ;
m_nSock = 0 ;
hzerr(E_SENDFAIL, "Could not send to host (%s) on port %d", *m_Hostname, m_nPort) ;
return E_SENDFAIL ;
}
}
return E_OK ;
}
hzEcode hzUdpClient::RecvPkt (hzPacket* pData, uint32_t& nRecv)
{
// Read a packet from the socket into the client buffer.
//
// Arguments: 1) pData The packet recepticle
// 2) nRecv Reference to number of bytes received
//
// Returns: E_ARGUMENT If the packet recipticle is not supplied
// E_NOSOCKET If the connection has been closed
// E_NODATA If no data was recieved
// E_RECVFAIL If the socket read operation fails
// E_OK If operation successfull
_hzfunc("hzUdpClient::Recv(buf,recv,max)") ;
if (!pData)
{
hzerr(E_ARGUMENT, "No IP recepticle supplied") ;
return E_ARGUMENT ;
}
if (m_nSock == 0)
{
hzerr(E_NOSOCKET, "Client has no connection") ;
return E_NOSOCKET ;
}
// Get response
//nRecv = recvfrom(m_nSock, pData->m_data, HZ_MAXPACKET, 0, (struct sockaddr*) &m_SvrAddr, &m_SvrLen) ;
nRecv = recvfrom(m_nSock, pData->m_data, HZ_MAXPACKET, 0, (SOCKADDR*) &m_SvrAddr, &m_SvrLen) ;
if (nRecv == 0)
return E_NODATA ;
if (nRecv < 0)
{
close(m_nSock) ;
m_nSock = 0 ;
hzerr(E_RECVFAIL, "Could not recv from server (%s) on port %d", *m_Hostname, m_nPort) ;
return E_RECVFAIL ;
}
pData->m_data[nRecv] = 0 ;
threadLog("Client sock %d recv %d bytes\n", m_nSock, nRecv) ;
return E_OK ;
}
hzEcode hzUdpClient::RecvChain (hzChain& C)
{
// Reads one or more packets from the socket into the supplied chain until a zero length read occurs.
//
// Argument: C The chain to populate
//
// Returns: E_NOSOCKET If the connection has been closed
// E_RECVFAIL If the socket read operation fails
// E_OK If operation successfull
_hzfunc("hzUdpClient::Recv(hzChain&)") ;
uint32_t nRecv ; // Bytes recieved in IP packet
// CHECK
C.Clear() ;
if (m_nSock == 0)
{
hzerr(E_NOSOCKET, "Client has no connection") ;
return E_NOSOCKET ;
}
if ((nRecv = recvfrom(m_nSock, m_pack.m_data, HZ_MAXPACKET, 0, (struct sockaddr*) &m_SvrAddr, &m_SvrLen)) < 0)
{
close(m_nSock) ;
m_nSock = 0 ;
hzerr(E_RECVFAIL, "Could not recv from server (%s) on port %d", *m_Hostname, m_nPort) ;
return E_RECVFAIL ;
}
C.Append(m_pack.m_data, nRecv) ;
for (;;)
{
nRecv = recvfrom(m_nSock, m_pack.m_data, HZ_MAXPACKET, 0, (struct sockaddr*) &m_SvrAddr, &m_SvrLen) ;
if (nRecv < 0)
{
close(m_nSock) ;
m_nSock = 0 ;
hzerr(E_RECVFAIL, "Could not recv from server (%s) on port %d", *m_Hostname, m_nPort) ;
return E_RECVFAIL ;
}
if (nRecv == 0)
break ;
C.Append(m_pack.m_data, nRecv) ;
}
return E_OK ;
}