* apps/system/zmodem/zm_state.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.
*
****************************************************************************/
* "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 <ctype.h>
#include <ctype.h>
#include <fcntl.h>
#include <sched.h>
#include <assert.h>
#include <errno.h>
#include <nuttx/crc16.h>
#include <nuttx/crc32.h>
#include <nuttx/ascii.h>
#include <nuttx/sched.h>
#include "zm.h"
* Pre-processor Definitions
****************************************************************************/
* Private Types
****************************************************************************/
* Private Function Prototypes
****************************************************************************/
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);
* transition table. NOTE that each state table must be terminated 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)
{
ptr++;
}
zmdbg("Transition ZM[R|S]_state %d->%d discard: %d action: %p\n",
pzm->state, ptr->next, ptr->bdiscard, ptr->action);
pzm->state = ptr->next;
if (ptr->bdiscard)
{
pzm->rcvlen = 0;
pzm->rcvndx = 0;
}
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);
pzm->pstate = PSTATE_IDLE;
pzm->psubstate = PIDLE_ZPAD;
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);
pzm->pstate = PSTATE_IDLE;
pzm->psubstate = PIDLE_ZPAD;
if (pzm->hdrfmt == ZBIN32)
{
uint32_t crc;
* 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;
* 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);
pzm->pstate = PSTATE_IDLE;
pzm->psubstate = PIDLE_ZPAD;
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;
}
* 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;
}
* the 2- byte checksum.
*/
pzm->pktlen -= 3;
}
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)
{
case ZPAD:
{
zmdbg("PSTATE %d:%d->%d:%d\n",
pzm->pstate, pzm->psubstate, pzm->pstate, PIDLE_ZDLE);
pzm->psubstate = PIDLE_ZDLE;
}
break;
case ZDLE:
* 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;
}
* 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':
* case if not.
*/
if ((pzm->flags & ZM_FLAG_OO) != 0)
{
if (pzm->psubstate == PIDLE_OO)
{
* 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
{
zmdbg("PSTATE %d:%d->%d:%d\n",
pzm->pstate, pzm->psubstate, pzm->pstate, PIDLE_OO);
pzm->psubstate = PIDLE_OO;
}
break;
}
* 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)
{
* escaped.
*/
if (ch == ZDLE && (pzm->flags & ZM_FLAG_ESC) == 0)
{
pzm->flags |= ZM_FLAG_ESC;
return OK;
}
if ((pzm->flags & ZM_FLAG_ESC) != 0)
{
switch (ch)
{
case ZRUB0:
ch = ASCII_DEL;
break;
case ZRUB1:
ch = 0xff;
break;
default:
ch ^= 0x40;
break;
}
pzm->flags &= ~ZM_FLAG_ESC;
}
* PSTATE_HEADER substate.
*/
switch (pzm->psubstate)
{
case PHEADER_FORMAT:
{
switch (ch)
{
case ZHEX:
case ZBIN:
case ZBIN32:
{
* data payload beginning with the header type.
*/
pzm->hdrfmt = ch;
pzm->psubstate = PHEADER_PAYLOAD;
pzm->hdrndx = 0;
}
break;
default:
{
return zm_nakhdr(pzm);
}
}
}
break;
case PHEADER_PAYLOAD:
{
int ndx = pzm->hdrndx;
switch (pzm->hdrfmt)
{
case ZHEX:
{
if (!isxdigit(ch))
{
return zm_nakhdr(pzm);
}
* is not incremented.
*/
pzm->hdrdata[ndx] = zm_decnibble(ch) << 4;
pzm->psubstate = PHEADER_LSPAYLOAD;
}
break;
case ZBIN:
case ZBIN32:
{
pzm->hdrdata[ndx] = ch;
ndx++;
*
* 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
{
pzm->psubstate = PHEADER_PAYLOAD;
pzm->hdrndx = ndx;
}
}
break;
default:
break;
}
}
break;
case PHEADER_LSPAYLOAD:
{
int ndx = pzm->hdrndx;
if (pzm->hdrfmt == ZHEX && isxdigit(ch))
{
pzm->hdrdata[ndx] |= zm_decnibble(ch);
ndx++;
* of the sequence is 1+4+2 = 7 bytes.
*/
if (ndx >= 7)
{
return zm_hdrevent(pzm);
}
else
{
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 expects 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;
* escaped. Escaped characters may appear anywhere within the data packet.
*/
if (ch == ZDLE && (pzm->flags & ZM_FLAG_ESC) == 0)
{
pzm->flags |= ZM_FLAG_ESC;
return OK;
}
if (pzm->pktlen >= ZM_PKTBUFSIZE)
{
zmdbg("ERROR: The packet buffer is full\n");
zmdbg(" ch=%c[%02x] pktlen=%d pkttype=%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;
}
if ((pzm->flags & ZM_FLAG_ESC) != 0)
{
switch (ch)
{
* substate.
*/
case ZCRCW:
case ZCRCE:
case ZCRCG:
case ZCRCQ:
{
* indicates the number 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;
case ZRUB0:
ch = ASCII_DEL;
break;
case ZRUB1:
ch = 0xff;
break;
default:
ch ^= 0x40;
break;
}
pzm->flags &= ~ZM_FLAG_ESC;
}
* 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)
{
* Check the CRC and post the event
*/
ret = zm_dataevent(pzm);
* packet data.
*/
pzm->pktlen = 0;
pzm->ncrc = 0;
return ret;
}
else if (pzm->ncrc > 1)
{
* 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);
* This is only so that deeply nested logic can use these values.
*/
pzm->rcvlen = rcvlen;
pzm->rcvndx = 0;
* data is discarded.
*/
while (pzm->rcvndx < pzm->rcvlen)
{
ch = pzm->rcvbuf[pzm->rcvndx];
pzm->rcvndx++;
* 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
{
pzm->ncan = 0;
}
if (ch != ASCII_XON && ch != ASCII_XOFF)
{
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;
default:
zmdbg("ERROR: Invalid state: %d\n", pzm->pstate);
ret = -EINVAL;
break;
}
* 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;
}
}
}
* 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;
* returned by the parser.
*/
do
{
* must anticipate a timeout because we can never be sure that the peer
* is still responding.
*/
sched_lock();
zm_timerstart(pzm, pzm->timeout);
* <= 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);
zm_timerstop(pzm);
sched_unlock();
* somehow.
*/
if (nread == 0)
{
zmdbg("ERROR: Unexpected end-of-file\n");
return -ENOTCONN;
}
else if (nread < 0)
{
int errorcode = errno;
* 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)
{
if ((pzm->flags & ZM_FLAG_TIMEOUT) != 0)
{
ret = zm_timeout(pzm);
}
}
else
{
* in those cases.
*/
zmdbg("ERROR: read failed: %d\n", errorcode);
return -errorcode;
}
}
* zm_parse() will return a non-zero value if we need to terminate
* the loop (with a negative value indicating a failure).
*/
else
{
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);
}