// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.

#include <algorithm>
#include <cstring>
#include <type_traits>
#include <unordered_map>
#include <vector>
#include "common/archives.h"
#include "common/assert.h"
#include "common/bit_field.h"
#include "common/common_types.h"
#include "common/logging/log.h"
#include "common/scope_exit.h"
#include "common/swap.h"
#include "core/core.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/shared_memory.h"
#include "core/hle/result.h"
#include "core/hle/service/soc/soc_u.h"
#include "network/socket_manager.h"

#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>

// MinGW does not define several errno constants
#ifndef _MSC_VER
#define EBADMSG 104
#define ENODATA 120
#define ENOMSG 122
#define ENOSR 124
#define ENOSTR 125
#define ETIME 137
#endif // _MSC_VER
#else
#include <cerrno>
#include <arpa/inet.h>
#include <fcntl.h>
#include <ifaddrs.h>
#include <netdb.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <poll.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#endif

#ifdef _WIN32
#define WSAEAGAIN WSAEWOULDBLOCK
#define WSAEMULTIHOP -1 // Invalid dummy value
#define ERRNO(x) WSA##x
#define GET_ERRNO WSAGetLastError()
#define poll(x, y, z) WSAPoll(x, y, z);
#define SHUT_RD SD_RECEIVE
#define SHUT_WR SD_SEND
#define SHUT_RDWR SD_BOTH
#else
#define ERRNO(x) x
#define GET_ERRNO errno
#define closesocket(x) close(x)
#endif
#define MSGCUSTOM_HANDLE_DONTWAIT 0x80000000

SERIALIZE_EXPORT_IMPL(Service::SOC::SOC_U)

// Change according the debugging needs
#define LOG_SEND_RECV LOG_TRACE
#define LOG_POLL LOG_TRACE

namespace Service::SOC {

const s32 SOCKET_ERROR_VALUE = -1;

static u32 SocketProtocolToPlatform(u32 protocol) {
    switch (protocol) {
    case 0:
        return 0;
    case 6:
        return IPPROTO_TCP;
    case 17:
        return IPPROTO_UDP;
    default:
        break;
    }
    return -1;
}

static u32 SocketProtocolFromPlatform(u32 protocol) {
    switch (protocol) {
    case 0:
        return 0;
    case IPPROTO_TCP:
        return 6;
    case IPPROTO_UDP:
        return 17;
    default:
        break;
    }
    return -1;
}

static u32 SocketDomainToPlatform(u32 domain) {
    switch (domain) {
    case 0:
        return AF_UNSPEC;
    case 2:
        return AF_INET;
    case 23:
        return AF_INET6;
    default:
        break;
    }
    return -1;
}

static u32 SocketDomainFromPlatform(u32 domain) {
    switch (domain) {
    case AF_UNSPEC:
        return 0;
    case AF_INET:
        return 2;
    case AF_INET6:
        return 23;
    default:
        break;
    }
    return -1;
}

static u32 SocketTypeToPlatform(u32 type) {
    switch (type) {
    case 0:
        return 0;
    case 1:
        return SOCK_STREAM;
    case 2:
        return SOCK_DGRAM;
    default:
        break;
    }
    return -1;
}

static u32 SocketTypeFromPlatform(u32 type) {
    switch (type) {
    case 0:
        return 0;
    case SOCK_STREAM:
        return 1;
    case SOCK_DGRAM:
        return 2;
    default:
        break;
    }
    return -1;
}

/// Holds the translation from system network errors to 3DS network errors
static const std::unordered_map<int, int> error_map = {{
    {E2BIG, 1},
    {ERRNO(EACCES), 2},
    {ERRNO(EADDRINUSE), 3},
    {ERRNO(EADDRNOTAVAIL), 4},
    {ERRNO(EAFNOSUPPORT), 5},
    {EAGAIN, 6},
#ifdef _WIN32
    {WSAEWOULDBLOCK, 6},
#else
#if EAGAIN != EWOULDBLOCK
    {EWOULDBLOCK, 6},
#endif
#endif // _WIN32
    {ERRNO(EALREADY), 7},
    {ERRNO(EBADF), 8},
    {EBADMSG, 9},
    {EBUSY, 10},
    {ECANCELED, 11},
    {ECHILD, 12},
    {ERRNO(ECONNABORTED), 13},
    {ERRNO(ECONNREFUSED), 14},
    {ERRNO(ECONNRESET), 15},
    {EDEADLK, 16},
    {ERRNO(EDESTADDRREQ), 17},
    {EDOM, 18},
    {ERRNO(EDQUOT), 19},
    {EEXIST, 20},
    {ERRNO(EFAULT), 21},
    {EFBIG, 22},
    {ERRNO(EHOSTUNREACH), 23},
    {EIDRM, 24},
    {EILSEQ, 25},
    {ERRNO(EINPROGRESS), 26},
    {ERRNO(EINTR), 27},
    {ERRNO(EINVAL), 28},
    {EIO, 29},
    {ERRNO(EISCONN), 30},
    {EISDIR, 31},
    {ERRNO(ELOOP), 32},
    {ERRNO(EMFILE), 33},
    {EMLINK, 34},
    {ERRNO(EMSGSIZE), 35},
#ifdef EMULTIHOP
    {ERRNO(EMULTIHOP), 36},
#endif
    {ERRNO(ENAMETOOLONG), 37},
    {ERRNO(ENETDOWN), 38},
    {ERRNO(ENETRESET), 39},
    {ERRNO(ENETUNREACH), 40},
    {ENFILE, 41},
    {ERRNO(ENOBUFS), 42},
#ifdef ENODATA
    {ENODATA, 43},
#endif
    {ENODEV, 44},
    {ENOENT, 45},
    {ENOEXEC, 46},
    {ENOLCK, 47},
    {ENOLINK, 48},
    {ENOMEM, 49},
    {ENOMSG, 50},
    {ERRNO(ENOPROTOOPT), 51},
    {ENOSPC, 52},
#ifdef ENOSR
    {ENOSR, 53},
#endif
#ifdef ENOSTR
    {ENOSTR, 54},
#endif
    {ENOSYS, 55},
    {ERRNO(ENOTCONN), 56},
#ifdef _WIN32
    {WSAESHUTDOWN, 56},
#endif
    {ENOTDIR, 57},
    {ERRNO(ENOTEMPTY), 58},
#ifdef _WIN32
    {ERRNO(ENOTSOCK), 8},
#else
    {ERRNO(ENOTSOCK), 59},
#endif
    {ENOTSUP, 60},
    {ENOTTY, 61},
    {ENXIO, 62},
    {ERRNO(EOPNOTSUPP), 63},
    {EOVERFLOW, 64},
    {EPERM, 65},
    {EPIPE, 66},
    {EPROTO, 67},
    {ERRNO(EPROTONOSUPPORT), 68},
    {ERRNO(EPROTOTYPE), 69},
    {ERANGE, 70},
    {EROFS, 71},
    {ESPIPE, 72},
    {ESRCH, 73},
    {ERRNO(ESTALE), 74},
#ifdef ETIME
    {ETIME, 75},
#endif
    {ERRNO(ETIMEDOUT), 76},
}};

/// Converts a network error from platform-specific to 3ds-specific
static int TranslateError(int error) {
    const auto& found = error_map.find(error);
    if (found != error_map.end()) {
        return -found->second;
    }
    return error;
}

struct CTRLinger {
    u32_le l_onoff;
    u32_le l_linger;
};

/// Holds the translation from system network socket options to 3DS network socket options
/// Note: -1 = No effect/unavailable
static constexpr u64 sockopt_map_key(int i, int j) {
    return (u64)i << 32 | (unsigned int)j;
}
const std::unordered_map<u64, std::pair<int, int>> SOC_U::sockopt_map = {{
    {sockopt_map_key(SOC_SOL_IP, 0x0007), {IPPROTO_IP, IP_TOS}},
    {sockopt_map_key(SOC_SOL_IP, 0x0008), {IPPROTO_IP, IP_TTL}},
    {sockopt_map_key(SOC_SOL_IP, 0x0009), {IPPROTO_IP, IP_MULTICAST_LOOP}},  // Never used?
    {sockopt_map_key(SOC_SOL_IP, 0x000A), {IPPROTO_IP, IP_MULTICAST_TTL}},   // Never used?
    {sockopt_map_key(SOC_SOL_IP, 0x000B), {IPPROTO_IP, IP_ADD_MEMBERSHIP}},  // Never used?
    {sockopt_map_key(SOC_SOL_IP, 0x000C), {IPPROTO_IP, IP_DROP_MEMBERSHIP}}, // Never used?
    {sockopt_map_key(SOC_SOL_SOCKET, 0x0004), {SOL_SOCKET, SO_REUSEADDR}},
    {sockopt_map_key(SOC_SOL_SOCKET, 0x0080), {SOL_SOCKET, SO_LINGER}},
    {sockopt_map_key(SOC_SOL_SOCKET, 0x0100), {SOL_SOCKET, SO_OOBINLINE}},
    {sockopt_map_key(SOC_SOL_SOCKET, 0x1001), {SOL_SOCKET, SO_SNDBUF}},
    {sockopt_map_key(SOC_SOL_SOCKET, 0x1002), {SOL_SOCKET, SO_RCVBUF}},
    {sockopt_map_key(SOC_SOL_SOCKET, 0x1003),
     {SOL_SOCKET, SO_SNDLOWAT}}, // Not supported in winsock2
    {sockopt_map_key(SOC_SOL_SOCKET, 0x1004),
     {SOL_SOCKET, SO_RCVLOWAT}}, // Not supported in winsock2
    {sockopt_map_key(SOC_SOL_SOCKET, 0x1008), {SOL_SOCKET, SO_TYPE}},
    {sockopt_map_key(SOC_SOL_SOCKET, 0x1009), {SOL_SOCKET, SO_ERROR}},
    {sockopt_map_key(SOC_SOL_TCP, 0x2001), {IPPROTO_TCP, TCP_NODELAY}},
    {sockopt_map_key(SOC_SOL_TCP, 0x2002), {IPPROTO_TCP, -1}}, // TCP_MAXSEG, never used?
    {sockopt_map_key(SOC_SOL_TCP, 0x2003), {IPPROTO_TCP, -1}}, // TCP_STDURG, never used?
}};

/// Converts a socket option from platform-specific to 3ds-specific
std::pair<int, int> SOC_U::TranslateSockOpt(int level, int opt) {
    const auto& found = sockopt_map.find(sockopt_map_key(level, opt));
    if (found != sockopt_map.end()) {
        return found->second;
    }
    return std::make_pair(SOL_SOCKET, opt);
}

void SOC_U::TranslateSockOptDataToPlatform(std::vector<u8>& out, const std::vector<u8>& in,
                                           int platform_level, int platform_opt) {
    // linger structure may be different between 3DS and platform
    if (platform_level == SOL_SOCKET && platform_opt == SO_LINGER &&
        in.size() == sizeof(CTRLinger)) {
        linger linger_out;
        linger_out.l_onoff = static_cast<decltype(linger_out.l_onoff)>(
            reinterpret_cast<const CTRLinger*>(in.data())->l_onoff);
        linger_out.l_linger = static_cast<decltype(linger_out.l_linger)>(
            reinterpret_cast<const CTRLinger*>(in.data())->l_linger);
        out.resize(sizeof(linger));
        std::memcpy(out.data(), &linger_out, sizeof(linger));
        return;
    }
    // Other options should have the size of an int, even for booleans
    int value;
    if (in.size() == sizeof(u8)) {
        value = static_cast<int>(*reinterpret_cast<const u8*>(in.data()));
    } else if (in.size() == sizeof(u16)) {
        value = static_cast<int>(*reinterpret_cast<const u16_le*>(in.data()));
    } else if (in.size() == sizeof(u32)) {
        value = static_cast<int>(*reinterpret_cast<const u32_le*>(in.data()));
    } else {
        LOG_ERROR(
            Service_SOC,
            "Unknown sockopt data combination: in_size={}, platform_level={}, platform_opt={}",
            in.size(), platform_level, platform_opt);
        out = in;
        return;
    }
    // Setting TTL to 0 means resetting it to the default value.
    if (platform_level == IPPROTO_IP && platform_opt == IP_TTL && value == 0) {
        value = SOC_TTL_DEFAULT;
    }
    out.resize(sizeof(int));
    std::memcpy(out.data(), &value, sizeof(int));
}

static u32 TranslateSockOptSizeToPlatform(int platform_level, int platform_opt) {
    if (platform_level == SOL_SOCKET && platform_opt == SO_LINGER)
        return sizeof(linger);
    return sizeof(int);
}

static void TranslateSockOptDataFromPlatform(std::vector<u8>& out, const std::vector<u8>& in,
                                             int platform_level, int platform_opt) {
    if (platform_level == SOL_SOCKET && platform_opt == SO_LINGER && in.size() == sizeof(linger)) {
        CTRLinger linger_out;
        linger_out.l_onoff = static_cast<decltype(linger_out.l_onoff)>(
            reinterpret_cast<const linger*>(in.data())->l_onoff);
        linger_out.l_linger = static_cast<decltype(linger_out.l_linger)>(
            reinterpret_cast<const linger*>(in.data())->l_linger);
        std::memcpy(out.data(), &linger_out, std::min(out.size(), sizeof(CTRLinger)));
        return;
    }
    if (out.size() == sizeof(u8) && in.size() == sizeof(int)) {
        *reinterpret_cast<u8*>(out.data()) =
            static_cast<u8>(*reinterpret_cast<const int*>(in.data()));
    } else if (out.size() == sizeof(u16_le) && in.size() == sizeof(int)) {
        *reinterpret_cast<u16_le*>(out.data()) =
            static_cast<u16_le>(*reinterpret_cast<const int*>(in.data()));
    } else if (out.size() == sizeof(u32_le) && in.size() == sizeof(int)) {
        *reinterpret_cast<u32_le*>(out.data()) =
            static_cast<u32_le>(*reinterpret_cast<const int*>(in.data()));
    } else {
        LOG_ERROR(Service_SOC,
                  "Unknown sockopt data combination: in_size={}, out_size={}, platform_level={}, "
                  "platform_opt={}",
                  in.size(), out.size(), platform_level, platform_opt);
        out = in;
        return;
    }
}

bool SOC_U::GetSocketBlocking(const SocketHolder& socket_holder) {
    return socket_holder.blocking;
}
u32 SOC_U::SetSocketBlocking(SocketHolder& socket_holder, bool blocking) {
    u32 posix_ret = 0;
#ifdef _WIN32
    unsigned long nonblocking = (blocking) ? 0 : 1;
    int ret = ioctlsocket(socket_holder.socket_fd, FIONBIO, &nonblocking);
    if (ret == SOCKET_ERROR_VALUE) {
        posix_ret = TranslateError(GET_ERRNO);
        return posix_ret;
    }
    socket_holder.blocking = blocking;
#else
    int flags = ::fcntl(socket_holder.socket_fd, F_GETFL, 0);
    if (flags == SOCKET_ERROR_VALUE) {
        posix_ret = TranslateError(GET_ERRNO);
        return posix_ret;
    }

    flags &= ~O_NONBLOCK;
    if (!blocking) { // O_NONBLOCK
        flags |= O_NONBLOCK;
    }

    socket_holder.blocking = blocking;

    const int ret = ::fcntl(socket_holder.socket_fd, F_SETFL, flags);
    if (ret == SOCKET_ERROR_VALUE) {
        posix_ret = TranslateError(GET_ERRNO);
        return posix_ret;
    }
#endif
    return posix_ret;
}

std::optional<std::reference_wrapper<SocketHolder>> SOC_U::GetSocketHolder(u32 ctr_socket_fd,
                                                                           u32 process_id,
                                                                           IPC::RequestParser& rp) {
    if (initialized_processes.find(process_id) == initialized_processes.end()) {
        LOG_DEBUG(Service_SOC, "Process not initialized: pid={}", process_id);
        IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
        rb.Push(ResultNotInitialized);
        return std::nullopt;
    }
    auto fd_info = created_sockets.find(ctr_socket_fd);
    if (fd_info == created_sockets.end()) {
        LOG_DEBUG(Service_SOC, "Invalid socket: pid={}, fd={}", process_id, ctr_socket_fd);
        IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
        rb.Push(ResultInvalidSocketDescriptor);
        return std::nullopt;
    }
    if (fd_info->second.ownerProcess != process_id && !fd_info->second.isGlobal) {
        LOG_DEBUG(Service_SOC, "Invalid process owner: pid={}, fd={}, owner_pid={}", process_id,
                  ctr_socket_fd, fd_info->second.ownerProcess);
        IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
        rb.Push(ResultWrongProcess);
        return std::nullopt;
    }
    return std::ref(fd_info->second);
}

void SOC_U::CloseAndDeleteAllSockets(s32 process_id) {
    std::erase_if(created_sockets, [process_id](const auto& entry) {
        if (process_id == -1 || entry.second.ownerProcess == static_cast<u32>(process_id)) {
            closesocket(entry.second.socket_fd);
            return true;
        }
        return false;
    });
}

static u32 SendRecvFlagsToPlatform(u32 flags) {
    u32 ret = 0;
    if (flags & 1) {
        ret |= MSG_OOB;
    }
    if (flags & 2) {
        ret |= MSG_PEEK;
    }
    if (flags & 4) {
        // Magic value to decide platform specific action
        ret |= MSGCUSTOM_HANDLE_DONTWAIT;
    }
    return ret;
}

static s32 ShutdownHowToPlatform(s32 how) {
    if (how == 0) {
        return SHUT_RD;
    }
    if (how == 1) {
        return SHUT_WR;
    }
    if (how == 2) {
        return SHUT_RDWR;
    }
    return -1;
}

/// Structure to represent the 3ds' pollfd structure, which is different than most implementations
struct CTRPollFD {
    u32 fd; ///< Socket handle

    union Events {
        u32 hex; ///< The complete value formed by the flags
        BitField<0, 1, u32> pollrdnorm;
        BitField<1, 1, u32> pollrdband; // The 3DS OS mistakenly uses POLLRDBAND as POLLPRI,
        BitField<2, 1, u32> pollpri;    // this is reflected in the translate functions below.
        BitField<3, 1, u32> pollwrnorm;
        BitField<4, 1, u32> pollwrband;
        BitField<5, 1, u32> pollerr;
        BitField<6, 1, u32> pollhup;
        BitField<7, 1, u32> pollnval;

        /// Translates the resulting events of a Poll operation from platform-specific to 3ds
        /// specific
        static Events TranslateTo3DS(u32 input_event, u8 haslibctrbug) {
            Events ev = {};

            if (input_event & POLLRDNORM)
                ev.pollrdnorm.Assign(1);
            if (input_event & POLLRDBAND)
                ev.pollpri.Assign(1);
            if (input_event & POLLHUP)
                ev.pollhup.Assign(1);
            if (input_event & POLLERR)
                ev.pollerr.Assign(1);
            if (input_event & POLLWRNORM) {
                if (haslibctrbug) {
                    ev.pollwrband.Assign(1);
                } else {
                    ev.pollwrnorm.Assign(1);
                }
            }
            if (input_event & POLLWRBAND)
                ev.pollwrband.Assign(1);
            if (input_event & POLLNVAL)
                ev.pollnval.Assign(1);
            return ev;
        }

        /// Translates the resulting events of a Poll operation from 3ds specific to platform
        /// specific
        static u32 TranslateToPlatform(Events input_event, bool isOutput, u8& has_libctru_bug) {
#if _WIN32
            constexpr bool isWin = true;
#else
            constexpr bool isWin = false;
#endif
            has_libctru_bug = 0;
            if (!input_event.pollwrnorm && input_event.pollwrband) {
                // Fixes a bug in libctru and some homebrew using libcurl
                // having the wrong value for POLLOUT
                input_event.pollwrnorm.Assign(1);
                input_event.pollwrband.Assign(0);
                has_libctru_bug = 1;
            }
            u32 ret = 0;
            if (input_event.pollrdnorm)
                ret |= POLLRDNORM;
            if (input_event.pollrdband && !isWin)
                ret |= POLLPRI;
            if (input_event.pollhup && isOutput)
                ret |= POLLHUP;
            if (input_event.pollerr && isOutput)
                ret |= POLLERR;
            if (input_event.pollwrnorm)
                ret |= POLLWRNORM;
            if (input_event.pollwrband && !isWin)
                ret |= POLLWRBAND;
            if (input_event.pollnval && isOutput)
                ret |= POLLNVAL;
            return ret;
        }
    };
    Events events;  ///< Events to poll for (input)
    Events revents; ///< Events received (output)

    /// Converts a platform-specific pollfd to a 3ds specific structure
    static CTRPollFD FromPlatform(SOC::SOC_U& socu, pollfd const& fd, u8 has_libctru_bug) {
        CTRPollFD result;
        result.events.hex = Events::TranslateTo3DS(fd.events, has_libctru_bug).hex;
        result.revents.hex = Events::TranslateTo3DS(fd.revents, has_libctru_bug).hex;
        for (const auto& socket : socu.created_sockets) {
            if (socket.second.socket_fd == fd.fd) {
                result.fd = socket.first;
                break;
            }
        }
        return result;
    }

    /// Converts a 3ds specific pollfd to a platform-specific structure
    static pollfd ToPlatform(SOC::SOC_U& socu, CTRPollFD const& fd, u8& haslibctrbug) {
        pollfd result;
        u8 unused = 0;
        result.events = Events::TranslateToPlatform(fd.events, false, haslibctrbug);
        result.revents = Events::TranslateToPlatform(fd.revents, true, unused);
        auto iter = socu.created_sockets.find(fd.fd);
        ASSERT(iter != socu.created_sockets.end());
        result.fd = iter->second.socket_fd;
        return result;
    }
};
static_assert(std::is_trivially_copyable_v<CTRPollFD>,
              "CTRPollFD is used with std::memcpy and must be trivially copyable");

/// Union to represent the 3ds' sockaddr structure
union CTRSockAddr {
    /// Structure to represent a raw sockaddr
    struct {
        u8 len;       ///< The length of the entire structure, only the set fields count
        u8 sa_family; ///< The address family of the sockaddr
        std::array<u8, 0x1A>
            sa_data; ///< The extra data, this varies, depending on the address family
    } raw;

    /// Structure to represent the 3ds' sockaddr_in structure
    struct CTRSockAddrIn {
        u8 len;        ///< The length of the entire structure
        u8 sin_family; ///< The address family of the sockaddr_in
        u16 sin_port;  ///< The port associated with this sockaddr_in
        u32 sin_addr;  ///< The actual address of the sockaddr_in
    } in;
    static_assert(sizeof(CTRSockAddrIn) == 8, "Invalid CTRSockAddrIn size");

    struct CTRSockAddrIn6 {
        u8 len;                         ///< The length of the entire structure
        u8 sin6_family;                 ///< The address family of the sockaddr_in6
        u16 sin6_port;                  ///< The port associated with this sockaddr_in6
        std::array<u8, 0x10> sin6_addr; ///< The actual address of the sockaddr_in6
        u32 sin6_flowinfo;              ///< The flow info of the sockaddr_in6
        u32 sin6_scope_id;              ///< The scope ID of the sockaddr_in6
    } in6;
    static_assert(sizeof(CTRSockAddrIn6) == 28, "Invalid CTRSockAddrIn6 size");

    /// Convert a 3DS CTRSockAddr to a platform-specific sockaddr
    static std::pair<sockaddr_storage, socklen_t> ToPlatform(CTRSockAddr const& ctr_addr) {
        sockaddr_storage result{};
        socklen_t result_len = sizeof(result.ss_family);
        ASSERT_MSG(ctr_addr.raw.len == sizeof(CTRSockAddrIn) ||
                       ctr_addr.raw.len == sizeof(CTRSockAddrIn6),
                   "Unhandled address size (len) in CTRSockAddr::ToPlatform");
        result.ss_family = SocketDomainToPlatform(ctr_addr.raw.sa_family);

        // We can not guarantee ABI compatibility between platforms so we copy the fields manually
        switch (result.ss_family) {
        case AF_INET: {
            sockaddr_in* result_in = reinterpret_cast<sockaddr_in*>(&result);
            result_in->sin_port = ctr_addr.in.sin_port;
            result_in->sin_addr.s_addr = ctr_addr.in.sin_addr;
            result_len = sizeof(sockaddr_in);
            break;
        }
        case AF_INET6: {
            sockaddr_in6* result_in6 = reinterpret_cast<sockaddr_in6*>(&result);
            result_in6->sin6_port = ctr_addr.in6.sin6_port;
            memcpy(&result_in6->sin6_addr, ctr_addr.in6.sin6_addr.data(),
                   sizeof(result_in6->sin6_addr));
            result_in6->sin6_flowinfo = ctr_addr.in6.sin6_flowinfo;
            result_in6->sin6_scope_id = ctr_addr.in6.sin6_scope_id;
            result_len = sizeof(sockaddr_in6);
            break;
        }
        default:
            ASSERT_MSG(false, "Unhandled address family (sa_family) in CTRSockAddr::ToPlatform");
            break;
        }
        return std::make_pair(result, result_len);
    }

    /// Convert a platform-specific sockaddr to a 3DS CTRSockAddr
    static CTRSockAddr FromPlatform(sockaddr_storage const& addr) {
        CTRSockAddr result;
        result.raw.sa_family = static_cast<u8>(SocketDomainFromPlatform(addr.ss_family));
        // We can not guarantee ABI compatibility between platforms so we copy the fields manually
        switch (addr.ss_family) {
        case AF_INET: {
            sockaddr_in const* addr_in = reinterpret_cast<sockaddr_in const*>(&addr);
            result.raw.len = sizeof(CTRSockAddrIn);
            result.in.sin_port = addr_in->sin_port;
            result.in.sin_addr = addr_in->sin_addr.s_addr;
            break;
        }
        case AF_INET6: {
            sockaddr_in6 const* addr_in6 = reinterpret_cast<sockaddr_in6 const*>(&addr);
            result.raw.len = sizeof(CTRSockAddrIn6);
            result.in6.sin6_port = addr_in6->sin6_port;
            memcpy(result.in6.sin6_addr.data(), &addr_in6->sin6_addr, sizeof(result.in6.sin6_addr));
            result.in6.sin6_flowinfo = addr_in6->sin6_flowinfo;
            result.in6.sin6_scope_id = addr_in6->sin6_scope_id;
            break;
        }
        default:
            ASSERT_MSG(false, "Unhandled address family (sa_family) in CTRSockAddr::ToPlatform");
            break;
        }
        return result;
    }
};

struct CTRAddrInfo {
    s32_le ai_flags;
    s32_le ai_family;
    s32_le ai_socktype;
    s32_le ai_protocol;
    s32_le ai_addrlen;
    char ai_canonname[256];
    CTRSockAddr ai_addr;

    static u32 AddressInfoFlagsToPlatform(u32 flags) {
        u32 ret = 0;
        if (flags & 1) {
            ret |= AI_PASSIVE;
        }
        if (flags & 2) {
            ret |= AI_CANONNAME;
        }
        if (flags & 4) {
            ret |= AI_NUMERICHOST;
        }
        if (flags & 8) {
            ret |= AI_NUMERICSERV;
        }
        return ret;
    }

    static u32 AddressInfoFlagsFromPlatform(u32 flags) {
        u32 ret = 0;
        if (flags & AI_PASSIVE) {
            ret |= 1;
        }
        if (flags & AI_CANONNAME) {
            ret |= 2;
        }
        if (flags & AI_NUMERICHOST) {
            ret |= 4;
        }
        if (flags & AI_NUMERICSERV) {
            ret |= 8;
        }
        return ret;
    }

    /// Converts a platform-specific addrinfo to a 3ds addrinfo.
    static CTRAddrInfo FromPlatform(const addrinfo& addr) {
        CTRAddrInfo ctr_addr{
            .ai_flags = static_cast<s32_le>(AddressInfoFlagsFromPlatform(addr.ai_flags)),
            .ai_family = static_cast<s32_le>(SocketDomainFromPlatform(addr.ai_family)),
            .ai_socktype = static_cast<s32_le>(SocketTypeFromPlatform(addr.ai_socktype)),
            .ai_protocol = static_cast<s32_le>(SocketProtocolFromPlatform(addr.ai_protocol)),
            .ai_addr =
                CTRSockAddr::FromPlatform(*reinterpret_cast<sockaddr_storage*>(addr.ai_addr)),
        };
        ctr_addr.ai_addrlen = static_cast<s32_le>(ctr_addr.ai_addr.raw.len);
        if (addr.ai_canonname)
            std::strncpy(ctr_addr.ai_canonname, addr.ai_canonname,
                         sizeof(ctr_addr.ai_canonname) - 1);
        return ctr_addr;
    }

    /// Converts a platform-specific addrinfo to a 3ds addrinfo.
    static addrinfo ToPlatform(const CTRAddrInfo& ctr_addr) {
        // Only certain fields are meaningful in hints, copy them manually
        addrinfo addr = {
            .ai_flags = static_cast<int>(AddressInfoFlagsToPlatform(ctr_addr.ai_flags)),
            .ai_family = static_cast<int>(SocketDomainToPlatform(ctr_addr.ai_family)),
            .ai_socktype = static_cast<int>(SocketTypeToPlatform(ctr_addr.ai_socktype)),
            .ai_protocol = static_cast<int>(SocketProtocolToPlatform(ctr_addr.ai_protocol)),
        };
        return addr;
    }
};

struct HostByNameData {
    static constexpr u32 max_entries = 24;

    u16_le addr_type;
    u16_le addr_len;
    u16_le addr_count;
    u16_le alias_count;
    std::array<char, 256> h_name;
    std::array<std::array<char, 256>, max_entries> aliases;
    std::array<std::array<u8, 16>, max_entries> addresses;
};
static_assert(sizeof(HostByNameData) == 0x1A88, "Invalid HostByNameData size");

static u32 NameInfoFlagsToPlatform(u32 flags) {
    u32 ret = 0;
    if (flags & 1) {
        ret |= NI_NOFQDN;
    }
    if (flags & 2) {
        ret |= NI_NUMERICHOST;
    }
    if (flags & 4) {
        ret |= NI_NAMEREQD;
    }
    if (flags & 8) {
        ret |= NI_NUMERICSERV;
    }
    if (flags & 16) {
        ret |= NI_DGRAM;
    }
    return ret;
}

static_assert(sizeof(CTRAddrInfo) == 0x130, "Size of CTRAddrInfo is not correct");

void SOC_U::Socket(Kernel::HLERequestContext& ctx) {
    IPC::RequestParser rp(ctx);
    const u32 domain = SocketDomainToPlatform(rp.Pop<u32>()); // Address family
    const u32 type = SocketTypeToPlatform(rp.Pop<u32>());
    const u32 protocol = SocketProtocolToPlatform(rp.Pop<u32>());
    const u32 pid = rp.PopPID();

    IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);

    // Only 0 is allowed according to 3dbrew, using 0 will let the OS decide which protocol to use
    if (protocol != 0) {
        rb.Push(UnimplementedFunction(ErrorModule::SOC)); // TODO(Subv): Correct error code
        rb.Skip(1, false);
        return;
    }

    if (domain != AF_INET) {
        rb.Push(UnimplementedFunction(ErrorModule::SOC)); // TODO(Subv): Correct error code
        rb.Skip(1, false);
        return;
    }

    if (type != SOCK_DGRAM && type != SOCK_STREAM) {
        rb.Push(UnimplementedFunction(ErrorModule::SOC)); // TODO(Subv): Correct error code
        rb.Skip(1, false);
        return;
    }

    u64 ret = static_cast<u64>(::socket(domain, type, protocol));
    u32 socketHandle = GetNextSocketID();

    if ((s64)ret != SOCKET_ERROR_VALUE) {
        created_sockets[socketHandle] = {
            .socket_fd = static_cast<decltype(SocketHolder::socket_fd)>(ret),
            .blocking = true,
            .isGlobal = false,
            .shutdown_rd = false,
            .ownerProcess = pid,
        };
#if _WIN32
        // Disable UDP connection reset
        int new_behavior = 0;
        unsigned long bytes_returned = 0;
        WSAIoctl(static_cast<SOCKET>(ret), _WSAIOW(IOC_VENDOR, 12), &new_behavior,
                 sizeof(new_behavior), NULL, 0, &bytes_returned, NULL, NULL);
#endif
    }

    if ((s64)ret == SOCKET_ERROR_VALUE) {
        ret = TranslateError(GET_ERRNO);
    } else {
        ret = socketHandle;
    }

    LOG_DEBUG(Service_SOC, "called, pid={}, ret={}", pid, static_cast<s32>(ret));

    rb.Push(ResultSuccess);
    rb.Push(static_cast<s32>(ret));
}

void SOC_U::Bind(Kernel::HLERequestContext& ctx) {
    IPC::RequestParser rp(ctx);
    const u32 socket_handle = rp.Pop<u32>();
    const u32 len = rp.Pop<u32>();
    const u32 pid = rp.PopPID();
    auto sock_addr_buf = rp.PopStaticBuffer();

    auto socket_holder_optional = GetSocketHolder(socket_handle, pid, rp);
    if (!socket_holder_optional) {
        return;
    }
    SocketHolder& holder = socket_holder_optional->get();

    CTRSockAddr ctr_sock_addr;
    std::memcpy(&ctr_sock_addr, sock_addr_buf.data(), std::min<size_t>(len, sizeof(ctr_sock_addr)));

    auto [sock_addr, sock_addr_len] = CTRSockAddr::ToPlatform(ctr_sock_addr);

    s32 ret = ::bind(holder.socket_fd, reinterpret_cast<sockaddr*>(&sock_addr), sock_addr_len);

    if (ret != 0)
        ret = TranslateError(GET_ERRNO);

    LOG_DEBUG(Service_SOC, "called, pid={}, fd={}, ret={}", pid, socket_handle,
              static_cast<s32>(ret));

    IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
    rb.Push(ResultSuccess);
    rb.Push(ret);
}

void SOC_U::Fcntl(Kernel::HLERequestContext& ctx) {
    IPC::RequestParser rp(ctx);
    const u32 socket_handle = rp.Pop<u32>();
    const u32 ctr_cmd = rp.Pop<u32>();
    const u32 ctr_arg = rp.Pop<u32>();
    const u32 pid = rp.PopPID();

    auto socket_holder_optional = GetSocketHolder(socket_handle, pid, rp);
    if (!socket_holder_optional) {
        return;
    }
    SocketHolder& holder = socket_holder_optional->get();

    u32 posix_ret = 0; // TODO: Check what hardware returns for F_SETFL (unspecified by POSIX)
    SCOPE_EXIT({
        LOG_DEBUG(Service_SOC, "called, pid={}, fd={}, ret={}", pid, socket_handle,
                  static_cast<s32>(posix_ret));

        IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
        rb.Push(ResultSuccess);
        rb.Push(posix_ret);
    });

    if (ctr_cmd == 3) { // F_GETFL
        posix_ret = 0;
        if (GetSocketBlocking(holder) == false)
            posix_ret |= 4;    // O_NONBLOCK
    } else if (ctr_cmd == 4) { // F_SETFL
        posix_ret = SetSocketBlocking(holder, !(ctr_arg & 4));
    } else {
        LOG_ERROR(Service_SOC, "Unsupported command ({}) in fcntl call", ctr_cmd);
        posix_ret = TranslateError(EINVAL); // TODO: Find the correct error
        return;
    }
}

void SOC_U::Listen(Kernel::HLERequestContext& ctx) {
    IPC::RequestParser rp(ctx);
    const u32 socket_handle = rp.Pop<u32>();
    const u32 backlog = rp.Pop<u32>();
    const u32 pid = rp.PopPID();

    auto socket_holder_optional = GetSocketHolder(socket_handle, pid, rp);
    if (!socket_holder_optional) {
        return;
    }
    SocketHolder& holder = socket_holder_optional->get();

    s32 ret = ::listen(holder.socket_fd, backlog);
    if (ret != 0)
        ret = TranslateError(GET_ERRNO);

    LOG_DEBUG(Service_SOC, "called, pid={}, fd={}, ret={}", pid, socket_handle,
              static_cast<s32>(ret));

    IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
    rb.Push(ResultSuccess);
    rb.Push(ret);
}

void SOC_U::Accept(Kernel::HLERequestContext& ctx) {
    IPC::RequestParser rp(ctx);
    const auto socket_handle = rp.Pop<u32>();
    const auto max_addr_len = rp.Pop<u32>();
    const u32 pid = rp.PopPID();

    auto socket_holder_optional = GetSocketHolder(socket_handle, pid, rp);
    if (!socket_holder_optional) {
        return;
    }
    SocketHolder& holder = socket_holder_optional->get();

    struct AsyncData {
        // Input
        u32 max_addr_len{};
        SocketHolder* fd_info;
        u32 pid;
        u32 socket_handle;

        // Output
        s32 ret{};
        int accept_error;
        sockaddr_storage addr;
    };

    auto async_data = std::make_shared<AsyncData>();
    async_data->max_addr_len = max_addr_len;
    async_data->fd_info = &holder;
    async_data->pid = pid;
    async_data->socket_handle = socket_handle;

    ctx.RunAsync(
        [async_data](Kernel::HLERequestContext& ctx) {
            socklen_t addr_len = sizeof(async_data->addr);
            async_data->ret = static_cast<u32>(
                ::accept(async_data->fd_info->socket_fd,
                         reinterpret_cast<sockaddr*>(&async_data->addr), &addr_len));
            async_data->accept_error = (async_data->ret == SOCKET_ERROR_VALUE) ? GET_ERRNO : 0;
            return 0;
        },
        [this, async_data](Kernel::HLERequestContext& ctx) {
            if (static_cast<s32>(async_data->ret) != SOCKET_ERROR_VALUE) {
                u32 socketID = GetNextSocketID();
                created_sockets[socketID] = {
                    .socket_fd = static_cast<decltype(SocketHolder::socket_fd)>(async_data->ret),
                    .blocking = true,
                    .isGlobal = false,
                    .shutdown_rd = false,
                    .ownerProcess = async_data->pid,
                };
                async_data->ret = socketID;
            }

            CTRSockAddr ctr_addr;
            std::vector<u8> ctr_addr_buf(sizeof(ctr_addr));
            if (static_cast<s32>(async_data->ret) == SOCKET_ERROR_VALUE) {
                async_data->ret = TranslateError(async_data->accept_error);
            } else {
                ctr_addr = CTRSockAddr::FromPlatform(async_data->addr);
                std::memcpy(ctr_addr_buf.data(), &ctr_addr, sizeof(ctr_addr));
            }

            if (ctr_addr_buf.size() > async_data->max_addr_len) {
                LOG_DEBUG(Service_SOC, "CTRSockAddr is too long, truncating data.");
                ctr_addr_buf.resize(async_data->max_addr_len);
            }

            LOG_DEBUG(Service_SOC, "called, pid={}, fd={}, ret={}", async_data->pid,
                      async_data->socket_handle, static_cast<s32>(async_data->ret));

            IPC::RequestBuilder rb(ctx, 0x04, 2, 2);
            rb.Push(ResultSuccess);
            rb.Push(async_data->ret);
            rb.PushStaticBuffer(std::move(ctr_addr_buf), 0);
        });
}

void SOC_U::SockAtMark(Kernel::HLERequestContext& ctx) {
    IPC::RequestParser rp(ctx);
    const auto socket_handle = rp.Pop<u32>();
    const u32 pid = rp.PopPID();

    auto socket_holder_optional = GetSocketHolder(socket_handle, pid, rp);
    if (!socket_holder_optional) {
        return;
    }
    [[maybe_unused]] SocketHolder& holder = socket_holder_optional->get();

    bool is_at_mark = false;
    int func_res = 0;
#ifdef _WIN32
    u_long atMark = 0;
    func_res = ::ioctlsocket(holder.socket_fd, SIOCATMARK, &atMark);
    is_at_mark = atMark != 0;
#else
#ifdef ANDROID
    func_res = 0;
    LOG_WARNING(Service_SOC, "(STUBBED) called");
#else
    func_res = ::sockatmark(holder.socket_fd);
#endif
    is_at_mark = func_res > 0;
#endif // _WIN32

    u32 ret;
    if (func_res == SOCKET_ERROR_VALUE) {
        ret = TranslateError(GET_ERRNO);
    } else {
        ret = is_at_mark ? 1 : 0;
    }

    LOG_POLL(Service_SOC, "called, pid={}, fd={}, ret={}", pid, socket_handle,
             static_cast<s32>(ret));

    IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
    rb.Push(ResultSuccess);
    rb.Push(ret);
}

void SOC_U::GetHostId(Kernel::HLERequestContext& ctx) {
    IPC::RequestParser rp(ctx);

    u32 host_id = 0;
    auto info = GetDefaultInterfaceInfo();
    if (info.has_value()) {
        host_id = info->address;
    }

    IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
    rb.Push(ResultSuccess);
    rb.Push(host_id);
}

void SOC_U::Close(Kernel::HLERequestContext& ctx) {
    IPC::RequestParser rp(ctx);
    const u32 socket_handle = rp.Pop<u32>();
    const u32 pid = rp.PopPID();

    auto socket_holder_optional = GetSocketHolder(socket_handle, pid, rp);
    if (!socket_holder_optional) {
        return;
    }
    SocketHolder& holder = socket_holder_optional->get();

    s32 ret = 0;
    ret = closesocket(holder.socket_fd);

    if (ret != 0) {
        ret = TranslateError(GET_ERRNO);
    } else {
        created_sockets.erase(socket_handle);
    }

    LOG_DEBUG(Service_SOC, "pid={}, fd={}, ret={}", pid, socket_handle, static_cast<s32>(ret));

    IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
    rb.Push(ResultSuccess);
    rb.Push(ret);
}

void SOC_U::SendToOther(Kernel::HLERequestContext& ctx) {
    IPC::RequestParser rp(ctx);
    const u32 socket_handle = rp.Pop<u32>();
    const u32 len = rp.Pop<u32>();
    u32 flags = SendRecvFlagsToPlatform(rp.Pop<u32>());
    const u32 addr_len = rp.Pop<u32>();
    const u32 pid = rp.PopPID();
    const auto dest_addr_buffer = rp.PopStaticBuffer();
    auto input_mapped_buff = rp.PopMappedBuffer();

    auto socket_holder_optional = GetSocketHolder(socket_handle, pid, rp);
    if (!socket_holder_optional) {
        return;
    }
    SocketHolder& holder = socket_holder_optional->get();

    bool dont_wait = (flags & MSGCUSTOM_HANDLE_DONTWAIT) != 0;
    flags &= ~MSGCUSTOM_HANDLE_DONTWAIT;
#ifdef _WIN32
    bool was_blocking = GetSocketBlocking(holder);
    if (dont_wait && was_blocking) {
        SetSocketBlocking(holder, false);
    }
#else
    if (dont_wait) {
        flags |= MSG_DONTWAIT;
    }
#endif // _WIN32
    std::vector<u8> input_buff(len);
    input_mapped_buff.Read(input_buff.data(), 0,
                           std::min(input_mapped_buff.GetSize(), static_cast<std::size_t>(len)));

    s32 ret = -1;
    if (addr_len > 0) {
        CTRSockAddr ctr_dest_addr;
        std::memcpy(&ctr_dest_addr, dest_addr_buffer.data(),
                    std::min<size_t>(addr_len, sizeof(ctr_dest_addr)));
        auto [dest_addr, dest_addr_len] = CTRSockAddr::ToPlatform(ctr_dest_addr);
        ret = static_cast<s32>(
            ::sendto(holder.socket_fd, reinterpret_cast<const char*>(input_buff.data()), len, flags,
                     reinterpret_cast<sockaddr*>(&dest_addr), dest_addr_len));
    } else {
        ret = static_cast<s32>(::sendto(holder.socket_fd,
                                        reinterpret_cast<const char*>(input_buff.data()), len,
                                        flags, nullptr, 0));
    }

    const auto send_error = (ret == SOCKET_ERROR_VALUE) ? GET_ERRNO : 0;

#ifdef _WIN32
    if (dont_wait && was_blocking) {
        SetSocketBlocking(holder, true);
    }
#endif

    if (ret == SOCKET_ERROR_VALUE) {
        ret = TranslateError(send_error);
    }

    LOG_SEND_RECV(Service_SOC, "called, fd={}, ret={}", socket_handle, static_cast<s32>(ret));

    IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
    rb.Push(ResultSuccess);
    rb.Push(ret);
}

s32 SOC_U::SendToImpl(SocketHolder& holder, u32 len, u32 flags, u32 addr_len,
                      const std::vector<u8>& input_buff, const u8* dest_addr_buff) {

    bool dont_wait = (flags & MSGCUSTOM_HANDLE_DONTWAIT) != 0;
    flags &= ~MSGCUSTOM_HANDLE_DONTWAIT;
#ifdef _WIN32
    bool was_blocking = GetSocketBlocking(holder);
    if (dont_wait && was_blocking) {
        SetSocketBlocking(holder, false);
    }
#else
    if (dont_wait) {
        flags |= MSG_DONTWAIT;
    }
#endif // _WIN32

    s32 ret = -1;
    if (addr_len > 0) {
        CTRSockAddr ctr_dest_addr;
        std::memcpy(&ctr_dest_addr, dest_addr_buff,
                    std::min<size_t>(addr_len, sizeof(ctr_dest_addr)));
        auto [dest_addr, dest_addr_len] = CTRSockAddr::ToPlatform(ctr_dest_addr);
        ret = static_cast<s32>(
            ::sendto(holder.socket_fd, reinterpret_cast<const char*>(input_buff.data()), len, flags,
                     reinterpret_cast<sockaddr*>(&dest_addr), dest_addr_len));
    } else {
        ret = static_cast<s32>(::sendto(holder.socket_fd,
                                        reinterpret_cast<const char*>(input_buff.data()), len,
                                        flags, nullptr, 0));
    }

    auto send_error = (ret == SOCKET_ERROR_VALUE) ? GET_ERRNO : 0;

#ifdef _WIN32
    if (dont_wait && was_blocking) {
        SetSocketBlocking(holder, true);
    }
#endif

    if (ret == SOCKET_ERROR_VALUE)
        ret = TranslateError(send_error);

    return ret;
}

void SOC_U::SendToSingle(Kernel::HLERequestContext& ctx) {
    IPC::RequestParser rp(ctx);
    const u32 socket_handle = rp.Pop<u32>();
    u32 len = rp.Pop<u32>();
    u32 flags = SendRecvFlagsToPlatform(rp.Pop<u32>());
    u32 addr_len = rp.Pop<u32>();
    const u32 pid = rp.PopPID();
    auto input_buff = rp.PopStaticBuffer();
    auto dest_addr_buff = rp.PopStaticBuffer();

    auto socket_holder_optional = GetSocketHolder(socket_handle, pid, rp);
    if (!socket_holder_optional) {
        return;
    }
    SocketHolder& holder = socket_holder_optional->get();

    s32 ret = SendToImpl(holder, len, flags, addr_len, input_buff, dest_addr_buff.data());

    LOG_SEND_RECV(Service_SOC, "called, fd={}, ret={}", socket_handle, static_cast<s32>(ret));

    IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
    rb.Push(ResultSuccess);
    rb.Push(ret);
}

// A poll busy loop has to be used when recieving from a blocking socket
// because calling shutdown on a socket that's currently in a blocking
// recv call does not make it return, which causes some games to hang.
void SOC_U::RecvBusyWaitForEvent(SocketHolder& holder) {
    constexpr int timeout_ms = 100;

    while (true) {
        pollfd poll_fd{};
        poll_fd.fd = holder.socket_fd;
        poll_fd.events = POLLIN; // Check for available data.
        int res = ::poll(&poll_fd, 1, timeout_ms);
        // Break if there is any event, error or socket RD shutdown.
        if (res != 0 || holder.shutdown_rd) {
            break;
        }
    }
}

void SOC_U::RecvFromOther(Kernel::HLERequestContext& ctx) {
    IPC::RequestParser rp(ctx);
    const u32 socket_handle = rp.Pop<u32>();
    u32 len = rp.Pop<u32>();
    u32 flags = SendRecvFlagsToPlatform(rp.Pop<u32>());
    u32 addr_len = rp.Pop<u32>();
    const u32 pid = rp.PopPID();
    Kernel::MappedBuffer& buffer = rp.PopMappedBuffer();

    auto socket_holder_optional = GetSocketHolder(socket_handle, pid, rp);
    if (!socket_holder_optional) {
        return;
    }
    SocketHolder& holder = socket_holder_optional->get();

    bool dont_wait = (flags & MSGCUSTOM_HANDLE_DONTWAIT) != 0;
    flags &= ~MSGCUSTOM_HANDLE_DONTWAIT;
#ifdef _WIN32
    bool was_blocking = GetSocketBlocking(holder);
    if (dont_wait && was_blocking) {
        SetSocketBlocking(holder, false);
    }
#else
    if (dont_wait) {
        flags |= MSG_DONTWAIT;
    }
#endif // _WIN32

    bool needs_async = GetSocketBlocking(holder) && !dont_wait;
    struct AsyncData {
        // Input
        u32 len{};
        u32 flags{};
        u32 addr_len{};
        SocketHolder* fd_info;
        u32 socket_handle;
#ifdef _WIN32
        bool dont_wait;
        bool was_blocking;
#endif
        bool is_blocking;

        // Output
        s32 ret{};
        int recv_error;
        Kernel::MappedBuffer* buffer;
        std::vector<u8> output_buff;
        std::vector<u8> addr_buff;
    };

    auto async_data = std::make_shared<AsyncData>();
    async_data->buffer = &buffer;
    async_data->ret = -1;
    async_data->len = len;
    async_data->flags = flags;
    async_data->addr_len = addr_len;
    async_data->output_buff.resize(len);
    async_data->addr_buff.resize(addr_len);
    async_data->fd_info = &holder;
    async_data->socket_handle = socket_handle;
#ifdef _WIN32
    async_data->dont_wait = dont_wait;
    async_data->was_blocking = was_blocking;
#endif
    async_data->is_blocking = needs_async;

    ctx.RunAsync(
        [async_data](Kernel::HLERequestContext& ctx) {
            sockaddr_storage src_addr;
            socklen_t src_addr_len = sizeof(src_addr);
            CTRSockAddr ctr_src_addr;
            // Windows, why do you have to be so special...
            if (async_data->is_blocking) {
                RecvBusyWaitForEvent(*async_data->fd_info);
            }
            if (async_data->addr_len > 0) {
                async_data->ret = static_cast<s32>(::recvfrom(
                    async_data->fd_info->socket_fd,
                    reinterpret_cast<char*>(async_data->output_buff.data()), async_data->len,
                    async_data->flags, reinterpret_cast<sockaddr*>(&src_addr), &src_addr_len));
                if (async_data->ret >= 0 && src_addr_len > 0) {
                    ctr_src_addr = CTRSockAddr::FromPlatform(src_addr);
                    std::memcpy(async_data->addr_buff.data(), &ctr_src_addr,
                                std::min<size_t>(async_data->addr_len, sizeof(ctr_src_addr)));
                }
            } else {
                async_data->ret = static_cast<s32>(
                    ::recvfrom(async_data->fd_info->socket_fd,
                               reinterpret_cast<char*>(async_data->output_buff.data()),
                               async_data->len, async_data->flags, NULL, 0));
                async_data->addr_buff.resize(0);
            }
            async_data->recv_error = (async_data->ret == SOCKET_ERROR_VALUE) ? GET_ERRNO : 0;
            return 0;
        },
        [this, async_data](Kernel::HLERequestContext& ctx) {
            if (async_data->ret == SOCKET_ERROR_VALUE) {
                async_data->ret = TranslateError(async_data->recv_error);
            } else {
                async_data->buffer->Write(async_data->output_buff.data(), 0, async_data->ret);
            }
#ifdef _WIN32
            if (async_data->dont_wait && async_data->was_blocking) {
                SetSocketBlocking(*async_data->fd_info, true);
            }
#else
            (void)this;
#endif
            LOG_SEND_RECV(Service_SOC, "called, fd={}, ret={}", async_data->socket_handle,
                          static_cast<s32>(async_data->ret));

            IPC::RequestBuilder rb(ctx, 0x07, 2, 4);
            rb.Push(ResultSuccess);
            rb.Push(async_data->ret);
            rb.PushStaticBuffer(std::move(async_data->addr_buff), 0);
            rb.PushMappedBuffer(*async_data->buffer);
        },
        needs_async);
}

void SOC_U::RecvFrom(Kernel::HLERequestContext& ctx) {
    IPC::RequestParser rp(ctx);
    const u32 socket_handle = rp.Pop<u32>();
    u32 len = rp.Pop<u32>();
    u32 flags = SendRecvFlagsToPlatform(rp.Pop<u32>());
    u32 addr_len = rp.Pop<u32>();
    const u32 pid = rp.PopPID();

    auto socket_holder_optional = GetSocketHolder(socket_handle, pid, rp);
    if (!socket_holder_optional) {
        return;
    }
    SocketHolder& holder = socket_holder_optional->get();

    bool dont_wait = (flags & MSGCUSTOM_HANDLE_DONTWAIT) != 0;
    flags &= ~MSGCUSTOM_HANDLE_DONTWAIT;
#ifdef _WIN32
    bool was_blocking = GetSocketBlocking(holder);
    if (dont_wait && was_blocking) {
        SetSocketBlocking(holder, false);
    }
#else
    if (dont_wait) {
        flags |= MSG_DONTWAIT;
    }
#endif // _WIN32

    bool needs_async = GetSocketBlocking(holder) && !dont_wait;
    struct AsyncData {
        // Input
        u32 len{};
        u32 flags{};
        u32 addr_len{};
        SocketHolder* fd_info;
        u32 socket_handle;
#ifdef _WIN32
        bool dont_wait;
        bool was_blocking;
#endif
        bool is_blocking;

        // Output
        s32 ret{};
        int recv_error;
        std::vector<u8> output_buff;
        std::vector<u8> addr_buff;
    };

    auto async_data = std::make_shared<AsyncData>();
    async_data->ret = -1;
    async_data->len = len;
    async_data->flags = flags;
    async_data->addr_len = addr_len;
    async_data->output_buff.resize(len);
    async_data->addr_buff.resize(addr_len);
    async_data->fd_info = &holder;
    async_data->socket_handle = socket_handle;
#ifdef _WIN32
    async_data->dont_wait = dont_wait;
    async_data->was_blocking = was_blocking;
#endif
    async_data->is_blocking = needs_async;

    ctx.RunAsync(
        [async_data](Kernel::HLERequestContext& ctx) {
            sockaddr_storage src_addr;
            socklen_t src_addr_len = sizeof(src_addr);
            CTRSockAddr ctr_src_addr;
            if (async_data->is_blocking) {
                RecvBusyWaitForEvent(*async_data->fd_info);
            }
            if (async_data->addr_len > 0) {
                // Only get src adr if input adr available
                async_data->ret = static_cast<s32>(::recvfrom(
                    async_data->fd_info->socket_fd,
                    reinterpret_cast<char*>(async_data->output_buff.data()), async_data->len,
                    async_data->flags, reinterpret_cast<sockaddr*>(&src_addr), &src_addr_len));
                if (async_data->ret >= 0 && src_addr_len > 0) {
                    ctr_src_addr = CTRSockAddr::FromPlatform(src_addr);
                    std::memcpy(async_data->addr_buff.data(), &ctr_src_addr,
                                std::min<size_t>(async_data->addr_len, sizeof(ctr_src_addr)));
                }
            } else {
                async_data->ret = static_cast<s32>(
                    ::recvfrom(async_data->fd_info->socket_fd,
                               reinterpret_cast<char*>(async_data->output_buff.data()),
                               async_data->len, async_data->flags, NULL, 0));
                async_data->addr_buff.resize(0);
            }
            async_data->recv_error = (async_data->ret == SOCKET_ERROR_VALUE) ? GET_ERRNO : 0;
            return 0;
        },
        [this, async_data](Kernel::HLERequestContext& ctx) {

#ifdef _WIN32
            if (async_data->dont_wait && async_data->was_blocking) {
                SetSocketBlocking(*async_data->fd_info, true);
            }
#else
            (void)this;
#endif
            s32 total_received = async_data->ret;
            if (async_data->ret == SOCKET_ERROR_VALUE) {
                async_data->ret = TranslateError(async_data->recv_error);
                total_received = 0;
            }

            // Write only the data we received to avoid overwriting parts of the buffer with zeros
            async_data->output_buff.resize(total_received);

            LOG_SEND_RECV(Service_SOC, "called, fd={}, ret={}", async_data->socket_handle,
                          static_cast<s32>(async_data->ret));

            IPC::RequestBuilder rb(ctx, 0x08, 3, 4);
            rb.Push(ResultSuccess);
            rb.Push(async_data->ret);
            rb.Push(total_received);
            rb.PushStaticBuffer(std::move(async_data->output_buff), 0);
            rb.PushStaticBuffer(std::move(async_data->addr_buff), 1);
        },
        needs_async);
}

void SOC_U::Poll(Kernel::HLERequestContext& ctx) {
    IPC::RequestParser rp(ctx);
    u32 nfds = rp.Pop<u32>();
    s32 timeout = rp.Pop<s32>();
    const u32 pid = rp.PopPID();
    auto input_fds = rp.PopStaticBuffer();

    struct AsyncData {
        // Input
        s32 timeout;
        u32 nfds;

        // Input/Output
        std::vector<pollfd> platform_pollfd;
        std::vector<u8> has_libctru_bug;
        std::vector<CTRPollFD> ctr_fds;

        // Output
        s32 ret;
        int poll_error;
    };
    auto async_data = std::make_shared<AsyncData>();
    async_data->timeout = timeout;
    async_data->nfds = nfds;

    async_data->ctr_fds.resize(nfds);
    std::memcpy(async_data->ctr_fds.data(), input_fds.data(), nfds * sizeof(CTRPollFD));

    // The 3ds_pollfd and the pollfd structures may be different (Windows/Linux have different
    // sizes)
    // so we have to copy the data in order
    async_data->platform_pollfd.resize(nfds);
    async_data->has_libctru_bug.resize(nfds, false);
    for (u32 i = 0; i < nfds; i++) {
        if (!GetSocketHolder(async_data->ctr_fds[i].fd, pid, rp)) {
            return;
        }
        async_data->platform_pollfd[i] =
            CTRPollFD::ToPlatform(*this, async_data->ctr_fds[i], async_data->has_libctru_bug[i]);
    }

    ctx.RunAsync(
        [async_data](Kernel::HLERequestContext& ctx) {
            async_data->ret =
                ::poll(async_data->platform_pollfd.data(), async_data->nfds, async_data->timeout);
            if (async_data->ret == SOCKET_ERROR_VALUE) {
                async_data->poll_error = GET_ERRNO;
            }
            return 0;
        },
        [this, async_data](Kernel::HLERequestContext& ctx) {
            // Now update the output 3ds_pollfd structure
            for (u32 i = 0; i < async_data->nfds; i++) {
                async_data->ctr_fds[i] = CTRPollFD::FromPlatform(
                    *this, async_data->platform_pollfd[i], async_data->has_libctru_bug[i]);
            }

            std::vector<u8> output_fds(async_data->nfds * sizeof(CTRPollFD));
            std::memcpy(output_fds.data(), async_data->ctr_fds.data(),
                        async_data->nfds * sizeof(CTRPollFD));

            if (async_data->ret == SOCKET_ERROR_VALUE) {
                async_data->ret = TranslateError(async_data->poll_error);
            }

            IPC::RequestBuilder rb(ctx, static_cast<u16>(ctx.CommandHeader().command_id.Value()), 2,
                                   2);
            rb.Push(ResultSuccess);
            rb.Push(async_data->ret);
            rb.PushStaticBuffer(std::move(output_fds), 0);

            LOG_POLL(Service_SOC, "called, fd_count={}, ret={}", async_data->nfds,
                     static_cast<s32>(async_data->ret));
        },
        timeout != 0);
}

void SOC_U::GetSockName(Kernel::HLERequestContext& ctx) {
    IPC::RequestParser rp(ctx);
    const auto socket_handle = rp.Pop<u32>();
    const auto max_addr_len = rp.Pop<u32>();
    const u32 pid = rp.PopPID();

    auto socket_holder_optional = GetSocketHolder(socket_handle, pid, rp);
    if (!socket_holder_optional) {
        return;
    }
    SocketHolder& holder = socket_holder_optional->get();

    sockaddr_storage dest_addr;
    socklen_t dest_addr_len = sizeof(dest_addr);
    s32 ret =
        ::getsockname(holder.socket_fd, reinterpret_cast<sockaddr*>(&dest_addr), &dest_addr_len);

    CTRSockAddr ctr_dest_addr = CTRSockAddr::FromPlatform(dest_addr);
    std::vector<u8> dest_addr_buff(sizeof(ctr_dest_addr));
    std::memcpy(dest_addr_buff.data(), &ctr_dest_addr, sizeof(ctr_dest_addr));

    if (ret != 0)
        ret = TranslateError(GET_ERRNO);

    if (dest_addr_buff.size() > max_addr_len) {
        LOG_DEBUG(Service_SOC, "CTRSockAddr is too long, truncating data.");
        dest_addr_buff.resize(max_addr_len);
    }

    LOG_DEBUG(Service_SOC, "called, pid={}, fd={}, ret={}", pid, socket_handle,
              static_cast<s32>(ret));

    IPC::RequestBuilder rb = rp.MakeBuilder(2, 2);
    rb.Push(ResultSuccess);
    rb.Push(ret);
    rb.PushStaticBuffer(std::move(dest_addr_buff), 0);
}

void SOC_U::Shutdown(Kernel::HLERequestContext& ctx) {
    IPC::RequestParser rp(ctx);
    const u32 socket_handle = rp.Pop<u32>();
    s32 how = ShutdownHowToPlatform(rp.Pop<s32>());
    const u32 pid = rp.PopPID();

    auto socket_holder_optional = GetSocketHolder(socket_handle, pid, rp);
    if (!socket_holder_optional) {
        return;
    }
    SocketHolder& holder = socket_holder_optional->get();

    s32 ret = ::shutdown(holder.socket_fd, how);
    if (ret != 0) {
        ret = TranslateError(GET_ERRNO);
    } else {
        if (how == SHUT_RD || how == SHUT_RDWR) {
            holder.shutdown_rd = true;
        }
    }

    LOG_DEBUG(Service_SOC, "called, pid={}, fd={}, ret={}", pid, socket_handle,
              static_cast<s32>(ret));

    IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
    rb.Push(ResultSuccess);
    rb.Push(ret);
}

static std::vector<u8> HostEntToHostByNameData(struct hostent* result) {
    std::vector<u8> hbn_data_out(sizeof(HostByNameData));
    HostByNameData& hbn_data = *reinterpret_cast<HostByNameData*>(hbn_data_out.data());

    hbn_data.addr_type = result->h_addrtype;
    hbn_data.addr_len = result->h_length;
    std::strncpy(hbn_data.h_name.data(), result->h_name, 255);
    u16 count;
    for (count = 0; count < HostByNameData::max_entries; count++) {
        char* curr = result->h_aliases[count];
        if (!curr) {
            break;
        }
        std::strncpy(hbn_data.aliases[count].data(), curr, 255);
    }
    hbn_data.alias_count = count;
    for (count = 0; count < HostByNameData::max_entries; count++) {
        char* curr = result->h_addr_list[count];
        if (!curr) {
            break;
        }
        std::memcpy(hbn_data.addresses[count].data(), curr, result->h_length);
    }
    hbn_data.addr_count = count;

    return hbn_data_out;
}

void SOC_U::GetHostByAddr(Kernel::HLERequestContext& ctx) {
    IPC::RequestParser rp(ctx);
    [[maybe_unused]] int addr_len = static_cast<int>(rp.Pop<u32>());
    int type = static_cast<int>(SocketDomainToPlatform(rp.Pop<u32>()));
    [[maybe_unused]] u32 out_buf_len = rp.Pop<u32>();
    auto addr = rp.PopStaticBuffer();

    auto [platform_addr, platform_addr_len] =
        CTRSockAddr::ToPlatform(*reinterpret_cast<CTRSockAddr*>(addr.data()));

    struct hostent* result =
        ::gethostbyaddr(reinterpret_cast<char*>(&platform_addr), platform_addr_len, type);

    IPC::RequestBuilder rb = rp.MakeBuilder(2, 2);
    rb.Push(ResultSuccess);
    s32 ret;
    if (!result) {
        rb.Push(ret = TranslateError(GET_ERRNO));
    } else {
        rb.Push(ret = 0);
        rb.PushStaticBuffer(HostEntToHostByNameData(result), 0);
    }

    LOG_DEBUG(Service_SOC, "called, ret={}", static_cast<s32>(ret));
}

void SOC_U::GetHostByName(Kernel::HLERequestContext& ctx) {
    IPC::RequestParser rp(ctx);
    [[maybe_unused]] u32 name_len = rp.Pop<u32>();
    [[maybe_unused]] u32 out_buf_len = rp.Pop<u32>();
    auto host_name = rp.PopStaticBuffer();

    struct hostent* result = ::gethostbyname(reinterpret_cast<char*>(host_name.data()));

    IPC::RequestBuilder rb = rp.MakeBuilder(2, 2);
    rb.Push(ResultSuccess);
    s32 ret;
    if (!result) {
        rb.Push(ret = TranslateError(GET_ERRNO));
    } else {
        rb.Push(ret = 0);
        rb.PushStaticBuffer(HostEntToHostByNameData(result), 0);
    }

    LOG_DEBUG(Service_SOC, "called, ret={}", static_cast<s32>(ret));
}

void SOC_U::GetPeerName(Kernel::HLERequestContext& ctx) {
    IPC::RequestParser rp(ctx);
    const auto socket_handle = rp.Pop<u32>();
    const auto max_addr_len = rp.Pop<u32>();
    const u32 pid = rp.PopPID();

    auto socket_holder_optional = GetSocketHolder(socket_handle, pid, rp);
    if (!socket_holder_optional) {
        return;
    }
    SocketHolder& holder = socket_holder_optional->get();

    sockaddr_storage dest_addr;
    socklen_t dest_addr_len = sizeof(dest_addr);
    const int ret =
        ::getpeername(holder.socket_fd, reinterpret_cast<sockaddr*>(&dest_addr), &dest_addr_len);

    CTRSockAddr ctr_dest_addr = CTRSockAddr::FromPlatform(dest_addr);
    std::vector<u8> dest_addr_buff(sizeof(ctr_dest_addr));
    std::memcpy(dest_addr_buff.data(), &ctr_dest_addr, sizeof(ctr_dest_addr));

    int result = 0;
    if (ret != 0) {
        result = TranslateError(GET_ERRNO);
    }

    if (dest_addr_buff.size() > max_addr_len) {
        LOG_DEBUG(Service_SOC, "CTRSockAddr is too long, truncating data.");
        dest_addr_buff.resize(max_addr_len);
    }

    LOG_DEBUG(Service_SOC, "called, pid={}, fd={}, ret={}", pid, socket_handle,
              static_cast<s32>(result));

    IPC::RequestBuilder rb = rp.MakeBuilder(2, 2);
    rb.Push(ResultSuccess);
    rb.Push(result);
    rb.PushStaticBuffer(std::move(dest_addr_buff), 0);
}

void SOC_U::Connect(Kernel::HLERequestContext& ctx) {
    IPC::RequestParser rp(ctx);
    const auto socket_handle = rp.Pop<u32>();
    const auto input_addr_len = rp.Pop<u32>();
    const u32 pid = rp.PopPID();
    auto input_addr_buf = rp.PopStaticBuffer();

    auto socket_holder_optional = GetSocketHolder(socket_handle, pid, rp);
    if (!socket_holder_optional) {
        return;
    }
    SocketHolder& holder = socket_holder_optional->get();

    struct AsyncData {
        // Input
        SocketHolder* fd_info;
        std::pair<sockaddr_storage, socklen_t> input_addr;
        u32 socket_handle;
        u32 pid;

        // Output
        s32 ret{};
        int connect_error;
    };

    auto async_data = std::make_shared<AsyncData>();
    async_data->fd_info = &holder;
    async_data->pid = pid;

    CTRSockAddr ctr_input_addr;
    std::memcpy(&ctr_input_addr, input_addr_buf.data(),
                std::min<size_t>(input_addr_len, sizeof(ctr_input_addr)));

    async_data->input_addr = CTRSockAddr::ToPlatform(ctr_input_addr);
    async_data->socket_handle = socket_handle;

    ctx.RunAsync(
        [async_data](Kernel::HLERequestContext& ctx) {
            async_data->ret = ::connect(async_data->fd_info->socket_fd,
                                        reinterpret_cast<sockaddr*>(&async_data->input_addr.first),
                                        async_data->input_addr.second);
            async_data->connect_error = (async_data->ret == SOCKET_ERROR_VALUE) ? GET_ERRNO : 0;
            return 0;
        },
        [async_data](Kernel::HLERequestContext& ctx) {
            if (async_data->ret != 0) {
                async_data->ret = TranslateError(async_data->connect_error);
            }

            LOG_DEBUG(Service_SOC, "called, pid={}, fd={}, ret={}", async_data->pid,
                      async_data->socket_handle, static_cast<s32>(async_data->ret));

            IPC::RequestBuilder rb(ctx, 0x06, 2, 0);
            rb.Push(ResultSuccess);
            rb.Push(async_data->ret);
        });
}

void SOC_U::InitializeSockets(Kernel::HLERequestContext& ctx) {
    IPC::RequestParser rp(ctx);
    [[maybe_unused]] const auto memory_block_size = rp.Pop<u32>();
    const u32 pid = rp.PopPID();
    [[maybe_unused]] auto shared_memory = rp.PopObject<Kernel::SharedMemory>();

    Result res = ResultSuccess;
    if (initialized_processes.find(pid) == initialized_processes.end()) {
        initialized_processes.insert(pid);
    } else {
        res = ResultAlreadyInitialized;
    }

    LOG_DEBUG(Service_SOC, "called, pid={}, res={:#08X}", pid, res.raw);

    IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
    rb.Push(res);
}

void SOC_U::ShutdownSockets(Kernel::HLERequestContext& ctx) {
    IPC::RequestParser rp(ctx);

    // This service call does not provide a PID like the others,
    // instead SOC seems to fetch the PID from the current session.
    const u32 pid = ctx.ClientThread()->owner_process.lock()->process_id;

    if (initialized_processes.find(pid) == initialized_processes.end()) {
        LOG_DEBUG(Service_SOC, "Process not initialized: pid={}", pid);
        IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
        rb.Push(ResultNotInitialized);
        return;
    }

    CloseAndDeleteAllSockets(pid);
    initialized_processes.erase(pid);

    LOG_DEBUG(Service_SOC, "called, pid={}", pid);

    IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
    rb.Push(ResultSuccess);
}

void SOC_U::GetSockOpt(Kernel::HLERequestContext& ctx) {
    IPC::RequestParser rp(ctx);
    const u32 socket_handle = rp.Pop<u32>();
    const u32 level = rp.Pop<u32>();
    const s32 optname = rp.Pop<s32>();
    u32 optlen = rp.Pop<u32>();
    const u32 pid = rp.PopPID();

    auto socket_holder_optional = GetSocketHolder(socket_handle, pid, rp);
    if (!socket_holder_optional) {
        return;
    }
    SocketHolder& holder = socket_holder_optional->get();

    s32 err = 0;

    std::vector<u8> optval(optlen);

    if (optname < 0) {
#ifdef _WIN32
        err = WSAEINVAL;
#else
        err = EINVAL;
#endif
    } else {
        const auto level_opt = TranslateSockOpt(level, optname);
        std::vector<u8> platform_data(
            TranslateSockOptSizeToPlatform(level_opt.first, level_opt.second));
        socklen_t platform_data_size = static_cast<socklen_t>(platform_data.size());
        err = ::getsockopt(holder.socket_fd, level_opt.first, level_opt.second,
                           reinterpret_cast<char*>(platform_data.data()), &platform_data_size);
        if (err == SOCKET_ERROR_VALUE) {
            err = TranslateError(GET_ERRNO);
        } else {
            platform_data.resize(static_cast<std::size_t>(platform_data_size));
            TranslateSockOptDataFromPlatform(optval, platform_data, level_opt.first,
                                             level_opt.second);
        }
    }

    LOG_DEBUG(Service_SOC, "called, pid={}, fd={}, ret={}", pid, socket_handle,
              static_cast<s32>(err));

    IPC::RequestBuilder rb = rp.MakeBuilder(3, 2);
    rb.Push(ResultSuccess);
    rb.Push(err);
    rb.Push(static_cast<u32>(optval.size()));
    rb.PushStaticBuffer(std::move(optval), 0);
}

void SOC_U::SetSockOpt(Kernel::HLERequestContext& ctx) {
    IPC::RequestParser rp(ctx);
    const auto socket_handle = rp.Pop<u32>();
    const auto level = rp.Pop<u32>();
    const auto optname = rp.Pop<s32>();
    const auto optlen = rp.Pop<u32>();
    const u32 pid = rp.PopPID();
    auto optval = rp.PopStaticBuffer();

    auto socket_holder_optional = GetSocketHolder(socket_handle, pid, rp);
    if (!socket_holder_optional) {
        return;
    }
    SocketHolder& holder = socket_holder_optional->get();

    optval.resize(optlen);

    s32 err = 0;

    if (optname < 0) {
#ifdef _WIN32
        err = WSAEINVAL;
#else
        err = EINVAL;
#endif
    } else {
        std::vector<u8> platform_data;
        const auto levelopt = TranslateSockOpt(level, optname);
        TranslateSockOptDataToPlatform(platform_data, optval, levelopt.first, levelopt.second);
        err = static_cast<u32>(::setsockopt(holder.socket_fd, levelopt.first, levelopt.second,
                                            reinterpret_cast<char*>(platform_data.data()),
                                            static_cast<socklen_t>(platform_data.size())));
        if (err == SOCKET_ERROR_VALUE) {
            err = TranslateError(GET_ERRNO);
        }
    }

    LOG_DEBUG(Service_SOC, "called, pid={}, fd={}, ret={}", pid, socket_handle,
              static_cast<s32>(err));

    IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
    rb.Push(ResultSuccess);
    rb.Push(err);
}

void SOC_U::GetNetworkOpt(Kernel::HLERequestContext& ctx) {
    IPC::RequestParser rp(ctx);
    u32 level = rp.Pop<u32>();
    u32 opt_name = rp.Pop<u32>();
    u32 opt_len = rp.Pop<u32>();
    std::vector<u8> opt_data(opt_len);

    u32 err = SOC_ERR_INAVLID_ENUM_VALUE;

    /// Only available level is SOC_SOL_CONFIG, any other value returns an error
    if (level == SOC_SOL_CONFIG) {
        switch (static_cast<NetworkOpt>(opt_name)) {
        case NetworkOpt::NETOPT_MAC_ADDRESS: {
            if (opt_len >= 6) {
                std::array<u8, 6> fake_mac = {0};
                std::memcpy(opt_data.data(), fake_mac.data(), fake_mac.size());
            }
            LOG_WARNING(Service_SOC, "(STUBBED) called, level={} opt_name={}", level, opt_name);
            err = 0;
        } break;
        case NetworkOpt::NETOPT_IP_INFO: {
            if (opt_len >= sizeof(InterfaceInfo)) {
                InterfaceInfo& out_info = *reinterpret_cast<InterfaceInfo*>(opt_data.data());
                auto info = GetDefaultInterfaceInfo();
                if (info.has_value()) {
                    out_info = info.value();
                }
            }
            // Extra data not used normally, takes 0xC bytes more
            if (opt_len >= sizeof(InterfaceInfo) + 0xC) {
                LOG_ERROR(Service_SOC, "(STUBBED) called, level={} opt_name={} opt_len >= 24",
                          level, opt_name);
            }
            err = 0;
        } break;
        default:
            LOG_ERROR(Service_SOC, "(STUBBED) called, level={} opt_name={}", level, opt_name);
            break;
        }
    } else {
        LOG_ERROR(Service_SOC, "Unknown level={}", level);
    }

    if (err != 0) {
        opt_data.resize(0);
        opt_len = 0;
    }

    IPC::RequestBuilder rb = rp.MakeBuilder(3, 2);
    rb.Push(ResultSuccess);
    rb.Push(err);
    rb.Push(static_cast<u32>(opt_len));
    rb.PushStaticBuffer(std::move(opt_data), 0);
}

void SOC_U::GetAddrInfoImpl(Kernel::HLERequestContext& ctx) {
    IPC::RequestParser rp(ctx);
    u32 node_length = rp.Pop<u32>();
    u32 service_length = rp.Pop<u32>();
    u32 hints_size = rp.Pop<u32>();
    u32 out_size = rp.Pop<u32>();
    auto node = rp.PopStaticBuffer();
    auto service = rp.PopStaticBuffer();
    auto hints_buff = rp.PopStaticBuffer();

    const char* node_data = node_length > 0 ? reinterpret_cast<const char*>(node.data()) : nullptr;
    const char* service_data =
        service_length > 0 ? reinterpret_cast<const char*>(service.data()) : nullptr;

    s32 ret = -1;
    addrinfo* out = nullptr;
    if (hints_size > 0) {
        CTRAddrInfo ctr_hints;
        std::memcpy(&ctr_hints, hints_buff.data(), hints_size);
        addrinfo hints = CTRAddrInfo::ToPlatform(ctr_hints);
        // Mimic what soc does in real hardware
        if (hints.ai_socktype != 0 && hints.ai_protocol == 0) {
            hints.ai_protocol = hints.ai_socktype == SOCK_STREAM ? IPPROTO_TCP : IPPROTO_UDP;
        }
        if (hints.ai_protocol != 0 && hints.ai_socktype == 0) {
            hints.ai_socktype = hints.ai_protocol == IPPROTO_TCP ? SOCK_STREAM : SOCK_DGRAM;
        }
        ret = getaddrinfo(node_data, service_data, &hints, &out);
    } else {
        ret = getaddrinfo(node_data, service_data, nullptr, &out);
    }

    std::vector<u8> out_buff(out_size);
    u32 count = 0;

    if (ret == SOCKET_ERROR_VALUE) {
        ret = TranslateError(GET_ERRNO);
        out_buff.resize(0);
    } else {
        std::size_t pos = 0;
        addrinfo* cur = out;
        while (cur != nullptr) {
            if (pos <= out_size - sizeof(CTRAddrInfo)) {
                // According to 3dbrew, this function fills whatever it can and does not error even
                // if the buffer is not big enough. However the count returned is always correct.
                CTRAddrInfo ctr_addr = CTRAddrInfo::FromPlatform(*cur);
                std::memcpy(out_buff.data() + pos, &ctr_addr, sizeof(ctr_addr));
                pos += sizeof(ctr_addr);
            }
            cur = cur->ai_next;
            count++;
        }
        if (out != nullptr)
            freeaddrinfo(out);
    }

    IPC::RequestBuilder rb = rp.MakeBuilder(3, 2);
    rb.Push(ResultSuccess);
    rb.Push(ret);
    rb.Push(count);
    rb.PushStaticBuffer(std::move(out_buff), 0);
}

void SOC_U::GetNameInfoImpl(Kernel::HLERequestContext& ctx) {
    IPC::RequestParser rp(ctx);
    u32 socklen = rp.Pop<u32>();
    u32 hostlen = rp.Pop<u32>();
    u32 servlen = rp.Pop<u32>();
    s32 flags = NameInfoFlagsToPlatform(rp.Pop<s32>());
    auto sa_buff = rp.PopStaticBuffer();

    CTRSockAddr ctr_sa;
    std::memcpy(&ctr_sa, sa_buff.data(), socklen);
    auto [sa, sa_len] = CTRSockAddr::ToPlatform(ctr_sa);

    std::vector<u8> host(hostlen);
    std::vector<u8> serv(servlen);
    char* host_data = hostlen > 0 ? reinterpret_cast<char*>(host.data()) : nullptr;
    char* serv_data = servlen > 0 ? reinterpret_cast<char*>(serv.data()) : nullptr;

    s32 ret = getnameinfo(reinterpret_cast<sockaddr*>(&sa), sa_len, host_data, hostlen, serv_data,
                          servlen, flags);
    if (ret == SOCKET_ERROR_VALUE) {
        ret = TranslateError(GET_ERRNO);
    }

    IPC::RequestBuilder rb = rp.MakeBuilder(2, 4);
    rb.Push(ResultSuccess);
    rb.Push(ret);
    rb.PushStaticBuffer(std::move(host), 0);
    rb.PushStaticBuffer(std::move(serv), 1);
}

void SOC_U::SendToMultiple(Kernel::HLERequestContext& ctx) {
    IPC::RequestParser rp(ctx);
    const u32 socket_handle = rp.Pop<u32>();
    u32 len = rp.Pop<u32>();
    u32 flags = SendRecvFlagsToPlatform(rp.Pop<u32>());
    u32 addr_len = rp.Pop<u32>();
    u32 total_addr_len = rp.Pop<u32>();
    const u32 pid = rp.PopPID();
    auto input_buff = rp.PopStaticBuffer();
    auto dest_addr_buff = rp.PopStaticBuffer();

    auto socket_holder_optional = GetSocketHolder(socket_handle, pid, rp);
    if (!socket_holder_optional) {
        return;
    }
    SocketHolder& holder = socket_holder_optional->get();

    u32 count = total_addr_len / addr_len;
    s32 ret = 0;
    u32 i = 0;
    do {
        ret = SendToImpl(holder, len, flags, addr_len, input_buff,
                         dest_addr_buff.data() + (i * addr_len));
        i++;
    } while (i < count && ret >= 0);

    LOG_DEBUG(Service_SOC, "called, pid={}, fd_count={}, ret={}", pid, count,
              static_cast<s32>(ret));

    IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
    rb.Push(ResultSuccess);
    rb.Push(ret);
}

void SOC_U::CloseSockets(Kernel::HLERequestContext& ctx) {
    IPC::RequestParser rp(ctx);
    const u32 pid = rp.PopPID();

    if (initialized_processes.find(pid) == initialized_processes.end()) {
        LOG_DEBUG(Service_SOC, "Process not initialized: pid={}", pid);
        IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
        rb.Push(ResultNotInitialized);
        return;
    }

    CloseAndDeleteAllSockets(pid);

    LOG_DEBUG(Service_SOC, "called, pid={}", pid);

    IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
    rb.Push(ResultSuccess);
}

void SOC_U::AddGlobalSocket(Kernel::HLERequestContext& ctx) {
    IPC::RequestParser rp(ctx);
    const auto socket_handle = rp.Pop<u32>();

    // This service call does not provide a PID like the others,
    // instead SOC seems to fetch the PID from the current session.
    const u32 pid = ctx.ClientThread()->owner_process.lock()->process_id;

    auto socket_holder_optional = GetSocketHolder(socket_handle, pid, rp);
    if (!socket_holder_optional) {
        return;
    }
    SocketHolder& holder = socket_holder_optional->get();

    holder.isGlobal = true;

    LOG_DEBUG(Service_SOC, "called, pid={}, fd={}", pid, socket_handle);

    IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
    rb.Push(ResultSuccess);
}

SOC_U::SOC_U() : ServiceFramework("soc:U", 18) {
    static const FunctionInfo functions[] = {
        // clang-format off
        {0x0001, &SOC_U::InitializeSockets, "InitializeSockets"},
        {0x0002, &SOC_U::Socket, "Socket"},
        {0x0003, &SOC_U::Listen, "Listen"},
        {0x0004, &SOC_U::Accept, "Accept"},
        {0x0005, &SOC_U::Bind, "Bind"},
        {0x0006, &SOC_U::Connect, "Connect"},
        {0x0007, &SOC_U::RecvFromOther, "recvfrom_other"},
        {0x0008, &SOC_U::RecvFrom, "RecvFrom"},
        {0x0009, &SOC_U::SendToOther, "SendToOther"},
        {0x000A, &SOC_U::SendToSingle, "SendToSingle"},
        {0x000B, &SOC_U::Close, "Close"},
        {0x000C, &SOC_U::Shutdown, "Shutdown"},
        {0x000D, &SOC_U::GetHostByName, "GetHostByName"},
        {0x000E, &SOC_U::GetHostByAddr, "GetHostByAddr"},
        {0x000F, &SOC_U::GetAddrInfoImpl, "GetAddrInfo"},
        {0x0010, &SOC_U::GetNameInfoImpl, "GetNameInfo"},
        {0x0011, &SOC_U::GetSockOpt, "GetSockOpt"},
        {0x0012, &SOC_U::SetSockOpt, "SetSockOpt"},
        {0x0013, &SOC_U::Fcntl, "Fcntl"},
        {0x0014, &SOC_U::Poll, "Poll"},
        {0x0015, &SOC_U::SockAtMark, "SockAtMark"},
        {0x0016, &SOC_U::GetHostId, "GetHostId"},
        {0x0017, &SOC_U::GetSockName, "GetSockName"},
        {0x0018, &SOC_U::GetPeerName, "GetPeerName"},
        {0x0019, &SOC_U::ShutdownSockets, "ShutdownSockets"},
        {0x001A, &SOC_U::GetNetworkOpt, "GetNetworkOpt"},
        {0x001B, nullptr, "ICMPSocket"},
        {0x001C, nullptr, "ICMPPing"},
        {0x001D, nullptr, "ICMPCancel"},
        {0x001E, nullptr, "ICMPClose"},
        {0x001F, nullptr, "GetResolverInfo"},
        {0x0020, &SOC_U::SendToMultiple, "SendToMultiple"},
        {0x0021, &SOC_U::CloseSockets, "CloseSockets"},
        {0x0023, &SOC_U::AddGlobalSocket, "AddGlobalSocket"},
        // clang-format on
    };

    RegisterHandlers(functions);

    Network::SocketManager::EnableSockets();
}

SOC_U::~SOC_U() {
    CloseAndDeleteAllSockets();
    Network::SocketManager::DisableSockets();
}

std::optional<SOC_U::InterfaceInfo> SOC_U::GetDefaultInterfaceInfo() {
    if (this->interface_info_cached) {
        return InterfaceInfo(this->interface_info);
    }

    InterfaceInfo ret;

    SocketHolder::SOCKET sock_fd = -1;
    bool interface_found = false;
    struct sockaddr_in s_in = {.sin_family = AF_INET, .sin_port = htons(53), .sin_addr = {}};
    s_in.sin_addr.s_addr = inet_addr("8.8.8.8");
    socklen_t s_info_len = sizeof(struct sockaddr_in);
    sockaddr_in s_info;

    if ((sock_fd = ::socket(AF_INET, SOCK_STREAM, 0)) == static_cast<SocketHolder::SOCKET>(-1)) {
        return std::nullopt;
    }

    if (::connect(sock_fd, (struct sockaddr*)(&s_in), sizeof(struct sockaddr_in)) != 0) {
        closesocket(sock_fd);
        return std::nullopt;
    }

    if (::getsockname(sock_fd, (struct sockaddr*)&s_info, &s_info_len) != 0 ||
        s_info_len != sizeof(struct sockaddr_in)) {
        closesocket(sock_fd);
        return std::nullopt;
    }
    closesocket(sock_fd);

#ifdef _WIN32
    sock_fd = WSASocket(AF_INET, SOCK_DGRAM, 0, 0, 0, 0);
    if (sock_fd == static_cast<SocketHolder::SOCKET>(SOCKET_ERROR)) {
        return std::nullopt;
    }

    const int max_interfaces = 100;
    std::vector<INTERFACE_INFO> interface_list_vec(max_interfaces);
    INTERFACE_INFO* interface_list = reinterpret_cast<INTERFACE_INFO*>(interface_list_vec.data());
    unsigned long bytes_used;
    if (WSAIoctl(sock_fd, SIO_GET_INTERFACE_LIST, 0, 0, interface_list,
                 max_interfaces * sizeof(INTERFACE_INFO), &bytes_used, 0, 0) == SOCKET_ERROR) {
        closesocket(sock_fd);
        return std::nullopt;
    }
    closesocket(sock_fd);

    int num_interfaces = bytes_used / sizeof(INTERFACE_INFO);
    for (int i = 0; i < num_interfaces; i++) {
        if (((sockaddr*)&(interface_list[i].iiAddress))->sa_family == AF_INET &&
            std::memcmp(&((sockaddr_in*)&(interface_list[i].iiAddress))->sin_addr.s_addr,
                        &s_info.sin_addr.s_addr, sizeof(s_info.sin_addr.s_addr)) == 0) {
            ret.address = ((sockaddr_in*)&(interface_list[i].iiAddress))->sin_addr.s_addr;
            ret.netmask = ((sockaddr_in*)&(interface_list[i].iiNetmask))->sin_addr.s_addr;
            ret.broadcast =
                ((sockaddr_in*)&(interface_list[i].iiBroadcastAddress))->sin_addr.s_addr;
            interface_found = true;
            {
                char address[16] = {0}, netmask[16] = {0}, broadcast[16] = {0};
                std::strncpy(address,
                             inet_ntoa(((sockaddr_in*)&(interface_list[i].iiAddress))->sin_addr),
                             sizeof(address) - 1);
                std::strncpy(netmask,
                             inet_ntoa(((sockaddr_in*)&(interface_list[i].iiNetmask))->sin_addr),
                             sizeof(netmask) - 1);
                std::strncpy(
                    broadcast,
                    inet_ntoa(((sockaddr_in*)&(interface_list[i].iiBroadcastAddress))->sin_addr),
                    sizeof(broadcast) - 1);

                LOG_DEBUG(Service_SOC, "Found interface: (addr: {}, netmask: {}, broadcast: {})",
                          address, netmask, broadcast);
            }
            break;
        }
    }
#else
    struct ifaddrs* ifaddr;
    struct ifaddrs* ifa;
    if (getifaddrs(&ifaddr) == -1) {
        return std::nullopt;
    }

    for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
        if (ifa->ifa_addr && ifa->ifa_addr->sa_family == AF_INET) {
            struct sockaddr_in* in_address = (struct sockaddr_in*)ifa->ifa_addr;
            struct sockaddr_in* in_netmask = (struct sockaddr_in*)ifa->ifa_netmask;
            struct sockaddr_in* in_broadcast = (struct sockaddr_in*)ifa->ifa_broadaddr;
            if (in_address->sin_addr.s_addr == s_info.sin_addr.s_addr) {
                ret.address = in_address->sin_addr.s_addr;
                ret.netmask = in_netmask->sin_addr.s_addr;
                ret.broadcast = in_broadcast->sin_addr.s_addr;
                interface_found = true;
                {
                    char address[16] = {0}, netmask[16] = {0}, broadcast[16] = {0};
                    std::strncpy(address, inet_ntoa(in_address->sin_addr), sizeof(address) - 1);
                    std::strncpy(netmask, inet_ntoa(in_netmask->sin_addr), sizeof(netmask) - 1);
                    std::strncpy(broadcast, inet_ntoa(in_broadcast->sin_addr),
                                 sizeof(broadcast) - 1);

                    LOG_DEBUG(Service_SOC,
                              "Found interface: (addr: {}, netmask: {}, broadcast: {})", address,
                              netmask, broadcast);
                }
            }
        }
    }
    freeifaddrs(ifaddr);
#endif // _WIN32
    if (interface_found) {
        this->interface_info = ret;
        this->interface_info_cached = true;
        return ret;
    } else {
        LOG_DEBUG(Service_SOC, "Interface not found");
        return std::nullopt;
    }
}

std::shared_ptr<SOC_U> GetService(Core::System& system) {
    return system.ServiceManager().GetService<SOC_U>("soc:U");
}

void InstallInterfaces(Core::System& system) {
    auto& service_manager = system.ServiceManager();
    std::make_shared<SOC_U>()->InstallAsService(service_manager);
}

} // namespace Service::SOC
