/****************************************************************** * File: janus.c * Created at Thu Jan 6 19:24:06 2000 by pk // aaz@ruxy.org.ru * Janus protocol implementation with: * - freqs support * - crc32 support * $Id: janus.c,v 1.20 2003/02/25 21:23:03 cyrilm Exp $ ******************************************************************/ /*---------------------------------------------------------------------------*/ /* Opus Janus revision 0.22, 1- 9-88 */ /* */ /* The Opus Computer-Based Conversation System */ /* (c) Copyright 1987, Rick Huebner, All Rights Reserved */ /*---------------------------------------------------------------------------*/ #include "headers.h" #include #include "defs.h" #include "qipc.h" #include "janus.h" #include "byteop.h" long brain_dead; /* Time at which to give up on other computer */ slist_t *reqs=NULL; unsigned int caps=0; void preparereqs(flist_t *l); /*****************************************************************************/ /* Super-duper neato-whizbang full-duplex streaming ACKless batch file */ /* transfer protocol for use in Opus-to-Opus mail sessions */ /*****************************************************************************/ int janus() { byte pkttype; /* Type of packet last received */ int blklen; /* Length of last data block sent */ word goodneeded; /* # good blocks to send before upping txblklen */ word goodblks; /* Number of good blocks sent at this block size */ word rpos_count; /* Number of RPOS packets sent at this position */ long xmit_retry; /* Time to retransmit lost FNAMEPKT or EOF packet */ long lasttx=0; /* Position within file of last data block we sent*/ long rpos_retry; /* Time at which to retry RPOS packet */ long rpos_sttime; /* Time at which we started current RPOS sequence */ long last_rpostime; /* Timetag of last RPOS which we performed */ long last_blkpos=0; /* File position of last out-of-sequence BLKPKT */ flist_t *l; int rc=0; char *p; int capslogged=0; /*-------------------------------------------------------------------------*/ /* Initialize file transmission variables */ /*-------------------------------------------------------------------------*/ last_rpostime= xmit_retry = 0L; if (effbaud < 300) effbaud = 300; timeout = 40960/effbaud; if (timeout < 10) timeout = 10; brain_dead=t_set(120); txmaxblklen = effbaud/300 * 128; if (txmaxblklen > BUFMAX) txmaxblklen = BUFMAX; txblklen = txmaxblklen; goodblks = 0; goodneeded = 3; txstate = XSENDFNAME; txbuf=xcalloc(txmaxblklen+8,1); rxbuf=xcalloc(txmaxblklen+8,1); rxbufmax = rxbuf+BUFMAX+8; l=fl; preparereqs(l); getfname(&l); sline("Janus session (%d block)", txblklen); /*-------------------------------------------------------------------------*/ /* Initialize file reception variables */ /*-------------------------------------------------------------------------*/ rxbufptr = NULL; rpos_retry = rpos_count = 0; rxstate = RRCVFNAME; /*-------------------------------------------------------------------------*/ /* Send and/or receive stuff until we're done with both */ /*-------------------------------------------------------------------------*/ do { /* while (txstate || rxstate) */ /*-----------------------------------------------------------------------*/ /* If nothing useful (i.e. sending or receiving good data block) has */ /* happened within the last 2 minutes, give up in disgust */ /*-----------------------------------------------------------------------*/ if (t_exp(brain_dead)) { write_log("other end died"); /* "He's dead, Jim." */ goto giveup; } /*-----------------------------------------------------------------------*/ /* If we're tired of waiting for an ACK, try again */ /*-----------------------------------------------------------------------*/ if (xmit_retry) { if (t_exp(xmit_retry)) { sline("Timeout waiting for ACK - retry"); xmit_retry = 0L; switch (txstate) { case XRCVFNACK: txstate = XSENDFNAME; break; case XRCVEOFACK: if(fseek(txfd, txpos=lasttx, SEEK_SET)) { write_log("seek error on %s", sendf.fname); goto giveup; } txstate = XSENDBLK; break; case XRCVFRNAKACK: txstate = XSENDFREQNAK; break; } } } /*-----------------------------------------------------------------------*/ /* Transmit next part of file, if any */ /*-----------------------------------------------------------------------*/ switch (txstate) { case XSENDBLK: lasttx = txpos; STORE32(txbuf,lasttx); blklen = fread(txbuf+4, 1, txblklen, txfd); if(blklen<0) { write_log("read error on %s", sendf.fname); goto giveup; } sendpkt(txbuf, 4+blklen, BLKPKT); txpos += blklen; sendf.foff=txpos; check_cps(); qpfsend(); if (txpos >= sendf.ftot || blklen < txblklen) { xmit_retry=t_set(timeout); txstate = XRCVEOFACK; } else brain_dead=t_set(120); if (txblklen < txmaxblklen && ++goodblks > goodneeded) { txblklen <<= 1; goodblks = 0; } break; case XSENDFNAME: blklen = strchr( strchr((char *)txbuf,'\0')+1, '\0') - (char *)txbuf + 3; sendpkt(txbuf,blklen,FNAMEPKT); xmit_retry=t_set(timeout); txstate = XRCVFNACK; sendf.cps=1; qpfsend(); break; case XSENDFREQNAK: sendpkt (NULL, 0, FREQNAKPKT); xmit_retry=t_set(timeout); txstate = XRCVFRNAKACK; break; } /*-----------------------------------------------------------------------*/ /* Catch up on our reading; receive and handle all outstanding packets */ /*-----------------------------------------------------------------------*/ while ((pkttype = rcvpkt())) { DEBUG(('J',1,"rcvpkt %d (%c) len=%d at txs=%d rxs=%d", pkttype, C0(pkttype), rxblklen, txstate, rxstate)); switch (pkttype) { /*-------------------------------------------------------------------*/ /* File data block or munged block */ /*-------------------------------------------------------------------*/ case BADPKT: case BLKPKT: if (rxstate == RRCVBLK) { unsigned int ln = FETCH32(rxbuf); if (pkttype == BADPKT || ln != rxpos) { if (pkttype == BLKPKT) { if (ln < last_blkpos) rpos_count = 0; last_blkpos = ln; } if (t_exp(rpos_retry)) { if (rpos_count > 5) rpos_count = 0; if (++rpos_count == 1) time(&rpos_sttime); sline("Bad packet at %ld", rxpos); STORE32(rxbuf, rxpos); STORE32(rxbuf+4, rpos_sttime); sendpkt(rxbuf, 8, RPOSPKT); rpos_retry=t_set(timeout/2); } } else { brain_dead=t_set(120); last_blkpos = rxpos; rpos_retry = rpos_count = 0; rxblklen -= 4; if(fwrite(rxbuf+4, rxblklen, 1, rxfd)<0) { write_log("write error on %s", recvf.fname); goto giveup; } rxpos += rxblklen; recvf.foff=rxpos; check_cps(); qpfrecv(); if (rxpos >= recvf.ftot) { rxclose(&rxfd, FOP_OK); rxstate = RRCVFNAME; } } } if (rxstate == RRCVFNAME) sendpkt(NULL,0,EOFACKPKT); break; /*-------------------------------------------------------------------*/ /* Name and other data for next file to receive */ /*-------------------------------------------------------------------*/ case FNAMEPKT: p=(char *)(rxbuf+strlen((char *)rxbuf)+1); if (rxblklen > strlen((char *)rxbuf)+strlen(p)+2 && !caps) { caps=*((char *)strchr(p,0)+1) & OUR_JCAPS; if (!capslogged) { write_log("janus link options: %dKB%s%s%s", txmaxblklen/1024, caps & JCAP_CRC32?",C32":"", caps & JCAP_FREQ?",FRQ":"", caps & JCAP_CLEAR8?",CL8":"" ); capslogged=1; } } if(rxstate==RRCVFNAME) { if(!rxbuf[0]) { if(reqs && (caps&JCAP_FREQ)) { snprintf((char *)txbuf, 1024, "%s%c%c", reqs->str, 0, caps); write_log("sent janus freq: %s", txbuf); sendpkt((byte *)txbuf,strlen((char *)txbuf)+2,FREQPKT); reqs=reqs->next; break; } else { rxstate=RDONE; qpreset(0); } } else { sscanf(p, "%u %lo %*o", &recvf.ftot, &recvf.mtime); switch(rxopen((char *)rxbuf, recvf.mtime, recvf.ftot, &rxfd)){ case FOP_SUSPEND: goto breakout; case FOP_CONT: case FOP_OK: qpfrecv(); rxstate=RRCVBLK; rxpos=recvf.foff; break; case FOP_SKIP: rxpos=-1; break; } recvf.cps=1; } } STORE32(txbuf, rxpos); txbuf[4]=caps&0xff; sendpkt((byte *)txbuf,5,FNACKPKT); if (!(txstate || rxstate)) goto breakout; break; /*-------------------------------------------------------------------*/ /* ACK to filename packet we just sent */ /*-------------------------------------------------------------------*/ case FNACKPKT: if (txstate == XRCVFNACK) { xmit_retry = 0L; caps=(rxblklen>=5)?rxbuf[4]:0; if(txfd) { if ((txpos = FETCH32(rxbuf)) > -1L) { sendf.soff=txpos; if(fseek(txfd, txstart = txpos, SEEK_SET)<0) { write_log("seek error on %s", sendf.fname); goto giveup; } txstate = XSENDBLK; } else { txclose(&txfd, FOP_SKIP); qpreset(1); flexecute(l); l=l->next; getfname(&l); txstate = XSENDFNAME; } } else { qpreset(1); txstate = XDONE; } } if (!(txstate || rxstate)) goto breakout; break; /*-------------------------------------------------------------------*/ /* ACK to last data block in file */ /*-------------------------------------------------------------------*/ case EOFACKPKT: if (txstate == XRCVEOFACK || txstate == XRCVFNACK) { xmit_retry = 0L; if (txstate == XRCVEOFACK) { txclose(&txfd, FOP_OK); qpfsend(); flexecute(l); l=l->next; getfname(&l); } txstate = XSENDFNAME; } break; /*---------------------------------------------------------------*/ /* We've got freq */ /*---------------------------------------------------------------*/ case FREQPKT: if ((txstate == XRCVFNACK) || (txstate == XDONE)) { caps=*(strchr((char *)rxbuf,'\0')+1); xmit_retry = 0L; write_log("recd janus freq: %s", rxbuf); /* TODO FREQS */ if(cfgs(CFG_EXTRP)) { slist_t req; req.str=(char *)rxbuf; req.next=NULL; if(freq_ifextrp(&req)) { l = fl; txstate = XSENDFNAME; getfname(&l); if(!txfd) txstate = XSENDFREQNAK; } else { txstate = XSENDFREQNAK; } } else { txstate = XSENDFREQNAK; } } break; /*---------------------------------------------------------------*/ /* Our last file request didn't match anything; move on to next */ /*---------------------------------------------------------------*/ case FREQNAKPKT: write_log("janus freq: remote hasn't such file"); sendpkt (NULL, 0, FRNAKACKPKT); break; /*---------------------------------------------------------------*/ /* ACK to no matching files for request error; try to end again */ /*---------------------------------------------------------------*/ case FRNAKACKPKT: if (txstate == XRCVFRNAKACK) { xmit_retry = 0L; /* if(l) l=&(*l)->next; */ getfname(&l); txstate = XSENDFNAME; } break; /*-------------------------------------------------------------------*/ /* Receiver says "let's try that again." */ /*-------------------------------------------------------------------*/ case RPOSPKT: if (txstate == XSENDBLK || txstate == XRCVEOFACK) { if (FETCH32(rxbuf+4) != last_rpostime) { last_rpostime = FETCH32(rxbuf+4); xmit_retry = 0L; if(fseek(txfd, txpos = lasttx = FETCH32(rxbuf), SEEK_SET)<0) { write_log("seek error on %s", sendf.fname); goto giveup; } sline("Resending from %ld", txpos); if (txblklen >= 128) txblklen >>= 1; goodblks = 0; goodneeded = goodneeded<<1 | 1; txstate = XSENDBLK; } } break; /*-------------------------------------------------------------------*/ /* Debris from end of previous Janus session; ignore it */ /*-------------------------------------------------------------------*/ case HALTACKPKT: break; /*-------------------------------------------------------------------*/ /* Abort the transfer and quit */ /*-------------------------------------------------------------------*/ default: write_log("janus: unknown packet type %d",pkttype); /* fallthrough */ case HALTPKT: giveup: if(txfd) { txclose(&txfd, FOP_ERROR); rc=1; } if(rxstate==RRCVBLK) { rxclose(&rxfd, FOP_ERROR); rc=1; } qpreset(0);qpreset(1); goto breakout; } /* switch (pkttype) */ } /* while (pkttype) */ UHASDATA(200); } while (txstate || rxstate); /*-------------------------------------------------------------------------*/ /* All done; make sure other end is also finished (one way or another) */ /*-------------------------------------------------------------------------*/ breakout: endbatch(); while(l) { if(!l->sendas) flexecute(l); l=l->next; } xfree(txbuf); xfree(rxbuf); slist_kill(&reqs); return rc; } /*****************************************************************************/ /* Build and send a packet of any type. */ /* Packet structure is: PKTSTRT,contents,packet_type,PKTEND,crc */ /* CRC is computed from contents and packet_type only; if PKTSTRT or PKTEND */ /* get munged we'll never even find the CRC. */ /*****************************************************************************/ void sendpkt(byte *buf, int len, int type) { word crc; if((caps&JCAP_CRC32) && type!=FNAMEPKT) { sendpkt32(buf,len,type); return; } DEBUG(('J',1,"sendpkt %d bytes, type:%c", len, type)); BUFCLEAR(); BUFCHAR(DLE); BUFCHAR(PKTSTRTCHR^0x40); crc=CRC16USD_INIT; while(--len>=0) { txbyte(*buf); crc = CRC16USD_UPDATE(*buf++,crc); } BUFCHAR(type); crc=CRC16USD_FINISH(CRC16USD_UPDATE(type,crc)); BUFCHAR(DLE); BUFCHAR(PKTENDCHR^0x40); txbyte(crc>>8); txbyte(crc&0xFF); BUFFLUSH(); } void sendpkt32(byte * buf, register int len, int type) { unsigned long crc32; BUFCHAR (DLE); BUFCHAR (PKTSTRTCHR32 ^ 0x40); DEBUG(('J',1,"sendpkt32 %d bytes, type:%c", len, type)); crc32 = CRC32_INIT; while (--len >= 0) { txbyte(*buf); crc32=CRC32_UPDATE(*buf++,crc32); } BUFCHAR ((byte) type); crc32 = CRC32_UPDATE(type,crc32); BUFCHAR (DLE); BUFCHAR (PKTENDCHR ^ 0x40); txbyte ((byte) (crc32 >> 24)); txbyte ((byte) ((crc32 >> 16) & 0xFF)); txbyte ((byte) ((crc32 >> 8) & 0xFF)); txbyte ((byte) (crc32 & 0xFF)); BUFFLUSH(); } /*****************************************************************************/ /* Transmit cooked escaped byte(s) corresponding to raw input byte. Escape */ /* DLE, XON, and XOFF using DLE prefix byte and ^ 0x40. Also escape */ /* CR-after-'@' to avoid Telenet/PC-Pursuit problems. */ /*****************************************************************************/ void txbyte(int c) { if((txlastc='@' && c==CR)||c==DLE||c==XON||c==XOFF) { BUFCHAR(DLE); c^=0x40; } BUFCHAR(c); txlastc=c; } int rcvbyte(int to) { int c; if ((c = GETCHAR(to)) == DLE) { if ((c = GETCHAR(timeout)) >= 0) { switch (c ^= 0x40) { case PKTSTRTCHR: c=PKTSTRT; break; case PKTSTRTCHR32: c=PKTSTRT32; break; case PKTENDCHR: c=PKTEND; break; } } } return c; } /*****************************************************************************/ /* Receive, validate, and extract a packet if available. If a complete */ /* packet hasn't been received yet, receive and store as much of the next */ /* packet as possible. Each call to rcvpkt() will continue accumulating a */ /* packet until a complete packet has been received or an error is detected. */ /* rxbuf must not be modified between calls to rcvpkt() if NOPKT is returned.*/ /* Returns type of packet received, NOPKT, or BADPKT. Sets rxblklen. */ /*****************************************************************************/ byte rcvpkt() { static int is32; byte *p; int c, i; unsigned int pktcrc; unsigned int clccrc; /*-------------------------------------------------------------------------*/ /* If not accumulating packet yet, find start of next packet */ /*-------------------------------------------------------------------------*/ if (!(p=rxbufptr)) { do c=rcvbyte(0); while (c >= 0 || c == PKTEND); switch (c) { case PKTSTRT: p = rxbuf;is32=0; break; case PKTSTRT32: p = rxbuf;is32=1; break; case ERROR: case RCDO: return HALTPKT; default: return NOPKT; } } /*-------------------------------------------------------------------------*/ /* Accumulate packet data until we empty buffer or find packet delimiter */ /*-------------------------------------------------------------------------*/ while((c=rcvbyte(0))>=0 && p 2) ++done; else { sendpkt(NULL,0,HALTPKT); timeval=t_set(timeout); } } break; case HALTPKT: case HALTACKPKT: ++done; break; default: timeouts = 0; sendpkt(NULL,0,HALTPKT); timeval=t_set(timeout); break; } } /*-------------------------------------------------------------------------*/ /* Announce quite insistently that we're done now */ /*-------------------------------------------------------------------------*/ for (done=0; done<10; ++done) sendpkt(NULL,0,HALTACKPKT); } slist_t *readreq(slist_t *l, char *fname) { FILE *f; char s[MAX_PATH], *p; f=fopen(fname, "rt"); if(!f) { write_log("can't read .req: %s", fname); return l; } while(fgets(s,MAX_PATH-1,f)) { p=s+strlen(s)-1; while(*p=='\r' || *p=='\n') *p--=0; slist_add(&l, s); } fclose(f); return l; } void getfname(flist_t **l) { while(*l) { if(!(*l)->sendas || !(txfd=txopen((*l)->tosend, (*l)->sendas))) { flexecute(*l); } else { break; } *l=(*l)->next; } if(txfd) snprintf((char *)txbuf,1024,"%s%c%u %lo %o%c%c",(*l)->sendas,0,sendf.ftot,sendf.mtime,0644,0,OUR_JCAPS); else snprintf((char *)txbuf,1024,"%c%c%c",0,0,OUR_JCAPS); } void preparereqs(flist_t *l) { while(l) { if(l->sendas && l->type==IS_REQ) { reqs=readreq(reqs, l->tosend); flexecute(l); } l=l->next; } }