#include "ephemeral.h"

using namespace NSrvKernel;

static TSocketHolder TryBindV4() {
    TSocketHolder sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if ((SOCKET)sock < 0) {
        return {};
    }

    CheckedSetSockOpt(sock, SOL_SOCKET, SO_REUSEADDR, 1, "reuse addr");
    SetNonBlock(sock, true);

    struct sockaddr_in addr;
    Zero(addr);
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);

    if (bind(sock, (const struct sockaddr*)&addr, sizeof(addr)) == 0) {
        return sock;
    } else {
        return {};
    }
}

static TSocketHolder TryBindV6() {
    TSocketHolder sock = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP);
    if ((SOCKET)sock < 0) {
        return {};
    }

    FixIPv6ListenSocket(sock);
    CheckedSetSockOpt(sock, SOL_SOCKET, SO_REUSEADDR, 1, "reuse addr");
    SetNonBlock(sock, true);

    struct sockaddr_in6 addr;
    Zero(addr);
    addr.sin6_family = AF_INET6;
    addr.sin6_addr = in6addr_loopback;

    if (bind(sock, (const sockaddr*)&addr, sizeof(addr)) == 0) {
        return sock;
    } else {
        return {};
    }
}


static TSocketHolder TryBindIp(TStringBuf ipStr) {
    TNetworkAddress addresses(TString(ipStr), 0);
    for (auto addr = addresses.Begin(); addr != addresses.End(); ++addr) {
        TSocketHolder sock = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol);
        if ((SOCKET)sock < 0) {
            continue;
        }

        if (addr->ai_family == AF_INET6) {
            FixIPv6ListenSocket(sock);
        }

        CheckedSetSockOpt(sock, SOL_SOCKET, SO_REUSEADDR, 1, "reuse addr");
        SetNonBlock(sock, true);

        if (bind(sock, addr->ai_addr, addr->ai_addrlen) != 0) {
            continue;
        }

        return sock;
    }
    ythrow yexception() << "failed to bind ephemeral socket for " << ipStr;
}

TEphemeralBoundSocket::TEphemeralBoundSocket() {
    TSocketHolder sock = TryBindV4();
    if ((SOCKET)sock < 0) {
        sock = TryBindV6();
        if ((SOCKET)sock < 0) {
            ythrow TSystemError() << "failed to create child admin socket";
        }
    }

    NAddr::IRemoteAddrPtr addr = NAddr::GetSockAddr(sock);

    Socket = std::move(sock);
    Address = std::move(addr);
}

TEphemeralBoundSocket::TEphemeralBoundSocket(TStringBuf ip) {
    TSocketHolder sock(TryBindIp(ip));
    NAddr::IRemoteAddrPtr addr = NAddr::GetSockAddr(sock);
    Socket = std::move(sock);
    Address = std::move(addr);
}
