NazaraEngine/src/Nazara/Network/AlgorithmNetwork.cpp

259 lines
7.2 KiB
C++

// Copyright (C) 2015 Jérôme Leclercq
// This file is part of the "Nazara Engine - Utility module"
// For conditions of distribution and use, see copyright notice in Config.hpp
#include <Nazara/Network/Algorithm.hpp>
#include <Nazara/Core/Error.hpp>
#include <cstring>
#include <Nazara/Network/Debug.hpp>
namespace Nz
{
namespace Detail
{
bool ParseDecimal(const char* str, unsigned int* number, const char** endOfRead)
{
const char* ptr = str;
unsigned int val = 0;
while (*ptr >= '0' && *ptr <= '9')
{
val *= 10;
val += *ptr - '0';
++ptr;
}
if (str == ptr)
return false;
if (number)
*number = val;
if (endOfRead)
*endOfRead = ptr;
return true;
}
bool ParseHexadecimal(const char* str, unsigned int* number, const char** endOfRead)
{
const char* ptr = str;
unsigned int val = 0;
while ((*ptr >= '0' && *ptr <= '9') || ((*ptr & 0x5F) >= 'A' && (*ptr & 0x5F) <= 'F'))
{
val *= 16;
val += (*ptr > '9') ? ((*ptr & 0x5F) - 'A' + 10) : *ptr - '0';
++ptr;
}
if (str == ptr)
return false;
if (number)
*number = val;
if (endOfRead)
*endOfRead = ptr;
return true;
}
}
// From http://rosettacode.org/wiki/Parse_an_IP_Address
// Parse a textual IPv4 or IPv6 address, optionally with port, into a binary
// array (for the address, in host order), and an optionally provided port.
// Also, indicate which of those forms (4 or 6) was parsed.
bool ParseIPAddress(const char* addressPtr, UInt8 result[16], UInt16* port, bool* isIPv6, const char** endOfRead)
{
NazaraAssert(addressPtr, "Invalid address string");
NazaraAssert(result, "Invalid result pointer");
//find first colon, dot, and open bracket
const char* colonPtr = std::strchr(addressPtr, ':');
const char* dotPtr = std::strchr(addressPtr, '.');
const char* openBracketPtr = std::strchr(addressPtr, '[');
// we'll consider this to (probably) be IPv6 if we find an open
// bracket, or an absence of dots, or if there is a colon, and it
// precedes any dots that may or may not be there
bool detectedIPv6 = openBracketPtr || !dotPtr || (colonPtr && (!dotPtr || colonPtr < dotPtr));
// OK, now do a little further sanity check our initial guess...
if (detectedIPv6)
{
// if open bracket, then must have close bracket that follows somewhere
const char* closeBracketPtr = std::strchr(addressPtr, ']');
if (openBracketPtr && (!closeBracketPtr || closeBracketPtr < openBracketPtr))
return false;
}
else // probably ipv4
{
// dots must exist, and precede any colons
if (!dotPtr || (colonPtr && colonPtr < dotPtr))
return false;
}
// OK, there should be no correctly formed strings which are miscategorized,
// and now any format errors will be found out as we continue parsing
// according to plan.
if (!detectedIPv6) //try to parse as IPv4
{
// 4 dotted quad decimal; optional port if there is a colon
// since there are just 4, and because the last one can be terminated
// differently, I'm just going to unroll any potential loop.
UInt8* resultPtr = result;
for (unsigned int i = 0; i < 4; ++i)
{
unsigned int value;
if (!Detail::ParseDecimal(addressPtr, &value, &addressPtr) || value > 255) //must be in range and followed by dot and nonempty
return false;
if (i != 3)
{
if (*addressPtr != '.')
return false;
addressPtr++;
}
*resultPtr++ = static_cast<UInt8>(value);
}
}
else // try to parse as IPv6
{
UInt8* resultPtr;
UInt8* zeroLoc;
// up to 8 16-bit hex quantities, separated by colons, with at most one
// empty quantity, acting as a stretchy run of zeros. optional port
// if there are brackets followed by colon and decimal port number.
// A further form allows an ipv4 dotted quad instead of the last two
// 16-bit quantities, but only if in the ipv4 space ::ffff:x:x .
if (openBracketPtr) // start past the open bracket, if it exists
addressPtr = openBracketPtr + 1;
resultPtr = result;
zeroLoc = nullptr; // if we find a 'zero compression' location
bool mappedIPv4 = false;
unsigned int i;
for (i = 0; i < 8; ++i) // we've got up to 8 of these, so we will use a loop
{
const char* savedPtr = addressPtr;
unsigned int value; // get value; these are hex
if (!Detail::ParseHexadecimal(addressPtr, &value, &addressPtr)) // if empty, we are zero compressing; note the loc
{
if (zeroLoc) //there can be only one!
{
// unless it's a terminal empty field, then this is OK, it just means we're done with the host part
if (resultPtr == zeroLoc)
{
--i;
break;
}
return false; // otherwise, it's a format error
}
if (*addressPtr != ':') // empty field can only be via :
return false;
if (i == 0 && *++addressPtr != ':') // leading zero compression requires an extra peek, and adjustment
return false;
zeroLoc = resultPtr;
++addressPtr;
}
else
{
if ('.' == *addressPtr) // special case of ipv4 convenience notation
{
addressPtr = savedPtr;
// who knows how to parse ipv4? we do!
UInt8 ipv4[16];
bool ipv6;
if (!ParseIPAddress(addressPtr, ipv4, nullptr, &ipv6, &addressPtr) || ipv6) // must parse and must be ipv4
return false;
// transfer addrlocal into the present location
for (unsigned int j = 0; j < 4; ++j)
*(resultPtr++) = ipv4[j];
++i; // pretend like we took another short, since the ipv4 effectively is two shorts
mappedIPv4 = true; // remember how we got here for further validation later
break; // totally done with address
}
if (value > 65535) // must be 16 bit quantity
return false;
*(resultPtr++) = value >> 8;
*(resultPtr++) = value & 0xFF;
if (*addressPtr == ':') // typical case inside; carry on
++addressPtr;
else // some other terminating character; done with this parsing parts
break;
}
}
// handle any zero compression we found
if (zeroLoc)
{
std::ptrdiff_t nHead = (int) (zeroLoc - result); // how much before zero compression
std::ptrdiff_t nTail = i * 2 - nHead; // how much after zero compression
std::ptrdiff_t nZeros = 16 - nTail - nHead; // how much zeros
std::memmove(&result[16 - nTail], zeroLoc, nTail); // scootch stuff down
std::memset(zeroLoc, 0, nZeros); // clear the compressed zeros
}
// validation of ipv4 subspace ::ffff:x.x
if (mappedIPv4)
{
static const UInt8 abyPfx[] = {0,0, 0,0, 0,0, 0,0, 0,0, 0xFF,0xFF};
if (std::memcmp(result, abyPfx, sizeof(abyPfx)) != 0)
return false;
}
// close bracket
if (openBracketPtr)
{
if (*addressPtr != ']')
return false;
++addressPtr;
}
}
// if asked to read the port
if (port)
{
if (*addressPtr == ':') // have port part
{
++addressPtr; // past the colon
unsigned int portValue;
if (!Detail::ParseDecimal(addressPtr, &portValue, nullptr) || portValue > 65535)
return false;
if (port)
*port = static_cast<UInt16>(portValue);
}
else // finished just with IP address
*port = 0; // indicate we have no port part
}
if (isIPv6)
*isIPv6 = detectedIPv6;
if (endOfRead)
*endOfRead = addressPtr;
return true;
}
}