/****************************************************************************
 * apps/examples/cc3000/telnetd_driver.c
 *
 *   Copyright (C) 2007, 2009, 2011-2013 Gregory Nutt. All rights reserved.
 *   Author: Gregory Nutt <gnutt@nuttx.org>
 *
 * This is a leverage of similar logic from uIP which has a compatible BSD
 * license:
 *
 *   Author: Adam Dunkels <adam@sics.se>
 *   Copyright (c) 2003, Adam Dunkels.
 *   All rights reserved.
 *
 * 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. Neither the name of the Institute, NuttX nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``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 INSTITUTE OR CONTRIBUTORS 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.
 *
 ****************************************************************************/

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include <nuttx/config.h>

#include <sys/types.h>
#include <nuttx/wireless/cc3000/include/sys/socket.h>

#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <poll.h>
#include <errno.h>
#include <nuttx/fs/fs.h>
#include <debug.h>

#include <nuttx/net/net.h>

#include "netutils/telnetd.h"

#include "telnetd.h"

/****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/
/* Telnet protocol stuff ****************************************************/

#define ISO_nl       0x0a
#define ISO_cr       0x0d

#define TELNET_IAC   255
#define TELNET_WILL  251
#define TELNET_WONT  252
#define TELNET_DO    253
#define TELNET_DONT  254

#define TELNET_RXBUFFER_SIZE 256
#define TELNET_TXBUFFER_SIZE 256

/* Device stuff *************************************************************/

#define TELNETD_DEVFMT "/dev/telnetd%d"

/****************************************************************************
 * Private Types
 ****************************************************************************/
/* The state of the telnet parser */

enum telnetd_state_e
{
  STATE_NORMAL = 0,
  STATE_IAC,
  STATE_WILL,
  STATE_WONT,
  STATE_DO,
  STATE_DONT
};

/* This structure describes the internal state of the driver */

struct telnetd_dev_s
{
  sem_t              td_exclsem; /* Enforces mutually exclusive access */
  uint8_t            td_state;   /* (See telnetd_state_e) */
  uint8_t            td_pending; /* Number of valid, pending bytes in the rxbuffer */
  uint8_t            td_offset;  /* Offset to the valid, pending bytes in the rxbuffer */
  uint8_t            td_crefs;   /* The number of open references to the session */
  int                td_minor;   /* Minor device number */
  long               td_psock;   /* A clone of the internal socket structure */
  char td_rxbuffer[TELNET_RXBUFFER_SIZE];
  char td_txbuffer[TELNET_TXBUFFER_SIZE];
};

/****************************************************************************
 * Private Function Prototypes
 ****************************************************************************/
/* Support functions */

#ifdef CONFIG_TELNETD_DUMPBUFFER
static inline void telnetd_dumpbuffer(FAR const char *msg,
                 FAR const char *buffer, unsigned int nbytes);
#else
# define telnetd_dumpbuffer(msg,buffer,nbytes)
#endif
static void    telnetd_getchar(FAR struct telnetd_dev_s *priv, uint8_t ch,
                 FAR char *dest, int *nread);
static ssize_t telnetd_receive(FAR struct telnetd_dev_s *priv,
                 FAR const char *src, size_t srclen, FAR char *dest,
                 size_t destlen);
static bool    telnetd_putchar(FAR struct telnetd_dev_s *priv, uint8_t ch,
                 int *nwritten);
static void    telnetd_sendopt(FAR struct telnetd_dev_s *priv, uint8_t option,
                 uint8_t value);

/* Character driver methods */

static int     telnetd_open(FAR struct file *filep);
static int     telnetd_close(FAR struct file *filep);
static ssize_t telnetd_read(FAR struct file *, FAR char *, size_t);
static ssize_t telnetd_write(FAR struct file *, FAR const char *, size_t);
static int     telnetd_ioctl(FAR struct file *filep, int cmd,
                 unsigned long arg);

/****************************************************************************
 * Private Data
 ****************************************************************************/

static const struct file_operations g_telnetdfops =
{
  telnetd_open,  /* open */
  telnetd_close, /* close */
  telnetd_read,  /* read */
  telnetd_write, /* write */
  0,             /* seek */
  telnetd_ioctl  /* ioctl */
#ifndef CONFIG_DISABLE_POLL
  , 0            /* poll */
#endif
};

/****************************************************************************
 * Private Functions
 ****************************************************************************/

/****************************************************************************
 * Name: telnetd_dumpbuffer
 *
 * Description:
 *   Dump a buffer of data (debug only)
 *
 ****************************************************************************/

#ifdef CONFIG_TELNETD_DUMPBUFFER
static inline void telnetd_dumpbuffer(FAR const char *msg,
                                      FAR const char *buffer,
                                      unsigned int nbytes)
{
  /* CONFIG_DEBUG_FEATURES, CONFIG_DEBUG_INFO, and CONFIG_DEBUG_NET have to be
  * defined or the following does nothing.
  */

  ninfodumpbuffer(msg, (FAR const uint8_t*)buffer, nbytes);
}
#endif

/****************************************************************************
 * Name: telnetd_getchar
 *
 * Description:
 *   Get another character for the user received buffer from the RX buffer
 *
 ****************************************************************************/

static void telnetd_getchar(FAR struct telnetd_dev_s *priv, uint8_t ch,
                            FAR char *dest, int *nread)
{
  register int index;

  /* Ignore carriage returns */

  if (ch != ISO_cr)
    {
      /* Add all other characters to the destination buffer */

      index = *nread;
      dest[index++] = ch;
      *nread = index;
    }
}

/****************************************************************************
 * Name: telnetd_receive
 *
 * Description:
 *   Process a received telenet buffer
 *
 ****************************************************************************/

static ssize_t telnetd_receive(FAR struct telnetd_dev_s *priv, FAR const char *src,
                               size_t srclen, FAR char *dest, size_t destlen)
{
  int nread;
  uint8_t ch;

  ninfo("srclen: %d destlen: %d\n", srclen, destlen);

  for (nread = 0; srclen > 0 && nread < destlen; srclen--)
    {
      ch = *src++;
      ninfo("ch=%02x state=%d\n", ch, priv->td_state);

      switch (priv->td_state)
        {
          case STATE_IAC:
            if (ch == TELNET_IAC)
              {
                telnetd_getchar(priv, ch, dest, &nread);
                priv->td_state = STATE_NORMAL;
             }
            else
              {
                switch (ch)
                  {
                    case TELNET_WILL:
                      priv->td_state = STATE_WILL;
                      break;

                    case TELNET_WONT:
                      priv->td_state = STATE_WONT;
                      break;

                    case TELNET_DO:
                      priv->td_state = STATE_DO;
                      break;

                    case TELNET_DONT:
                      priv->td_state = STATE_DONT;
                      break;

                    default:
                      priv->td_state = STATE_NORMAL;
                      break;
                  }
              }
            break;

          case STATE_WILL:
            /* Reply with a DONT */

            telnetd_sendopt(priv, TELNET_DONT, ch);
            priv->td_state = STATE_NORMAL;
            break;

          case STATE_WONT:
            /* Reply with a DONT */

            telnetd_sendopt(priv, TELNET_DONT, ch);
            priv->td_state = STATE_NORMAL;
            break;

          case STATE_DO:
            /* Reply with a WONT */

            telnetd_sendopt(priv, TELNET_WONT, ch);
            priv->td_state = STATE_NORMAL;
            break;

          case STATE_DONT:
            /* Reply with a WONT */

            telnetd_sendopt(priv, TELNET_WONT, ch);
            priv->td_state = STATE_NORMAL;
            break;

          case STATE_NORMAL:
            if (ch == TELNET_IAC)
              {
                priv->td_state = STATE_IAC;
              }
            else
              {
                telnetd_getchar(priv, ch, dest, &nread);
              }
            break;
        }
    }

  /* We get here if (1) all of the received bytes have been processed, or
   * (2) if the user's buffer has become full.
   */

  if (srclen > 0)
    {
      /* Remember where we left off.  These bytes will be returned the next
       * time that telnetd_read() is called.
       */

      priv->td_pending = srclen;
      priv->td_offset = (src - priv->td_rxbuffer);
    }
  else
    {
      /* All of the received bytes were consumed */

      priv->td_pending = 0;
      priv->td_offset  = 0;
    }

  return nread;
}

/****************************************************************************
 * Name: telnetd_putchar
 *
 * Description:
 *   Put another character from the user buffer to the TX buffer.
 *
 ****************************************************************************/

static bool telnetd_putchar(FAR struct telnetd_dev_s *priv, uint8_t ch,
                            int *nread)
{
  register int index;
  bool ret = false;

  /* Ignore carriage returns (we will put these in automatically as necesary) */

  if (ch != ISO_cr)
    {
      /* Add all other characters to the destination buffer */

      index = *nread;
      priv->td_txbuffer[index++] = ch;

      /* Check for line feeds */

      if (ch == ISO_nl)
        {
          /* Now add the carriage return */

          priv->td_txbuffer[index++] = ISO_cr;
          priv->td_txbuffer[index++] = '\0';

          /* End of line */

          ret = true;
        }

      *nread = index;
    }

  return ret;
}

/****************************************************************************
 * Name: telnetd_sendopt
 *
 * Description:
 *   Send the telnet option bytes
 *
 ****************************************************************************/

static void telnetd_sendopt(FAR struct telnetd_dev_s *priv, uint8_t option,
                            uint8_t value)
{
  uint8_t optbuf[4];
  optbuf[0] = TELNET_IAC;
  optbuf[1] = option;
  optbuf[2] = value;
  optbuf[3] = 0;

  telnetd_dumpbuffer("Send optbuf", optbuf, 4);
  if (send(priv->td_psock, optbuf, 4, 0) < 0)
    {
      nerr("ERROR: Failed to send TELNET_IAC\n");
    }
}

/****************************************************************************
 * Name: telnetd_open
 ****************************************************************************/

static int telnetd_open(FAR struct file *filep)
{
  FAR struct inode *inode = filep->f_inode;
  FAR struct telnetd_dev_s *priv = inode->i_private;
  int tmp;
  int ret;

  ninfo("td_crefs: %d\n", priv->td_crefs);

  /* O_NONBLOCK is not supported */

  if (filep->f_oflags & O_NONBLOCK)
    {
      ret = -ENOSYS;
      goto errout;
    }

  /* Get exclusive access to the device structures */

  ret = sem_wait(&priv->td_exclsem);
  if (ret < 0)
    {
      ret = -errno;
      goto errout;
    }

  /* Increment the count of references to the device.  If this the first
   * time that the driver has been opened for this device, then initialize
   * the device.
   */

  tmp = priv->td_crefs + 1;
  if (tmp > 255)
    {
      /* More than 255 opens; uint8_t would overflow to zero */

      ret = -EMFILE;
      goto errout_with_sem;
    }

  /* Save the new open count on success */

  priv->td_crefs = tmp;
  ret = OK;

errout_with_sem:
  sem_post(&priv->td_exclsem);

errout:
  return ret;
}

/****************************************************************************
 * Name: telnetd_close
 ****************************************************************************/

static int telnetd_close(FAR struct file *filep)
{
  FAR struct inode *inode = filep->f_inode;
  FAR struct telnetd_dev_s *priv = inode->i_private;
  FAR char *devpath;
  int ret;

  ninfo("td_crefs: %d\n", priv->td_crefs);

  /* Get exclusive access to the device structures */

  ret = sem_wait(&priv->td_exclsem);
  if (ret < 0)
    {
      ret = -errno;
      goto errout;
    }

  /* Decrement the references to the driver.  If the reference count will
   * decrement to 0, then uninitialize the driver.
   */

  if (priv->td_crefs > 1)
    {
      /* Just decrement the reference count and release the semaphore */

      priv->td_crefs--;
      sem_post(&priv->td_exclsem);
    }
  else
    {
      /* Re-create the path to the driver. */

      sched_lock();
      ret = asprintf(&devpath, TELNETD_DEVFMT, priv->td_minor);
      if (ret < 0)
        {
          nerr("ERROR: Failed to allocate the driver path\n");
        }
      else
        {
          /* Unregister the character driver */

          ret = unregister_driver(devpath);
          if (ret < 0)
            {
              nerr("ERROR: Failed to unregister the driver %s: %d\n", devpath, ret);
            }

          free(devpath);
        }

      /* Close the socket */

      closesocket(priv->td_psock);

      /* Release the driver memory.  What if there are threads waiting on
       * td_exclsem?  They will never be awakened!  How could this happen?
       * crefs == 1 so there are no other open references to the driver.
       * But this could have if someone were trying to re-open the driver
       * after every other thread has closed it.  That really should not
       * happen in the intended usage model.
       */

      DEBUGASSERT(priv->td_exclsem.semcount == 0);
      sem_destroy(&priv->td_exclsem);
      free(priv);
      sched_unlock();
    }

  ret = OK;

errout:
  return ret;
}

/****************************************************************************
 * Name: telnetd_read
 ****************************************************************************/

static ssize_t telnetd_read(FAR struct file *filep, FAR char *buffer, size_t len)
{
  FAR struct inode *inode = filep->f_inode;
  FAR struct telnetd_dev_s *priv = inode->i_private;
  ssize_t ret;

  ninfo("len: %d\n", len);

  /* First, handle the case where there are still valid bytes left in the
   * I/O buffer from the last time that read was called.  NOTE:  Much of
   * what we read may be protocol stuff and may not correspond to user
   * data.  Hence we need the loop and we need may need to call psock_recv()
   * multiple times in order to get data that the client is interested in.
   */

  do
    {
      if (priv->td_pending > 0)
        {
          /* Process the buffered telnet data */

          FAR const char *src = &priv->td_rxbuffer[priv->td_offset];
          ret = telnetd_receive(priv, src, priv->td_pending, buffer, len);
        }

      /* Read a buffer of data from the telnet client */

      else
        {
          ret = recv(priv->td_psock, priv->td_rxbuffer,
                     TELNET_RXBUFFER_SIZE, 0);

          /* Did we receive anything? */

          if (ret > 0)
            {
              /* Yes.. Process the newly received telnet data */

              telnetd_dumpbuffer("Received buffer", priv->td_rxbuffer, ret);
              ret = telnetd_receive(priv, priv->td_rxbuffer, ret, buffer, len);
           }

          /* Otherwise the peer closed the connection (ret == 0) or an error
           * occurred (ret < 0).
           */

          else
            {
              break;
            }
        }
    }
  while (ret == 0);

  /* Return:
   *
   * ret > 0:  The number of characters copied into the user buffer by
   *           telnetd_receive().
   * ret <= 0: Loss of connection or error events reported by recv().
   */

  return ret;
}

/****************************************************************************
 * Name: telnetd_write
 ****************************************************************************/

static ssize_t telnetd_write(FAR struct file *filep, FAR const char *buffer, size_t len)
{
  FAR struct inode *inode = filep->f_inode;
  FAR struct telnetd_dev_s *priv = inode->i_private;
  FAR const char *src = buffer;
  ssize_t nsent;
  ssize_t ret;
  int ncopied;
  char ch;
  bool eol;

  ninfo("len: %d\n", len);

  /* Process each character from the user buffer */

  for (nsent = 0, ncopied = 0; nsent < len; nsent++)
    {
      /* Get the next character from the user buffer */

      ch = *src++;

      /* Add the character to the TX buffer */

      eol = telnetd_putchar(priv, ch, &ncopied);

      /* Was that the end of a line? Or is the buffer too full to hold the
       * next largest character sequence ("\r\n\0")?
       */

      if (eol || ncopied > TELNET_TXBUFFER_SIZE-3)
        {
          /* Yes... send the data now */

          ret = send(priv->td_psock, priv->td_txbuffer, ncopied, 0);
          if (ret < 0)
            {
              nerr("ERROR: psock_send failed '%s': %d\n", priv->td_txbuffer, ret);
              return ret;
            }

          /* Reset the index to the beginning of the TX buffer. */

          ncopied = 0;
        }
    }

  /* Send anything remaining in the TX buffer */

  if (ncopied > 0)
    {
      ret = send(priv->td_psock, priv->td_txbuffer, ncopied, 0);
      if (ret < 0)
        {
          nerr("ERROR: psock_send failed '%s': %d\n", priv->td_txbuffer, ret);
          return ret;
        }
    }

  /* Notice that we don't actually return the number of bytes sent, but
   * rather, the number of bytes that the caller asked us to send.  We may
   * have sent more bytes (because of CR-LF expansion and because of NULL
   * termination). But it confuses some logic if you report that you sent
   * more than you were requested to.
   */

  return len;
}

/****************************************************************************
 * Name: telnetd_poll
 ****************************************************************************/

static int telnetd_ioctl(FAR struct file *filep, int cmd, unsigned long arg)
{
#if 0 /* No ioctl commands are yet supported */
  struct inode        *inode = filep->f_inode;
  struct cdcacm_dev_s *priv  = inode->i_private;
  int                  ret   = OK;

  switch (cmd)
    {
    /* Add ioctl commands here */

    default:
      ret = -ENOTTY;
      break;
    }

  return ret;
#else
  return -ENOTTY;
#endif
}

/****************************************************************************
 * Name: telnetd_poll
 ****************************************************************************/

#if 0 /* Not used by this driver */
static int telnetd_poll(FAR struct file *filep, FAR struct pollfd *fds,
                        bool setup)
{
  FAR struct inode         *inode = filep->f_inode;
  FAR struct telnetd_dev_s *priv  = inode->i_private;
}
#endif

/****************************************************************************
 * Public Functions
 ****************************************************************************/

/****************************************************************************
 * Name: telnetd_driver
 *
 * Description:
 *   Create a character driver to "wrap" the telnet session.  This function
 *   will select and return a unique path for the new telnet device.
 *
 * Parameters:
 *   sd - The socket descriptor that represents the new telnet connection.
 *   daemon - A pointer to the structure representing the overall state of
 *     this instance of the telnet daemon.
 *
 * Return:
 *   An allocated string represent the full path to the created driver.  The
 *   receiver of the string must de-allocate this memory when it is no longer
 *   needed.  NULL is returned on a failure.
 *
 ****************************************************************************/

FAR char *telnetd_driver(long sd, FAR struct telnetd_s *daemon)
{
  FAR struct telnetd_dev_s *priv;
  FAR char *devpath = NULL;
  int ret;

  /* Allocate instance data for this driver */

  priv = (FAR struct telnetd_dev_s*)malloc(sizeof(struct telnetd_dev_s));
  if (!priv)
    {
      nerr("ERROR: Failed to allocate the driver data structure\n");
      return NULL;
    }

  /* Initialize the allocated driver instance */

  sem_init(&priv->td_exclsem, 0, 1);

  priv->td_state   = STATE_NORMAL;
  priv->td_crefs   = 0;
  priv->td_pending = 0;
  priv->td_offset  = 0;
  priv->td_psock = sd;


  /* Allocation a unique minor device number of the telnet drvier */

  do
    {
      ret = sem_wait(&g_telnetdcommon.exclsem);
      if (ret < 0 && errno != -EINTR)
        {
          goto errout_with_dev;
        }
    }
  while (ret < 0);

  priv->td_minor = g_telnetdcommon.minor;
  g_telnetdcommon.minor++;
  sem_post(&g_telnetdcommon.exclsem);

  /* Create a path and name for the driver. */

  ret = asprintf(&devpath, TELNETD_DEVFMT, priv->td_minor);
  if (ret < 0)
    {
      nerr("ERROR: Failed to allocate the driver path\n");
      goto errout_with_dev;
    }

  /* Register the driver */

  ret = register_driver(devpath, &g_telnetdfops, 0666, priv);
  if (ret < 0)
    {
      nerr("ERROR: Failed to register the driver %s: %d\n", devpath, ret);
      goto errout_with_devpath;
    }

  /* Return the path to the new telnet driver */

  return devpath;

errout_with_devpath:
  free(devpath);
errout_with_dev:
  free(priv);
  return NULL;
}
