/*
    SPDX-FileCopyrightText: 2005 Joris Guisson <joris.guisson@gmail.com>

    SPDX-License-Identifier: GPL-2.0-or-later
*/

#include "address.h"

#include <util/functions.h>
#include <util/log.h>

#ifndef Q_OS_WIN
#include <sys/socket.h>
#else
#include <Ws2tcpip.h>
#endif

using namespace bt;

namespace net
{
Address::Address()
    : port_number(0)
{
}

Address::Address(quint32 ip4Addr, Uint16 port)
    : QHostAddress(ip4Addr)
    , port_number(port)
{
}

Address::Address(const quint8 *ip6Addr, Uint16 port)
    : QHostAddress(ip6Addr)
    , port_number(port)
{
}

Address::Address(const Q_IPV6ADDR &ip6Addr, Uint16 port)
    : QHostAddress(ip6Addr)
    , port_number(port)
{
}

Address::Address(const struct sockaddr_storage *ss)
    : port_number(0)
{
    if (ss->ss_family == AF_INET) {
        setAddress((const struct sockaddr *)ss);
        port_number = ntohs(((const struct sockaddr_in *)ss)->sin_port);
    } else if (ss->ss_family == AF_INET6) {
        setAddress((const struct sockaddr *)ss);
        port_number = ntohs(((const struct sockaddr_in6 *)ss)->sin6_port);
        if (isIPv4Mapped()) {
            Q_IPV6ADDR ipv6 = toIPv6Address();
            const quint32 ip = ipv6[12] << 24 | ipv6[13] << 16 | ipv6[14] << 8 | ipv6[15];
            setAddress(ip);
        }
    } else {
        Out(SYS_GEN | LOG_DEBUG) << "Unknown address family" << endl;
    }
}

Address::Address(const QString &host, Uint16 port)
    : QHostAddress(host)
    , port_number(port)
{
}

Address::Address(const Address &addr)
    : QHostAddress(addr)
    , port_number(addr.port_number)
{
}

Address::Address(const QHostAddress &addr, Uint16 port)
    : QHostAddress(addr)
    , port_number(port)
{
}

Address::~Address()
{
}

Address Address::fromCompactIPv4(QByteArrayView ba)
{
    const Uint32 ip = bt::ReadUint32(reinterpret_cast<const Uint8 *>(ba.data()), 0);
    const Uint16 port = bt::ReadUint16(reinterpret_cast<const Uint8 *>(ba.data()), 4);
    return Address{ip, port};
}

Address Address::fromCompactIPv6(QByteArrayView ba)
{
    const Uint16 port = bt::ReadUint16(reinterpret_cast<const Uint8 *>(ba.data()), 16);
    return Address{reinterpret_cast<const quint8 *>(ba.data()), port};
}

void Address::toSocketAddress(sockaddr_storage *ss, int &length, bool as_ipv6) const
{
    if (protocol() == QAbstractSocket::IPv4Protocol && !as_ipv6) {
        struct sockaddr_in *addr = (struct sockaddr_in *)ss;
        memset(addr, 0, sizeof(struct sockaddr_in));
        addr->sin_family = AF_INET;
        addr->sin_port = htons(port_number);
        addr->sin_addr.s_addr = htonl(toIPv4Address());
        length = sizeof(struct sockaddr_in);
    } else {
        struct sockaddr_in6 *addr = (struct sockaddr_in6 *)ss;
        memset(addr, 0, sizeof(struct sockaddr_in6));
        addr->sin6_family = AF_INET6;
        addr->sin6_port = htons(port_number);
        memcpy(&addr->sin6_addr, toIPv6Address().c, 16);
        length = sizeof(struct sockaddr_in6);
    }
}

bool Address::operator==(const net::Address &other) const
{
    return QHostAddress::operator==(other) && port_number == other.port();
}

bool Address::operator<(const net::Address &other) const
{
    if (ipVersion() != other.ipVersion()) {
        return ipVersion() < other.ipVersion();
    } else if (other.ipVersion() == 4) {
        return toIPv4Address() == other.toIPv4Address() ? port_number < other.port_number : toIPv4Address() < other.toIPv4Address();
    } else { // IPv6
        if (QHostAddress::operator==(other)) {
            return port_number < other.port_number;
        } else {
            Q_IPV6ADDR a = toIPv6Address();
            Q_IPV6ADDR b = other.toIPv6Address();
            for (int i = 0; i < 16; i++) {
                if (a[i] < b[i]) {
                    return true;
                } else if (a[i] > b[i]) {
                    return false;
                }
            }
            return false;
        }
    }
}

Address &Address::operator=(const net::Address &other)
{
    QHostAddress::operator=(other);
    port_number = other.port();
    return *this;
}

Address &Address::operator=(const struct sockaddr_storage &ss)
{
    if (ss.ss_family == AF_INET) {
        setAddress((const struct sockaddr *)&ss);
        port_number = ntohs(((const struct sockaddr_in *)&ss)->sin_port);
    } else if (ss.ss_family == AF_INET6) {
        setAddress((const struct sockaddr *)&ss);
        port_number = ntohs(((const struct sockaddr_in6 *)&ss)->sin6_port);
        if (isIPv4Mapped()) {
            Q_IPV6ADDR ipv6 = toIPv6Address();
            const quint32 ip = ipv6[12] << 24 | ipv6[13] << 16 | ipv6[14] << 8 | ipv6[15];
            setAddress(ip);
        }
    }

    return *this;
}

bool Address::isIPv4Mapped() const
{
    Q_IPV6ADDR addr = toIPv6Address();
    for (int i = 0; i < 10; i++) {
        if (addr[i] != 0) {
            return false;
        }
    }

    return addr[10] == 0xff && addr[11] == 0xff;
}

Address Address::convertIPv4Mapped() const
{
    if (isIPv4Mapped()) {
        Q_IPV6ADDR ipv6 = toIPv6Address();
        const quint32 ip = ipv6[12] << 24 | ipv6[13] << 16 | ipv6[14] << 8 | ipv6[15];
        return net::Address(ip, port());
    }

    return net::Address(*this);
}

Uint32 Address::writeCompact(Uint8 *buf) const
{
    if (ipVersion() == 4) {
        WriteUint32(buf, 0, toIPv4Address());
        WriteUint16(buf, 4, port_number);
        return 6;
    } else {
        const auto ipv6 = toIPv6Address();
        memcpy(buf, ipv6.c, 16);
        WriteUint16(buf, 16, port_number);
        return 18;
    }
}
}
