/* $Id: netaddrstr.cpp $ */
/** @file
 * IPRT - Network Address String Handling.
 *
 * @remarks Don't add new functionality to this file, it goes into netaddrstr2.cpp
 *          or some other suitable file (legal reasons + code not up to oracle
 *          quality standards and requires rewrite from scratch).
 */

/*
 * Contributed by Oliver Loch.
 *
 * Copyright (C) 2012 Oracle Corporation
 *
 * This file is part of VirtualBox Open Source Edition (OSE), as
 * available from http://www.virtualbox.org. This file is free software;
 * you can redistribute it and/or modify it under the terms of the GNU
 * General Public License (GPL) as published by the Free Software
 * Foundation, in version 2 as it comes in the "COPYING" file of the
 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
 *
 * The contents of this file may alternatively be used under the terms
 * of the Common Development and Distribution License Version 1.0
 * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
 * VirtualBox OSE distribution, in which case the provisions of the
 * CDDL are applicable instead of those of the GPL.
 *
 * You may elect to license modified versions of this file under the
 * terms and conditions of either the GPL or the CDDL or both.
 */


/*******************************************************************************
*   Header Files                                                               *
*******************************************************************************/
#include "internal/iprt.h"
#include <iprt/net.h>

#include <iprt/mem.h>
#include <iprt/string.h>
#include <iprt/stream.h>
#include "internal/string.h"


/** @page pg_rtnetipv6_addr IPv6 Address Format
 *
 * IPv6 Addresses, their representation in text and other problems.
 *
 * The following is based on:
 *
 * - http://tools.ietf.org/html/rfc4291
 * - http://tools.ietf.org/html/rfc5952
 * - http://tools.ietf.org/html/rfc6052
 *
 *
 * Before you start using those functions, you should have an idea of
 * what you're dealing with, before you come and blame the functions...
 *
 * First of all, the address itself:
 *
 * An address is written like this: (READ THIS FINE MANUAL!)
 *
 * - 2001:db8:abc:def::1
 *
 * The characters between two colons are called a "hextet".
 * Each hextet consists of four characters and each IPv6 address
 * consists of a maximum of eight hextets. So a full blown address
 * would look like this:
 *
 * - 1111:2222:3333:4444:5555:6666:7777:8888
 *
 * The allowed characters are "0123456789abcdef". They have to be
 * lower case. Upper case is not allowed.
 *
 * *** Gaps and adress shortening
 *
 * If an address contains hextets that contain only "0"s, they
 * can be shortened, like this:
 *
 * - 1111:2222:0000:0000:0000:0000:7777:8888 -> 1111:2222::7777:8888
 *
 * The double colon represents the hextets that have been shortened "::".
 * The "::" will be called "gap" from now on.
 *
 * When shortening an address, there are some special rules that need to be applied:
 *
 * - Shorten always the longest group of hextets.
 *
 *   Let's say, you have this address: 2001:db8:0:0:0:1:0:0 then it has to be
 *   shortened to "2001:db8::1:0:0". Shortening to "2001:db8:0:0:0:1::" would
 *   return an error.
 *
 * - Two or more gaps the same size.
 *
 *   Let's say you have this address: 2001:db8:0:0:1:0:0:1. As you can see, there
 *   are two gaps, both the size of two hextets. If you shorten the last two hextets,
 *   you end up in pain, as the RFC forbids this, so the correct address is:
 *   "2001:db8::1:0:0:1"
 *
 * It's important to note that an address can only be shortened ONE TIME!
 * This is invalid: "2001:db8::1::1"
 *
 * *** The scope.
 *
 * Each address has a so called "scope" it is added to the end of the address,
 * separated by a percent sign "%". If there is no scope at the end, it defaults
 * to "0".
 *
 * So "2001:db8::1" is the same as "2001:db8::1%0".
 *
 * As in IPv6 all network interfaces can/should have the same address, the scope
 * gives you the ability to choose on which interface the system should listen.
 *
 * AFAIK, the scope can be used with unicast as well as link local addresses, but
 * it is mandatory with link local addresses (starting with fe80::).
 *
 * On Linux the default scope is the interface's name. On Windows it's just the index
 * of the interface. Run "route print -6" in the shell, to see the interface's index
 * on Winodows.
 *
 * All functions can deal with the scope, and DO NOT warn if you put garbage there.
 *
 * *** Port added to the IPv6 address
 *
 * There is only one way to add a port to an IPv6 address is to embed it in brackets:
 *
 * [2001:db8::1]:12345
 *
 * This gives you the address "2001:db8::1" and the port "12345".
 *
 * What also works, but is not recommended by rfc is to separate the port
 * by a dot:
 *
 * 2001:db8::1.12345
 *
 * It even works with embedded IPv4 addresses.
 *
 * *** Special addresses and how they are written
 *
 * The following are notations to represent "special addresses".
 *
 * "::" IN6ADDR_ANY
 * ":::123" IN6ADDR_ANY with port "123"
 * "[::]:123" IN6ADDR_ANY with port "123"
 * "[:::123]" -> NO. Not allowed and makes no sense
 * "::1" -> address of the loopback device (127.0.0.1 in v4)
 *
 * On systems with dual sockets, one can use so called embedded IPv4 addresses:
 *
 * "::ffff:192.168.1.1" results in the IPv6 address "::ffff:c0a8:0101" as two octets
 * of the IPv4 address will be converted to one hextet in the IPv6 address.
 *
 * The prefix of such addresses MUST BE "::ffff:", 10 bytes as zero and two bytes as 255.
 *
 * The so called IPv4-compatible IPv6 addresses are deprecated and no longer in use.
 *
 * *** Valid addresses and string
 *
 * If you use any of the IPv6 address functions, keep in mind, that those addresses
 * are all returning "valid" even if the underlying system (e.g. VNC) doesn't like
 * such strings.
 *
 * [2001:db8::1]
 * [2001:db8::1]:12345
 *
 * and so on. So to make sure you only pass the underlying software a pure IPv6 address
 * without any garbage, you should use the "outAddress" parameters to get a RFC compliant
 * address returned.
 *
 * So after reading the above, you'll start using the functions and see a bool called
 * "followRfc" which is true by default. This is what this bool does:
 *
 * The following addresses all represent the exact same address:
 *
 * 1 - 2001:db8::1
 * 2 - 2001:db8:0::1
 * 3 - 2001:0db8:0000:0000:0000:0000:0000:0001
 * 4 - 2001:DB8::1
 * 5 - [2001:db8::1]
 * 6 - [2001:db8:0::1]
 *
 * According to RFC 5952, number two, three, four and six are invalid.
 *
 * #2 - because there is a single hextet that hasn't been shortened
 *
 * #3 - because there has nothing been shortened (hextets 3 to 7) and
 *      there are leading zeros in at least one hextet ("0db8")
 *
 * #4 - all characters in an IPv6 address have to be lower case
 *
 * #6 - same as two but included in brackets
 *
 * If you follow RFC, the above addresses are not converted and an
 * error is returned. If you turn RFC off, you will get the expected
 * representation of the address.
 *
 * It's a nice way to convert "weird" addresses to rfc compliant addresses
 *
 */


/**
 * Parses any string and tests if it is an IPv6 Address
 *
 * This function should NOT be used directly. If you do, note
 * that no security checks are done at the moment. This can change.
 *
 * @returns iprt sstatus code, yeah, right... This function most certainly DOES
 *          NOT RETURN ANY IPRT STATUS CODES.  It's also a unreadable mess.
 * @param pszAddress       The strin that holds the IPv6 address
 * @param addressLength    The length of pszAddress
 * @param pszAddressOut    Returns a plain, full blown IPv6 address
 *                         as a char array
 * @param addressOutSize   The size of pszAddressOut (length)
 * @param pPortOut         32 bit unsigned integer, holding the port
 *                         If pszAddress doesn't contain a port, it's 0
 * @param pszScopeOut      Returns the scope of the address, if none it's 0
 * @param scopeOutSize     sizeof(pszScopeOut)
 * @param pBrackets        returns true if the address was enclosed in brackets
 * @param pEmbeddedV4      returns true if the address is an embedded IPv4 address
 * @param followRfc        if set to true, the function follows RFC (default)
 */
static int rtStrParseAddrStr6(const char *pszAddress, size_t addressLength, char *pszAddressOut, size_t addressOutSize, uint32_t *pPortOut, char *pszIfIdOut, size_t ifIdOutSize, bool *pBrackets, bool *pEmbeddedV4, bool followRfc)
{
    /************************\
     *  Pointer Hell Ahead  *
    \************************/

    const char szIpV6AddressChars[] = "ABCDEF01234567890abcdef.:[]%"; // order IMPORTANT
    const char szIpV4AddressChars[] = "01234567890.:[]"; // order IMPORTANT
    const char szLinkLocalPrefix[] = "FfEe8800"; //
    const char *pszIpV6AddressChars = NULL, *pszIpV4AddressChars = NULL, *pszLinkLocalPrefix = NULL;

    char *pszSourceAddress = NULL, *pszSourceAddressStart = NULL;
    char *pszResultAddress = NULL, *pszResultAddressStart = NULL;
    char *pszResultAddress4 = NULL, *pszResultAddress4Start = NULL;
    char *pszResultPort = NULL, *pszResultPortStart = NULL;
    char *pszInternalAddress = NULL, *pszInternalAddressStart = NULL;
    char *pszInternalPort = NULL, *pszInternalPortStart = NULL;

    char *pStart = NULL, *pNow = NULL, *pNext = NULL, *pNowChar = NULL, *pIfId = NULL, *pIfIdEnd = NULL;
    char *pNowDigit = NULL, *pFrom = NULL, *pTo = NULL, *pLast = NULL;
    char *pGap = NULL, *pMisc = NULL, *pDotStart = NULL, *pFieldStart = NULL, *pFieldEnd = NULL;
    char *pFieldStartLongest = NULL, *pBracketOpen = NULL, *pBracketClose = NULL;
    char *pszRc = NULL;

    bool isLinkLocal = false;
    char szDummy[4];

    uint8_t *pByte = NULL;
    uint32_t byteOut = 0;
    uint16_t returnValue = 0;
    uint32_t colons = 0;
    uint32_t colonsOverAll = 0;
    uint32_t fieldLength = 0;
    uint32_t dots = 0;
    size_t gapSize = 0;
    uint32_t intPortOut = 0;

    pszIpV4AddressChars = &szIpV4AddressChars[0];
    pszIpV6AddressChars = &szIpV6AddressChars[6];
    pszLinkLocalPrefix = &szLinkLocalPrefix[6];

    if (!followRfc)
        pszIpV6AddressChars = &szIpV6AddressChars[0];

    if (addressLength<2)
        returnValue = 711;

    pszResultAddressStart = (char *)RTMemTmpAlloc(34);
    pszInternalAddressStart = (char *)RTMemTmpAlloc(34);
    pszInternalPortStart = (char * )RTMemTmpAlloc(10);

    if (! (pszResultAddressStart && pszInternalAddressStart && pszInternalPortStart))
    {
        if (pszResultAddressStart)
            RTMemTmpFree(pszResultAddressStart);

        if (pszInternalAddressStart)
            RTMemTmpFree(pszInternalAddressStart);

        if (pszInternalPortStart)
            RTMemTmpFree(pszInternalPortStart);

        return -701;
    }

    memset(szDummy, '\0', 4);

    pszResultAddress = pszResultAddressStart;
    memset(pszResultAddressStart, '\0', 34);

    pszInternalAddress = pszInternalAddressStart;
    memset(pszInternalAddressStart, '\0' , 34);

    pszInternalPort = pszInternalPortStart;
    memset(pszInternalPortStart, '\0', 10);

    pszSourceAddress = pszSourceAddressStart = (char *)pszAddress;

    pFrom = pTo = pStart = pLast = pszSourceAddressStart;

    while (*pszSourceAddress != '\0' && !returnValue)
    {
        pNow = NULL;
        pNext = NULL;
        pNowChar = NULL;
        pNowDigit = NULL;

        pNow = pszSourceAddress;
        pNext = pszSourceAddress + 1;

        if (!pFrom)
            pFrom = pTo = pNow;

        pNowChar = (char *)memchr(pszIpV6AddressChars, *pNow, strlen(pszIpV6AddressChars));
        pNowDigit = (char *)memchr(pszIpV6AddressChars, *pNow, strlen(pszIpV6AddressChars) - 5);

        if (pszResultPort)
        {
            if (pLast && (pszResultPort == pszSourceAddressStart))
            {
                if (*pLast == '\0')
                    returnValue = 721;

                pszResultPortStart = (char *)RTMemTmpAlloc(10);

                if (!pszResultPortStart)
                    returnValue = 702;

                memset(pszResultPortStart, '\0', 10);
                pszResultPort = pszResultPortStart;
                pszSourceAddress = pLast;
                pMisc = pLast;
                pLast = NULL;
                continue;
            }

            pNowDigit = NULL;
            pNowDigit = (char *)memchr(pszIpV4AddressChars, *pNow, strlen(pszIpV4AddressChars) - 4);

            if (strlen(pszResultPortStart) == 5)
                returnValue = 11;

            if (*pNow == '0' && pszResultPort == pszResultPortStart && *pNext != '\0' && (pNow - pMisc) < 5 )
            {
                pszSourceAddress++;
                continue;
            }

            if (pNowDigit)
            {
                *pszResultPort = *pNowDigit;
                pszResultPort++;
                pszSourceAddress++;
                continue;
            }
            else
                returnValue = 12;
        }

        if (pszResultAddress4)
        {
            if (pszResultAddress4 == pszSourceAddressStart && pLast)
            {
                dots = 0;
                pszResultAddress4 = NULL;
                pszResultAddress4Start = NULL;
                pszResultAddress4Start = (char *)RTMemTmpAlloc(20);

                if (!pszResultAddress4Start)
                {
                    returnValue = 401;
                    break;
                }

                memset(pszResultAddress4Start, '\0', 20);
                pszResultAddress4 = pszResultAddress4Start;
                pszSourceAddress = pLast;
                pFrom = pLast;
                pTo = pLast;
                pLast = NULL;
                continue;
            }

            pTo = pNow;
            pNowDigit = NULL;
            pNowDigit = (char *)memchr(pszIpV4AddressChars, *pNow, strlen(pszIpV4AddressChars) - 4);

            if (!pNowDigit && *pNow != '.' && *pNow != ']' && *pNow != ':' && *pNow != '%')
                returnValue = 412;

            if ((pNow - pFrom) > 3)
            {
                returnValue = 402;
                break;
            }

            if (pNowDigit && *pNext != '\0')
            {
                pszSourceAddress++;
                continue;
            }

            if (!pNowDigit  && !pBracketOpen && (*pNext == '.' || *pNext == ']' || *pNext == ':'))
                returnValue = 411;

            memset(pszResultAddress4, '0', 3);
            pMisc = pszResultAddress4 + 2;
            pszResultAddress4 = pszResultAddress4 + 3;

            if (*pNow != '.' && !pNowDigit && strlen(pszResultAddress4Start) < 9)
                returnValue = 403;

            if ((pTo - pFrom) > 0)
                pTo--;

            dots++;

            while (pTo >= pFrom)
            {
                *pMisc = *pTo;
                pMisc--;
                pTo--;
            }

            if (dots == 4 && *pNow == '.')
            {
                if (!pBracketOpen)
                {
                    pszResultPort = pszSourceAddressStart;
                    pLast = pNext;
                }
                else
                {
                    returnValue = 409;
                }
            }

            dots = 0;

            pFrom = pNext;
            pTo = pNext;

            if (strlen(pszResultAddress4Start) > 11)
                pszResultAddress4 = NULL;

            if ((*pNow == ':' || *pNow == '.') && strlen(pszResultAddress4Start) == 12)
            {
                pLast = pNext;
                pszResultPort = pszSourceAddressStart;
            }

            if (*pNow == '%')
            {
                pIfId =  pNow;
                pLast = pNow;
                continue;
            }
            pszSourceAddress = pNext;

            if (*pNow != ']')
                continue;

            pFrom = pNow;
            pTo = pNow;
        }

        if (pIfId && (!pIfIdEnd))
        {
            if (*pIfId == '%' && pIfId == pLast && *pNext != '\0')
            {
                pFrom = pNext;
                pIfId = pNext;
                pLast = NULL;

                pszSourceAddress++;
                continue;
            }

            if (*pNow == '%' && pIfId <= pNow)
            {
                returnValue = 442;
                break;
            }

            if (*pNow != ']' && *pNext != '\0')
            {
                pTo = pNow;
                pszSourceAddress++;
                continue;
            }

            if (*pNow == ']')
            {
                pIfIdEnd = pNow - 1;
                pFrom = pNow;
                pTo = pNow;
                continue;
            }
            else
            {
                pIfIdEnd = pNow;
                pFrom = NULL;
                pTo = NULL;
                pszSourceAddress++;
                continue;
            }
        }

        if (!pNowChar)
        {
            returnValue = 254;

            if (followRfc)
            {
                pMisc = (char *)memchr(&szIpV6AddressChars[0], *pNow, strlen(&szIpV6AddressChars[0]));

                if (pMisc)
                    returnValue = 253;
            }
        }

        if (strlen(pszResultAddressStart) > 32 && !pszResultAddress4Start)
            returnValue = 255;

        if (pNowDigit && *pNext != '\0' && colons == 0)
        {
            pTo = pNow;
            pszSourceAddress++;
            continue;
        }

        if (*pNow == ':' && *pNext != '\0')
        {
            colonsOverAll++;
            colons++;
            pszSourceAddress++;
            continue;
        }

        if (*pNow == ':' )
        {
            colons++;
            colonsOverAll++;
        }

        if (*pNow == '.')
        {
            pMisc = pNow;

            while (*pMisc != '\0' && *pMisc != ']')
            {
                if (*pMisc == '.')
                    dots++;

                pMisc++;
            }
        }

        if (*pNow == ']')
        {
            if (pBracketClose)
                returnValue = 77;

            if (!pBracketOpen)
                returnValue = 22;

            if (*pNext == ':' || *pNext == '.')
            {
                pszResultPort = pszSourceAddressStart;
                pLast = pNext + 1;
            }

            if (pFrom == pNow)
                pFrom = NULL;

            pBracketClose = pNow;
        }

        if (*pNow == '[')
        {
            if (pBracketOpen)
                returnValue = 23;

            if (pStart != pNow)
                returnValue = 24;

            pBracketOpen = pNow;
            pStart++;
            pFrom++;
            pszSourceAddress++;
            continue;
        }

        if (*pNow == '%')
        {
            if (pIfId)
                returnValue = 441;

            pLast = pNext;
            pIfId = pNext;
        }

        if (colons > 0)
        {
            if (colons == 1)
            {
                if (pStart + 1 == pNow )
                    returnValue = 31;

                if (*pNext == '\0' && !pNowDigit)
                    returnValue = 32;

                pLast = pNow;
            }

            if (colons == 2)
            {
                if (pGap)
                    returnValue = 33;

                pGap = pszResultAddress + 4;

                if (pStart + 1 == pNow || pStart + 2 == pNow)
                {
                    pGap = pszResultAddressStart;
                    pFrom = pNow;
                }

                if (*pNext == '\0' && !pNowDigit)
                    pszSourceAddress++;

                if (*pNext != ':' && *pNext != '.')
                    pLast = pNow;
            }

            if (colons == 3)
            {
                pFrom = pLast;
                pLast = pNow;

                if (*pNext == '\0' && !pNowDigit)
                    returnValue = 34;

                if (pBracketOpen)
                    returnValue = 35;

                if (pGap && followRfc)
                    returnValue = 36;

                if (!pGap)
                    pGap = pszResultAddress + 4;

                if (pStart + 3 == pNow)
                {
                    pszResultPort = pszSourceAddressStart;
                    pGap = pszResultAddress;
                    pFrom = NULL;
                }

                if (pNowDigit)
                {
                    pszResultPort = pszSourceAddressStart;
                }
            }
        }
        if (*pNext == '\0' && colons == 0 && !pIfIdEnd)
        {
            pFrom = pLast;

            if (pNowDigit)
                pTo = pNow;

            pLast = NULL;
        }

        if (dots > 0)
        {
            if (dots == 1)
            {
                pszResultPort = pszSourceAddressStart;
                pLast = pNext;
            }

            if (dots == 4 && pBracketOpen)
                returnValue = 601;

            if (dots == 3 || dots == 4)
            {
                pszResultAddress4 = pszSourceAddressStart;
                pLast = pFrom;
                pFrom = NULL;
            }

            if (dots > 4)
                returnValue = 603;

            dots = 0;
        }

        if (pFrom && pTo)
        {
            if (pTo - pFrom > 3)
            {
                returnValue = 51;
                break;
            }

            if (followRfc)
            {
                if ((pTo - pFrom > 0) && *pFrom == '0')
                    returnValue = 101;

                if ((pTo - pFrom) == 0 && *pFrom == '0' && colons == 2)
                    returnValue = 102;

                if ((pTo - pFrom) == 0 && *pFrom == '0' && pszResultAddress == pGap)
                    returnValue = 103;

                if ((pTo - pFrom) == 0 && *pFrom == '0')
                {
                    if (!pFieldStart)
                    {
                        pFieldStart = pszResultAddress;
                        pFieldEnd = pszResultAddress + 4;
                    }
                    else
                    {
                        pFieldEnd = pFieldEnd + 4;
                    }
                }
                else
                {
                    if ((size_t)(pFieldEnd - pFieldStart) > fieldLength)
                    {
                        fieldLength = pFieldEnd - pFieldStart;
                        pFieldStartLongest = pFieldStart;
                    }

                    pFieldStart = NULL;
                    pFieldEnd = NULL;
                }
            }
            if (!(pGap == pszResultAddressStart && (size_t)(pNow - pStart) == colons))
            {
                memset(pszResultAddress, '0', 4);
                pMisc = pszResultAddress + 3;
                pszResultAddress = pszResultAddress + 4;

                if (pFrom == pStart && (pTo - pFrom) == 3)
                {
                    isLinkLocal = true;

                    while (pTo >= pFrom)
                    {
                        *pMisc = *pTo;

                        if (*pTo != *pszLinkLocalPrefix && *pTo != *(pszLinkLocalPrefix + 1))
                            isLinkLocal = false;

                        pTo--;
                        pMisc--;
                        pszLinkLocalPrefix = pszLinkLocalPrefix - 2;
                    }
                }
                else
                {
                    while (pTo >= pFrom)
                    {
                        *pMisc = *pTo;
                        pMisc--;
                        pTo--;
                    }
                }
            }

            pFrom = pNow;
            pTo = pNow;

        }
        if (*pNext == '\0' && colons == 0)
            pszSourceAddress++;

        if (*pNext == '\0' && !pBracketClose && !pszResultPort)
            pTo = pNext;

        colons = 0;
    } // end of loop

    if (!returnValue && colonsOverAll < 2)
        returnValue = 252;

    if (!returnValue && (pBracketOpen && !pBracketClose))
        returnValue = 25;

    if (!returnValue && pGap)
    {
        gapSize = 32 - strlen(pszResultAddressStart);

        if (followRfc)
        {
            if (gapSize < 5)
                returnValue = 104;

            if (fieldLength > gapSize)
                returnValue = 105;

            if (fieldLength == gapSize && pFieldStartLongest < pGap)
                returnValue = 106;
        }

        pszResultAddress = pszResultAddressStart;
        pszInternalAddress = pszInternalAddressStart;

        if (!returnValue && pszResultAddress4Start)
        {
            if (strlen(pszResultAddressStart) > 4)
                returnValue = 405;

            pszResultAddress = pszResultAddressStart;

            if (pGap != pszResultAddressStart)
                returnValue = 407;

            memset(pszInternalAddressStart, '0', 20);
            pszInternalAddress = pszInternalAddressStart + 20;

            for (int i = 0; i < 4; i++)
            {
                if (*pszResultAddress != 'f' && *pszResultAddress != 'F')
                {
                    returnValue = 406;
                    break;
                }

                *pszInternalAddress = *pszResultAddress;
                pszResultAddress++;
                pszInternalAddress++;
            }
            pszResultAddress4 = pszResultAddress4Start;

            for (int i = 0; i<4; i++)
            {
                memcpy(szDummy, pszResultAddress4, 3);

                int rc = RTStrToUInt32Ex((const char *)&szDummy[0], NULL, 16, &byteOut);

                if (rc == 0 && byteOut < 256)
                {
                    RTStrPrintf(szDummy, 3, "%02x", byteOut);
                    memcpy(pszInternalAddress, szDummy, 2);
                    pszInternalAddress = pszInternalAddress + 2;
                    pszResultAddress4 = pszResultAddress4 + 3;
                    memset(szDummy, '\0', 4);
                }
                else
                {
                    returnValue = 499;
                }
            }
        }
        else
        {
            while (!returnValue && pszResultAddress != pGap)
            {
                *pszInternalAddress = *pszResultAddress;
                pszResultAddress++;
                pszInternalAddress++;
            }

            memset(pszInternalAddress, '0', gapSize);
            pszInternalAddress = pszInternalAddress + gapSize;

            while (!returnValue && *pszResultAddress != '\0')
            {
                *pszInternalAddress = *pszResultAddress;
                pszResultAddress++;
                pszInternalAddress++;
            }
        }
    }
    else
    {
        if (!returnValue)
        {
            if (strlen(pszResultAddressStart) != 32)
                returnValue = 111;

            if (followRfc)
            {
                if (fieldLength > 4)
                    returnValue = 112;
            }

            memcpy(pszInternalAddressStart, pszResultAddressStart, strlen(pszResultAddressStart));
        }
    }

    if (pszResultPortStart)
    {
        if (strlen(pszResultPortStart) > 0 && strlen(pszResultPortStart) < 6)
        {
            memcpy(pszInternalPortStart, pszResultPortStart, strlen(pszResultPortStart));

            intPortOut = 0;
            int rc = RTStrToUInt32Ex(pszInternalPortStart, NULL, 10, &intPortOut);

            if (rc == 0)
            {
                if (!(intPortOut > 0 && intPortOut < 65536))
                    intPortOut = 0;
            }
            else
            {
                returnValue = 888;
            }
        }
        else
        {
            returnValue = 889;
        }
    }

    /*
       full blown address 32 bytes, no colons -> pszInternalAddressStart
       port as string -> pszResultPortStart
       port as binary integer -> intPortOut
       interface id in pIfId and pIfIdEnd

       Now fill the out parameters.

     */

    if (!returnValue && pszAddressOut)
    {
        if (strlen(pszInternalAddressStart) < addressOutSize)
        {
            pszRc = NULL;
            pszRc = (char *)memset(pszAddressOut, '\0', addressOutSize);

            if (!pszRc)
                returnValue = 910;

            pszRc = NULL;

            pszRc = (char *)memcpy(pszAddressOut, pszInternalAddressStart, strlen(pszInternalAddressStart));

            if (!pszRc)
                returnValue = 911;
        }
        else
        {
            returnValue = 912;
        }
    }

    if (!returnValue && pPortOut)
    {
        *pPortOut = intPortOut;
    }

    if (!returnValue && pszIfIdOut)
    {
        if (pIfIdEnd && pIfId)
        {
            if ((size_t)(pIfIdEnd - pIfId) + 1 < ifIdOutSize)
            {
                pszRc = NULL;
                pszRc = (char *)memset(pszIfIdOut, '\0', ifIdOutSize);

                if (!pszRc)
                    returnValue = 913;

                pszRc = NULL;
                pszRc = (char *)memcpy(pszIfIdOut, pIfId, (pIfIdEnd - pIfId) + 1);

                if (!pszRc)
                    returnValue = 914;
            }
            else
            {
                returnValue = 915;
            }
        }
        else
        {
            pszRc = NULL;
            pszRc = (char *)memset(pszIfIdOut, '\0', ifIdOutSize);

            if (!pszRc)
                returnValue = 916;
        }
        // temporary hack
        if (isLinkLocal && (strlen(pszIfIdOut) < 1))
        {
            memset(pszIfIdOut, '\0', ifIdOutSize);
            *pszIfIdOut = '%';
            pszIfIdOut++;
            *pszIfIdOut = '0';
            pszIfIdOut++;
        }
    }

    if (pBracketOpen && pBracketClose && pBrackets)
        *pBrackets = true;

    if (pEmbeddedV4 && pszResultAddress4Start)
        *pEmbeddedV4 = true;

    if (pszResultAddressStart)
        RTMemTmpFree(pszResultAddressStart);

    if (pszResultPortStart)
        RTMemTmpFree(pszResultPortStart);

    if (pszResultAddress4Start)
        RTMemTmpFree(pszResultAddress4Start);

    if (pszInternalAddressStart)
        RTMemTmpFree(pszInternalAddressStart);

    if (pszInternalPortStart)
        RTMemTmpFree(pszInternalPortStart);

    return (uint32_t)(returnValue - (returnValue * 2)); // make it negative...
}

/**
 * Takes a string and returns a RFC compliant string of the address
 * This function SHOULD NOT be used directly. It expects a 33 byte
 * char array with a full blown IPv6 address without separators.
 *
 * @returns iprt status code.
 * @param psz              The string to convert
 * @param pszAddrOut       The char[] that will hold the result
 * @param addOutSize       The size of the char[] from above.
 * @param pszPortOut       char[] for the text representation of the port
 * @param portOutSize      sizeof(pszPortOut);
 */
DECLHIDDEN(int) rtStrToIpAddr6Str(const char *psz, char *pszAddrOut, size_t addrOutSize, char *pszPortOut, size_t portOutSize, bool followRfc)
{
    char *pStart = NULL;
    char *pGapStart = NULL;
    char *pGapEnd = NULL;
    char *pGapTStart = NULL;
    char *pGapTEnd = NULL;
    char *pCurrent = NULL;
    char *pOut = NULL;

    if (!psz || !pszAddrOut)
        return VERR_NOT_SUPPORTED;

    if (addrOutSize < 40)
        return VERR_NOT_SUPPORTED;

    pStart = (char *)psz;
    pCurrent = (char *)psz;
    pGapStart = (char *)psz;
    pGapEnd =  (char *)psz;

    while (*pCurrent != '\0')
    {
        if (*pCurrent != '0')
            pGapTStart = NULL;

        if ((pCurrent - pStart) % 4 == 0) // ok, start of a hextet
        {
            if (*pCurrent == '0' &&  !pGapTStart)
                pGapTStart = pCurrent;
        }

        if ((pCurrent - pStart) % 4 == 3)
        {
            if (*pCurrent == '0' && pGapTStart)
                pGapTEnd = pCurrent;

            if (pGapTStart && pGapTEnd)
            {
                pGapTEnd = pCurrent;

                if ((pGapTEnd - pGapTStart) > (pGapEnd - pGapStart))
                {
                    pGapEnd = pGapTEnd;
                    pGapStart = pGapTStart;
                }
            }
        }

        pCurrent++;
    }

    pCurrent = (char *)psz;
    pStart = (char *)psz;
    pOut = (char *)pszAddrOut;

    while (*pCurrent != '\0')
    {
        if (*pCurrent != '0')
            pGapTStart = NULL;

        if (!pGapTStart)
        {
            *pOut = *pCurrent;
            pOut++;
        }

        if ((pCurrent - pStart) % 4 == 3)
        {
            if (pGapTStart && *pCurrent == '0')
            {
                *pOut = *pCurrent;
                pOut++;
            }

            if (*(pCurrent + 1) != '\0')
            {
                *pOut = ':';
                pOut++;
            }

            pGapTStart = pCurrent + 1;
        }

        if ((pCurrent + 1) == pGapStart && (pGapEnd - pGapStart) > 3)
        {
            *pOut = ':';
            pOut++;
            pCurrent = pGapEnd;
        }

        pCurrent++;
    }

    return VINF_SUCCESS;
}


/**
 * Tests if the given string is a valid IPv6 address.
 *
 * @returns 0 if valid, some random number if not.  THIS IS NOT AN IPRT STATUS!
 * @param psz                  The string to test
 * @param pszResultAddress     plain address, optional read "valid addresses
 *                             and strings" above.
 * @param resultAddressSize    size of pszResultAddress
 * @param addressOnly          return only the plain address (no scope)
 *                             Ignored, and will always return the if id
 */
static int rtNetIpv6CheckAddrStr(const char *psz, char *pszResultAddress, size_t resultAddressSize, bool addressOnly, bool followRfc)
{
    int rc;
    int rc2;
    int returnValue = VERR_NOT_SUPPORTED; /* gcc want's this initialized, I understand its confusion. */

    char *p = NULL, *pl = NULL;

    size_t memAllocMaxSize = RT_MAX(strlen(psz), resultAddressSize) + 40;

    char *pszAddressOutLocal = (char *)RTMemTmpAlloc(memAllocMaxSize);
    char *pszIfIdOutLocal = (char *)RTMemTmpAlloc(memAllocMaxSize);
    char *pszAddressRfcOutLocal = (char *)RTMemTmpAlloc(memAllocMaxSize);

    if (!pszAddressOutLocal || !pszIfIdOutLocal || !pszAddressRfcOutLocal)
        return VERR_NO_TMP_MEMORY;

    memset(pszAddressOutLocal, '\0', memAllocMaxSize);
    memset(pszIfIdOutLocal, '\0', memAllocMaxSize);
    memset(pszAddressRfcOutLocal, '\0', memAllocMaxSize);

    rc = rtStrParseAddrStr6(psz, strlen(psz), pszAddressOutLocal, memAllocMaxSize, NULL, pszIfIdOutLocal, memAllocMaxSize, NULL, NULL, followRfc);

    if (rc == 0)
        returnValue = VINF_SUCCESS;

    if (rc == 0 && pszResultAddress)
    {
        // convert the 32 characters to a valid, shortened ipv6 address

        rc2 = rtStrToIpAddr6Str((const char *)pszAddressOutLocal, pszAddressRfcOutLocal, memAllocMaxSize, NULL, 0, followRfc);

        if (rc2 != 0)
            returnValue = 951;

        // this is a temporary solution
        if (!returnValue && strlen(pszIfIdOutLocal) > 0) // the if identifier is copied over _ALWAYS_ && !addressOnly)
        {
            p = pszAddressRfcOutLocal + strlen(pszAddressRfcOutLocal);

            *p = '%';

            p++;

            pl = (char *)memcpy(p, pszIfIdOutLocal, strlen(pszIfIdOutLocal));

            if (!pl)
                returnValue = VERR_NOT_SUPPORTED;
        }

        pl = NULL;

        pl = (char *)memcpy(pszResultAddress, pszAddressRfcOutLocal, strlen(pszAddressRfcOutLocal));

        if (!pl)
            returnValue = VERR_NOT_SUPPORTED;
    }

    if (rc != 0)
        returnValue = VERR_NOT_SUPPORTED;

    if (pszAddressOutLocal)
        RTMemTmpFree(pszAddressOutLocal);

    if (pszAddressRfcOutLocal)
        RTMemTmpFree(pszAddressRfcOutLocal);

    if (pszIfIdOutLocal)
        RTMemTmpFree(pszIfIdOutLocal);

    return returnValue;

}


RTDECL(bool) RTNetIsIPv6AddrStr(const char *pszAddress)
{
    return rtNetIpv6CheckAddrStr(pszAddress, NULL, 0, true, true) >= 0;
}
RT_EXPORT_SYMBOL(RTNetIsIPv6AddrStr);


RTDECL(bool) RTNetIsIPv4AddrStr(const char *pszAddress)
{
    static char const s_szIpV4Digits[] = "0123456789.";

    size_t cchAddress = strlen(pszAddress);
    if (cchAddress < 7 || cchAddress > 15)
        return false;

    const char *pStart, *pFrom, *pTo, *pNow;
    pStart = pNow = pFrom = pTo = pszAddress;

    unsigned cOctets = 0;
    while (*pNow != '\0')
    {
        const char *pChar  = (const char *)memchr(s_szIpV4Digits, *pNow, sizeof(s_szIpV4Digits) - 1);
        const char *pDigit = (const char *)memchr(s_szIpV4Digits, *pNow, sizeof(s_szIpV4Digits) - 2);
        const char *pNext  = pNow + 1;

        if (!pChar)
            return false;

        if (pDigit && *pNext != '\0')
        {
            pTo = pNow;
            pNow++;
            continue;
        }

        if (*pNow == '.' || *pNext == '\0')
        {
            if (*pNext == '\0')
                pTo = pNow;

            size_t cchSub = pTo - pFrom;
            if (cchSub > 2)
                return false;

            char szDummy[4] = { 0, 0, 0, 0 };
            memcpy(szDummy, pFrom, cchSub + 1);

            int rc = RTStrToUInt8Ex(szDummy, NULL, 10, NULL);
            if (rc != VINF_SUCCESS)
                return false;

            cOctets++;
            if (cOctets > 4)
                return false;
            pFrom = pNext;
        }
        pNow++;
    }

    if (cOctets != 4)
        return false;

    return true;
}
RT_EXPORT_SYMBOL(RTNetIsIPv4AddrStr);
