Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Tools

Intelligent XYModem


DEC94: Intelligent XYModem

Establishing a common ground for XModem/YModem dialects

More on XYModem and other serial protocols can be found in Tim's upcoming book from Coriolis Group Books. Tim can be contacted at [email protected].


When Ward Christensen upgraded his MODEM program to MODEM2 in 1977, he added a method for transferring binary files between two CP/M computers. This protocol, which became known as "XModem," spread rapidly, primarily because of it's simplicity. But this popularity came at a price. New implementations often incorporated minor changes to the original protocol, resulting in a babel of incompatible "dialects."

Today, programs that attempt to support a wide variety of file-transfer protocols sometimes list as many as eight XModem and YModem variations. Worse, the same dialect can appear under different names: What some systems refer to as "XModem-1k" is referred to by others as "YModem." This lack of standardization confuses users; even if the local terminal program and remote host are using identically named protocols, they still may not be able to transfer a file!

The XYModem Protocol

About five years ago, I began to develop techniques for automatically determining which XModem or YModem dialect is being used by the other end. The result is a single protocol, which I call "XYModem," implemented in the program XY.C. Applications using this "smart" XYModem protocol can transfer files with any of the common XModem or YModem dialects. Listing One presents excerpts from XY.C. The complete implementation is available electronically; see "Availability," page 3.

The major advantage of this combined protocol over more traditional implementations is that it simplifies the user interface. A smart implementation reduces the need for the user to set his or her program to exactly match the other end. It also significantly reduces the number of settings and technical terms that the user has to contend with.

Listing One is organized by layers. Each layer reflects a different level of functionality, ranging from basic serial I/O to a complete transfer session.

Packet Layer

The lowest layer is the packet layer. XYModem has data packets (see Table 1), and control packets (Table 2). One of XYModem's weaknesses, compared to protocols such as Kermit or ZModem, is that control packets do not contain error-check information. This means that higher layers need to allow for altered or spurious control packets.

Reliability Layer

The reliability layer is responsible for ensuring that data packets are correctly transferred. It calls packet-layer functions to send or receive data and interprets responses and errors. The receiver responds to most errors by waiting for the line to clear and sending a NAK to request that the packet be resent.

XYModem is receiver driven so the XYModem receiver should always timeout before the sender. To simplify the UI, I usually offer the user a short menu of timeouts. I then subtract one second from the user's choice to get the actual receiver timeout and multiply it by 5 to get the actual sender timeout. Thus, the default user setting of 10 seconds results in a 9-second receiver timeout and a 50-second sender timeout.

File Layer

The file layer, responsible for transferring a single file, involves a handshake negotiation and an optional batch-header transfer, followed by the transfer of the actual file data.

By convention, text files are converted to a neutral format with CR/LF terminating each line, and the last packet is filled out with SUB characters (Ctrl-Z). This corresponds to the CP/M file format used by the first XModem implementations. For optimal compatibility with older systems, it's advisable to ensure that text files end with CR/LF, and that there is at least one SUB character after the end of the file data.

Handshake. The file layer's handling of the initial handshake is a bit complicated because the handshakes used by different dialects overlap. In particular, the C handshake is used by both the non-batch XModem-CRC dialect and the batch YModem dialect. Table 3 lists the handshake sequences that may be sent by the XYModem receiver. (Forsberg, the inventor of YModem, allows a YModem transfer to begin with a NAK handshake, specifying Checksum error detection. However, this is rarely used.)

The receiver starts with a handshake based on the user's protocol setting, which should default to YModem. The receiver repeats the initial handshake three times; if there is no response, it tries a different handshake, as indicated under "Fallback" in Table 3. After a total of ten timeouts, the receiver fails.

When the sender sees a handshake it recognizes, it responds with one of two packets--a filename packet (to attempt a batch-mode transfer) or the first packet of data (for a non-batch-mode transfer)--depending on the receiver's handshake. After three failures, the sender tries the other. Once the receiver acknowledges one of these packets, the sender knows whether or not batch mode will be used.

At this point, the sender and receiver agree on the batch mode and error-check method. The only remaining possibility for disagreement is the packet size. Unfortunately, it's dangerous to try sending the same data with different packet sizes just to discover the receiver's capabilities. For example, spurious ACKs can cause both sides to believe the file transfer has succeeded, even though data has been duplicated. (ZModem, which uses file position rather than packet number to identify data, is immune to this problem.) So the XYModem sender assumes that all batch receivers can handle long data packets and relies on the initial handshake to determine if nonbatch receivers can handle long packets.

To show how these rules work in practice, Figures 1 and 2 illustrate negotiations between XYModem and non-XYModem implementations. Figure 1 demonstrates an XYModem sender attempting to transfer a file with an XModem-CRC receiver. The XYModem sender initially sends a batch header, but after three NAKs changes to a nonbatch transfer, which succeeds. In this case, the startup is quite fast, since the XModem-CRC receiver is likely to NAK the unrecognized packets immediately. Other instances of an XYModem sender transferring data to a non-XYModem receiver work similarly.

Figure 2 demonstrates an XYModem receiver attempting to transfer a file with a YModem-G sender. The XYModem receiver has been configured to start with plain YModem. This example shows that, although the XYModem receiver will eventually manage to negotiate a common protocol, it can be time consuming. If the user can choose the protocol for the XYModem receiver to try first, the negotiation is much quicker. Note that if the sender were an XYModem sender, it would recognize and respond to the first handshake and a normal YModem transfer would take place immediately.

Nonbatch File Transfer. In nonbatch mode, the file data is transferred beginning with packet number one. After the receiver acknowledges the last data packet, the sender sends an EOT and waits for the receiver's acknowledgment. Figure 3 illustrates this exchange. Notice that because EOT can conceivably be generated by noise, the receiver sends NAK to challenge the first EOT. (Older nonbatch senders don't wait for the acknowledgment, so a receiver using this technique should be prepared to terminate gracefully if there is no response to the NAK.)

Batch File Transfer. Instead of simply transferring the file contents as Figure 3 indicates, the sender may attempt a batch transfer. The batch transfer consists of adding a packet zero with data about the file; see Figure 4. If the receiver fails to acknowledge packet zero, the sender should try sending the first packet of data and continue in nonbatch mode if the receiver acknowledges.

Packet zero contains two consecutive, null-terminated strings. The remainder of the packet is filled with nulls. The first string is the mandatory filename, the second contains the file size as a decimal number, the file's time of creation (an octal number representing seconds since January 1, 1970), and the file's mode (as a UNIX-style octal number). These three fields are separated by blanks and are all optional. If any field is omitted, all following fields must also be omitted. Also, if the file size is unknown (which is common for text files that require end-of-line conversion), the size field must be omitted.

Session Layer

The session layer handles multiple files. After the first file is transferred, the protocol knows if batch mode is being used. If so, the session layer loops to repeatedly transfer the remaining files and does a final transfer of a file with no name to terminate the session. Otherwise, it simply terminates after transferring the first file.

XYModem Implementation

I've implemented the XModem and YModem protocols, both separately and in the combined form discussed earlier, several times over the last ten years. Each time I've learned a few more tricks.

One of the major reasons file-transfer protocols exist is to deal with errors. Good error handling can't be easily added as an afterthought. The approach I prefer is for each function to return a status code: 0 if the function succeeded with no errors, or a code indicating the type of error. A collection of constants defines the error values. Using this approach, most function calls end up looking somewhat like Example 1.

Three basic serial functions simplify the implementation of most file-transfer protocols, while improving portability.

  • ReadBytesWithTimeout accepts a buffer and a timeout interval and returns when the buffer is filled or the timeout expires.
  • SendBytes queues bytes to be sent.
  • WaitForSentBytes waits until the outgoing serial queue is empty.
Each of these returns a status code to indicate one of the following:

  • The operation was completed successfully.
  • A user interrupt was detected. This result is eventually returned to the top layer of the transfer code, which sends the abort sequence.
  • There was a fatal serial error which cannot be recovered from (loss of carrier, for example).
  • There was a nonfatal serial error which invalidates the received data (a framing or overrun error, for example).
  • There was a timeout.
There are two reasons why it's useful to implement sending as two separate functions. First, accurate timing requires that the protocol not begin waiting for a response until the data has actually been sent. Calling WaitForSentBytes after each packet is sent helps to ensure this. Second, it allows you to queue data for sending and then do additional work while the serial queue empties. In particular, this speeds sending packets, since the CRC calculation can be started after the data bytes are queued for sending.

Providing good user feedback for file transfers requires balancing user requirements against efficiency. If you don't update the display for every transfer event, then at low baud rates a user may easily believe that the transfer has stopped. If you do update the display, then at high baud rates the overhead of updating can slow the transfer. (Excessive screen updates have been known to slow transfers by 20 percent or more.)

My solution is to generate frequent updates to the screen information, but to limit the frequency at which the screen is actually updated. It is usually easy to record the time of the last update and only change the screen if there is some unusual condition or if a minimum time has elapsed.

Care also must be given when deciding what information to display. Too much information can be intimidating to nontechnical users. Table 4 lists the information I have found to be consistently useful.

Many programs overwhelm the user with a wealth of settings. My preference is to provide only two: a timeout setting with options of 5, 10, or 20 seconds; and a protocol setting with choices of XModem, YModem, or YModem-G. The other settings commonly provided by terminal programs are either not useful in practice, or made largely obsolete by the automatic negotiation of the XYModem protocol.

The only real trick to implementing the combined protocol is dealing with ambiguity. For example, if the sender sees a C handshake, then the receiver may or may not support batch transfers. To help with this, my implementation maintains a structure with information about which protocol options are currently active. Each option contains two variables: one to indicate whether that option is currently enabled, and another to indicate if that option is locked or not. During the handshake negotiation, the program uses the current options, changing them after repeated errors. When there is evidence that a particular option is correct, that option is locked and not changed again during the session.

Summary

XModem and YModem are often criticized for being difficult to use. Much of that difficulty is due to unrefined user interfaces which require the user to know many details of protocol operation. By shifting the burden to the implementation and using automatic negotiation, the UI complexity can be significantly reduced.

Table 1: XYModem data packet.

Size         Description
             
1            1 for 128-byte packet or 2 for 1024-byte packet.
1            Packet-sequence number.
1            Complement of sequence number.
128 or 1024  Data.
1 or 2       1-byte. checksum/2-byte CRC-16.

Table 2: XYModem control packets.

Control Packet   Description

ACK              Sent by receiver to acknowledge successful receipt
                  of a packet; sender responds with next data packet 
                  or EOT.
NAK              Sent by receiver to request repeat of previous packet.
EOT              Sent by sender to indicate end of file data.
CAN CAN          Sent by either side to abort transfer.

Table 3: Handshakes.

Handshake   Fallback   Error Check   Batch   Long Packets   Dialect

NAK         G          Checksum      No      No             XModem
C           NAK        CRC-16        No      Yes            XModem-1K
C           NAK        CRC-16        No      No             XModem-CRC
C           NAK        CRC-16        Yes     Yes            YModem
G           C          CRC-16        Yes     Yes            YModem-G

Table 4: Progress information.

Relative progress. During sends or during batch receives, the file size should be available so it's possible to compute the relative progress and display it using a "thermometer" bar. If the file size is not available, then the number of bytes transferred can be substituted.

Current filename. The receiver should show the filename under which the file is being stored, rather than the name specified by the sender. These may differ if the name given by the sender is illegal on this particular system or the receiver is modifying names to avoid overwriting existing files.

Estimated time remaining. On some systems, this display can be updated every second, providing a reassuring, steady countdown to the user. This should be computed from a decaying average of the measured transfer speed, not from the serial-port baud rate.

Throughput estimate. Percent-efficiency estimates, while popular, can be misleading. Using newer, speed-buffering modems, the "efficiency" may appear very low because the modems are connected at a low speed. I prefer to display a throughput estimate in bits per second. Since modems are commonly rated in bits per second, these numbers are more likely to be meaningful to the user.

Status. Detailed status messages are not very useful to the user and can actually slow down the transfer if too much time is spent frequently updating the display. I prefer to limit the status during the transfer to: Negotiating_, Sending_, Receiving_, and Finishing_. When the transfer stops, the status shows either Finished, Failed, or Aborted.

Figure 1: Handshake example: XModem-CRC receiver, XYModem sender.


        Receiver        Sender

Send C handshake
                        Send filename packet
             NAK
                        Send filename packet
             NAK
                        Send filename packet
             NAK
                        Send first data packet
     Acknowledge

Nonbatch XModem-CRC transfer proceeds

Figure 2: Handshake example: XYModem receiver, YModem-G sender.

        Receiver        Sender

Send C handshake
                        Timeout
Send C handshake
                        Timeout
Send C handshake
                        Timeout
Send NAK handshake
                        Timeout
Send NAK handshake
                        Timeout
Send NAK handshake
                        Timeout
Send G handshake
                        Send filename packet

     YModem-G transfer proceeds

Figure 3: XYModem nonbatch file layer.

      Receiver          Sender

Send handshake
                        Send packet one
   Acknowledge
               .
               .
               .
                        Send last packet
   Acknowledge
                        Send EOT
           NAK
                        Resend EOT
   Acknowledge
              End of file

Figure 4: XYModem batch file layer.

        Receiver          Sender

  Send handshake
                          Send packet zero
     Acknowledge
Repeat handshake
                          Send packet one
     Acknowledge
                 .
                 .
                 .
                          Send EOT
             NAK
                          Resend EOT

     Acknowledge
               End of file

Example 1: XYModem function calls.

int status;
status = function(arguments);
if (status == special value)
  fix problem;
else if (status != 0)
  return status;

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)

Copyright © 1994, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.