/****************************************************************************
 * system/zmodem/zm_state.c
 *
 *   Copyright (C) 2013 Gregory Nutt. All rights reserved.
 *   Author: Gregory Nutt <gnutt@nuttx.org>
 *
 * References:
 *   "The ZMODEM Inter Application File Transfer Protocol", Chuck Forsberg,
 *    Omen Technology Inc., October 14, 1988
 *
 *    This is an original work, but I want to make sure that credit is given
 *    where due:  Parts of the state machine design were inspired by the
 *    Zmodem library of Edward A. Falk, dated January, 1995.  License
 *    unspecified.
 *
 * 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 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 COPYRIGHT HOLDERS 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
 * COPYRIGHT OWNER 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 <sys/stat.h>

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>
#include <ctype.h>
#include <fcntl.h>
#include <sched.h>
#include <assert.h>
#include <errno.h>
#include <crc16.h>
#include <crc32.h>

#include <nuttx/ascii.h>

#include "zm.h"

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

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

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

/* State-specific data receipt handlers */

static int zm_idle(FAR struct zm_state_s *pzm, uint8_t ch);
static int zm_header(FAR struct zm_state_s *pzm, uint8_t ch);
static int zm_data(FAR struct zm_state_s *pzm, uint8_t ch);

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

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

/****************************************************************************
 * Name: zm_event
 *
 * Description:
 *   This is the heart of the Zmodem state machine.  Logic initiated by
 *   zm_parse() will detect events and, eventually call this function.
 *   This function will make the state transition, performing any action
 *   associated with the event.
 *
 ****************************************************************************/

static int zm_event(FAR struct zm_state_s *pzm, int event)
{
  FAR const struct zm_transition_s *ptr;

  zmdbg("ZM[R|S]_state: %d event: %d\n", pzm->state, event);

  /* Look up the entry associated with the event in the current state
   * transition table.  NOTE that each state table must be termined with a
   * ZME_ERROR entry that provides indicates that the event was not
   * expected.  Thus, the following search will always be successful.
   */

  ptr = pzm->evtable[pzm->state];
  while (ptr->type != ZME_ERROR && ptr->type != event)
    {
      /* Skip to the next entry */

      ptr++;
    }

  zmdbg("Transition ZM[R|S]_state %d->%d discard: %d action: %p\n",
        pzm->state, ptr->next, ptr->bdiscard, ptr->action);

  /* Perform the state transition */

  pzm->state = ptr->next;

  /* Discard buffered data if so requrested */

  if (ptr->bdiscard)
    {
      pzm->rcvlen = 0;
      pzm->rcvndx = 0;
    }

  /* And, finally, perform the associated action */

  return ptr->action(pzm);
}

/****************************************************************************
 * Name: zm_nakhdr
 *
 * Description:
 *   Send a NAK in response to a malformed or unsupported header.
 *
 ****************************************************************************/

static int zm_nakhdr(FAR struct zm_state_s *pzm)
{
  zmdbg("PSTATE %d:%d->%d:%d: NAKing\n",
        pzm->pstate, pzm->psubstate, PSTATE_IDLE, PIDLE_ZPAD);

  /* Revert to the IDLE state */

  pzm->pstate    = PSTATE_IDLE;
  pzm->psubstate = PIDLE_ZPAD;

  /* And NAK the header */

  return zm_sendhexhdr(pzm, ZNAK, g_zeroes);
}

/****************************************************************************
 * Name: zm_hdrevent
 *
 * Description:
 *   Process an event associated with a header.
 *
 ****************************************************************************/

static int zm_hdrevent(FAR struct zm_state_s *pzm)
{
  zmdbg("Received type: %d data: %02x %02x %02x %02x\n",
        pzm->hdrdata[0],
        pzm->hdrdata[1], pzm->hdrdata[2], pzm->hdrdata[3], pzm->hdrdata[4]);
  zmdbg("PSTATE %d:%d->%d:%d\n",
        pzm->pstate, pzm->psubstate, PSTATE_IDLE, PIDLE_ZPAD);

  /* Revert to the IDLE state */

  pzm->pstate    = PSTATE_IDLE;
  pzm->psubstate = PIDLE_ZPAD;

  /* Verify the checksum.  16- or 32-bit? */

  if (pzm->hdrfmt == ZBIN32)
    {
      uint32_t crc;

      /* Checksum is over 9 bytes:  The header type, 4 data bytes, plus 4 CRC bytes */

      crc = crc32part(pzm->hdrdata, 9, 0xffffffff);
      if (crc != 0xdebb20e3)
        {
          zmdbg("ERROR: ZBIN32 CRC32 failure: %08x vs debb20e3\n", crc);
          return zm_nakhdr(pzm);
        }
    }
  else
    {
      uint16_t crc;

      /* Checksum is over 7 bytes:  The header type, 4 data bytes, plus 2 CRC bytes */

      crc = crc16part(pzm->hdrdata, 7, 0);
      if (crc != 0)
        {
          zmdbg("ERROR: ZBIN/ZHEX CRC16 failure: %04x vs 0000\n", crc);
          return zm_nakhdr(pzm);
        }
    }

  return zm_event(pzm, pzm->hdrdata[0]);
}

/****************************************************************************
 * Name: zm_dataevent
 *
 * Description:
 *   Process an event associated with a header.
 *
 ****************************************************************************/

static int zm_dataevent(FAR struct zm_state_s *pzm)
{
  zmdbg("Received type: %d length: %d\n", pzm->pkttype, pzm->pktlen);
  zmdbg("PSTATE %d:%d->%d:%d\n",
        pzm->pstate, pzm->psubstate, PSTATE_IDLE, PIDLE_ZPAD);

  /* Revert to the IDLE state */

  pzm->pstate    = PSTATE_IDLE;
  pzm->psubstate = PIDLE_ZPAD;

  /* Verify the checksum. 16- or 32-bit? */

  if (pzm->hdrfmt == ZBIN32)
    {
      uint32_t crc;

      crc = crc32part(pzm->pktbuf, pzm->pktlen, 0xffffffff);
      if (crc != 0xdebb20e3)
        {
          zmdbg("ERROR: ZBIN32 CRC32 failure: %08x vs debb20e3\n", crc);
          pzm->flags &= ~ZM_FLAG_CRKOK;
        }
      else
        {
          pzm->flags |= ZM_FLAG_CRKOK;
        }

      /* Adjust the back length to exclude the packet type length of the 4-
       * byte checksum.
       */

      pzm->pktlen -= 5;
    }
  else
    {
      uint16_t crc;

      crc = crc16part(pzm->pktbuf, pzm->pktlen, 0);
      if (crc != 0)
        {
          zmdbg("ERROR: ZBIN/ZHEX CRC16 failure: %04x vs 0000\n", crc);
          pzm->flags &= ~ZM_FLAG_CRKOK;
        }
      else
        {
          pzm->flags |= ZM_FLAG_CRKOK;
        }

     /* Adjust the back length to exclude the packet type length of the 2-
      * byte checksum.
      */

      pzm->pktlen -= 3;
    }

  /* Then handle the data received event */

  return zm_event(pzm, ZME_DATARCVD);
}

/****************************************************************************
 * Name: zm_idle
 *
 * Description:
 *   Data has been received in state PSTATE_IDLE.  In this state we are
 *   looking for the beginning of a header indicated by the receipt of
 *   ZDLE.  We skip over ZPAD characters and flush the received buffer in
 *   the case where anything else is received.
 *
 ****************************************************************************/

static int zm_idle(FAR struct zm_state_s *pzm, uint8_t ch)
{
  switch (ch)
    {
    /* One or more ZPAD characters must precede the ZDLE */

    case ZPAD:
      {
        /* The ZDLE character is expected next */

        zmdbg("PSTATE %d:%d->%d:%d\n",
              pzm->pstate, pzm->psubstate, pzm->pstate, PIDLE_ZDLE);

        pzm->psubstate = PIDLE_ZDLE;
      }
      break;

    /* ZDLE indicates the beginning of a header. */

    case ZDLE:

      /* Was the ZDLE preceded by ZPAD[s]?  If not, revert to the PIDLE_ZPAD
       * substate.
       */

      if (pzm->psubstate == PIDLE_ZDLE)
        {
          zmdbg("PSTATE %d:%d->%d:%d\n",
                pzm->pstate, pzm->psubstate, PSTATE_HEADER, PHEADER_FORMAT);

          pzm->flags    &= ~ZM_FLAG_OO;
          pzm->pstate    = PSTATE_HEADER;
          pzm->psubstate = PHEADER_FORMAT;
          break;
        }
      else
        {
          zmdbg("PSTATE %d:%d->%d:%d\n",
                pzm->pstate, pzm->psubstate, pzm->pstate, PIDLE_ZPAD);

          pzm->psubstate = PIDLE_ZPAD;
        }

    /* O might be the first character of "OO".  "OO" might be part of the file
     * receiver protocol.  After receiving on e file in a group of files, the
     * receiver expected either "OO" indicating that all files have been sent,
     * or a ZRQINIT header indicating the start of the next file.
     */

    case 'O':
      /* Is "OO" a possibility in this context?  Fall through to the default
       * case if not.
       */

      if ((pzm->flags & ZM_FLAG_OO) != 0)
        {
          /* Yes... did we receive an 'O' before this one? */

          if (pzm->psubstate == PIDLE_OO)
            {
              /* This is the second 'O' of "OO". the receiver operation is
               * finished.
               */

              zmdbg("PSTATE %d:%d->%d:%d\n",
                    pzm->pstate, pzm->psubstate, pzm->pstate, PIDLE_ZPAD);

              pzm->flags    &= ~ZM_FLAG_OO;
              pzm->psubstate = PIDLE_ZPAD;
              return zm_event(pzm, ZME_OO);
            }
          else
            {
              /* No... then this is the first 'O' that we have seen */

              zmdbg("PSTATE %d:%d->%d:%d\n",
                    pzm->pstate, pzm->psubstate, pzm->pstate, PIDLE_OO);

              pzm->psubstate = PIDLE_OO;
            }
          break;
        }

    /* Unexpected character.  Wait for the next ZPAD to get us back in sync. */

    default:
      if (pzm->psubstate != PIDLE_ZPAD)
        {
          zmdbg("PSTATE %d:%d->%d:%d\n",
                pzm->pstate, pzm->psubstate, pzm->pstate, PIDLE_ZPAD);

          pzm->psubstate = PIDLE_ZPAD;
        }
      break;
    }

  return OK;
}

/****************************************************************************
 * Name: zm_header
 *
 * Description:
 *   Data has been received in state PSTATE_HEADER (i.e., ZDLE was received
 *   in PSTAT_IDLE).
 *
 *   The following headers are supported:
 *
 *   16-bit Binary:
 *     ZPAD ZDLE ZBIN type f3/p0 f2/p1 f1/p2 f0/p3 crc-1 crc-2
 *     Payload length: 7 (type, 4 bytes data, 2 byte CRC)
 *   32-bit Binary:
 *     ZPAD ZDLE ZBIN32 type f3/p0 f2/p1 f1/p2 f0/p3 crc-1 crc-2 crc-3 crc-4
 *     Payload length: 9 (type, 4 bytes data, 4 byte CRC)
 *   Hex:
 *     ZPAD ZPAD ZDLE ZHEX type f3/p0 f2/p1 f1/p2 f0/p3 crc-1 crc-2 CR LF [XON]
 *     Payload length: 16 (14 hex digits, cr, lf, ignoring optional XON)
 *
 ****************************************************************************/

static int zm_header(FAR struct zm_state_s *pzm, uint8_t ch)
{
  /* ZDLE encountered in this state means that the following character is
   * escaped.
   */

  if (ch == ZDLE && (pzm->flags & ZM_FLAG_ESC) == 0)
    {
      /* Indicate that we are beginning the escape sequence and return */

      pzm->flags |= ZM_FLAG_ESC;
      return OK;
    }

  /* Handle the escaped character in an escape sequence */

  if ((pzm->flags & ZM_FLAG_ESC) != 0)
    {
      switch (ch)
        {
        /* Two special cases */

        case ZRUB0:
          ch = ASCII_DEL;
          break;

        case ZRUB1:
          ch = 0xff;
          break;

        /* The typical case:  Toggle bit 6 */

        default:
          ch ^= 0x40;
          break;
        }

      /* We are no longer in an escape sequence */

      pzm->flags &= ~ZM_FLAG_ESC;
    }

  /* Now handle the next character, escaped or not, according to the current
   * PSTATE_HEADER substate.
   */

  switch (pzm->psubstate)
    {
    /* Waiting for the header format {ZBIN, ZBIN32, ZHEX} */

    case PHEADER_FORMAT:
      {
        switch (ch)
          {
          /* Supported header formats */

          case ZHEX:
          case ZBIN:
          case ZBIN32:
            {
              /* Save the header format character. Next we expect the header
               * data payload beginning with the header type.
               */

              pzm->hdrfmt    = ch;
              pzm->psubstate = PHEADER_PAYLOAD;
              pzm->hdrndx    = 0;
            }
            break;

          default:
            {
              /* Unrecognized header format. */

              return zm_nakhdr(pzm);
            }
        }
      }
      break;

    /* Waiting for header payload */

    case PHEADER_PAYLOAD:
      {
        int ndx = pzm->hdrndx;

        switch (pzm->hdrfmt)
          {
          /* Supported header formats */

          case ZHEX:
            {
              if (!isxdigit(ch))
                {
                  return zm_nakhdr(pzm);
                }

              /* Save the MS nibble; setup to receive the LS nibble.  Index
               * is not incremented.
               */

              pzm->hdrdata[ndx] = zm_decnibble(ch) << 4;
              pzm->psubstate    = PHEADER_LSPAYLOAD;
            }
            break;

          case ZBIN:
          case ZBIN32:
            {
              /* Save the payload byte and increment the index. */

              pzm->hdrdata[ndx] = ch;
              ndx++;

              /* Check if the full header payload has bee buffered.
               *
               * The ZBIN format uses 16-bit CRC so the binary length of the
               * full payload is 1+4+2 = 7 bytes; the ZBIN32 uses a 32-bit CRC
               * so the binary length of the payload is 1+4+4 = 9 bytes;
               */

              if (ndx >= 9 || (pzm->hdrfmt == ZBIN && ndx >= 7))
                {
                  return zm_hdrevent(pzm);
                }
              else
                {
                  /* Setup to receive the next byte */

                  pzm->psubstate = PHEADER_PAYLOAD;
                  pzm->hdrndx    = ndx;
                }
            }
            break;

          default: /* Should not happen */
            break;
          }
      }
      break;

    /* Waiting for LS nibble header type (ZHEX only) */

    case PHEADER_LSPAYLOAD:
      {
        int ndx = pzm->hdrndx;

        if (pzm->hdrfmt == ZHEX && isxdigit(ch))
          {
            /* Save the LS nibble and increment the index. */

            pzm->hdrdata[ndx] |= zm_decnibble(ch);
            ndx++;

            /* The ZHEX format uses 16-bit CRC.  So the binary length
             * of the sequence is 1+4+2 = 7 bytes.
             */

            if (ndx >= 7)
              {
                return zm_hdrevent(pzm);
              }
            else
              {
                /* Setup to receive the next MS nibble */

                pzm->psubstate = PHEADER_PAYLOAD;
                pzm->hdrndx    = ndx;
              }
          }
        else
          {
            return zm_nakhdr(pzm);
          }
      }
      break;
    }

  return OK;
}

/****************************************************************************
 * Name: zm_data
 *
 * Description:
 *   Data has been received in state PSTATE_DATA.  PSTATE_DATA is set by
 *   Zmodem transfer logic when it exepects to received data from the
 *   remote peer.
 *
 *   FORMAT:
 *     xx xx xx xx ... xx ZDLE <type> crc-1 crc-2 [crc-3 crc-4]
 *
 *   Where xx is binary data (that may be escaped).  The 16- or 32-bit CRC
 *   is selected based on a preceding header.  ZHEX data packets are not
 *   supported.
 *
 *   When setting pstate to PSTATE_DATA, it is also expected that the
 *   following initialization is performed:
 *
 *   - The crc value is initialized appropriately
 *   - ncrc is set to zero.
 *   - pktlen is set to zero
 *
 ****************************************************************************/

static int zm_data(FAR struct zm_state_s *pzm, uint8_t ch)
{
  int ret;

  /* ZDLE encountered in this state means that the following character is
   * escaped.  Escaped characters may appear anywhere within the data packet.
   */

  if (ch == ZDLE && (pzm->flags & ZM_FLAG_ESC) == 0)
    {
      /* Indicate that we are beginning the escape sequence and return */

      pzm->flags |= ZM_FLAG_ESC;
      return OK;
    }

  /* Make sure that there is space for another byte in the packet buffer */

  if (pzm->pktlen >= ZM_PKTBUFSIZE)
    {
      zmdbg("ERROR:  The packet buffer is full\n");
      zmdbg("        ch=%c[%02x] pktlen=%d ptktype=%02x ncrc=%d\n",
            isprint(ch) ? ch : '.', ch, pzm->pktlen, pzm->pkttype, pzm->ncrc);
      zmdbg("        rcvlen=%d rcvndx=%d\n",
            pzm->rcvlen, pzm->rcvndx);
      return -ENOSPC;
    }

  /* Handle the escaped character in an escape sequence */

  if ((pzm->flags & ZM_FLAG_ESC) != 0)
    {
      switch (ch)
        {
        /* The data packet type may immediately follow the ZDLE in PDATA_READ
         * substate.
         */

        case ZCRCW: /* Data packet (Non-streaming, ZACK response expected) */
        case ZCRCE: /* Data packet (End-of-file, no response unless an error occurs) */
        case ZCRCG: /* Data packet (Full streaming, no response) */
        case ZCRCQ: /* Data packet (ZACK response expected) */
          {
            /* Save the packet type, change substates, and set of count that
             * indicates the nubmer of bytes still to be added to the packet
             * buffer:
             *
             *   ZBIN:   1+2 = 3
             *   ZBIN32: 1+4 = 5
             */

            pzm->pkttype   = ch;
            pzm->psubstate = PDATA_CRC;
            pzm->ncrc      = (pzm->hdrfmt == ZBIN32) ? 5 : 3;
          }
          break;

        /* Some special cases */

        case ZRUB0:
          ch = ASCII_DEL;
          break;

        case ZRUB1:
          ch = 0xff;
          break;

        /* The typical case:  Toggle bit 6 */

        default:
          ch ^= 0x40;
          break;
        }

      /* We are no longer in an escape sequence */

      pzm->flags &= ~ZM_FLAG_ESC;
    }

  /* Transfer received data from the I/O buffer to the packet buffer.
   * Accumulate the CRC for the received data.  This includes the data
   * payload plus the packet type code plus the CRC itself.
   */

   pzm->pktbuf[pzm->pktlen++] = ch;
   if (pzm->ncrc == 1)
     {
       /* We are at the end of the packet.  Check the CRC and post the event */

       ret = zm_dataevent(pzm);

       /* The packet data has been processed.  Discard the old buffered
        * packet data.
        */

       pzm->pktlen = 0;
       pzm->ncrc   = 0;
       return ret;
     }
   else if (pzm->ncrc > 1)
     {
       /* We are still parsing the CRC.  Decrement the count of CRC bytes
        * remaining.
        */

       pzm->ncrc--;
     }

  return OK;
}

/****************************************************************************
 * Name: zm_parse
 *
 * Description:
 *   New data from the remote peer is available in pzm->rcvbuf.  The number
 *   number of bytes of new data is given by rcvlen.
 *
 *   This function will parse the data in the buffer and, based on the
 *   current state and the contents of the buffer, will drive the Zmodem
 *   state machine.
 *
 ****************************************************************************/

static int zm_parse(FAR struct zm_state_s *pzm, size_t rcvlen)
{
  uint8_t ch;
  int ret;

  DEBUGASSERT(pzm && rcvlen < CONFIG_SYSTEM_ZMODEM_RCVBUFSIZE);
  zm_dumpbuffer("Received", pzm->rcvbuf, rcvlen);

  /* We keep a copy of the length and buffer index in the state structure.
   * This is only so that deeply nested logic can use these values.
   */

  pzm->rcvlen = rcvlen;
  pzm->rcvndx = 0;

  /* Process each byte until we reach the end of the buffer (or until the
   * data is discarded.
   */

  while (pzm->rcvndx < pzm->rcvlen)
    {
      /* Get the next byte from the buffer */

      ch = pzm->rcvbuf[pzm->rcvndx];
      pzm->rcvndx++;

      /* Handle sequences of CAN characters.  When we encounter 5 in a row,
       * then we consider this a request to cancel the file transfer.
       */

      if (ch == ASCII_CAN)
        {
          if (++pzm->ncan >= 5)
            {
              zmdbg("Remote end has canceled\n");
              pzm->rcvlen = 0;
              pzm->rcvndx = 0;
              return zm_event(pzm, ZME_CANCEL);
            }
        }
      else
        {
          /* Not CAN... reset the sequence count */

          pzm->ncan = 0;
        }

      /* Skip over XON and XOFF */

      if (ch != ASCII_XON && ch != ASCII_XOFF)
        {
          /* And process what follows based on the current parsing state */

          switch (pzm->pstate)
            {
            case PSTATE_IDLE:
              ret = zm_idle(pzm, ch);
              break;

            case PSTATE_HEADER:
              ret = zm_header(pzm, ch);
              break;

            case PSTATE_DATA:
              ret = zm_data(pzm, ch);
              break;

            /* This should not happen */

            default:
              zmdbg("ERROR: Invalid state: %d\n", pzm->pstate);
              ret = -EINVAL;
              break;
            }

          /* Handle end-of-transfer and irrecoverable errors by breaking out
           * of the loop and return a non-zero return value to indicate that
           * transfer is complete.
           */

          if (ret != OK)
            {
              zmdbg("%s: %d\n", ret < 0 ? "Aborting" : "Done", ret);
              return ret;
            }
        }
    }

  /* If we made it through the entire buffer with no errors detected, then
   * return OK == 0 meaning that everything is okay, but we are not finished
   * with the transfer.
   */

  return OK;
}

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

/****************************************************************************
 * Name: zm_datapump
 *
 * Description:
 *   Drive the Zmodem state machine by reading data from the remote peer and
 *   providing that data to the parser.  This loop runs until a fatal error
 *   is detected or until the state machine reports that the transfer has
 *   completed successfully.
 *
 ****************************************************************************/

int zm_datapump(FAR struct zm_state_s *pzm)
{
  int ret = OK;
  ssize_t nread;

  /* Loop until either a read error occurs or until a non-zero value is
   * returned by the parser.
   */

  do
    {
      /* Start/restart the timer.  Whenever we read data from the peer we
       * must anticipate a timeout because we can never be sure that the peer
       * is still responding.
       */

      sched_lock();
      zm_timerstart(pzm, pzm->timeout);

      /* Read a block of data.  read() will return: (1) nread > 0 and nread
       * <= CONFIG_SYSTEM_ZMODEM_RCVBUFSIZE on success, (2) nread == 0 on end
       * of file, or (3) nread < 0 on a read error or interruption by a
       * signal.
       */

      nread = read(pzm->remfd, pzm->rcvbuf, CONFIG_SYSTEM_ZMODEM_RCVBUFSIZE);

      /* Stop the timer */

      (void)zm_timerstop(pzm);
      sched_unlock();

      /* EOF from the remote peer can only mean that we lost the connection
       * somehow.
       */

      if (nread == 0)
        {
          zmdbg("ERROR: Unexpected end-of-file\n");
          return -ENOTCONN;
        }

      /* Did some error occur? */

      else if (nread < 0)
        {
          int errorcode = errno;

          /* EINTR is not an error... it simply means that this read was
           * interrupted by an signal before it obtained in data.  However,
           * the signal may be SIGALRM indicating an timeout condition.
           * We will know in this case because the signal handler will set
           * ZM_FLAG_TIMEOUT.
           */

          if (errorcode == EINTR)
            {
              /* Check for a timeout */

              if ((pzm->flags & ZM_FLAG_TIMEOUT) != 0)
                {
                  /* Yes... a timeout occurred */

                  ret = zm_timeout(pzm);
                }

              /* No.. then just ignore the EINTR. */
            }
          else
            {
              /* But anything else is bad and we will return the failure
               * in those cases.
               */

              zmdbg("ERROR: read failed: %d\n", errorcode);
              return -errorcode;
            }
        }

      /* Then provide that data to the state machine via zm_parse().
       * zm_parse() will return a non-zero value if we need to terminate
       * the loop (with a negative value indicating a failure).
       */

      else /* nread > 0 */
        {
          ret = zm_parse(pzm, nread);
          if (ret < 0)
            {
              zmdbg("ERROR: zm_parse failed: %d\n", ret);
            }
        }
    }
  while (ret == OK);

  return ret;
}


/****************************************************************************
 * Name: zm_readstate
 *
 * Description:
 *   Enter PSTATE_DATA.
 *
 ****************************************************************************/

void zm_readstate(FAR struct zm_state_s *pzm)
{
  zmdbg("PSTATE %d:%d->%d:%d\n",
        pzm->pstate, pzm->psubstate, PSTATE_DATA, PDATA_READ);

  pzm->pstate    = PSTATE_DATA;
  pzm->psubstate = PDATA_READ;
  pzm->pktlen    = 0;
  pzm->ncrc      = 0;
}

/****************************************************************************
 * Name: zm_timeout
 *
 * Description:
 *   Called by the watchdog logic if/when a timeout is detected.
 *
 ****************************************************************************/

int zm_timeout(FAR struct zm_state_s *pzm)
{
  return zm_event(pzm, ZME_TIMEOUT);
}
