/**
 * @file
 * RTP client/server module
 *
 */

/*
 * Redistribution and use in source and binary forms, with or without modification, 
 * are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission. 
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED 
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 
 * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT 
 * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING 
 * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY 
 * OF SUCH DAMAGE.
 *
 * This file is part of the lwIP TCP/IP stack.
 * 
 */

#include "lwip/opt.h"

#if LWIP_SOCKET && LWIP_IGMP /* don't build if not configured for use in lwipopts.h */

#include "lwip/sys.h"
#include "lwip/sockets.h"

#include "rtpdata.h"

#include <string.h>

/** This is an example of a "RTP" client/server based on a MPEG4 bitstream (with socket API).
 */

/**
 * RTP_DEBUG: Enable debugging for RTP.
 */
#ifndef RTP_DEBUG
#define RTP_DEBUG                   LWIP_DBG_ON
#endif

/** RTP stream port */
#ifndef RTP_STREAM_PORT
#define RTP_STREAM_PORT             4000
#endif

/** RTP stream multicast address as IPv4 address in "u32_t" format */
#ifndef RTP_STREAM_ADDRESS
#define RTP_STREAM_ADDRESS          inet_addr("232.0.0.0")
#endif

/** RTP send delay - in milliseconds */
#ifndef RTP_SEND_DELAY
#define RTP_SEND_DELAY              40
#endif

/** RTP receive timeout - in milliseconds */
#ifndef RTP_RECV_TIMEOUT
#define RTP_RECV_TIMEOUT            2000
#endif

/** RTP stats display period - in received packets */
#ifndef RTP_RECV_STATS
#define RTP_RECV_STATS              50
#endif

/** RTP macro to let the application process the data */
#ifndef RTP_RECV_PROCESSING
#define RTP_RECV_PROCESSING(p,s)
#endif

/** RTP packet/payload size */
#define RTP_PACKET_SIZE             1500
#define RTP_PAYLOAD_SIZE            1024

/** RTP header constants */
#define RTP_VERSION                 0x80
#define RTP_TIMESTAMP_INCREMENT     3600
#define RTP_SSRC                    0
#define RTP_PAYLOADTYPE             96
#define RTP_MARKER_MASK             0x80

/** RTP message header */
#ifdef PACK_STRUCT_USE_INCLUDES
#  include "arch/bpstruct.h"
#endif
PACK_STRUCT_BEGIN
struct rtp_hdr {
  PACK_STRUCT_FIELD(u8_t  version);
  PACK_STRUCT_FIELD(u8_t  payloadtype);
  PACK_STRUCT_FIELD(u16_t seqNum);
  PACK_STRUCT_FIELD(u32_t timestamp);
  PACK_STRUCT_FIELD(u32_t ssrc);
} PACK_STRUCT_STRUCT;
PACK_STRUCT_END
#ifdef PACK_STRUCT_USE_INCLUDES
#  include "arch/epstruct.h"
#endif

/** RTP packets */
static u8_t rtp_send_packet[RTP_PACKET_SIZE];
static u8_t rtp_recv_packet[RTP_PACKET_SIZE];

/**
 * RTP send packets
 */
static void
rtp_send_packets( int sock, struct sockaddr_in* to)
{
  struct rtp_hdr* rtphdr;
  u8_t*           rtp_payload;
  int             rtp_payload_size;
  int             rtp_data_index;

  /* prepare RTP packet */
  rtphdr = (struct rtp_hdr*)rtp_send_packet;
  rtphdr->version     = RTP_VERSION;
  rtphdr->payloadtype = 0;
  rtphdr->ssrc        = PP_HTONL(RTP_SSRC);
  rtphdr->timestamp   = htonl(ntohl(rtphdr->timestamp) + RTP_TIMESTAMP_INCREMENT);

  /* send RTP stream packets */
  rtp_data_index = 0;
  do {
    rtp_payload      = rtp_send_packet+sizeof(struct rtp_hdr);
    rtp_payload_size = min(RTP_PAYLOAD_SIZE, (sizeof(rtp_data) - rtp_data_index));

    memcpy(rtp_payload, rtp_data + rtp_data_index, rtp_payload_size);

    /* set MARKER bit in RTP header on the last packet of an image */
    rtphdr->payloadtype = RTP_PAYLOADTYPE | (((rtp_data_index + rtp_payload_size)
      >= sizeof(rtp_data)) ? RTP_MARKER_MASK : 0);

    /* send RTP stream packet */
    if (sendto(sock, rtp_send_packet, sizeof(struct rtp_hdr) + rtp_payload_size,
        0, (struct sockaddr *)to, sizeof(struct sockaddr)) >= 0) {
      rtphdr->seqNum  = htons(ntohs(rtphdr->seqNum) + 1);
      rtp_data_index += rtp_payload_size;
    } else {
      LWIP_DEBUGF(RTP_DEBUG, ("rtp_sender: not sendto==%i\n", errno));
    }
  }while (rtp_data_index < sizeof(rtp_data));
}

/**
 * RTP send thread
 */
static void
rtp_send_thread(void *arg)
{
  int                sock;
  struct sockaddr_in local;
  struct sockaddr_in to;
  u32_t              rtp_stream_address;

  LWIP_UNUSED_ARG(arg);

  /* initialize RTP stream address */
  rtp_stream_address = RTP_STREAM_ADDRESS;

  /* if we got a valid RTP stream address... */
  if (rtp_stream_address != 0) {
    /* create new socket */
    sock = socket(AF_INET, SOCK_DGRAM, 0);
    if (sock >= 0) {
      /* prepare local address */
      memset(&local, 0, sizeof(local));
      local.sin_family      = AF_INET;
      local.sin_port        = PP_HTONS(INADDR_ANY);
      local.sin_addr.s_addr = PP_HTONL(INADDR_ANY);

      /* bind to local address */
      if (bind(sock, (struct sockaddr *)&local, sizeof(local)) == 0) {
        /* prepare RTP stream address */
        memset(&to, 0, sizeof(to));
        to.sin_family      = AF_INET;
        to.sin_port        = PP_HTONS(RTP_STREAM_PORT);
        to.sin_addr.s_addr = rtp_stream_address;

        /* send RTP packets */
        memset(rtp_send_packet, 0, sizeof(rtp_send_packet));
        while (1) {
          rtp_send_packets( sock, &to);
          sys_msleep(RTP_SEND_DELAY);
        }
      }

      /* close the socket */
      closesocket(sock);
    }
  }
}

/**
 * RTP recv thread
 */
static void
rtp_recv_thread(void *arg)
{
  int                sock;
  struct sockaddr_in local;
  struct sockaddr_in from;
  int                fromlen;
  struct ip_mreq     ipmreq;
  struct rtp_hdr*    rtphdr;
  u32_t              rtp_stream_address;
  int                timeout;
  int                result;
  int                recvrtppackets  = 0;
  int                lostrtppackets  = 0;
  u16_t              lastrtpseq = 0;

  LWIP_UNUSED_ARG(arg);

  /* initialize RTP stream address */
  rtp_stream_address = RTP_STREAM_ADDRESS;

  /* if we got a valid RTP stream address... */
  if (rtp_stream_address != 0) {
    /* create new socket */
    sock = socket(AF_INET, SOCK_DGRAM, 0);
    if (sock >= 0) {
      /* prepare local address */
      memset(&local, 0, sizeof(local));
      local.sin_family      = AF_INET;
      local.sin_port        = PP_HTONS(RTP_STREAM_PORT);
      local.sin_addr.s_addr = PP_HTONL(INADDR_ANY);

      /* bind to local address */
      if (bind(sock, (struct sockaddr *)&local, sizeof(local)) == 0) {
        /* set recv timeout */
        timeout = RTP_RECV_TIMEOUT;
        setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout, sizeof(timeout));

        /* prepare multicast "ip_mreq" struct */
        ipmreq.imr_multiaddr.s_addr = rtp_stream_address;
        ipmreq.imr_interface.s_addr = PP_HTONL(INADDR_ANY);

        /* join multicast group */
        if (setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, &ipmreq, sizeof(ipmreq)) == 0) {
          /* receive RTP packets */
          while(1) {
            fromlen = sizeof(from);
            result  = recvfrom(sock, rtp_recv_packet, sizeof(rtp_recv_packet), 0,
              (struct sockaddr *)&from, (socklen_t *)&fromlen);
            if (result >= sizeof(struct rtp_hdr)) {
              rtphdr = (struct rtp_hdr *)rtp_recv_packet;
              recvrtppackets++;
              if ((lastrtpseq == 0) || ((lastrtpseq + 1) == ntohs(rtphdr->seqNum))) {
                RTP_RECV_PROCESSING((rtp_recv_packet + sizeof(rtp_hdr)),(result-sizeof(rtp_hdr)));
              } else {
                lostrtppackets++;
              }
              lastrtpseq = ntohs(rtphdr->seqNum);
              if ((recvrtppackets % RTP_RECV_STATS) == 0) {
                LWIP_DEBUGF(RTP_DEBUG, ("rtp_recv_thread: recv %6i packet(s) / lost %4i packet(s) (%.4f%%)...\n", recvrtppackets, lostrtppackets, (lostrtppackets*100.0)/recvrtppackets));
              }
            } else {
              LWIP_DEBUGF(RTP_DEBUG, ("rtp_recv_thread: recv timeout...\n"));
            }
          }

          /* leave multicast group */
          setsockopt(sock, IPPROTO_IP, IP_DROP_MEMBERSHIP, &ipmreq, sizeof(ipmreq));
        }
      }

      /* close the socket */
      closesocket(sock);
    }
  }
}

void
rtp_init(void)
{
  sys_thread_new("rtp_send_thread", rtp_send_thread, NULL, DEFAULT_THREAD_STACKSIZE, DEFAULT_THREAD_PRIO);
  sys_thread_new("rtp_recv_thread", rtp_recv_thread, NULL, DEFAULT_THREAD_STACKSIZE, DEFAULT_THREAD_PRIO);
}

#endif /* LWIP_SOCKET && LWIP_IGMP */
