//
// File: hzIpaddr.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 hzIpaddr (IP address) class.
//
#include <fstream>
#include <iostream>
using namespace std ;
#include <fcntl.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/stat.h>
#include <sys/shm.h>
#include <sys/mman.h>
#include <errno.h>
#include "hzChars.h"
#include "hzTextproc.h"
#include "hzDirectory.h"
#include "hzIpaddr.h"
global const hzIpaddr _hzGlobal_nullIP ; // Null IP address
bool IsIPAddr (uint32_t& nIpValue, const char* cpIpa)
{
// Category: Text Processing
//
// Test if supplied string is of the form of a valid IP address (four numbers between 0 and 255 separated by a period)
//
// Arguments: 1) nIpValue The IP value, set if supplied string is of the form
// 2) cpIpa The string to be tested
//
// Returns: True If the supplied string amounts to a valid IP address
// False If it doesn't
const char* i ; // Text IP address iterator
uint32_t nVal = 0 ; // 32-bit IP address value
uint32_t nTmp ; // IP segment value
int32_t nNos = 0 ; // Count of IP numeric segments
nIpValue = IPADDR_BAD ;
if (!cpIpa) return false ;
if (!cpIpa[0]) return false ;
for (i = cpIpa ; *i ; i++)
{
if (*i < '0' || *i > '9')
return false ;
for (nTmp = 0 ; *i >= '0' && *i <= '9' ; i++)
{ nTmp *= 10 ; nTmp += (*i - '0') ; }
if (nTmp > 255)
return false ;
nVal *= 256 ;
nVal += nTmp ;
nNos++ ;
if (*i == 0 || *i == CHAR_COMMA)
break ;
if (*i != CHAR_PERIOD)
return false ;
}
if (nNos != 4)
return false ;
nIpValue = nVal ;
return true ;
}
bool IsIPAddr (const char* cpIpa)
{
// Category: Text Processing
//
// Test if supplied string is of the form of a valid IP address (four numbers between 0 and 255 separated by a period)
//
// Arguments: 1) The string to be tested
//
// Returns: True If the supplied string amounts to a valid IP address
// False If it doesn't
uint32_t nVal ; // 32-bit IP address value
return IsIPAddr(nVal, cpIpa) ;
}
hzEcode hzIpaddr::SetValue (const char* cpIpa)
{
// Sets the IP address to the value provided as a character string. The string must be of the form x.x.x.x where x can be any number between 0 and 255
//
// Arguments: 1) cpIpa IP address as a cstr
//
// Returns: E_NODATA If the input is not supplied
// E_FORMAT If the input cstr is no an IP address
// E_OK If this IP address instance is set
if (!cpIpa || !cpIpa[0])
return E_NODATA ;
if (IsIPAddr(m_Ipa, cpIpa))
return E_OK ;
return E_FORMAT ;
}
hzIpaddr& hzIpaddr::operator= (const char* cpIpa)
{
// Sets the IP address to the operand provided as a character string. The string must be of the form x.x.x.x where x can be any number between 0 and 255
//
// Arguments: 1) cpIpa IP address as a cstr
//
// Returns: Reference to this instance
if (!cpIpa || !cpIpa[0])
m_Ipa = 0 ;
else
IsIPAddr(m_Ipa, cpIpa) ;
return *this ;
}
/*
** Get value methods
*/
const char* hzIpaddr::AsBytes (void) const
{
// Populates the supplied buffer with the IP address as a series of 4 bytes. This is for the benefit of network functions.
//
// Argument: buf The buffer to be populated
//
// Returns: Pointer to the populated buffer
_hzfunc("hzIpaddr::AsBytes") ;
char* pBuf ; // Text recepticle
pBuf = _thisfn.ScratchPad(8) ;
pBuf[0] = (m_Ipa & 0xff000000) >> 24 ;
pBuf[1] = (m_Ipa & 0xff0000) >> 16 ;
pBuf[2] = (m_Ipa & 0xff00) >> 8 ;
pBuf[3] = m_Ipa & 0xff ;
return pBuf ;
}
const char* hzIpaddr::Full (void) const
{
// Populates the supplied buffer with the fully expanded text value of the IP address. This is for the benefit of diagnostics.
//
// Argument: r The 16-byte recepticle to be populated
//
// Returns: Pointer to the populated buffer
_hzfunc("hzIpaddr::Full") ;
char* pBuf ; // Text recepticle
pBuf = _thisfn.ScratchPad(24) ;
sprintf(pBuf, "%03u.%03u.%03u.%03u", (m_Ipa & 0xff000000) >> 24, (m_Ipa & 0xff0000) >> 16, (m_Ipa & 0xff00) >> 8, m_Ipa & 0xff) ;
return pBuf ;
}
// Stream operator
std::ostream& operator<< (std::ostream& os, const hzIpaddr& op)
{
// Category: Data Output
//
// Streams out the text form of the IP address to the supplied output stream
//
// Arguments: 1) os Reference to the output stream
// 2) op The IP address instance
//
// Returns: Reference to the supplied output stream
uint32_t val ; // 32-bit IP address value
char buf [20] ; // Text formulation buffer
val = (uint32_t) op ;
sprintf(buf, "%d.%d.%d.%d", (val & 0xff000000) >> 24, (val & 0xff0000) >> 16, (val & 0xff00) >> 8, (val & 0xff)) ;
os << buf ;
return os ;
}
/*
** IP Location and Country Codes
*/
// The s_CC series just deals with country codes and country code lookup.
static const char* s_CC_buffer ; // Array of 2-char null terminated country codes plus country name
static const uint16_t* s_CC_offsets ; // Array of country code offsets - one per IP range
static uint32_t s_CC_max ; // Total country codes
static uint32_t s_CC_start ; // Country code binary chop start point
static uint32_t s_CC_div ; // Country code initial binary divider
// The s_IpBasic series deals with Basic level IP location
static const uchar* s_IpBasic_codes ; // Array of country codes. Each element is an 8-bit unsigned number indicating the country. There is an element for each IP range.
static const uint32_t* s_IpBasic_zones ; // Array of IP ranges. These comprise only the starting IP. The range limit is given by the entry above or 255.255.255.255.
static uint32_t s_IpBasic_max ; // Total population of IP ranges in the base level data
static uint32_t s_IpBasic_start ; // IP range binary chop start point
static uint32_t s_IpBasic_div ; // IP range initial binary divider
// The s_IpCity series deals with City level IP location
static const char* s_IpCity_text ; // Array of city-level locations (as concatenated Cstr)
static const uint32_t* s_IpCity_zones ; // Array of IP ranges. These comprise only the starting IP. The range limit is given by the entry above or 255.255.255.255.
static const uint32_t* s_IpCity_osets ; // Array of offsets into the city-level location (as they vary in length)
static uint32_t s_IpCity_max ; // Total population of IP ranges in the city level data
static uint32_t s_IpCity_start ; // IP range binary chop start point
static uint32_t s_IpCity_div ; // IP range initial binary divider
hzEcode InitCountryCodes (void)
{
// Category: Internet
//
// Read in the HadronZoo data file global.country.dat which provides a mapping of two letter country codes to country names. This file is expected to be in
// the HadronZoo data directory $HADRONZOO/data which is supplied as a part of the HadronZoo Suite download.
//
// Arguments: None
//
// Returns: E_NODATA If the Country code source could not be established
// E_NOTFOUND If the Country code file does not exist
// E_OPENFAIL If the Country code file could not be opened
// E_READFAIL If the Country code file could not be read
// E_FORMAT If the Country code file does not have the expected format
// E_OK If the Country code tables were initialized
_hzfunc(__func__) ;
ifstream is ; // Input stream
FSTAT fs ; // File status
char* pCC_buf ; // Shared mem segment
uint16_t* pShort ; // Offsets in mem segment
uint32_t n ; // Buffer iterator
int32_t fd ; // Shered mem 'file descriptor'
hzEcode rc = E_OK ; // Return code
fd = shm_open("deltaCountryCodes", O_RDONLY, 0) ;
if (fd < 0)
return hzerr(E_INITFAIL, "Cannot initialize shared memory segment (deltaCountryCodes)\n") ;
fstat(fd, &fs);
threadLog("Set fd to %d\n", fd) ;
pCC_buf = (char*) mmap(0, fs.st_size, PROT_READ, MAP_SHARED, fd, 0);
if (!pCC_buf)
return hzerr(E_INITFAIL, "Could not map Shared memory segment. Errno %d", errno) ;
threadLog("Country codes mem at %p, size is %d\n", pCC_buf, fs.st_size) ;
pShort = (uint16_t*) pCC_buf ;
s_CC_max = *pShort++ ;
threadLog("Total codes %d\n", s_CC_max) ;
s_CC_offsets = pShort ;
threadLog("Offsets at %p\n", s_CC_offsets) ;
pShort += s_CC_max ;
s_CC_buffer = (char*) pShort ;
threadLog("Buffer at %p\n", s_CC_buffer) ;
for (n = 2 ; n <= s_CC_max ; n *= 2) ;
s_CC_div = n / 2 ;
s_CC_start = n - 1 ;
threadLog("Total codes %d (div %d start %d)\n\n", s_CC_max, s_CC_div, s_CC_start) ;
/*
for (n = 0 ; n < s_CC_max ; n++)
threadLog("%s -> %s (%d)\n", s_CC_buffer + s_CC_offsets[n], s_CC_buffer + s_CC_offsets[n] + 3, s_CC_offsets[n]) ;
*/
return rc ;
}
uint32_t GetCountryByCode (const char* ccode)
{
// Category: Internet
//
// Locate country by country code
//
// Argument: ccode Two letter country code
//
// Returns: Number being RID of country or 0 if not found.
_hzfunc(__func__) ;
const char* i ; // In-table country code
uint32_t nDiv ; // Binary chop divider
uint32_t nPos ; // Starting position
int32_t res ; // Comparison result
bool bFound ; // Position if redult
// Ensure country code shared memory is in place
if (!s_CC_buffer)
return 0 ;
// Ensure country code is two upper case letters
if (!ccode) return 0 ;
if (!ccode[0]) return 0 ;
if (ccode[2]) return 0 ;
if (ccode[0] < 'A' || ccode[0] > 'Z') return 0 ;
if (ccode[1] < 'A' || ccode[1] > 'Z') return 0 ;
// Perform binary chop
if (!s_CC_start)
return 0 ;
nPos = s_CC_start ;
bFound = false ;
for (nDiv = s_CC_div ;; nDiv /= 2)
{
if (nPos > s_CC_max)
{
if (!nDiv)
break ;
nPos -= nDiv ;
continue ;
}
// Compare
i = s_CC_buffer + s_CC_offsets[nPos] ;
if (i[0] > ccode[0])
res = -1 ;
else if (i[0] < ccode[0])
res = 1 ;
else
res = i[1] > ccode[1] ? -1 : i[1] < ccode[1] ? 1 : 0 ;
// Chop
if (res > 0)
{
if (!nDiv)
break ;
nPos += nDiv ;
continue ;
}
if (res < 0)
{
if (!nDiv)
break ;
nPos -= nDiv ;
continue ;
}
bFound = true ;
break ;
}
return bFound ? nPos : 0 ;
}
const char* GetCountryCode (uint32_t countryRID)
{
// Category: Internet
//
// Return the 2-byte country code
_hzfunc(__func__) ;
if (countryRID > s_CC_max)
return s_CC_buffer ;
return s_CC_buffer + s_CC_offsets[countryRID] ;
}
const char* GetCountryName (uint32_t countryRID)
{
// Category: Internet
//
// Return the country Name
_hzfunc(__func__) ;
if (countryRID > s_CC_max)
return s_CC_buffer ;
return s_CC_buffer + s_CC_offsets[countryRID] + 3 ;
}
hzEcode InitIpBasic (void)
{
// Category: Internet
//
// Initialize basic IP lookup tables as client. The HadronZoo Delta Server is assumed to have previously created the necessary shared memory segments.
//
// Arguments: None
//
// Returns: E_NODATA If there are no IP ranges
// E_OPENFAIL If the IP range file cannot be opened
_hzfunc(__func__) ;
ifstream is ; // Input stream
FSTAT fs ; // Shared memory 'file' stat
uint32_t* pRanges ; // Pointer to IP Ranges block
uint32_t* pLocations ; // Pointer to IP Locations block
uint32_t n ; // Loop control
int32_t m_fd ; // Sheared memory file descriptor
// Check we have country codes. This will ensure _hzGlobal_HadronZooBase has a value and from this, _hzGlobal_IpRanges can be derived.
// Check we have deltaIpBaseZone
m_fd = shm_open("deltaIpBaseZone", O_RDONLY, 0) ;
if (m_fd < 0)
return hzerr(E_OPENFAIL, "Could not open deltaIpBaseZone") ;
threadLog("Opened deltaIpBaseZone\n") ;
fstat(m_fd, &fs);
pRanges = (uint32_t*) mmap(0, fs.st_size, PROT_READ, MAP_SHARED, m_fd, 0);
if (!pRanges)
return hzerr(E_INITFAIL, "Could not map shared memory segment for deltaIpBaseZone. Errno %d\n", errno) ;
threadLog("IP ranges size %u mem at %p\n", fs.st_size, pRanges) ;
// Check we have deltaIpBaseCode
m_fd = shm_open("deltaIpBaseCode", O_RDONLY, 0) ;
if (m_fd < 0)
return hzerr(E_OPENFAIL, "Could not open deltaIpBaseCode") ;
threadLog("Opened deltaIpBaseCode\n") ;
fstat(m_fd, &fs);
s_IpBasic_max = fs.st_size ;
pLocations = (uint32_t*) mmap(0, fs.st_size, PROT_READ, MAP_SHARED, m_fd, 0);
if (!pLocations)
return hzerr(E_INITFAIL, "Shared memory address at %p errno is %d\n", pLocations, errno) ;
threadLog("locations size %u mem at %p\n", fs.st_size, pLocations) ;
s_IpBasic_zones = pRanges ;
s_IpBasic_codes = (uchar*) pLocations ;
// Calculate start and divider
for (n = 2 ; n < s_IpBasic_max ; n *= 2) ;
s_IpBasic_div = n / 2 ;
s_IpBasic_start = n - 1 ;
threadLog("Total ranges %d (div %d start %d)\n\n", s_IpBasic_max, s_IpBasic_div, s_IpBasic_start) ;
return E_OK ;
}
hzEcode InitIpCity (void)
{
// Category: Internet
//
// Initialize city-level IP lookup tables as client. The HadronZoo Delta Server is assumed to have previously created the necessary shared memory segments.
//
// Arguments: None
//
// Returns: E_NODATA If there are no IP ranges
// E_OPENFAIL If the IP range file cannot be opened
_hzfunc(__func__) ;
ifstream is ; // Input stream
FSTAT fs ; // Shared memory 'file' stat
uint32_t* pIpr ; // Pointer to IP Ranges block
uint32_t m_fd ; // Loop control
uint32_t n ; // Loop control
// Check we have country codes. This will ensure _hzGlobal_HadronZooBase has a value and from this, _hzGlobal_IpRanges can be derived.
m_fd = shm_open("deltaIpCityText", O_RDONLY, 0) ;
if (m_fd < 0)
return hzerr(E_OPENFAIL, "Could not open deltaIpCityText") ;
threadLog("Set fd to %d\n", m_fd) ;
fstat(m_fd, &fs);
s_IpCity_text = (char*) mmap(0, fs.st_size, PROT_READ, MAP_SHARED, m_fd, 0);
if (!s_IpCity_text)
return hzerr(E_INITFAIL, "Shared memory address at %p errno is %d\n", s_IpCity_text, errno) ;
threadLog("text mem at %p\n", s_IpCity_text) ;
m_fd = shm_open("deltaIpCityZone", O_RDONLY, 0) ;
if (m_fd < 0)
return hzerr(E_OPENFAIL, "Could not open deltaIpCityZone") ;
threadLog("Set fd to %d\n", m_fd) ;
fstat(m_fd, &fs);
pIpr = (uint32_t*) mmap(0, fs.st_size, PROT_READ, MAP_SHARED, m_fd, 0);
if (!pIpr)
return hzerr(E_INITFAIL, "Shared memory address at %p errno is %d\n", pIpr, errno) ;
threadLog("zone mem at %p\n", pIpr) ;
s_IpCity_max = pIpr[0] ;
s_IpCity_zones = pIpr + 1 ;
m_fd = shm_open("deltaIpCityLocn", O_RDONLY, 0) ;
fstat(m_fd, &fs);
threadLog("Set fd to %d\n", m_fd) ;
s_IpCity_osets = (uint32_t*) mmap(0, fs.st_size, PROT_READ, MAP_SHARED, m_fd, 0);
if (!s_IpCity_osets)
return hzerr(E_INITFAIL, "Shared memory address at %p errno is %d\n", s_IpCity_osets, errno) ;
threadLog("locn mem at %p\n", s_IpCity_osets) ;
// Calculate start and divider
for (n = 2 ; n < s_IpCity_max ; n *= 2) ;
s_IpCity_div = n / 2 ;
s_IpCity_start = n - 1 ;
threadLog("Have %d zones, div %d start %d\n", s_IpCity_max, s_IpCity_div, s_IpCity_start) ;
return E_OK ;
}
/*
** IP Lookup Functions
*/
const char* GetIpLocation (const hzIpaddr& ipa)
{
// Category: Internet
//
// This returns a location description for the supplied IP address if the IP location table has been loaded. Otherwise this function calls LocateCountry(),
// which will return the country code for the IP address - if the country codes table has been set up.
//
// This function never returns NULL. A pointer to a static string is returned in the event of lookup failure. If the supplied IP address is invalid but the
// IP location table has been loaded, this will read "-- Invalid IP location". If the IP location table is not loaded and LocateCountry() is used instead,
// this will read "-- Invalid Country Code".
//
// The lookup is by binary chop array of known IP blocks to see if the IP address falls within any of them.
//
// Arguments: 1) The client IP address
//
// Returns: Pointer to either the country code or the location code
_hzfunc(__func__) ;
static hzString ip_not_found = "-- IP Location Not-found" ;
static hzString ip_error = "-- IP Location Error" ;
const uint32_t* pZones ; // IP ranges
uint32_t nDiv ; // Binary chop divider
uint32_t nPos ; // Position found within IP range table
uint32_t nMax ; // Total IP zones in operation
uint32_t ipval ; // IP as 32 bit uint
bool bFound ; // Position if redult
if (s_IpCity_max)
{
// There is a city level IP location table
pZones = s_IpCity_zones ;
nDiv = s_IpCity_div ;
nPos = s_IpCity_start ;
nMax = s_IpCity_max ;
}
else
{
if (!s_IpBasic_max)
return *ip_not_found ;
pZones = s_IpBasic_zones ;
nDiv = s_IpBasic_div ;
nPos = s_IpBasic_start ;
nMax = s_IpBasic_max ;
}
bFound = false ;
ipval = (uint32_t) ipa ;
for (;;)
{
if (nPos >= nMax)
{
if (!nDiv)
break ;
nPos -= nDiv ;
nDiv /= 2 ;
continue ;
}
if (ipval >= pZones[nPos+1])
{
if (!nDiv)
break ;
nPos += nDiv ;
nDiv /= 2 ;
continue ;
}
if (ipval < pZones[nPos])
{
if (!nDiv)
break ;
nPos -= nDiv ;
nDiv /= 2 ;
continue ;
}
bFound = true ;
break ;
}
if (!bFound)
{
threadLog("Not found\n") ;
return *ip_not_found ;
}
if (s_IpCity_max)
{
return s_IpCity_text + s_IpCity_osets[nPos] ;
}
return GetCountryCode(s_IpBasic_codes[nPos]) ;
}