/*@ptxt[This is a minimal version of Kermit written in C. It has many limitations, but does most of what is necessary. It has no SET commands, no command parser, does only ASCII file transmission (no eight-@|bit or @q<.EXE> files) and some system-@|dependent routines are not filled in (like @q for multiple sends), and does not implement any of the server functions. This is intended mainly as an example, but it also demonstrates how a minimal version can be implemented. Any command parser that will parse the four basic commands would be sufficient. @Section These are all the global variables and definitions.] */ #include "c:stdio.h" #define maxpack 94 /* Maximum packet size */ #define SOH 1 /* Start of header */ #define SP 32 /* ASCII space */ #define CR 13 /* Carriage return */ #define DEL 127 /* Delete (rubout) */ #define maxtry 5 /* Times to retry a packet */ #define myquote '#' /* Quote character I will use */ #define mypad 0 /* Number of padding characters I need */ #define mypchar 0 /* Padding character I need */ #define myeol CR /* End-Of-Line character I need */ #define mytime 10 /* Seconds after which I should be timed out */ /* Declarations of global variables: */ int size, /* Size of present data */ n, /* Message number */ rpsiz, /* Maximum receive packet size */ spsiz, /* Maximum send packet size */ pad, /* How much padding to send */ timint, /* Timeout for foreign host on sends */ numtry, /* Times this packet retried */ oldtry; /* Times previous packet retried */ char state, /* Present state of the automaton */ padchar, /* Padding character to send */ eol, /* End-Of-Line to send */ quote, /* Quote character in incoming data */ filnam[50], /* The file name */ recpkt[maxpack], /* Receive packet buffer */ packet[maxpack]; /* Packet buffer */ FILE *fptr; /* File pointer @End @Section @Index[Parsing Commands]@Index[Prompt] The parsing depends upon the capabilities of the host, but the following syntax is recommended for uniformity: The prompt should be "@q{Kermit-XXX>}" where @q denotes the implementation, e.g. "@q{Kermit-20>}", "@q{Kermit-80>}", etc. It's important to have distinct prompts to avoid confusion while the user talks to two different Kermits from the same terminal. Commands should be a keyword followed by arguments. Optional arguments are shown in square brackets. @SubSection @begin SEND RECEIVE [] HELP EXIT ? @end "@q" should simply list the commands or arguments possible at that point. File specifications conform to the requirements of the specific system. Most systems allow the specification of a file group by the inclusion of special "wildcard" or pattern-matching characters in the file specification. A list of files, or file groups, may also be allowed. For SEND or RECEIVE, parse and store the file (group) name. If RECEIVE, the file name is optional, since it can be built from the name provided in the file header sent by the foreign host. Including a file name on the receive command implies that the user wants to store the file under a different name than it was sent with. This could be useful if the two hosts have different file name formats, but should not be done when receiving file groups. HELP types a help message at the terminal, EXIT terminates execution of Kermit. @SubSection @begin @tabclear()@tabdivide(3) SET @\@i(e.g. padding, packet size, etc.) SHOW @\@i(anything that can be SET) STATUS@\@i(e.g. of connection) CONNECT@\@i(to remote host) SERVER@\@i(act as a server) PROMPT@\@i(run interactively) QUIT@\@i(equivalent to) EXIT @end @Index[Virtual Terminal]@Index[Connect]@Index[Escape Character] CONNECT establishes a "virtual terminal" connection to the remote host. It is mainly useful on microcomputers with serial i/o ports which can be plugged in as terminals to the remote host. An escape mechanism must be provided to get the user back to the local host; preferably a two-@|character sequence, in which the first character (normally control-@|rightbracket) gets the attention of the local host and the second tells it what to do, for example: @begin @q@\Close the connection, return to local host. @q@\Show the status of the connection. @q@\Send a BREAK character. @q<^]>@\Send a control-rightbracket (or whatever the escape character is). @q@\Go into extended command mode, e.g. allowing SET commands. @q@\List the available options @end @Index[Set] SET should allow establishment of any system-@|dependent parameters. Here is the suggested syntax: @begin | SEND | | PACKET-LENGTH | SET | | | PADDING | | RECEIVE | | PADCHAR | | TIMEOUT | | END-OF-LINE | | QUOTE | | DEBUGGING | | ON | | FILE-WARNING | | OFF | | LOCAL-ECHO | LINE ESCAPE DELAY @end where @q() is a decimal number in the range 0-94, @q() is an octal number (0-37), @q() is a single printable character, and @q() is a number in whatever radix the system expresses terminal numbers in@foot. The LINE specification would let Kermit communicate over another terminal port on the same system, rather than its own controlling terminal. The following SET parameters are recommended for an implementation of Kermit that wants to be completely general, i.e. to communicate with as many different machines as possible: @begin @Index[Packet-Length] PACKET-LENGTH@\The maximum packet length for either outgoing or incoming packets. The maximum packet size can be no bigger than 96 (decimal), but may have to be smaller than that if the host has trouble buffering packets of that size (especially at higher baud rates). @Index[Timeout] TIMEOUT@\Specify the timeout interval for the outgoing (SEND) and incoming (RECEIVE) Kermits. @Index[Padding] PADDING@\The user should be able to specify the padding on outgoing packets (so the first send-init packet can get through) and on incoming packets (so the local Kermit can inform the remote Kermit of its own padding requirements). The argument specifies the number of pad characters with which to precede each packet. PADCHAR@\The character to use for padding. This should default to NUL, but some systems want others; e.g. IBM uses DEL. @Index[End-Of-Line (EOL)] END-OF-LINE@\The user should be able to specify the line terminator for outgoing packets, so the first @q packet can get through. If the user knows that the remote Kermit will use a different terminator than the one the local Kermit expects, then that can be set too, though the appropriate default should be hardwired into the program and sent to the remote Kermit in the @q packet. @Index[Quote] QUOTE@\The user might need to change the quote character on outgoing packets in case the default quote character is causing problems at the other end. @Index[Eight-Bit-Quote] EIGHT-BIT-QUOTE@\The character to be used for quoting bytes with the 8th bit set, for systems that cannot transmit or receive 8-bit ASCII. @Index[Escape Character] ESCAPE@\If a CONNECT command is provided, this allows the default escape character (Control-@q<]>) to be replaced by the one specified. @Index[Local-Echo] LOCAL-ECHO@\For CONNECTing to a half-@|duplex host. @Index[Parity] PARITY@\Specify parity on outgoing packets -- NONE, SPACE, MARK, EVEN, or ODD. If NONE, then the 8th bit can be used for data; all the others usurp the 8th bit. If other than NONE, the parity bit is ignored on incoming characters. @Index[IBM-Mode] IBM-MODE@\For communicating with an IBM mainframe. Makes the local Kermit wait for the half duplex line to turn around (i.e.@ wait for the receipt of an XON character), and has the local Kermit always send whatever parity the IBM front ends insist upon. @Index[Delay] DELAY@\Allow the user to specify a delay before sending the @q packet. If the remote Kermit is sending, this gives the user time to get back and start up the local receiving Kermit in time to catch the first incoming packet. @Index[File-Warning] FILE-WARNING@\When receiving files, allow the user to elect whether to be warned if a filename conflict will occur, i.e. if Kermit is about to receive a file that has the same name as one already on the disk. If the user elects to be warned, Kermit should allow the user to specify new names when conflicts arise. @Index[Debugging] DEBUGGING@\Allow the user to see packets as they go by, to receive extra informative and error messages, and to have some extra controls over the operation of the program. @end @Begin @Section
/*@ptxt[This is the main routine. It should parse for the parameters specified in the previous section and call the appropriate routine (@q for send and @q for receive.)] */ main () { if(sendsw() == FALSE) printf("Sending failed\n"); else printf("Sending suceeded\n"); } /*@ptxt[The main routine is left to the future implementer to fill in.] @End @Section @Begin /*@ptxt[Sendsw is the state table switcher for sending files. It loops until either it is finished or an error is encountered. The routines called by sendsw are responsible for changing the state.] */ sendsw () { state = 'S'; /* Send initiate is the start state */ n = 0; /* Initialize message number */ numtry = 0; /* Say no tries yet */ while (TRUE) /* Do this for as long as necessary */ { printf("State: %c\n",state); /* Temp for testing */ switch (state) { case 'D': state = sdata(); break; /* Data-Send state */ case 'F': state = sfile(); break; /* File-Send */ case 'Z': state = seof(); break; /* End-of-File */ case 'S': state = sinit(); break; /* Send-Init */ case 'B': state = sbreak(); break; /* Break-Send */ case 'C': return(TRUE); /* Complete */ case 'A': return(FALSE); /* Abort */ default: return(FALSE); /* Unknown, abort */ } } } /* Finished. @End @SubSection @Begin /*@ptxt[This routine initializes the connection with the foreign host. sinit is the prototype for all packet sending routines. It calls spack to send a correctly formatted Send-Initiate packet with the necessary data for initialization (packet size, timeouts, etc). The foreign host will ACK with a packet containing its own init data: how long a packet it can handle, when to time it out, etc. A dumb host can ignore the timeout data; hopefully the other host is smart and can handle the timeouts. If any of the following occur, numtry is incremented and the routine returns without changing state (this will happen at most maxtry times after which the state changes to Abort): @begin(itemize,spread 0) A bad packet (@q returns FALSE); A NAK is received; The wrong ACK is received. @end(itemize) If the foreign host ACKs correctly, the values for packet size, etc, should be taken from its ACK packet and the state changed to Send-File (F).] */ char sinit () { int num, len; /* Local variables */ printf("sinit\n"); if (numtry++ > maxtry) return('A'); /* If too many tries, abort */ spar(packet); /* Fill up with init info */ spack('S',n,6,packet); /* Send an 'S' packet, */ switch (rpack(&len,&num,recpkt)) /* What was the reply? */ { /* NAK */ case 'N': if (n != num--) return(state); /* NAK: fail */ /* If NAK for next pack its like ACK /* ACKnowlegement */ case 'Y': { if (n != num) return(state); /* If wrong ACK, fail */ rpar(recpkt); /* Get the init info */ if (eol == 0) eol = '\n'; /* Check and set defaults */ if (quote == 0) quote = '#'; numtry = 0; /* reset try counter, */ n = (n + 1) % 64; /* and bump packet count */ fptr = fopen(filnam,"r"); /* Open the file to read */ if (fptr < 0) return('A'); /* Abort if can't */ return('F'); /* Switch state to 'F' */ } case FALSE: return(state); /* Receive failure: fail */ default: return('A'); /* Just "Abort" */ } } /* Return @End @SubSection @Begin /*@ptxt[This routine is almost the same as sinit except the data field contains the name of the file being sent. It reacts the same way on all packets received except when a correct ACK is received. In that case the state is changed to 'D' (data send) and bufill is called to fill up the buffer to send to the foreign host.] */ char sfile () { int num, len; /* Local variables */ if (numtry++ > maxtry) return('A'); /* If too many tries, abort */ for(len=0;filnam[len] != '\0';len++); /* Count the length */ len++; spack('F',n,len,filnam); /* Send an 'F' packet, */ switch (rpack(&len,&num,recpkt)) /* What was the reply? */ { /* NAK */ case 'N': if (n != num--) return(state); /* NAK: fail */ /* If NAK for next pack it's like ACK, fall through /* ACKnowlegement */ case 'Y': { if (n != num) return(state); /* If wrong ACK, fail */ numtry = 0; /* Reset try counter, */ n = (n + 1) % 64; /* and bump packet count */ size = bufill(packet); /* Get the data from file */ return('D'); /* Switch state to 'D' */ } case FALSE: return(state); /* Receive failure: fail */ default: return('A') /* Just "Abort" */; } } /* Return @End @SubSection @Begin /*@ptxt[Another similar routine. It sends a data packet containing the contents of the data buffer. On ACK it does the same as sfile. When bufill returns EOF the state changes to 'Z' (EOF send).] */ char sdata () { int num, len; /* Local variables */ if (numtry++ > maxtry) return('A'); /* If too many tries, abort */ spack('D',n,size,packet); /* Send an 'D' packet, */ switch (rpack(&len,&num,recpkt)) /* What was the reply? */ { /* NAK */ case 'N': if (n != num--) return(state); /* NAK: fail */ /* If NAK for next pack it's like ACK, fall through /* ACKnowlegement */ case 'Y': { if (n != num) return(state); /* If wrong ACK, fail */ numtry = 0; /* Reset try counter, */ n = (n + 1) % 64; /* and bump packet count */ if ((size = bufill(packet)) == EOF) return('Z'); /* Get the data from file; if EOF set state to it */ return('D'); /* Switch state to 'D' */ } case FALSE: return(state); /* Receive failure: fail */ default: return('A') /* Just "Abort" */; } } /* Return @End @SubSection @Begin /*@ptxt[Sends an End-Of-File packet. On ACK it calls gnxtfl. If that succeeds the state becomes 'F' (file send) again. If it fails the state becomes 'B' (break connection).] */ char seof () { int num, len; /* Local variables */ if (numtry++ > maxtry) return('A'); /* If too many tries, abort */ spack('Z',n,0,packet); /* Send an 'Z' packet, */ switch (rpack(&len,&num,recpkt)) /* What was the reply? */ { /* NAK */ case 'N': if (n != num--) return(state); /* NAK: fail */ /* If NAK for next pack it's like ACK, fall through /* ACKnowlegement */ case 'Y': { if (n != num) return(state); /* If wrong ACK, fail */ numtry = 0; /* Reset try counter, */ n = (n + 1) % 64; /* and bump packet count */ if (gnxtfl() == EOF) return('B'); /* No more files go to Break */ return('F'); /* Switch state to 'F' */ } case FALSE: return(state); /* Receive failure: fail */ default: return('A') /* Just "Abort" */; } } /* Return @End @SubSection @Begin /*@ptxt[Sends a Break (End Of Transmission) packet. On ACK the state becomes 'C' (complete).] */ char sbreak () { int num, len; /* Local variables */ if (numtry++ > maxtry) return('A'); /* If too many tries, abort */ spack('B',n,0,packet); /* Send an 'B' packet, */ switch (rpack(&len,&num,recpkt)) /* What was the reply? */ { /* NAK */ case 'N': if (n != num--) return(state); /* NAK: fail */ /* If NAK for next pack it's like ACK, fall through /* ACKnowlegement */ case 'Y': { if (n != num) return(state); /* If wrong ACK, fail */ numtry = 0; /* Reset try counter, */ n = (n + 1) % 64; /* and bump packet count */ return('C'); /* Switch state to 'C' */ } case FALSE: return(state); /* Receive failure: fail */ default: return('A') /* Just "Abort" */; } } /* Return @End @Section @Begin /*@ptxt[The following routines deal with the building, sending and receiving of packets as well as the manipulation of files. Their low level I/O routines tend to be system dependent. The use of a high-level language such as C tends to insulate the programmer from this.] @End @SubSection @Begin /*@ptxt[This routine builds and sends the packet according to the specifications it is passed. It computes the checksum. The packet is sent the best way the machine will allow. It assumes that any control characters in the data buffer are already quoted, and the count includes the quote characters. Packet format (all numbers in octal):] @begin @u @ux @u @u start of packet 001-037 1 001 by definition character count 040-176 1 0 to 136 + SP message number 040-137 1 modulo 100 + SP message type 040-176 1 mnemonic (see below) data 040-176 count-3 0 to 132 chars checksum 040-137 1 (sum+((sum & 300)/100) & 77 Mnemonics for message type: D data packet Y acknowledge N negative acknowledge S send initiate B break transmission F file header Z end of file E error @end */ spack (type,num,len,data) char type, data[]; int num, len; { int i; char chksum; /* Local variables /* Initialize some parameters */ printf("spack\n"); for(i=1;i<=pad;i++) fputc(padchar,stdout); /* Issue necessary padding */ fputc(SOH,stdout); /* Packet marker, ASCII 1 (SOH)*/ chksum = tochar(len+3); /* Initialize the checksum */ fputc(tochar(len+3),stdout); /* Send the character count */ chksum = chksum + tochar(num); /* Init checksum */ fputc(tochar(num),stdout); /* Packet number */ chksum = chksum + type; fputc(type,stdout); /* Packet type /* Loop for all data characters */ for(i=0;i @SubSection @Begin /*@ptxt[This is a routine which does the opposite of Send-packet. It waits for characters to arrive from the foreign host. Upon receiving a SOH character it starts interpreting the ensuing characters depending on their position and value. If it encounters another SOH before it comes to the end of the packet it starts over with the new SOH as the beginning of the packet. If the packet is invalid in any way (bad checksum, invalid message number, etc.) the routine returns FALSE. If the packet is valid its type, message number, length of data and pointer to data are set in in the appropriate global variables.] */ rpack (len,num,data) int *len, *num; char *data; { int i, fld; char chksum, t, type; /* Local variables printf("\nrpack\n"); /* For testing purposes */ chksum = 0; /* Initialize checksum */ t = 0; /* Initialize input char value */ while ((t=fgetc(stdin)) != SOH); /* Wait for packet header /* Got one, loop for each field */ for (fld=1;fld<=5;fld++) { if(fld != 5 || i != 0) /* Don't get char on no data packets */ if((t=fgetc(stdin)) == SOH) fld = 0; /* Resynch if SOH */ if(fld <= 3) chksum = chksum + t; /* Accumulate checksum */ printf("case: %d, checksum: %d\n",fld,chksum); switch (fld) { case 0: chksum = 0; break; /* Restart loop */ case 1: *len = unchar(t)-3; break; /* Character count */ case 2: *num = unchar(t); break; /* Packet number */ case 3: type = t; break; /* Packet type */ case 4: for(i=0;i<*len;i++) { if(i != 0) if((t=fgetc(stdin)) == SOH) /* Get a char */ { fld = -1; break; } chksum = chksum + t; /* Add it to the checksum */ printf("i: %d, checksum: %d\n",i,chksum); data[i] = t; /* Normal character */ } break; case 5: chksum = (chksum + (chksum & 192) / 64) & 63; break; } } /* We now have all the chars /* Check the checksum */ if(chksum != unchar(t)) { printf("Bad checksum: %c, should be: %c\n",t,tochar(chksum)); return(FALSE); } return(type); /* Return packet type */ } /*@ptxt[A system that has only line or record input would get the line into an intermediate buffer and then pick it apart character by character.] @End @SubSection @Begin /*@ptxt[Bufill gets the characters from the file fptr. The characters are put into the buffer and any quoting of control characters is done here. As many characters as possible should be put into buffer, but the buffer must not be longer than the receiving Kermit's buffer size; spsiz minus 5. The count of characters includes the quoting characters. When EOF is encountered as many characters as are left should be put in the buffer. The next time this routine is called it should return failure to indicate EOF. All control characters (0-37 and 177) in the data field are substituted by a quote character followed by the control character plus 100 modulo 200, i.e. the result of transforming the character via the ctl function. The quote character is '#' by default, but can be any value 41-76 or 140-176 specified by the host, though for efficiency it should be a seldom used character. Transmission of the quote character itself is obtained by quoting it with itself. The number of characters put into the buffer is returned.] */ bufill (buffer) char buffer[]; { int i, t; /* Local variables */ i = 0; /* Init data buffer pointer */ while ((t = fgetc(fptr)) != EOF) /* Get the next character */ { t = t & 0177; /* Strip parity */ if(t < SP || t == DEL || t == quote) { buffer[i++] = quote; if(t != quote) t = ctl(t); } buffer[i++] = t; if(i >= spsiz-6) return(i); } if(i == 0) return(EOF); /* Wind up here only on EOF */ return(i); } /* So the partial buffer isn't lost @End @SubSection @Begin /*@ptxt[This is a system dependent routine that takes the characters from the buffer and writes them to the file. The ACK for the packet containing the characters should not be given until the characters are written. If there is an error writing the file, particulary of the "disk full" or "quota exceeded" type, send an appropriate error packet (if a remote Kermit) and then a Break packet to terminate transmission.] */ bufemp (buffer,filptr,len) char buffer[]; FILE *filptr; int len; { int i, t; for(i=0;i @SubSection @Begin /*@ptxt[This routine gets a file to output data to. If the user specified a file name in the RECEIVE command, that file is used. If not, the information in data supplied by the foreign host is used, transformed if necessary to construct a legal file name. If the SET FILE-WARNING feature has been implemented and the user has specified that it should be used, that name is looked up on the disk to see if a file of that name already exists, and if so the file is given another name and the user is informed of the new name. This command is not implemented in this version. The file is opened for output, and fptr becomes the file pointer for that file. ] */ getfil (filenm) char *filenm; { if(filenm[0] == '\0') fptr = fopen(packet,"w"); else fptr = fopen(filenm,"w"); } /* If user didn't specify a file use the one from other host. @End @SubSection @Begin /*@ptxt[This routine gets the next file in a file group if there is one. If not it returns EOF. In this implementation we only allow single file transmission, so this routine always returns EOF.] */ gnxtfl () { return(EOF); } /* Don't do anything @End @Section @Begin /*@ptxt[This routine is another state table switcher, like sendsw. It loops until it is finished or an error is encountered. The routines called by this one are responsible for changing the state.] */ recsw () { state = 'R'; /* Receive is the start state */ n = 0; /* Initialize message number */ numtry = 0; /* Say no tries yet */ while (TRUE) switch (state) /* Do this for as long as necessary */ { case 'D': state = rdata(); break; /* Data receive state */ case 'F': state = rfile(); break; /* File receive state */ case 'R': state = rinit(); break; /* Send initiate state */ case 'C': return(TRUE); /* Complete state */ case 'A': return(FALSE); /* Abort state */ default: return(FALSE); /* Unknown state, abort */ } } /* Finished. @End @SubSection @Begin /*@ptxt[This routine initializes the connection with the foreign host. It waits for the foreign host to initiatate the connection. Upon receipt of a send-initiate packet it sets its variables to what the foreign host requested and calls spack to send an ACK packet with the necessary data for initialization (packet size and time outs). The state is then set to 'F' (File-receive). If the packet received is not a send initiate the state goes to abort. If a packet (receive-packet returns FALSE) is received the state is not changed.] */ rinit () { int len, num; /* Local variables */ if(numtry++ > maxtry) return('A'); /* "Abort" if too many tries */ switch (rpack(&len,&num,packet)) /* Get a packet */ { case 'S': /* Send-Init - Get parameters */ { n = num; /* Synchronize packet numbers */ rpar(packet); /* Get the init data */ spar(packet); /* Fill up packet with init info */ spack('Y',n,6,packet); /* ACK with my parameters */ oldtry = numtry; /* Save old try count */ numtry = 0; /* And start a new counter */ n = (n + 1) % 64; /* Bump packet number */ return('F'); /* OK, now enter File-Send state */ } case FALSE: return(state); /* Oops, we didn't receive a packet */ default: return('A'); /* Unknown type, "Abort" */ } } /* Return @End @SubSection @Begin /*@ptxt[The rest of the receive routines are similar to this one. If the expected packet is received (in this case an 'F' packet with the correct message number) the packet is ACKed and the state changed (in this case to 'D', Data-receive). In this routine, if no file name was specified by the user the one supplied by the foreign host in the 'F' packet is used to build one. If the packet is an 'S' or 'Z' packet with the previous message number, the routine ACKs the previous packet again and returns without changing state. If a 'B' is received then there are no more files and the state goes to 'C'. If any other packet is received the state goes to "Abort".] */ char rfile () { int num, len; if(numtry++ > maxtry) return('A'); /* "Abort" if too many tries */ switch (rpack(&len,&num,packet)) /* Get a packet */ { /* Send-Init */ case 'S': { if(oldtry++ > maxtry) return('A'); /* If too many tries */ if(num == n - 1) /* Check packet number */ { spar(packet); /* Get the init params */ spack('Y',num,6,packet); /* It's right, ACK it */ numtry = 0; /* Reset try counter */ return(state); } else return('A'); /* Sorry, wrong number */ } /* /* End-of-File */ case 'Z': { if(oldtry++ > maxtry) return('A'); if(num == n - 1) /* Acknowledge good packet */ { spack('Y',num,0,0); numtry = 0; return(state); } else return('A'); /* or "Abort" on bad one */ } /* /* File-Header */ case 'F': { if(num != n) return('A'); getfil(packet); /* Open the file */ spack('Y',n,0,0); /* Acknowledge */ oldtry = numtry; numtry = 0; n = (n + 1) % 64; return('D'); /* Switch to data state */ } /* /* Break transmission */ case 'B': { if(num != n) return('A'); spack('Y',n,0,0); /* Say OK */ return('C'); /* and go to Complete state */ } case FALSE: return(state); default: return('A'); } } /* Return @End @SubSection @Begin /*@ptxt[This routine is almost the same as Rfile except that it must take into account that its previous state could have been either 'F' or 'D'. If packets of either type with the previous message number are received the previous packet is again acknowledged. Also if the packet is a 'Z' (end of file) the packet is ACKed, any necessary file closing is done and the state becomes 'F'. If a data packet is received, bufemp is called to write the characters into the target file. To prevent losing packets while writing, the packet should not be ACKed until the characters are written out.] */ char rdata () { int num, len; if(numtry++ > maxtry) return('A'); /* "Abort" if too many tries */ switch (rpack(&len,&num,packet)) /* Get a packet */ { /* Data */ case 'D': { if(num != n) /* Right packet? */ { /* No */ if(oldtry++ > maxtry) return('A'); /* If too many tries */ if(num == n - 1) /* Check packet num */ { spack('Y',num,0,0); /* It's right, ACK it */ numtry = 0; /* Reset try counter */ return(state); } else return('A'); /* Sorry, wrong number */ } /* /* Data with right packet number */ bufemp(packet,fptr,len); /* Write out the packet */ spack('Y',n,0,0); /* Right packet, acknowledge */ oldtry = numtry; numtry = 0; n = (n + 1) % 64; return('D'); /* Remain in data state */ } /* /* File-Send */ case 'F': { if(oldtry++ > maxtry) return('A'); if(num == n - 1) /* Acknowledge good packet */ { spack('Y',num,0,0); numtry = 0; return(state); } else return('A'); /* or "Abort" on bad one */ } /* /* End-of-File */ case 'Z': { if(num != n) return('A'); spack('Y',n,0,0); /* Say OK */ close(fptr); /* Close up the file */ return('F'); /* and go to File state */ } case FALSE: return(state); default: return('A'); } } /* Return @End @Section @Begin /*@ptxt[Converts a control character to a printable one by adding a space.] */ char tochar (ch) char ch; { return(ch + ' '); /* Make sure not a control char */ } /* Undoes tochar. */ char unchar (ch) char ch; { return(ch - ' '); /* Restore char */ } /*@ptxt[Turns a control character into a printable character by toggling the @i bit (i.e. ^A becomes A and A becomes ^A.)] */ char ctl (ch) char ch; { return(ch ^ 64); /* Toggle the "Control" bit */ } /* /*@ptxt[Fill the array data with the appropriate Send-init parameters.] */ spar (data) char data[]; { printf("spar\n"); data[0] = tochar(maxpack); /* Biggest packet I can receive */ data[1] = tochar(mytime); /* When I want to be timed out */ data[2] = tochar(mypad); /* How much padding I need */ data[3] = ctl(mypchar); /* Padding character I want */ data[4] = tochar(myeol); /* End-Of-Line character I want */ data[5] = myquote; /* Quote character I send */ } /* /*@ptxt[Get the other host's Send-init parameters.] */ rpar (data) char data[]; { printf("rpar\n"); spsiz = unchar(data[0]); /* Maximum send packet size */ timint = unchar(data[1]); /* When I should time out */ pad = unchar(data[2]); /* Number of pads to send */ padchar = ctl(data[3]); /* Padding character to send */ eol = unchar(data[4]); /* EOL character I must send */ quote = data[5]; /* Incoming data quote character */ }  .