_INTELLIGENT XYMODEM_ by Tim Kientzle Listing One /**************************************************************************** ** Excerpts from XY.C ** ****************************************************************************/ /**** Every function returns a status code. These macros help. ****/ #define StsWarn(s) Sts_Warn(s,__FILE__,__LINE__) #define StsRet(e) do{int tmp_s; if ((tmp_s = (e)) != xyOK) \ return StsWarn(tmp_s);}while(FALSE) static int Sts_Warn(int s,char *file,int line) { switch(s) { case xyFail: fprintf(stderr,"!?:%s:%d:xyFail\n",file,line); break; /* ... other cases deleted ... */ } return s; } /* A `capability' has two fields. The `enabled' field determines current state of the capability, while the `certain' field determines whether the capability can still change. Whenever we have evidence of a capability (for example, we receive an acknowledge for a long packet), set the `certain' field to TRUE. */ typedef struct CAPABILITY { int enabled, certain; } CAPABILITY; /* This structure contains the current state of the transfer. By keeping all information about the transfer in a dynamically-allocated structure, we can allow multiple simultaneous transfers in a multi-threaded system. */ typedef struct XYMODEM XYMODEM; struct XYMODEM { CAPABILITY crc, /* Does other end support CRC? */ longPacket, /* Does other end support 1K blocks? */ batch, /* Does other end support batch? */ G; /* Does other end support `G'? */ int timeout; /* Number of seconds timeout */ int retries; /* Number of times to retry a packet */ int userAbort; /* Set when user asks for abort */ int packetNumber; /* Current packet number */ PORT_TYPE port; /* Port to use */ FILE * f; /* Current file */ char fileName[128]; /* Name of file being transferred */ long fileSize; /* Size of file being transferred, -1 if not known */ long fileDate; /* Mod time, GMT, in seconds after 1/1/1970 */ long fileMode; /* Unix-style file mode */ long transferred;/* Number of bytes transferred so far */ }; /**************************** Packet Layer *********************************/ /* XYSendPacket. Send an XYModem data packet. Length must be less than 1024. Packets shorter than 128 or longer than 128 but shorter than 1024 are padded with SUB characters. */ static int XYSendPacket(XYMODEM *pXY, BYTE *pBuffer, int length) /* ... body deleted ... */ /* XYReceivePacket. Receiver must be able to receive three different types of packets: data packets start with a recognizable 3-byte sequence; EOT packets consist of a single EOT character; CAN packets consist of two consecutive CAN. We use a shorter timeout between bytes within a single packet than we do between packets. This is to help speed error recovery. */ static int XYReceivePacket(XYMODEM *pXY, int *pPacketNumber, BYTE *pBuffer, int *pLength) { BYTE startOfPacket = 0; BYTE packet = 0; BYTE packetCheck = 0; /* This loop searches incoming bytes for a valid packet start. This reduces */ /* our sensitivity to inter-packet noise. Also check for EOT and CAN packets.*/ StsRet(XYReadBytesWithTimeout(pXY,pXY->timeout,&packetCheck,1)); if (packetCheck == EOT) return xyEOT; do { startOfPacket = packet; packet = packetCheck; StsRet(XYReadBytesWithTimeout(pXY,pXY->timeout,&packetCheck,1)); if ((packetCheck == CAN) && (packet == CAN)) return StsWarn(xyFailed); } while ( ( (startOfPacket != 0x01) && (startOfPacket != 0x02) ) || ( ((packet ^ packetCheck) & 0xFF) != 0xFF ) ); /* ... rest of body deleted ... */ } /************************* Reliability Layer ******************************/ /* XYSendPacketReliable. Repeatedly sends a packet until it is acknowledged. Note that only a slight change is required to handle the YModem-G protocol. */ static int XYSendPacketReliable(XYMODEM *pXY, BYTE *pBuffer, int length) { int err; BYTE response = ACK; do { StsRet(XYSendPacket(pXY, pBuffer, length)); if (pXY->G.enabled) return xyOK; do { /* Read ACK or NAK response */ err = XYReadBytesWithTimeout(pXY, pXY->timeout, &response, 1); if (err == xyTimeout) return StsWarn(xyFail); StsRet(err); } while ((response != ACK) && (response != NAK)); } while (response != ACK); pXY->crc.certain = TRUE; /* Checksum/crc mode is now known */ if (length > 128) pXY->longPacket.enabled = pXY->longPacket.certain = TRUE; return xyOK; } /* XYReceivePacketReliable. Handles NAK of data packets until a valid packet is received. The next layer up is responsible for sending the ACK and dealing with packet sequencing issues. EOT packets are handled here by the following logic: An EOT is considered reliable if it is repeated twice by the sender or if we get three consecutive timeouts after an EOT. Cancel packets are already reliable. We don't ACK packets here so the next layer up can do some work (opening files, etc.) before the ACK is sent. That way, we can avoid having to deal with the issue of overlapped serial and disk I/O. */ static int XYReceivePacketReliable(XYMODEM *pXY, int *pPacket, BYTE *pBuffer, int *pLength) /* ... body deleted ... */ /************************* File Layer *************************************/ /* Read and interpret receiver's handshake */ static int XYSendReadHandshake(XYMODEM *pXY, BYTE *pResponse) { int err; do { /* Read handshake */ err = XYReadBytesWithTimeout(pXY, pXY->timeout, pResponse, 1); if (err == xyTimeout) return xyFail; if (err != xyOK) return err; /* Interpret the receiver's handshake */ switch(*pResponse) { case 'G': if (pXY->G.enabled) return xyOK; if (!pXY->G.certain) pXY->G.enabled = TRUE; if (!pXY->G.enabled) break; return xyOK; case 'C': /* ... deleted ... */ case NAK: /* ... deleted ... */ case ACK: return xyOK; default: break; } } while (TRUE); } /* Send packet zero or one, depending on batch mode. If there are too many failures, we swap batch mode. The buffer is passed as a parameter to reduce our stack requirements. */ static int XYSendFirstPacket(XYMODEM *pXY, BYTE *pBuffer, int bufferLength ) { int err; int totalRetries = pXY->retries; int retries = pXY->retries / 2; BYTE response = ACK; int dataLength = 0; /* Get initial handshake */ do { StsRet(XYSendReadHandshake(pXY,&response)); } while (response == ACK); do { /* Send packet 0 or 1, depending on current batch mode */ if (pXY->batch.enabled) { StsRet(XYSendPacketZero(pXY)); if (pXY->G.enabled) return xyOK; } else { if (dataLength == 0) { /* Get packet 1 */ dataLength = (pXY->longPacket.enabled)?1024:128; err = XYFileRead(pXY, pBuffer, &dataLength); } pXY->packetNumber = 1; StsRet(XYSendPacket(pXY,pBuffer,dataLength)); } /* Get response or repeated handshake */ StsRet(XYSendReadHandshake(pXY,&response)); /* Count down number of retries */ if (response != ACK) { if (retries-- == 0) { if (!pXY->batch.certain) pXY->batch.enabled = !pXY->batch.enabled; if (!pXY->longPacket.certain) pXY->longPacket.enabled = pXY->batch.enabled; retries = 2; } if (totalRetries-- == 0) return xyFail; } } while (response != ACK); if ( (pXY->packetNumber == 0) && (dataLength > 0) ) { pXY->packetNumber++; StsRet(XYSendPacketReliable(pXY, pBuffer, dataLength)); pXY->transferred += dataLength; } pXY->G.certain = pXY->batch.certain = TRUE; /* batch mode is now known */ return xyOK; } /* Send EOT and wait for acknowledgement */ static int XYSendEOT(XYMODEM *pXY) /* ... body deleted ... */ static int XYSendFile(XYMODEM *pXY) /* ... body deleted ... */ /* Shuffles capabilities for falling back to a lower protocol. Note that we do actually `fall' from basic XModem to YModem-G, to handle obstinate senders that may be looking for only a `G' or `C' handshake. */ static int XYReceiveFallback(XYMODEM *pXY) { if (pXY->G.enabled) pXY->G.enabled = FALSE; else if (pXY->crc.enabled) pXY->crc.enabled = pXY->batch.enabled = pXY->longPacket.enabled = FALSE; else pXY->G.enabled = pXY->batch.enabled = pXY->longPacket.enabled = pXY->crc.enabled = TRUE; return xyOK; } /* Send the correct handshake for the current capabilities. */ static int XYReceiveSendHandshake(XYMODEM *pXY) /* ... body deleted ... */ static int XYReceiveFile(XYMODEM *pXY) { BYTE data[1024]; int dataLength, err = xyOK, packetNumber; int retries = pXY->retries/2 + 1; int totalRetries = (pXY->retries * 3)/2+1; /* Try different handshakes until we get the first packet */ XYNewFile(pXY); XYProgress(pXY,stsNegotiating); do { if (--retries == 0) { XYReceiveFallback(pXY); XYProgress(pXY,stsNegotiating); retries = (pXY->retries/3); } if (totalRetries-- == 0) return xyFail; StsRet(XYReceiveSendHandshake(pXY)); err = XYReceivePacket(pXY, &packetNumber, data, &dataLength); if (err == xyEOT) /* EOT must be garbage... */ StsRet(XYGobble(pXY, pXY->timeout/2)); if (err == xyBadCRC) /* garbaged block */ StsRet(XYGobble(pXY,pXY->timeout/3)); } while ( (err == xyTimeout) || (err == xyBadCRC) || (err == xyEOT) ); StsRet(err); if ((packetNumber != 0) && (packetNumber != 1)) return xyFail; /* The first packet tells us the sender's batch mode */ /* If batch mode is certain, then a mismatch is fatal. */ /* Note that batch mode is never certain for the first file. */ if (packetNumber == 0) pXY->batch.enabled = TRUE; else if (pXY->batch.enabled && pXY->batch.certain) return xyFail; else pXY->batch.enabled = FALSE; pXY->batch.certain = TRUE; /* Open the file and make sure `data' contains the first part of file */ if (packetNumber == 0) { if (data[0] == 0) { /* Empty filename? */ StsRet(XYSendByte(pXY,ACK)); /* Ack packet zero */ return StsWarn(xyEndOfSession); } StsRet(XYFileWriteOpen(pXY, data, dataLength)); StsRet(XYSendByte(pXY,ACK)); /* Ack packet zero */ StsRet(XYReceiveSendHandshake(pXY)); err = XYReceivePacketReliable(pXY, &packetNumber, data, &dataLength); } else StsRet(XYFileWriteOpen(pXY, NULL, 0)); pXY->packetNumber = 1; pXY->transferred = 0; XYProgress(pXY,stsReceiving); /* We have the first packet of file data. Receive remaining packets and /* write it all to the file. Note that we're careful to ACK only after /* file I/O is complete. */ while (err == xyOK) { if (packetNumber == (pXY->packetNumber & 0xFF)) { if ( (pXY->fileSize >= 0) && (dataLength + pXY->transferred > pXY->fileSize) ) dataLength = pXY->fileSize - pXY->transferred; StsRet(XYFileWrite(pXY,data,dataLength)); pXY->transferred += dataLength; pXY->packetNumber++; XYProgress(pXY,stsReceiving); StsRet(XYSendByte(pXY,ACK)); /* Ack correct packet */ } else if (packetNumber == (pXY->packetNumber-1) & 0xFF) StsRet(XYSendByte(pXY,ACK)); /* Ack repeat of previous packet */ else return xyFail; /* Fatal: wrong packet number! */ err = XYReceivePacketReliable(pXY, &packetNumber, data, &dataLength); } /* ACK the EOT. Note that the Reliability layer has already */ /* handled a challenge, if necessary. */ if (err == xyEOT) { XYProgress(pXY,stsFinishing); err = XYSendByte(pXY,ACK); } StsRet(XYFileWriteClose(pXY)); StsRet(err); if (!pXY->batch.enabled) return StsWarn(xyEndOfSession); return xyOK; } /************************ Session Layer *********************************/ static int XYSend(XYMODEM *pXY) { int err; XYNewFile(pXY); err = XYFileReadOpenNext(pXY); while (err == xyOK) { err = XYSendFile(pXY); XYFileReadClose(pXY); XYNewFile(pXY); if (err == xyOK) err = XYFileReadOpenNext(pXY); } if (err == xyEndOfSession) { err = xyOK; if (pXY->batch.enabled) err = XYSendSessionEnd(pXY); /* Transfer empty filename */ } if (err == xyFail) { static BYTE cancel[] = {CAN,CAN,CAN,CAN,CAN,8,8,8,8,8}; XYSendBytes(pXY,cancel,sizeof(cancel)/sizeof(cancel[0])); return xyFailed; } if (err == xyOK) XYProgress(pXY,stsFinished); else if (pXY->userAbort) XYProgress(pXY,stsAborted); else XYProgress(pXY,stsFailed); return xyOK; } /* The session layer simply receives successive files until end-of-session */ static int XYReceive(XYMODEM *pXY) /* ... body deleted ... */ /******************* Public Interface Layer ******************************/ /* Initialize the XY structure from the user preferences. Protocol codes are defined in xy.h, timeout is in seconds. */ int XYModemInit( void **ppXY, int protocol, int timeout, PORT_TYPE port) /* ... body deleted ... */ /* The public interfaces are simply stubs that call the internal XYSend/ XYReceive functions. We isolate the interface so it can be easily changed. */ int XYModemSend(void *pXY_public, char *filenames[], int count) int XYModemReceive( void *pXY_public) Example 1: int status; status = function(arguments); if (status == special value) fix problem; else if (status != 0) return status;