/****************************************************************************
 * apps/system/zmodem/zm_send.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.
 *
 ****************************************************************************/

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

/****************************************************************************
 * 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 <fcntl.h>
#include <assert.h>
#include <errno.h>

#include <nuttx/crc16.h>
#include <nuttx/crc32.h>
#include <nuttx/ascii.h>

#include <system/zmodem.h>

#include "zm.h"

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

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

/* Zmodem transmit states.
 *
 * A simple transaction, one file, no errors, no CHALLENGE, overlapped I/O.
 * These happen when zm_initialize() is called:
 *
 *   Sender               Receiver    State
 *   --------------     ------------  --------
 *   "rz\r"       ---->               N/A
 *   ZRQINIT      ---->               ZMS_START
 *                <---- ZRINIT
 *   ZSINIT       ---->               ZMS_INITACK
 *                <---- ZACK          End-of-Transfer
 *
 * These happen each time that zm_send() is called:
 *
 *   Sender               Receiver    State
 *   --------------     ------------  --------
 *   ZFILE        ---->               ZMS_FILEWAIT
 *                <---- ZRPOS
 *   ZCRC         ---->               ZMS_CRCWAIT
 *                <---- ZRPOS
 *   ZDATA        ---->
 *   Data packets ---->               ZMS_SENDING /ZMS_SENDWAIT
 *   Last packet  ---->               ZMS_SENDDONE
 *   ZEOF         ---->               ZMS_SENDEOF
 *                <---- ZRINIT
 *   ZFIN         ---->               ZMS_FINISH
 *                <---- ZFIN          End-of-Transfer
 *
 * And, finally, when zm_release() is called:
 *
 *   Sender               Receiver    State
 *   --------------     ------------  --------
 *   OO            ---->
 */

enum zmodem_state_e
{
  ZMS_START = 0,      /* ZRQINIT sent, waiting for ZRINIT from receiver */
  ZMS_INITACK,        /* Received ZRINIT, sent ZSINIT, waiting for ZACK */
  ZMS_FILEWAIT,       /* Sent file header, waiting for ZRPOS */
  ZMS_CRCWAIT,        /* Sent file CRC, waiting for ZRPOS */
  ZMS_SENDING,        /* Streaming data subpackets, ready for interrupt */
  ZMS_SENDWAIT,       /* Waiting for ZACK */
  ZMS_SENDDONE,       /* File finished, need to send ZEOF */
  ZMS_SENDEOF,        /* Sent ZEOF, waiting for ZACK */
  ZMS_FINISH,         /* Sent ZFIN, waiting for ZFIN */
  ZMS_COMMAND,        /* Waiting for command data */
  ZMS_MESSAGE,        /* Waiting for message from received */
  ZMS_DONE            /* Finished with the file transfer */
};

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

/* Transition actions */

static int zms_zrinit(FAR struct zm_state_s *pzm);
static int zms_attention(FAR struct zm_state_s *pzm);
static int zms_challenge(FAR struct zm_state_s *pzm);
static int zms_abort(FAR struct zm_state_s *pzm);
static int zms_ignore(FAR struct zm_state_s *pzm);
static int zms_command(FAR struct zm_state_s *pzm);
static int zms_message(FAR struct zm_state_s *pzm);
static int zms_stderrdata(FAR struct zm_state_s *pzm);
static int zms_initdone(FAR struct zm_state_s *pzm);
static int zms_sendzsinit(FAR struct zm_state_s *pzm);
static int zms_sendfilename(FAR struct zm_state_s *pzm);
static int zms_endoftransfer(FAR struct zm_state_s *pzm);
static int zms_fileskip(FAR struct zm_state_s *pzm);
static int zms_sendfiledata(FAR struct zm_state_s *pzm);
static int zms_sendpacket(FAR struct zm_state_s *pzm);
static int zms_filecrc(FAR struct zm_state_s *pzm);
static int zms_sendwaitack(FAR struct zm_state_s *pzm);
static int zms_sendnak(FAR struct zm_state_s *pzm);
static int zms_sendrpos(FAR struct zm_state_s *pzm);
static int zms_senddoneack(FAR struct zm_state_s *pzm);
static int zms_resendeof(FAR struct zm_state_s *pzm);
static int zms_xfrdone(FAR struct zm_state_s *pzm);
static int zms_finish(FAR struct zm_state_s *pzm);
static int zms_timeout(FAR struct zm_state_s *pzm);
static int zms_cmdto(FAR struct zm_state_s *pzm);
static int zms_doneto(FAR struct zm_state_s *pzm);
static int zms_error(FAR struct zm_state_s *pzm);

/* Internal helpers */

static int zms_startfiledata(FAR struct zms_state_s *pzms);
static int zms_sendfile(FAR struct zms_state_s *pzms,
                        FAR const char *filename,
                        FAR const char *rfilename, uint8_t f0, uint8_t f1);

/****************************************************************************
 * Public Data
 ****************************************************************************/

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

/* Events handled in state ZMS_START - ZRQINIT sent, waiting for ZRINIT from
 * receiver
 */

static const struct zm_transition_s g_zms_start[] =
{
  {ZME_RINIT,     true,  ZMS_START,    zms_zrinit},
  {ZME_SINIT,     false, ZMS_START,    zms_ignore},
  {ZME_CHALLENGE, true,  ZMS_START,    zms_challenge},
  {ZME_ABORT,     true,  ZMS_FINISH,   zms_abort},
  {ZME_FERR,      true,  ZMS_FINISH,   zms_abort},
  {ZME_NAK,       false, ZMS_START,    zms_ignore},
  {ZME_COMMAND,   false, ZMS_COMMAND,  zms_command},
  {ZME_STDERR,    false, ZMS_MESSAGE,  zms_message},
  {ZME_TIMEOUT,   false, ZMS_START,    zms_timeout},
  {ZME_ERROR,     false, ZMS_START,    zms_error},
};

/* Events handled in state ZMS_INITACK - Received ZRINIT, sent (optional)
 * ZSINIT, waiting for ZACK
 */

static const struct zm_transition_s g_zmr_initack[] =
{
  {ZME_SINIT,     false, ZMS_START,    zms_ignore},
  {ZME_ACK,       true,  ZMS_INITACK,  zms_initdone},
  {ZME_NAK,       true,  ZMS_INITACK,  zms_sendzsinit},
  {ZME_RINIT,     true,  ZMS_INITACK,  zms_zrinit},
  {ZME_CHALLENGE, true,  ZMS_INITACK,  zms_challenge},
  {ZME_ABORT,     true,  ZMS_FINISH,   zms_abort},
  {ZME_FERR,      true,  ZMS_FINISH,   zms_abort},
  {ZME_COMMAND,   false, ZMS_COMMAND,  zms_command},
  {ZME_STDERR,    false, ZMS_MESSAGE,  zms_message},
  {ZME_TIMEOUT,   false, ZMS_INITACK,  zms_timeout},
  {ZME_ERROR,     false, ZMS_INITACK,  zms_error},
};

/* Events handled in state ZMS_FILEWAIT- Sent file header,
 * waiting for ZRPOS
 */

static const struct zm_transition_s g_zms_filewait[] =
{
  {ZME_SINIT,     false, ZMS_START,    zms_ignore},
  {ZME_RPOS,      true,  ZMS_SENDING,  zms_sendfiledata},
  {ZME_SKIP,      true,  ZMS_FILEWAIT, zms_fileskip},
  {ZME_CRC,       true,  ZMS_FILEWAIT, zms_filecrc},
  {ZME_NAK,       true,  ZMS_FILEWAIT, zms_sendfilename},
  {ZME_RINIT,     true,  ZMS_FILEWAIT, zms_sendfilename},
  {ZME_ABORT,     true,  ZMS_FINISH,   zms_abort},
  {ZME_FERR,      true,  ZMS_FINISH,   zms_abort},
  {ZME_CHALLENGE, true,  ZMS_FILEWAIT, zms_challenge},
  {ZME_COMMAND,   false, ZMS_COMMAND,  zms_command},
  {ZME_STDERR,    false, ZMS_MESSAGE,  zms_message},
  {ZME_TIMEOUT,   false, ZMS_FILEWAIT, zms_timeout},
  {ZME_ERROR,     false, ZMS_FILEWAIT, zms_error},
};

/* Events handled in state ZMS_CRCWAIT - Sent file CRC,
 * waiting for ZRPOS response.
 */

static const struct zm_transition_s g_zms_crcwait[] =
{
  {ZME_SINIT,     false, ZMS_START,    zms_ignore},
  {ZME_RPOS,      true,  ZMS_SENDING,  zms_sendfiledata},
  {ZME_SKIP,      true,  ZMS_FILEWAIT, zms_fileskip},
  {ZME_NAK,       true,  ZMS_CRCWAIT,  zms_filecrc},
  {ZME_RINIT,     true,  ZMS_FILEWAIT, zms_sendfilename},
  {ZME_ABORT,     true,  ZMS_FINISH,   zms_abort},
  {ZME_FERR,      true,  ZMS_FINISH,   zms_abort},
  {ZME_CRC,       false, ZMS_CRCWAIT,  zms_filecrc},
  {ZME_CHALLENGE, false, ZMS_CRCWAIT,  zms_challenge},
  {ZME_COMMAND,   false, ZMS_COMMAND,  zms_command},
  {ZME_STDERR,    false, ZMS_MESSAGE,  zms_message},
  {ZME_TIMEOUT,   false, ZMS_CRCWAIT,  zms_timeout},
  {ZME_ERROR,     false, ZMS_CRCWAIT,  zms_error},
};

/* Events handled in state ZMS_SENDING - Sending data subpackets, ready for
 * interrupt
 */

static const struct zm_transition_s g_zmr_sending[] =
{
  {ZME_SINIT,     false, ZMS_START,    zms_attention},
  {ZME_ACK,       false, ZMS_SENDING,  zms_sendpacket},
  {ZME_RPOS,      true,  ZMS_SENDING,  zms_sendrpos},
  {ZME_SKIP,      true,  ZMS_FILEWAIT, zms_fileskip},
  {ZME_NAK,       true,  ZMS_SENDING,  zms_sendnak},
  {ZME_RINIT,     true,  ZMS_FILEWAIT, zms_sendfilename},
  {ZME_ABORT,     true,  ZMS_FINISH,   zms_abort},
  {ZME_FERR,      true,  ZMS_FINISH,   zms_abort},
  {ZME_TIMEOUT,   false, ZMS_SENDING,  zms_sendpacket},
  {ZME_ERROR,     false, ZMS_SENDING,  zms_error},
};

/* Events handled in state ZMS_SENDWAIT - Waiting for ZACK */

static const struct zm_transition_s g_zms_sendwait[] =
{
  {ZME_SINIT,     false, ZMS_START,    zms_attention},
  {ZME_ACK,       false, ZMS_SENDING,  zms_sendwaitack},
  {ZME_RPOS,      false, ZMS_SENDWAIT, zms_sendrpos},
  {ZME_SKIP,      true,  ZMS_FILEWAIT, zms_fileskip},
  {ZME_NAK,       false, ZMS_SENDING,  zms_sendnak},
  {ZME_RINIT,     true,  ZMS_FILEWAIT, zms_sendfilename},
  {ZME_ABORT,     true,  ZMS_FINISH,   zms_abort},
  {ZME_FERR,      true,  ZMS_FINISH,   zms_abort},
  {ZME_TIMEOUT,   false, ZMS_SENDWAIT, zms_timeout},
  {ZME_ERROR,     false, ZMS_SENDWAIT, zms_error},
};

/* Events handled in state ZMS_SENDDONE - File sent, need to send ZEOF */

static const struct zm_transition_s g_zms_senddone[] =
{
  {ZME_SINIT,     false, ZMS_START,    zms_ignore},
  {ZME_ACK,       false, ZMS_SENDWAIT, zms_senddoneack},
  {ZME_RPOS,      true,  ZMS_SENDING,  zms_sendrpos},
  {ZME_SKIP,      true,  ZMS_FILEWAIT, zms_fileskip},
  {ZME_NAK,       true,  ZMS_SENDING,  zms_sendnak},
  {ZME_RINIT,     true,  ZMS_FILEWAIT, zms_sendfilename},
  {ZME_ABORT,     true,  ZMS_FINISH,   zms_abort},
  {ZME_FERR,      true,  ZMS_FINISH,   zms_abort},
  {ZME_ERROR,     false, ZMS_SENDWAIT, zms_error},
};

/* Events handled in state ZMS_SENDEOF - Sent ZEOF, waiting for ZACK or
 * ZRINIT
 *
 * Paragraph 8.2: "The sender sends a ZEOF header with the file ending
 * offset equal to the number of characters in the file.  The receiver
 * compares this number with the number of characters received.  If the
 * receiver has received all of the file, it closes the file.  If the
 * file close was satisfactory, the receiver responds with ZRINIT.  If
 * the receiver has not received all the bytes of the file, the receiver
 * ignores the ZEOF because a new ZDATA is coming.  If the receiver cannot
 * properly close the file, a ZFERR header is sent.
 */

const struct zm_transition_s g_zms_sendeof[] =
{
  {ZME_RINIT,     true,  ZMS_START,    zms_endoftransfer},
  {ZME_SINIT,     false, ZMS_START,    zms_ignore},
  {ZME_ACK,       false, ZMS_SENDEOF,  zms_ignore},
  {ZME_RPOS,      true,  ZMS_SENDWAIT, zms_sendrpos},
  {ZME_SKIP,      true,  ZMS_START,    zms_fileskip},
  {ZME_NAK,       true,  ZMS_SENDEOF,  zms_resendeof},
  {ZME_RINIT,     true,  ZMS_FILEWAIT, zms_sendfilename},
  {ZME_ABORT,     true,  ZMS_FINISH,   zms_abort},
  {ZME_FERR,      true,  ZMS_FINISH,   zms_abort},
  {ZME_TIMEOUT,   false, ZMS_SENDEOF,  zms_timeout},
  {ZME_ERROR,     false, ZMS_SENDEOF,  zms_error},
};

/* Events handled in state ZMS_FINISH - Sent ZFIN, waiting for ZFIN */

static const struct zm_transition_s g_zms_finish[] =
{
  {ZME_SINIT,     false, ZMS_START,    zms_ignore},
  {ZME_FIN,       true,  ZMS_DONE,     zms_xfrdone},
  {ZME_NAK,       true,  ZMS_FINISH,   zms_finish},
  {ZME_RINIT,     true,  ZMS_FINISH,   zms_finish},
  {ZME_ABORT,     true,  ZMS_FINISH,   zms_abort},
  {ZME_FERR,      true,  ZMS_FINISH,   zms_abort},
  {ZME_TIMEOUT,   false, ZMS_FINISH,   zms_timeout},
  {ZME_ERROR,     false, ZMS_FINISH,   zms_error}
};

/* Events handled in state ZMS_COMMAND - Waiting for command data */

static struct zm_transition_s g_zms_command[] =
{
  {ZME_SINIT,     false, ZMS_START,    zms_ignore},
  {ZME_DATARCVD,  false, ZMS_COMMAND,  zms_ignore},
  {ZME_TIMEOUT,   false, ZMS_COMMAND,  zms_cmdto},
  {ZME_ERROR,     false, ZMS_COMMAND,  zms_error}
};

/* Events handled in state ZMS_MESSAGE - Waiting for stderr data */

static struct zm_transition_s g_zms_message[] =
{
  {ZME_SINIT,     false, ZMS_START,    zms_ignore},
  {ZME_DATARCVD,  false, ZMS_MESSAGE,  zms_stderrdata},
  {ZME_TIMEOUT,   false, ZMS_MESSAGE,  zms_cmdto},
  {ZME_ERROR,     false, ZMS_MESSAGE,  zms_error}
};

/* Events handled in state ZMS_DONE - Finished with transfer */

static struct zm_transition_s g_zms_done[] =
{
  {ZME_TIMEOUT,   false, ZMS_DONE,     zms_doneto},
  {ZME_ERROR,     false, ZMS_DONE,     zms_error}
};

/* State x Event table for Zmodem receive.  The order of states must
 * exactly match the order defined in enum zms_e
 */

static FAR const struct zm_transition_s * const g_zms_evtable[] =
{
  g_zms_start,    /* ZMS_START:    ZRQINIT sent, waiting for ZRINIT from receiver */
  g_zmr_initack,  /* ZMS_INITACK:  Received ZRINIT, sent ZSINIT, waiting for ZACK */
  g_zms_filewait, /* ZMS_FILEWAIT: Sent file header, waiting for ZRPOS */
  g_zms_crcwait,  /* ZMS_CRCWAIT:  Sent file CRC, waiting for ZRPOS response */
  g_zmr_sending,  /* ZMS_SENDING:  Sending data subpackets, ready for interrupt */
  g_zms_sendwait, /* ZMS_SENDWAIT: Waiting for ZACK */
  g_zms_senddone, /* ZMS_SENDDONE: File sent, need to send ZEOF */
  g_zms_sendeof,  /* ZMS_SENDEOF:  Sent ZEOF, waiting for ZACK */
  g_zms_finish,   /* ZMS_FINISH:   Sent ZFIN, waiting for ZFIN */
  g_zms_command,  /* ZMS_COMMAND:  Waiting for command data */
  g_zms_message,  /* ZMS_MESSAGE:  Waiting for message from receiver */
  g_zms_done      /* ZMS_DONE:     Finished with transfer */
};

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

/****************************************************************************
 * Name: zms_zrinit
 *
 * Description:
 *   Received ZRINIT.  Usually received while in start state, this can
 *   also be an attempt to resync after a protocol failure.
 *
 ****************************************************************************/

static int zms_zrinit(FAR struct zm_state_s *pzm)
{
  FAR struct zms_state_s *pzms = (FAR struct zms_state_s *)pzm;
  uint16_t rcaps;

  zmdbg("ZMS_STATE %d\n", pzm->state);

  /* hdrdata[0] is the header type; header[1-4] is payload:
   *
   *   F0 and F1 contain the  bitwise OR of the receiver capability flags
   *   P0 and ZP1 contain the size of the receiver's buffer in bytes (or 0
   *     if nonstop I/O is allowed.
   */

  pzms->rcvmax = (uint16_t)pzm->hdrdata[2] << 8 | (uint16_t)pzm->hdrdata[1];
  rcaps        = (uint16_t)pzm->hdrdata[3] << 8 | (uint16_t)pzm->hdrdata[4];

  /* Set flags associated with the capabilities */

  if ((rcaps & CANFC32) != 0)
    {
      pzm->flags |= ZM_FLAG_CRC32;
    }

  if ((rcaps & ESCCTL) != 0)
    {
      pzm->flags |= ZM_FLAG_ESCCTRL;
    }

  /* Check if the receiver supports full-duplex streaming
   *
   * ZCRCW:
   *   "If the receiver cannot overlap serial and disk I/O, it uses the
   *    ZRINIT frame to specify a buffer length which the sender will
   *    not overflow.  The sending program sends a ZCRCW data subpacket
   *    and waits for a ZACK header before sending the next segment of
   *    the file.
   *
   * ZCRCG
   *   "A data subpacket terminated by ZCRCG and CRC does not elicit a
   *    response unless an error is detected; more data subpacket(s)
   *    follow immediately."
   *
   *
   * In order to support ZCRCG, this logic must be able to sample the
   * reverse channel while streaming to determine if the receiving wants
   * interrupt the transfer (CONFIG_SYSTEM_ZMODEM_RCVSAMPLE).
   *
   * ZCRCQ
   *   "ZCRCQ data subpackets expect a ZACK response with the
   *    receiver's file offset if no error, otherwise a ZRPOS response
   *    with the last good file offset.  Another data subpacket
   *    continues immediately.  ZCRCQ subpackets are not used if the
   *    receiver does not indicate FDX ability with the CANFDX bit.
   */

#ifdef CONFIG_SYSTEM_ZMODEM_RCVSAMPLE
  /* We support CANFDX.  We can do ZCRCG if the remote sender does too */

  if ((rcaps & (CANFDX | CANOVIO)) ==
      (CANFDX | CANOVIO) && pzms->rcvmax == 0)
    {
      pzms->dpkttype = ZCRCG;
    }
#else
  /* We don't support CANFDX.  We can do ZCRCQ if the remote sender does  */

  if ((rcaps & (CANFDX | CANOVIO)) ==
      (CANFDX | CANOVIO) && pzms->rcvmax == 0)
    {
      /* For the local sender, this is just like ZCRCW */

      pzms->dpkttype = ZCRCQ;
    }
#endif

  /* Otherwise, we have to do ZCRCW */

  else
    {
      pzms->dpkttype = ZCRCW;
    }

#ifdef CONFIG_SYSTEM_ZMODEM_ALWAYSSINT
  return zms_sendzsinit(pzm);
#else
#  ifdef CONFIG_SYSTEM_ZMODEM_SENDATTN
  if (pzms->attn != NULL)
    {
      return zms_sendzsinit(pzm);
    }
  else
#  endif
    {
      zmdbg("ZMS_STATE %d->%d\n", pzm->state, ZMS_DONE);
      pzm->state = ZMS_DONE;
      return ZM_XFRDONE;
    }
#endif
}

/****************************************************************************
 * Name: zms_attention
 *
 * Description:
 *   Received ZSINIT while sending data.  The receiver wants something.
 *   Switch tot he ZMS_SENDWAIT state and wait.  A ZRPOS should be forth-
 *   coming.
 *
 ****************************************************************************/

static int zms_attention(FAR struct zm_state_s *pzm)
{
  zmdbg("ZMS_STATE %d\n", pzm->state);

  /* In the case of full streaming, the presence of pending read data should
   * cause the sending logic to break out of the loop and handle the received
   * data, getting us to this point.
   */

  if (pzm->state == ZMS_SENDING || pzm->state == ZMS_SENDWAIT)
    {
      /* Enter a wait state and see what they want.  Next header *should* be
       * ZRPOS.
       */

      zmdbg("ZMS_STATE %d->%d: Interrupt\n", pzm->state, ZMS_SENDWAIT);

      pzm->state   = ZMS_SENDWAIT;
      pzm->timeout = CONFIG_SYSTEM_ZMODEM_RESPTIME;
    }

  return OK;
}

/****************************************************************************
 * Name: zms_challenge
 *
 * Description:
 *   Answer challenge from receiver
 *
 ****************************************************************************/

static int zms_challenge(FAR struct zm_state_s *pzm)
{
  zmdbg("ZMS_STATE %d\n", pzm->state);
  return zm_sendhexhdr(pzm, ZACK, pzm->hdrdata + 1);
}

/****************************************************************************
 * Name: zms_abort
 *
 * Description:
 *   Receiver has cancelled
 *
 ****************************************************************************/

static int zms_abort(FAR struct zm_state_s *pzm)
{
  zmdbg("ZMS_STATE %d\n", pzm->state);
  return zm_sendhexhdr(pzm, ZFIN, g_zeroes);
}

/****************************************************************************
 * Name: zms_ignore
 *
 * Description:
 *   Ignore the header
 *
 ****************************************************************************/

static int zms_ignore(FAR struct zm_state_s *pzm)
{
  zmdbg("ZMS_STATE %d\n", pzm->state);
  return OK;
}

/****************************************************************************
 * Name: zms_command
 *
 * Description:
 *   Remote command received -- refuse it.
 *
 ****************************************************************************/

static int zms_command(FAR struct zm_state_s *pzm)
{
  uint8_t rbuf[4];

  zmdbg("ZMS_STATE %d\n", pzm->state);

  rbuf[0] = EPERM;
  rbuf[1] = 0;
  rbuf[2] = 0;
  rbuf[3] = 0;
  return zm_sendhexhdr(pzm, ZCOMPL, rbuf);
}

/****************************************************************************
 * Name: zms_message
 *
 * Description:
 *   The remote system wants to put a message on stderr
 *
 ****************************************************************************/

static int zms_message(FAR struct zm_state_s *pzm)
{
  zmdbg("ZMS_STATE %d\n", pzm->state);

  zm_readstate(pzm);
  return OK;
}

/****************************************************************************
 * Name: zms_stderrdata
 *
 * Description:
 *   The remote system wants to put a message on stderr
 *
 ****************************************************************************/

static int zms_stderrdata(FAR struct zm_state_s *pzm)
{
  zmdbg("ZMS_STATE %d\n", pzm->state);

  pzm->pktbuf[pzm->pktlen] = '\0';
  fprintf(stderr, "Message: %s", (FAR char *)pzm->pktbuf);
  return OK;
}

/****************************************************************************
 * Name: zms_initdone
 *
 * Description:
 *   Received ZRINIT, sent ZRINIT, waiting for ZACK, ZACK received.  This
 *   completes the initialization sequence.  Returning ZM_XFRDONE will
 *   alloc zm_initialize() to return.
 *
 ****************************************************************************/

static int zms_initdone(FAR struct zm_state_s *pzm)
{
  zmdbg("ZMS_STATE %d->%d\n", pzm->state, ZMS_DONE);

  pzm->state = ZMS_DONE;
  return ZM_XFRDONE;
}

/****************************************************************************
 * Name: zms_sendzsinit
 *
 * Description:
 *   The remote system wants to put a message on stderr
 *
 ****************************************************************************/

static int zms_sendzsinit(FAR struct zm_state_s *pzm)
{
#ifdef CONFIG_SYSTEM_ZMODEM_SENDATTN
  FAR struct zms_state_s *pzms = (FAR struct zms_state_s *)pzm;
  FAR char *at = (pzms->attn != NULL) ? pzms->attn : "";
#endif
  int ret;

  /* Change to ZMS_INITACK state */

  zmdbg("ZMS_STATE %d->%d\n", pzm->state, ZMS_INITACK);
  pzm->state = ZMS_INITACK;

  /* Send the ZSINIT header (optional)
   *
   * Paragraph 11.3  ZSINIT.  "The Sender sends flags followed by a binary
   * data subpacket terminated with ZCRCW."
   */

  ret = zm_sendbinhdr(pzm, ZSINIT, g_zeroes);
  if (ret >= 0)
    {
      /* Paragraph 11.3 "The data subpacket contains the null terminated
       * Attn sequence, maximum length 32 bytes including the terminating
       * null."
       *
       * We expect a ZACK next.
       */

#ifdef CONFIG_SYSTEM_ZMODEM_SENDATTN
      /* Send the NUL-terminated attention string */

      ret = zm_senddata(pzm, (FAR uint8_t *)at, strlen(at) + 1);
#else
      /* Send a null string */

      ret = zm_senddata(pzm, g_zeroes, 1);
#endif
    }

  return ret;
}

/****************************************************************************
 * Name: zms_sendfilename
 *
 * Description:
 *   Send ZFILE header and filename.  Wait for a response from receiver.
 *
 ****************************************************************************/

static int zms_sendfilename(FAR struct zm_state_s *pzm)
{
  FAR struct zms_state_s *pzms = (FAR struct zms_state_s *)pzm;
  FAR char *ptr = (FAR char *)pzm->scratch;
  int len;
  int ret;

  zmdbg("ZMS_STATE %d->%d\n", pzm->state, ZMS_FILEWAIT);

  pzm->state = ZMS_FILEWAIT;
  ret = zm_sendbinhdr(pzm, ZFILE, pzms->fflags);
  if (ret < 0)
    {
      zmdbg("ERROR: zm_sendbinhdr failed: %d\n", ret);
      return ret;
    }

  /* Paragraph 13:
   *   Pathname
   *     The pathname (conventionally, the file name) is sent as a null
   *     terminated ASCII string.
   */

  len = strlen(pzms->rfilename);
  memcpy(ptr, pzms->rfilename, len + 1);
  ptr += len + 1;

  /* Paragraph 13:
   *
   *   Length
   *     The file length ... is stored as a decimal string counting  the
   *     number of data bytes in the file.
   *   Modification Date
   *     A single space separates the modification date from the file
   *     length. ... The mod date is sent as an octal number giving ...
   *     A date of 0 implies the modification date is unknown and should be
   *     left as the date the file is received.
   *   File Mode
   *     A single space separates the file mode from the modification date.
   *     The file mode is stored as an octal string. Unless the file
   *     originated from a Unix system, the file mode is set to 0.
   *   Serial Number
   *     A single space separates the serial number from the file mode.
   *     The serial number of the transmitting program is stored as an
   *     octal string.  Programs which do not have a serial number should
   *     omit this field, or set it to 0.
   *   Number of Files Remaining
   *     If the number of files remaining is sent, a single space separates
   *     this field from the previous field.  This field is coded as a
   *     decimal number, and includes the current file.
   *   Number of Bytes Remaining
   *     If the number of bytes remaining is sent, a single space
   *     separates this field from the previous field. This field is coded
   *     as a decimal number, and includes the current file
   *   File Type
   *     If the file type is sent, a single space separates this field from
   *     the previous field.  This field is coded as a decimal number.
   *     Currently defined values are:
   *
   *       0  Sequential file - no special type
   *       1  Other types to be defined.
   *
   *   The file information is terminated by a null.
   */

#ifdef CONFIG_SYSTEM_ZMODEM_TIMESTAMPS
  snprintf(ptr, sizeof(pzm->scratch), "%ld %lo 0 %d 1 %ld 0",
           (unsigned long)pzms->filesize, (unsigned long)pzms->timestamp,
           CONFIG_SYSTEM_ZMODEM_SERIALNO, (unsigned long)pzms->filesize);
#else
  snprintf(ptr, sizeof(pzm->scratch), "%ld 0 0 %d 1 %ld 0",
           (unsigned long)pzms->filesize, CONFIG_SYSTEM_ZMODEM_SERIALNO,
           (unsigned long)pzms->filesize);
#endif

  ptr += strlen(ptr);
  *ptr++ = '\0';

  len = ptr - (FAR char *)pzm->scratch;
  DEBUGASSERT(len < CONFIG_SYSTEM_ZMODEM_SNDBUFSIZE);
  return zm_senddata(pzm, pzm->scratch, len);
}

/****************************************************************************
 * Name: zms_fileskip
 *
 * Description:
 *   The entire file has been transferred, ZEOF has been sent to the remote
 *   receiver, and the receiver has returned ZRINIT.  Time to send ZFIN.
 *
 ****************************************************************************/

static int zms_endoftransfer(FAR struct zm_state_s *pzm)
{
  zmdbg("ZMS_STATE %d send ZFIN\n", pzm->state);
  pzm->state = ZMS_FINISH;

  return zm_sendhexhdr(pzm, ZFIN, g_zeroes);
}

/****************************************************************************
 * Name: zms_fileskip
 *
 * Description:
 *   Received ZSKIP, receiver doesn't want this file.
 *
 ****************************************************************************/

static int zms_fileskip(FAR struct zm_state_s *pzm)
{
  FAR struct zms_state_s *pzms = (FAR struct zms_state_s *)pzm;

  zmdbg("ZMS_STATE %d\n", pzm->state);
  close(pzms->infd);
  pzms->infd = -1;
  return ZM_XFRDONE;
}

/****************************************************************************
 * Name: zms_sendfiledata
 *
 * Description:
 *   Send a chunk of file data in response to a ZRPOS.  This may be followed
 *   followed by a ZCRCE, ZCRCG, ZCRCQ or ZCRCW dependinig on protocol flags.
 *
 ****************************************************************************/

static int zms_sendfiledata(FAR struct zm_state_s *pzm)
{
  FAR struct zms_state_s *pzms = (FAR struct zms_state_s *)pzm;

  pzm->flags &= ~ZM_FLAG_WAIT;
  return zms_startfiledata(pzms);
}

/****************************************************************************
 * Name: zms_sendpacket
 *
 * Description:
 *   Send a chunk of file data in response to a ZRPOS.  This may be followed
 *   followed by a ZCRCE, ZCRCG, ZCRCQ or ZCRCW dependinig on protocol flags.
 *
 *   This function is called after ZDATA is send, after previous data was
 *   ACKed, and on certain error conditions where it is necessary to re-send
 *   the file data.
 *
 ****************************************************************************/

static int zms_sendpacket(FAR struct zm_state_s *pzm)
{
  FAR struct zms_state_s *pzms = (FAR struct zms_state_s *)pzm;
  ssize_t nwritten;
  int32_t unacked;
  bool bcrc32;
  uint32_t crc;
  uint8_t by[4];
  uint8_t *ptr;
  uint8_t type;
  bool wait = false;
  int sndsize;
  int pktsize;
  int i;

  /* Loop, sending packets while we can if the receiver supports streaming
   * data.
   */

  do
    {
      /* This is the number of byte left in the file to be sent */

      sndsize = pzms->filesize - pzms->offset;

      /* This is the number of bytes that have been sent but not yet
       * acknowledged.
       */

      unacked = pzms->offset - pzms->lastoffs;

      /* Can we still send?  If so, how much?   If rcvmax is zero, then the
       * remote can handle full streaming and we never have to wait.
       * Otherwise, we have to restrict the total number of unacknowledged
       * bytes to rcvmax.
       */

      zmdbg("sndsize: %d unacked: %d rcvmax: %d\n",
            sndsize, unacked, pzms->rcvmax);

      if (pzms->rcvmax != 0)
        {
          /* If we were to send 'sndsize' more bytes,
           * would that exceed recvmax?
           */

          if (sndsize + unacked > pzms->rcvmax)
            {
              /* Yes... clip the maximum so that we stay within that limit */

              int maximum = pzms->rcvmax - unacked;
              if (sndsize < maximum)
                {
                  sndsize = maximum;
                }

              wait = true;
              zmdbg("Clipped sndsize: %d\n", sndsize);
            }
        }

      /* Can we send anything? */

      if (sndsize <= 0)
        {
          /* No, not now. Keep waiting */

          zmdbg("ZMS_STATE %d->%d\n", pzm->state, ZMS_SENDWAIT);

          pzm->state   = ZMS_SENDWAIT;
          pzm->timeout = CONFIG_SYSTEM_ZMODEM_RESPTIME;
          return OK;
        }

      /* Determine what kind of packet to send
       *
       * ZCRCW:
       *   "If the receiver cannot overlap serial and disk I/O, it uses the
       *    ZRINIT frame to specify a buffer length which the sender will
       *    not overflow.  The sending program sends a ZCRCW data subpacket
       *    and waits for a ZACK header before sending the next segment of
       *    the file.
       *
       * ZCRCG
       *   "A data subpacket terminated by ZCRCG and CRC does not elicit a
       *    response unless an error is detected; more data subpacket(s)
       *    follow immediately."
       *
       * ZCRCQ
       *   "ZCRCQ data subpackets expect a ZACK response with the
       *    receiver's file offset if no error, otherwise a ZRPOS response
       *    with the last good file offset.  Another data subpacket
       *    continues immediately.  ZCRCQ subpackets are not used if the
       *    receiver does not indicate FDX ability with the CANFDX bit.
       */

      if ((pzm->flags & ZM_FLAG_WAIT) != 0)
        {
          type = ZCRCW;
          pzm->flags &= ~ZM_FLAG_WAIT;
        }
      else if (wait)
        {
          type = ZCRCW;
        }
      else
        {
          type = pzms->dpkttype;
        }

      /* Read characters from file and put into buffer until buffer is full
       * or file is exhausted
       */

      bcrc32      = ((pzm->flags & ZM_FLAG_CRC32) != 0);
      crc         = bcrc32 ? 0xffffffff : 0;
      pzm->flags &= ~ZM_FLAG_ATSIGN;

      ptr         = pzm->scratch;
      pktsize     = 0;

#ifdef CONFIG_SYSTEM_ZMODEM_SNDFILEBUF
      /* Read multiple bytes of file and store into the temporal buffer */

      zm_read(pzms->infd, pzm->filebuf, CONFIG_SYSTEM_ZMODEM_SNDBUFSIZE);

      i = 0;
#endif

      while (pktsize <= (CONFIG_SYSTEM_ZMODEM_SNDBUFSIZE - 10) &&
             (pzms->offset < pzms->filesize))
        {
          /* Add the new value to the accumulated CRC */

#ifdef CONFIG_SYSTEM_ZMODEM_SNDFILEBUF
          uint8_t ch = pzm->filebuf[i++];
#else
          uint8_t ch = zm_getc(pzms->infd);
#endif
          if (!bcrc32)
            {
              crc = (uint32_t)crc16part(&ch, 1, (uint16_t)crc);
            }
          else
            {
              crc = crc32part(&ch, 1, crc);
            }

          /* Put the character into the buffer, escaping as necessary */

          ptr = zm_putzdle(pzm, ptr, ch);

          /* Recalculate the accumulated packet size to handle expansion due
           * to escaping.
           */

          pktsize = (int32_t)(ptr - pzm->scratch);

          /* And increment the file offset */

          pzms->offset++;
        }

#ifdef CONFIG_SYSTEM_ZMODEM_SNDFILEBUF
      /* Restore file position to be read next time */

      lseek(pzms->infd, pzms->offset, SEEK_SET);
#endif

      /* If we've reached file end, a ZEOF header will follow.  If there's
       * room in the outgoing buffer for it, end the packet with ZCRCE and
       * append the ZEOF header.  If there isn't room, we'll have to do a
       * ZCRCW
       */

      pzm->flags &= ~ZM_FLAG_EOF;
      if (pzms->offset == pzms->filesize)
        {
          pzm->flags |= ZM_FLAG_EOF;
          if (wait || (pzms->rcvmax != 0 && pktsize < 24))
            {
              type = ZCRCW;
            }
          else
            {
              type = ZCRCE;
            }
        }

      /* Save the ZDLE in the transmit buffer */

      *ptr++ = ZDLE;

      /* Save the type */

      if (!bcrc32)
        {
          crc = (uint32_t)crc16part(&type, 1, (uint16_t)crc);
        }
      else
        {
          crc = crc32part(&type, 1, crc);
        }

      *ptr++ = type;

      /* Update the CRC and put the CRC in the transmit buffer */

      if (!bcrc32)
        {
          ptr = zm_putzdle(pzm, ptr, (crc >> 8) & 0xff);
          ptr = zm_putzdle(pzm, ptr, crc & 0xff);
        }
      else
        {
          /* REVISIT: Why complemented? */

          crc = ~crc;
          for (i = 0; i < 4; i++, crc >>= 8)
            {
              ptr = zm_putzdle(pzm, ptr, crc & 0xff);
            }
        }

      /* Get the final packet size */

      pktsize = ptr - pzm->scratch;
      DEBUGASSERT(pktsize < CONFIG_SYSTEM_ZMODEM_SNDBUFSIZE);

      /* And send the packet */

      zmdbg("Sending %d bytes. New offset: %ld\n",
            pktsize, (unsigned long)pzms->offset);

      nwritten = zm_remwrite(pzm->remfd, pzm->scratch, pktsize);
      if (nwritten < 0)
        {
          zmdbg("ERROR: zm_remwrite failed: %d\n", (int)nwritten);
          return (int)nwritten;
        }

      /* Then do what?   That depends on the type of the transfer */

      switch (type)
        {
          /* That was the last packet.  Send ZCRCE to indicate the end of
           * file.
           */

        case ZCRCE:  /* CRC next, transfer ends, ZEOF follows */
          zmdbg("ZMS_STATE %d->%d: ZCRCE\n", pzm->state, ZMS_SENDEOF);

          pzm->state   = ZMS_SENDEOF;
          pzm->timeout = CONFIG_SYSTEM_ZMODEM_RESPTIME;
          zm_be32toby(pzms->offset, by);
          return zm_sendhexhdr(pzm, ZEOF, by);

        /* We need to want for ZACK */

        case ZCRCW:  /* CRC next, send ZACK, transfer ends */
          if ((pzm->flags & ZM_FLAG_EOF) != 0)
            {
              zmdbg("ZMS_STATE %d->%d: EOF\n", pzm->state, ZMS_SENDDONE);
              pzm->state = ZMS_SENDDONE;
            }
          else
            {
              zmdbg("ZMS_STATE %d->%d: Not EOF\n", pzm->state, ZMS_SENDWAIT);
              pzm->state = ZMS_SENDWAIT;
            }

          pzm->timeout = CONFIG_SYSTEM_ZMODEM_RESPTIME;
          break;

       /* No response is expected -- we are streaming */

        case ZCRCG:  /* Transfer continues non-stop */
        case ZCRCQ:  /* Expect ZACK, transfer may continues non-stop */
        default:
          zmdbg("ZMS_STATE %d->%d: Default\n", pzm->state, ZMS_SENDING);

          pzm->state = ZMS_SENDING;
          break;
        }
    }
#ifdef CONFIG_SYSTEM_ZMODEM_RCVSAMPLE
  while (pzm->state == ZMS_SENDING && !zm_rcvpending(pzm));
#else
  while (0);
#endif

  return OK;
}

/****************************************************************************
 * Name: zms_filecrc
 *
 * Description:
 *   ZFILE has been sent and waiting for ZCRC.  ZRPOS received.
 *
 ****************************************************************************/

static int zms_filecrc(FAR struct zm_state_s *pzm)
{
  FAR struct zms_state_s *pzms = (FAR struct zms_state_s *)pzm;
  uint8_t by[4];
  uint32_t crc;

  crc = zm_filecrc(pzm, pzms->filename);
  zmdbg("ZMS_STATE %d: CRC %08x\n", pzm->state, crc);

  zm_be32toby(crc, by);
  return zm_sendhexhdr(pzm, ZCRC, by);
}

/****************************************************************************
 * Name: zms_sendwaitack
 *
 * Description:
 *   An ACK arrived while waiting to transmit data.  Update last known
 *   receiver offset, and try to send more data.
 *
 ****************************************************************************/

static int zms_sendwaitack(FAR struct zm_state_s *pzm)
{
  FAR struct zms_state_s *pzms = (FAR struct zms_state_s *)pzm;
  uint8_t by[4];
  off_t offset;
  int ret;

  /* Paragraph 11.4  ZACK.  ?Acknowledgment to a ZSINIT frame, ..., ZCRCQ or
   * ZCRCW data subpacket.  ZP0 to ZP3 contain file offset."
   */

  offset = zm_bytobe32(pzm->hdrdata + 1);

  if (offset > pzms->lastoffs)
    {
      pzms->lastoffs = offset;
    }

  zmdbg("ZMS_STATE %d: offset: %ld\n", pzm->state, (unsigned long)offset);

  /* Now send the next data packet */

  zm_be32toby(pzms->offset, by);
  ret = zm_sendbinhdr(pzm, ZDATA, by);
  if (ret != OK)
    {
      return ret;
    }

  return zms_sendpacket(pzm);
}

/****************************************************************************
 * Name: zms_sendnak
 *
 * Description:
 *   ZDATA header was corrupt.  Start from beginning
 *
 ****************************************************************************/

static int zms_sendnak(FAR struct zm_state_s *pzm)
{
  FAR struct zms_state_s *pzms = (FAR struct zms_state_s *)pzm;
  off_t offset;

  /* Save the ZRPOS file offset */

  pzms->offset = pzms->zrpos;

  /* TODO: What is the correct thing to do if lseek fails? Send ZEOF? */

  offset = lseek(pzms->infd, pzms->offset, SEEK_SET);
  if (offset == (off_t)-1)
    {
      int errorcode = errno;

      zmdbg("ERROR: Failed to seek to %ld: %d\n",
            (unsigned long)pzms->offset, errorcode);
      DEBUGASSERT(errorcode > 0);
      return -errorcode;
    }

  zmdbg("ZMS_STATE %d: offset: %ld\n",
        pzm->state, (unsigned long)pzms->offset);

  return zms_sendpacket(pzm);
}

/****************************************************************************
 * Name: zms_sendrpos
 *
 * Description:
 *   Received ZRPOS while sending a file.  Set the new offset and try again.
 *
 ****************************************************************************/

static int zms_sendrpos(FAR struct zm_state_s *pzm)
{
  FAR struct zms_state_s *pzms = (FAR struct zms_state_s *)pzm;

  pzm->nerrors++;
  pzm->flags |= ZM_FLAG_WAIT;
  return zms_startfiledata(pzms);
}

/****************************************************************************
 * Name: zms_senddoneack
 *
 * Description:
 *   ACK arrived after last file packet sent.  Send the ZEOF
 *
 ****************************************************************************/

static int zms_senddoneack(FAR struct zm_state_s *pzm)
{
  FAR struct zms_state_s *pzms = (FAR struct zms_state_s *)pzm;
  uint8_t by[4];
  off_t offset;

  /* Paragraph 11.4  ZACK.  Acknowledgment to a ZSINIT frame, ..., ZCRCQ or
   * ZCRCW data subpacket.  ZP0 to ZP3 contain file offset.
   */

  offset = zm_bytobe32(pzm->hdrdata + 1);

  if (offset > pzms->lastoffs)
    {
      pzms->lastoffs = offset;
    }

  zmdbg("ZMS_STATE %d->%d: offset: %ld\n",
        pzm->state, ZMS_SENDEOF, (unsigned long)pzms->offset);

  /* Now send the ZEOF */

  pzm->state   = ZMS_SENDEOF;
  pzm->timeout = CONFIG_SYSTEM_ZMODEM_RESPTIME;
  zm_be32toby(pzms->offset, by);
  return zm_sendhexhdr(pzm, ZEOF, by);
}

/****************************************************************************
 * Name: zms_resendeof
 *
 * Description:
 *   ACK arrived after last file packet sent.  Send the ZEOF
 *
 ****************************************************************************/

static int zms_resendeof(FAR struct zm_state_s *pzm)
{
  FAR struct zms_state_s *pzms = (FAR struct zms_state_s *)pzm;
  uint8_t by[4];

  zm_be32toby(pzms->offset, by);
  return zm_sendhexhdr(pzm, ZEOF, by);
}

/****************************************************************************
 * Name: zms_xfrdone
 *
 * Description:
 *   Sent ZFIN, received ZFIN in response.  This transfer is complete.
 *   We will not send the "OO" until the last file has been sent.
 *
 ****************************************************************************/

static int zms_xfrdone(FAR struct zm_state_s *pzm)
{
  zmdbg("Transfer complete\n");
  return ZM_XFRDONE;
}

/****************************************************************************
 * Name: zms_finish
 *
 * Description:
 *   Sent ZFIN, received ZNAK or ZRINIT
 *
 ****************************************************************************/

static int zms_finish(FAR struct zm_state_s *pzm)
{
  return ZM_XFRDONE;
}

/****************************************************************************
 * Name: zms_timeout
 *
 * Description:
 *   An timeout occurred in this state
 *
 ****************************************************************************/

static int zms_timeout(FAR struct zm_state_s *pzm)
{
  zmdbg("ERROR: Receiver did not respond\n");
  return -ETIMEDOUT;
}

/****************************************************************************
 * Name: zms_cmdto
 *
 * Description:
 *   Timed out waiting for command or stderr data
 *
 ****************************************************************************/

static int zms_cmdto(FAR struct zm_state_s *pzm)
{
  zmdbg("ERROR: No command received\n");
  return -ETIMEDOUT;
}

/****************************************************************************
 * Name: zms_doneto
 *
 * Description:
 *   Timed out in ZMS_DONE state
 *
 ****************************************************************************/

static int zms_doneto(FAR struct zm_state_s *pzm)
{
  zmdbg("Timeout if ZMS_DONE\n");
  return ZM_XFRDONE;
}

/****************************************************************************
 * Name: zms_error
 *
 * Description:
 *   An unexpected event occurred in this state
 *
 ****************************************************************************/

static int zms_error(FAR struct zm_state_s *pzm)
{
  zmdbg("Unhandled event, header=%d\n", pzm->hdrdata[0]);
  pzm->flags |= ZM_FLAG_WAIT;
  return OK;
}

/****************************************************************************
 * Name: zms_sendfiledata
 *
 * Description:
 *   Send a chunk of file data in response to a ZRPOS.  This may be followed
 *   followed by a ZCRCE, ZCRCG, ZCRCQ or ZCRCW dependinig on protocol flags.
 *
 ****************************************************************************/

static int zms_startfiledata(FAR struct zms_state_s *pzms)
{
  off_t offset;
  int ret;

  zmdbg("ZMS_STATE %d: offset %ld nerrors %d\n",
        pzms->cmn.state, (unsigned long)pzms->offset, pzms->cmn.nerrors);

  /* Paragraph 8.2: "A ZRPOS header from the receiver initiates transmission
   * of the file data starting at the offset in the file specified in the
   * ZRPOS header."
   */

  pzms->zrpos      = zm_bytobe32(pzms->cmn.hdrdata + 1);
  pzms->offset     = pzms->zrpos;
  pzms->lastoffs   = pzms->zrpos;

  /* See to the requested file position */

  offset = lseek(pzms->infd, pzms->offset, SEEK_SET);
  if (offset == (off_t)-1)
    {
      int errorcode = errno;

      zmdbg("ERROR: Failed to seek to %ld: %d\n",
            (unsigned long)pzms->offset, errorcode);
      DEBUGASSERT(errorcode > 0);
      return -errorcode;
    }

  /* Paragraph 8.2: "The sender sends a ZDATA binary header (with file
   * position) followed by one or more data subpackets."
   */

  zmdbg("ZMS_STATE %d: Send ZDATA offset %ld\n",
        pzms->cmn.state, (unsigned long)pzms->offset);

  ret = zm_sendbinhdr(&pzms->cmn, ZDATA, pzms->cmn.hdrdata + 1);
  if (ret != OK)
    {
      return ret;
    }

  return zms_sendpacket(&pzms->cmn);
}

/****************************************************************************
 * Name: zms_sendfile
 *
 * Description:
 *   Begin transmission of a file
 *
 ****************************************************************************/

/* Called by user to begin transmission of a file */

static int zms_sendfile(FAR struct zms_state_s *pzms,
                        FAR const char *filename,
                        FAR const char *rfilename, uint8_t f0, uint8_t f1)
{
  struct stat buf;
  int ret;

  DEBUGASSERT(pzms && filename && rfilename);
  zmdbg("filename: %s rfilename: %s f0: %02x f1: %02x\n",
        filename, rfilename, f0, f1);

  /* TODO: The local file name *must* be an absolute patch for now.  This if
   * environment variables are supported, then any relative paths could be
   * extended using the contents of the current working directory CWD.
   */

  if (filename[0] != '/')
    {
      zmdbg("ERROR: filename must be an absolute path: %s\n", filename);
      return -ENOSYS;
    }

  /* Get information about the local file */

  ret = stat(filename, &buf);
  if (ret < 0)
    {
      int errorcode = errno;
      DEBUGASSERT(errorcode > 0);
      zmdbg("Failed to stat %s: %d\n", filename, errorcode);
      return -errorcode;
    }

  /* Open the local file for reading */

  pzms->infd = open(filename, O_RDONLY);
  if (pzms->infd < 0)
    {
      int errorcode = errno;
      DEBUGASSERT(errorcode > 0);
      zmdbg("Failed to open %s: %d\n", filename, errorcode);
      return -errorcode;
    }

  /* Initialize for the transfer */

  pzms->cmn.flags &= ~ZM_FLAG_EOF;
  pzms->filename   = filename;
  pzms->rfilename  = rfilename;
  DEBUGASSERT(pzms->filename && pzms->rfilename);

  pzms->fflags[3]  = f0;
  pzms->fflags[2]  = f1;
  pzms->fflags[1]  = 0;
  pzms->fflags[0]  = 0;
  pzms->offset     = 0;
  pzms->lastoffs   = 0;

  pzms->filesize   = buf.st_size;
#ifdef CONFIG_SYSTEM_ZMODEM_TIMESTAMPS
  pzms->timestamp  = buf.st_mtime;
#endif

  zmdbg("ZMS_STATE %d->%d\n", pzms->cmn.state, ZMS_FILEWAIT);

  pzms->cmn.state  = ZMS_FILEWAIT;

  zmdbg("ZMS_STATE %d: Send ZFILE(%s)\n", pzms->cmn.state, pzms->rfilename);
  return zms_sendfilename(&pzms->cmn);
}

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

/****************************************************************************
 * Name: zms_initialize
 *
 * Description:
 *   Initialize for Zmodem send operation.  The overall usage is as follows:
 *
 *   1) Call zms_initialize() to create the partially initialized struct
 *      zms_state_s instance.
 *   2) Make any custom settings in the struct zms_state_s instance.
 *   3) Create a stream instance to get the "file" to transmit and add
 *      the filename to pzms
 *   4) Call zms_send() to transfer the file.
 *   5) Repeat 3) and 4) to transfer as many files as desired.
 *   6) Call zms_release() when the final file has been transferred.
 *
 * Input Parameters:
 *   remfd - The R/W file/socket descriptor to use for communication with the
 *      remote peer.
 *
 ****************************************************************************/

ZMSHANDLE zms_initialize(int remfd)
{
  FAR struct zms_state_s *pzms;
  FAR struct zm_state_s *pzm;
  ssize_t nwritten;
  int ret;

  DEBUGASSERT(remfd >= 0);

  /* Allocate the instance */

  pzms = (FAR struct zms_state_s *)zalloc(sizeof(struct zms_state_s));
  if (pzms)
    {
      pzm            = &pzms->cmn;
      pzm->evtable   = g_zms_evtable;
      pzm->state     = ZMS_START;
      pzm->pstate    = PSTATE_IDLE;
      pzm->psubstate = PIDLE_ZPAD;
      pzm->remfd     = remfd;

      /* Create a timer to handle timeout events */

      ret = zm_timerinit(pzm);
      if (ret < 0)
        {
          zmdbg("ERROR: zm_timerinit failed: %d\n", ret);
          goto errout;
        }

      /* Send "rz\r" to the remote end.
       *
       * Paragraph 8.1: "The sending program may send the string "rz\r" to
       * invoke the receiving program from a possible command mode.  The
       * "rz" followed by carriage return activates a ZMODEM receive program
       * or command if it were not already active.
       */

      nwritten = zm_remwrite(pzm->remfd, (FAR uint8_t *)"rz\r", 3);
      if (nwritten < 0)
        {
          zmdbg("ERROR: zm_remwrite failed: %d\n", (int)nwritten);
          goto errout_with_timer;
        }

      /* Send ZRQINIT
       *
       * Paragraph 8.1: "Then the sender may send a ZRQINIT header.  The
       * ZRQINIT header causes a previously started receive program to send
       * its ZRINIT header without delay."
       */

      ret = zm_sendhexhdr(&pzms->cmn, ZRQINIT, g_zeroes);
      if (ret < 0)
        {
          zmdbg("ERROR: zm_sendhexhdr failed: %d\n", ret);
          goto errout_with_timer;
        }

      /* Set up a timeout for the response */

      pzm->timeout = CONFIG_SYSTEM_ZMODEM_RESPTIME;
      zmdbg("ZMS_STATE %d sent ZRQINIT\n", pzm->state);

      /* Now drive the state machine by reading data from the remote peer
       * and providing that data to the parser.  zm_datapump runs until an
       * irrecoverable error is detected or until the ZRQINIT is ACK-ed by
       * the remote receiver.
       */

      ret = zm_datapump(&pzms->cmn);
      if (ret < 0)
        {
          zmdbg("ERROR: zm_datapump failed: %d\n", ret);
          goto errout_with_timer;
        }
    }

  return (ZMSHANDLE)pzms;

errout_with_timer:
  zm_timerrelease(&pzms->cmn);
errout:
  free(pzms);
  return NULL;
}

/****************************************************************************
 * Name: zms_send
 *
 * Description:
 *   Send a file.
 *
 * Input Parameters:
 *   handle - Handle previoulsy returned by xms_initialize()
 *   filename - The name of the local file to transfer
 *   rfilename - The name of the remote file name to create
 *   option - Describes optional transfer behavior
 *   f1 - The F1 transfer flags
 *   skip - True:  Skip if file not present at receiving end.
 *
 * Assumptions:
 *   The filename and rfilename pointers refer to memory that will persist
 *   at least until the transfer completes.
 *
 ****************************************************************************/

int zms_send(ZMSHANDLE handle, FAR const char *filename,
             FAR const char *rfilename, enum zm_xfertype_e xfertype,
             enum zm_option_e option,  bool skip)
{
  FAR struct zms_state_s *pzms = (FAR struct zms_state_s *)handle;
  uint8_t f1;
  int ret;

  /* At this point either (1) zms_intiialize() has just been called or
   * (2) the previous file has been sent.
   */

  DEBUGASSERT(pzms && filename && rfilename && pzms->cmn.state == ZMS_DONE);

  /* Set the ZMSKNOLOC option is so requested */

  f1 = option;
  if (skip)
    {
      f1 |= ZMSKNOLOC;
    }

  /* Initiate sending of the file.  This will open the source file,
   * initialize data structures and set the ZMSS_FILEWAIT state.
   */

  ret = zms_sendfile(pzms, filename, rfilename, (uint8_t)xfertype, f1);
  if (ret < 0)
    {
      zmdbg("ERROR: zms_sendfile failed: %d\n", ret);
      return ret;
    }

  /* Now drive the state machine by reading data from the remote peer
   * and providing that data to the parser.  zm_datapump runs until an
   * irrecoverable error is detected or until the file is sent correctly.
   */

  return zm_datapump(&pzms->cmn);
}

/****************************************************************************
 * Name: zms_release
 *
 * Description:
 *   Called by the user when there are no more files to send.
 *
 ****************************************************************************/

int zms_release(ZMSHANDLE handle)
{
  FAR struct zms_state_s *pzms = (FAR struct zms_state_s *)handle;
  ssize_t nwritten;
  int ret = OK;

  /* Send "OO" */

  nwritten = zm_remwrite(pzms->cmn.remfd, (FAR const uint8_t *)"OO", 2);
  if (nwritten < 0)
    {
      zmdbg("ERROR: zm_remwrite failed: %d\n", (int)nwritten);
      ret = (int)nwritten;
    }

  /* Release the timer resources */

  zm_timerrelease(&pzms->cmn);

  /* Make sure that the file is closed */

  if (pzms->infd)
    {
      close(pzms->infd);
    }

  /* And free the Zmodem state structure */

  free(pzms);
  return ret;
}