/*
 *      rtp socket communication functions
 *
 *      initially contributed by Felix von Leitner
 *
 *      Copyright (c) 2000 Mark Taylor
 *                    2010 Robert Hegemann
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

/* $Id$ */

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

#ifdef HAVE_STDINT_H
# include <stdint.h>
#endif

struct rtpbits {
    int     sequence:16;     /* sequence number: random */
    int     pt:7;            /* payload type: 14 for MPEG audio */
    int     m:1;             /* marker: 0 */
    int     cc:4;            /* number of CSRC identifiers: 0 */
    int     x:1;             /* number of extension headers: 0 */
    int     p:1;             /* is there padding appended: 0 */
    int     v:2;             /* version: 2 */
};

struct rtpheader {           /* in network byte order */
    struct rtpbits b;
    int     timestamp;       /* start: random */
    int     ssrc;            /* random */
    int     iAudioHeader;    /* =0?! */
};


#if !defined( _WIN32 ) && !defined(__MINGW32__)

#ifdef STDC_HEADERS
# include <stdio.h>
# include <stdarg.h>
# include <stdlib.h>
# include <string.h>
#else
# ifndef HAVE_MEMCPY
#  define memcpy(d, s, n) bcopy ((s), (d), (n))
#  define memmove(d, s, n) bcopy ((s), (d), (n))
# endif
#endif

#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif

#include <sys/types.h>
#include <sys/socket.h>
#ifdef __int8_t_defined
#undef uint8_t
#undef uint16_t
#undef uint32_t
#undef uint64_t
#endif
#include <netinet/in.h>
#include <arpa/inet.h>

#ifdef WITH_DMALLOC
#include <dmalloc.h>
#endif

#include "rtp.h"
#include "console.h"

#ifdef IPV6
# include <netdb.h>
# define MAX_PORT_LENGTH 6
#endif

typedef int SOCKET;

struct rtpheader RTPheader;
SOCKET  rtpsocket;


/* create a sender socket. */
int
rtp_socket(char const *address, unsigned int port, unsigned int TTL)
{
    int     iRet, iLoop = 1;
    int     iSocket = -1;
#ifdef IPV6
    char    cPortStr[MAX_PORT_LENGTH];
    struct  addrinfo hint, *multicastAddr = NULL;    
    int     iTtl = TTL;
    unsigned int  iMulticastLoop = 0;    
#else
    struct sockaddr_in sin;
    unsigned char cTtl = TTL;
    char    cLoop = 0;
    unsigned int tempaddr;
#endif  

    if(port == 0 || port > 0xffff){
        error_printf("rtp_socket() Invalid port number.\n");
        goto err_cleanup;
    }

#ifdef IPV6
    snprintf(cPortStr,MAX_PORT_LENGTH,"%d",port);
    memset(&hint, 0, sizeof(hint));
    hint.ai_family = AF_UNSPEC;
    hint.ai_socktype = SOCK_DGRAM;
    iRet = getaddrinfo(address, cPortStr, &hint, &multicastAddr);
    if (iRet){
        error_printf("getaddrinfo() failed.\n");
        goto err_cleanup; 	
    }

    iSocket = socket(multicastAddr->ai_family, multicastAddr->ai_socktype, 0);
    if (iSocket < 0) {
        error_printf("socket() failed.\n");
        goto err_cleanup;
    }

#else
    iSocket = socket(AF_INET, SOCK_DGRAM, 0);
    if (iSocket < 0) {
        error_printf("socket() failed.\n");
        goto err_cleanup;
    }

    memset(&sin, 0, sizeof(sin));
    tempaddr = inet_addr(address);
    sin.sin_family = AF_INET;
    sin.sin_port = htons(port);
    sin.sin_addr.s_addr = tempaddr;
#endif

    iRet = setsockopt(iSocket, SOL_SOCKET, SO_REUSEADDR, &iLoop, sizeof(int));
    if (iRet < 0) {
        error_printf("setsockopt SO_REUSEADDR failed\n");
        close(iSocket);
        goto err_cleanup;
    }

#ifdef IPV6
    if(((multicastAddr->ai_family == PF_INET6)
                ?(((struct sockaddr_in6*)multicastAddr->ai_addr)->sin6_addr.s6_addr[0] == 0xFF)
                :(((unsigned)ntohl(((struct sockaddr_in*)multicastAddr->ai_addr)->sin_addr.s_addr)) >> 28 == 0xE)))
    {
        /* only set multicast parameters for multicast destination IPs */
        iRet = setsockopt(iSocket,
                multicastAddr->ai_family == PF_INET6 ? IPPROTO_IPV6        : IPPROTO_IP,
                multicastAddr->ai_family == PF_INET6 ? IPV6_MULTICAST_HOPS : IP_MULTICAST_TTL,
                (char*) &iTtl, sizeof(iTtl));

        if (iRet < 0) {
            error_printf("setsockopt IPV6_MULTICAST_HOPS failed.  multicast in kernel?\n");
            goto err_cleanup;
        }

        iMulticastLoop = 1;
        iRet = setsockopt(iSocket,
                multicastAddr->ai_family == PF_INET6 ? IPPROTO_IPV6        : IPPROTO_IP,
                multicastAddr->ai_family == PF_INET6 ? IPV6_MULTICAST_LOOP : IP_MULTICAST_LOOP,
                (char*) &iMulticastLoop, sizeof(iMulticastLoop));

        if (iRet < 0) {
            error_printf("setsockopt IPV6_MULTICAST_LOOP failed.  multicast in kernel?\n");
            goto err_cleanup;
        }
    }
    iRet = connect(iSocket, multicastAddr->ai_addr, multicastAddr->ai_addrlen);
    if (iRet < 0) {
        error_printf("connect IPV6_MULTICAST_LOOP failed.  multicast in kernel?\n");
        goto err_cleanup;
    }
    freeaddrinfo(multicastAddr);
#else
    if ((ntohl(tempaddr) >> 28) == 0xe) {
        /* only set multicast parameters for multicast destination IPs */
        iRet = setsockopt(iSocket, IPPROTO_IP, IP_MULTICAST_TTL, &cTtl, sizeof(char));
        if (iRet < 0) {
            error_printf("setsockopt IP_MULTICAST_TTL failed.  multicast in kernel?\n");
            close(iSocket);
            goto err_cleanup;
        }

        cLoop = 1;      /* !? */
        iRet = setsockopt(iSocket, IPPROTO_IP, IP_MULTICAST_LOOP, &cLoop, sizeof(char));
        if (iRet < 0) {
            error_printf("setsockopt IP_MULTICAST_LOOP failed.  multicast in kernel?\n");
            close(iSocket);
            goto err_cleanup;
        }
    }
    iRet = connect(iSocket, (struct sockaddr *) &sin, sizeof(struct sockaddr_in));
    if (iRet < 0) {
        error_printf("connect IP_MULTICAST_LOOP failed.  multicast in kernel?\n");
        close(iSocket);
        goto err_cleanup;
    }
#endif

    rtpsocket = iSocket;

    return 0;

err_cleanup:
    if(iSocket >= 0)
        close(iSocket);
#ifdef IPV6
    if(multicastAddr)
        freeaddrinfo(multicastAddr);
#endif
    return 1;
}


static void
rtp_initialization_extra(void)
{
}

static void
rtp_close_extra(void)
{
}

#else

#include <Winsock2.h>
#ifndef IP_MULTICAST_TTL
#define IP_MULTICAST_TTL 3
#endif
#include <stdio.h>
#include <stdarg.h>

#include "rtp.h"
#include "console.h"


struct rtpheader RTPheader;
SOCKET  rtpsocket;

static char *
last_error_message(int err_code)
{
    char   *msg;
    void   *p_msg_buf;
    FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER,
                   (void *) 0,
                   (DWORD) err_code,
                   MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR) & p_msg_buf, 0, NULL);
    msg = strdup(p_msg_buf);
    LocalFree(p_msg_buf);
    return msg;
}

static int
print_socket_error(int error)
{
    char   *err_txt = last_error_message(error);
    error_printf("error %d\n%s\n", error, err_txt);
    free(err_txt);
    return error;
}

static int
on_socket_error(SOCKET s)
{
    int     error = WSAGetLastError();
    print_socket_error(error);
    if (s != INVALID_SOCKET) {
        closesocket(s);
    }
    return error;
}

/* create a sender socket. */
int
rtp_socket(char const *address, unsigned int port, unsigned int TTL)
{
    char const True = 1;
    char const *c = "";
    int     error;
    UINT    ip;
    PHOSTENT host;
    SOCKET  s;
    SOCKADDR_IN source, dest;

    source.sin_family = AF_INET;
    source.sin_addr.s_addr = htonl(INADDR_ANY);
    source.sin_port = htons(0);

    dest.sin_family = AF_INET;
    dest.sin_addr.s_addr = inet_addr(address);

    if (!strcmp(address, "255.255.255.255")) {
    }
    else if (dest.sin_addr.s_addr == INADDR_NONE) {
        host = gethostbyname(address);

        if (host) {
            dest.sin_addr = *(PIN_ADDR) host->h_addr;
        }
        else {
            error_printf("Unknown host %s\r\n", address);
            return 1;
        }
    }

    dest.sin_port = htons((u_short) port);

    ip = ntohl(dest.sin_addr.s_addr);

    if (IN_CLASSA(ip))
        c = "class A";
    if (IN_CLASSB(ip))
        c = "class B";
    if (IN_CLASSC(ip))
        c = "class C";
    if (IN_CLASSD(ip))
        c = "class D";
    if (ip == INADDR_LOOPBACK)
        c = "loopback";
    if (ip == INADDR_BROADCAST)
        c = "broadcast";

    s = socket(AF_INET, SOCK_DGRAM, PF_UNSPEC);
    if (s == INVALID_SOCKET) {
        error_printf("socket () ");
        return on_socket_error(s);
    }
    error = setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &True, sizeof(True));
    error = bind(s, (struct sockaddr *) &source, sizeof(source));
    if (error == SOCKET_ERROR) {
        error_printf("bind () ");
        return on_socket_error(s);
    }
    if (ip == INADDR_BROADCAST) {
        error_printf("broadcast %s:%u %s\r\n", inet_ntoa(dest.sin_addr), ntohs(dest.sin_port), c);
        error = setsockopt(s, SOL_SOCKET, SO_BROADCAST, &True, sizeof(True));
        if (error == SOCKET_ERROR) {
            error_printf("setsockopt (%u, SOL_SOCKET, SO_BROADCAST, ...) ", s);
            return on_socket_error(s);
        }
    }
    if (IN_CLASSD(ip)) {
        error_printf("multicast %s:%u %s\r\n", inet_ntoa(dest.sin_addr), ntohs(dest.sin_port), c);
        error = setsockopt(s, IPPROTO_IP, IP_MULTICAST_TTL, (const char *) &TTL, sizeof(TTL));
        if (error == SOCKET_ERROR) {
            error_printf("setsockopt (%u, IPPROTO_IP, IP_MULTICAST_TTL, ...) ", s);
            return on_socket_error(s);
        }
    }
    error = connect(s, (PSOCKADDR) & dest, sizeof(SOCKADDR_IN));
    if (error == SOCKET_ERROR) {
        error_printf("connect: ");
        return on_socket_error(s);
    }
    rtpsocket = s;
    return 0;
}

static void
rtp_initialization_extra(void)
{
    WSADATA wsaData;
    int     rc = WSAStartup(MAKEWORD(2, 2), &wsaData);
    if (rc != 0) {
        print_socket_error(rc);
    }
}

static void
rtp_close_extra(void)
{
    WSACleanup();
}

#endif


static int
rtp_send(unsigned char const *data, int len)
{
    SOCKET  s = rtpsocket;
    struct rtpheader *foo = &RTPheader;
    char   *buffer = malloc(len + sizeof(struct rtpheader));
    int    *cast = (int *) foo;
    int    *outcast = (int *) buffer;
    int     count, size;

    outcast[0] = htonl(cast[0]);
    outcast[1] = htonl(cast[1]);
    outcast[2] = htonl(cast[2]);
    outcast[3] = htonl(cast[3]);
    memmove(buffer + sizeof(struct rtpheader), data, len);
    size = len + sizeof(*foo);
    count = send(s, buffer, size, 0);
    free(buffer);

    return count != size;
}

void
rtp_output(unsigned char const *mp3buffer, int mp3size)
{
    rtp_send(mp3buffer, mp3size);
    RTPheader.timestamp += 5;
    RTPheader.b.sequence++;
}

void
rtp_initialization(void)
{
    struct rtpheader *foo = &RTPheader;
    foo->b.v = 2;
    foo->b.p = 0;
    foo->b.x = 0;
    foo->b.cc = 0;
    foo->b.m = 0;
    foo->b.pt = 14;     /* MPEG Audio */
    foo->b.sequence = rand() & 65535;
    foo->timestamp = rand();
    foo->ssrc = rand();
    foo->iAudioHeader = 0;
    rtp_initialization_extra();
}

void
rtp_deinitialization(void)
{
    rtp_close_extra();
}
