summaryrefslogblamecommitdiff
path: root/apps/system/zmodem/zm_send.c
blob: 7ddd4d5db1325c6950f99c07f1cf85aec7add2bb (plain) (tree)
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
































































                                                                              
                        































                                                                              

                                                               


















                                                                          
                                                                          













                                                                              
                                                  














































                                                                               
                                                   
                                                   













                                                                         
                                                     
 
                                                   

                                                       
                                                   












                                                                               
                                                   



















                                                                         
                                                   

















                                                                           
                                                     
































                                                                         
                                                   

























                                                                          
                                                   














                                                                      
                                                   












                                                                    
                                                   






                                                                   
                                               
 
                                                   


















                                                                    











                                                                                    






                                                                              
                   






                                                                              
                                                 




























                                                                            





























                                                                         

                                                                              
                             
     
     

                                                                            

                                                                              


                                                         
     
      



                                      
     
                             












                                      

                                                








                                                                             


                                                                         





                                                                              

















                                                                             












































































                                                                              
 














                                                                              
 

                                                     















                                                                              
 


























































































                                                                              
    










                                                                           
                                                                  














                                                                            
                                                                      










































































































                                                                              

                                                                          

     
    
     
                                                                  
 
                                              
 
                                                                                    
 
                                              
 




                                                                           
 

                                                   
 


                                                                                   
 


                                                                             
 




                                                   
 



                                                      
 


                                 
         
                                         
 




                                                                
         
 




















                                                                           
 












                                           
 


                                                                           
 


                                                        
 

                                 
 



                                                                 
 








                                                               
 
                                                                        
 
                                         
 


                                                                            
 
                                                  
 
                                             
 

                         
 



                                                                          
         
 












                                                          
 
                                                 
 
                    
 


                         
         
                                                             


          
                                         
         
 
                    
 
                                                                 
 
                  
         

                                                                

                                                 







                                                     
 
                                     
 

                                                             
 
                               
 

                                                  
 





                                                                  
 
                                                                    
 




                                                                         
 

                                                                      
 



                                                       
 
                                      
 










                                                                             
 

                                                       
 
                                                        
 



                                                                        
 


                                   
     
                                     
                                                           


            






































                                                                              
                                                                      





























                                                                                

                                                                            










































































































































































































































































































































































































                                                                                
 























































































































































                                                                              






                                         




                                           
/****************************************************************************
 * system/zmodem/zm_send.c
 *
 *   Copyright (C) 2013 Gregory Nutt. All rights reserved.
 *   Author: Gregory Nutt <gnutt@nuttx.org>
 *
 * References:
 *   "The ZMODEM Inter Application File Transfer Protocol", Chuck Forsberg,
 *    Omen Technology Inc., October 14, 1988
 *
 *    This is an original work, but I want to make sure that credit is given
 *    where due:  Parts of the state machine design were inspired by the
 *    Zmodem library of Edward A. Falk, dated January, 1995.  License
 *    unspecified.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 * 3. Neither the name NuttX nor the names of its contributors may be
 *    used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 ****************************************************************************/

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include <nuttx/config.h>

#include <sys/types.h>
#include <sys/stat.h>

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

#include <nuttx/ascii.h>
#include <apps/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_sendack(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_sendack},
  {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 */

  rcaps &= ~(ZM_FLAG_CRC32 | ZM_FLAG_ESCCTRL);
  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 to 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, );
      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", (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 uint8_t *ptr = 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
   *     Iff 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
   *     Iff 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
   *     Iff 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
  sprintf((FAR char *)ptr, "%ld %lo 0 %d 1 %ld 0",
          (unsigned long)pzms->filesize, (unsigned long)pzms->timestamp,
          CONFIG_SYSTEM_ZMODEM_SERIALNO, (unsigned long)pzms->filesize);
#else
  sprintf((FAR char *)ptr, "%ld 0 0 %d 1 %ld 0",
          (unsigned long)pzms->filesize, CONFIG_SYSTEM_ZMODEM_SERIALNO,
          (unsigned long)pzms->filesize);
#endif

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

  len =  ptr - 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 ret;
  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 nubmer of bytes that have been sent but not yet ackowledged. */

      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;

      while (pktsize <= (CONFIG_SYSTEM_ZMODEM_SNDBUFSIZE - 10) &&
             (ret = zm_getc(pzms->infd)) != EOF)
        {
          /* Add the new value to the accumulated CRC */

          uint8_t ch = (uint8_t)ret;
          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++;
        }

      /* 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 (ret == EOF)
        {
          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)
        {
          crc = (uint32_t)crc16part(g_zeroes, 2, (uint16_t)crc);
          ptr = zm_putzdle(pzm, ptr, (crc >> 8) & 0xff);
          ptr = zm_putzdle(pzm, ptr, crc & 0xff);
        }
      else
        {
          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_sendack
 *
 * Description:
 *   An ACK arrived while transmitting data.  Update last known receiver
 *   offset, and try to send more data.
 *
 ****************************************************************************/

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

  /* Paragraph 11.4  ZACK.  Acknowledgment to a ZSINIT , ..., 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)pzms->offset);
  return OK;
}

/****************************************************************************
 * 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 pathes 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, (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:
  (void)zm_timerrelease(&pzms->cmn);
errout:
  free(pzms);
  return (ZMSHANDLE)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 */

  (void)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;
}