/*

    $Header: Welmat:src/Welmat/xprfts/RCS/receiveX.c,v 1.2 92/11/24 23:40:39 rwm Exp Locker: rwm $

    receive a file with XModem.

    Copyright (C) 1988,1989,1990 Michael Richardson
    Copyright (C) 1992 Russell McOrmond

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

*/

#include <proto/all.h>
#include <exec/types.h>
#include <exec/memory.h>
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "xproto.h"
#include "xmodem.h"
#include "xprfts.h"

#ifdef RCSID
static char RCSid[]="$Id: receiveX.c,v 1.2 92/11/24 23:40:39 rwm Exp Locker: rwm $";
#endif

#define WRITESIZE 1024
#define EMPTYSIZE 1024

struct count_buf {
  short   count;
  UBYTE   *buf;
  short   state;
  short   ckmode;
  short   gotblock;
  unsigned short   check;
};

extern unsigned short compute_crc();
extern int incomingNotify();

long receiveXPacket(struct count_buf *thisCount_buf,struct Vars *v)
{
  long count;
  short gotit=FALSE;
  char temp[2];

  thisCount_buf->ckmode=0;
  while(!gotit) {
    count=xpr_sread(&v->io,temp,1,2000000);
    switch(count) {
      case -1:
        updmsg(v,"RXP:No Carrier");
        return(NODCD);

      case  0:
        updmsg(v,"RXP:Timeout");
        return(TIMEOUT); /* a timeout */

      case  1:
        {
          char bbb[80];
          SPrintF(bbb,"RCPC: %ld",temp[0]&0xff);
          updmsg(v,bbb);
        }
        switch(temp[0]&0xff) {
          case TSYNCH:
            updmsg(v,"RXP:TSYNCH");
            return(TIMEOUT); /* Cause us to poll again... */

          case EOT:
            updmsg(v,"RXP:EOT");
            return(GOTEOT);

          case SOH:
            thisCount_buf->ckmode=1;
          case SYN:
            gotit=TRUE;
            break;

          case ACK:
            thisCount_buf->ckmode=2;
            if(v->option_w=='N') return(GOTACK);
            gotit=TRUE;
            break;

          case NAK:
            thisCount_buf->ckmode=3;
            if(v->option_w=='N') return(GOTNAK);
            gotit=TRUE;
            break;

          case CNAK:
            thisCount_buf->ckmode=4;
            if(v->option_w=='N') return(GOTCNAK);
            gotit=TRUE;
            break;

          default:
            break;
        }
        break;
    }
  }
  /* let's get the block and block compliment */
  count=xpr_sread(&v->io,temp,2,500000);
  switch(count) {
    case -1:
      updmsg(v,"RXP:No Carrier");
      return(NODCD);

    case  1:
    case  0:
      switch(thisCount_buf->ckmode) {
        case 2:
          updmsg(v,"RXP:TimeOut getting block/comp for ACK");
          return(GOTACK);
        case 3:
          updmsg(v,"RXP:TimeOut getting block/comp for NAK");
          return(GOTNAK);
        case 4:
          updmsg(v,"RXP:TimeOut getting block/comp for CNAK");
          return(GOTCNAK);
      }
      updmsg(v,"RXP:TimeOut getting block/comp for block");
      return(GOTBADBLOCK); /* a timeout */

    case  2:
      thisCount_buf->gotblock=temp[0];
      if((temp[0]&0xff)!=((~temp[1])&0xff)) {
        updmsg(v,"RXP:Block/Comp didn't match!");
        return(GOTBADBLOCK);
      }
      break;
  }
  switch(thisCount_buf->ckmode) {
    case 2:
      return(GOTSACK);
    case 3:
      return(GOTSNAK);
    case 4:
      return(GOTSCNAK);
  }

  count=xpr_sread(&v->io,thisCount_buf->buf,128,5000000);
  switch(count) {
    case -1:
      updmsg(v,"RXP:NoCarrier");
      return(NODCD);

    default: /* we didn't get all the characters! */
      updmsg(v,"RXP:Didn't get all chars!!");
      return(GOTBADBLOCK); /* a timeout */

    case 128:
      break;
  }
  if(thisCount_buf->ckmode) {
    unsigned long crc;
    unsigned long gotcrc;

    /* get the CRC */
    count=xpr_sread(&v->io,temp,2,5000000);
    if(count==2) {

      gotcrc=((temp[0]&0xff)<<8)|(temp[1]&0xff);
      crc=compute_crc(thisCount_buf->buf,128)&0xffff;
      if(crc==gotcrc) {
        return(GOTBLOCK);
      }
    }
  } else {
    unsigned short i;
    unsigned short cksum;

    count=xpr_sread(&v->io,temp,1,5000000);
    if(count==1) {
      cksum=0;
      for(i=0; i<128; cksum+=thisCount_buf->buf[i++]);
      if((temp[0]&0xff)==(cksum&0xff)) {
        return(GOTBLOCK);
      }
    }
  }
  return(GOTBADBLOCK);
}

long receiveXFile(char *filename,struct Vars *v,BOOL fileattach)
{
  char ckmode;
  short count;
  int state;
  BYTE rcvwindow;
  struct count_buf aCount_buf;
  void *pktFile; /* XPR filehandle to file */
  long filesize,sizeleft;
  long filedate;
  block *lastPacket;
  struct WindowX myWindow;
  short  OneEot;
  short  SeaLink;     /* true if SOH initiated SeaLink block received */

  OneEot=FALSE;

  filesize=filedate=0;  /* FIXME */
 
  ckmode=1;
  aCount_buf.count=count=0;
  myWindow.nextblock=0;
  aCount_buf.gotblock = 0;

  if (!AllocWindowX(&myWindow)) {
    updmsg(v,"Couldn't allocate Xmodem blocks");
    return(ERROR);
  }

  if((lastPacket=(block *)RemHead(&myWindow.wx_freeblocks))==NULL) {
    updmsg(v,"Full Tilt-Head");
    FreeWindowX(&myWindow);
    return(ERROR);
  }
  state=GOTBADBLOCK;

  xpr_sflush(&v->io);
  count=0;

  while((aCount_buf.count<10) && (count++<30) && (state!=GOTBLOCK || 
            ((v->option_i=='T') &&
                (aCount_buf.ckmode) && (aCount_buf.gotblock==0)))) {
/*
 |-----+----------+-------------------------+-------------------------+-----|
 | XR0 | RecStart | 1 prefer crc mode       | Send "C"                | XR1 |
 |     |          | 2 want checksum mode    | send NAK                | XR1 |
 */

    AckNak(v,(v->option_a=='Y'),0, ((aCount_buf.count<6) && (v->option_n=='N'))
                 ? CNAK: NAK);

/*
 |-----+----------+-------------------------+-------------------------+-----|
 | XR1 | WaitFirst| 1 10 retries or 1 minute| report receive failure  | exit|
 |     |          | 2 > 3 retries or 30 secs| set want checksum mode  | XR0 |
 |     |          | 3 EOT received          | send ACK, report no file| exit|
 |     |          | 4 TeLink block recd     | send ACK, set crc/cksm  | XR2 |
 |     |          | 5 data block recd       | send ACK, set crc/cksm  | XR2 |
 |     |          | 6 bad block or 2-10 secs| incr retry count        | XR0 |
 */

    aCount_buf.buf=lastPacket->bl_contents;
    aCount_buf.ckmode=0;  /* always use cksum on first block */
    myWindow.nextblock=0;

    state=receiveXPacket(&aCount_buf,v);
    if(state==TIMEOUT);
    else if(state==GOTEOT && !OneEot) {
      updmsg(v,"Xpacket EOT?");
      OneEot=TRUE;
    }
    else if(state==GOTEOT) {
      updmsg(v,"No more files");
      FreeWindowX(&myWindow);
      return(GOTEOT);
    }
    else if(state==NODCD) {
      FreeWindowX(&myWindow);
      return(NODCD);
    }
    else if(state==GOTBADBLOCK) {
      xpr_sflush(&v->io);
      if((aCount_buf.count++)==6) {
        ckmode=0;          /* try checksum mode */
      }
      else if(aCount_buf.count==10) {
        updmsg(v,"Too many tries. Other end dead?");
        FreeWindowX(&myWindow);
        return(ERROR);
      }
    } else if (state==GOTBLOCK) {
      if (aCount_buf.ckmode) updmsg(v,"SHeader!");
      else updmsg(v,"THeader!");
      if (aCount_buf.gotblock) {
        SPrintF(v->scratch,"GotBlock %ld!",aCount_buf.gotblock);
        updmsg(v,v->scratch);
      }
    } else if (((state==GOTSACK) || (state==GOTACK)) && (v->option_7=='Y')) {
      state=receivemdm7(filename,v);
    }
  }


  if (aCount_buf.gotblock==0) {
    if (aCount_buf.ckmode) updmsg(v,"SHeader!");
       else updmsg(v,"THeader!");

    SeaLink=(v->option_s=='F')
        || (aCount_buf.ckmode && (v->option_s!='N'));
 
    /* Receiving of 'SOH' causes ckmode to be set to 1 - SYN stays at 0 */
  }
  else SeaLink=FALSE;

  AckNak(v,SeaLink,aCount_buf.gotblock,ACK);

  if(aCount_buf.gotblock!=0) {
      /* we missed the Telink block! */

      xpr_sflush(&v->io);
      if (fileattach && !strlen(filename)) {
        updmsg(v,"NO FILENAME on FILE ATTACH! Remote Broken.");
        FreeWindowX(&myWindow);
        return(ERROR);
      }
      sizeleft=-1;
      updmsg(v,"Receiving Packet of Unknown Size");
      strcpy(filename,"00000000.PKT");
  } else {
      register unsigned char *p,*q;
      register short i;

      /* get filesize */
      p=(unsigned char *)&filesize;
      q=(unsigned char *)lastPacket->bl_contents;
      p[3]=q[0];
      p[2]=q[1];
      p[1]=q[2];
      p[0]=q[3];

      /* get filename */
      p=(v->scratch);
      q=(unsigned char *)lastPacket->bl_contents+8;
      for(i=0;i<32 && *q!=' ' && *q!='\0';i++) {
        *p++=*q++;
      }
      *p='\0';

      strcpy(filename,v->scratch);

      ckmode=lastPacket->bl_contents[41];
      sizeleft=filesize;
      SPrintF(v->scratch,"Receiving %ld bytes (%ld blocks) with %s",
              filesize,filesize/128+1, ((SeaLink) ? "FTS-7" : "FTS-1"));
      updmsg(v,v->scratch);
      if(strlen(filename)<16) {
         *(lastPacket->bl_contents+40)='\0'; /* Null terminate automatically */
         SPrintF(v->scratch,"Mailer: %s",lastPacket->bl_contents+25);
         updmsg(v,v->scratch);
      }
  }

  if((pktFile=(void *)xpr_fopen(&v->io,filename,"w"))==NULL) {
    SPrintF(v->scratch,"Can't open temp file %s",filename);
    upderr(v,v->scratch);
    FreeWindowX(&myWindow);
    return(ERROR);
  }
  if(aCount_buf.gotblock!=0) {
     filesize=128;
     xpr_fwrite(&v->io,lastPacket->bl_contents,128,1,pktFile);
     SPrintF(v->scratch,"Received block #1");
     updmsg(v,v->scratch);
     myWindow.nextblock=2;
  } else {
    myWindow.nextblock=1;
  }

  aCount_buf.buf=lastPacket->bl_contents;
  aCount_buf.ckmode=ckmode;
  OneEot=FALSE;

  state=GOTBLOCK;
  while(state==GOTBLOCK) {
/*
 |-----+----------+-------------------------+-------------------------+-----|
 | XR2 | WaitBlock| 1 10 retries or 1 minute| report receive failure  | exit|
 |     |          | 2 EOT received          | send ACK, report recd ok| exit|
 |     |          | 3 data block received   | send ACK                | XR2 |
 |     |          | 4 bad block or 2-10 secs| send NAK, incr retry cnt| XR2 |
 */
    aCount_buf.count=0;
    state=GOTBADBLOCK;
    while(state==GOTBADBLOCK) {
      state=receiveXPacket(&aCount_buf,v);
      if(state==GOTBLOCK) {
        OneEot=FALSE;
        rcvwindow=((aCount_buf.gotblock&0xff)-(myWindow.nextblock&0xff))&0xff;
        if(rcvwindow==0) {
          int blocksize;
          if(sizeleft==-1) {
            blocksize=128;
            filesize+=128;
          }
          else if(sizeleft<128) {
            blocksize=sizeleft;
            sizeleft=sizeleft-blocksize;
          }
          else {
            blocksize=128;
            sizeleft=sizeleft-blocksize;
          }
          xpr_fwrite(&v->io,lastPacket->bl_contents,blocksize,1,pktFile);
          AckNak(v,SeaLink,aCount_buf.gotblock,ACK);
          SPrintF(v->scratch,"BytesLeft %08ld",sizeleft);
          updmsg(v,v->scratch);
          ++(myWindow.nextblock);
          aCount_buf.count=0;    /* Got to reset the bad block count */
        }
        else if(rcvwindow==(-1)) {
          /* previous block was resent */
          AckNak(v,SeaLink,aCount_buf.gotblock,ACK);
        }
        else if (SeaLink && (rcvwindow<MAXXWINDOW)) {
          SPrintF(v->scratch,"Received Block #%ld wanted %d",
                       aCount_buf.gotblock , myWindow.nextblock);
          updmsg(v,v->scratch);

/* Do some wild Sealink Windowing Stuff */

          aCount_buf.count++;
        }
        else {
          SPrintF(v->scratch,"Was waiting for block %ld, got block %ld",
                       myWindow.nextblock, aCount_buf.gotblock);
          updmsg(v,v->scratch);
          xpr_sflush(&v->io);
          aCount_buf.count++;
          if(SeaLink) AckNak(v,SeaLink,myWindow.nextblock,NAK);
        }
      }
      else if(state==GOTBADBLOCK) {
        OneEot=FALSE;
        xpr_sflush(&v->io);
        AckNak(v,SeaLink,aCount_buf.gotblock,NAK);
        aCount_buf.count++;
      }
      else if(state==NODCD) {
        xpr_fclose(&v->io,pktFile);
        FreeWindowX(&myWindow);
        return(NODCD);
      }
      else if(state==GOTEOT && (sizeleft == -1)) {
          /*  We don't know the size - Do Double EOT to make sure */
         if (!OneEot) {
           AckNak(v,SeaLink,myWindow.nextblock&0xff,NAK);
           xpr_sflush(&v->io); 
           OneEot=TRUE;
           state=GOTBADBLOCK;
           updmsg(v,"Eot? Tell me again!");
         } else {
           updmsg(v,"Eot. I understand.");
           AckNak(v,SeaLink,myWindow.nextblock&0xff,ACK);
           xpr_sflush(&v->io);
         }
      }
      else if(state==GOTEOT && (sizeleft>0)) {
        /* Junk EOT received */
        AckNak(v,SeaLink,myWindow.nextblock&0xff,NAK);
        xpr_sflush(&v->io);
        state=GOTBADBLOCK;
        updmsg(v,"Hey, We're not finished!");
      }
      else if(state==GOTEOT) {
        updmsg(v,"EOT!");
        AckNak(v,SeaLink,myWindow.nextblock&0xff,ACK);
        xpr_sflush(&v->io);
      }
    }
  }
  xpr_fclose(&v->io,pktFile);
  FreeWindowX(&myWindow);
  return((state==GOTEOT) ? OK : ERROR);
}

long receivemdm7(char *filename,struct Vars *v)
{
  long i,state;
  UBYTE filechar,c,cksum,inboundfile[13],temp[2],temp1[2];

  for(filechar=0;filechar<13;filechar++) 
            (inboundfile[filechar])=NULL;

/*
  |-----+----------+-------------------------+-------------------------+-----|
  | MR0 | SendNak  | 1 20 tries or 1 minute  | report filename failure | exit|
  |     |          | 2                       | send NAK, incr try cnt  | MR1 |
  |-----+----------+-------------------------+-------------------------+-----|
  | MR1 | WaitAck  | 1 rcd ACK               |                         | MR2 |
  |     |          | 2 rcd EOT               | report no files remain  | exit|
  |     |          | 3 5 secs & no ACK/EOT   |                         | MR0 |

   If we are here, we've already received the ACK/SealinkACK

*/

  updmsg(v,"Getting Modem7 filename\n");
  filechar=cksum=c=0;

/*
  |-----+----------+-------------------------+-------------------------+-----|
  | MR2 | WaitChar | 1 recd EOT (can happen?)| report no files remain  | exit|
  |     |          | 2 recd SUB              | send checksum byte      | MR3 |
  |     |          | 3 recd "u"              |                         | MR0 |
  |     |          | 4 recd char of name     | send ACK                | MR2 |
  |     |          | 5 no char in 1 second   |                         | MR0 |
*/

  while(TRUE) {
    state=xpr_sread(&v->io,temp,1,10000000);
    if(state==-1) return(NODCD);
    if(state==0) return(TIMEOUT);
    else if((c=temp[0]&0xff)=='u') return(MDM7BAD);
    else if(c==EOT) return(GOTEOT);
    else if(c!=SUB) {
      inboundfile[(filechar<13)? filechar++ : filechar ]=c;
      cksum+=c;
      temp1[0]=ACK;
      xpr_swrite(&v->io,temp1,1);
    }
    else {
      cksum+=SUB;
      temp1[0]=cksum&0xff;
      xpr_swrite(&v->io,temp1,1);

/*
  |-----+----------+-------------------------+-------------------------+-----|
  | MR3 | WaitOkCk | 1 recd ACK within 1 sec | report recd filename ok | exit|
  |     |          | 2 recd "u" or other char|                         | MR0 |
*/

      state=xpr_sread(&v->io,temp,1,10000000);
      if(state==1 && ((temp[0]&0xff)==ACK)) {
        for (i=10;i>7;i--) {
          inboundfile[i+1]=inboundfile[i];
        }
        inboundfile[8]='.';

        SPrintF(v->scratch,"Modem7: '%s'\n",inboundfile);
        updmsg(v,v->scratch);

        filechar=0;
        for (i=0;i<13;i++) {
          if(inboundfile[i]!=' ')
             inboundfile[filechar++]=inboundfile[i];
        }
        inboundfile[filechar]='\0';
        SPrintF(v->scratch,"Modem7 Filename: %s \n",inboundfile);
        updmsg(v,v->scratch);
        strcpy(filename,inboundfile);
        return(OK);
      } else
        return(MDM7BAD);
    }
  }
}
