home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Shareware Overload
/
ShartewareOverload.cdr
/
comm
/
zmax.zip
/
ZMAX.C
< prev
next >
Wrap
Text File
|
1990-08-16
|
60KB
|
1,752 lines
/*
LEGAL STUFF
------------------------------------------------------------------------------
The protocol, documentation, and Zmax.c routines are by Mike Bryeans.
Zmax is NOT public domain. Micro TECH Systems Retains ALL RIGHTS TO
THIS PROTOCOL.
No PART of Zmax may be used in development of a NON-ZMAX protocol.
Developers are granted a license to use Zmax in NON-COMMERCIAL
applications and environments ONLY.
A 10 dollar donation is requested to help
offset development costs and to help with
the salaries of the technical support
person(s).
If you application is intended for use in a program that is to be sold
commercially, including shareware, you must apply for a Zmax COMMERCIAL
License.
If you intend to develop a Zmax driver for USE in a commercial
environment, you must apply for a Zmax COMMERCIAL license.
All goverment agents Federal, State, and Local must apply for a Zmax
COMMERICAL license.
You may NOT modify Zmax specifications. Zmax MUST remain uniform and
only Micro TECH Systems may issue NEW or IMPROVED Zmax specifications.
If you have a particular item in mind, submit it along with a modified
Zmax.c (Using our Zmax.c driver code) to Micro TECH Systems.
Micro TECH Systems
555 North Spring, #39
Cape Girardeau Mo. 63701
Data Number 1-314-334-6359
Zmax specifications, ver. 1.00
This is a full functional, tested, driver. I've attempted to keep
the code as simple as possible and use terms that anyone thats
written any type of protocol knows since its to be used as a guide
to other implementations.
Zmax was designed with reliablity in mind as the FIRST and foremost
concern, speed came in a close second. You'll no doubt notice a couple
of areas where it could be speeded up and I was tempted. Zmax isn't
the FASTEST protocol around, but then it wasn't suppose to be.
I wanted the best all round protocol which would always get the file
there intact and as quickly as possible. I beleive you'll be very hard
pressed to find any protocol that will best Zmax in RELIABLITY, FLEXABLITY,
and SPEED.
*/
/* Include files */
#include<stdio.h>
#include "zmax.h"
#include<malloc.h>
#include<filedata.h>
#include<comm.h>
#include<ctype.h>
/* Globle Declares */
FILE *out;
unsigned long UpdCrc32(char byte, unsigned long crc);
long file_position,seof;
int cancel,percent_size,total_errors = 0,endsend = 0,delete_aborted_transfers = 1;
struct filedata filstruc;
long locked_port, atol(), timerset(),amt_sent;
char *Screen_ptr;
char acks_required=0;
char tbuf[2048],junk_string[81];
int block_len,return_code = 1,port_ptr;
unsigned cur_baud,connect_rate,sending_file;
ASYNC *port;
int IOadrs = 0x3f8; /* comm port base I/O address */
char irqm = IRQ4; /* mask for selected IRQ line */
char vctrn = VCTR4; /* comm interrupt vector nbr */
char log_file[81];
/* Zmax Structures */
struct Zhead /* File info data structure */
{ long flen; /* file length */
unsigned fdate; /* Files DATE stamp, DOS format */
unsigned ftime; /* Files TIME stamp, DOS format */
char fnam[14]; /* original file name */
} ;
struct _block_header
{
long position;
unsigned bytes;
char ack; /* If on (set to 1) the receiver must
ACK data block if good.
*/
};
struct _info_header
{
unsigned flags;
/* 00000000 00000001 = Can handle Continues Stream Transmissions.
If this flag is off, ACK is required following
each block of data.
ACKING each block of data turns Zmax into a
X/Ymodem Batch type protocol.
It is suggested that once you reach 128
byte blocks, bad lines, Zmax should
require each block of data to be ACKED.
This will switch it to a batch
Xmodem style protocol which is
better for bad phone lines.
You'll also notice that although there are
provisions for decreasing the block size
when noisy lines are encountered, you don't
see any place where I raise the block size back
up.
There is nothing saying you CAN'T do that, I just
don't beleive you should. If you encounter
enough noise to cause the block size to drop, then
the likely hood of you encountering MORE is very high.
If you do raise the block size back up, I would recommend
that you do it based on TIME and not number of good block
sent. Given that 13 blocks (256 byte blocks) can be
stuffed into a 3K TX buffer, you may THINK you've sent
13 good blocks when in fact the very next one you sent
could be bad. I think 10 to 20 seconds without an error
would be a good time frame.
You'll also notice that I do not drop the block size
on the first error. One error does not make for bad
phone lines, 2 or more does.
If you decide to use large blocks than I do here,
then you may want to go ahead and drop them on the
first error.
*/
int buf_size; /* Used to pass transmitter the receivers max
buffer size. Its is suggested that the receiver
support 2K buffer.
The transmitter doesn't have to give you those
sizes and can replace them with his own as long
as it is less than or equal to your max buffer
size.
Nor do the block sizes have to be powers of 2.
You can use odd block sizes...231, 888, etc.
I used powers of 2 because of the way DOS formats
harddrives, ie. cluster size.
You'll also notice that Zmax places little restrictions
on the maximum SIZE of the block although going over
2048 shows little gain and lots of loses when a resend
is requested.
To give you some guidelines for setting block sizes
see chart below:
Chart based on sending a 60,416 byte file WITH the
protocol overhead figured in and driving the serial
port to its maximum Cps at that baud rate which this
driver does.
Blk Size Tx Time
Bytes 2400 9600 LOCKED Port.
64 318.60 79.65
128 285.17 71.29
256 268.45 67.11
512 260.09 65.02
1024 255.91 63.98
2048 253.82 63.46
4096 252.78 63.20
8192 252.26 63.07
As you can see, there's quite a bit of savings
across the baud rate scale with each increament
of the block size up to 2048 bytes. Once you exceed
2048 BPB, the savings is almost not measureable.
Taking into account the time required to resend 2K of
data at 2400 bps, I wouldn't risk going over 1K at 2400
unless the call is local or under MNP.
You may REQUEST any size data block up to
the size of a signed integer.
*/
unsigned char version ; /*
This allows for future expansion to the protocol
and still yet allow for backwards compatibly.
This is the version of the protocol specs that the
receiver supports. Currently this should be set to one.
*/
char empty[123]; /* Reserves an extra 123 bytes for future expansion */
};
/* DEFINES */
#define STX 2
#define TSYNCH 0xae
#define ACK 0x06
#define NAK 0x15
#define SOH 0x01
#define EOT 0x04
#define CAN 0x18
#define RESYNC 9
#define PER_WEEK 60480000L
#define PER_DAY 8640000L
#define PER_HOUR 360000L
#define PER_MINUTE 6000L
#define PER_SECOND 100L
#define ENQ 0x05
main(int argc, char *argv[])
{
/*
This is the main section of this driver. It's makeup is entirely
up to you. With of course, several REQUIRED sets.
*/
int i,x,count,can_display_user_name=0;
int y=0,block_size=1024;
int send=0,do_acks=0;
char s[81],s1[81];
FILE *in;
qvideo();
clrscrn();
Screen_ptr = malloc(1 * 4000);
if(Screen_ptr == NULL) exit(1);
uncomprs(&Screen_ptr[0*4000], tmod); /* uncompress screen 1 */
memtoscr(2000, 0, Screen_ptr);
curoff();
memset(s,'\0',sizeof(s));
if(!envget2("OSIRIS",s))
{
sprintf(log_file,"%s\\Zmax.log",s);
can_display_user_name=1;
}
else
sprintf(log_file,"Zmax.log");
for(i=1;i<argc;i++)
{
if(argv[i][0] == '/' || argv[i][0] == '-')
{
switch(toupper(argv[i][1]))
{
case 'N' :
if(can_display_user_name)
{
sprintf(s1,"%s\\node%d",s,atoi(argv[i+1]));
in = fopen(s1,"r");
fgets(s,80,in);
fclose(in);
s[strlen(s)] = '\0';
curlocat(23,2);
colrprts(s,15,1);
}
break;
case 'S' : block_size = atoi(argv[i+1]);
if(block_size > 2048)
block_size = 2048;
break;
case 'A' : do_acks = 1;break;
case 'K' : delete_aborted_transfers = 0;
break;
case 'P' : port_ptr = atoi(argv[i+1]);
port_ptr--;
break;
case 'B' : connect_rate = atoi(argv[i+1]);
cur_baud = connect_rate;
block_size = (connect_rate > 2400) ? 2048 : 1024;
break;
case 'L' : locked_port = atol(argv[i+1]);
break;
case 'R' : set_com();
/*
You should continue to cycle receiving files until
Zrecfile returns a ZERO, indicating that it did not
receive any files or the session was aborted.
*/
while ( Zrecfile(do_acks,block_size) && (async_carrier(port))) clear_screen() ;
/* Following the last file, the sender will require an ACK
to signal the receivers acknowledges end of session.
The Sender doesn't respond back after receiving the ACK so
continue with you processing after sending a couple of
ACKS. Two is suggested, an extra incase line noise blew the
first one.
*/
com_putc(ACK);
com_putc(ACK);
end_prg();
break;
case 'F' : set_com();
i++;
for(;i<argc;i++)
{
memset(s,'\0',sizeof(s));
parser(argv[i],&s[0]);
/* Cycle until all files are sent or Zsendfile returns
a zero indicating Session was aborted. */
acks_required = 0;
if(!Zsendfile(argv[i],s))
end_prg();
if(!async_carrier(port)) end_prg();
clear_screen();
}
i = count = 0;
/*
Signal end of Batch Transfer.
Cycle sending EOT until receiver sees it and ACKs
the ending EOT. I suggest cycling only two periods
, period being defined as trying ACKEOT function twice.
If your using Zmax in a mailer environment and as the
sender you find that you have NOTHING to send, then bypass
the Zsendfile routine and go directly to this section.
*/
while(!count && i <= 1)
{
com_putc(EOT);
i++;
if(ackeot()) count = 1;
if(!async_carrier(port)) count = 1;
}
end_prg();
break;
}
}
}
}
Zsendfile(name,s_name) /* transmit a file */
char *name; /* FULL Drive, path, and name of file to send */
char s_name[]; /* Only the filename, fit to be used in file info
header.
*/
{
long location, blkloc,size,st,ft,t1,t2;
int error=0,endblk,x,y,i,cps;
struct Zhead zero;
struct _info_header info_header;
unsigned file_date,file_time,actual;
char s[80];
int ok;
struct _block_header block_header;
if(!async_carrier(port)) return(0);
acks_required = 1;
async_txflush(port);
file_position = 0L;
if(!filexist(name)) return(1);
x = openfil(name,2,&sending_file);
getstamp(sending_file,&file_date,&file_time);
memset(&zero,'\0',sizeof(zero)); /* clear out data block */
filsize(name,&size);
percent_size = size / 40;
seof = size;
strcpy(zero.fnam,s_name);
zero.fdate = (unsigned) file_date;
zero.ftime = (unsigned) file_time;
zero.flen = (long) size;
curlocat(5,28);
colrprtf(0,0,7,"%-14s",zero.fnam);
curlocat(6,28);
colrprtf(0,0,7,"%-7ld",size);
amt_sent = 0L;
total_errors = cancel = 0;
t1 = timerset(6000L); /* time limit for first block */
com_putc(TSYNCH);
com_putc(TSYNCH);
endsend = i = 0;
while(x != 'C' && !timeup(t1)) /* Wait for primer start */
{
x = com_getc(2);
if(x != 'C')
com_putc(TSYNCH);
if(!async_carrier(port)) return(0);
if(x == 0x18) cancel++;
if(cancel > 3)
{
curlocat(7,28);
colrprtf(0,0,7,"%-40s","Rcv Canceled Transfer");
closefil(sending_file);
end_prg();
}
}
cancel = 0;
if(x != 'C')
goto abort; /* If we did not get it then abort */
ok = 0;
while(!get_system_info(&info_header) && async_carrier(port))
{
if(++ok > 3)
async_dtr(port,0); /* After 3 attempts, drop carrier
and bail out.
*/
}
if(!async_carrier(port)) goto abort;
block_len = info_header.buf_size;
if(block_len > 2048)
block_len = 2048;
if( (info_header.flags&0x0001))
acks_required = 0;
cancel = ok = 0;
curlocat(7,28);
colrprtf(0,0,7,"%-40s","Sending File Header");
while(!ok)
{
async_rxflush(port);
SendHeader(&zero);
switch(wait_ack())
{
case 1: break; /* I treat both an Abort and
a duplicate the same to save
a little space. You don't have
to.
case 2:
closefil(sending_file);
curlocat(7,28);
colrprtf(0,0,7,"%-40s","Rcv Already has File");
return(1);
case 3:/* REPOS ie. Restart */
default: curlocat(7,28);
sprintf(junk_string,"BufSize %d (%sStream Mode)",info_header.buf_size,(acks_required) ? "Non-" : "");
colrprtf(0,0,7,"%-40s",junk_string);
ok = 1;
break;
}/* End switch */
}
st = timerset(0L);
async_rxflush(port);
async_txflush(port);
do
{
if( !async_carrier(port) )
goto abort;
if(timeup(t1))
goto abort;
if(checkkey())
{
i = getkey(&x);
if(x == 27)
{
while(!async_txempty(port)){
if(!async_carrier(port)) break;
}
cancel_transfer();
goto abort;
}
}
ChkForNak();
while(file_position < zero.flen)/* loop until all sent */
{
if(!async_carrier(port))
goto abort;
ChkForNak();
if(checkkey())
{
i = getkey(&x);
if(x == 27)
{
while(!async_txempty(port)){
if(!async_carrier(port)) break;
}
cancel_transfer();
goto abort;
}
}
/*
NOTE: My file functions; seekfil,
readfil, creatfil, openfil, writefil,
etc. all have built in error handling
routines to reduce redundent coding on
MY part. You should add your own error
trapping according to what your using.
*/
i = seekfil(sending_file,0,file_position,&location);
i = readfil(sending_file,block_len,tbuf,&actual);
/*
Sending the current file position with
each data block removes all
possiblities of Desqview shifting the
file position, a network or satellite
link mis-routing or losing a block of
data. I've seen both happen before and
they weren't detected until the end of
file.
This totally insures that the receiver
and sender are never out of sync.*/
block_header.position = file_position;
block_header.bytes = actual;
block_header.ack = (acks_required) ? 1 : 0 ;
file_position = (long) file_position + (long) actual;
amt_sent = (long) file_position;
com_putc(SOH);
com_putc(ENQ);
send_block_header(&block_header);
SendDataBlock(tbuf,actual);
if(block_header.ack)
{
while(!async_txempty(port))
if(!async_carrier(port)) break; /* Wait till
its all out
before beginning
timer*/
t2 = timerset(3000L);/* Give receiver 30
seconds to ACK or
NAK the block.
30 seconds seems
like a lot of time,
but you have to allow
for satellite/network delays,
HST and Telebit ARQ resends.
etc.
*/
ok = i = 0;
while(!timeup(t2) && !ok)
{
i = com_getc(0);
if(i == ACK) ok = 1;
else
if(i == 'C')
{
i = com_getc(2);
if(i == '*')
{
if(GetNewPosition())
ok = 1;
}/* End Get REPOS */
}/* End possible NAK */
} /* End 10 second timer loop */
if(!ok)
async_dtr(port,0); /* No responds in
30 seconds, drop carrier
and get out. */
}/* End wait for ACK */
else
ChkForNak();
if(file_position == zero.flen)
{
com_putc(EOT);
endsend = 0;
t1 = timerset(9000L); /* time limit for last block if ackless(90 secs) */
sprintf(junk_string,"%-7ld",zero.flen);
curlocat(6,58);
colrprtf(0,0,7,"%s",junk_string);
for(i=1;i<=40;i++)
{
curlocat(19,(21+i));
colrprtf(0,15,7,"█");
}
}
ChkForNak();
if(cancel >= 5) goto abort;
}/* Still to go */
ChkForNak();
if(cancel >= 5) goto abort;
}while(!endsend);
/* end main send */
ft = timerset(0L);
closefil(sending_file);
ft = (long) (ft-st) / 100L;
if(ft == 0) ft = 1L;
cps = (int) (zero.flen / ft);
x = (cps*10) / (connect_rate/100);
curlocat(14,28);
colrprtf(0,0,7,"%3d%%",x);
out = fopen(log_file,"a");
fprintf(out,"%d bps %-14s %-7ld %3d%%\n",connect_rate,zero.fnam,zero.flen,x);
fclose(out);
return_code = 0;
return(1); /* exit with good status */
abort:
async_txflush(port);
closefil(sending_file);
return(0); /* exit with bad status */
}
ChkForNak() /* check for bad block */
{
unsigned char c=0;
int q;
top:;
c = com_getc(0);
if(c == 'C' || c == ACK)
{
if(c == ACK)
{
if(file_position == seof)
endsend = 1;
return;
}
q = com_getc(1);
if(q != '*')
return;
async_txflush(port);
q = GetNewPosition();
}
else
if(c == CAN)
cancel++;
if( (async_rxcnt(port)))
goto top;
}
SendDataBlock(blk,size) /* physically ship a block */
char *blk; /* data to be shipped */
int size;/* size of block */
{
register i,x,y;
unsigned long oldcrc;
long location;
static unsigned char ch;
char *b = blk;
char *j = blk;
static int n,q,flow,aa,kk;
while( (async_txblk(port, b, size)) == R_TXERR)
{
curlocat(6,58);
location = (long) file_position - size;
colrprtf(0,0,7,"%-7ld",location);
kk = location / percent_size;
if(kk > 40) kk = 40;
for(aa=1;aa<=kk;aa++)
{
curlocat(19,(21+aa));
colrprtf(0,15,7,"█");
}
if(async_rxcnt(port))
{
flow = (async_peek(port,0)&0xff);
if(flow == 'C')
{
async_txflush(port);
return;
}
else
if(flow != CAN)
flow = async_rx(port); /* Eat non C/CAN char, more
than likely was line noise
*/
}
else
if(!async_carrier(port)) return;
}/* end while*/
oldcrc = 0xFFFFFFFF;
for(n=0;n<size;n++){
oldcrc = UpdCrc32(((unsigned short) (*j++)), oldcrc);
}
oldcrc = ~oldcrc;
for(n=4; --n >=0;)
{
com_putc((unsigned char) oldcrc);
oldcrc >>= 8;
}
if(acks_required)
{
curlocat(6,58);
location = (long) file_position - size;
colrprtf(0,0,7,"%-7ld",location);
kk = location / percent_size;
if(kk > 40) kk = 40;
for(aa=1;aa<=kk;aa++)
{
curlocat(19,(21+aa));
colrprtf(0,15,7,"█");
}
}
}
ackeot()
{
long t;
int c;
t = timerset(300L);
while(!timeup(t))
{
c = com_getc(0);
if(c == ACK)
return(1);
}
return(0);
}
long timerset (t)
long t;
{
long l;
int l2;
int hours,mins,secs,ths,year,month,day;
sysitime (&hours,&mins,&secs,&ths);
sysidate(&year,&month,&day);
l2 = dayofwee(year,month,day);
l = l2 * PER_DAY +
hours * PER_HOUR +
mins * PER_MINUTE +
secs * PER_SECOND +
ths ;
l += t;
return (l);
}
timeup (t)
long t;
{
long l;
l = timerset (0L);
if(l < (t-PER_DAY)) l = l + PER_DAY;
return ((l - t) >= 0L);
}
com_getc(t)
int t;
{
static long t1,t2;
int c;
if(!((c = async_rx(port)) & B_RXEMPTY))
return((c&0xff));
t2 = (long) t * 100L;
t1 = timerset (t2);
while(((c = async_rx(port)) & B_RXEMPTY))
{
if(!async_carrier(port))
{
return(EOF);
}
if (timeup(t1))
{
return(EOF);
}
}
return((c&0xff));
}
wait_ack()
{
int ch;
long t;
struct _block_header block_header;
t = timerset(3000L); /* If no rsponds in 30 seconds, skip file */
for(;;)
{
ch = com_getc(5);
if(ch == 'D')
{
ch = com_getc(5);
if(ch == 'P')
{
com_putc(ACK);
return(2);
}
}/* End if Duplicate File */
if(ch == ACK) return(4);
if(ch == CAN || timeup(t))
{
async_rxflush(port);
return(2);
}
if(ch == RESYNC)
{
if(get_block_header(&block_header))
{
file_position = block_header.position;
return(3);
}
}/* Resync */
if(ch == 'C')
return(1);
}
}
SendHeader(blk) /* physically ship Fileinfo block */
char *blk; /* data to be shipped */
{
register ch,n;
unsigned long oldcrc; /* CRC check value */
com_putc(SOH);
com_putc(NAK);
while( (async_txblk(port, blk, 22)) == R_TXERR);
oldcrc = 0xFFFFFFFF;
for(n=0;n<22;n++){
oldcrc = UpdCrc32(((unsigned short) (*blk++)), oldcrc);
}
oldcrc = ~oldcrc;
for(n=4; --n >=0;)
{
com_putc((unsigned char) oldcrc);
oldcrc >>= 8;
}
}
set_com()
{
char parms[80],s2[30],s3[30];
int ch;
long atol();
if(port_ptr)
{
IOadrs = 0x2f8;
irqm = 0x08;
vctrn = 11;
}
port = malloc(sizeof(ASYNC));
sprintf(s3,"COM%d",port_ptr+1);
memset(s2,'\0',sizeof(s2));
if(!envget2(s3,s2))
locked_port = (long) atol(s2);
if(locked_port)
sprintf(parms,"%ldN81",locked_port);
else
sprintf(parms,"%dN81",cur_baud);
if(!(AllocRingBuffer(port, 2048, 3072, 1)))/* Note that I allocate
a TX buffer 1K larger
than the largest block
size that I support.
This gives me a little
breathing space in STREAM
mode at 9600+ for file management. */
exit(0);
if ((ch = async_open(port, IOadrs, irqm, vctrn, parms)) != R_OK)
exit(0);
async_rxflush(port);
async_txflush(port);
async_msrflow(port, B_CTS);
async_FIFOtxlvl(port,4);/*
I strongly suggest that you keep the
FIFO Tx bytes per interrupt to no more
than 4.
Reason:
This driver will drive the modem so
fast and hard that an HST 450
Backchannel will loose its sync with
an HST Dual Standard or HST 14.4 if
its (450) doing the sending.
*/
async_FIFOrxlvl(port,1);/* Never go over 1 here */
}
parser(s,name) char s[],name[];
{
char stack[80];
int i,x;
if(strindex("\\",s) == -1)
{
name[0] = 0;
strcpy(name,s);
return;
}
memset(stack,'\0',sizeof(stack));
x = strlen(s);
i = 0;
while(s[x] != '\\' && x > 0)
{
if(s[x] != 0 && s[x] != 13 && s[x] != 10) stack[i++] = s[x];
x--;
}
x = 0;
i--;
while(i >= 0)
name[x++] = stack[i--];
name[x] = '\0';
}
end_prg()
{
curon();
if(async_carrier(port))
port->OldMCR |= B_DTR;
async_close(port);
if(!delete_aborted_transfers)
sleep(20);
exit(return_code);
}
Zrecfile(int do_acks,int block_size) /* receive file */
{
int n,w,x,y,tries,i,c,cps;
char outname[81];
int kk,aa;
unsigned send,actual,file_date,file_time;
struct Zhead zero; /* file header data storage */
struct _block_header block_header;
struct _info_header SystemInfo;
long t1,st,ft,lsize,testl;
cancel=tries = total_errors = c = 0;
percent_size = 32000;
memset(outname,'\0',sizeof(outname));
memset(&zero,'\0',sizeof(zero));
amt_sent = 0L;
file_position = 0L;
st = timerset(6000L);
com_putc(' ');
while(!timeup(st))
{
if(!async_carrier(port))
return(0);
c = com_getc(1);
if(c == TSYNCH)
st =timerset(0L);
if(c == EOT)
return(0);
if(c == ENQ)
{
com_putc(ACK);
st = timerset(30000L); /*
This is relevant only if
zmax is being used in a mailer
application.
ENQ at this point means to
extend the time out period to 5 minutes.
This is used primaryly for allowing
service requests time to be processed
by exteranl programs. See Mailer.Doc
for information on service requests.
*/
}
}
if(c != TSYNCH)
{
curlocat(7,28);
colrprtf(0,0,7,"%-40s","Sender flobbed it");
st = timerset(500L);
while(!timeup(st)) ;
return(0);
}
cancel = c = 0;
st = 0L;
com_putc('C');
c = '\0';
memset(&SystemInfo,'\0',sizeof(SystemInfo));
if(!do_acks)
SystemInfo.flags ^= 0x0001;
SystemInfo.buf_size = block_size;
SystemInfo.version = 1;
send_system_info(&SystemInfo);
curlocat(7,28);
colrprtf(0,0,7,"%-40s","Waiting For File");
goto nextblock;
badblock: /* we got a bad block */
if(++tries>10)
{
async_dtr(port,0);
sleep(3);
async_dtr(port,1);
goto abort;
}
async_rxflush(port);
send_resync();
i = seekfil(send,2,0L,&testl);
curlocat(5,58);
colrprtf(0,0,7,"%d",++total_errors);
goto nextblock;
goodblock: /* we got a good block */
if(block_header.ack)
com_putc(ACK);
amt_sent = (long) file_position;
nextblock: /* start of "get a block" */
t1 = timerset(2000L);
while(!timeup(t1))
{
if(checkkey())
{
aa = getkey(&kk);
if(kk == 27)
{
cancel_transfer();
goto abort;
}
}
c = (com_getc(3)&0xff);
switch (c)
{
case CAN : cancel++;
if(cancel >= 5)
goto abort;
break;
case SOH :
x = com_getc(5);
switch(x)
{
case NAK :
if(readblock(&zero,22))
{
st = timerset(0L);
sprintf(outname,"%s",zero.fnam);
if(!filexist(outname))
com_putc(ACK);
if(filexist(outname))
{
openfil(outname,2,&send);
getstamp(send,&file_date,&file_time);
closefil(send);
filsize(outname,&lsize);
if(lsize < zero.flen && zero.fdate == file_date && zero.ftime == file_time)
{
openfil(outname,2,&send);
seekfil(send,2,0L,&file_position);
curlocat(7,28);
sprintf(junk_string,"Re-Start: %ld",file_position);
colrprtf(0,0,7,"%-40s",junk_string);
com_putc(RESYNC);
block_header.position = file_position;
send_block_header(&block_header);
}
else
{
if(lsize == zero.flen && zero.fdate == file_date && zero.ftime == file_time)
{
x = i = 0;
curlocat(7,28);
colrprtf(0,0,7,"%-40s","Duplicate: Asking for Skip");
async_rxflush(port);
com_putc('D');
com_putc('P');
c = com_getc(5);
if(c == ACK)
return(1);
return(0);
}
}
} /* end if exits */
else
i = creatfil(outname,0,&send);
curlocat(5,28);
colrprtf(0,0,7,"%-14s",outname);
curlocat(6,28);
colrprtf(0,0,7,"%-7ld",zero.flen);
percent_size = zero.flen / 40;
curlocat(7,28);
colrprtf(0,0,7,"%-40s","Getting File");
goto goodblock;
}
curlocat(7,28);
colrprtf(0,0,7,"%-40s","Bad File Header");
com_putc('C');
goto nextblock; /* bad header block */
break;
case ENQ :memset(&block_header,'\0',sizeof(block_header));
if(get_block_header(&block_header))
{
if(block_header.position != file_position)
{
curlocat(7,28);
sprintf(junk_string,"Got %ld, Needed %ld",block_header.position,file_position);
colrprtf(0,0,7,"%-40s",junk_string);
goto badblock;
}
if(readblock(&tbuf[0],block_header.bytes)) /* else if we get it okay */
{
n = writefil(send,block_header.bytes,tbuf,&actual);
file_position = (long) file_position + (long) actual;
curlocat(6,58);
colrprtf(0,0,7,"%-7ld",file_position);
cancel = tries = 0;
kk = file_position / percent_size;
if(kk > 40) kk = 40;
for(aa=1;aa<=kk;aa++)
{
curlocat(19,(21+aa));
colrprtf(0,15,7,"█");
}
goto goodblock;
}
curlocat(7,28);
sprintf(junk_string,"Bad Block, REPOS %ld",file_position);
colrprtf(0,0,7,"%-40s",junk_string);
goto badblock;
}
goto badblock;
break;
default :curlocat(7,28);
sprintf(junk_string,"Huh?? (%02x)",x);
colrprtf(0,0,7,"%-40s",junk_string);
goto badblock;
}/* end switch x*/
break;
case EOT :
if(file_position == zero.flen || file_position == 0)
{
com_putc(ACK);
if(file_position == 0) return(0);
ft = timerset(0L);
sprintf(junk_string,"%-7ld",zero.flen);
curlocat(6,58);
colrprtf(0,0,7,"%s",junk_string);
closefil(send);
openfil(outname,2,&send);
setstamp(send,zero.fdate,zero.ftime);
closefil(send);
for(i=1;i<=40;i++)
{
curlocat(19,(21+i));
colrprtf(0,15,7,"█");
}
ft = (long) (ft - st) /100L;
if(ft == 0) ft = 1L;
cps = (int) (zero.flen / ft);
i = (connect_rate/100);
if(i<=0)i=1;
x = (cps*10) / i;
curlocat(14,28);
return_code = 0;
colrprtf(0,0,7,"%3d%%",x);
out = fopen(log_file,"a");
fprintf(out,"%d bps %-14s %-7ld %3d%%\n",connect_rate,zero.fnam,zero.flen,x);
fclose(out);
return(1);
}
break;
}
if(!async_carrier(port)){
goto abort;
}
}
curlocat(7,28);
sprintf(junk_string,"Timeout, Position %ld",file_position);
colrprtf(0,0,7,"%-40s",junk_string);
goto badblock;
abort:
async_rxflush(port);
if(file_position > 0 )
{
closefil(send);
openfil(outname,2,&send);
setstamp(send,zero.fdate,zero.ftime);
closefil(send);
if(delete_aborted_transfers)
unlink(outname);
}
else
return(0);
}
readblock(buf,size) /* read a block of data */
char *buf; /* data buffer */
int size;
{
register n;
unsigned long crc; /* CRC check values */
long t1; /* short block timeout */
int x,c;
char *p = buf;
char *s = buf;
n = 0;
c = size;
while(n<size)
{
x = async_rxblk(port,s,c);
n+=x,c-=x,s+=x;
if(!(async_rxcnt(port)) && n<size)
{
t1 = timerset(1000L);
while(!timeup(t1) && !async_rxcnt(port))
{
if(!async_carrier(port)) return(0);
}
if(timeup(t1)) return(0);
}/* end if */
} /* end while n<size */
crc = 0xFFFFFFFF;
for(n=0;n<size;n++){
crc = UpdCrc32(((unsigned short) (*p++)), crc);
}
for (n = 4; --n >= 0;)
{
c = com_getc(2);
crc = UpdCrc32(c, crc);
}
if (crc != 0xDEBB20E3)
{
return(0);
}
return(1);
}
com_putc(int t)
{
if(!async_carrier(port)) return;
while((async_tx(port,t)) == R_TXERR)
{
if(!async_carrier(port)) return;
}
}
sleep(delay)
int delay;
{
long a;
a = timerset( (long) (delay * 10L) );
while(!timeup(a)) ;
}
clear_screen()
{
int x;
curlocat(4,28); colrprtf(0,0,7,"%-19s","");
curlocat(5,28); colrprtf(0,0,7,"%-14s","");
curlocat(6,28); colrprtf(0,0,7,"%-8s","");
curlocat(7,28); colrprtf(0,0,7,"%-40s","");
curlocat(5,58); colrprtf(0,0,7,"%-3s","");
curlocat(6,58); colrprtf(0,0,7,"%-7s","");
for(x=1;x<=40;x++)
{
curlocat(19,(21+x));
colrprtf(0,0,7,"▓");
}
}
get_block_header(char *buf)
{
register n;
unsigned long crc;
int c,x;
long t1;
char *s = buf;
char *p = buf;
n = 0;
c = 7;
while(n<7)
{
x = async_rxblk(port,s,c);
n+=x,c-=x,s+=x;
if(!(async_rxcnt(port)) && n<7)
{
t1 = timerset(1000L);
while(!timeup(t1) && !async_rxcnt(port))
{
if(!async_carrier(port)) return(0);
}
if(timeup(t1)) return(0);
}/* end if */
} /* end while n<7 */
crc = 0xFFFFFFFF;
for(n=0;n<7;n++){
crc = UpdCrc32(((unsigned short) (*p++)), crc);
}
for (n = 4; --n >= 0;)
{
c = com_getc(2);
crc = UpdCrc32(c, crc);
}
if (crc != 0xDEBB20E3)
return(0);
return(1);
}
send_block_header(char *b)
{
register n;
unsigned long oldcrc;
/*
Considerable thought went into making the decision to split the
header block apart from the actual data block.
Including it as part of the block would mean that only one CRC
would have to be sent, but considering that this information is
critical to the proper reception of the block (size and file
position), it could then be used for error repositioning and
restarts without additional code (some computers will be running
under memory restrictions that IBM Compatibles may not have), and
finally... I couldn't tell any difference either way, speed wise.
I elected to go for the additional reliablity and reduced code
size and keep them apart.
*/
while( (async_txblk(port, b, 7)) == R_TXERR) ;
oldcrc = 0xFFFFFFFF;
for(n=0;n<7;n++){
oldcrc = UpdCrc32(((unsigned short) (*b++)), oldcrc);
}
oldcrc = ~oldcrc;
for(n=4; --n >=0;)
{
com_putc((unsigned char) oldcrc);
oldcrc >>= 8;
}
}
get_system_info(char *buf)
{
register n;
unsigned long crc;
int c,x;
long t1;
char *s = buf;
char *p = buf;
curlocat(7,28);
colrprtf(0,0,7,"%-40s","Waiting For System Information");
n = 0;
c = 128;
while(n<128)
{
x = async_rxblk(port,s,c);
n+=x,c-=x,s+=x;
if(!(async_rxcnt(port)) && n<128)
{
t1 = timerset(1000L);
while(!timeup(t1) && !async_rxcnt(port))
{
if(!async_carrier(port)) return(0);
}
if(timeup(t1)) return(0);
}/* end if */
} /* end while n<128 */
crc = 0xFFFFFFFF;
for(n=0;n<128;n++){
crc = UpdCrc32(((unsigned short) (*p++)), crc);
}
for (n = 4; --n >= 0;)
{
c = com_getc(2);
crc = UpdCrc32(c, crc);
}
if (crc != 0xDEBB20E3){
com_putc('C');
return(0);
}
com_putc(ACK);
return(1);
}
send_system_info(char *b)
{
register n;
int c,attempts=0;
unsigned long oldcrc;
char *p;
curlocat(7,28);
colrprtf(0,0,7,"%-40s","Sending System Information");
async_rxflush(port);
while(async_rxcnt(port)){
async_rxflush(port);
}
for(;;)
{
while( (async_txblk(port, b, 128)) == R_TXERR) ;
oldcrc = 0xFFFFFFFF;
p = b;
for(n=0;n<128;n++){
oldcrc = UpdCrc32(((unsigned short) (*p++)), oldcrc);
}
oldcrc = ~oldcrc;
for(n=4; --n >=0;)
{
com_putc((unsigned char) oldcrc);
oldcrc >>= 8;
}
c = com_getc(5);
if(c == ACK) return;
if(++attempts > 3)
{
async_dtr(port,0);
sleep(3);
async_dtr(port,1);
return;
}/* Screw it, drop connection and try later */
}/* End send system info */
}
cancel_transfer()
{
/* only 5 CAN's needed to abort a session, but I like
to send more thans actual needed. */
com_putc(CAN);
com_putc(CAN);
com_putc(CAN);
com_putc(CAN);
com_putc(CAN);
com_putc(CAN);
com_putc(CAN);
com_putc(CAN);
com_putc(CAN);
com_putc(CAN);
com_putc(CAN);
com_putc(CAN);
com_putc(CAN);
com_putc(CAN);
com_putc(CAN);
com_putc(CAN);
com_putc(CAN);
com_putc(CAN);
com_putc(CAN);
com_putc(CAN);
while(!async_txempty(port))
if(!async_carrier(port)) break;
}
send_resync()
{
long t1,t2;
int ch;
struct _block_header block_header;
/* Here is where the receiver does all of the
actual resend requests.
The receiver will send the resend request every 1.5
seconds until the sender acknowledges that he has
received it.
At that point, the receiver will go into a wait loop
eating everything that comes in until NO data is present
before giving the sender the OK to resume sending.
This means that when we go back to the receive file loop,
the buffers will be clear and the next characters should
be the start of the next block.
This also gives us a chance to clear out any garbage caused
by a burst of static on the phone line.
*/
com_putc('C');com_putc('*');
block_header.position = file_position;
send_block_header(&block_header);
t1 = timerset(150L);
t2 = timerset(6000L);
for(;;)
{
if(!async_carrier(port))
return;
if(timeup(t2))
{/* if you can't get the sender to acknowledge your request
within a reasonable time frame, 60 seconds in this
case, then something is really wrong. Best to drop
carrier and try later.
60 Seconds can be shorter if you like, but bare in mind
that HSTs and TELEBITS are half duplex modems and can
have some trouble turning around and accepting incoming
data when there sending full bore.
*/
async_dtr(port,0);
return;
}
if(com_getc(2) == ACK) /* I *THINK* that this method is
slightly faster than using a compound
conditional statment, as least with
my compiler. */
{
if(com_getc(1) == '*')
{
if(com_getc(1) == ACK)
{
if(com_getc(1) == '*')
{
t1 = timerset(1000L);
while (readbyte(1) != 0)
{
ch = com_getc(0);
if(timeup(t1))
return;
}
com_putc(SOH);
return;
}
}
}
}
if(timeup(t1)){
com_putc('C');com_putc('*');
block_header.position = file_position;
send_block_header(&block_header);
t1 = timerset(150L);
}
}
}
readbyte(t)
int t;
{
unsigned char c;
long t1,t2;
t2 = (long) t * 100;
if (async_rxcnt(port))
return(1);
t1 = timerset (t2);
while (!(async_rxcnt(port)))
{
if (timeup (t1))
return (0);
if(!async_carrier(port))
{
return(0);
}
}
return(1);
}
GetNewPosition()
{
unsigned char c=0;
struct _block_header block_header;
long t1,t2;
/* This routine retreives the new file position from
the receiver when a REPOS is requested. Its returns 1 if
it got the block header and 0 if it didn't.
In this driver, the return code is only used if in ACK required
mode.*/
if(get_block_header(&block_header))
{
/* There really isn't any need to validate
the file position since the block function is
protected with its own 32 bit CRC. The likely
hood of it being incorrect is so remote....*/
/*
One bad aspect to a streamed protocol is the
possiblity of a HUGE amount of data being in
the stream. In the tx buffer, the modem's
tx buffer, on the actual phone line, and if a
network or satellite link is involved that
can add even more.
I've seen the sender as far as 8K ahead of
the receiver and at 2400 baud, that would
take awhile to clear out before the resent
block would ever make it there.
This can make resyncing difficult to do as
well as time consuming. Especially if the
sender miss the first resend request. HST's
and Telebits being half duplex sometimes don't
work well when your pushing them near the
maximum.
In order to get the resyncs to work 100 percent
of the time and as quickly as possible, complex
resend request and verfication routines have to be
used, complex compared to most protocols.
Here you'll find that the sender once he's received
the resend request is required to send the receiver
an acknowledgement that he HAS processed it.
The acknowledgement will consist of 4 bytes arranged
so that the likely hood of running across the same
pattern in the actual file data is small.
The sender MUST wait until the receiver gives the
all clear to resume sending. This helps keep both
sides in sync with each other.
If the sender doesn't get the OK to resume sending
within 60 seconds, we must assume that the receiver
has hung or there is so much line static that there
isn't much hope of resuming. Our only recourse at
that point is to drop carrier.
*/
file_position = block_header.position;
com_putc(ACK);/* 4 byte seq. to
signal recevier
that repos was
received ok */
com_putc('*');
com_putc(ACK);
com_putc('*');
curlocat(7,28);
sprintf(junk_string,"REPOS %ld",file_position);
colrprtf(0,0,7,"%-40s",junk_string);
curlocat(5,58);
colrprtf(0,0,7,"%d",++total_errors);
if(total_errors > 1 && block_len > 128)
block_len >>= 1;
if(block_len == 128 && !acks_required)
acks_required = 1;
t1 = timerset(6000L);
t2 = timerset(200L);/* given the possiblity that
the receiver sent more than
one resend request and we
missed the first, resend
the resync acknowledgement no
closer than 2 seconds apart.
Anything received before
that time we'll ignore.
*/
while(!timeup(t1))
{
c = com_getc(1);
if(c == 'C' && timeup(t2))
{
/* Must have missed it, send the
resync acknowledgement again */
com_putc(ACK);
com_putc('*');
com_putc(ACK);
com_putc('*');
t2 = timerset(200L);
}
if(c == SOH) return(1);
} /* end t1 time loop */
/* Receiver possible hung or lines are just flat to bad*/
async_dtr(port,0);
}
sprintf(junk_string,"Bad Blk Header");
colrprtf(0,0,7,"%-40s",junk_string);
curlocat(5,58);
return(0);