/****************************************************************************
 * drivers/net/telnet.c
 *
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.  The
 * ASF licenses this file to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance with the
 * License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 * License for the specific language governing permissions and limitations
 * under the License.
 *
 ****************************************************************************/

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

#include <nuttx/config.h>

#include <assert.h>
#include <stdio.h>
#include <fcntl.h>
#include <poll.h>
#include <errno.h>
#include <debug.h>
#include <termios.h>

#include <nuttx/kmalloc.h>
#include <nuttx/kthread.h>
#include <nuttx/signal.h>
#include <nuttx/mutex.h>
#include <nuttx/fs/fs.h>
#include <nuttx/net/net.h>
#include <nuttx/net/telnet.h>

#ifdef CONFIG_NETDEV_TELNET

/****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/

/* Configuration ************************************************************/

#ifndef CONFIG_TELNET_RXBUFFER_SIZE
#  define CONFIG_TELNET_RXBUFFER_SIZE 256
#endif

#ifndef CONFIG_TELNET_TXBUFFER_SIZE
#  define CONFIG_TELNET_TXBUFFER_SIZE 256
#endif

#ifndef CONFIG_TELNET_MAXLCLIENTS
#  define CONFIG_TELNET_MAXLCLIENTS 8
#endif

#undef HAVE_SIGNALS
#if defined(CONFIG_TTY_SIGINT) || defined(CONFIG_TTY_SIGTSTP)
#  define HAVE_SIGNALS
#endif

/* Telnet protocol stuff ****************************************************/

#define TELNET_NL             0x0a
#define TELNET_CR             0x0d

/* Telnet commands */

#define TELNET_ECHO           1
#define TELNET_SGA            3     /* Suppress Go Ahead */
#define TELNET_NAWS           31    /* Negotiate about window size */

/* Telnet control */

#define TELNET_IAC            255
#define TELNET_WILL           251
#define TELNET_WONT           252
#define TELNET_DO             253
#define TELNET_DONT           254
#define TELNET_SB             250
#define TELNET_SE             240

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

#define TELNET_DEVFMT         "/dev/telnet%d"

/****************************************************************************
 * Private Types
 ****************************************************************************/

/* The state of the telnet parser */

enum telnet_state_e
{
  STATE_NORMAL = 0,
  STATE_IAC,
  STATE_WILL,
  STATE_WONT,
  STATE_DO,
  STATE_DONT,
  STATE_SB,
  STATE_SB_NAWS,
  STATE_SE
};

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

struct telnet_dev_s
{
  uint8_t           td_state;     /* (See telnet_state_e) */
  uint8_t           td_crefs;     /* The number of open references to the session */
  uint8_t           td_minor;     /* Minor device number */
  uint16_t          td_offset;    /* Offset to the valid, pending bytes in the rxbuffer */
  uint16_t          td_pending;   /* Number of valid, pending bytes in the rxbuffer */
#ifdef CONFIG_TELNET_SUPPORT_NAWS
  uint16_t          td_rows;      /* Number of NAWS rows */
  uint16_t          td_cols;      /* Number of NAWS cols */
  int               td_sb_count;  /* Count of TELNET_SB bytes received */
#endif
#ifdef HAVE_SIGNALS
  pid_t             td_pid;
#endif
  tcflag_t          td_lflag;     /* Local modes */
  FAR struct socket td_psock;     /* A clone of the internal socket structure */
  char td_rxbuffer[CONFIG_TELNET_RXBUFFER_SIZE];
  char td_txbuffer[CONFIG_TELNET_TXBUFFER_SIZE];
};

/****************************************************************************
 * Private Function Prototypes
 ****************************************************************************/

/* Support functions */

#ifdef CONFIG_TELNET_DUMPBUFFER
static inline void telnet_dumpbuffer(FAR const char *msg,
                 FAR const char *buffer, unsigned int nbytes);
#else
#  define telnet_dumpbuffer(msg,buffer,nbytes)
#endif
static void    telnet_getchar(FAR struct telnet_dev_s *priv, uint8_t ch,
                 FAR char *dest, int *nread);
static ssize_t telnet_receive(FAR struct telnet_dev_s *priv,
                 FAR const char *src, size_t srclen, FAR char *dest,
                 size_t destlen);
static bool    telnet_putchar(FAR struct telnet_dev_s *priv, uint8_t ch,
                 int *nwritten);
static void    telnet_sendopt(FAR struct telnet_dev_s *priv, uint8_t option,
                 uint8_t value);

/* Telnet character driver methods */

static int     telnet_open(FAR struct file *filep);
static int     telnet_close(FAR struct file *filep);
static ssize_t telnet_read(FAR struct file *filep, FAR char *buffer,
                           size_t len);
static ssize_t telnet_write(FAR struct file *filep, FAR const char *buffer,
                            size_t len);
static int     telnet_ioctl(FAR struct file *filep, int cmd,
                            unsigned long arg);
static int     telnet_poll(FAR struct file *filep, FAR struct pollfd *fds,
                           bool setup);

/* Telnet session creation */

static int     telnet_session(FAR struct telnet_session_s *session);

/* Telnet factory driver methods */

static ssize_t factory_read(FAR struct file *filep, FAR char *buffer,
                 size_t buflen);
static ssize_t factory_write(FAR struct file *filep, FAR const char *buffer,
                 size_t buflen);
static int     factory_ioctl(FAR struct file *filep, int cmd,
                 unsigned long arg);

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

static const struct file_operations g_telnet_fops =
{
  telnet_open,   /* open */
  telnet_close,  /* close */
  telnet_read,   /* read */
  telnet_write,  /* write */
  NULL,          /* seek */
  telnet_ioctl,  /* ioctl */
  NULL,          /* mmap */
  NULL,          /* truncate */
  telnet_poll    /* poll */
};

static const struct file_operations g_factory_fops =
{
  NULL,          /* open */
  NULL,          /* close */
  factory_read,  /* read */
  factory_write, /* write */
  NULL,          /* seek */
  factory_ioctl, /* ioctl */
};

/* This is an global data set of all of all active Telnet drivers.  This
 * additional logic in included to handle killing of task via control
 * characters received via Telenet (via Ctrl-C SIGINT, in particular).
 */

static struct telnet_dev_s *g_telnet_clients[CONFIG_TELNET_MAXLCLIENTS];
static mutex_t              g_clients_lock = NXMUTEX_INITIALIZER;

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

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

#ifdef CONFIG_TELNET_DUMPBUFFER
static inline void telnet_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: telnet_check_ctrlchar
 *
 * Description:
 *   Check if an incoming control character should generate a signal.
 *
 ****************************************************************************/

#ifdef HAVE_SIGNALS
static void telnet_check_ctrlchar(FAR struct telnet_dev_s *priv,
                                  FAR char *buffer, size_t len)
{
  int signo = 0;

  for (; priv->td_pid >= 0 && len > 0; buffer++, len--)
    {
#ifdef CONFIG_TTY_SIGINT
      /* Is this the special character that will generate the SIGINT
       * signal?
       */

      if (*buffer == CONFIG_TTY_SIGINT_CHAR)
        {
          /* Yes.. note that the kill is needed and do not put the character
           * into the Rx buffer.  It should not be read as normal data.
           */

          signo = SIGINT;
          break;
        }
#endif

#ifdef CONFIG_TTY_SIGTSTP
      /* Is this the special character that will generate the SIGTSTP
       * signal?
       */

      if (*buffer == CONFIG_TTY_SIGTSTP_CHAR)
        {
          /* Note that the kill is needed and do not put the character
           * into the Rx buffer.  It should not be read as normal data.
           */

          signo = SIGTSTP;
#ifndef CONFIG_TTY_SIGINT
          break;
#endif
        }
#endif
    }

  /* Send the signal if necessary */

  if (signo != 0)
    {
      nxsig_kill(priv->td_pid, signo);
    }
}
#endif

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

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

  /* Add all characters to the destination buffer */

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

/****************************************************************************
 * Name: telnet_receive
 *
 * Description:
 *   Process a received Telnet buffer
 *
 ****************************************************************************/

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

  ninfo("srclen: %zd destlen: %zd\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)
              {
                telnet_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;

#ifdef CONFIG_TELNET_SUPPORT_NAWS
                    case TELNET_SB:
                      priv->td_state = STATE_SB;
                      priv->td_sb_count = 0;
                      break;

                    case TELNET_SE:
                      priv->td_state = STATE_NORMAL;
                      break;
#endif

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

          case STATE_WILL:
#ifdef CONFIG_TELNET_SUPPORT_NAWS
            /* For NAWS, Reply with a DO */

            if (ch == TELNET_NAWS)
              {
                telnet_sendopt(priv, TELNET_DO, ch);
              }

            /* Reply with a DON'T */

            else
#endif
              {
                telnet_sendopt(priv, TELNET_DONT, ch);
                ninfo("Suppress: 0x%02X (%d)\n", ch, ch);
              }

            priv->td_state = STATE_NORMAL;
            break;

          case STATE_WONT:
            telnet_sendopt(priv, TELNET_DONT, ch);
            priv->td_state = STATE_NORMAL;
            break;

          case STATE_DO:

            if (ch == TELNET_SGA)
              {
                /* Reply with a WONT, that means we will not work in
                 * character mode and stay in line mode,
                 * it's OK for modern rich featured telnet clients.
                 */

                telnet_sendopt(priv, TELNET_WONT, ch);
              }
            else if ((priv->td_lflag & ECHO) != 0 && ch == TELNET_ECHO)
              {
                telnet_sendopt(priv, TELNET_WONT, ch);
              }
            else
              {
                telnet_sendopt(priv, TELNET_WILL, ch);
              }

            priv->td_state = STATE_NORMAL;
            break;

          case STATE_DONT:

            /* Reply with a WONT */

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

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

#ifdef CONFIG_TELNET_SUPPORT_NAWS
          /* Handle Telnet Sub negotiation request */

          case STATE_SB:
            switch (ch)
              {
                case TELNET_NAWS:
                  priv->td_state = STATE_SB_NAWS;
                  break;

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

          /* Handle NAWS sub-option negotiation */

          case STATE_SB_NAWS:

            /* Update cols / rows based on received byte count */

            switch (priv->td_sb_count)
              {
                case 0:
                  priv->td_cols = (priv->td_cols & 0x00ff) | (ch << 8);
                  break;

                case 1:
                  priv->td_cols = (priv->td_cols & 0xff00) | ch;
                  break;

                case 2:
                  priv->td_rows = (priv->td_rows & 0x00ff) | (ch << 8);
                  break;

                case 3:
                  priv->td_rows = (priv->td_rows & 0xff00) | ch;
                  ninfo("NAWS: %d,%d", priv->td_cols, priv->td_rows);
                  break;
              }

            /* Increment SB count and switch to NORMAL when complete */

            if (++priv->td_sb_count == 4)
              {
                priv->td_state = STATE_NORMAL;
              }

            break;
#endif
        }
    }

  /* 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 telnet_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: telnet_putchar
 *
 * Description:
 *   Put another character from the user buffer to the TX buffer.
 *
 ****************************************************************************/

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

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

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

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

      /* Check for line feeds */

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

          priv->td_txbuffer[index++] = TELNET_CR;

          /* End of line */

          ret = true;
        }

      *nread = index;
    }

  return ret;
}

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

static void telnet_sendopt(FAR struct telnet_dev_s *priv, uint8_t option,
                           uint8_t value)
{
  uint8_t optbuf[3];
  int ret;

  optbuf[0] = TELNET_IAC;
  optbuf[1] = option;
  optbuf[2] = value;

  telnet_dumpbuffer("Send optbuf", optbuf, 3);

  ret = psock_send(&priv->td_psock, optbuf, 3, 0);
  if (ret < 0)
    {
      nerr("ERROR: Failed to send TELNET_IAC: %d\n", ret);
    }
}

/****************************************************************************
 * Name: telnet_open
 ****************************************************************************/

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

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

  /* Get exclusive access to the device structures */

  ret = nxmutex_lock(&g_clients_lock);
  if (ret < 0)
    {
      nerr("ERROR: nxmutex_lock failed: %d\n", ret);
      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_lock;
    }

  /* Save the new open count on success */

  priv->td_crefs = tmp;
  ret = OK;

errout_with_lock:
  nxmutex_unlock(&g_clients_lock);

errout:
  return ret;
}

/****************************************************************************
 * Name: telnet_close
 ****************************************************************************/

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

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

  /* Get exclusive access to the device structures */

  ret = nxmutex_lock(&g_clients_lock);
  if (ret < 0)
    {
      nerr("ERROR: nxmutex_lock failed: %d\n", ret);
      return ret;
    }

  /* 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 */

      priv->td_crefs--;
    }
  else
    {
      /* Re-create the path to the driver. */

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

          ret = unregister_driver(devpath);
          if (ret < 0)
            {
              /* NOTE: a return value of -EBUSY is not an error, it simply
               * means that the Telnet driver is busy now and cannot be
               * registered now because there are other sessions using the
               * connection.  The driver will be properly unregistered when
               * the final session terminates.
               */

              if (ret != -EBUSY)
                {
                  nerr("ERROR: Failed to unregister the driver %s: %d\n",
                       devpath, ret);
                }
              else
                {
                  ret = OK;
                }
            }

          lib_free(devpath);
        }

      for (i = 0; i < CONFIG_TELNET_MAXLCLIENTS; i++)
        {
          if (g_telnet_clients[i] == priv)
            {
              g_telnet_clients[i] = NULL;
              break;
            }
        }

      /* Close the socket */

      psock_close(&priv->td_psock);
      kmm_free(priv);
    }

  nxmutex_unlock(&g_clients_lock);
  return ret;
}

/****************************************************************************
 * Name: telnet_read
 ****************************************************************************/

static ssize_t telnet_read(FAR struct file *filep, FAR char *buffer,
                           size_t len)
{
  FAR struct inode *inode = filep->f_inode;
  FAR struct telnet_dev_s *priv = inode->i_private;
  ssize_t nread = 0;

  ninfo("len: %zd\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 wait for data
   * multiple times in order to get data that the client is interested in.
   */

  do
    {
      FAR const char *src;

      if (priv->td_pending == 0)
        {
          nread = psock_recv(&priv->td_psock,
                             priv->td_rxbuffer,
                             CONFIG_TELNET_RXBUFFER_SIZE,
                             0);
          if (nread <= 0)
            {
              return nread;
            }

          priv->td_pending = nread;
        }

      /* Process the buffered telnet data */

      src = &priv->td_rxbuffer[priv->td_offset];
      nread = telnet_receive(priv, src, priv->td_pending, buffer, len);
    }
  while (nread == 0);

#ifdef HAVE_SIGNALS
  /* Check if any of the received characters is a
   * control that should generate a signal.
   */

  telnet_check_ctrlchar(priv, buffer, nread);
#endif

  /* Returned Value:
   *
   * nread > 0:  The number of characters copied into the user buffer by
   *             telnet_receive().
   * nread <= 0: Loss of connection or error events reported by recv().
   */

  return nread;
}

/****************************************************************************
 * Name: telnet_write
 ****************************************************************************/

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

  ninfo("len: %zd\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 = telnet_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")?
       */

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

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

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

          ncopied = 0;
        }
    }

  /* Send anything remaining in the TX buffer */

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

  /* 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). But it confuses
   * some logic if you report that you sent more than you were requested to.
   */

out:
  return nsent ? nsent : ret;
}

/****************************************************************************
 * Name: telnet_session
 *
 * Description:
 *   Create a character driver to "wrap" the telnet session.  This function
 *   will select and return a unique path for the new telnet device.
 *
 * Input Parameters:
 *   session - On input, contains the socket descriptor that represents the
 *   new telnet connection.  On output, it holds the path to the new Telnet
 *   driver.
 *
 * Returned Value:
 *   Zero (OK) on success; a negated errno value on failure.
 *
 ****************************************************************************/

static int telnet_session(FAR struct telnet_session_s *session)
{
  FAR struct telnet_dev_s *priv;
  FAR struct socket *psock;
  int ret;

  /* Allocate instance data for this driver */

  priv = kmm_zalloc(sizeof(struct telnet_dev_s));
  if (!priv)
    {
      nerr("ERROR: Failed to allocate the driver data structure\n");
      return -ENOMEM;
    }

  priv->td_state     = STATE_NORMAL;
  priv->td_crefs     = 0;
  priv->td_minor     = 0;
  priv->td_pending   = 0;
  priv->td_offset    = 0;
#ifdef HAVE_SIGNALS
  priv->td_pid       = INVALID_PROCESS_ID;
#endif
#ifdef CONFIG_TELNET_SUPPORT_NAWS
  priv->td_rows      = 25;
  priv->td_cols      = 80;
  priv->td_sb_count  = 0;
#endif

  /* Clone the internal socket structure.  We do this so that it will be
   * independent of threads and of socket descriptors (the original socket
   * instance resided in the daemon's task group`).
   */

  ret = sockfd_socket(session->ts_sd, &psock);
  if (ret != OK)
    {
      nerr("ERROR: Failed to convert sd=%d to a socket structure\n",
           session->ts_sd);
      goto errout_with_dev;
    }

  ret = psock_dup2(psock, &priv->td_psock);
  if (ret < 0)
    {
      nerr("ERROR: psock_dup2 failed: %d\n", ret);
      goto errout_with_dev;
    }

  /* Allocate a unique minor device number of the telnet driver.
   * Get exclusive access to the minor counter.
   */

  ret = nxmutex_lock(&g_clients_lock);
  if (ret < 0)
    {
      nerr("ERROR: nxmutex_lock failed: %d\n", ret);
      goto errout_with_clone;
    }

  /* Loop until the device name is verified to be unique. */

  while (priv->td_minor < CONFIG_TELNET_MAXLCLIENTS)
    {
      if (g_telnet_clients[priv->td_minor] == NULL)
        {
          snprintf(session->ts_devpath, TELNET_DEVPATH_MAX,
                   TELNET_DEVFMT, priv->td_minor);
          break;
        }

      priv->td_minor++;
    }

  if (priv->td_minor >= CONFIG_TELNET_MAXLCLIENTS)
    {
      nerr("ERROR: Too many sessions\n");
      ret = -ENFILE;
      goto errout_with_lock;
    }

  /* Setting terminal attributes */

  priv->td_lflag = ECHO;

  /* Register the driver */

  ret = register_driver(session->ts_devpath, &g_telnet_fops, 0666, priv);
  if (ret < 0)
    {
      nerr("ERROR: Failed to register the driver %s: %d\n",
           session->ts_devpath, ret);
      goto errout_with_lock;
    }

  /* Close the original psock (keeping the clone) */

  nx_close(session->ts_sd);

#ifdef CONFIG_TELNET_SUPPORT_NAWS
  telnet_sendopt(priv, TELNET_DO, TELNET_NAWS);
#endif

  /* Save ourself in the list of Telnet client threads */

  g_telnet_clients[priv->td_minor] = priv;
  nxmutex_unlock(&g_clients_lock);

  return OK;

errout_with_lock:
  nxmutex_unlock(&g_clients_lock);

errout_with_clone:
  psock_close(&priv->td_psock);

errout_with_dev:
  kmm_free(priv);
  return ret;
}

/****************************************************************************
 * Name: factory_read
 ****************************************************************************/

static ssize_t factory_read(FAR struct file *filep, FAR char *buffer,
                            size_t len)
{
  return 0; /* Return EOF */
}

/****************************************************************************
 * Name: factory_write
 ****************************************************************************/

static ssize_t factory_write(FAR struct file *filep, FAR const char *buffer,
                             size_t len)
{
  return len; /* Say that everything was written */
}

/****************************************************************************
 * Name: telnet_ioctl
 ****************************************************************************/

static int telnet_ioctl(FAR struct file *filep, int cmd, unsigned long arg)
{
  FAR struct inode *inode = filep->f_inode;
  FAR struct telnet_dev_s *priv = inode->i_private;
  FAR struct termios *termiosp;
  int ret = OK;

  switch (cmd)
    {
#ifdef HAVE_SIGNALS
      /* Make the given terminal the controlling terminal of the calling
       * process.
       */

    case TIOCSCTTY:
      {
        /* Check if the ISIG flag is set in the termios c_lflag to enable
         * this feature.  This flag is set automatically for a serial console
         * device.
         */

        /* Save the PID of the recipient of the SIGINT signal. */

        priv->td_pid = (pid_t)arg;
        DEBUGASSERT((unsigned long)(priv->td_pid) == arg);
      }
      break;
#endif

#ifdef CONFIG_TELNET_SUPPORT_NAWS
      case TIOCGWINSZ:
        {
          FAR struct winsize *pw = (FAR struct winsize *)((uintptr_t)arg);

          /* Get row/col from the private data */

          pw->ws_row = priv->td_rows;
          pw->ws_col = priv->td_cols;
        }
      break;
#endif

    /* Handle TERMIOS command */

    case TCGETS:
      {
        termiosp = (FAR struct termios *)((uintptr_t)arg);
        DEBUGASSERT(termiosp != NULL);

        cfmakeraw(termiosp);

        termiosp->c_lflag = priv->td_lflag;
      }
      break;

    case TCSETS:
      {
        termiosp = (FAR struct termios *)((uintptr_t)arg);
        DEBUGASSERT(termiosp != NULL);

        /* Save the termios settings */

        priv->td_lflag = termiosp->c_lflag;

        if ((priv->td_lflag & ECHO) != 0)
          {
            /* If ECHO is set, then we need to send the won't echo option
             * to the client, let the client do echo to emulate
             * the behavior of a real terminal.
             */

            telnet_sendopt(priv, TELNET_WONT, TELNET_ECHO);
          }
        else
          {
            /* Otherwise, we need to send the will echo option to the
             * client, let the client don't echo to disable the echo.
             */

            telnet_sendopt(priv, TELNET_WILL, TELNET_ECHO);
          }
      }
      break;

    default:
      ret = psock_ioctl(&priv->td_psock, cmd, arg);
      break;
    }

  return ret;
}

/****************************************************************************
 * Name: telnet_poll
 *
 * Description:
 *   The standard poll() operation redirects operations on socket descriptors
 *   to this function.
 *
 * Input Parameters:
 *   fd    - The socket descriptor of interest
 *   fds   - The structure describing the events to be monitored, OR NULL if
 *           this is a request to stop monitoring events.
 *   setup - true: Setup up the poll; false: Teardown the poll
 *
 * Returned Value:
 *  0: Success; Negated errno on failure
 *
 ****************************************************************************/

static int telnet_poll(FAR struct file *filep, FAR struct pollfd *fds,
                       bool setup)
{
  FAR struct inode *inode = filep->f_inode;
  FAR struct telnet_dev_s *priv = inode->i_private;

  DEBUGASSERT(fds != NULL);

  /* Test if we have cached data waiting to be read */

  if (priv->td_pending > 0)
    {
      /* Yes.. then signal the poll logic */

      poll_notify(&fds, 1, POLLRDNORM);
    }

  /* Then let psock_poll() do the heavy lifting */

  return psock_poll(&priv->td_psock, fds, setup);
}

/****************************************************************************
 * Name: factory_ioctl
 ****************************************************************************/

static int factory_ioctl(FAR struct file *filep, int cmd, unsigned long arg)
{
  int ret = OK;

  switch (cmd)
    {
    /* Command:      SIOCTELNET
     * Description:  Create a Telnet sessions.
     * Argument:     A pointer to a write-able instance of struct
     *               telnet_session_s.
     * Dependencies: CONFIG_NETDEV_TELNET
     */

    case SIOCTELNET:
      {
        FAR struct telnet_session_s *session =
            (FAR struct telnet_session_s *)((uintptr_t)arg);

        if (session == NULL)
          {
            ret = -EINVAL;
          }
        else
          {
            ret = telnet_session(session);
          }
      }
      break;

    default:
      ret = -ENOTTY;
      break;
    }

  return ret;
}

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

/****************************************************************************
 * Name: telnet_initialize
 *
 * Description:
 *   Create the Telnet factory at /dev/telnet.
 *
 * Input Parameters:
 *   None
 *
 * Returned Value:
 *   Zero (OK) on success; a negated errno value on failure.
 *
 ****************************************************************************/

int telnet_initialize(void)
{
  return register_driver("/dev/telnet", &g_factory_fops, 0666, NULL);
}

#endif /* CONFIG_NETDEV_TELNET */