Alibek Omarov
3 years ago
2 changed files with 431 additions and 0 deletions
@ -0,0 +1,377 @@
@@ -0,0 +1,377 @@
|
||||
#include <stdio.h> |
||||
#include <string.h> |
||||
#include "ipv6text.h" |
||||
|
||||
#ifdef _WIN32 |
||||
#ifndef snprintf |
||||
#define snprintf _snprintf |
||||
#endif |
||||
#endif |
||||
|
||||
void IPv6IPToString( char *pszOutText, const unsigned char *ip ) |
||||
{ |
||||
|
||||
// Find the longest run of consecutive zero quads.
|
||||
// If there's a tie, we want the leftmost one.
|
||||
int idxLongestRunStart = -1; |
||||
int nLongestRun = 1; // It must be at least 2 quads in a row, a single 0 must not be compressed
|
||||
int nCurrentRun = 0; |
||||
int idxQuad; |
||||
for ( idxQuad = 0 ; idxQuad < 8 ; ++idxQuad ) |
||||
{ |
||||
// Zero
|
||||
if ( ip[idxQuad*2] || ip[idxQuad*2 + 1] ) |
||||
{ |
||||
// Terminate run
|
||||
nCurrentRun = 0; |
||||
} |
||||
else |
||||
{ |
||||
// Extend (or begin) run
|
||||
++nCurrentRun; |
||||
|
||||
// Longer than previously found run?
|
||||
if ( nCurrentRun > nLongestRun ) |
||||
{ |
||||
nLongestRun = nCurrentRun; |
||||
idxLongestRunStart = idxQuad - nCurrentRun + 1; |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Print the quads
|
||||
char *p = pszOutText; |
||||
idxQuad = 0; |
||||
bool bNeedColon = false; |
||||
while ( idxQuad < 8 ) |
||||
{ |
||||
// Run of compressed zeros?
|
||||
if ( idxQuad == idxLongestRunStart ) |
||||
{ |
||||
*(p++) = ':'; |
||||
*(p++) = ':'; |
||||
bNeedColon = false; |
||||
idxQuad += nLongestRun; |
||||
} |
||||
else |
||||
{ |
||||
// Colon to separate from previous, unless
|
||||
// we are first or immediately follow compressed zero "::"
|
||||
if ( bNeedColon ) |
||||
*(p++) = ':'; |
||||
|
||||
// Next quad should should print a separator
|
||||
bNeedColon = true; |
||||
|
||||
// Assemble 16-bit quad value from the two bytes
|
||||
unsigned quad = ( (unsigned)ip[idxQuad*2] << 8U ) | ip[idxQuad*2 + 1]; |
||||
|
||||
// Manually do the hex number formatting.
|
||||
// Lowercase hex digits, with leading zeros omitted
|
||||
static const char hexdigits[] = "0123456789abcdef"; |
||||
if ( quad >= 0x0010 ) |
||||
{ |
||||
if ( quad >= 0x0100 ) |
||||
{ |
||||
if ( quad >= 0x1000 ) |
||||
*(p++) = hexdigits[ quad >> 12U ]; |
||||
*(p++) = hexdigits[ ( quad >> 8U ) & 0xf ]; |
||||
} |
||||
*(p++) = hexdigits[ ( quad >> 4U ) & 0xf ]; |
||||
} |
||||
|
||||
// Least significant digit, which is always printed
|
||||
*(p++) = hexdigits[ quad & 0xf ]; |
||||
|
||||
// On to the next one
|
||||
++idxQuad; |
||||
} |
||||
} |
||||
|
||||
// String terminator
|
||||
*p = '\0'; |
||||
} |
||||
|
||||
void IPv6AddrToString( char *pszOutText, const unsigned char *ip, uint16_t port, uint32_t scope ) |
||||
{ |
||||
char *p = pszOutText; |
||||
|
||||
// Open bracket
|
||||
*(p++) = '['; |
||||
|
||||
// Print in the IP
|
||||
IPv6IPToString( p, ip ); |
||||
|
||||
// Find the end of the string
|
||||
while (*p) |
||||
++p; |
||||
|
||||
if ( scope ) |
||||
{ |
||||
// And now the scope. Max 32-digit scope number is 10 digits
|
||||
snprintf( p, 12, "%%%d", scope ); |
||||
|
||||
// Find the end of the string
|
||||
while (*p) |
||||
++p; |
||||
} |
||||
|
||||
// And now the rest. Max 16-digit port number is 6 digits
|
||||
snprintf( p, 8, "]:%u", (unsigned int)port ); |
||||
} |
||||
|
||||
static inline int ParseIPv6Addr_HexDigitVal( char c ) |
||||
{ |
||||
if ( c >= '0' && c <= '9' ) return c - '0'; |
||||
if ( c >= 'a' && c <= 'f' ) return c - ('a' - 0xa); |
||||
if ( c >= 'A' && c <= 'F' ) return c - ('A' - 0xa); |
||||
return -1; |
||||
} |
||||
static inline int ParseIPv6Addr_DecimalDigitVal( char c ) |
||||
{ |
||||
if ( c >= '0' && c <= '9' ) return c - '0'; |
||||
return -1; |
||||
} |
||||
bool ParseIPv6Addr_IsSpace( char c ) |
||||
{ |
||||
// Newlines don't count, intentionally
|
||||
return c == ' ' || c == '\t'; |
||||
} |
||||
bool ParseIPv6Addr( const char *pszText, unsigned char *pOutIP, int *pOutPort, uint32_t *pOutScope ) |
||||
{ |
||||
while ( ParseIPv6Addr_IsSpace( *pszText ) ) |
||||
++pszText; |
||||
const char *s = pszText; |
||||
|
||||
// Skip opening bracket, if present
|
||||
if ( *s == '[' ) |
||||
{ |
||||
++s; |
||||
while ( ParseIPv6Addr_IsSpace( *s ) ) |
||||
++s; |
||||
} |
||||
|
||||
// Special case for leading "::"
|
||||
bool bQuadMustFollow = true; |
||||
unsigned char *d = pOutIP; |
||||
unsigned char *pZeroFill = NULL; |
||||
unsigned char *pEndIP = pOutIP + 16; |
||||
if ( s[0] == ':' && s[1] == ':' ) |
||||
{ |
||||
pZeroFill = d; |
||||
s += 2; |
||||
bQuadMustFollow = false; |
||||
} |
||||
|
||||
// Parse quads until we get to the end
|
||||
for (;;) |
||||
{ |
||||
// Next thing must be a quad, or end of input. Is it a quad?
|
||||
int quadDigit = ParseIPv6Addr_HexDigitVal( *s ); |
||||
if ( quadDigit < 0 ) |
||||
{ |
||||
if ( bQuadMustFollow ) |
||||
return false; |
||||
break; |
||||
} |
||||
|
||||
// No room for more quads?
|
||||
if ( d >= pEndIP ) |
||||
return false; |
||||
|
||||
++s; |
||||
int quad = quadDigit; |
||||
|
||||
// Now parse up to three additional characters
|
||||
quadDigit = ParseIPv6Addr_HexDigitVal( *s ); |
||||
if ( quadDigit >= 0 ) |
||||
{ |
||||
quad = ( quad << 4 ) | quadDigit; |
||||
++s; |
||||
|
||||
quadDigit = ParseIPv6Addr_HexDigitVal( *s ); |
||||
if ( quadDigit >= 0 ) |
||||
{ |
||||
quad = ( quad << 4 ) | quadDigit; |
||||
++s; |
||||
|
||||
quadDigit = ParseIPv6Addr_HexDigitVal( *s ); |
||||
if ( quadDigit >= 0 ) |
||||
{ |
||||
quad = ( quad << 4 ) | quadDigit; |
||||
++s; |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Stash it in the next slot, ignoring for now the issue
|
||||
// of compressed zeros
|
||||
*(d++) = (unsigned char)( quad >> 8 ); |
||||
*(d++) = (unsigned char)quad; |
||||
|
||||
// Only valid character for the IP portion is a colon.
|
||||
// Anything else ends the IP portion
|
||||
if ( *s != ':' ) |
||||
break; |
||||
|
||||
// Compressed zeros?
|
||||
if ( s[1] == ':' ) |
||||
{ |
||||
|
||||
// Eat '::'
|
||||
s += 2; |
||||
|
||||
// Can only have one range of compressed zeros
|
||||
if ( pZeroFill ) |
||||
return false; |
||||
|
||||
// Remember where to insert the compressed zeros
|
||||
pZeroFill = d; |
||||
|
||||
// An IP can end with '::'
|
||||
bQuadMustFollow = false; |
||||
} |
||||
else |
||||
{ |
||||
// If they have filed the entire IP with no compressed zeros,
|
||||
// then this is unambiguously a port number. That's not
|
||||
// necessarily the best style, but it *is* unambiguous
|
||||
// what it should mean, so let's allow it. If there
|
||||
// are compressed zeros, then this is ambiguous, and we will
|
||||
// always interpret it as a quad.
|
||||
if ( !pZeroFill && d >= pEndIP ) |
||||
break; // leave ':' as next character, for below
|
||||
|
||||
// Eat ':'
|
||||
++s; |
||||
|
||||
// A single colon must be followed by another quad
|
||||
bQuadMustFollow = true; |
||||
} |
||||
} |
||||
|
||||
// End of the IP. Do we have compressed zeros?
|
||||
if ( pZeroFill ) |
||||
{ |
||||
// How many zeros do we need to fill?
|
||||
intptr_t nZeros = pEndIP - d; |
||||
if ( nZeros <= 0 ) |
||||
return false; |
||||
|
||||
// Shift the quads after the bytes to the end
|
||||
memmove( pZeroFill+nZeros, pZeroFill, d-pZeroFill ); |
||||
|
||||
// And now fill the zeros
|
||||
memset( pZeroFill, 0, nZeros ); |
||||
} |
||||
else |
||||
{ |
||||
// No compressed zeros. Just make sure we filled the IP exactly
|
||||
if ( d != pEndIP ) |
||||
return false; |
||||
} |
||||
|
||||
if ( *s == '%' ) |
||||
{ |
||||
++s; |
||||
|
||||
// Parse scope number
|
||||
uint32_t unScope = 0; |
||||
int nScopeDigit = ParseIPv6Addr_DecimalDigitVal( *s ); |
||||
if ( nScopeDigit < 0 ) |
||||
return false; |
||||
unScope = (uint32_t)nScopeDigit; |
||||
for (;;) |
||||
{ |
||||
++s; |
||||
if ( *s == '\0' || *s == ']' || ParseIPv6Addr_IsSpace( *s ) ) |
||||
break; |
||||
nScopeDigit = ParseIPv6Addr_DecimalDigitVal( *s ); |
||||
if ( nScopeDigit < 0 ) |
||||
return false; |
||||
unScope = unScope * 10 + nScopeDigit; |
||||
} |
||||
|
||||
if ( pOutScope ) |
||||
*pOutScope = unScope; |
||||
} |
||||
else |
||||
{ |
||||
if ( pOutScope ) |
||||
*pOutScope = 0; |
||||
} |
||||
|
||||
// If we started with a bracket, then the next character MUST be a bracket.
|
||||
// (And this is the only circumstance in which a closing bracket would be legal)
|
||||
if ( *pszText == '[' ) |
||||
{ |
||||
while ( ParseIPv6Addr_IsSpace( *s ) ) |
||||
++s; |
||||
if ( *s != ']' ) |
||||
return false; |
||||
++s; |
||||
} |
||||
|
||||
// Now we are definitely at the end of the IP. Do we have a port?
|
||||
// We support all of the syntaxes mentioned in RFC5952 section 6 other
|
||||
// than the ambiguous case
|
||||
if ( *s == ':' || *s == '#' || *s == '.' || *s == 'p' || *s == 'P' ) |
||||
{ |
||||
++s; |
||||
} |
||||
else |
||||
{ |
||||
while ( ParseIPv6Addr_IsSpace( *s ) ) |
||||
++s; |
||||
if ( *s == '\0' ) |
||||
{ |
||||
// Parsed IP without port OK
|
||||
if ( pOutPort ) |
||||
*pOutPort = -1; |
||||
return true; |
||||
} |
||||
|
||||
if ( strncmp( s, "port", 4 ) == 0 ) |
||||
{ |
||||
s += 4; |
||||
while ( ParseIPv6Addr_IsSpace( *s ) ) |
||||
++s; |
||||
} |
||||
else |
||||
{ |
||||
// Extra stuff after the IP which isn't whitespace or a port
|
||||
return false; |
||||
} |
||||
} |
||||
|
||||
// We have a port. If they didn't ask for it, that's considered a parse failure.
|
||||
if ( !pOutPort ) |
||||
return false; |
||||
|
||||
// Parse port number
|
||||
int nPort = ParseIPv6Addr_DecimalDigitVal( *s ); |
||||
if ( nPort < 0 ) |
||||
return false; |
||||
for (;;) |
||||
{ |
||||
++s; |
||||
if ( *s == '\0' || ParseIPv6Addr_IsSpace( *s ) ) |
||||
break; |
||||
int portDigit = ParseIPv6Addr_DecimalDigitVal( *s ); |
||||
if ( portDigit < 0 ) |
||||
return false; |
||||
nPort = nPort * 10 + portDigit; |
||||
if ( nPort > 0xffff ) |
||||
return false; |
||||
} |
||||
|
||||
// Consume trailing whitespace; confirm nothing else in the input
|
||||
while ( ParseIPv6Addr_IsSpace( *s ) ) |
||||
++s; |
||||
if ( *s != '\0' ) |
||||
return false; |
||||
|
||||
*pOutPort = nPort; |
||||
return true; |
||||
} |
||||
|
@ -0,0 +1,54 @@
@@ -0,0 +1,54 @@
|
||||
/// Standalone plain C utilities for parsing and printing IPv6 addresses
|
||||
#pragma once |
||||
|
||||
#include <stdint.h> |
||||
#include <stdbool.h> |
||||
|
||||
/// Max length of an IPv6 string, with scope, WITHOUT port number, including \0':
|
||||
/// 0123:4567:89ab:cdef:0123:4567:89ab:cdef%4294967295
|
||||
#define k_ncchMaxIPV6AddrStringWithoutPort 51; |
||||
|
||||
/// Max number of bytes output by IPv6AddrToString, including '\0':
|
||||
/// [0123:4567:89ab:cdef:0123:4567:89ab:cdef%4294967295]:12345
|
||||
/// There are other strings that are acceptable to ParseIPv6Addr
|
||||
/// that are longer than this, but this is the longest canonical
|
||||
/// string.
|
||||
#define k_ncchMaxIPV6AddrStringWithPort 59; |
||||
|
||||
#ifdef __cplusplus |
||||
extern "C" { |
||||
#endif |
||||
|
||||
/// Format an IPv6 address to the canonical form according to RFC5952.
|
||||
/// The address should be 16 bytes (e.g. same as in6_addr::s6_addr).
|
||||
/// Your buffer MUST be at least k_ncchMaxIPV6AddrStringWithoutPort bytes.
|
||||
extern void IPv6IPToString( char *pszOutText, const unsigned char *ip ); |
||||
|
||||
/// Format IPv6 IP and port to string. This uses the recommended
|
||||
/// bracket notation, eg [1234::1]:12345. Your buffer must be
|
||||
/// at least k_ncchMaxIPV6AddrStringWithPort bytes.
|
||||
extern void IPv6AddrToString( char *pszOutText, const unsigned char *ip, uint16_t port, uint32_t scope ); |
||||
|
||||
/// Parse IPv6 address string. Returns true if parsed OK. Returns false
|
||||
/// if input cannot be parsed, or if input specifies a port but pOutPort is NULL.
|
||||
/// If input does not specify a port, and pOutPort is non-NULL, then *pOutPort is
|
||||
/// set to -1.
|
||||
///
|
||||
/// Parsing is tolerant of any unambiguous IPv6 representation, the input
|
||||
/// need not be the canonical RFC5952 representation.
|
||||
///
|
||||
/// IPv6 zones are not supported.
|
||||
///
|
||||
/// Leading and trailing whitespace is OK around the entire string,
|
||||
/// but not internal whitespace. The different methods for separating the
|
||||
/// port in RFC5952 are supported section 6, except the ambiguous case
|
||||
/// of a colon to separate the port, when the IP contains a double-colon.
|
||||
/// Brackets around an IP are OK, even if there is no port.
|
||||
///
|
||||
/// Address must point to a 16-byte buffer (e.g. same as in6_addr::s6_addr)
|
||||
/// Port is returned in host byte order.
|
||||
extern bool ParseIPv6Addr( const char *pszText, unsigned char *pOutIP, int *pOutPort, uint32_t *pOutScope ); |
||||
|
||||
#ifdef __cplusplus |
||||
} |
||||
#endif |
Loading…
Reference in new issue