/**************************************************************************** * system/zmodem/zm_state.c * * Copyright (C) 2013 Gregory Nutt. All rights reserved. * Author: Gregory Nutt * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 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); }