/*
 * Copyright (C) 1996-2026 The Squid Software Foundation and contributors
 *
 * Squid software is distributed under GPLv2+ license and includes
 * contributions from numerous individuals and organizations.
 * Please see the COPYING and CONTRIBUTORS files for details.
 */

/* DEBUG: section 14    IP Storage and Handling */

#include "squid.h"
#include "debug/Stream.h"
#include "ip/Address.h"
#include "ip/tools.h"
#include "util.h"

#include <cassert>
#include <cstring>
#if HAVE_ARPA_INET_H
/* for inet_ntoa() */
#include <arpa/inet.h>
#endif
#if HAVE_WS2TCPIP_H
// Windows IPv6 definitions
#include <ws2tcpip.h>
#endif

// some OS (ie WIndows) define IN6_ADDR_EQUAL instead
#if !defined(IN6_ARE_ADDR_EQUAL) && _SQUID_WINDOWS_
#define IN6_ARE_ADDR_EQUAL IN6_ADDR_EQUAL
#endif

/* Debugging only. Dump the address content when a fatal assert is encountered. */
#define IASSERT(a,b)  \
    if(!(b)){   printf("assert \"%s\" at line %d\n", a, __LINE__); \
        printf("Ip::Address invalid? with isIPv4()=%c, isIPv6()=%c\n",(isIPv4()?'T':'F'),(isIPv6()?'T':'F')); \
        printf("ADDRESS:"); \
        for(unsigned int i = 0; i < sizeof(mSocketAddr_.sin6_addr); ++i) { \
            printf(" %x", mSocketAddr_.sin6_addr.s6_addr[i]); \
        } printf("\n"); assert(b); \
    }

std::optional<Ip::Address>
Ip::Address::Parse(const char * const raw)
{
    Address tmp;
    // TODO: Merge with lookupHostIP() after removing DNS lookups from Ip.
    if (tmp.lookupHostIP(raw, true))
        return tmp;
    return std::nullopt;
}

int
Ip::Address::cidr() const
{
    uint8_t shift,ipbyte;
    uint8_t bit,caught;
    int len = 0;
    const uint8_t *ptr= mSocketAddr_.sin6_addr.s6_addr;

    /* Let's scan all the bits from Most Significant to Least */
    /* Until we find an "0" bit. Then, we return */
    shift=0;

    /* return IPv4 CIDR for any Mapped address */
    /* Thus only check the mapped bit */

    if ( !isIPv6() ) {
        shift = 12;
    }

    for (; shift<sizeof(mSocketAddr_.sin6_addr) ; ++shift) {
        ipbyte= *(ptr+shift);

        if (ipbyte == 0xFF) {
            len += 8;
            continue ;  /* A short-cut */
        }

        for (caught = 0, bit= 7 ; !caught && (bit <= 7); --bit) {
            caught = ((ipbyte & 0x80) == 0x00);  /* Found a '0' at 'bit' ? */

            if (!caught)
                ++len;

            ipbyte <<= 1;
        }

        if (caught)
            break; /* We have found the most significant "0" bit.  */
    }

    return len;
}

int
Ip::Address::applyMask(Ip::Address const &mask_addr)
{
    uint32_t *p1 = (uint32_t*)(&mSocketAddr_.sin6_addr);
    uint32_t const *p2 = (uint32_t const *)(&mask_addr.mSocketAddr_.sin6_addr);
    unsigned int blen = sizeof(mSocketAddr_.sin6_addr)/sizeof(uint32_t);
    unsigned int changes = 0;

    for (unsigned int i = 0; i < blen; ++i) {
        if ((p1[i] & p2[i]) != p1[i])
            ++changes;

        p1[i] &= p2[i];
    }

    return changes;
}

void
Ip::Address::turnMaskedBitsOn(const Address &mask)
{
    const auto addressWords = reinterpret_cast<uint32_t*>(&mSocketAddr_.sin6_addr);
    const auto maskWords = reinterpret_cast<const uint32_t*>(&mask.mSocketAddr_.sin6_addr);
    const auto len = sizeof(mSocketAddr_.sin6_addr)/sizeof(uint32_t);
    for (size_t i = 0; i < len; ++i)
        addressWords[i] |= ~maskWords[i];
}

void
Ip::Address::applyClientMask(const Address &mask)
{
    if (!isLocalhost() && isIPv4())
        (void)applyMask(mask);
}

bool
Ip::Address::applyMask(const unsigned int cidrMask, int mtype)
{
    uint8_t clearbits = 0;
    uint8_t* p = nullptr;

    // validation and short-cuts.
    if (cidrMask > 128)
        return false;

    if (cidrMask > 32 && mtype == AF_INET)
        return false;

    if (cidrMask == 0) {
        /* CIDR /0 is NoAddr regardless of the IPv4/IPv6 protocol */
        setNoAddr();
        return true;
    }

    clearbits = (uint8_t)( (mtype==AF_INET6?128:32) - cidrMask);

    // short-cut
    if (clearbits == 0)
        return true;

    p = (uint8_t*)(&mSocketAddr_.sin6_addr) + 15;

    for (; clearbits>0 && p >= (uint8_t*)&mSocketAddr_.sin6_addr ; --p ) {
        if (clearbits < 8) {
            *p &= ((0xFF << clearbits) & 0xFF);
            clearbits = 0;
        } else {
            *p &= 0x00;
            clearbits -= 8;
        }
    }

    return true;
}

bool
Ip::Address::isSockAddr() const
{
    return (mSocketAddr_.sin6_port != 0);
}

bool
Ip::Address::isIPv4() const
{
    return IN6_IS_ADDR_V4MAPPED( &mSocketAddr_.sin6_addr );
}

bool
Ip::Address::isIPv6() const
{
    return !isIPv4();
}

bool
Ip::Address::isAnyAddr() const
{
    return IN6_IS_ADDR_UNSPECIFIED(&mSocketAddr_.sin6_addr) || IN6_ARE_ADDR_EQUAL(&mSocketAddr_.sin6_addr, &v4_anyaddr);
}

/// NOTE: Does NOT clear the Port stored. Only the Address and Type.
void
Ip::Address::setAnyAddr()
{
    memset(&mSocketAddr_.sin6_addr, 0, sizeof(struct in6_addr) );
}

/// NOTE: completely empties the Ip::Address structure. Address, Port, Type, everything.
void
Ip::Address::setEmpty()
{
    memset(&mSocketAddr_, 0, sizeof(mSocketAddr_) );
}

#if _SQUID_AIX_
// Bug 2885 comment 78 explains.
// In short AIX has a different netinet/in.h union definition
const struct in6_addr Ip::Address::v4_localhost = {{{ 0x00000000, 0x00000000, 0x0000ffff, 0x7f000001 }}};
const struct in6_addr Ip::Address::v4_anyaddr = {{{ 0x00000000, 0x00000000, 0x0000ffff, 0x00000000 }}};
const struct in6_addr Ip::Address::v4_noaddr = {{{ 0x00000000, 0x00000000, 0x0000ffff, 0xffffffff }}};
const struct in6_addr Ip::Address::v6_noaddr = {{{ 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff }}};
#else
const struct in6_addr Ip::Address::v4_localhost = {{{
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0xff, 0xff, 0x7f, 0x00, 0x00, 0x01
        }
    }
};
const struct in6_addr Ip::Address::v4_anyaddr = {{{
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00
        }
    }
};
const struct in6_addr Ip::Address::v4_noaddr = {{{
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
        }
    }
};
const struct in6_addr Ip::Address::v6_noaddr = {{{
            0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
            0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
        }
    }
};
#endif

bool
Ip::Address::setIPv4()
{
    if ( isLocalhost() ) {
        mSocketAddr_.sin6_addr = v4_localhost;
        return true;
    }

    if ( isAnyAddr() ) {
        mSocketAddr_.sin6_addr = v4_anyaddr;
        return true;
    }

    if ( isNoAddr() ) {
        mSocketAddr_.sin6_addr = v4_noaddr;
        return true;
    }

    if ( isIPv4())
        return true;

    // anything non-IPv4 and non-convertable is BAD.
    return false;
}

bool
Ip::Address::isLocalhost() const
{
    return IN6_IS_ADDR_LOOPBACK( &mSocketAddr_.sin6_addr ) || IN6_ARE_ADDR_EQUAL( &mSocketAddr_.sin6_addr, &v4_localhost );
}

void
Ip::Address::setLocalhost()
{
    if (Ip::EnableIpv6) {
        mSocketAddr_.sin6_addr = in6addr_loopback;
        mSocketAddr_.sin6_family = AF_INET6;
    } else {
        mSocketAddr_.sin6_addr = v4_localhost;
        mSocketAddr_.sin6_family = AF_INET;
    }
}

bool
Ip::Address::isSiteLocal6() const
{
    // RFC 4193 the site-local allocated range is fc00::/7
    // with fd00::/8 as the only currently allocated range (so we test it first).
    // BUG: as of 2010-02 Linux and BSD define IN6_IS_ADDR_SITELOCAL() to check for fec::/10
    return mSocketAddr_.sin6_addr.s6_addr[0] == static_cast<uint8_t>(0xfd) ||
           mSocketAddr_.sin6_addr.s6_addr[0] == static_cast<uint8_t>(0xfc);
}

bool
Ip::Address::isSiteLocalAuto() const
{
    return mSocketAddr_.sin6_addr.s6_addr[11] == static_cast<uint8_t>(0xff) &&
           mSocketAddr_.sin6_addr.s6_addr[12] == static_cast<uint8_t>(0xfe);
}

bool
Ip::Address::isNoAddr() const
{
    // IFF the address == 0xff..ff (all ones)
    return IN6_ARE_ADDR_EQUAL( &mSocketAddr_.sin6_addr, &v6_noaddr )
           || IN6_ARE_ADDR_EQUAL( &mSocketAddr_.sin6_addr, &v4_noaddr );
}

void
Ip::Address::setNoAddr()
{
    memset(&mSocketAddr_.sin6_addr, 0xFF, sizeof(struct in6_addr) );
    mSocketAddr_.sin6_family = AF_INET6;
}

bool
Ip::Address::getReverseString6(char buf[MAX_IPSTRLEN], const struct in6_addr &dat) const
{
    char *p = buf;
    unsigned char const *r = dat.s6_addr;

    /* RFC1886 says: */
    /*     4321:0:1:2:3:4:567:89ab */
    /*     must be sent */
    /*     b.a.9.8.7.6.5.0.4.0.0.0.3.0.0.0.2.0.0.0.1.0.0.0.0.0.0.0.1.2.3.4.ip6.int. */

    /* Work from the binary field. Anything else may have representation changes. */
    /* The sin6_port and sin6_addr members shall be in network byte order. */

    /* Compile Err: 'Too many arguments for format. */

    for (int i = 15; i >= 0; --i, p+=4) {
        snprintf(p, 5, "%x.%x.", ((r[i])&0xf), (((r[i])>>4)&0xf) );
    }

    /* RFC3152 says: */
    /*     ip6.int is now deprecated TLD, use ip6.arpa instead. */
    snprintf(p,10,"ip6.arpa.");

    return true;
}

bool
Ip::Address::getReverseString4(char buf[MAX_IPSTRLEN], const struct in_addr &dat) const
{
    unsigned int i = (unsigned int) ntohl(dat.s_addr);
    snprintf(buf, 32, "%u.%u.%u.%u.in-addr.arpa.",
             i & 255,
             (i >> 8) & 255,
             (i >> 16) & 255,
             (i >> 24) & 255);
    return true;
}

bool
Ip::Address::getReverseString(char buf[MAX_IPSTRLEN], int show_type) const
{

    if (show_type == AF_UNSPEC) {
        show_type = isIPv6() ? AF_INET6 : AF_INET ;
    }

    if (show_type == AF_INET && isIPv4()) {
        struct in_addr* tmp = (struct in_addr*)&mSocketAddr_.sin6_addr.s6_addr[12];
        return getReverseString4(buf, *tmp);
    } else if ( show_type == AF_INET6 && isIPv6() ) {
        return getReverseString6(buf, mSocketAddr_.sin6_addr);
    }

    debugs(14, DBG_CRITICAL, "ERROR: Unable to convert '" << toStr(buf,MAX_IPSTRLEN) << "' to the rDNS type requested.");

    buf[0] = '\0';

    return false;
}

Ip::Address::Address(const char*s)
{
    setEmpty();
    lookupHostIP(s, true);
}

bool
Ip::Address::operator =(const char* s)
{
    return lookupHostIP(s, true);
}

bool
Ip::Address::GetHostByName(const char* s)
{
    return lookupHostIP(s, false);
}

bool
Ip::Address::lookupHostIP(const char *s, bool nodns)
{
    struct addrinfo want;
    memset(&want, 0, sizeof(struct addrinfo));
    if (nodns) {
        want.ai_flags = AI_NUMERICHOST; // prevent actual DNS lookups!
    }

    int err = 0;
    struct addrinfo *res = nullptr;
    if ( (err = getaddrinfo(s, nullptr, &want, &res)) != 0) {
        debugs(14,3, "Given Non-IP '" << s << "': " << gai_strerror(err) );
        /* free the memory getaddrinfo() dynamically allocated. */
        if (res)
            freeaddrinfo(res);
        return false;
    }

    struct addrinfo *resHead = res; // we need to free the whole list later
    if (!Ip::EnableIpv6) {
        // if we are IPv6-disabled, use first-IPv4 instead of first-IP.
        struct addrinfo *maybeIpv4 = res;
        while (maybeIpv4) {
            if (maybeIpv4->ai_family == AF_INET)
                break;
            maybeIpv4 = maybeIpv4->ai_next;
        }
        if (maybeIpv4 != nullptr)
            res = maybeIpv4;
        // else IPv6-only host, let the caller deal with first-IP anyway.
    }

    /*
     *  NP: =(sockaddr_*) may alter the port. we don't want that.
     *      all we have been given as input was an IPA.
     */
    short portSaved = port();
    operator=(*res);
    port(portSaved);

    /* free the memory getaddrinfo() dynamically allocated. */
    freeaddrinfo(resHead);
    return true;
}

Ip::Address::Address(struct sockaddr_in const &s)
{
    setEmpty();
    operator=(s);
};

Ip::Address &
Ip::Address::operator =(struct sockaddr_in const &s)
{
    map4to6((const in_addr)s.sin_addr, mSocketAddr_.sin6_addr);
    mSocketAddr_.sin6_port = s.sin_port;
    mSocketAddr_.sin6_family = AF_INET6;
    return *this;
};

Ip::Address &
Ip::Address::operator =(const struct sockaddr_storage &s)
{
    /* some AF_* magic to tell socket types apart and what we need to do */
    if (s.ss_family == AF_INET6) {
        memmove(&mSocketAddr_, &s, sizeof(struct sockaddr_in6));
    } else { // convert it to our storage mapping.
        struct sockaddr_in *sin = (struct sockaddr_in*)&s;
        mSocketAddr_.sin6_port = sin->sin_port;
        map4to6( sin->sin_addr, mSocketAddr_.sin6_addr);
    }
    return *this;
};

Ip::Address::Address(struct sockaddr_in6 const &s)
{
    setEmpty();
    operator=(s);
};

Ip::Address &
Ip::Address::operator =(struct sockaddr_in6 const &s)
{
    memmove(&mSocketAddr_, &s, sizeof(struct sockaddr_in6));
    return *this;
};

Ip::Address::Address(struct in_addr const &s)
{
    setEmpty();
    operator=(s);
};

Ip::Address &
Ip::Address::operator =(struct in_addr const &s)
{
    map4to6((const in_addr)s, mSocketAddr_.sin6_addr);
    mSocketAddr_.sin6_family = AF_INET6;
    return *this;
};

Ip::Address::Address(struct in6_addr const &s)
{
    setEmpty();
    operator=(s);
};

Ip::Address &
Ip::Address::operator =(struct in6_addr const &s)
{
    memmove(&mSocketAddr_.sin6_addr, &s, sizeof(struct in6_addr));
    mSocketAddr_.sin6_family = AF_INET6;

    return *this;
};

Ip::Address::Address(const struct hostent &s)
{
    setEmpty();
    operator=(s);
}

bool
Ip::Address::operator =(const struct hostent &s)
{

    struct in_addr* ipv4 = nullptr;

    struct in6_addr* ipv6 = nullptr;

    //struct hostent {
    //        char    *h_name;        /* official name of host */
    //        char    **h_aliases;    /* alias list */
    //        int     h_addrtype;     /* host address type */
    //        int     h_length;       /* length of address */
    //        char    **h_addr_list;  /* list of addresses */
    //}

    switch (s.h_addrtype) {

    case AF_INET:
        ipv4 = (in_addr*)(s.h_addr_list[0]);
        /* this */
        operator=(*ipv4);
        break;

    case AF_INET6:
        ipv6 = (in6_addr*)(s.h_addr_list[0]);
        /* this */
        operator=(*ipv6);
        break;

    default:
        IASSERT("false",false);
        return false;
    }

    return true;
}

Ip::Address::Address(const struct addrinfo &s)
{
    setEmpty();
    operator=(s);
}

bool
Ip::Address::operator =(const struct addrinfo &s)
{

    struct sockaddr_in* ipv4 = nullptr;

    struct sockaddr_in6* ipv6 = nullptr;

    //struct addrinfo {
    //             int ai_flags;           /* input flags */
    //             int ai_family;          /* protocol family for socket */
    //             int ai_socktype;        /* socket type */
    //             int ai_protocol;        /* protocol for socket */
    //             socklen_t ai_addrlen;   /* length of socket-address */
    //             struct sockaddr *ai_addr; /* socket-address for socket */
    //             char *ai_canonname;     /* canonical name for service location */
    //             struct addrinfo *ai_next; /* pointer to next in list */
    //}

    switch (s.ai_family) {

    case AF_INET:
        ipv4 = (sockaddr_in*)(s.ai_addr);
        /* this */
        assert(ipv4);
        operator=(*ipv4);
        break;

    case AF_INET6:
        ipv6 = (sockaddr_in6*)(s.ai_addr);
        /* this */
        assert(ipv6);
        operator=(*ipv6);
        break;

    case AF_UNSPEC:
    default:
        // attempt to handle partially initialised addrinfo.
        // such as those where data only comes from getsockopt()
        if (s.ai_addr != nullptr) {
            if (s.ai_addrlen == sizeof(struct sockaddr_in6)) {
                operator=(*((struct sockaddr_in6*)s.ai_addr));
                return true;
            } else if (s.ai_addrlen == sizeof(struct sockaddr_in)) {
                operator=(*((struct sockaddr_in*)s.ai_addr));
                return true;
            }
        }
        return false;
    }

    return true;
}

void
Ip::Address::getAddrInfo(struct addrinfo *&dst, int force) const
{
    if (dst == nullptr) {
        dst = new addrinfo;
    }

    memset(dst, 0, sizeof(struct addrinfo));

    // set defaults
    // Mac OS X does not emit a flag indicating the output is numeric (IP address)
#if _SQUID_APPLE_
    dst->ai_flags = 0;
#else
    dst->ai_flags = AI_NUMERICHOST;
#endif

    if (dst->ai_socktype == 0)
        dst->ai_socktype = SOCK_STREAM;

    if (dst->ai_socktype == SOCK_STREAM // implies TCP
            && dst->ai_protocol == 0)
        dst->ai_protocol = IPPROTO_TCP;

    if (dst->ai_socktype == SOCK_DGRAM // implies UDP
            && dst->ai_protocol == 0)
        dst->ai_protocol = IPPROTO_UDP;

    InitAddr(dst);
    if (force == AF_INET6 || (force == AF_UNSPEC && isIPv6()) ) {
        getSockAddr(*((struct sockaddr_in6*)dst->ai_addr));

        dst->ai_addrlen = sizeof(struct sockaddr_in6);

        dst->ai_family = ((struct sockaddr_in6*)dst->ai_addr)->sin6_family;

#if 0
        /**
         * Enable only if you must and please report to squid-dev if you find a need for this.
         *
         * Vista may need this to cope with dual-stack (unsetting IP6_V6ONLY).
         *         http://msdn.microsoft.com/en-us/library/ms738574(VS.85).aspx
         * Linux appears to only do some things when its present.
         *         (93) Bad Protocol
         * FreeBSD dies horribly when using dual-stack with it set.
         *         (43) Protocol not supported
         */
        dst->ai_protocol = IPPROTO_IPV6;
#endif

    } else if ( force == AF_INET || (force == AF_UNSPEC && isIPv4()) ) {
        getSockAddr(*((struct sockaddr_in*)dst->ai_addr));

        dst->ai_addrlen = sizeof(struct sockaddr_in);

        dst->ai_family = ((struct sockaddr_in*)dst->ai_addr)->sin_family;
    } else {
        IASSERT("false",false);
    }
}

void
Ip::Address::InitAddr(struct addrinfo *&ai)
{
    if (ai == nullptr) {
        ai = new addrinfo;
        memset(ai,0,sizeof(struct addrinfo));
    }

    // remove any existing data.
    delete reinterpret_cast<struct sockaddr_storage *>(ai->ai_addr);

    ai->ai_addr = reinterpret_cast<struct sockaddr *>(new sockaddr_storage);
    memset(ai->ai_addr, 0, sizeof(struct sockaddr_storage));

    ai->ai_addrlen = sizeof(struct sockaddr_storage);

}

void
Ip::Address::FreeAddr(struct addrinfo *&ai)
{
    if (ai == nullptr) return;

    delete reinterpret_cast<struct sockaddr_storage *>(ai->ai_addr);

    ai->ai_addr = nullptr;

    ai->ai_addrlen = 0;

    // NP: name fields are NOT allocated at present.
    delete ai;

    ai = nullptr;
}

int
Ip::Address::matchIPAddr(const Ip::Address &rhs) const
{
    uint8_t *l = (uint8_t*)mSocketAddr_.sin6_addr.s6_addr;
    uint8_t *r = (uint8_t*)rhs.mSocketAddr_.sin6_addr.s6_addr;

    // loop a byte-wise compare
    // NP: match MUST be R-to-L : L-to-R produces inconsistent gt/lt results at varying CIDR
    //     expected difference on CIDR is gt/eq or lt/eq ONLY.
    for (unsigned int i = 0 ; i < sizeof(mSocketAddr_.sin6_addr) ; ++i) {

        if (l[i] < r[i])
            return -1;

        if (l[i] > r[i])
            return 1;
    }

    return 0;
}

int
Ip::Address::compareWhole(const Ip::Address &rhs) const
{
    return memcmp(this, &rhs, sizeof(*this));
}

bool
Ip::Address::operator ==(const Ip::Address &s) const
{
    return (0 == matchIPAddr(s));
}

bool
Ip::Address::operator !=(const Ip::Address &s) const
{
    return ! ( operator==(s) );
}

bool
Ip::Address::operator <=(const Ip::Address &rhs) const
{
    if (isAnyAddr() && !rhs.isAnyAddr())
        return true;

    return (matchIPAddr(rhs) <= 0);
}

bool
Ip::Address::operator >=(const Ip::Address &rhs) const
{
    if (isNoAddr() && !rhs.isNoAddr())
        return true;

    return ( matchIPAddr(rhs) >= 0);
}

bool
Ip::Address::operator >(const Ip::Address &rhs) const
{
    if (isNoAddr() && !rhs.isNoAddr())
        return true;

    return ( matchIPAddr(rhs) > 0);
}

bool
Ip::Address::operator <(const Ip::Address &rhs) const
{
    if (isAnyAddr() && !rhs.isAnyAddr())
        return true;

    return ( matchIPAddr(rhs) < 0);
}

unsigned short
Ip::Address::port() const
{
    return ntohs( mSocketAddr_.sin6_port );
}

unsigned short
Ip::Address::port(unsigned short prt)
{
    mSocketAddr_.sin6_port = htons(prt);

    return prt;
}

char *
Ip::Address::toStr(char* buf, const unsigned int blen, int force) const
{
    // Ensure we have a buffer.
    if (buf == nullptr) {
        return nullptr;
    }

    /* some external code may have blindly memset a parent. */
    /* that's okay, our default is known */
    if ( isAnyAddr() ) {
        if (isIPv6())
            memcpy(buf,"::\0", min(static_cast<unsigned int>(3),blen));
        else if (isIPv4())
            memcpy(buf,"0.0.0.0\0", min(static_cast<unsigned int>(8),blen));
        return buf;
    }

    memset(buf,0,blen); // clear buffer before write

    /* Pure-IPv6 CANNOT be displayed in IPv4 format. */
    /* However IPv4 CAN. */
    if ( force == AF_INET && !isIPv4() ) {
        if ( isIPv6() ) {
            memcpy(buf, "{!IPv4}\0", min(static_cast<unsigned int>(8),blen));
        }
        return buf;
    }

    if ( force == AF_INET6 || (force == AF_UNSPEC && isIPv6()) ) {

        inet_ntop(AF_INET6, &mSocketAddr_.sin6_addr, buf, blen);

    } else  if ( force == AF_INET || (force == AF_UNSPEC && isIPv4()) ) {

        struct in_addr tmp;
        getInAddr(tmp);
        inet_ntop(AF_INET, &tmp, buf, blen);
    } else {
        debugs(14, DBG_CRITICAL, "WARNING: Corrupt IP Address details OR required to display in unknown format (" <<
               force << "). accepted={" << AF_UNSPEC << "," << AF_INET << "," << AF_INET6 << "}");
        fprintf(stderr,"WARNING: Corrupt IP Address details OR required to display in unknown format (%d). accepted={%d,%d,%d} ",
                force, AF_UNSPEC, AF_INET, AF_INET6);
        memcpy(buf,"dead:beef::\0", min(static_cast<unsigned int>(13),blen));
        assert(false);
    }

    return buf;
}

unsigned int
Ip::Address::toHostStr(char *buf, const unsigned int blen) const
{
    char *p = buf;

    if (isIPv6() && blen > 0) {
        *p = '[';
        ++p;
    }

    /* 8 being space for [ ] : and port digits */
    if ( isIPv6() )
        toStr(p, blen-8, AF_INET6);
    else
        toStr(p, blen-8, AF_INET);

    // find the end of the new string
    while (*p != '\0' && p < buf+blen)
        ++p;

    if (isIPv6() && p < (buf+blen-1) ) {
        *p = ']';
        ++p;
    }

    /* terminate just in case. */
    *p = '\0';

    /* return size of buffer now used */
    return (p - buf);
}

char *
Ip::Address::toUrl(char* buf, unsigned int blen) const
{
    char *p = buf;

    // Ensure we have a buffer.

    if (buf == nullptr) {
        return nullptr;
    }

    p += toHostStr(p, blen);

    if (mSocketAddr_.sin6_port > 0 && p <= (buf+blen-7) ) {
        // ':port' (short int) needs at most 6 bytes plus 1 for 0-terminator
        snprintf(p, 7, ":%d", port() );
    }

    // force a null-terminated string
    buf[blen-1] = '\0';

    return buf;
}

bool
Ip::Address::fromHost(const char *host)
{
    setEmpty();

    if (!host)
        return false;

    if (host[0] != '[')
        return lookupHostIP(host, true); // no brackets

    /* unwrap a bracketed [presumably IPv6] address, presumably without port */

    const char *start = host + 1;
    if (!*start)
        return false; // missing address after an opening bracket

    // XXX: Check that there is a closing bracket and no trailing garbage.

    char *tmp = xstrdup(start); // XXX: Slow. TODO: Bail on huge strings and use an on-stack buffer.
    tmp[strlen(tmp)-1] = '\0'; // XXX: Wasteful: xstrdup() just did strlen().
    const bool result = lookupHostIP(tmp, true);
    xfree(tmp);
    return result;
}

void
Ip::Address::getSockAddr(struct sockaddr_storage &addr, const int family) const
{
    struct sockaddr_in *sin = nullptr;

    if ( family == AF_INET && !isIPv4()) {
        // TODO INET6: caller using the wrong socket type!
        debugs(14, DBG_CRITICAL, "ERROR: Ip::Address::getSockAddr : Cannot convert non-IPv4 to IPv4. from " << *this);
        assert(false);
    }

    if ( family == AF_INET6 || (family == AF_UNSPEC && isIPv6()) ) {
        struct sockaddr_in6 *ss6 = (struct sockaddr_in6*)&addr;
        getSockAddr(*ss6);
    } else if ( family == AF_INET || (family == AF_UNSPEC && isIPv4()) ) {
        sin = (struct sockaddr_in*)&addr;
        getSockAddr(*sin);
    } else {
        IASSERT("false",false);
    }
}

void
Ip::Address::getSockAddr(struct sockaddr_in &buf) const
{
    if ( isIPv4() ) {
        buf.sin_family = AF_INET;
        buf.sin_port = mSocketAddr_.sin6_port;
        map6to4( mSocketAddr_.sin6_addr, buf.sin_addr);
    } else {
        debugs(14, DBG_CRITICAL, "ERROR: Ip::Address::getSockAddr : Cannot convert non-IPv4 to IPv4. from " << *this );

        memset(&buf,0xFFFFFFFF,sizeof(struct sockaddr_in));
        assert(false);
    }

#if HAVE_SIN_LEN_IN_SAI
    /* not all OS have this field, BUT when they do it can be a problem if set wrong */
    buf.sin_len = sizeof(struct sockaddr_in);
#endif
}

void
Ip::Address::getSockAddr(struct sockaddr_in6 &buf) const
{
    memmove(&buf, &mSocketAddr_, sizeof(struct sockaddr_in6));
    /* maintain address family. It may have changed inside us. */
    buf.sin6_family = AF_INET6;

#if HAVE_SIN6_LEN_IN_SAI
    /* not all OS have this field, BUT when they do it can be a problem if set wrong */
    buf.sin6_len = sizeof(struct sockaddr_in6);
#endif
}

void
Ip::Address::map4to6(const struct in_addr &in, struct in6_addr &out) const
{
    /* check for special cases */

    if ( in.s_addr == 0x00000000) {
        /* ANYADDR */
        out = v4_anyaddr;
    } else if ( in.s_addr == 0xFFFFFFFF) {
        /* NOADDR */
        out = v4_noaddr;
    } else {
        /* general */
        out = v4_anyaddr;
        out.s6_addr[12] = ((uint8_t *)&in.s_addr)[0];
        out.s6_addr[13] = ((uint8_t *)&in.s_addr)[1];
        out.s6_addr[14] = ((uint8_t *)&in.s_addr)[2];
        out.s6_addr[15] = ((uint8_t *)&in.s_addr)[3];
    }
}

void
Ip::Address::map6to4(const struct in6_addr &in, struct in_addr &out) const
{
    /* ANYADDR */
    /* NOADDR */
    /* general */

    memset(&out, 0, sizeof(struct in_addr));
    ((uint8_t *)&out.s_addr)[0] = in.s6_addr[12];
    ((uint8_t *)&out.s_addr)[1] = in.s6_addr[13];
    ((uint8_t *)&out.s_addr)[2] = in.s6_addr[14];
    ((uint8_t *)&out.s_addr)[3] = in.s6_addr[15];
}

void
Ip::Address::getInAddr(struct in6_addr &buf) const
{
    memmove(&buf, &mSocketAddr_.sin6_addr, sizeof(struct in6_addr));
}

bool
Ip::Address::getInAddr(struct in_addr &buf) const
{
    if ( isIPv4() ) {
        map6to4(mSocketAddr_.sin6_addr, buf);
        return true;
    }

    // default:
    // non-compatible IPv6 Pure Address

    debugs(14, DBG_IMPORTANT, "ERROR: Ip::Address::getInAddr : Cannot convert non-IPv4 to IPv4. IPA=" << *this);
    memset(&buf,0xFFFFFFFF,sizeof(struct in_addr));
    assert(false);
    return false;
}

