/*********************************************************************************************************************** PicoMite MMBasic XModem.c Geoff Graham, Peter Mather Copyright (c) 2021, All rights reserved. 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. The name MMBasic be used when referring to the interpreter in any documentation and promotional material and the original copyright message be displayed on the console at startup (additional copyright messages may be added). 4. All advertising materials mentioning features or use of this software must display the following acknowledgement: This product includes software developed by the . 5. Neither the name of the 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 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 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. ************************************************************************************************************************/ /** * @file XModem.c * @author Geoff Graham, Peter Mather * @brief Source for the MMBasic XMODEM command */ /** * @cond * The following section will be excluded from the documentation. */ #include "MMBasic_Includes.h" #include "Hardware_Includes.h" void xmodemTransmit(char *p, int fnbr); void xmodemReceive(char *sp, int maxbytes, int fnbr, int crunch); int FindFreeFileNbr(void); bool rcvnoint; /* @endcond */ void MIPS16 cmd_xmodem(void) { char *buf, BreakKeySave, *p, *fromp; int rcv = 0, fnbr, crunch = false; char *fname; ClearExternalIO(); if(toupper(*cmdline) == 'R') rcv = true; else if(toupper(*cmdline) == 'S') rcv = false; else if(toupper(*cmdline) == 'C') crunch = rcv = true; else error("Syntax"); while(isalpha(*cmdline)) cmdline++ ; // find the filename (if it is there) skipspace(cmdline); BreakKeySave = BreakKey; BreakKey = 0; if(*cmdline == 0 || *cmdline == '\'') { // no file name, so this is a transfer to/from program memory if(CurrentLinePtr) error("Invalid in a program"); if(Option.DISPLAY_TYPE>=VIRTUAL && WriteBuf)FreeMemorySafe((void **)&WriteBuf); if(rcv)ClearProgram(true); // we need all the RAM else { closeframebuffer('A'); CloseAudio(1); ClearVars(0,true); } buf = GetTempMemory(EDIT_BUFFER_SIZE); if(rcv) { xmodemReceive(buf, EDIT_BUFFER_SIZE, 0, crunch); ClearSavedVars(); // clear any saved variables SaveProgramToFlash((unsigned char *)buf, true); } else { int nbrlines = 0; // we must copy program memory into RAM expanding tokens as we go fromp = (char *)ProgMemory; p = buf; // the RAM buffer while(1) { if(*fromp == T_NEWLINE) { fromp = (char *)llist((unsigned char *)p, (unsigned char *)fromp); // expand the line into the buffer nbrlines++; if(!(nbrlines==1 && p[0]=='\'' && p[1]=='#')){ p += strlen(p); if((p - buf) > (EDIT_BUFFER_SIZE - STRINGSIZE)) error("Not enough memory"); *p++ = '\n'; *p = 0; // terminate that line } } if(fromp[0] == 0 || fromp[0] == 0xff) break; // finally, is it the end of the program? } --p; *p = 0; // erase the last line terminator xmodemTransmit(buf, 0); // send it off } } else { // this is a transfer to/from the SD card if(crunch) error("Invalid command"); if(!InitSDCard()) return; fnbr = FindFreeFileNbr(); fname = (char *)getFstring(cmdline); // get the file name if(Option.SerialConsole){ rcvnoint=true; uart_set_irq_enables((Option.SerialConsole & 3)==1 ? uart0 : uart1, false, false); } else rcvnoint=false; if(rcv) { if(!BasicFileOpen(fname, fnbr, FA_WRITE | FA_CREATE_ALWAYS)) return; xmodemReceive(NULL, 0, fnbr, false); if(rcvnoint)uart_set_irq_enables((Option.SerialConsole & 3)==1 ? uart0 : uart1, true, false); } else { if(!BasicFileOpen(fname, fnbr, FA_READ)) return; xmodemTransmit(NULL, fnbr); if(rcvnoint)uart_set_irq_enables((Option.SerialConsole & 3)==1 ? uart0 : uart1, true, false); } FileClose(fnbr); } BreakKey = BreakKeySave; cmdline=NULL; do_end(false); longjmp(mark, 1); // jump back to the input prompt } /* * @cond * The following section will be excluded from the documentation. */ int _inbyte(int timeout) { int c; uint64_t timer=time_us_64()+timeout*1000; if(!rcvnoint){ while(time_us_64() < timer) { c = getConsole(); if(c != -1) { return c; } } } else { while(time_us_64() < timer && !uart_is_readable((Option.SerialConsole & 3)==1 ? uart0 : uart1)) {} if(time_us_64() < timer) return uart_getc((Option.SerialConsole & 3)==1 ? uart0 : uart1); } return -1; } char _outbyte(char c, int f){ if(!rcvnoint)SerialConsolePutC(c,f); else uart_putc_raw((Option.SerialConsole & 3)==1 ? uart0 : uart1, c); return c; } // for the MX470 we don't want any XModem data echoed to the LCD panel //#define _outbyte(c,d) SerialConsolePutC(c,d) /*********************************************************************************************** the xmodem protocol ************************************************************************************************/ /* derived from the work of Georges Menie (www.menie.org) Copyright 2001-2010 Georges Menie * very much debugged and changed * * this is just the basic XModem protocol (no 1K blocks, crc, etc). It has been tested on * Terra Term and is intended for use with that software. */ #define SOH 0x01 #define STX 0x02 #define EOT 0x04 #define ACK 0x06 #define NAK 0x15 #define CAN 0x18 #define PAD 0x1a #define DLY_1S 1000 #define MAXRETRANS 25 #define X_BLOCK_SIZE 128 #define X_BUF_SIZE X_BLOCK_SIZE + 6 // 128 for XModem + 3 head chars + 2 crc + nul static int check(const unsigned char *buf, int sz) { 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) #ifdef PICOMITEWEB ProcessWeb(1) #endif ; } // receive data // if sp == NULL we are saving to a file on the SD card (fnbr is the file number) // otherwise we are saving to RAM which will later be written to program memory void xmodemReceive(char *sp, int maxbytes, int fnbr, int crunch) { unsigned char xbuff[X_BUF_SIZE]; unsigned char *p; unsigned char trychar = NAK; //'C'; unsigned char packetno = 1; int i, c; int retry, retrans = MAXRETRANS; CrunchData((unsigned char **)&sp, 0); // initialise the crunch subroutine // first establish communication with the remote while(1) { for( retry = 0; retry < 32; ++retry) { if(trychar) _outbyte(trychar,1); if ((c = _inbyte((DLY_1S)<<1)) >= 0) { switch (c) { case SOH: goto start_recv; case EOT: flushinput(); _outbyte(ACK,1);; if(sp != NULL) { if(maxbytes <= 0) error("Not enough memory"); *sp++ = 0; // terminate the data } return; // no more data case CAN: flushinput(); _outbyte(ACK,1); error("Cancelled by remote"); break; default: break; } } } flushinput(); _outbyte(CAN,1); _outbyte(CAN,1); _outbyte(CAN,1); error("Remote did not respond"); // no sync start_recv: trychar = 0; p = xbuff; *p++ = SOH; for (i = 0; i < (X_BLOCK_SIZE+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(&xbuff[3], X_BLOCK_SIZE)) { if (xbuff[1] == packetno) { for(i = 0 ; i < X_BLOCK_SIZE ; i++) { if(sp != NULL) { // save the data to the RAM buffer if(--maxbytes > 0) { if(xbuff[i + 3] == PAD) continue; // if(xbuff[i + 3] == PAD) // *sp++ = 0; // replace any EOF's (used to pad out a block) with NUL // else if(xbuff[i + 3] == 0) continue; if(crunch) CrunchData((unsigned char **)&sp, xbuff[i + 3]); else *sp++ = xbuff[i + 3]; // saving to a memory buffer } } else { // we are saving to a file FilePutChar(xbuff[i + 3], fnbr); } } ++packetno; retrans = MAXRETRANS+1; } if (--retrans <= 0) { flushinput(); _outbyte(CAN,1); _outbyte(CAN,1); _outbyte(CAN,1); error("Too many errors"); } _outbyte(ACK,1); continue; } reject: flushinput(); _outbyte(NAK,1); } } // transmit data // if p == NULL we are reading the data to be sent from a file on the SD card (fnbr is the file number) // otherwise we are reading from RAM and p points to the start of the data (which is terminated by a zero char) void xmodemTransmit(char *p, int fnbr) { unsigned char xbuff[X_BUF_SIZE]; unsigned char packetno = 1; char prevchar = 0; int i, c, len; int retry; // first establish communication with the remote while(1) { for( retry = 0; retry < 32; ++retry) { if ((c = _inbyte((DLY_1S)<<1)) >= 0) { switch (c) { case NAK: // start sending goto start_trans; case CAN: if ((c = _inbyte(DLY_1S)) == CAN) { _outbyte(ACK,1);; flushinput(); error("Cancelled by remote"); } break; default: break; } } } _outbyte(CAN,1);; _outbyte(CAN,1);; _outbyte(CAN,1);; flushinput(); error("Remote did not respond"); // no sync // send a packet while(1) { start_trans: memset (xbuff, 0, X_BUF_SIZE); // start with an empty buffer xbuff[0] = SOH; // copy the header xbuff[1] = packetno; xbuff[2] = ~packetno; if(p != NULL) { // our data is in RAM for(len = 0; len < 128 && *p; len++) { if(*p == '\n' && prevchar != '\r') prevchar = xbuff[len + 3] = '\r'; else prevchar = xbuff[len + 3] = *p++; // copy the data from memory into the packet } } else { // we get the data from a file for(len = 0; len < 128 && !FileEOF(fnbr); len++) { xbuff[len + 3] = FileGetChar(fnbr); // copy the data from the file into the packet } } if (len > 0) { unsigned char ccks = 0; for (i = 3; i < X_BLOCK_SIZE+3; ++i) { ccks += xbuff[i]; } xbuff[X_BLOCK_SIZE+3] = ccks; // now send the block for (retry = 0; retry < MAXRETRANS && !MMAbort; ++retry) { // send the block for (i = 0; i < X_BLOCK_SIZE+4 && !MMAbort; ++i) { _outbyte(xbuff[i],1); } // check the response if ((c = _inbyte(DLY_1S)) >= 0 ) { switch (c) { case ACK: ++packetno; goto start_trans; case CAN: // cancelled by remote _outbyte(ACK,1);; flushinput(); error("Cancelled by remote"); break; case NAK: // receiver got a corrupt block default: break; } } } // too many retrys... give up _outbyte(CAN,1); _outbyte(CAN,1); _outbyte(CAN,1); flushinput(); error("Too many errors"); } // finished sending - send end of text else { for (retry = 0; retry < 10; ++retry) { _outbyte(EOT,1); if ((c = _inbyte((DLY_1S)<<1)) == ACK) break; } flushinput(); if(c == ACK) return; error("Error closing"); } } } } /* @endcond */