Permalink
Switch branches/tags
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
608 lines (519 sloc) 22.1 KB
/* SEAlink - Sliding window file transfer protocol
Version 1.20, created on 08/05/87 at 17:51:40
Copyright 1986,87 by System Enhancement Associates; ALL RIGHTS RESERVED
By: Thom Henderson
Description:
This file contains a set of routines to illustrate the SEAlink
sliding window file transfer protocol. SEAlink is fully backward
compatible to XMODEM, and can be easily adapted to most XMODEM
variants.
The intent of SEAlink is to provide a file transfer protocol that
does not suffer from propagation delays, such as are introduced
by satellite relays or packet switched networks.
Instructions:
Two routines are provided to implement SEAlink file transfers.
int xmtfile(name) /+ transmit a file +/
char *name; /+ name of file to transmit +/
This routine is used to send a file. One file is sent at a time.
If the name is blank (name is null or *name points to a null),
then only an end of transmission marker is sent.
This routine returns a one if the file is successfully
transmitted, or a zero if a fatal error occurs.
char *rcvfile(name) /+ receive a file +/
char *name; /+ name of file (optional) +/
This routine is used to receive a file. One file is received.
The name, if given, takes precedence and will be the name of
the resulting file. If the name is blank (name is null or *name
points to a null), then the name given by the transmitter is used.
If the transmitter does not give a name, then the file transfer
is aborted.
This routine returns a pointer to the name of the file that
was received. If the file transfer is not successful, then
a null pointer is returned.
The pointer returned by rcvfile() points to a static data buffer.
This does not have to be freed (and should not be), but it will
be overwritten the next time rcvfile() is called.
The rcvfile() function works on a temporary file whose name is
the same as the final file, but with a dash ("-") added at the
beginning. If a file transfer is aborted, then this temporary
file will be retained. An aborted file transfer will not harm
a pre-existing file of the same name.
Programming notes:
These routines can be used for either single or multiple file
transfers.
To send multiple files, send each one one at a time until either
a transmit fails or all files are sent. If all files are sent,
then signal the end by calling xmtfile() with a null pointer.
To receive multiple files, call rcvfile() repeatedly until it
returns a null pointer.
These routines pass a "block zero", which contains information
about the original file name, size, and date/time of last
modification. If you cannot implement block zero, then you can
leave it out. If you cannot set any given field in block zero
when transmitting, then you should leave it set to zeros. If you
cannot use any given field of block zero when receiving, then
you should ignore it.
These routines are fully compatible with XMODEM, including the
original checksum method and later CRC adaptations. It can be
easily adapted to Modem7 protocol by adding a Modem7 filename
transfer shell, though we do not recommend it. The underlying
logic, of course, can be adapted to almost any variant of XMODEM.
License:
You are granted a license to use this code in your programs, and
to adapt it to your particular situation and needs, subject only
to the following conditions:
1) You must refer to it as the SEAlink protocol, and you must
give credit to System Enhancement Associates.
2) If you modify it in such a way that your version cannot
converse with the original code as supplied by us, then
you should refer to it as "SEAlink derived", or as a
"variation of SEAlink", or words to that effect.
In short, we're not asking for any money, but we'd like to
get some credit for our work.
Language:
Computer Innovations Optimizing C86
*/
#include <stdio.h>
#include "fstat.h"
#include <time.h>
struct zeros /* block zero data structure */
{ long flen; /* file length */
long fstamp; /* file date/time stamp */
char fnam[17]; /* original file name */
char prog[15]; /* sending program name */
char noacks; /* true if ACKing not required */
char fill[87]; /* reserved for future use */
} ;
#define ACK 0x06
#define NAK 0x15
#define SOH 0x01
#define EOT 0x04
#define WINDOW 6 /* normal window size */
static int outblk; /* number of next block to send */
static int ackblk; /* number of last block ACKed */
static int blksnt; /* number of last block sent */
static int slide; /* true if sliding window */
static int ackst; /* ACK/NAK state */
static int numnak; /* number of sequential NAKs */
static int chktec; /* check type, 1=CRC, 0=checksum */
static int toterr; /* total number of errors */
static int ackrep; /* true when ACK or NAK reported */
static int ackseen; /* count of sliding ACKs seen */
char *progname = "Your name here"; /* name of sending program */
int ackless = 0; /* true if ACKs not required */
/* File transmitter logic */
int xmtfile(name) /* transmit a file */
char *name; /* name of file to send */
{
FILE *f, *fopen(); /* file to send */
long t1, timerset(); /* timers */
int endblk; /* block number of EOT */
struct filstat fst; /* data about file */
struct zeros zero; /* block zero data */
if(name && *name) /* if sending a file */
{ if(!(f=fopen(name,"rb")))
{ printf(" Can't read %s\n",name);
return 0;
}
setmem(&zero,sizeof(zero),0); /* clear out data block */
filestat(name,&fst); /* get file statistics */
zero.flen = fst.fs_fsize;
zero.fstamp = fst.fs_abs;
strcpy(zero.fnam,fst.fs_fname);
strcpy(zero.prog,progname);
zero.noacks = ackless;
endblk = ((zero.flen+127)/128) + 1;
printf(" Ready to send %d blocks of %s\n",endblk-1,zero.fnam);
}
else endblk = 0; /* fake for no file */
outblk = 1; /* set starting state */
ackblk = -1;
blksnt = slide = ackst = numnak = toterr = ackrep = ackseen = 0;
chktec = 2; /* undetermined */
t1 = timerset(300); /* time limit for first block */
printf(" Waiting...\r");
while(ackblk<endblk) /* while not all there yet */
{ if(!carrier())
{ printf("\n Lost carrier\n");
goto abort;
}
if(key_scan()!=EOF)
{ if((key_getc()&0xff)==27)
{ printf("\n Aborted by operator\n");
goto abort;
}
}
if(timeup(t1))
{ printf("\n Fatal timeout\n");
goto abort;
}
if(outblk <= ackblk + (slide? WINDOW : 1))
{ if(outblk<endblk)
{ if(outblk>0)
sendblk(f,outblk);
else shipblk(&zero,0);
if(ackrep)
printf(" Sending block #%d \r",outblk);
if(ackless && slide)
{ if(!(outblk%10))
printf("\r Passing block %d \t\t\r",outblk);
ackblk = outblk;
}
}
else if(outblk==endblk)
{ com_putc(EOT);
if(ackrep)
printf(" Sending EOT \r");
}
outblk++;
t1 = timerset(300); /* time limit between blocks */
}
ackchk();
if(numnak>10)
{ printf("\n Too many errors\n");
goto abort;
}
}
printf(" End of file \n");
if(endblk)
fclose(f);
if(toterr>2)
printf(" %d errors detected and fixed in %d blocks.\n",
toterr,blksnt);
return 1; /* exit with good status */
abort:
if(endblk)
fclose(f);
if(toterr)
printf(" %d errors detected and fixed in %d blocks.\n",
toterr,blksnt);
return 0; /* exit with bad status */
}
/* The various ACK/NAK states are:
0: Ground state, ACK or NAK expected.
1: ACK received
2: NAK received
3: ACK, block# received
4: NAK, block# received
5: Returning to ground state
*/
static ackchk() /* check for ACK or NAK */
{
int c; /* one byte of data */
static int rawblk; /* raw block number */
ackrep = 0; /* nothing reported yet */
while((c=com_getc(0))!=EOF)
{ if(ackst==3 || ackst==4)
{ slide = 0; /* assume this will fail */
if(rawblk == (c^0xff)) /* see if we believe the number */
{ rawblk = outblk - ((outblk-rawblk)&0xff);
if(rawblk >= 0 && rawblk<=outblk && rawblk>outblk-128)
{ if(ackst==3) /* advance for an ACK */
{ ackblk = ackblk>rawblk? ackblk : rawblk;
slide = 1;
if(ackless && ++ackseen>10)
{ ackless = 0; /* receiver not ACKless */
printf("- Overdrive disengaged \n");
}
printf("\r ACK %d ==",rawblk);
}
else /* else retransmit for a NAK */
{ outblk = rawblk<0? 0 : rawblk;
slide = numnak<4;
printf("\r NAK %d ==",rawblk);
}
ackrep = 1; /* we reported something */
}
}
ackst = 5; /* return to ground state */
}
if(ackst==1 || ackst==2)
{ rawblk = c;
ackst += 2;
}
if(!slide || ackst==0)
{ if(c==ACK)
{ if(!slide)
{ ackblk++;
printf("\r ACK %d --",ackblk);
ackrep = 1; /* we reported an ACK */
}
ackst = 1;
numnak = 0;
}
else if(c=='C' || c==NAK)
{ if(chktec>1) /* if method not determined yet */
chktec = (c=='C'); /* then do what rcver wants */
com_dump(); /* purge pending output */
delay(6); /* resynch */
if(!slide)
{ outblk = ackblk+1;
printf("\r NAK %d --",ackblk+1);
ackrep = 1; /* we reported a negative ACK */
}
ackst = 2;
numnak++;
if(blksnt) toterr++;
}
}
if(ackst==5)
ackst = 0;
}
}
static sendblk(f,blknum) /* send one block */
FILE *f; /* file to read from */
int blknum; /* block to send */
{
long blkloc; /* address of start of block */
char buf[128]; /* one block of data */
if(blknum != blksnt+1) /* if jumping */
{ blkloc = (long)(blknum-1) * 128L;
fseek(f,blkloc,0); /* move where to */
}
blksnt = blknum;
setmem(buf,128,26); /* fill buffer with control Zs */
fread(buf,1,128,f); /* read in some data */
shipblk(buf,blknum); /* pump it out the comm port */
}
static int shipblk(blk,blknum) /* physically ship a block */
char *blk; /* data to be shipped */
int blknum; /* number of block */
{
char *b = blk; /* data pointer */
int crc = 0; /* CRC check value */
int n; /* index */
com_putc(SOH); /* block header */
com_putc(blknum); /* block number */
com_putc(blknum^0xff); /* block number check value */
for(n=0; n<128; n++) /* ship the data */
{ if(chktec)
crc = crc_update(crc,*b);
else crc += *b;
com_putc(*b++);
}
if(chktec) /* send proper check value */
{ crc = crc_finish(crc);
com_putc(crc>>8);
com_putc(crc&0xff);
}
else com_putc(crc);
return 1;
}
/* File receiver logic */
char *rcvfile(name) /* receive file */
char *name; /* name of file */
{
int c; /* received character */
int tries; /* retry counter */
long t1, timerset(); /* timer */
int blknum; /* desired block number */
int inblk; /* this block number */
FILE *f, *fopen(); /* file, opener */
char buf[128]; /* data buffer */
char tmpname[100]; /* name of temporary file */
static char outname[100]; /* name of final file */
struct zeros zero; /* file header data storage */
int endblk; /* block number of EOT, if known */
long left; /* bytes left to output */
int n; /* index */
char *stat = "Init"; /* receive block status */
char *getblock(), *why; /* single block receiver, status */
if(name && *name) /* figure out names to use */
{ makefnam("X:\\",name,outname);
outname[2] = '-';
makefnam(outname+2,name,tmpname);
strcpy(outname,name);
}
else
{ *outname = '\0';
strcpy(tmpname,"-TMPFILE.$$$");
}
if(!(f=fopen(tmpname,"wb"))) /* open output file */
{ printf(" Cannot create %s\n",tmpname);
return NULL;
}
blknum = *outname? 1 : 0; /* first block we must get */
tries = -10; /* kludge for first time around */
chktec = 1; /* try for CRC error checking */
toterr = 0; /* no errors yet */
endblk = 0; /* we don't know the size yet */
ackless = 0; /* we don't know about this yet */
setmem(&zero,sizeof(zero),0); /* or much of anything else */
if(com_scan()==SOH) /* kludge for adaptive Modem7 */
goto nextblock;
nakblock: /* we got a bad block */
if(blknum>1) toterr++;
if(++tries>10)
{ printf("\n Too many errors\n");
goto abort;
}
if(tries==0) /* if CRC isn't going */
chktec = 0; /* then give checksum a try */
sendack(0,blknum); /* send the NAK */
printf(" NAK block %d %-5s\r",blknum,stat);
if(ackless && toterr>20) /* if ackless mode isn't working */
{ ackless = 0; /* then shut it off */
printf("- Overdrive disengaged\n");
}
goto nextblock;
ackblock: /* we got a good block */
if(!ackless)
printf(" ACK block %d %-5s\r",blknum-1,stat);
else if(!(blknum%10))
printf(" Got block %d \r",blknum);
nextblock: /* start of "get a block" */
stat = "";
if(!carrier())
{ printf("\n Lost carrier\n");
goto abort;
}
if(key_scan()!=EOF)
{ if((key_getc()&0xff)==27)
{ printf("\n Aborted by operator\n");
goto abort;
}
}
t1 = timerset(30); /* timer to start of block */
while(!timeup(t1))
{ c = com_getc(0);
if(c==EOT)
{ if(!endblk || endblk==blknum)
goto endrcv;
}
else if(c==SOH)
{ inblk = com_getc(5);
if(com_getc(5) == (inblk^0xff))
goto blockstart; /* we found a start */
}
}
stat = "Time";
goto nakblock;
blockstart: /* start of block detected */
c = blknum&0xff;
if(inblk==0 && blknum<=1) /* if this is the header */
{ if(!(why=getblock(&zero)))
{ sendack(1,inblk); /* ack the header */
if(!*name) /* given name takes precedence */
strcpy(outname,zero.fnam);
if(left=zero.flen) /* length to transfer */
endblk = (left+127)/128 + 1;
if(ackless!=zero.noacks) /* note variant employed */
printf("+ Overdrive %sengaged\n",zero.noacks?"":"dis");
ackless = zero.noacks;
printf(" Receiving");
if(endblk)
printf(" %d blocks of",endblk-1);
printf(" %s",outname);
if(*zero.prog)
printf(" from %s",zero.prog);
printf("\n");
blknum = 1; /* now we want first data block */
goto ackblock;
}
else
{ stat = why;
goto nakblock; /* bad header block */
}
}
else if(inblk==c) /* if this is the one we want */
{ if(!(why=getblock(buf))) /* else if we get it okay */
{ if(!ackless) /* if we're sending ACKs */
sendack(1,inblk); /* then ACK the good data */
for(n=0; n<128; n++)
{ if(endblk) /* limit file size if known */
{ if(!left)
break;
left--;
}
if(fputc(buf[n],f)==EOF)
{ printf("\n Write error (disk full?)\n");
goto abort;
}
}
tries = 0; /* reset try count */
blknum++; /* we want the next block */
goto ackblock;
}
else
{ stat = why;
goto nakblock; /* ask for a resend */
}
}
else if(inblk<c || inblk>c+100) /* else if resending what we have */
{ getblock(buf); /* ignore it */
sendack(1,inblk); /* but ack it */
stat = "Dup";
goto ackblock;
}
else goto nextblock; /* else if running ahead */
endrcv:
sendack(0,blknum);
printf(" NAK EOT \r");
if(com_getc(20)!=EOT)
goto nakblock;
sendack(1,blknum);
printf(" ACK EOT\r");
if(blknum>1) /* if we really got anything */
{ if(toterr>2)
printf(" %d errors detected and fixed in %d blocks.\n",
toterr,blknum-1);
if(zero.fstamp) /* set stamp, if known */
setstamp(f,zero.fstamp);
fclose(f);
unlink(outname); /* rename temp to proper name */
rename(tmpname,outname);
return outname; /* signal what file we got */
}
else /* else no real file */
{ fclose(f);
unlink(tmpname); /* discard empty file */
return NULL; /* signal end of transfer */
}
abort:
if(toterr)
printf(" %d errors detected and fixed in %d blocks.\n",
toterr,blknum-1);
fclose(f);
return NULL;
}
static sendack(acknak,blknum) /* send an ACK or a NAK */
int acknak; /* 1=ACH, 0=NAK */
int blknum; /* block number */
{
if(acknak) /* send the right signal */
com_putc(ACK);
else if(chktec)
com_putc('C');
else com_putc(NAK);
com_putc(blknum); /* block number */
com_putc(blknum^0xff); /* block number check */
}
static char *getblock(buf) /* read a block of data */
char *buf; /* data buffer */
{
int ourcrc = 0, hiscrc; /* CRC check values */
int c; /* one byte of data */
int n; /* index */
int timeout = ackless? 200 : 5; /* short block timeout */
for(n=0; n<128; n++)
{ if((c=com_getc(timeout))==EOF)
return "Short";
if(chktec)
ourcrc = crc_update(ourcrc,c);
else ourcrc += c;
*buf++ = c;
}
if(chktec)
{ ourcrc = crc_finish(ourcrc);
hiscrc = (com_getc(timeout)<<8) | com_getc(timeout);
}
else
{ ourcrc &= 0xff;
hiscrc = com_getc(timeout) & 0xff;
}
if(ourcrc == hiscrc)
return NULL; /* block is good */
else if(chktec)
return "CRC"; /* else CRC error */
else return "Check"; /* or maybe checksum error */
}