Xmodem function source code

Contributed by Thuffir: XMODEM-1K improvement, chunked transfert on both transmit and receive and binary transfer mode.

without html formatting: source code for xmodem.c

  /* SPDX-License-Identifier: BSD-3-Clause */
  /*
   * Copyright 2001-2022 Georges Menie (www.menie.org)
   *
   * 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 of the copyright holder 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 HOLDER 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.
   */
   /* Modified by Thuffir in 2019 */

   /* this code needs standard functions memcpy() and memset()
      and input/output functions _inbyte() and _outbyte().

      the prototypes of the input/output functions are:
        int _inbyte(unsigned short timeout); // msec timeout
        void _outbyte(int c);

    */

   #include "crc16.h"

   #define SOH  0x01
   #define STX  0x02
   #define EOT  0x04
   #define ACK  0x06
   #define NAK  0x15
   #define CAN  0x18
   #define CTRLZ 0x1A

   #define DLY_1S 1000
   #define MAXRETRANS 25

   static int check(int crc, const unsigned char *buf, int sz)
   {
       if (crc) {
           unsigned short crc = crc16_ccitt(buf, sz);
           unsigned short tcrc = (buf[sz]<<8)+buf[sz+1];
           if (crc == tcrc)
               return 1;
       }
       else {
           int i;
           unsigned char cks = 0;
           for (i = 0; i < sz; ++i) {
               cks += buf[i];
           }
           if (cks == buf[sz])
           return 1;
       }

       return 0;
   }

   static void flushinput(void)
   {
       while (_inbyte(((DLY_1S)*3)>>1) >= 0)
           ;
   }

   /*
    * XMODEM Receive
    */
   int xmodemReceive(
       /* Function pointer for storing the received chunks or NULL*/
       void (*storeChunk)(
           /* Pointer to the function context (can be used for anything) */
           void *funcCtx,
           /* Pointer to the XMODEM receive buffer (store data from here) */
           void *xmodemBuffer,
           /* Number of bytes received in the XMODEM buffer (and to be stored) */
           int xmodemSize),
       /* If storeChunk is NULL, pointer to the buffer to store the received data, else function context pointer to pass to storeChunk() */
       void *ctx,
       /* Number of bytes to receive */
       int destsz)
   {
       unsigned char xbuff[1030]; /* 1024 for XModem 1k + 3 head chars + 2 crc + nul */
       unsigned char *p;
       int bufsz, crc = 0;
       unsigned char trychar = 'C';
       unsigned char packetno = 1;
       int i, c, len = 0;
       int retry, retrans = MAXRETRANS;

       for(;;) {
           for( retry = 0; retry < 16; ++retry) {
               if (trychar) _outbyte(trychar);
               if ((c = _inbyte((DLY_1S)<<1)) >= 0) {
                   switch (c) {
                   case SOH:
                       bufsz = 128;
                       goto start_recv;
                   case STX:
                       bufsz = 1024;
                       goto start_recv;
                   case EOT:
                       flushinput();
                       _outbyte(ACK);
                       return len; /* normal end */
                   case CAN:
                       if ((c = _inbyte(DLY_1S)) == CAN) {
                           flushinput();
                           _outbyte(ACK);
                           return -1; /* canceled by remote */
                       }
                       break;
                   default:
                       break;
                   }
               }
           }
           if (trychar == 'C') { trychar = NAK; continue; }
           flushinput();
           _outbyte(CAN);
           _outbyte(CAN);
           _outbyte(CAN);
           return -2; /* sync error */

       start_recv:
           if (trychar == 'C') crc = 1;
           trychar = 0;
           p = xbuff;
           *p++ = c;
           for (i = 0;  i < (bufsz+(crc?1:0)+3); ++i) {
               if ((c = _inbyte(DLY_1S)) < 0) goto reject;
               *p++ = c;
           }

           if (xbuff[1] == (unsigned char)(~xbuff[2]) &&
               (xbuff[1] == packetno || xbuff[1] == (unsigned char)packetno-1) &&
               check(crc, &xbuff[3], bufsz)) {
               if (xbuff[1] == packetno)	{
                   register int count = destsz - len;
                   if (count > bufsz) count = bufsz;
                   if (count > 0) {
                       if(storeChunk) {
                           storeChunk(ctx, &xbuff[3], count);
                       }
                       else {
                           memcpy (&((unsigned char *)ctx)[len], &xbuff[3], count);
                       }
                       len += count;
                   }
                   ++packetno;
                   retrans = MAXRETRANS+1;
               }
               if (--retrans <= 0) {
                   flushinput();
                   _outbyte(CAN);
                   _outbyte(CAN);
                   _outbyte(CAN);
                   return -3; /* too many retry error */
               }
               _outbyte(ACK);
               continue;
           }
       reject:
           flushinput();
           _outbyte(NAK);
       }
   }

   /*
    * XMODEM Transmit
    */
   int xmodemTransmit(
       /* Function pointer for fetching the data chunks to be sent or NULL*/
       void (*fetchChunk)(
           /* Pointer to the function context (can be used for anything) */
           void *funcCtx,
           /* Pointer to the XMODEM send buffer (fetch data into here) */
           void *xmodemBuffer,
           /* Number of bytes that should be fetched (and stored into the XMODEM send buffer) */
           int xmodemSize),
       /* If fetchChunk is NULL, pointer to the buffer to be sent, else function context pointer to pass to fetchChunk() */
       void *ctx,
       /* Number of bytes to send */
       int srcsz,
       /* If nonzero 1024 byte blocks are used (XMODEM-1K) */
       int onek,
       /* If nonzero binary mode is active (do not append CTRLZ to the end of data) */
       int binary)
   {
       unsigned char xbuff[1030]; /* 1024 for XModem 1k + 3 head chars + 2 crc + nul */
       int bufsz, crc = -1;
       unsigned char packetno = 1;
       int i, c, len = 0;
       int retry;

       for(;;) {
           for( retry = 0; retry < 16; ++retry) {
               if ((c = _inbyte((DLY_1S)<<1)) >= 0) {
                   switch (c) {
                   case 'C':
                       crc = 1;
                       goto start_trans;
                   case NAK:
                       crc = 0;
                       goto start_trans;
                   case CAN:
                       if ((c = _inbyte(DLY_1S)) == CAN) {
                           _outbyte(ACK);
                           flushinput();
                           return -1; /* canceled by remote */
                       }
                       break;
                   default:
                       break;
                   }
               }
           }
           _outbyte(CAN);
           _outbyte(CAN);
           _outbyte(CAN);
           flushinput();
           return -2; /* no sync */

           for(;;) {
           start_trans:
               if(onek) {
                   xbuff[0] = STX; bufsz = 1024;
               }
               else {
                   xbuff[0] = SOH; bufsz = 128;
               }
               xbuff[1] = packetno;
               xbuff[2] = ~packetno;
               c = srcsz - len;
               if (c > bufsz) c = bufsz;
               if ((c > 0) || (!binary && (c == 0))) {
                   memset (&xbuff[3], 0, bufsz);
                   if (!binary && (c == 0)) {
                       xbuff[3] = CTRLZ;
                   }
                   else {
                       if(fetchChunk) {
                           fetchChunk(ctx, &xbuff[3], c);
                       }
                       else {
                           memcpy (&xbuff[3], &((unsigned char *)ctx)[len], c);
                       }
                       if (!binary && (c < bufsz)) xbuff[3+c] = CTRLZ;
                   }
                   if (crc) {
                       unsigned short ccrc = crc16_ccitt(&xbuff[3], bufsz);
                       xbuff[bufsz+3] = (ccrc>>8) & 0xFF;
                       xbuff[bufsz+4] = ccrc & 0xFF;
                   }
                   else {
                       unsigned char ccks = 0;
                       for (i = 3; i < bufsz+3; ++i) {
                           ccks += xbuff[i];
                       }
                       xbuff[bufsz+3] = ccks;
                   }
                   for (retry = 0; retry < MAXRETRANS; ++retry) {
                       for (i = 0; i < bufsz+4+(crc?1:0); ++i) {
                           _outbyte(xbuff[i]);
                       }
                       if ((c = _inbyte(DLY_1S)) >= 0 ) {
                           switch (c) {
                           case ACK:
                               ++packetno;
                               len += bufsz;
                               goto start_trans;
                           case CAN:
                               if ((c = _inbyte(DLY_1S)) == CAN) {
                                   _outbyte(ACK);
                                   flushinput();
                                   return -1; /* canceled by remote */
                               }
                               break;
                           case NAK:
                           default:
                               break;
                           }
                       }
                   }
                   _outbyte(CAN);
                   _outbyte(CAN);
                   _outbyte(CAN);
                   flushinput();
                   return -4; /* xmit error */
               }
               else {
                   for (retry = 0; retry < 10; ++retry) {
                       _outbyte(EOT);
                       if ((c = _inbyte((DLY_1S)<<1)) == ACK) break;
                   }
                   flushinput();
                   return (c == ACK)?len:-5;
               }
           }
       }
   }

   #ifdef TEST_XMODEM_RECEIVE
   int main(void)
   {
       int st;

       printf ("Send data using the xmodem protocol from your terminal emulator now...\n");
       /* the following should be changed for your environment:
          0x30000 is the download address,
          65536 is the maximum size to be written at this address
        */
       st = xmodemReceive(NULL, (char *)0x30000, 65536);
       if (st < 0) {
           printf ("Xmodem receive error: status: %d\n", st);
       }
       else  {
           printf ("Xmodem successfully received %d bytes\n", st);
       }

       return 0;
   }
   #endif
   #ifdef TEST_XMODEM_SEND
   int main(void)
   {
       int st;

       printf ("Prepare your terminal emulator to receive data now...\n");
       /* the following should be changed for your environment:
          0x30000 is the download address,
          12000 is the maximum size to be send from this address
        */
       st = xmodemTransmit(NULL, (char *)0x30000, 12000, 0, 0);
       if (st < 0) {
           printf ("Xmodem transmit error: status: %d\n", st);
       }
       else  {
           printf ("Xmodem successfully transmitted %d bytes\n", st);
       }

       return 0;
   }
   #endif