/*--------------------------------------------------------------------------*/
/*               The Opus Computer-Based Conversation System                */
/*       (c) Copyright 1986, Wynn Wagner III, All Rights Reserved           */
/*                                                                          */
/*         The original sealink protocol is copyrighted by SEA, inc.        */
/*                                                                          */
/*                 This module was written by W.Wagner III                  */
/*        with modifications done by Bob Hartman and Rick Huebner           */
/*                                                                          */
/*                                                                          */
/*        GENERAL PURPOSE FILE TRANSMITTER (Xmodem/YModem/Sealink)          */
/*                                                                          */
/*                                                                          */
/*                                                                          */
/*  This module is similar to a routine used by Opus-Cbcs (1.00).  It is    */
/*  provided for your information only.  You will find routines that need   */
/*  to be coded and identifiers to be resolved.                             */
/*                                                                          */
/*  There is absolutely no guarantee that anything here will work.  If you  */
/*  break this routine, you own both pieces.                                */
/*                                                                          */
/*  USAGE:  You may use this material in any program with no obligation     */
/*          as long as there is no charge for your program.  For more       */
/*          information about commercial use, contact the "OPUSinfo HERE"   */
/*          BBS (124/111).                                                  */
/*                                                                          */
/*                                                                          */
/*--------------------------------------------------------------------------*/
#include <sys\types.h>                                                              /*ww/870225*/
#include <sys\stat.h>                                                               /*ww/870225*/
#include <errno.h>
#include <stdio.h>
#include "xfer.h"
#include "com.h"

extern byte *local_CEOL;

char *ultoa();



/*--------------------------------------------------------------------------*/
/* MULTI-PURPOSE FILE TRANSFER                                              */
/*--------------------------------------------------------------------------*/
int send_file( fname, protocol )
   char *fname, protocol;
   begin
      register int         i;
      register byte       *b;

      struct FILEINFO      dta;
      struct zero_block   *header;
      int                  in_char;
      int                  ackblock;
      int                  blknum;
      unsigned int         last_block;
      unsigned int         errs;
      int                  sliding;
      int                  base;
      int                  head;
      int                  block_size;
      char                *buffer;
      int                  do_chksum;
      FILE                *fp;
      char                *message;
		int						block_timer;

		int						win_size;
      int                  full_window;

      fp          = NULL;
      sliding     =    0;
      ackblock    =   -1;
      do_chksum   = errs     =    0;


      /*--------------------------------------------------------------------*/
		/* The following window size works out to be between 3.5 and 4.5      */
		/* seconds of data at any valid baud rate.  This gives us a set time  */
		/* that the other end will wait before NAK'ing a block if it waits    */
		/* to flush its buffer before NAK'ing.  If it does not wait, then it  */
		/* does not matter very much except to make sure we only have a set   */
		/* amount of run-ahead that will fit in a 4K buffer                   */
      /*--------------------------------------------------------------------*/
		full_window    = cur_baud/400;

      /* seadog can't handle large window */
      if (full_window>6)
         begin
#ifndef BINKLEY
            if ((!is_another_Opus) and (protocol=='S'))
#endif
               full_window = 6;
         end



      XON_DISABLE();

      /*--------------------------------------------------------------------*/
      /* Prepare file                                                       */
      /*--------------------------------------------------------------------*/
      if ((!fname) || (!fname[0]))     /* No file?  Just send an EOT.       */
         begin

            CLEAR_INBOUND();

            for(i=0; i<5; i++)
               begin
               	switch( in_char=TIMED_READ(5) )
                  	begin

                        case 'C'    :
                        case NAK    :
                        case CAN    :  SENDBYTE(EOT);
											      break;

                        case TSYNC  :  return TSYNC;

                  		default     :  if (in_char<' ') return 0;

                  	end /* switch */
               end
            return 0;
         end

      strlwr(fname);                                                                /*ww/870225*/
      CLEAR_IOERR();
      if (!dfind(&dta, fname,0))  fp = fopen(fname,read_binary);
      else errno  = ENOENT;

      if (got_error(OPEN_msg,fname))
         begin
            send_can();
            return 0;
         end


      /*--------------------------------------------------------------------*/
      /* Prepare buffer                                                     */
      /*--------------------------------------------------------------------*/
      buffer = malloc(1030);
		header = (struct zero_block *)buffer;
      if (!buffer)
         begin
            message = MEMOVFL_msg;
            goto fubar;
         end


      /*--------------------------------------------------------------------*/
      /* Prepare method                                                     */
      /*--------------------------------------------------------------------*/
      block_size  = 128;
      head        = SOH;
      switch(protocol)
         begin
            case 'Y' :  base=1; block_size=1024; head=STX;              break;
            case 'X' :  base=1;                                         break;
            case 'S' :  base=0;                                         break;
            case 'T' :  base=0;                  head=SYN;              break;
            case 'M' :  base=1;                                         break;
            default  :  status_line("!Protocol?");                   return 0;
         end
      blknum= base;

      last_block = (int )((dta.size+((long )block_size-1L))/(long)block_size);
      throughput(0,0L);

      if (locate_y>1) gotoxy(0,locate_y-1);
      status_line("-Send %s (%d %c-blks)",fname,last_block,protocol);
      set_xy( "Sending  " );


      /*--------------------------------------------------------------------*/
      /* Wait for signal to begin                                           */
      /*--------------------------------------------------------------------*/
      do
         begin
            i = TIMED_READ(5);
            switch ( i )
               begin
                  case NAK :  /*--------------------------------------------*/
                              /* NAK                                        */
                              /*--------------------------------------------*/
                              do_chksum = 1;
                              /* fallthrough */

                  case 'C' :  /*--------------------------------------------*/
                              /* CRC RESPONSE                               */
                              /*--------------------------------------------*/
                              begin
                                  int send_tmp;
                                  if (((send_tmp = TIMED_READ(1))>=0)
                                     &&(TIMED_READ(1)==((~send_tmp)&0xff)))
                                     begin
                                        sliding=1;
                                     end
                                  errs = 0;
                                  goto sendloop;
                              end

                  case CAN :  /*--------------------------------------------*/
                              /* CANCEL                                     */
                              /*--------------------------------------------*/
                              message = CAN_msg;
                              goto fubar;

                  default  :  /*--------------------------------------------*/
                              /* OTHER RESPONSES (debris)                   */
                              /*--------------------------------------------*/
                              if (((KEYPRESS()) and (READKB()==27)))
                                 begin
                                    message = KBD_msg;
                                    goto fubar;
                                 end

                              else if ( (errs++)>15 )
                                 begin
                                    message = TIME_msg;
                                    goto fubar;
                                 end

                              else CLEAR_INBOUND();

               end /* switch */

         end /* wait for signal to begin */
      while(CARRIER);

      message = CARRIER_msg;
      goto fubar;


      /*--------------------------------------------------------------------*/
      /* Transfer                                                           */
      /*--------------------------------------------------------------------*/
sendloop:
      while(CARRIER)
         begin

            message  = NULL;
            win_size = (blknum<2)? 2: full_window;

            if (((KEYPRESS()) and (READKB()==27)))
               begin
                  message = KBD_msg;
                  goto fubar;
               end

            if (blknum <= last_block)
               begin
                  /*--------------------------------------------------------*/
                  /* DATA                                                   */
                  /*--------------------------------------------------------*/
                  memset( buffer, 0, block_size );
                  if (blknum)
                     begin
                        fseek( fp, ((long)(blknum-1)*(long)block_size), 0);
                        if (got_error(READ_msg,fname))
                           begin
                              message = SEEK_msg;
                              goto fubar;
                           end
                  
                        fread( buffer, block_size, 1, fp );
                        if (got_error(READ_msg,fname))
                           begin
                              message = READ_msg;
                              goto fubar;
                           end
                     end
                  else 
                     begin
                        block_size   = 128;
                        header->size = dta.size;

                        stcgfn( header->name, fname );

                        if (protocol=='T')
                           begin
                              for(i=0; i<HEADER_NAMESIZE; i++)
                                 if (!(header->name[i])) header->name[i] = ' ';
                              header->time = dta.time;
                           end

                        strcpy( header->moi,  xfer_id  );
                     end
      
                  /*--------------------------------------------------------*/
                  /* HEAD                                                   */
                  /*--------------------------------------------------------*/
                  SENDBYTE(  head   );
                  SENDBYTE(  blknum );
                  SENDBYTE( ~blknum );
      
      
                  /*--------------------------------------------------------*/
                  /* SEND                                                   */
                  /*--------------------------------------------------------*/
                  for (b=buffer,i=0;i<block_size;i++,b++) SENDBYTE(*b);


                  /*--------------------------------------------------------*/
                  /* CHECK                                                  */
                  /*--------------------------------------------------------*/
                  if ((do_chksum)||(head==SYN))
                     begin
                        unsigned char chksum = '\0';
                        for ( b=buffer,i=0; i<block_size; i++, b++ )
                           chksum+=(*b);
                        SENDBYTE( chksum );
                     end
                  else
                     begin
                        word crc;

                        for(b=buffer, crc=i=0; i<block_size; i++, b++)
                           crc = crc_update(crc,(byte )(*b));

                        crc = crc_finish(crc);
                        SENDBYTE( crc>>8   );
                        SENDBYTE( crc&0xff );
                     end
               end /* if not finished */

            /*--------------------------------------------------------------*/
				/* Only wait 30 seconds for a response                          */
				/* For non-sliding this number is updated, for sliding we have	 */
				/*	a problem since the output buffer may still have stuff in	 */
				/*	it.  We need to make sure we reset this when output is done  */
				/* in the sliding case.  What we actually do is reset it if the */
				/* output has not yet completed.  We only check it in the case  */
				/* of debris, then recycle, so this should be a reasonable way  */
				/* of doing things in the sliding case.                         */
            /*--------------------------------------------------------------*/
				block_timer = timerset(30);

slide_reply:
            /*--------------------------------------------------------------*/
				/* If we are not sliding, force the output and purge the input. */
				/* This is simply good Xmodem practice.  It is also necessary   */
				/* to make sure that the other end has received all of the data */
				/* before starting the block reply timeout.                     */
            /*--------------------------------------------------------------*/
				if (!sliding)
					begin
						CLEAR_INBOUND();
						while (!OUT_EMPTY())
							time_release();
						block_timer = timerset(30);
					end

            /*--------------------------------------------------------------*/
            /* REPLY: Slide                                                 */
            /*        Don't wait, unless other end has something to say     */
            /*        or we're at/beyond the window.                        */
            /*--------------------------------------------------------------*/
            else if ( (blknum    < (ackblock+win_size)) and /* in window    */
                      (blknum    < last_block   ) and /* have more to send  */
                      (PEEKBYTE()< 0 )                /* other end is quiet */
                    )
               begin
                 blknum++;
                 goto sendloop;
               end



            /*--------------------------------------------------------------*/
reply:      /* REPLY: Wait state                                            */
            /*        No sliding, or slid too far.  Wait for a reply.       */
            /*                                                              */
				/* If we got here we are either not sliding, or we are sliding  */
				/* and have received an input character, or we are sliding and  */
				/* have slid too far.  In the first case the following loop     */
				/* will not do anything because the output is empty, in the     */
				/* second case we also will not do anything since we have an    */
				/* input character ready, in the final case we want to execute  */
				/* the loop since we are in a wait state.  The loop will go on  */
				/* until the output is empty (in the worst case), and since we  */
				/* we have disabled flow control here (right???) we will never  */
				/* have to worry about the loop not terminating.  Now, we just  */
				/* sit and loop as long as there is output to do and no input.  */
				/* Just to make wwIII happy do a time_release() so that his     */
				/* DDos system will be more efficient!                          */
            /*--------------------------------------------------------------*/

				while ( (!OUT_EMPTY()) and
						  (PEEKBYTE()<0))
					time_release();  /* ... time slice routine for multi-taskers */

            /*--------------------------------------------------------------*/
				/* If we got here we either sent all the output, or got input.  */
				/* In the first case we should get a response within 15 seconds */
				/* In the second case we should get an immediate response.      */
				/* Either way, nothing in 30 seconds is a timeout               */
            /*--------------------------------------------------------------*/
            if ((in_char=TIMED_READ(30))<0)
               begin
                  message = TIME_msg;
                  goto fubar;
               end

            if (in_char=='C')
               begin
                  do_chksum = 0;
                  in_char   = NAK;
               end


            /*--------------------------------------------------------------*/
            /* CAN                                                          */
            /*--------------------------------------------------------------*/
            if (in_char==CAN)
               begin
                  message = CAN_msg;
                  goto fubar;
               end


            /*--------------------------------------------------------------*/
            /* REPLY: Take action based on slide-response                   */
            /*--------------------------------------------------------------*/
            if ((in_char>0) and (sliding))
               begin

                  register int i, j;

                  if ((in_char==ACK) || (in_char==NAK))
                     begin
                        if ((i=TIMED_READ(2))<0)
                           begin
                              sliding=0;
                           end
                        else
                           begin
                              if ((j=TIMED_READ(2))<0)
                                 begin
                                    sliding = 0;
                                 end
                              else if (i==(j^0xff))
                                 begin
                                    i = blknum-((blknum-i)&0xff);
                                    if ((i<=blknum)&&(i>(blknum-win_size-10)))
                                       begin
                                          if (in_char==ACK)
                                             begin
                                                ackblock=i;
                                                blknum++;
                                                if (ackblock>=last_block)
                                                   begin
                                                      goto done;
                                                   end
                                                errs = 0;

                                                if ((head==SYN)&&(blknum))
                                                   head = SOH; /* ZapTeLink */

                                             end
                                          else
                                             begin
                                                blknum=i;
                                                CLEAR_INBOUND();
                                                CLEAR_OUTBOUND();
                                                errs++;
                                             end
                                       end
                                 end
                              else
                                 begin
                                    message = "SLIDE CMPL ERR";
                                 end
                           end
                     end
                  else
                     begin

				            /*--------------------------------------------------*/
								/* If we got debris, just ignore it and go on if we */
								/* have not yet had a timeout.  If we timed out,    */
								/* then just go to the fubar label and abort        */
				            /*--------------------------------------------------*/

                        gotoxy(locate_x+10,locate_y);
                        cprintf(" Debris [%Xh]",in_char);


				            /*--------------------------------------------------*/
								/* The error is timeout with the output buffer      */
								/* empty (we can't penalize someone for a timeout   */
								/* if we haven't yet sent them all they need!!!     */
				            /*--------------------------------------------------*/

								if (timeup(block_timer) and OUT_EMPTY())
									begin
										message = TIME_msg;
										goto fubar;
									end

				            /*--------------------------------------------------*/
								/* Ok, we either have a timeout or the buffer still */
								/* has stuff in it.  If it still has stuff, reset   */
								/* the timeout variable before trying again         */
				            /*--------------------------------------------------*/

								else if (!OUT_EMPTY()) block_timer = timerset(30);

								goto slide_reply;
                     end
               end

            /*--------------------------------------------------------------*/
            /* REPLY: Take action based on regular response                 */
            /*--------------------------------------------------------------*/
            if (!sliding)
               begin
                  if (in_char==ACK)
                     begin

                        /*--------------------------------------------------*/
                        /* On block 10, throw in an extra pause to give the */
                        /* other end a change to fall into a slide if it    */
                        /* feels it's something it needs to do.             */
                        /*--------------------------------------------------*/
                        if (blknum==10) timer(3);   /* approx. 3/10s second */

                        /*--------------------------------------------------*/
                        /* Smell the other system vis-a-vis a slide.        */
                        /*--------------------------------------------------*/
                        if (PEEKBYTE()>0)
                           begin
                              int send_tmp;
                              if (((send_tmp = TIMED_READ(1))>=0)
                                   &&(TIMED_READ(1)==((~send_tmp)&0xff)))
                                 begin
                                    sliding  =  1;
                                    ackblock =  send_tmp;
                                 end
                           end
                        if (!sliding) CLEAR_INBOUND();


                        message = NULL;
                        if (blknum>=last_block) goto done;
                        blknum++;

                        if ((head==SYN)&&(blknum)) head = SOH; /* ZapTeLink */

                        errs = 0;
                     end
                  else if (in_char==NAK)
                     begin
                        errs++;
                        timer(5);
                        CLEAR_INBOUND();
                        CLEAR_OUTBOUND();
                        message = NAK_msg;
                     end
                  else if (CARRIER)
							begin
								/* Did we time out yet? */
								if (!timeup(block_timer))
									goto reply;
								else
									begin
										message = TIME_msg;
										goto fubar;
									end
							end
						else
							begin
								message = CARRIER_msg;
								goto fubar;
							end
               end

            if (errs>10)
               begin
                  message = FUBAR_msg;
                  goto fubar;
               end

bottom:     gotoxy(locate_x,locate_y);
		      i  = (blknum<=last_block)?blknum:last_block;
            cputs( ultoa(((unsigned long )(i)),e_input,10) );

            if ((sliding) and (ackblock>0))
               begin
                  bdos(9,": $");
                  cputs( ultoa(((unsigned long )(ackblock)),e_input,10) );
               end

            if (message)
               begin
                  putch(' ');
                  cputs( message );
                  cputs( local_CEOL );
               end

         end /* while */

      message = CARRIER_msg;



fubar:

      CLEAR_OUTBOUND();
      send_can();

      status_line("#%s not sent: %s",fname,message);

      if (fp)     fclose(fp);
      if (buffer) free(buffer);
      CLEAR_INBOUND();

      return 0;


done:
      gotoxy(locate_x,locate_y);
      throughput(1,dta.size);

      CLEAR_INBOUND();
      SENDBYTE(EOT);
      if (fp)     fclose(fp);
      if (buffer) free(buffer);

      for(i=0; i<5; i++)
         begin
            switch( TIMED_READ(5) )
               begin

                  case 'C':
                  case NAK:
                  case CAN:   SENDBYTE(EOT);
			   break;

                  case TSYNC: return TSYNC;

                  default :
                  case -1 :   return 1;

               end /* switch */
         end

      return 1;

   end


/* END OF FILE: F_Send.C */
