/*
 * FILENAME: example3.c
 *
 * Copyright 2011-2012 By InterNiche Technologies Inc. All rights reserved
 *
 * Test program for UDP echo client
 *
 * ROUTINES: udp_cecho_init(), udp_cecho_loop(), 
 * ROUTINES: udp_send_an_echo(), udp_cecho_close()
 */

#include "FreeRTOS.h"
#include "task.h"
#include <stdio.h>
#include "embtcp.h"   

/* Some configuration parameters  */
char *usvraddrstr =  "0.0.0.0";

#define  ECHO_PORT     7    /* standard UDP/TCP echo port */
#define  ECHODFTLEN    255  /* Default echo packet length */
#define  ECHODFTNUMPKT 10   /* Default number of echo packets */
#define  ECHODFTTICKS  TPS  /* Send once a second */
#define  ECHOBUFSIZE   256  /* Size of echo buffers  */


/* Structure for holding information regarding a UDP Client connection */
struct UdpClient
{
   SOCKTYPE sock;              /* client socket   */
   struct sockaddr_in tec_sin;
   int    replies;             /* Number replies received */
   int    send_cnt;            /* Number of echo requests sent */
   u_char inbuf[ECHOBUFSIZE];  /* data input buffer */ 
   u_char outbuf[ECHOBUFSIZE]; /* data output buffer */
   u_long nextsend;            /* ClockTick when next burst is to be sent */
   int    nextrcvbyte;         /* Next value we expect to read */
   int    tot_sent;            /* Total echo bytes sent */
   int    tot_rcvd;            /* Total echo bytes received */
};

struct UdpClient uec;

int  udp_cecho_init(void);
void udp_cecho_loop(void);
int  udp_send_an_echo(void);
void udp_cecho_close(void);

/* Define and create the udp echo client task */
TASK *to_uc_echo;
void tk_uc_echo(void *parm);

extern int get_mac_address(int iface, uint8_t *mac_addr);

/* FUNCTION: example_init()
 *
 * Create udp echo client task
 *
 * PARAMS: none
 *
 * RETURNS: int               0 if OK, else error code
 */
int
example_init(void)
{
   int ret;
   uint8_t my_macaddr[6];
   static uint8_t cmpbuf[6] = { 0, 0, 0, 0, 0, 0 };

   if ((get_mac_address(0, my_macaddr) != 0) || 
      memcmp(my_macaddr, cmpbuf, 6) == 0)
   {
      panic("get_mac_address() returned all zeros\n");
   }
   ret = TK_CREATE(&tk_uc_echo, "uc_echo", 1024, 0, 2, &to_uc_echo);

   return (ret);
}


/* FUNCTION: tk_uc_echo
 *
 * Waits for iniche_net_ready, then starts udp_cecho_init. On return it 
 * deletes the task.
 *
 * PARAM1: not used
 *
 * RETURNS: none--task deleted
 */

void
tk_uc_echo(void *parm)
{
   while (!iniche_net_ready)
      TK_SLEEP(4);

   TK_SLEEP(5 * TPS);

   udp_cecho_init();     /* Runs whole task once */
   TK_DELETE(TK_THIS);
}


/* FUNCTION: udp_cecho_init()
 *
 * Do the initialization for a UDP Echo Client
 *
 * PARAM1: void
 *
 * RETURNS: SUCCESS or error number 
 */

int 
udp_cecho_init(void)
{
   int   err;               /* error holder */
   struct sockaddr_in me;   /* my IP info, for bind() */
   struct sockaddr_in him;  /* server's IP info, for client connect() */
   ip_addr svraddr;

   printf("udp echo client is starting\n");
   if (inet_pton(AF_INET, usvraddrstr, &svraddr) != 0)
   {
      printf("Invalid IP address\n");
      return (-1);
   }
   uec.sock = INVALID_SOCKET;  
   uec.nextsend = CTICKS;   /* Send 1st request right away */

   uec.sock = t_socket(AF_INET, SOCK_DGRAM, 0);  /* open UDP socket */
   if (uec.sock == INVALID_SOCKET)
   {
      printf("udp_echo: bad socket: %d\n", uec.sock);
      return (-1);
   }
   me.sin_family = AF_INET;
   me.sin_addr.s_addr = INADDR_ANY;
   me.sin_port = 0;  /* let UDP pick a client port */

   err = t_bind(uec.sock, (struct sockaddr *)&me, sizeof(me));
   if (err != 0)
   {
      err = t_errno(uec.sock);
      printf("udp_echo: bind error: %d\n", err);
      udp_cecho_close();      
      return (-1);
   }
   /* make client socket a connected socket */
   him.sin_family = AF_INET;
   him.sin_addr.s_addr = svraddr;   /* for testing */
   him.sin_port = htons(ECHO_PORT);

   err = t_connect(uec.sock, (struct sockaddr *)&him, sizeof(him));
   if (err != 0)
   {
      err = t_errno(uec.sock);
      printf("udp_echo: client connect error: %d\n", err);
      return (-1);
   }
   /* put socket into non-blocking mode */
   t_setsockopt(uec.sock, SOL_SOCKET, SO_NBIO, NULL, 0);

   if (udp_send_an_echo() == 0)
      udp_cecho_loop();

   udp_cecho_close();
   return (0);
}


/* FUNCTION: udp_cecho_loop()
 *
 * Send echo data and read and compare echo replies
 * 
 * PARAM1: none
 *
 * RETURNS: void
 */

void
udp_cecho_loop(void)
{
   int   len;
   int   err;
   fd_set fd_recv;   /* fd for recv */
   int     i;
   u_char *bufp;

   for (;;)
   {
      FD_ZERO(&fd_recv);
      FD_SET(uec.sock, &fd_recv);

      t_select(&fd_recv, NULL, NULL, 200);

      /* check for received echo reply */
      len = t_recv(uec.sock, (char *)uec.inbuf, ECHODFTLEN, 0);
      if (len < 0)
      {
         err = t_errno(uec.sock);
         if (err != EWOULDBLOCK)
            printf("UDP echo client socket error %d\n", err);
      }
      else  
      {
         bufp = uec.inbuf;
         for (i = 0; i < len; i++)
         {
            if (*bufp++ != uec.nextrcvbyte++)
            {
               printf("udp echo data error; got %u, expected %u\n",
                       bufp - 1, uec.nextrcvbyte - 1);
               return;
            }
            if (uec.nextrcvbyte == ECHODFTLEN)
               uec.nextrcvbyte = 0;
         }
         uec.replies++;
         uec.tot_rcvd += len;
         printf("UDP echo: received correct response\n");
      }
      if (uec.send_cnt >= ECHODFTNUMPKT)
         return;

      if (TIME_EXP(uec.nextsend, CTICKS))  
      {
         if (udp_send_an_echo() != 0)
            return;
      }
   }
}


/* FUNCTION: udp_send_an_echo()
 * 
 * Send a UDP Echo packet from our client to remote server
 *
 * PARAM1: void
 *
 * RETURNS: 0 on SUCCESS or error number 
 */

int
udp_send_an_echo(void)
{
   int     err;
   u_char  i;
   u_char *bufp = uec.outbuf;
   int     bytestosend = ECHODFTLEN; /* NOTE: ECHODFTLEN must be <= 255 */
   int     bytessent = 0;   /* sent in this loop */

   for (i = 0; i < bytestosend; i++)
      *bufp++ = i;

   printf("sending UDP echo request %ld to %s\n", uec.send_cnt + 1, 
           usvraddrstr);

   bufp = uec.outbuf;
   while (bytestosend)
   {
      bytessent = t_send(uec.sock, (char *)uec.outbuf, (int)bytestosend, 0);
      if (bytessent < 0)
      {
         err = t_errno(uec.sock);
         if (err == EWOULDBLOCK)
         {
            taskYIELD();   /* We're blocked, so let other tasks run */
            continue;
         }
         printf("UDP echo send error = %d\n", err);
         return (-1);
      }
      else if (bytessent < bytestosend)   
      {
         /* Only part of the data was written */
         bufp += bytessent;
         bytestosend -= bytessent;
         taskYIELD();       /* Let other tasks run before we try again */
         continue;
      }
      else
         break;      /* All bytes sent */
   }
   /* Set when to send next request */
   uec.nextsend = TIME_ADD(CTICKS, ECHODFTTICKS);   
   uec.send_cnt++;                  /* keep counters current */
   uec.tot_sent += ECHODFTLEN;
   return (0);
}


/* FUNCTION: udp_cecho_close()
 * 
 * Close the underlying socket. Remove node from the Q
 *
 * PARAM1: void
 *
 * RETURNS: void 
 */

void 
udp_cecho_close(void)
{
   /* Close the underlying socket */
   if (uec.sock != INVALID_SOCKET)
   {  
      t_socketclose(uec.sock);
   }
   printf("\nudp echo: Command complete, bytes sent=%d bytes rcvd=%d\n", 
           uec.tot_sent, uec.tot_rcvd);
   return;
}


