#include <u.h>
#include <libc.h>
#include <ctype.h>

enum {
       Soh=    0x1,
       Stx=    0x2,
       Eot=    0x4,
       Ack=    0x6,
       Nak=    0x15,
       Cancel= 0x18,
};

int send(uchar*, int);
int notifyf(void*, char*);

int debug, progress, onek;

void
errorout(int ctl, int bytes)
{
       uchar buf[2];

       buf[0] = Cancel;
       write(1, buf, 1);
       fprint(2, "\nxms: gave up after %d bytes\n", bytes);
       write(ctl, "rawoff", 6);
       exits("cancel");
}

ushort
updcrc(int c, ushort crc)
{
       int count;

       for (count=8; --count>=0;) {
               if (crc & 0x8000) {
                       crc <<= 1;
                       crc += (((c<<=1) & 0400)  !=  0);
                       crc ^= 0x1021;
               }
               else {
                       crc <<= 1;
                       crc += (((c<<=1) & 0400)  !=  0);
               }
       }
       return crc;
}

void
main(int argc, char **argv)
{
       uchar c;
       uchar buf[1024+5];
       uchar seqno;
       int fd, ctl;
       long n;
       int sum;
       uchar *p;
       int bytes;
       int crcmode;

       ARGBEGIN{
       case 'd':
               debug = 1;
               break;
       case 'p':
               progress = 1;
               break;
       case '1':
               onek = 1;
               break;
       }ARGEND

       if(argc != 1){
               fprint(2, "usage: xms filename\n");
               exits("usage");
       }
       fd = open(argv[0], OREAD);
       if(fd < 0){
               perror("xms");
               exits("open");
       }

       ctl = open("/dev/consctl", OWRITE);
       if(ctl < 0){
               perror("xms");
               exits("consctl");
       }
       write(ctl, "rawon", 5);

       /* give the other side a 30 seconds to signal ready */
       atnotify(notifyf, 1);
       alarm(30*1000);
       crcmode = 0;
       for(;;){
               if(read(0, &c, 1) != 1){
                       fprint(2, "xms: timeout\n");
                       exits("timeout");
               }
               c = c & 0x7f;
               if(c == Nak)
                       break;
               if(c == 'C') {
                       if (debug)
                               fprint(2, "crc mode engaged\n");
                       crcmode = 1;
                       break;
               }
       }
       alarm(0);

       /* send the file in 128/1024 byte chunks */
       for(bytes = 0, seqno = 1; ; bytes += n, seqno++){
               n = read(fd, buf+3, onek ? 1024 : 128);
               if(n < 0)
                       exits("read");
               if(n == 0)
                       break;
               if(n < (onek ? 1024 : 128))
                       memset(&buf[n+3], 0, (onek ? 1024 : 128)-n);
               buf[0] = onek ? Stx : Soh;
               buf[1] = seqno;
               buf[2] = 255 - seqno;

               /* calculate checksum and stuff into last byte */
               if (crcmode) {
                       unsigned short crc;
                       crc = 0;
                       for(p = buf + 3; p < &buf[(onek ? 1024 : 128)+3]; p++)
                               crc = updcrc(*p, crc);
                       crc = updcrc(0, crc);
                       crc = updcrc(0, crc);
                       buf[(onek ? 1024 : 128) + 3] = crc >> 8;
                       buf[(onek ? 1024 : 128) + 4] = crc;
               }
               else {
                       sum = 0;
                       for(p = buf + 3; p < &buf[(onek ? 1024 : 128)+3]; p++)
                               sum += *p;
                       buf[(onek ? 1024 : 128) + 3] = sum;
               }

               if(send(buf, (onek ? 1024 : 128) + 4 + crcmode) < 0)
                       errorout(ctl, bytes);
               if (progress && bytes % 10240 == 0)
                       fprint(2, "%dK\n", bytes / 1024);
       }

       /* tell other side we're done */
       buf[0] = Eot;
       if(send(buf, 1) < 0)
               errorout(ctl, bytes);

       fprint(2, "xms: %d bytes transmitted\n", bytes);
       write(ctl, "rawoff", 6);
       exits(0);
}

/*
*  send a message till it's acked or we give up
*/
int
send(uchar *buf, int len)
{
       int tries;
       int n;
       uchar c;

       for(tries = 0;; tries++, sleep(2*1000)){
               if(tries == 10)
                       return -1;
               if(write(1, buf, len) != len)
                       return -1;

               alarm(30*1000);
               n = read(0, &c, 1);
               alarm(0);
               if(debug) switch(c){
               case Soh:
                       fprint(2, " Soh");
                       break;
               case Eot:
                       fprint(2, " Eot");
                       break;
               case Ack:
                       fprint(2, " Ack");
                       break;
               case Nak:
                       fprint(2, " Nak");
                       break;
               case Cancel:
                       fprint(2, "\nremote Cancel\n");
                       return -1;
               default:
                       if(isprint(c))
                               fprint(2, "%c", c);
                       else
                               fprint(2, " \\0%o", c);
               }
               c = c & 0x7f;
               if(n == 1 && c == Ack)
                       break;
       }
       return 0;
}

int
notifyf(void *a, char *msg)
{
       USED(a);
       if(strcmp(msg, "alarm") == 0)
               return 1;
       return 0;
}