
/*---------------------------------------------------------------------------*/
/* udpsocket.cpp                                                             */
/* copyright (c) innovaphone 2015                                            */
/*                                                                           */
/*---------------------------------------------------------------------------*/

#include <signal.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#ifdef _IOS_
#include <netdb.h>
#include <sys/sockio.h>
#include <net/if.h>
#include <net/if_dl.h>
#include <sys/ioctl.h>
#include <ifaddrs.h>
#include <net/ethernet.h>
#include <sys/types.h>
#define IPV6_ADD_MEMBERSHIP     IPV6_JOIN_GROUP
#else
#include <netinet/udp.h>
#ifndef SOCK_NONBLOCK
#include <asm/ioctls.h>
#endif
#endif

#include <platform.h>
#include "sdk/common/os/iomux.h"
#include "common/ilib/str.h"
#include "sdk/common/interface/socket.h"
#include "udpsocket.h"

static const int __UDP_SOCKET_IO_EXEC_SEND = 1;
static const int __UDP_SOCKET_IO_EXEC_RECV = 2;
static const int __UDP_SOCKET_IO_EXEC_BIND = 3;
static const int __UDP_SOCKET_IO_EXEC_SHTD = 4;
static const int __UDP_SOCKET_IO_EXEC_CONN = 5;


#define TOS_DSCP_VOICE			0xb8    // RFC 3246 - Expedited Forwarding
#define TOS_DSCP_VIDEO			0x88    // RFC 4594 - 
#define TOS_DSCP_SIGNALING		0x68	// RFC 2597 - Assured Forwarding 31

/*---------------------------------------------------------------------------*/
/* class UDPSocketContext	                                                 */
/*---------------------------------------------------------------------------*/


/*---------------------------------------------------------------------------*/
/* class UDPSocketProvider	                                                 */
/*---------------------------------------------------------------------------*/

class UDPSocket * UDPSocketProvider::NewSocket(IIoMux * const iomux, USocket * const user, IInstanceLog * const log)
{
    return new UDPSocket(iomux, user, log, this);
}

ISocketContext * UDPSocketProvider::CreateSocketContext(class IInstanceLog * const log)
{
    return new class UDPSocketContext();
}

ISocket * UDPSocketProvider::CreateSocket(IIoMux * const iomux, USocket * const user, IInstanceLog * const log, bool useIPv6, class ISocketContext * socketContext)
{
    UDPSocket * tmp = NewSocket(iomux, user, log);
    tmp->OpenSocket(useIPv6);
    return tmp;
}

ISocketProvider * CreateUDPSocketProvider()
{
    return new UDPSocketProvider;
}

/*---------------------------------------------------------------------------*/
/* struct SendBuffer     	                                                 */
/*---------------------------------------------------------------------------*/

struct SendBuffer
{
    unsigned char * buffer;
    int len;
    int startAt;

    SendBuffer() : buffer(0), len(0), startAt(0) {}

    ~SendBuffer()
    {
        free(buffer);
    }
};

/*---------------------------------------------------------------------------*/
/* class UDPSocket                                                           */
/*---------------------------------------------------------------------------*/

UDPSocket::UDPSocket(IIoMux * iomux, USocket * const user, IInstanceLog * const log, UDPSocketProvider * provider)
    : useIPv6(false),
      iomux(iomux),
      user(user),
      log(log),
      provider(provider),
      socketStatus(SST_CLOSED),
      recvBuf(NULL),
      recvBufLen(0),
      execCnt(0),
      shutDown(false),
      isMulticast(false),
      connected(false),
      shutdownReason(SOCKET_SHUTDOWN_NORMAL)
{
    memset(&localAddr, 0, sizeof(sockaddr_storage));
    memset(&connectedAddr, 0, sizeof(sockaddr_storage));
    memset(&localIfAddr, 0, sizeof(IPaddr));
}


UDPSocket::~UDPSocket()
{
    ASSERT(socketStatus == SST_CLOSED, "UDPSocket::~UDPSocket deleted without waiting for SocketShutdown");
}


void UDPSocket::OpenSocket(bool useIPv6)
{
    ASSERT(socketStatus == SST_CLOSED, "UDPSocket::OpenSocket socket already opened");
    log->Log(LOG_UDP, "UDPSocket(%08x,%08x)::OpenSocket%s", this, user, useIPv6 ? "IPv6" : "IPv4");

	this->useIPv6 = useIPv6;
	
    FDIIoMux = socket((useIPv6 ? AF_INET6 : AF_INET), SOCK_DGRAM
#ifdef SOCK_NONBLOCK
        | SOCK_NONBLOCK
#endif
        , IPPROTO_UDP);
    if (FDIIoMux < 0) {
        log->Log(LOG_UDP, "UDPSocket(%08x,%08x)::OpenSocket socket() failed(%u : %s)", this, user, errno, strerror(errno));
        shutdownReason = SOCKET_OPEN_FAILED;
        this->Shutdown();
        return;
    }

    socketStatus = SST_OPENED;

#ifndef SOCK_NONBLOCK
    unsigned int nonBlocking = 1;
    ioctl(FDIIoMux, FIONBIO, &nonBlocking);
    // listening sockets must be asynchronous, since otherwise indicated accept(2)s may disappear
    // between poll and accept and then our accept would block
    long flags = fcntl(FDIIoMux, F_GETFL);
    if (flags == -1) {
        log->Log(LOG_UDP, "UDPSocket(%08x,%08x)::OpenSocket fcntl(F_GETFL) failed (%u : %s)", this, user, errno, strerror(errno));
    }
    // do not block if there is no incoming connection
    flags |= O_NONBLOCK;
    if (fcntl(FDIIoMux, F_SETFL, flags) == -1) {
        log->Log(LOG_UDP, "UDPSocket(%08x,%08x)::OpenSocket fcntl(F_SETFL) failed (%u : %s)", this, user, errno, strerror(errno));
    }
#endif
#ifdef _IOS_
    int noSigPipe = 1;
    if (setsockopt(FDIIoMux, SOL_SOCKET, SO_NOSIGPIPE, &noSigPipe, sizeof(noSigPipe)) < 0) {
        log->Log(LOG_UDP, "UDPSocket(%08x,%08x)::OpenSocket setsockopt(SO_NOSIGPIPE) failed (%u : %s)", this, user, errno, strerror(errno));
    }
#endif
    sigset_t set;
    sigemptyset(&set);
    sigaddset(&set, SIGPIPE);
    if (sigprocmask(SIG_BLOCK, &set, 0) < 0) {
        log->Log(LOG_UDP, "UDPSocket(%08x,%08x)::OpenSocket sigprocmask(SIG_BLOCK) failed (%u : %s)", this, user, errno, strerror(errno));
    }
    int broadcastPermission = 1;          /* Socket opt to set permission to broadcast */
    if (setsockopt(FDIIoMux, SOL_SOCKET, SO_BROADCAST, (void *)&broadcastPermission, sizeof(broadcastPermission)) < 0) {
        log->Log(LOG_UDP, "UDPSocket(%08x,%08x)::OpenSocket setsockopt(SO_BROADCAST) failed (%u : %s)", this, user, errno, strerror(errno));
    }
    
    int tos = TOS_DSCP_VOICE;
    int pktInfo = 1;
    if (useIPv6) {
#ifdef IPV6_TCLASS
        if (setsockopt(FDIIoMux, IPPROTO_IPV6, IPV6_TCLASS, &tos, (socklen_t)(sizeof(tos))) < 0) {
            log->Log(LOG_UDP, "UDPSocket(%08x,%08x)::OpenSocket setsockopt(IPV6_TCLASS) failed (%u : %s)", this, user, errno, strerror(errno));
        }
#else
        if (setsockopt(FDIIoMux, IPPROTO_IPV6, IP_TOS, &tos, (socklen_t)(sizeof(tos))) < 0) {
            log->Log(LOG_UDP, "UDPSocket(%08x,%08x)::OpenSocket setsockopt(IPV6_TOS) failed (%u : %s)", this, user, errno, strerror(errno));
        }
#endif
        if (setsockopt(FDIIoMux, IPPROTO_IPV6, IPV6_RECVPKTINFO, &pktInfo, sizeof(pktInfo)) < 0) {
            log->Log(LOG_UDP, "UDPSocket(%08x,%08x)::OpenSocket setsockopt(IPV6_RECVPKTINFO) failed (%u : %s)", this, user, errno, strerror(errno));
        }
    }
    else {
        if(setsockopt(FDIIoMux, IPPROTO_IP, IP_TOS, &tos, (socklen_t)(sizeof(tos))) < 0) {
            log->Log(LOG_UDP, "UDPSocket(%08x,%08x)::OpenSocket setsockopt(IP_TOS) failed (%u : %s)", this, user, errno, strerror(errno));
        }
        if (setsockopt(FDIIoMux, IPPROTO_IP, IP_PKTINFO, &pktInfo, sizeof(pktInfo)) < 0) {
            log->Log(LOG_UDP, "UDPSocket(%08x,%08x)::OpenSocket setsockopt(IP_PKTINFO) failed (%u : %s)", this, user, errno, strerror(errno));
        }
    }
}


void UDPSocket::SetQoS(qostraffictype_t type)
{
    int tos = (type == QOS_VOICE) ? TOS_DSCP_VOICE : ((type == QOS_AUDIO_VIDEO) ? TOS_DSCP_VIDEO : TOS_DSCP_SIGNALING);
    if(useIPv6) {
#ifdef IPV6_TCLASS
        if(setsockopt(FDIIoMux, IPPROTO_IPV6, IPV6_TCLASS, &tos, (socklen_t)(sizeof(tos))) < 0) {
            log->Log(LOG_UDP, "UDPSocket(%08x,%08x)::SetQoS setsockopt(IPV6_TCLASS) failed (%u : %s)", this, user, errno, strerror(errno));
        }
#else
        if(setsockopt(FDIIoMux, IPPROTO_IPV6, IP_TOS, &tos, (socklen_t)(sizeof(tos))) < 0) {
            log->Log(LOG_UDP, "UDPSocket(%08x,%08x)::SetQoS setsockopt(IPV6_TOS) failed (%u : %s)", this, user, errno, strerror(errno));
        }
#endif
    }
    else {
        if(setsockopt(FDIIoMux, IPPROTO_IP, IP_TOS, &tos, (socklen_t)(sizeof(tos))) < 0) {
            log->Log(LOG_UDP, "UDPSocket(%08x,%08x)::SetQoS setsockopt(IP_TOS) failed (%u : %s)", this, user, errno, strerror(errno));
        }
    }
}


bool UDPSocket::ConnectIPv4(const char * address)
{
    int len = strlen(address);
    int port = 0;
    char ipaddress[128];
    char *c = strpbrk((char *)address, ":");

    if (c != 0) {
        len -= strlen(c);
        port = atoi(c + 1);
    }

    memcpy(&ipaddress, address, len);
    ipaddress[len] = 0;

    memset(&connectedAddr, 0x0, sizeof(struct sockaddr_storage));
    int result = inet_pton(AF_INET, ipaddress, &(((sockaddr_in*)&connectedAddr)->sin_addr));
    if(result == 0) {
        log->Log(LOG_UDP, "UDPSocket(%08x,%08x)::ConnectIPv4 \"%s\" is not a valid IPv4 address", this, user, ipaddress);
        shutdownReason = SOCKET_ADDRESS_INVALID;
        memset(&connectedAddr, 0x0, sizeof(struct sockaddr_storage));
        return false;
    }

    connectedAddr.ss_family = AF_INET;
    ((sockaddr_in*)&connectedAddr)->sin_port = htons(port);

    int res = connect(FDIIoMux, (struct sockaddr *)(&connectedAddr), sizeof(sockaddr_in));
    if (res < 0 && errno != EINPROGRESS) {
        log->Log(LOG_UDP, "UDPSocket(%08x,%08x)::ConnectIPv4 address:%s connect() failed (%u : %s)", this, user, address, errno, strerror(errno));
        shutdownReason = SOCKET_CONNECT_FAILED;
        return false;
    }
    return true;
}


bool UDPSocket::ConnectIPv6(const char *address)
{
    int port = 0;
    char ipaddress[128];
    char *bo = strpbrk((char *)address, "[");
    char *bc = strpbrk((char *)address, "]");

    if (bc != 0) {
        // Format of IPv6 with Port = [xxxx]:port, optional
        char *c = strpbrk(bc + 1, ":");
        if (c != 0)
            port = atoi(c + 1);
    }

    const char *x = (bo == 0 ? address : bo + 1);
    int len = strlen(x);
    if (bc != 0) len -= strlen(bc);

    memcpy(&ipaddress, x, len);
    ipaddress[len] = 0;

    memset(&connectedAddr, 0x0, sizeof(struct sockaddr_storage));
    int result = inet_pton(AF_INET6, ipaddress, &(((sockaddr_in6*)&connectedAddr)->sin6_addr));
    if(result == 0) {
        log->Log(LOG_UDP, "UDPSocket(%08x,%08x)::ConnectIPv6 \"%s\" is not a valid IPv6 address", this, user, address);
        shutdownReason = SOCKET_ADDRESS_INVALID;
        return false;
    }

    connectedAddr.ss_family = AF_INET6;
    ((sockaddr_in6*)&connectedAddr)->sin6_port = htons(port);

    int res = connect(FDIIoMux, (struct sockaddr *)(&connectedAddr), sizeof(sockaddr_in6));
    if (res < 0 && errno != EINPROGRESS) {
        log->Log(LOG_UDP, "UDPSocket(%08x,%08x)::ConnectIPv6 address:%s connect() failed (%u : %s)", this, user, address, errno, strerror(errno));
        shutdownReason = SOCKET_CONNECT_FAILED;
        return false;
    }
    return true;
}


void UDPSocket::GetLocalAddress(char * localAddr, unsigned size, word * localPort)
{
    log->Log(LOG_UDP, "UDPSocket(%08x,%08x)::GetLocalAddress useIPv6=%d size=%d", this, user, useIPv6, size);

    str::from_ip(localAddr, size, &this->localIfAddr);
    if(useIPv6) {
        sockaddr_in6 tmp;
        socklen_t l = sizeof(sockaddr_in6);
        getsockname(FDIIoMux, (sockaddr *)&tmp, &l);
        *localPort = ntohs(tmp.sin6_port);
    }
    else {
        sockaddr_in tmp;
        socklen_t l = sizeof(sockaddr_in);
        getsockname(FDIIoMux, (sockaddr *)&tmp, &l);
        *localPort = ntohs(tmp.sin_port);
    }
}


void UDPSocket::Connect(const char * address, const char * serverName)
{
    log->Log(LOG_UDP, "UDPSocket(%08x,%08x)::Connect address:%s serverName:%s", this, user, address, serverName);

    if ((this->socketStatus == SST_CLOSED) && !shutDown) this->OpenSocket(useIPv6);
    if (shutDown) {
        if (execCnt == 0) { // if an OpenSocket failed inside CreateSocket, we must answer async to a bind/connect call to avoid the ASSERT below
            this->socketStatus = SST_DO_SHUTDOWN;
            this->Shutdown();
        }
        return;
    }

    bool result;
    if (useIPv6)
        result = this->ConnectIPv6(address);
    else
        result = this->ConnectIPv4(address);

    if (!result) {
        this->Shutdown();
        return;
    }

    if(socketStatus == SST_OPENED) {
        iomux->FDAdd(FDIIoMux, this, false);
    }

    iomux->SetExec(this, (void *)&__UDP_SOCKET_IO_EXEC_CONN);
    execCnt++;
    socketStatus = SST_CONNECTED;
    connected = true;
}


bool UDPSocket::BindIPv4(const char * address, word port)
{
    char ipaddress[128];

    ipaddress[0] = 0;
    if (address) {
        int len = strlen(address);
        char *c = strpbrk((char *)address, ":");

        if (c != 0) {
            len -= strlen(c);
            port = atoi(c + 1);
        }

        memcpy(&ipaddress, address, len);
        ipaddress[len] = 0;
    }

    memset(&localAddr, 0, sizeof(sockaddr_in));
    localAddr.ss_family = AF_INET;

    isMulticast = IsMulticast(ipaddress);
    if(isMulticast) {
        (((sockaddr_in*)&localAddr)->sin_addr).s_addr = htonl(INADDR_ANY);
    }
    else {
        if (address == NULL) {
            (((sockaddr_in*)&localAddr)->sin_addr).s_addr = htonl(INADDR_ANY);
        }
        else {
            int result = inet_pton(AF_INET, ipaddress, &(((sockaddr_in*)&localAddr)->sin_addr));
            if (result == 0) {
                log->Log(LOG_UDP, "UDPSocket(%08x,%08x)::BindIPv4 \"%s\" is not a valid IPv4 address", this, user, address);
                errno = 0;
                socketStatus = SST_ADDRESS_INVALID;
                return false;
            }
        }
    }

    ((sockaddr_in*)&localAddr)->sin_port = htons(port);

    int res = bind(FDIIoMux, (struct sockaddr *)(&localAddr), sizeof(sockaddr_in));
    if (res != 0) {
        log->Log(LOG_UDP, "UDPSocket(%08x,%08x)::BindIPv4 bind() failed (%u : %s)", this, user, errno, strerror(errno));
        return false;
    }

    if(isMulticast) {
        mcReq = {};
        mcReq.imr_multiaddr.s_addr = inet_addr(ipaddress);
        mcReq.imr_interface.s_addr = htonl(INADDR_ANY);
        if(setsockopt(FDIIoMux, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char*)&mcReq, sizeof(mcReq)) < 0) {
            debug->printf("UDPSocket(%08x,%08x)::BindIPv4 failed to join multicast group (%u : %s)", this, user, errno, strerror(errno));
            return false;
        }
        byte b = 0;
        if(setsockopt(FDIIoMux, IPPROTO_IP, IP_MULTICAST_LOOP, &b, (socklen_t)(sizeof(b))) < 0) {
            debug->printf("UDPSocket(%08x,%08x)::BindIPv4 failed to set multicast loop off (%u : %s)", this, user, errno, strerror(errno));
        }
    }
    return true;
}


bool UDPSocket::BindIPv6(const char * address, word port)
{
    int yes = 1;
    if (setsockopt(FDIIoMux, IPPROTO_IPV6, IPV6_V6ONLY, &yes, sizeof(yes)) != 0) {
        log->Log(LOG_TCP, "UDPSocket(%08x,%08x)::Bind setsockopt(IPPROTO_IPV6) failed (%u : %s)", this, user, errno, strerror(errno));
        shutdownReason = SOCKET_BIND_FAILED;
        return false;
    }

    char ipaddress[128];

    ipaddress[0] = 0;
    if(address) {
        char *bo = strpbrk((char *)address, "[");
        char *bc = strpbrk((char *)address, "]");

        if (bc != 0) {
            // Format of IPv6 with Port = [xxxx]:port, optional
            char *c = strpbrk(bc + 1, ":");
            if (c != 0)
                port = atoi(c + 1);
        }

        const char *x = (bo == 0 ? address : bo + 1);
        int len = strlen(x);
        if (bc != 0) len -= strlen(bc);

        memcpy(&ipaddress, x, len);
        ipaddress[len] = 0;
    }

    memset(&localAddr, 0, sizeof(sockaddr_storage));
    localAddr.ss_family = AF_INET6;

    isMulticast = IsMulticast(ipaddress);
    if(isMulticast) {
        memcpy(&(((sockaddr_in6*)&localAddr)->sin6_addr), &in6addr_any, sizeof(struct in6_addr));
    }
    else {
        if (address == NULL) {
            memcpy(&(((sockaddr_in6*)&localAddr)->sin6_addr), &in6addr_any, sizeof(struct in6_addr));
        }
        else {
            int result = inet_pton(AF_INET6, ipaddress, &(((sockaddr_in6*)&localAddr)->sin6_addr));
            if (result == 0) {
                log->Log(LOG_UDP, "UDPSocket(%08x,%08x)::BindIPv6 \"%s\" is not a valid IPv6 address", this, user, address);
                errno = 0;
                socketStatus = SST_ADDRESS_INVALID;
                return false;
            }
        }
    }

    ((sockaddr_in6*)&localAddr)->sin6_port = htons(port);

    int res = bind(FDIIoMux, (struct sockaddr *)(&localAddr), sizeof(sockaddr_in6));
    if (res != 0) {
        log->Log(LOG_UDP, "UDPSocket(%08x,%08x)::BindIPv6 bind() failed (%u : %s)", this, user, errno, strerror(errno));
        return false;
    }

    if(isMulticast) {
        mcReqIPv6 = {};
        inet_pton(AF_INET6, ipaddress, &(((sockaddr_in6*)&mcReqIPv6.ipv6mr_multiaddr)->sin6_addr));
        mcReqIPv6.ipv6mr_interface = 0;
        if(setsockopt(FDIIoMux, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, (char*)&mcReqIPv6, sizeof(mcReqIPv6)) < 0) {
            debug->printf("UDPSocket(%08x,%08x)::BindIPv6 failed to join multicast group (%u : %s)", this, user, errno, strerror(errno));
            return false;
        }
        byte b = 0;
        if(setsockopt(FDIIoMux, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &b, (socklen_t)(sizeof(b))) < 0) {
            debug->printf("UDPSocket(%08x,%08x)::BindIPv6 failed to set multicast loop off (%u : %s)", this, user, errno, strerror(errno));
        }
    }

    return true;
}


void UDPSocket::Bind(const char * localAddr, word localPort)
{
    log->Log(LOG_UDP, "UDPSocket(%08x,%08x)::Bind localAddr:%s localPort:%i", this, user, localAddr, localPort);

    if ((this->socketStatus == SST_CLOSED) && !shutDown) this->OpenSocket(useIPv6);
    if (shutDown) {
        if (execCnt == 0) { // if an OpenSocket failed inside CreateSocket, we must answer async to a bind/connect call to avoid the ASSERT below
            this->socketStatus = SST_DO_SHUTDOWN;
            this->Shutdown();
        }
        return;
    }
    ASSERT(socketStatus == SST_OPENED, "UDPSocket::Bind called but socketState != SST_OPENED");

    bool result;
    if (useIPv6)
        result = this->BindIPv6(localAddr, localPort);
    else
        result = this->BindIPv4(localAddr, localPort);

    if (!result) {
        log->Log(LOG_UDP, "UDPSocket(%x)::Bind failed: %d on Port %d", this, result, localPort);
        socketStatus = SST_BIND_FAILED;
        iomux->SetExec(this, (void *)&__UDP_SOCKET_IO_EXEC_BIND);
        execCnt++;
        return;
    }

    iomux->FDAdd(FDIIoMux, this, false);
    socketStatus = SST_BIND_START;
    // Bind is not async, but we need it...
    iomux->SetExec(this, (void *)&__UDP_SOCKET_IO_EXEC_BIND);
    execCnt++;
}


void UDPSocket::Shutdown()
{
    log->Log(LOG_UDP, "UDPSocket::Shutdown(%08x,%08x)", this, user);
    // So accept one shutdown call...
    shutDown = true;
    if ((socketStatus != SST_CLOSED) && (execCnt == 0)) {
        iomux->SetExec(this, (void *)&__UDP_SOCKET_IO_EXEC_SHTD);
        execCnt++;
    }
}


void UDPSocket::DoShutdown()
{
    if (execCnt != 0) {
        socketStatus = SST_DO_SHUTDOWN;
        return;
    }
    socketStatus = SST_CLOSED;
    shutdown(FDIIoMux, SHUT_RDWR);
    iomux->FDClose(this);
    FDIIoMux = -1;
    if (recvBuf) {
        UserSocketRecvCanceled(recvBuf);
        recvBuf = NULL;
    }
    shutDown = false;
    shutdownreason_t r = shutdownReason;
    shutdownReason = SOCKET_SHUTDOWN_NORMAL;
    UserSocketShutdown(r);
}


void UDPSocket::Send(const void * buf, size_t len)
{
    log->Log(LOG_UDP, "UDPSocket(%08x,%08x)::Send len:%i", this, user, len);
    log->HexDump(LOG_UDP, buf, len);
    if (this->shutDown || (socketStatus == SST_CLOSED))
        return;

    int sendLen = send(FDIIoMux, buf, len, 0);
    if (sendLen < 0) {
        log->Log(LOG_UDP, "UDPSocket(%08x,%08x)::Send send() failed (%u : %s), fd:%u", this, user, errno, strerror(errno), FDIIoMux);
    }
    // Start an Exec-Callback...
    iomux->SetExec(this, (void *)&__UDP_SOCKET_IO_EXEC_SEND);
    execCnt++;
}


void UDPSocket::SendTo(const void * buf, size_t len, IPaddr dstAddr, word dstPort)
{
    struct sockaddr_storage sockAddr;
    char ipaddr[INET6_ADDRSTRLEN+2];
    memset(&sockAddr, 0, sizeof(sockAddr));
    if (useIPv6) {
        sockAddr.ss_family = AF_INET6;
        ((sockaddr_in6*)(&sockAddr))->sin6_port = htons(dstPort);
        memcpy(&(((sockaddr_in6*)(&sockAddr))->sin6_addr), dstAddr.b, sizeof(struct in6_addr));
    }
    else {
        sockAddr.ss_family = AF_INET;
        ((sockaddr_in*)(&sockAddr))->sin_port = htons(dstPort);
        memcpy(&(((sockaddr_in*)(&sockAddr))->sin_addr), &(dstAddr.b[12]), sizeof(struct in_addr));
    }
    str::from_ip(ipaddr, INET6_ADDRSTRLEN+2, &dstAddr, true);

    if (log->LogFlagSet(LOG_UDP)) {
        char b[100];
        word localPort = 0;
        this->GetLocalAddress(b, sizeof(b), &localPort);
        debug->printf("UDPSocket(%08x,%08x)::SendTo len:%i dst-addr:%s:%u src-addr:%s:%u", this, user, len, ipaddr, dstPort, b, localPort);
        debug->hexdump(buf, len);
    }
    if (this->shutDown || (socketStatus == SST_CLOSED))
        return;

    int sendLen = sendto(FDIIoMux, buf, len, 0,
        (struct sockaddr *)(&sockAddr), useIPv6 ? sizeof(sockaddr_in6) : sizeof(sockaddr_in));
    if (sendLen < 0) {
        log->Log(LOG_UDP, "UDPSocket(%08x,%08x)::SendTo sendto() failed (%u : %s), fd:%u", this, user, errno, strerror(errno), FDIIoMux);
    }
    // Start an Exec-Callback...
    iomux->SetExec(this, (void *)&__UDP_SOCKET_IO_EXEC_SEND);
    execCnt++;
}


bool UDPSocket::IsMulticast(const char * addr)
{
    log->Log(LOG_UDP, "UDPSocket(%08x,%08x)::IsMulticast (%s)", this, user, addr ? addr : "");
    if(!addr) return false;

    struct sockaddr_storage multicastAddr;
    memset(&multicastAddr, 0x0, sizeof(struct sockaddr_storage));
    int result = inet_pton(AF_INET6, addr, &(((sockaddr_in6*)&multicastAddr)->sin6_addr));
    if(result != 0) {
        byte *ipStr = ((sockaddr_in6*)&multicastAddr)->sin6_addr.s6_addr;
        if(ipStr[0] == 0xFF) return true;
        return false;
    }
    else {
        int result = inet_pton(AF_INET, addr, &(((sockaddr_in*)&multicastAddr)->sin_addr));
        if(!result) {
            debug->printf("UDPSocket(%08x,%08x)::IsMulticast inet_pton failed for %s", this, user, addr);
            return false;
        }
        byte *ipStr = (byte *)(&((sockaddr_in*)&multicastAddr)->sin_addr.s_addr);
    
        log->Log(LOG_UDP, "UDPSocket(%08x,%08x)::IsMulticast (%s) %u.%u.%u.%u", this, user, addr, ipStr[0], ipStr[1], ipStr[2], ipStr[3]);

        if((ipStr[0] & 0xf0) == 0xe0) return true;
        return false;
    }
}


void UDPSocket::ReceiveData()
{
    if (recvBuf != NULL) {
        struct sockaddr_in recvAddr;
        struct sockaddr_in6 recvAddr6;
        struct iovec iov[1];
        struct msghdr msg;
        char control[1024];
        
        iov[0].iov_base = recvBuf;
        iov[0].iov_len = recvBufLen;

        memset(&msg, 0, sizeof(msg));
        if (this->useIPv6) {
            msg.msg_name = &recvAddr6;
            msg.msg_namelen = sizeof(recvAddr6);
        }
        else {
            msg.msg_name = &recvAddr;
            msg.msg_namelen = sizeof(recvAddr);
        }
        msg.msg_iov = iov;
        msg.msg_iovlen = 1;
        msg.msg_control = control;
        msg.msg_controllen = sizeof(control);

        ssize_t bytesRead = recvmsg(FDIIoMux, &msg, 0);        
        if (bytesRead >= 0) {
            void * tmp = recvBuf; // Need a copy because the user could call recv() in SocketRecvResult()
            recvBuf = NULL;
            IPaddr srcAddr;
            word srcPort;
            if (useIPv6) {
                memcpy(srcAddr.b, &recvAddr6.sin6_addr, sizeof(struct in6_addr));
                srcPort = htons(recvAddr6.sin6_port);
                // Parse the control message to get IPV6_PKTINFO data which contains the IP address of the interface which received the data
                for (struct cmsghdr * cmsg = CMSG_FIRSTHDR(&msg); cmsg != nullptr; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
                    if (cmsg->cmsg_level == IPPROTO_IPV6 && cmsg->cmsg_type == IPV6_PKTINFO) {
                        struct in6_pktinfo * pktinfo = (struct in6_pktinfo *)CMSG_DATA(cmsg);
                        memcpy(this->localIfAddr.b, &(pktinfo->ipi6_addr), sizeof(struct in6_addr));
                        break;
                    }                    
                }
            }
            else {
                memcpy(srcAddr.b, ip_4_anyaddr.b, 12);
                memcpy(&(srcAddr.b[12]), &recvAddr.sin_addr, sizeof(struct in_addr));
                srcPort = htons(recvAddr.sin_port);
                // Parse the control message to get IP_PKTINFO data which contains the IP address of the interface which received the data
                for (struct cmsghdr * cmsg = CMSG_FIRSTHDR(&msg); cmsg != nullptr; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
                    if (cmsg->cmsg_level == IPPROTO_IP && cmsg->cmsg_type == IP_PKTINFO) {
                        struct in_pktinfo * pktinfo = (struct in_pktinfo *)CMSG_DATA(cmsg);
                        memcpy(this->localIfAddr.b, ip_4_anyaddr.b, 12);
                        memcpy(&(this->localIfAddr.b[12]), &(pktinfo->ipi_addr), sizeof(struct in_addr));
                        break;
                    }
                }
            }
            UserSocketRecvFromResult(tmp, bytesRead, srcAddr, srcPort);
        }
    }
}


void UDPSocket::Recv(void * buf, size_t len, bool recvPartial)
{
    log->Log(LOG_UDP, "UDPSocket(%08x,%08x)::Recv len:%i recvPartial:%i", this, user, len, recvPartial);
    
    ASSERT(recvBuf == NULL, "UDPSocket::Recv called with an already set receive buffer");
    ASSERT(buf != NULL, "UDPSocket::Recv called with NULL pointer to buffer ");

    recvBuf = buf;
    recvBufLen = len;
    
    // if the socket is closing, cancel the recv request after assigning the buffer (to ensure a SocketRecvCanceled inside DoShutdown) ...
    // check shutdown as a previous Send might have failed and socket is already broken
    if (this->shutDown || (socketStatus == SST_CLOSED)) {
        return;
    }
    
    iomux->SetExec(this, (void *)&__UDP_SOCKET_IO_EXEC_RECV);
    execCnt++;
}


void UDPSocket::IoContextNotify(bool indRX, bool indTX)
{
    if (indRX) {
        this->ReceiveData();
    }
    if (isError) {
        int errNum;
        socklen_t errNumLen = sizeof(errNum);
        if (getsockopt(FDIIoMux, SOL_SOCKET, SO_ERROR, &errNum, &errNumLen) != 0) {
            debug->printf("UDPSocket(%08x,%08x)::IoContextNotify getsockopt() failed (%u : %s)", this, user, errno, strerror(errno));
        }
        else {
            debug->printf("UDPSocket(%08x,%08x)::IoContextNotify socket error (%u : %s)", this, user, errNum, strerror(errNum));
        }
    }
}


void UDPSocket::IoExec(void * context)
{
    execCnt--;
    if (context == &__UDP_SOCKET_IO_EXEC_SEND) {
        UserSocketSendResult();
    }
    else if (context == &__UDP_SOCKET_IO_EXEC_RECV) {
        this->ReceiveData();
    }
    else if (context == &__UDP_SOCKET_IO_EXEC_BIND) {

        char ipaddr[INET6_ADDRSTRLEN];
        int port;

        if(useIPv6) {
            socklen_t l = sizeof(sockaddr_in6);
            getsockname(FDIIoMux, (sockaddr *)&localAddr, &l);
            inet_ntop(AF_INET6, &(((sockaddr_in6*)&localAddr)->sin6_addr), ipaddr, INET6_ADDRSTRLEN);
            port = ntohs(((sockaddr_in6*)&localAddr)->sin6_port);
        }
        else {
            socklen_t l = sizeof(sockaddr_in);
            getsockname(FDIIoMux, (sockaddr *)&localAddr, &l);
            inet_ntop(AF_INET, &(((sockaddr_in*)&localAddr)->sin_addr), ipaddr, INET6_ADDRSTRLEN);
            port = ntohs(((sockaddr_in*)&localAddr)->sin_port);
        }
        if(socketStatus == SST_BIND_FAILED) {
            socketStatus = SST_OPENED;
            UserSocketBindResult(ipaddr, 0);
        }
        else {
            socketStatus = SST_BIND_DONE;
            UserSocketBindResult(ipaddr, port);
        }
    }
    else if (context == &__UDP_SOCKET_IO_EXEC_CONN) {
        if(useIPv6) {
            sockaddr_in6 tmp;
            socklen_t l = sizeof(sockaddr_in6);
            getsockname(FDIIoMux, (sockaddr *)&tmp, &l);
            memcpy(this->localIfAddr.b, &(tmp.sin6_addr), sizeof(struct in6_addr));
        }
        else {
            sockaddr_in tmp;
            socklen_t l = sizeof(sockaddr_in);
            getsockname(FDIIoMux, (sockaddr *)&tmp, &l);
            memcpy(this->localIfAddr.b, ip_4_anyaddr.b, 12);
            memcpy(&(this->localIfAddr.b[12]), &(tmp.sin_addr), sizeof(struct in_addr));
        }
        UserSocketConnectComplete();
    }
    else if (context == &__UDP_SOCKET_IO_EXEC_SHTD) {
        // Nothing to do here. The exec Counter will deleted later and because of
        // the already set shutDown flag, the end of that function will finally
        // let the socket close...
    }

    if ((shutDown || (socketStatus == SST_DO_SHUTDOWN)) && (execCnt == 0)) {
        this->DoShutdown();
    }
}

void UDPSocket::UserSocketConnectComplete()
{
    log->Log(LOG_UDP, "UDPSocket(%08x,%08x)::SocketConnectComplete", this, user);
    user->SocketConnectComplete(this);
}

void UDPSocket::UserSocketBindResult(const char * localAddr, word localPort)
{
    log->Log(LOG_UDP, "UDPSocket(%08x,%08x)::SocketBindResult localAddr:%s localPort:%i", this, user, localAddr, localPort);
    user->SocketBindResult(this, localAddr, localPort);
}

void UDPSocket::UserSocketShutdown(shutdownreason_t reason)
{
    log->Log(LOG_UDP, "UDPSocket(%08x,%08x)::SocketShutdown reason:%i", this, user, reason);
    user->SocketShutdown(this, reason);
}

void UDPSocket::UserSocketSendResult()
{
    log->Log(LOG_UDP, "UDPSocket(%08x,%08x)::SocketSendResult", this, user);
    user->SocketSendResult(this);
}

void UDPSocket::UserSocketRecvFromResult(void * buf, size_t len, IPaddr srcAddr, word srcPort)
{
    char ipaddr[INET6_ADDRSTRLEN+2];
    str::from_ip(ipaddr, INET6_ADDRSTRLEN+2, &srcAddr, true);
    log->Log(LOG_UDP, "UDPSocket(%08x,%08x)::SocketRecvFromResult len:%i addr:%s:%u", this, user, len, ipaddr, srcPort);

    log->HexDump(LOG_UDP, buf, len);
    if (connected) user->SocketRecvResult(this, buf, len);
    else user->SocketRecvFromResult(this, buf, len, srcAddr, srcPort);
}

void UDPSocket::UserSocketRecvCanceled(void * buf)
{
    log->Log(LOG_UDP, "UDPSocket(%08x,%08x)::SocketRecvCanceled", this, user);
    user->SocketRecvCanceled(this, buf);
}
