
/* Copyright (C) 2008 Arch Rock Corporation 2008      */
/* License Agreement: refer to LICENSE.txt            */
/*                                                    */
/******************************************************/

/*!
@file shell.c

@brief Manages the TCP connection, also initializes the Command Shell service upon startup

*/
#include <sys/svcs.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <notifychange.h>

#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <netinet/inet.h>
#include <iwconfig.h>

#include <raven.h>
#include "ieee802154.h"
#include "uart.h"
#include "cmd.h"
#include "server.h"
#include "shell.h"

/* System Event Handlers */
void __main          (event_t event, void * cbargs, void * context);
void tcp_handler     (event_t event, void * cbargs, void * context);
void err_handler     (event_t event, void * cbargs, void * context);
void iwconfig_handler(event_t event, void * cbargs, void * context);
void timer_handler   (event_t event, void * cbargs, void * context);

//! Standard TCP Telnet Service Port
#define TELNET_PORT 23

//! User defined TCP transmission queue buffer size
#define BUFSIZE 512

//! User defined command line size
#define CMDLINESIZE 256
 
//! Socket ID
static int16_t g_sockfd;
//! Local Socket Address 
static sockaddr_in6_t g_localaddr;

enum{
  TELNET_WILL = 251,
  TELNET_IAC = 255,
};

enum{
  IAC = 1,
  WILL = 2,
};


//! TCP event descriptor
static tcp_event_t g_app_tcp_event;

//! Pass this buffer for the stack
//! to use as TCP transmission queue buffer for
//! the socket.
static uint8_t g_tcpbuf[BUFSIZE];

//! Current index into the command line buffer
static uint8_t g_cmdline_index;

//! Command line buffer
static char g_cmdline[CMDLINESIZE];

//! Enumeration of TCP states
enum tcp_state_enum {
  TCPOPEN,
  TCPCONN,
  TCPCLOSE
};

//! Global variable to keep track of the current TCP state machine
enum tcp_state_enum tcp_state;

//! tcp_telnet_handler
//!
//! @brief Implements the TCP state machine to for TCP connection
//! 
//! Accumulates the cmd line into cmd buffer; processes telnet escape charaters
//!
//! @param event equals to TCPBIND as this is a tcpbind() callback
//! @param cbargs passes \em tcp_event back from tcpbind() call
//! @param context passes \em context back from tcpbind() call
//!
//! @return none
//!
void tcp_telnet_handler(event_t event, void * cbargs, void * context)
{
  // Strings to display when state change occurs
  char  AMSG[] = "Acpt";
  char  CMSG[] = "Conn";
  char  RMSG[] = "RCV";
  char  ZMSG[] = "Closed";
  char  EMSG[] = "TCP err";

  int  len,i,j;
  char *p_cmdend = NULL;
  char msg[64];
  char cmd[128];
  static uint8_t mode =0;
  int status;

  switch(g_app_tcp_event.type){
  case ACCEPT:			// accept an incoming TCP connection
    print_msg(AMSG,sizeof(AMSG));
    accept(g_sockfd, &g_app_tcp_event.addr);
    break;
  case CONNECTED:		// respond when connected
    print_msg(CMSG,sizeof(CMSG));
    tcp_state = TCPCONN;
    sprintf(msg, "Welcome to Rock Raven Shell\r\n");
    if (send(g_sockfd, msg, strlen(msg), 0) == -1)
      // send failed, notify me again in err_handler 
      // when send is ready.
      notifywrite(g_sockfd, err_handler, NULL, NULL);
    prompt(g_sockfd);
    g_cmdline_index = 0;
    memset(g_cmdline, 0, CMDLINESIZE); /* Clear command line */
    break;
  case RECV:
    print_msg(RMSG,sizeof(RMSG));
    // copy msg out of recv buffer so that application owns it
    // after call back event returns
    p_cmdend = strchr(g_app_tcp_event.recvbuf, '\n'); 
    if (p_cmdend == NULL){	// EOL not in buffer
      memcpy(&g_cmdline[g_cmdline_index], // append to cmdline
	     g_app_tcp_event.recvbuf,
	     g_app_tcp_event.recvlen);
      g_cmdline_index += g_app_tcp_event.recvlen;
    } else {			// got EOL
      len = (uint16_t) p_cmdend - (uint16_t)g_app_tcp_event.recvbuf + 1;
      memcpy(&g_cmdline[g_cmdline_index], g_app_tcp_event.recvbuf, len);
				/* Process buffer char by char */
      for (j=0, i=0; i < g_cmdline_index + len; i++){
	if (g_cmdline[i] == TELNET_IAC){ // Interpret as Command
	  mode = IAC; continue;
	}
	if (mode == IAC && g_cmdline[i] >= TELNET_WILL){
	  mode = WILL; continue; // Willing to perform option
	}
	if (mode == WILL){	// no Option
	  mode = 0; continue;
	}
	if (g_cmdline[i] == '\b'){ // backspace
	  if (j > 0) j--; 
	  continue;
	}
	cmd[j++] = g_cmdline[i]; // Copy char to cmd for proce
      }
      status = processCmd(cmd, 0, j, g_sockfd);
      g_cmdline_index = g_app_tcp_event.recvlen - len;
      memset(g_cmdline, 0, CMDLINESIZE);
      memcpy(&g_cmdline[0], p_cmdend + 1, g_cmdline_index);
      if (status < 0) {
	close(g_sockfd);
      }
    }
    break;
  case CLOSED:
    tcp_state = TCPCLOSE;
    print_msg(ZMSG,sizeof(ZMSG));
    telnet_init();		/* Reopen socket */
    break;
  default: 
    print_msg(EMSG,sizeof(EMSG));
  }
}

//! TCPecho_init
//!
//! @brief initializes the TCP socket
//!
//! @param none
//!
//! @return none
//!
void telnet_init(){
  // Network interface is up, create a TCP socket
  // and a IP address structure.
  setBlink(1);
  g_localaddr.sin6_port = TELNET_PORT;
  memset(&g_localaddr.sin6_addr, 0, sizeof(g_localaddr.sin6_addr));
  g_sockfd = socket(AF_INET6, SOCK_STREAM, 0);

  // Bind the socket to the IP address to listen on, 
  // the buffer, the continuation handler,
  // and the tcp_event argument.
  if (g_sockfd > 0)
    // After the bind call, listening on the port 
    // starts automatically.
    // app_handler will handle all TCP events.
    tcp_state = TCPOPEN;
    tcpbind(g_sockfd, &g_localaddr, 
	    g_tcpbuf, BUFSIZE,
	    tcp_telnet_handler, &g_app_tcp_event, NULL);
}


//! err_handler
//!
//! @brief Notification that send may be possible again
//!
//! @param event equals to NOTIFYWRITE as this is a notifywrite() callback
//! @param cbargs passes \em resource back from notifywrite() call
//! @param context passes \em context back from notifywrite() call
//!
//! @return none
//!
void err_handler(event_t event, void * cbargs, void * context)
{
  char msg[] = "Out of Buffer.\n";

  // Notify sender that some bytes have been dropped
  // due to buffer limitation.
  if (send(g_sockfd, msg, sizeof(*msg), 0) == -1)
    notifywrite(g_sockfd, err_handler, NULL, NULL);
}



