/* 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 #include "zmax.h" #include #include #include #include /* 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 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 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=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= 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); }