/*----------------------------------------------------------------------------*
 *                                                                            *
 *            M I N I M A L I S T I C     U D P     E N T I T Y               *
 *                                                                            *
 *                    Copyright (C) 2010 Amit Chawre.                         *
 *                                                                            *
 *----------------------------------------------------------------------------*/


/** 
 * @file NwMiniUdpEntity.c
 * @brief This file contains example of a minimalistic ULP entity.
*/

#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include "NwEvt.h"
#include "NwGtpv2c.h"
#include "NwMiniLogMgrEntity.h"
#include "NwMiniUdpEntity.h"

#ifndef NW_ASSERT
#define NW_ASSERT assert
#endif 

#define MAX_UDP_PAYLOAD_LEN             (4096)

#ifdef __cplusplus
extern "C" {
#endif

static
NwCharT* gLogLevelStr[] = {"EMER", "ALER", "CRIT",  "ERRO", "WARN", "NOTI", "INFO", "DEBG"};

static int yes = 1;
/*---------------------------------------------------------------------------
 * Private functions
 *--------------------------------------------------------------------------*/

static
void NW_EVT_CALLBACK(nwUdpDataIndicationCallbackData)
{
  NwRcT         rc;
  NwU8T         udpBuf[MAX_UDP_PAYLOAD_LEN];
  NwS32T        bytesRead;
  struct sockaddr_in peer;
  NwU32T        peerLen;
  NwGtpv2cNodeUdpT* thiz = (NwGtpv2cNodeUdpT*) arg;

  peerLen = sizeof(peer);

  bytesRead = recvfrom(fd, udpBuf, MAX_UDP_PAYLOAD_LEN , 0, (struct sockaddr *) &peer,(socklen_t*) &peerLen);
  if(bytesRead > 0)
  {
    thiz->packetsRcvd++;
    NW_LOG(NW_LOG_LEVEL_DEBG, "Received UDP message of size %u from peer %u.%u.%u.%u:%u", bytesRead,
        (peer.sin_addr.s_addr & 0x000000ff),
        (peer.sin_addr.s_addr & 0x0000ff00) >> 8,
        (peer.sin_addr.s_addr & 0x00ff0000) >> 16,
        (peer.sin_addr.s_addr & 0xff000000) >> 24,
        ntohs(peer.sin_port));
    rc = nwGtpv2cProcessUdpReq(thiz->hGtpv2cStack, udpBuf, bytesRead, ntohs(peer.sin_port), ntohl(peer.sin_addr.s_addr));
  }
  else
  {
    switch (errno)
    {

      case ENETUNREACH:
        NW_LOG(NW_LOG_LEVEL_ERRO, "Network not reachable", errno, strerror(errno));
        break;

      case EHOSTUNREACH:
        NW_LOG(NW_LOG_LEVEL_ERRO, "Host not reachable", errno, strerror(errno));
        break;

      case ECONNREFUSED:
        NW_LOG(NW_LOG_LEVEL_ERRO, "Port not reachable", errno, strerror(errno));
        break;

      default:
        NW_LOG(NW_LOG_LEVEL_ERRO, "%s", strerror(errno));
    }
    nwGtpv2cUdpReset(thiz);
  }
  return;
}


/*---------------------------------------------------------------------------
 * Public functions
 *--------------------------------------------------------------------------*/

NwRcT nwGtpv2cUdpInit(NwGtpv2cNodeUdpT* thiz, NwGtpv2cStackHandleT hGtpv2cStack, NwU8T* ipAddrStr) 
{
  int sd;
  struct sockaddr_in addr;

  sd = socket(AF_INET, SOCK_DGRAM, 0);

  if (sd < 0)
  {
    NW_LOG(NW_LOG_LEVEL_ERRO, "%s", strerror(errno));
    NW_ASSERT(0);
  }

  addr.sin_family       = AF_INET;
  addr.sin_port         = htons(2123);
  addr.sin_addr.s_addr  = (strlen(ipAddrStr) ? inet_addr(ipAddrStr) : INADDR_ANY);
  memset(addr.sin_zero, '\0', sizeof (addr.sin_zero));

  if(bind(sd, (struct sockaddr *)&addr, sizeof(addr)) < 0)
  {
    NW_LOG(NW_LOG_LEVEL_ERRO, "Error - %s", strerror(errno));
    NW_ASSERT(0);
  }

  yes = 1;
  if (setsockopt(sd, SOL_IP, IP_RECVERR, &yes, sizeof(yes))) 
  {
    NW_LOG(NW_LOG_LEVEL_ERRO, "%s", strerror(errno));
    NW_ASSERT(0);
  }

  NW_EVENT_ADD((thiz->ev), sd, nwUdpDataIndicationCallbackData, thiz, NW_EVT_READ | NW_EVT_PERSIST);

  thiz->ipv4Addr        = addr.sin_addr.s_addr;
  thiz->hSocket         = sd;
  thiz->hGtpv2cStack    = hGtpv2cStack;

  return NW_OK;
}

NwRcT nwGtpv2cUdpDestroy(NwGtpv2cNodeUdpT* thiz)
{
  close(thiz->hSocket);
}

NwRcT nwGtpv2cUdpReset(NwGtpv2cNodeUdpT* thiz)
{
  int sd;
  struct sockaddr_in addr;

  event_del(&thiz->ev);
  close(thiz->hSocket);

  sd = socket(AF_INET, SOCK_DGRAM, 0);

  if (sd < 0)
  {
    NW_LOG(NW_LOG_LEVEL_ERRO, "%s", strerror(errno));
    NW_ASSERT(0);
  }

  addr.sin_family       = AF_INET;
  addr.sin_port         = htons(2123);
  addr.sin_addr.s_addr  = (thiz->ipv4Addr);
  memset(addr.sin_zero, '\0', sizeof (addr.sin_zero));

  if(bind(sd, (struct sockaddr *)&addr, sizeof(addr)) < 0)
  {
    NW_LOG(NW_LOG_LEVEL_ERRO, "%s", strerror(errno));
    NW_ASSERT(0);
  }

  yes = 1;
  if (setsockopt(sd, SOL_IP, IP_RECVERR, &yes, sizeof(yes))) 
  {
    NW_LOG(NW_LOG_LEVEL_ERRO, "%s", strerror(errno));
    NW_ASSERT(0);
  }

  NW_EVENT_ADD((thiz->ev), sd, nwUdpDataIndicationCallbackData, thiz, (NW_EVT_READ | NW_EVT_PERSIST));

  thiz->hSocket         = sd;

  return NW_OK;
}

NwRcT nwGtpv2cUdpDataReq(NwGtpv2cUdpHandleT udpHandle,
    NwU8T* dataBuf,
    NwU32T dataSize,
    NwU32T peerIp,
    NwU32T peerPort)
{
  struct sockaddr_in peerAddr;
  NwS32T bytesSent;
  NwGtpv2cNodeUdpT* thiz = (NwGtpv2cNodeUdpT*) udpHandle;

  NW_LOG(NW_LOG_LEVEL_DEBG, "Sending %u bytes of data to %u.%u.%u.%u:%u", dataSize,
      (peerIp & 0xff000000) >> 24,
      (peerIp & 0x00ff0000) >> 16,
      (peerIp & 0x0000ff00) >> 8,
      (peerIp & 0x000000ff),
      peerPort);

  peerAddr.sin_family       = AF_INET;
  peerAddr.sin_port         = htons(peerPort);
  peerAddr.sin_addr.s_addr  = htonl(peerIp);
  memset(peerAddr.sin_zero, '\0', sizeof (peerAddr.sin_zero));

  bytesSent = sendto (thiz->hSocket, dataBuf, dataSize, 0, (struct sockaddr *) &peerAddr, sizeof(peerAddr));
  if(bytesSent < 0)
  {
    NW_LOG(NW_LOG_LEVEL_ERRO, "sendto - %s", strerror(errno));
  }
  else
  {
    thiz->packetsSent++;
  }

  return NW_OK;
}


#ifdef __cplusplus
}
#endif

