#include <balancer/kernel/net/addr.h>

#include <library/cpp/testing/unittest/registar.h>

#include <util/digest/city.h>
#include <util/generic/scope.h>

using namespace NSrvKernel;

Y_UNIT_TEST_SUITE(TTestBalancerAddr) {
    Y_UNIT_TEST(TestEmptyAddr) {
        TIpAddr addr;
        UNIT_ASSERT(!addr);
        UNIT_ASSERT(addr == TIpAddr());
        UNIT_ASSERT(!addr.ToString());
        UNIT_ASSERT(!addr.AddrFamily());
        UNIT_ASSERT(!addr.SizeOf());
        UNIT_ASSERT_VALUES_EQUAL(addr.Hash(), CityHash64(TStringBuf()));
        UNIT_ASSERT(!addr.V4());
        UNIT_ASSERT(!addr.V6());
    }

    Y_UNIT_TEST(TestIp4Addr) {
        TIpAddr addr;
        auto err = TIpAddr::FromIp("127.0.0.1").AssignTo(addr);
        UNIT_ASSERT(!err);
        in_addr lb{.s_addr = htonl(INADDR_LOOPBACK)};
        UNIT_ASSERT_VALUES_EQUAL(addr, TIpAddr(lb));
        UNIT_ASSERT_VALUES_EQUAL(addr.ToString(), "127.0.0.1");
        UNIT_ASSERT_VALUES_EQUAL(addr.AddrFamily(), AF_INET);
        UNIT_ASSERT_VALUES_EQUAL(addr.SizeOf(), sizeof(lb));
        UNIT_ASSERT_VALUES_EQUAL(addr.Hash(), CityHash64((const char*)&lb, sizeof(lb)));
        UNIT_ASSERT(addr.V4());
        UNIT_ASSERT_VALUES_EQUAL(addr.V4()->s_addr, lb.s_addr);
        UNIT_ASSERT(!addr.V6());
    }

    Y_UNIT_TEST(TestIp6Addr) {
        TIpAddr addr;
        auto err = TIpAddr::FromIp("::1").AssignTo(addr);
        UNIT_ASSERT(!err);
        UNIT_ASSERT_VALUES_EQUAL(addr, TIpAddr(in6addr_loopback));
        UNIT_ASSERT_VALUES_EQUAL(addr.ToString(), "::1");
        UNIT_ASSERT_VALUES_EQUAL(addr.AddrFamily(), AF_INET6);
        UNIT_ASSERT_VALUES_EQUAL(addr.SizeOf(), sizeof(in6_addr));
        UNIT_ASSERT_VALUES_EQUAL(addr.Hash(), CityHash64((const char*) &in6addr_loopback, sizeof(in6addr_loopback)));
        UNIT_ASSERT(!addr.V4());
        UNIT_ASSERT(addr.V6());
        UNIT_ASSERT(!memcmp(addr.V6()->s6_addr, in6addr_loopback.s6_addr, 16));
    }

    void DoTestParseIpAddr(TString ip, const TString canonIp) {
        TIpAddr addr;
        auto err = TIpAddr::FromIp(ip).AssignTo(addr);
        UNIT_ASSERT(!err);
        UNIT_ASSERT_VALUES_EQUAL(to_lower(addr.ToString()), to_lower(canonIp));
    }

    Y_UNIT_TEST(TestParseIpAddr) {
        DoTestParseIpAddr("::", "::");
        DoTestParseIpAddr("[::]", "::");
        DoTestParseIpAddr("::1", "::1");
        DoTestParseIpAddr("[::1]", "::1");
        DoTestParseIpAddr("fe80::", "fe80::");
        DoTestParseIpAddr(
            "FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF", "FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF");
        DoTestParseIpAddr(
            "FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:255.255.255.255", "FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF");
        DoTestParseIpAddr(
            "[FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:255.255.255.255]", "FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF");
        DoTestParseIpAddr("0.0.0.0", "0.0.0.0");
        DoTestParseIpAddr("255.255.255.255", "255.255.255.255");
    }

    void DoTestRejectIpAddr(TString ip) {
        auto err = TIpAddr::FromIp(ip).ReleaseError();
        UNIT_ASSERT(err);
        UNIT_ASSERT(err.GetAs<TNetworkResolutionError>());
    }

    Y_UNIT_TEST(TestRejectIpAddr) {
        DoTestRejectIpAddr("");
        DoTestRejectIpAddr(":");
        DoTestRejectIpAddr(".");
        DoTestRejectIpAddr(":1");
        DoTestRejectIpAddr("1:");
        DoTestRejectIpAddr("0"); //getaddrinfo would interpret numbers as ipv4, and we avoid using it
        DoTestRejectIpAddr(".0");
        DoTestRejectIpAddr("0.");
        DoTestRejectIpAddr("0.0");
        DoTestRejectIpAddr(".0.0");
        DoTestRejectIpAddr("0.0.");
        DoTestRejectIpAddr("0.0.0");
        DoTestRejectIpAddr("0.0.0.");
        DoTestRejectIpAddr(".0.0.0");
        DoTestRejectIpAddr("[");
        DoTestRejectIpAddr("]");
        DoTestRejectIpAddr("[]");
        DoTestRejectIpAddr("[:]");
        DoTestRejectIpAddr("]::[");
        DoTestRejectIpAddr("]::]");
        DoTestRejectIpAddr("[::[");
    }

    void DoTestParseSubnet(TString snet, const TString canonSnet) {
        TIpSubnet res;
        auto err = TIpSubnet::FromSubnet(snet).AssignTo(res);
        UNIT_ASSERT(!err);
        UNIT_ASSERT_VALUES_EQUAL(res.ToString(), canonSnet);
    }

    Y_UNIT_TEST(TestParseIpSubnet) {
        DoTestParseSubnet("127.0.0.1/32", "127.0.0.1/32");
        DoTestParseSubnet("127.0.0.1", "127.0.0.1/32");
        DoTestParseSubnet("127.0.0.1/0", "0.0.0.0/0");
        DoTestParseSubnet("127.0.0.1/8", "127.0.0.0/8");
        DoTestParseSubnet("::1", "::1/128");
        DoTestParseSubnet("::1/128", "::1/128");
        DoTestParseSubnet("::1/0", "::/0");
        DoTestParseSubnet("::/0", "::/0");
    }

    void DoTestRejectSubnet(TString snet) {
        auto err = TIpSubnet::FromSubnet(snet).ReleaseError();
        UNIT_ASSERT(err);
        UNIT_ASSERT(err.GetAs<TNetworkResolutionError>());
    }

    Y_UNIT_TEST(TestRejectIpSubnet) {
        DoTestRejectIpAddr("::/");
        DoTestRejectIpAddr("::/");
        DoTestRejectIpAddr("127.0.0.1/");
        DoTestRejectIpAddr("[::/0]");
    }

    Y_UNIT_TEST(TestMakeSubnet) {
        UNIT_ASSERT_VALUES_EQUAL(TIpSubnet(TIp4Raw{255, 255, 255, 255}, 0).ToString(), "0.0.0.0/0");
        UNIT_ASSERT_VALUES_EQUAL(TIpSubnet(TIp4Raw{255, 255, 255, 255}, 1).ToString(), "128.0.0.0/1");
        UNIT_ASSERT_VALUES_EQUAL(TIpSubnet(TIp4Raw{255, 255, 255, 255}, 3).ToString(), "224.0.0.0/3");
        UNIT_ASSERT_VALUES_EQUAL(TIpSubnet(TIp4Raw{255, 255, 255, 255}, 11).ToString(), "255.224.0.0/11");
        UNIT_ASSERT_VALUES_EQUAL(TIpSubnet(TIp4Raw{255, 255, 255, 255}, 19).ToString(), "255.255.224.0/19");
        UNIT_ASSERT_VALUES_EQUAL(TIpSubnet(TIp4Raw{255, 255, 255, 255}, 27).ToString(), "255.255.255.224/27");
        UNIT_ASSERT_VALUES_EQUAL(TIpSubnet(TIp4Raw{255, 255, 255, 255}, 31).ToString(), "255.255.255.254/31");
        UNIT_ASSERT_VALUES_EQUAL(TIpSubnet(TIp4Raw{255, 255, 255, 255}, 32).ToString(), "255.255.255.255/32");
        UNIT_ASSERT_VALUES_EQUAL(TIpSubnet(TIp4Raw{255, 255, 255, 255}, 33).ToString(), "255.255.255.255/32");

        UNIT_ASSERT_VALUES_EQUAL(TIpSubnet(TIp4Raw{}, 0).ToString(), "0.0.0.0/0");
        UNIT_ASSERT_VALUES_EQUAL(TIpSubnet(TIp4Raw{}, 1).ToString(), "0.0.0.0/1");
        UNIT_ASSERT_VALUES_EQUAL(TIpSubnet(TIp4Raw{}, 3).ToString(), "0.0.0.0/3");
        UNIT_ASSERT_VALUES_EQUAL(TIpSubnet(TIp4Raw{}, 11).ToString(), "0.0.0.0/11");
        UNIT_ASSERT_VALUES_EQUAL(TIpSubnet(TIp4Raw{}, 19).ToString(), "0.0.0.0/19");
        UNIT_ASSERT_VALUES_EQUAL(TIpSubnet(TIp4Raw{}, 27).ToString(), "0.0.0.0/27");
        UNIT_ASSERT_VALUES_EQUAL(TIpSubnet(TIp4Raw{}, 31).ToString(), "0.0.0.0/31");
        UNIT_ASSERT_VALUES_EQUAL(TIpSubnet(TIp4Raw{}, 32).ToString(), "0.0.0.0/32");
        UNIT_ASSERT_VALUES_EQUAL(TIpSubnet(TIp4Raw{}, 33).ToString(), "0.0.0.0/32");

        UNIT_ASSERT_VALUES_EQUAL(TIpSubnet(TIp4Raw{}, 0).Broadcast().ToString(), "255.255.255.255");
        UNIT_ASSERT_VALUES_EQUAL(TIpSubnet(TIp4Raw{}, 1).Broadcast().ToString(), "127.255.255.255");
        UNIT_ASSERT_VALUES_EQUAL(TIpSubnet(TIp4Raw{}, 31).Broadcast().ToString(), "0.0.0.1");
        UNIT_ASSERT_VALUES_EQUAL(TIpSubnet(TIp4Raw{}, 32).Broadcast().ToString(), "0.0.0.0");

        UNIT_ASSERT_VALUES_EQUAL(
            TIpSubnet(TIp6Raw{0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff}, 0).ToString(),
            "::/0");
        UNIT_ASSERT_VALUES_EQUAL(
            to_lower(TIpSubnet(TIp6Raw{0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff}, 1).ToString()),
            "8000::/1");
        UNIT_ASSERT_VALUES_EQUAL(
            to_lower(TIpSubnet(TIp6Raw{0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff}, 5).ToString()),
            "f800::/5");
        UNIT_ASSERT_VALUES_EQUAL(
            to_lower(TIpSubnet(TIp6Raw{0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff}, 9).ToString()),
            "ff80::/9");
        UNIT_ASSERT_VALUES_EQUAL(
            to_lower(TIpSubnet(TIp6Raw{0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff}, 13).ToString()),
            "fff8::/13");
        UNIT_ASSERT_VALUES_EQUAL(
            to_lower(TIpSubnet(TIp6Raw{0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff}, 17).ToString()),
            "ffff:8000::/17");
        UNIT_ASSERT_VALUES_EQUAL(
            to_lower(TIpSubnet(TIp6Raw{0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff}, 21).ToString()),
            "ffff:f800::/21");
        UNIT_ASSERT_VALUES_EQUAL(
            to_lower(TIpSubnet(TIp6Raw{0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff}, 25).ToString()),
            "ffff:ff80::/25");
        UNIT_ASSERT_VALUES_EQUAL(
            to_lower(TIpSubnet(TIp6Raw{0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff}, 29).ToString()),
            "ffff:fff8::/29");
        UNIT_ASSERT_VALUES_EQUAL(
            to_lower(TIpSubnet(TIp6Raw{0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff}, 109).ToString()),
            "ffff:ffff:ffff:ffff:ffff:ffff:fff8:0/109");
        UNIT_ASSERT_VALUES_EQUAL(
            to_lower(TIpSubnet(TIp6Raw{0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff}, 113).ToString()),
            "ffff:ffff:ffff:ffff:ffff:ffff:ffff:8000/113");
        UNIT_ASSERT_VALUES_EQUAL(
            to_lower(TIpSubnet(TIp6Raw{0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff}, 117).ToString()),
            "ffff:ffff:ffff:ffff:ffff:ffff:ffff:f800/117");
        UNIT_ASSERT_VALUES_EQUAL(
            to_lower(TIpSubnet(TIp6Raw{0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff}, 121).ToString()),
            "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ff80/121");
        UNIT_ASSERT_VALUES_EQUAL(
            to_lower(TIpSubnet(TIp6Raw{0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff}, 125).ToString()),
            "ffff:ffff:ffff:ffff:ffff:ffff:ffff:fff8/125");
        UNIT_ASSERT_VALUES_EQUAL(
            to_lower(TIpSubnet(TIp6Raw{0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff}, 127).ToString()),
            "ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffe/127");
        UNIT_ASSERT_VALUES_EQUAL(
            to_lower(TIpSubnet(TIp6Raw{0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff}, 128).ToString()),
            "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128");
        UNIT_ASSERT_VALUES_EQUAL(
            to_lower(TIpSubnet(TIp6Raw{0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff}, 129).ToString()),
            "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128");

        UNIT_ASSERT_VALUES_EQUAL(
            TIpSubnet(TIp6Raw{}, 0).ToString(),
            "::/0");
        UNIT_ASSERT_VALUES_EQUAL(
            to_lower(TIpSubnet(TIp6Raw{}, 1).ToString()),
            "::/1");
        UNIT_ASSERT_VALUES_EQUAL(
            to_lower(TIpSubnet(TIp6Raw{}, 125).ToString()),
            "::/125");
        UNIT_ASSERT_VALUES_EQUAL(
            to_lower(TIpSubnet(TIp6Raw{}, 128).ToString()),
            "::/128");

        UNIT_ASSERT_VALUES_EQUAL(
            to_lower(TIpSubnet(TIp6Raw{}, 0).Broadcast().ToString()),
            "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff");
        UNIT_ASSERT_VALUES_EQUAL(
            to_lower(TIpSubnet(TIp6Raw{}, 1).Broadcast().ToString()),
            "7fff:ffff:ffff:ffff:ffff:ffff:ffff:ffff");
        UNIT_ASSERT_VALUES_EQUAL(
            to_lower(TIpSubnet(TIp6Raw{}, 127).Broadcast().ToString()),
            "::1");
        UNIT_ASSERT_VALUES_EQUAL(
            to_lower(TIpSubnet(TIp6Raw{}, 128).Broadcast().ToString()),
            "::");
    }

    enum class EScope {
        NodeLocal, LinkLocal, Global,
    };
    enum class EType {
        Empty, Unicast, Multicast
    };

    void DoTestSpecialIpAddr(TIpAddr addr, EScope scope, EType realm) {
        if (EType::Multicast == realm) {
            UNIT_ASSERT_C(addr.Multicast(), addr.ToString());
            UNIT_ASSERT_C(!addr.Loopback(), addr.ToString());
            UNIT_ASSERT_C(!addr.LinkLocal(), addr.ToString());
            UNIT_ASSERT_C(!addr.Global(), addr.ToString());
            switch (scope) {
            case EScope::NodeLocal:
                UNIT_ASSERT_C(addr.McNodeLocal(), addr.ToString());
                break;
            case EScope::LinkLocal:
                UNIT_ASSERT_C(addr.McLinkLocal(), addr.ToString());
                break;
            case EScope::Global:
                UNIT_ASSERT_C(addr.McGlobal(), addr.ToString());
                break;
            }
        } else if (EType::Unicast == realm) {
            UNIT_ASSERT_C(!addr.Multicast(), addr.ToString());
            UNIT_ASSERT_C(!addr.McNodeLocal(), addr.ToString());
            UNIT_ASSERT_C(!addr.McLinkLocal(), addr.ToString());
            UNIT_ASSERT_C(!addr.McGlobal(), addr.ToString());
            switch (scope) {
            case EScope::NodeLocal:
                UNIT_ASSERT_C(addr.Loopback(), addr.ToString());
                break;
            case EScope::LinkLocal:
                UNIT_ASSERT_C(addr.LinkLocal(), addr.ToString());
                break;
            case EScope::Global:
                UNIT_ASSERT_C(addr.Global(), addr.ToString());
                break;
            }
        } else {
            UNIT_ASSERT_C(!addr, addr.ToString());
            UNIT_ASSERT_C(!addr.Loopback(), addr.ToString());
            UNIT_ASSERT_C(!addr.LinkLocal(), addr.ToString());
            UNIT_ASSERT_C(!addr.Global(), addr.ToString());
            UNIT_ASSERT_C(!addr.Multicast(), addr.ToString());
            UNIT_ASSERT_C(!addr.McNodeLocal(), addr.ToString());
            UNIT_ASSERT_C(!addr.McLinkLocal(), addr.ToString());
            UNIT_ASSERT_C(!addr.McGlobal(), addr.ToString());
        }
    }

    Y_UNIT_TEST(TestSpecialIpAddr) {
        DoTestSpecialIpAddr({}, EScope::Global, EType::Empty);
        DoTestSpecialIpAddr(TIp4Raw{126, 255, 255, 255}, EScope::Global, EType::Unicast);
        DoTestSpecialIpAddr(TIp4Raw{127, 0, 0, 0}, EScope::NodeLocal, EType::Unicast);
        DoTestSpecialIpAddr(TIp4Raw{127, 0, 0, 1}, EScope::NodeLocal, EType::Unicast);
        DoTestSpecialIpAddr(TIp4Raw{127, 255, 255, 255}, EScope::NodeLocal, EType::Unicast);
        DoTestSpecialIpAddr(TIp4Raw{128, 0, 0, 0}, EScope::Global, EType::Unicast);

        DoTestSpecialIpAddr(Loopback6(), EScope::NodeLocal, EType::Unicast);
        UNIT_ASSERT_VALUES_EQUAL(Loopback6(), TIpAddr(TIp6Raw{0, 0, 0, 0, 0, 0, 0, 1}));

        DoTestSpecialIpAddr(TIp4Raw{169, 253, 255, 255}, EScope::Global, EType::Unicast);
        DoTestSpecialIpAddr(TIp4Raw{169, 254, 0, 0}, EScope::LinkLocal, EType::Unicast);
        DoTestSpecialIpAddr(TIp4Raw{169, 254, 255, 255}, EScope::LinkLocal, EType::Unicast);
        DoTestSpecialIpAddr(TIp4Raw{169, 255, 0, 0}, EScope::Global, EType::Unicast);

        DoTestSpecialIpAddr(
            TIp6Raw{0xfe7f, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff}, EScope::Global, EType::Unicast);
        DoTestSpecialIpAddr(
            TIp6Raw{0xfe80, 0, 0, 0, 0, 0, 0, 0}, EScope::LinkLocal, EType::Unicast);
        DoTestSpecialIpAddr(
            TIp6Raw{0xfe80, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff}, EScope::LinkLocal, EType::Unicast);
        DoTestSpecialIpAddr(
            TIp6Raw{0xfec0}, EScope::Global, EType::Unicast);

        DoTestSpecialIpAddr(
            TIp6Raw{0xfeff}, EScope::Global, EType::Unicast);
        DoTestSpecialIpAddr(
            TIp6Raw{0xff00}, EScope::Global, EType::Multicast);
        DoTestSpecialIpAddr(
            TIp6Raw{0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff}, EScope::Global, EType::Multicast);

        DoTestSpecialIpAddr(TIp4Raw{223, 255, 255, 255}, EScope::Global, EType::Unicast);
        DoTestSpecialIpAddr(TIp4Raw{224, 0, 0, 0}, EScope::LinkLocal, EType::Multicast);
        DoTestSpecialIpAddr(TIp4Raw{224, 0, 0, 255}, EScope::LinkLocal, EType::Multicast);
        DoTestSpecialIpAddr(TIp4Raw{224, 0, 1, 0}, EScope::Global, EType::Multicast);
        DoTestSpecialIpAddr(TIp4Raw{239, 255, 255, 255}, EScope::Global, EType::Multicast);
        DoTestSpecialIpAddr(TIp4Raw{240, 0, 0, 0}, EScope::Global, EType::Unicast);
    }

    Y_UNIT_TEST(TestSockAddr) {
        TSockAddr addr;
        UNIT_ASSERT(!addr);
        UNIT_ASSERT_VALUES_EQUAL(addr, TSockAddr());
        UNIT_ASSERT(!addr.ToString());
        UNIT_ASSERT(!addr.AddrFamily());
        UNIT_ASSERT(!addr.Ip());
        UNIT_ASSERT(!addr.Port());
        UNIT_ASSERT(!addr.SizeOf());
        UNIT_ASSERT_VALUES_EQUAL(addr.Hash(), CityHash64(TStringBuf()));
        UNIT_ASSERT(!addr.Addr());
        UNIT_ASSERT(!addr.V4());
        UNIT_ASSERT(!addr.V6());
    }

    Y_UNIT_TEST(TestIp4SockAddr) {
        TSockAddr addr;
        auto err = TSockAddr::FromIpPort("127.0.0.1:12345").AssignTo(addr);
        UNIT_ASSERT(!err);
        sockaddr_in lb{
            .sin_family = AF_INET,
            .sin_port = htons(12345),
            .sin_addr = {.s_addr = htonl(INADDR_LOOPBACK)},
            .sin_zero = {},
        };
        UNIT_ASSERT(addr);
        UNIT_ASSERT_VALUES_EQUAL(addr, TSockAddr(lb));
        UNIT_ASSERT_VALUES_EQUAL(addr.ToString(), "127.0.0.1:12345");
        UNIT_ASSERT_VALUES_EQUAL(addr.AddrFamily(), AF_INET);
        UNIT_ASSERT_VALUES_EQUAL(addr.Ip(), TIpAddr(in_addr{.s_addr = htonl(INADDR_LOOPBACK)}));
        UNIT_ASSERT_VALUES_EQUAL(addr.Port(), 12345);
        UNIT_ASSERT_VALUES_EQUAL(addr.SizeOf(), sizeof(lb));
        UNIT_ASSERT_VALUES_EQUAL(addr.Hash(), CityHash64((const char*)&lb, sizeof(lb)));
        UNIT_ASSERT(addr.Addr());
        UNIT_ASSERT_VALUES_EQUAL(addr.Addr()->sa_family, AF_INET);
        UNIT_ASSERT(addr.V4());
        UNIT_ASSERT(!memcmp(addr.V4(), &lb, sizeof(lb)));
        UNIT_ASSERT(!addr.V6());
    }

    Y_UNIT_TEST(TestIp4SockAddrZeroPort) {
        TSockAddr addr;
        auto err = TSockAddr::FromIpPort("127.0.0.1").AssignTo(addr);
        UNIT_ASSERT(!err);
        sockaddr_in lb{
            .sin_family = AF_INET,
            .sin_port = 0,
            .sin_addr = {.s_addr = htonl(INADDR_LOOPBACK)},
            .sin_zero = {},
        };
        UNIT_ASSERT(addr);
        UNIT_ASSERT_VALUES_EQUAL(addr, TSockAddr(lb));
        UNIT_ASSERT_VALUES_EQUAL(addr.ToString(), "127.0.0.1");
        UNIT_ASSERT_VALUES_EQUAL(addr.AddrFamily(), AF_INET);
        UNIT_ASSERT_VALUES_EQUAL(addr.Ip(), TIpAddr(in_addr{.s_addr = htonl(INADDR_LOOPBACK)}));
        UNIT_ASSERT_VALUES_EQUAL(addr.Port(), 0);
        UNIT_ASSERT_VALUES_EQUAL(addr.SizeOf(), sizeof(lb));
        UNIT_ASSERT_VALUES_EQUAL(addr.Hash(), CityHash64((const char*)&lb, sizeof(lb)));
        UNIT_ASSERT(addr.Addr());
        UNIT_ASSERT_VALUES_EQUAL(addr.Addr()->sa_family, AF_INET);
        UNIT_ASSERT(addr.V4());
        UNIT_ASSERT(!memcmp(addr.V4(), &lb, sizeof(lb)));
        UNIT_ASSERT(!addr.V6());
    }

    Y_UNIT_TEST(TestIp6SockAddrZeroPort) {
        TSockAddr addr;
        auto err = TSockAddr::FromIpPort("::1").AssignTo(addr);
        UNIT_ASSERT(!err);
        sockaddr_in6 lb{
            .sin6_family = AF_INET6,
            .sin6_port = 0,
            .sin6_flowinfo = 0,
            .sin6_addr = in6addr_loopback,
            .sin6_scope_id = 0,
        };
        UNIT_ASSERT(addr);
        UNIT_ASSERT_VALUES_EQUAL(addr, TSockAddr(lb));
        UNIT_ASSERT_VALUES_EQUAL(addr.ToString(), "::1");
        UNIT_ASSERT_VALUES_EQUAL(addr.AddrFamily(), AF_INET6);
        UNIT_ASSERT_VALUES_EQUAL(addr.Ip(), TIpAddr(in6addr_loopback));
        UNIT_ASSERT_VALUES_EQUAL(addr.Port(), 0);
        UNIT_ASSERT_VALUES_EQUAL(addr.SizeOf(), sizeof(lb));
        UNIT_ASSERT_VALUES_EQUAL(addr.Len(), sizeof(lb));
        UNIT_ASSERT_VALUES_EQUAL(addr.Hash(), CityHash64((const char*)&lb, sizeof(lb)));
        UNIT_ASSERT(addr.Addr());
        UNIT_ASSERT_VALUES_EQUAL(addr.Addr()->sa_family, AF_INET6);
        UNIT_ASSERT(!addr.V4());
        UNIT_ASSERT(addr.V6());
        UNIT_ASSERT(!memcmp(addr.V6(), &lb, sizeof(lb)));
        UNIT_ASSERT(!memcmp(addr.Addr(), &lb, sizeof(lb)));
    }

    Y_UNIT_TEST(TestSockAddrNetworkAddress) {
        std::vector<TSockAddr> addrs;
        auto err = TSockAddr::FromAddrInfo(TNetworkAddress("localhost", 12345)).AssignTo(addrs);
        UNIT_ASSERT(!err);
        UNIT_ASSERT(!addrs.empty());
        for (auto&& a : addrs) {
            UNIT_ASSERT(IsIn({"127.0.0.1:12345", "[::1]:12345"}, a.ToString()));
        }
    }

    Y_UNIT_TEST(TestSockAddrAddrInfo4) {
        addrinfo req = {};
        req.ai_flags = AI_NUMERICSERV | AI_NUMERICHOST;
        req.ai_family = AF_INET;
        addrinfo* resp = nullptr;
        Y_DEFER {
            if (resp) {
                freeaddrinfo(resp);
            }
        };
        UNIT_ASSERT(!getaddrinfo("127.0.0.1", "12345", &req, &resp));
        UNIT_ASSERT(resp);

        std::vector<TSockAddr> addrs;
        auto err = TSockAddr::FromAddrInfo(*resp).AssignTo(addrs);
        UNIT_ASSERT(!err);
        UNIT_ASSERT_VALUES_EQUAL(addrs.size(), 1);
        UNIT_ASSERT_VALUES_EQUAL(addrs[0].ToString(), "127.0.0.1:12345");
    }

    Y_UNIT_TEST(TestSockAddrAddrInfo6) {
        addrinfo req = {};
        req.ai_flags = AI_NUMERICSERV | AI_NUMERICHOST;
        req.ai_family = AF_INET6;
        addrinfo* resp = nullptr;
        Y_DEFER {
            if (resp) {
                freeaddrinfo(resp);
            }
        };
        UNIT_ASSERT(!getaddrinfo("::1", "12345", &req, &resp));
        UNIT_ASSERT(resp);

        std::vector<TSockAddr> addrs;
        auto err = TSockAddr::FromAddrInfo(*resp).AssignTo(addrs);
        UNIT_ASSERT(!err);
        UNIT_ASSERT_VALUES_EQUAL(addrs.size(), 1);
        UNIT_ASSERT_VALUES_EQUAL(addrs[0].ToString(), "[::1]:12345");
    }
}
