/*--------------------------------------------------------*
 *
 *    Protocolo XMODEM-1024
 *
 *    Autor: Jose' Antonio Borges
 *
 *    Em dezembro/95
 *
 *--------------------------------------------------------*/

#include <stdio.h>
#include <signal.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/types.h>

#define TAMDEFAULT 1024

/*--------------------------------------------------------*
 *                  retornos das rotinas
 *--------------------------------------------------------*/
/*
        0 - Ok, bem transmitido
        1 - Arquivo nao encontrado
        2 - Erro na leitura do arquivo
        3 - Nao sincronizou com parceiro
        4 - Limite de tentativas de transmissao esgotado
        5 - Faltou confirmacao do fechamento do arquivo
        6 - Erro na criacao do arquivo
        7 - Erro na escrita do arquivo
        8 - Erro de transmissao ou parceiro mudo
        9 - Erro de sequencia dos blocos
*/

/*--------------------------------------------------------*
 *                  constantes e variaveis
 *--------------------------------------------------------*/

#define MAXTENT 20

#define NAK 21
#define ACK 6
#define SOH 1
#define STX 2
#define EOT 4
#define CAN 24
#define CTLC 3

#define XM_OK      0
#define XM_ARQNENC 1
#define XM_ERROARQ 2
#define XM_NAOSINC 3
#define XM_LIMTENT 4
#define XM_ERRFIM  5
#define XM_ARQNCRI 6
#define XM_ERRESC  7
#define XM_ERROREC 8
#define XM_ERROSEQ 9

#define FALSE 0
#define TRUE  1
#define ERRO  -1

unsigned char outchar, block[1024];
int arq;
unsigned char timeout;

/*--------------------------------------------------------
 *               tratamento da saida sem eco
 *--------------------------------------------------------*/

void comEco ()
{
    system ("stty echo -raw");
}

/*--------------------------------------------------------*/

void cancela ()
{
    comEco();
    exit (1);
}

/*--------------------------------------------------------*/

void semEco ()
{
    system ("stty -echo raw");

    if (signal(SIGINT, cancela) == SIG_IGN) {
        signal (SIGINT,  SIG_IGN);
        signal (SIGKILL, SIG_IGN);
    }
    else  {
        signal (SIGINT,  cancela);
        signal (SIGKILL, cancela);
    }
}

/*--------------------------------------------------------*
 *                 escreve um caractere
 *--------------------------------------------------------*/

int esclink(c)
unsigned char c;
{
   unsigned char outchar;

   outchar = c;
   write (1, &outchar, 1);
}

/*--------------------------------------------------------*
 *            recebe um buffer com timeout
 *--------------------------------------------------------*/

void recebeBuf (buf, ncarac, maxsegundos)
unsigned char *buf;
int ncarac, maxsegundos;
{
    int i, nchegados;
    int posic;

    posic = 0;
    i = 1;
    while (i <= maxsegundos+1)  {
	ioctl (0, FIONREAD, &nchegados);

        if (nchegados > 0) {
             if (nchegados > ncarac)  nchegados = ncarac;
	     read (0, &buf[posic], nchegados);

             ncarac -= nchegados;
             posic += nchegados;

             if (ncarac == 0)  {
	         timeout = FALSE;
	         return;
             }
        }
	else  {
	    i++;
            sleep(1);
	}
    }

    timeout = TRUE;
}

/*--------------------------------------------------------*
 *            recebe um caractere com timeout
 *--------------------------------------------------------*/

unsigned char recebeCarac (maxsegundos)
int maxsegundos;
{
    unsigned char c;
    c = 0xff;
    recebeBuf (&c, 1, maxsegundos);
    return c;
}

/*--------------------------------------------------------*
 *                       limpa o buffer
 *--------------------------------------------------------*/

limpaBuf ()
{
    int nchegados;

    do  {
        ioctl (0, FIONREAD, &nchegados); 
	if (nchegados > 0) 
            read (0, block, nchegados <= 1024 ? nchegados : 1024);
    }  while (nchegados > 0);
}

/*--------------------------------------------------------*
 *     envia arquivo segundo protocolo Xmodem 1024-CRC
 *--------------------------------------------------------*/

int sendXModemBlock (blockn, tamBloco)
unsigned char blockn;
int tamBloco;
{
    int i;
    unsigned char checksum;

    if (tamBloco == 1024)
        esclink (STX);
    else
        esclink (SOH);

    esclink (blockn);
    esclink (blockn ^ 0xff);
    write (1, block, tamBloco);

    checksum = 0;
    for (i = 0; i <= tamBloco-1; i++)
            checksum = (checksum + block[i]) & 0xff;
    esclink (checksum);
}

/*--------------------------------------------------------*/

int xmodemSend (nomearq)
char *nomearq;
{
    unsigned char numBloco;
    char c;
    unsigned char mandaBloco;
    int n, tamBloco, lidos, nrep;
    long faltaTransm;
    unsigned char status;

    semEco ();
    status = 0;

    arq = open (nomearq, O_RDONLY, 0x400);
    if (arq < 0)  {
            status = XM_ARQNENC;    /* retorno 1: arquivo nao encontrado */
            goto fim;
    }

    for (nrep = 1; nrep <= MAXTENT; nrep++)  {
            c = recebeCarac (5);
            if (c == NAK)  goto iniciaTransmissao;
	    if (c == CTLC)  goto cancelaTudo;
	    sleep(1);
	    limpaBuf ();
    }

cancelaTudo:
    status = XM_NAOSINC;     /* retorno 3: nao recebeu NAK inicial */
    goto fim;

iniciaTransmissao:

    limpaBuf ();
    tamBloco = TAMDEFAULT;
    numBloco = 1;
    faltaTransm = lseek (arq, 0L, SEEK_END);
    lseek (arq, 0L, SEEK_SET);

    while (faltaTransm > 0)  {
            if (faltaTransm < 1024)
                tamBloco = 128;
            faltaTransm = faltaTransm - tamBloco;

            lidos = read (arq, block, tamBloco);
            if (lidos < 0)  {
                    status = XM_ERROARQ;   /* erro no arquivo */
                    goto fim;
                }

	    for (n = lidos; n < tamBloco; n++)
                block[n] = 0x1a;   /* control-Z */

            mandaBloco = TRUE;
	    for (nrep = 1; nrep <= MAXTENT; nrep++)
                {
                    if (mandaBloco)  {
                             sendXModemBlock (numBloco, tamBloco);
                             mandaBloco = FALSE;
                    }

                    c = recebeCarac (2);
                    switch (c)  {
                        case 'C':
                        case NAK:
                                  mandaBloco = TRUE;
                                  break;
                        case ACK:
                                 numBloco = (numBloco + 1) & 0xff;
                                 goto proximoBloco;
                        case CAN:
				 status = XM_ERRFIM;
                                 esclink (ACK);
                                 goto fim;
			default:
				 limpaBuf();
				 sleep (1);
				 break;
                    }
                }

            status = XM_LIMTENT;    /* limite de tentativas esgotado */
            esclink (CAN);
            c = recebeCarac (1);
            goto fim;

proximoBloco:  ;

        }

    for (nrep = 1; nrep <= 7; nrep++)  {
            esclink (EOT);
            c = recebeCarac (1);
            if (c == ACK) goto fim;
        }

    if (c != ACK)
        status = XM_ERRFIM;    /* faltou ACK de EOT */

fim:
    close (arq);
    comEco ();
    return status;
}

/*--------------------------------------------------------*
 *        recebe arquivo segundo protocolo Xmodem
 *--------------------------------------------------------*/

int recXModemBlock (tamBloco)
int tamBloco;
{
    unsigned char blockn;
    unsigned char invblockn, rchecksum, checksum;
    int i;

    checksum = 0;
    blockn    = recebeCarac (1);    if (timeout) return ERRO;
    invblockn = recebeCarac (1);    if (timeout) return ERRO;

    recebeBuf (block, tamBloco, 10);
    if (timeout) return -1;

    for (i = 0; i <= tamBloco-1; i++)
             checksum = (checksum + block[i]) & 0xff;

    rchecksum = recebeCarac (1);

    if (timeout)                      return ERRO;
    if ((invblockn ^ 0xff) != blockn) return ERRO;
    if ((checksum != rchecksum))      return ERRO;

    return blockn;
}

/*--------------------------------------------------------*/

unsigned int recebeBloco (numBloco, tamBloco)
unsigned char *numBloco;
int tamBloco;
{
    unsigned char recebeu;
    int nbloco;

    nbloco = recXModemBlock (tamBloco);
    if (nbloco < 0)  {
            esclink (NAK);
            return XM_ERROREC;      /* timeout ou checksum errado */
    }

    if (((nbloco+1) & 0xff) == *numBloco) {   /* repetiu */
           esclink (ACK);
           return XM_OK;
    }

    if (*numBloco == nbloco)  {
            if (write (arq, block, tamBloco) < 0)
                    return XM_ERRESC;   /* retorno 2: erro de disco */

            *numBloco = ((*numBloco)+1) & 0xff;
            esclink (ACK);
            return XM_OK;
    }

    return XM_ERROSEQ;    /* bloco com sequencia errada */
}

/*--------------------------------------------------------*/

int xmodemReceive (nomearq)
char *nomearq;
{
    int i;
    unsigned char c;
    unsigned char numBloco;
    int status, nrep;
    unsigned char processando;
    int tamBloco;


    arq = open (nomearq, O_WRONLY | O_CREAT | O_TRUNC, 0664);
    if (arq < 0)  {
            esclink (CAN);
            return XM_ARQNCRI;    /* retorno 1: arquivo nao pode ser criado */
    }

    semEco();
    status = XM_OK;
    limpaBuf ();

    numBloco = 1;
    nrep = 0;
    processando = TRUE;

    esclink (NAK);

    while (processando && (nrep <= MAXTENT))  {
            nrep = nrep + 1;
            c = recebeCarac (5);
            if (timeout)  esclink (NAK);

            switch (c)  {
                 case STX:  tamBloco = 1024;
			    goto recebetexto;

                 case SOH:  tamBloco = 128;

                           /* em STX e SOH */

recebetexto:
                           status = recebeBloco (&numBloco, tamBloco);
                           if (status == XM_OK)
                               nrep = 0;
                           else  {
                               if ( (status == XM_ARQNCRI) ||
                                    (status == XM_ERRESC)  ||
                                    (status == XM_ERROSEQ)  )
                                           processando = FALSE;
                           }
                           break;

                 case EOT:
                           esclink (ACK);
                           processando = FALSE;
                           break;

                 case CAN:
		 case CTLC:
                           for (i = 1; i <= 12; i++)  esclink (CAN);
                           status = XM_ERRFIM;
                           processando = FALSE;
                           break;

                 default:
		     limpaBuf ();    /* timeout ou erro */
            }
    }

    if (nrep >= MAXTENT)  {
            status = XM_NAOSINC;
            for (i = 1; i <= 5; i++)  {
                    esclink (CAN);             /* cancela transmissao */
		    sleep (1);
                    if (recebeCarac (1) == ACK) goto fim;
		    limpaBuf ();
            }
    }

fim:
    close (arq);
    comEco();
    return status;
}
