/*
 * FILENAME: example1.c
 *
 * Copyright  2011-2012 By InterNiche Technologies Inc. All rights reserved
 *
 * This example program implements a TCP echo client
 *
 * ROUTINES: tec_init(), tcecho_loop(), tcp_send_an_echo(),  
 * ROUTINES: tcp_client_close(),  
 */

#include "FreeRTOS.h"
#include "task.h"
#include <stdio.h>
#include "embtcp.h" 

/* Some configuration parameters  */
char *svraddrstr =  "0.0.0.0";   /* CHANGE THIS to a tcp echo server */

/* NOTE: Additional code would be need for echo lengths larger than 255 */
#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 TCP Client connection */

struct TcpClient
{
   SOCKTYPE sock;              /* client socket */
   struct sockaddr_in tec_sin;
   int    replies;             /* Number replies received */
   int    send_cnt;            /* Number of echo requests sent */
   u_long nextsend;            /* ClockTick when next burst is to be sent */
   u_char inbuf[ECHOBUFSIZE];  /* data input buffer */
   u_char outbuf[ECHOBUFSIZE]; /* data output buffer */
   int    nextrcvbyte;         /* Next value we expect to read */
   int    tot_sent;            /* Total echo bytes sent */
   int    tot_rcvd;            /* Total echo bytes received */
};

typedef struct TcpClient *TCPCLIENT;

TCPCLIENT tec = NULL;           /* TCP CLIENT */

int  tec_init(void);
void tcecho_loop(void);
int  tcp_send_an_echo(void);
int  tcp_client_close(void);

/* Define and create the tcp echo client task */
TASK *to_tc_echo;
void tk_tc_echo(void *parm);


extern int get_mac_address(int iface, uint8_t *mac_addr);

/* FUNCTION: example_init()
 *
 * Create tcp 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_tc_echo, "tc_echo", 1024, 0, 2, &to_tc_echo);

   return (ret);
}


/* FUNCTION: tk_tc_echo()
 *
 * Waits for iniche_net_ready, then starts tec_init. On return it 
 * deletes the task.
 *
 * PARAM1: not used
 *
 * RETURNS: none--task deleted
 */

void
tk_tc_echo(void *parm)
{
   while (!iniche_net_ready)    /* Wait for the underlying network code to initialize */
      TK_SLEEP(4);

   TK_SLEEP(5 * TPS);      /* wait for DHCP address resolution */

   tec_init();     /* Runs whole task once */
   TK_DELETE(TK_THIS);
}


/* FUNCTION: tec_init()
 * 
 * Send TCP Echo packets to a TCP Echo Server. 
 *
 * PARAM1: void
 *
 * RETURNS: 0 on SUCCESS or error number. 
 */

int
tec_init(void)
{
   int  err = 0;
   ip_addr svraddr;

   printf("tcp echo client is starting\n");

   /* Convert the IP address string to a binary long number */
   if (inet_pton(AF_INET, svraddrstr, &svraddr) != 0)
   {
      printf("Invalid IP address = %s\n", svraddrstr);
      return (-1);
   }
   /* Allocate the TCPCLIENT structure */
   tec = (TCPCLIENT)pvPortMalloc((sizeof(struct TcpClient) + 3) & ~0x3);
   if (tec == NULL)
   {
      printf("Allocation error.\n");
      return (-1);
   }
   memset(tec, 0, sizeof(struct TcpClient));
   tec->sock = INVALID_SOCKET;  
   tec->nextsend = CTICKS;   /* Send 1st request right away */

   /* open new socket to echo server */
   tec->tec_sin.sin_family = AF_INET;
   tec->tec_sin.sin_addr.s_addr = svraddr;
   tec->tec_sin.sin_port = htons(ECHO_PORT);

   tec->sock = t_socket(AF_INET, SOCK_STREAM, 0);
   if (tec->sock == INVALID_SOCKET) 
   {
      printf("tcp echo: can't open socket\n");
      tcp_client_close();
      return (-1);
   }
   /* We will wait for connection success in t_select */
   err = t_connect(tec->sock, (struct sockaddr *)&tec->tec_sin, 
                   sizeof(tec->tec_sin));
   if (err < 0)
   {
      err = t_errno(tec->sock);
      printf("tcp_echo: socket connect failed, err: %d\n", err);
      tcp_client_close();
      return (-1);
   }
   /* put socket into non-blocking mode */
   t_setsockopt(tec->sock, 0, SO_NBIO, NULL, 0);  
   if (tcp_send_an_echo() == 0)
   {
      tcecho_loop();
   }
   tcp_client_close();
   return (0); 
}


/* FUNCTION: tcp_send_an_echo()
 * 
 * Send a TCP Echo packet from our client to remote server
 *
 * PARAM1: void
 *
 * RETURNS: 0 on SUCCESS or error number 
 */

int
tcp_send_an_echo(void)
{
   int     err;
   u_char  i;
   u_char *bufp = tec->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 TCP echo request %ld to %s\n", tec->send_cnt + 1, 
           svraddrstr);

   bufp = tec->outbuf;
   while (bytestosend)
   {
      bytessent = t_send(tec->sock, (char *)tec->outbuf, (int)bytestosend, 0);
      if (bytessent < 0)
      {
         err = t_errno(tec->sock);
         if (err == EWOULDBLOCK)
         {
            taskYIELD();   /* We're blocked, so let other tasks run */
            continue;
         }
         printf("TCP 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 */
   tec->nextsend = TIME_ADD(CTICKS, ECHODFTTICKS);   
   tec->send_cnt++;                 /* keep counters current */
   tec->tot_sent += ECHODFTLEN;     /* All bytes were sent */
   return (0);
}


/* FUNCTION: tcecho_loop()
 *
 * Send echo data and read and compare echo replies
 * 
 * PARAM1: none
 *
 * RETURNS: void
 */

void
tcecho_loop(void)
{
   int     len;        /* length of recv data */
   int     err;        /* error holder */
   fd_set  fd_recv;    /* fd for recv */
   int     i;
   u_char *bufp;

   for (;;)
   {
      FD_ZERO(&fd_recv);
      FD_SET(tec->sock, &fd_recv);

      t_select(&fd_recv, NULL, NULL, 200);
      len = t_recv(tec->sock, (char *)tec->inbuf, ECHOBUFSIZE, 0);
      if (len < 0)
      {
         err = t_errno(tec->sock);
         if (err != EWOULDBLOCK)
         {
            printf("TCP echo recv error %d\n", err);
         }
      }
      else  /* if (len > 0) - got some echo data */
      {
         bufp = tec->inbuf;
         for (i = 0; i < len; i++)
         {
            if (*bufp++ != tec->nextrcvbyte++)
            {
               printf("tecp_echo data error; got %u, expected %u\n",
                       bufp - 1, tec->nextrcvbyte - 1);
               tcp_client_close();
               return;
            }
            if (tec->nextrcvbyte == ECHODFTLEN)
               tec->nextrcvbyte = 0;
         }
         tec->replies++;
         tec->tot_rcvd += len;
         printf("TCP echo: received correct response\n");
      }
      if (tec->send_cnt >= ECHODFTNUMPKT)
      {
         return;   /* We're done */
      }
      if (TIME_EXP(tec->nextsend, CTICKS))  
         tcp_send_an_echo();
   }
}


/* FUNCTION: tcp_client_close()
 * 
 * Close the underlying socket and free the client structure 
 *
 * PARAM1: void
 *
 * RETURNS: 0 on SUCCESS or error number 
 */

int 
tcp_client_close(void)
{
   /* Close the underlying socket */
   if (tec->sock != INVALID_SOCKET)
   {  
      t_socketclose(tec->sock);
   }
   printf("\ntecpecho: Command complete, bytes sent=%d bytes rcvd=%d\n", 
           tec->tot_sent, tec->tot_rcvd);
   if (tec != NULL)
   {
      vPortFree(tec);
      tec = NULL;
   }
   return (0);
}


