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