
/*---------------------------------------------------------------------------*/
/* ios_screen_capture_sender.c                                               */
/* copyright (c) innovaphone 2015                                            */
/*                                                                           */
/*---------------------------------------------------------------------------*/

#include <signal.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#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>

#include "platform/platform.h"
#include "ios_screen_capture_sender.h"

extern void ns_log(const char *format, ...);

#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

/*---------------------------------------------------------------------------*/
/* IosScreenCaptureSender                                                    */
/*---------------------------------------------------------------------------*/

bool IosScreenCaptureSenderOpen(IosScreenCaptureSender *tx)
{
    int FDIIoMux;

    tx->FDIIoMux = -1;
    FDIIoMux = socket(AF_INET, SOCK_DGRAM
#ifdef SOCK_NONBLOCK
        | SOCK_NONBLOCK
#endif
        , IPPROTO_UDP);
    if (FDIIoMux < 0) {
        ns_log("IosScreenCaptureSender::OpenSocket socket() failed(%u : %s)", errno, strerror(errno));
        return false;
    }
#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) {
        ns_log("IosScreenCaptureSender::OpenSocket fcntl(F_GETFL) failed (%u : %s)", errno, strerror(errno));
    }
    // do not block if there is no incoming connection
    flags |= O_NONBLOCK;
    if (fcntl(FDIIoMux, F_SETFL, flags) == -1) {
        ns_log("IosScreenCaptureSender::OpenSocket fcntl(F_SETFL) failed (%u : %s)", errno, strerror(errno));
    }
#endif
#ifdef _IOS_
    int noSigPipe = 1;
    if (setsockopt(FDIIoMux, SOL_SOCKET, SO_NOSIGPIPE, &noSigPipe, sizeof(noSigPipe)) < 0) {
        ns_log("IosScreenCaptureSender::OpenSocket setsockopt(SO_NOSIGPIPE) failed (%u : %s)", errno, strerror(errno));
    }
#endif
    sigset_t set;
    sigemptyset(&set);
    sigaddset(&set, SIGPIPE);
    if (sigprocmask(SIG_BLOCK, &set, 0) < 0) {
        ns_log("IosScreenCaptureSender::OpenSocket sigprocmask(SIG_BLOCK) failed (%u : %s)", 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) {
        ns_log("IosScreenCaptureSender::OpenSocket setsockopt(SO_BROADCAST) failed (%u : %s)", errno, strerror(errno));
    }
    int tos = TOS_DSCP_VOICE;
    if(setsockopt(FDIIoMux, IPPROTO_IP, IP_TOS, &tos, (socklen_t)(sizeof(tos))) < 0) {
        ns_log("IosScreenCaptureSender::OpenSocket setsockopt(IP_TOS) failed (%u : %s)", errno, strerror(errno));
    }

    struct sockaddr_storage localAddr;
    memset(&localAddr, 0, sizeof(struct sockaddr_in));
    localAddr.ss_family = AF_INET;
    (((struct sockaddr_in *)&localAddr)->sin_addr).s_addr = htonl(INADDR_LOOPBACK);
    ((struct sockaddr_in *)&localAddr)->sin_port = htons(10011);
    int res = bind(FDIIoMux, (struct sockaddr *)(&localAddr), sizeof(struct sockaddr_in));
    if (res != 0) {
        ns_log("IosScreenCaptureSender::BindIPv4 bind() failed (%u : %s)", errno, strerror(errno));
    }
    tx->FDIIoMux = FDIIoMux;
    socklen_t l = sizeof(struct sockaddr_in);
    getsockname(FDIIoMux, (struct sockaddr *)&localAddr, &l);
    return true;
}

void IosScreenCaptureSenderClose(IosScreenCaptureSender *tx)
{
    if (tx->FDIIoMux != -1) {
        shutdown(tx->FDIIoMux, SHUT_RDWR);
        if (close(tx->FDIIoMux) < 0) {
            ns_log("IosScreenCaptureSender::FDClose close() error (%u : %s)", errno, strerror(errno));
        }
        tx->FDIIoMux = -1;
    }
}

#define IOS_APPSHARING_TRANSFER_MTU (1024 & ~1)

void IosScreenCaptureSenderSend(IosScreenCaptureSender *tx, const byte *frameBuf, dword frameLen, dword timestamp, word duration, word flags, word width, word height, unsigned fractionNom, unsigned fractionDenom)
{
    struct sockaddr_storage sockAddr;
    byte header[17];
    int FDIIoMux;
    ssize_t sendLen;
    unsigned i, len;

    FDIIoMux = tx->FDIIoMux;
    memset(&sockAddr, 0, sizeof(struct sockaddr_in));
    sockAddr.ss_family = AF_INET;
    (((struct sockaddr_in *)&sockAddr)->sin_addr).s_addr = htonl(INADDR_LOOPBACK);
    ((struct sockaddr_in *)&sockAddr)->sin_port = htons(10010);
    i = ((frameLen / IOS_APPSHARING_TRANSFER_MTU) * fractionNom) / fractionDenom;
    while (i < ((frameLen / IOS_APPSHARING_TRANSFER_MTU) * (fractionNom + 1)) / fractionDenom) {
/*
        ns_log("IosScreenCaptureSender::SendTo len:%i addr:%u.%u.%u.%u:%u",
            IOS_APPSHARING_TRANSFER_MTU,
            ((byte *)(&((struct sockaddr_in *)&sockAddr)->sin_addr))[0], ((byte *)(&((struct sockaddr_in *)&sockAddr)->sin_addr))[1], ((byte *)(&((struct sockaddr_in *)&sockAddr)->sin_addr))[2], ((byte *)(&((struct sockaddr_in *)&sockAddr)->sin_addr))[3],
            htons(((struct sockaddr_in *)&sockAddr)->sin_port));
*/
        sendLen = sendto(FDIIoMux, &frameBuf[i * IOS_APPSHARING_TRANSFER_MTU], IOS_APPSHARING_TRANSFER_MTU, 0, (struct sockaddr *)(&sockAddr), sizeof(struct sockaddr_in));
        if (sendLen < 0) {
            ns_log("IosScreenCaptureSender::SendTo sendto() failed (%u : %s)", errno, strerror(errno));
        }
        i++;
    }
    if (fractionNom + 1 == fractionDenom) {
        header[0] = (frameLen & 1) ? frameBuf[frameLen - 1] : 0;
        header[1] = (byte) frameLen;
        header[2] = (byte)(frameLen >> 8);
        header[3] = (byte)(frameLen >> 16);
        header[4] = (byte)(frameLen >> 24);
        header[5] = (byte) timestamp;
        header[6] = (byte)(timestamp >> 8);
        header[7] = (byte)(timestamp >> 16);
        header[8] = (byte)(timestamp >> 24);
        header[9] = (byte) duration;
        header[10] = (byte)(duration >> 8);
        header[11] = (byte) flags;
        header[12] = (byte)(flags >> 8);
        header[13] = (byte) width;
        header[14] = (byte)(width >> 8);
        header[15] = (byte) height;
        header[16] = (byte)(height >> 8);
        len = (frameLen & ~1) - i * IOS_APPSHARING_TRANSFER_MTU;
        if (len != 0) {
/*
            ns_log("IosScreenCaptureSender::SendTo len:%i addr:%u.%u.%u.%u:%u",
                len,
                ((byte *)(&((struct sockaddr_in *)&sockAddr)->sin_addr))[0], ((byte *)(&((struct sockaddr_in *)&sockAddr)->sin_addr))[1], ((byte *)(&((struct sockaddr_in *)&sockAddr)->sin_addr))[2], ((byte *)(&((struct sockaddr_in *)&sockAddr)->sin_addr))[3],
                htons(((struct sockaddr_in *)&sockAddr)->sin_port));
*/
            sendLen = sendto(FDIIoMux, &frameBuf[i * IOS_APPSHARING_TRANSFER_MTU], len, 0, (struct sockaddr *)(&sockAddr), sizeof(struct sockaddr_in));
            if (sendLen < 0) {
                ns_log("IosScreenCaptureSender::SendTo sendto() failed (%u : %s)", errno, strerror(errno));
            }
        }
/*
        ns_log("IosScreenCaptureSender::SendTo len:%i addr:%u.%u.%u.%u:%u",
            sizeof(header),
            ((byte *)(&((struct sockaddr_in *)&sockAddr)->sin_addr))[0], ((byte *)(&((struct sockaddr_in *)&sockAddr)->sin_addr))[1], ((byte *)(&((struct sockaddr_in *)&sockAddr)->sin_addr))[2], ((byte *)(&((struct sockaddr_in *)&sockAddr)->sin_addr))[3],
            htons(((struct sockaddr_in *)&sockAddr)->sin_port));
*/
        sendLen = sendto(FDIIoMux, header, sizeof(header), 0, (struct sockaddr *)(&sockAddr), sizeof(struct sockaddr_in));
        if (sendLen < 0) {
            ns_log("IosScreenCaptureSender::SendTo sendto() failed (%u : %s)", errno, strerror(errno));
        }
    }
}

int IosScreenCaptureSenderRecv(IosScreenCaptureSender *tx, byte *buffer, dword size)
{
    int FDIIoMux, bytesRead;
    socklen_t addrLen;
    struct sockaddr_storage recvAddr;

    FDIIoMux = tx->FDIIoMux;
    addrLen = sizeof(struct sockaddr_in);
    bytesRead = (int)(recvfrom(FDIIoMux, buffer, size, 0, (struct sockaddr *)(&recvAddr), &addrLen));
/*
    ns_log("IosScreenCaptureSender::RecvFrom len:%i addr:%u.%u.%u.%u:%u",
        bytesRead,
        ((byte *)(&((struct sockaddr_in *)&recvAddr)->sin_addr))[0], ((byte *)(&((struct sockaddr_in *)&recvAddr)->sin_addr))[1], ((byte *)(&((struct sockaddr_in *)&recvAddr)->sin_addr))[2], ((byte *)(&((struct sockaddr_in *)&recvAddr)->sin_addr))[3],
        htons(((struct sockaddr_in *)&recvAddr)->sin_port));
*/
    if (bytesRead < 0) {
//        ns_log("IosScreenCaptureSender::RecvFrom negative");
        return bytesRead;
    }
    else if (((((struct sockaddr_in *)&recvAddr)->sin_addr).s_addr != htonl(INADDR_LOOPBACK)) || (((struct sockaddr_in *)&recvAddr)->sin_port != htons(10010))) {
        ns_log("IosScreenCaptureSender::RecvFrom foreign len:%i addr:%u.%u.%u.%u:%u",
            bytesRead,
            ((byte *)(&((struct sockaddr_in *)&recvAddr)->sin_addr))[0], ((byte *)(&((struct sockaddr_in *)&recvAddr)->sin_addr))[1], ((byte *)(&((struct sockaddr_in *)&recvAddr)->sin_addr))[2], ((byte *)(&((struct sockaddr_in *)&recvAddr)->sin_addr))[3],
            htons(((struct sockaddr_in *)&recvAddr)->sin_port));
        return 0;
    }
    return bytesRead;
}

/*---------------------------------------------------------------------------*/
