/*

  Zerocat Chipflasher --- Flash free firmware, kick the Management Engine.

  Copyright (C) 2015, 2016  kai <kmx@posteo.net>
  Copyright (C) 2016, 2017, 2018, 2020, 2021  Kai Mertens <kmx@posteo.net>

  This file is part of Zerocat Chipflasher.

  Zerocat Chipflasher 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 3 of the License, or (at your
  option) any later version.

  Zerocat Chipflasher 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 Zerocat Chipflasher.
  If not, see <http://www.gnu.org/licenses/>.


***/


//standard library headers
# include <string.h>
# include <stdlib.h>
# include <stdio.h>
# include <unistd.h>
# include <fcntl.h>
# include <termios.h>
# include <sys/ioctl.h>
# include <sys/types.h>
# include <sys/timerfd.h>
# include <sys/stat.h>
# include <sys/select.h>
# include <signal.h>
# include <errno.h>

//project headers
# include "../../firmware1/src/libcommon/common.h"
# include "../../firmware1/src/libcommon/linespec.h"
# include "../../firmware1/src/libcommon/serial-codes.h"    //include serial control codes
# include "connect.h"

//project source files
# include "../../firmware1/src/libcommon/hexdigit2bin.c"
# include "bin2hexdigit.c"
# include "SREC_addrlen.c"
# include "lineinfo.c"

//globals
struct termios old_stdio;
char * fileToSend = FILE_TO_SEND;
char * fileToCatch = FILE_TO_CATCH;



void errout (
  enum ERRCODE_t errcode,
  int count
)
{
  /*

    Validate error code.

    TODO: Shouldn't we use stderr for error messages?

    errcode       Error code.
    count         Number of additional line requests, total or
                  individual error

  ***/


  if(errcode == ERRC__SUCCESS) {
    if(!count)
      printf(MSG_START);
    else
      printf(MSG_WARN);
    headline('~', HEADER_CONNECT);
  }
  else {
    if(count && errcode >= ERRC__CHECKSUM_MISMATCH) {   //still some retry trials left
      printf(MSG_WARN);
      headline('~', HEADER_CONNECT);
      printf("\rTransmission Warning: ");
    }
    else {    //no retry trials left, no suitable error code
      printf(MSG_ERROR);
      headline('~', HEADER_CONNECT);
      printf("\rTransmission Error: ");
    }
  }

  switch(errcode) {
    case ERRC__SUCCESS:
      if(!count) {
        printf("\rTransmission complete, please verify.\r\n");
      }
      else {
        printf("\rTransmission complete, however ");
        (count == 1)
          ? printf("one transmission error ")
          : printf("%d transmission errors ", count);
        printf("had to be repaired. Please verify!\r\n");
      }
      break;

    case ERRC__LINE_COUNT_MISMATCH:
      printf("Line Count Mismatch.\r\n");
      break;

    case ERRC__JOB_CANCELLATION:
      printf("Job Cancellation.\r\n");
      break;

    case ERRC__NO_SREC:
      printf("No Motorola S-Record.\r\n");
      break;

    case ERRC__BUFFER_OVERRUN:
      printf("Buffer Overrun.\r\n");
      break;

    case ERRC__LINE_TOO_LONG:
      printf("Line too Long.\r\n");
      break;

    case ERRC__NO_HEX_DIGIT:
      printf("No HEX Digit received.\r\n");
      break;

    case ERRC__LINE_ENDING_ERROR:
      printf("Line Ending Error.\r\n");
      break;

    case ERRC__HEXD_PARSE_ERROR:
      printf("Hex-Dump Parse Error.\r\n");
      break;

    case ERRC__NO_SUCH_FILE:
      printf("File '%s' is not available.\r\n", fileToSend);
      break;

    case ERRC__PORT_OPEN_FAILURE:
      printf("Failed to Open TTY Port.\r\n");
      printf("An existing TTY port connection now has probably been corrupted.\r\n");
      break;

    case ERRC__INVALID_ADDRESS:
      printf("Invalid Address!\r\n");
      break;

    case ERRC__DEBUG:
      printf("DEBUG!\r\n");
      break;

    case ERRC__CHECKSUM_MISMATCH:
      printf("Checksum Mismatch.\r\n");
      break;

    case ERRC__LINE_LENGTH_MISMATCH:
      printf("Line Length Mismatch.\r\n");
      break;

    case ERRC__WRONG_CHARACTER:
      printf("Wrong Character.\r\n");
      break;

    case ERRC__LINE_TIMEOUT:
      printf("Line Timeout.\r\n");
      break;

  }
  printf(MSG_STOP);
}



void set_DTR (
  int fdtty,
  int level
)
{
  /*

    Asserts MARK or SPACE to the DTR line.
    fdtty   File descriptor for serial device Teletype.
    level   i.e. MARK or SPACE.

  ***/


  int serial;

  //get struct info
  ioctl(fdtty, TIOCMGET, &serial);

  //modify struct
  if(level == MARK)   //negative voltage on DTR
    serial &= ~TIOCM_DTR;
  else    //positive voltage on DTR
    serial |= TIOCM_DTR;

  //apply struct info
  ioctl(fdtty, TIOCMSET, &serial);
}



void lock_tty (
  int fdtty
)
{
  /*

    Lock Serial Port

    See `man tty_ioctl`

  ***/


  ioctl(fdtty, TIOCEXCL);
}



void unlock_tty (
  int fdtty
)
{
  /*

    Unlock Serial Port. See `man tty_ioctl`.

  ***/


  ioctl(fdtty, TIOCNXCL);
}



void set_RTS (
  int fdtty,
  int level
)
{
  /*

    Asserts @ref MARK or @ref SPACE to the RTS line.
    fdtty   File descriptor for serial device Teletype.
    level   i.e. @ref MARK or @ref SPACE.

  ***/


  int serial;

  //get struct info
  ioctl(fdtty, TIOCMGET, &serial);

  //modify struct
  if(level == MARK)   //negative voltage on RTS
    serial &= ~TIOCM_RTS;
  else    //positive voltage on RTS
    serial |= TIOCM_RTS;

  //apply struct info
  ioctl(fdtty, TIOCMSET, &serial);
}



int get_DTR (
  int fdtty
)
{
  /*

    Helps to get the current status of the DTR line. This code had
    been picked from the Linux Programmer's Manual.

    NOTE: Please refer to `$ man tty_ioctl`, section 'modem control'.

    fdtty   File descriptor for serial device Teletype.
            Returns zero if DTR is set, otherwise 1.

  ***/


  int serial;

  ioctl(fdtty, TIOCMGET, &serial);

  if(serial & TIOCM_DTR)
    puts(MSG_START"TIOCM_DTR is not set.\r"MSG_STOP);
  else
    puts(MSG_START"TIOCM_DTR is set.\r"MSG_STOP);

  return(serial & TIOCM_DTR);
}



void headline (
  char c,
  char * pstr
)
{
  for(int n = MENUWIDTH; n > 0; n--)
    putchar(c);
  puts("\r");
  for(int n = MENUWIDTH - (strlen(pstr)); n > 0; n--)
    putchar(' ');
  printf("%s\r\n\n", pstr);
}



void greeting (
  const char * port,
  const char * rate
)
{
  //Put a greeting with some basic info on screen.

  printf(MSG_START);
  headline('~', HEADER_CONNECT);
  printf(HEADER_LICENSE);
  printf(MSG_START);
  printf("This is `connect', the project’s host utility.\r\n");
  printf("\r\n");
  printf("* Connection Specs\r\n");
  printf("\r\n");
  printf("    TTY Port:\r\t\t%s  \r\n", port);
  printf("    Baudrate:\r\t\t%s  \r\n", rate);
  printf("\r\n");
  printf("    Please disable network devices and resource demanding processes\r\n");
  printf("    in order to obtain a reliable, serial connection.\r\n");
  printf("\r\n");
  printf("* Read Chip\r\n");
  printf("\r\n");
  printf("    File in use for “chip -> file” operation:\r\t\t\t\t\t\t`%s`\r\n", fileToCatch);
  printf("    Please backup your data before initiating a chip readout!\r\n");
  printf("\r\n");
  printf("* Write to Chip\r\n");
  printf("\r\n");
  printf("    File in use for “file -> chip” operation:\r\t\t\t\t\t\t`%s`\r\n", fileToSend);
  printf("\r\n");
  printf("Waiting for the firmware, `kick' or `kick2' ...\r\n");
  printf(MSG_STOP);
}



void goodbye (
  void
)
{
  printf(MSG_START);
  headline('~', HEADER_CONNECT);
  printf("Good-Bye.\r\n");
  printf(MSG_RESET);
}



/******************************************************************//**
 * @brief Endless Loop of Stream Monitoring.
 *
 * The following streams are monitored/accessed as necessary:
 * - stdin  Standard Input
 * - fdtty  Serial Port /dev/ttyS0
 * - fdrd   File, used for file to chip transmission
 * - fdwr   File, used for chip to file transmission
 * - stdout Standard Output
 * - stderr Standard Error Output
 *
 * In general, everything that is received from fdtty is send to stdout,
 * and everything that is received at stdin is send to fdtty. But not
 * everything, there are some control chars that trigger some more
 * actions...
 *
 * @param fdtty   /dev/ttyS0 File Descriptor
 * @return        Returns status of kick after exiting.
 *
 */
char polling (
  int fdtty
)
{
  /*

    Endless Loop of Stream Monitoring.

    The following streams are monitored/accessed as necessary:
    - stdin  Standard Input
    - fdtty  Serial Port /dev/ttyS0
    - fdrd   File, used for file to chip transmission
    - fdwr   File, used for chip to file transmission
    - stdout Standard Output
    - stderr Standard Error Output

    In general, everything that is received from fdtty is send to stdout,
    and everything that is received at stdin is send to fdtty. But not
    everything, there are some control chars that trigger some more
    actions...

    fdtty   /dev/ttyS0 File Descriptor
            Returns status of kick after exiting.

  ***/


  //tty exit sequence
  enum STEP_EXIT_t {
    CHECK_A = 0,
    CHECK_B,
    EXIT_C
  } step_exit = CHECK_A;

  //SOH data
  char file_format;
  char file_command;
  enum SOH_STEP_t {
    SOH_GET_COMMAND = 0,
    SOH_GET_FORMAT
  } SOH_step;

  //send file to chip
  char a = 0;
  char b = 0;
  enum ERRCODE_t errcode;
  enum CONNECT_FILESTEP_t tx;
  int wait_feedback;
  int flag_stx;
  int fdrd = -1;    //used to switch appropriate functionality on/off

  //send chip to file
  char c = 0;
  char * pc;
  int fdwr = -1;    //used to switch appropriate functionality on/off
  char spinwheel[] = {'\\', '-', '/', '|'};       //read backwards!
  char* pspinwheel = spinwheel + sizeof(spinwheel) - 1;
  int flag_nodata;
  int dry_line = OFF;
  int line_retry;
  int retry_count;

  //protocol control
  char stx = STX;
  char etx = ETX;
  char eot = EOT;
  char ack = ACK;
  char can = CAN;
  int flag_can = OFF;
  char nak = NAK;
  char enq = ENQ;
  char clim = CLIM_HEXD;

  //stream supervision
  fd_set rdfds;
  fd_set wrfds;
  int nfds;       //maximal file descriptor, see macro MAX()
  int r;          //return value for pselect(), read(), write(), ...
  const struct timespec tspec = { TIMEOUT_SEC, TIMEOUT_NSEC };

  //explicit stream switches and flags
  int read_stdin = OFF;
  const int read_tty = ON;
  int write_stdout = OFF;
  int write_tty = OFF;
  int write_file = OFF;
  int read_SOH = OFF;
  int screen_output;

  //tty read buffer
  unsigned char buf_rdtty[SIZE_BUF_RDTTY];
  int pwr_rdtty = 0;
  int prd_rdtty = 0;

  //stream buffer and pointers, stdout
  unsigned char buf_stdout[SIZE_BUF_STDOUT];
  int pwr_stdout = 0;
  int prd_stdout = 0;

  //stream buffer and pointers, file
  unsigned char buf_file[SIZE_BUF_FILE];
  int pwr_file = 0;
  int prd_file = 0;

  //HEXD
  struct CONNECT_LINEHEXD_t linehexd = {
    OFF, OFF, '-'
  };
  //SREC
  struct CONNECT_LINESREC_t linesrec;
  //chip to file
  int iline_chip2file = SCANNER_OFF;
  //file to chip
  int iline_file2chip = SCANNER_OFF;
  int ispayload_file2chip = 0;
  int isbyte_file;  //will be initialized at STX of line

  //setup trigger
  write_tty = ON;
  pc = &ack;

  while(ON) {
    //reset pointers
    if(prd_rdtty == pwr_rdtty)
      prd_rdtty = pwr_rdtty = 0;
    if(prd_stdout == pwr_stdout)
      prd_stdout = pwr_stdout = 0;
    if(prd_file == pwr_file)
      prd_file = pwr_file = 0;

    //is the read buffer empty?
    //do we need to write?
    if(prd_rdtty == pwr_rdtty || write_stdout || write_tty || write_file) {
      //clear
      nfds = 0;
      FD_ZERO(&rdfds);
      FD_ZERO(&wrfds);

      //write to stdout?
      if(write_stdout == ON) {
        FD_SET(STDOUT_FILENO, &wrfds);
        nfds = MAX(nfds, STDOUT_FILENO);
        write_stdout = OFF;   //automatic reset
      }

      //write to file?
      if(write_file == ON) {
        FD_SET(fdwr, &wrfds);
        nfds = MAX(nfds, fdwr);
        write_file = OFF;    //automatic reset
      }

      //write to tty?
      if(write_tty == ON) {
        FD_SET(fdtty, &wrfds);
        nfds = MAX(nfds, fdtty);
        write_tty = OFF;     //automatic reset
      }

      //read from tty?
      if((read_tty == ON) || (read_SOH == ON)) {
        FD_SET(fdtty, &rdfds);
        nfds = MAX(nfds, fdtty);
      }

      //read stdin?
      if(read_stdin == ON) {
        FD_SET(STDIN_FILENO, &rdfds);
        nfds = MAX(nfds, STDIN_FILENO);
      }

      //read from file?
      if(fdrd > -1) {
        if(wait_feedback == OFF) {
          FD_SET(fdrd, &rdfds);
          nfds = MAX(nfds, fdrd);
        }
      }

      //===============================================================
      //wait for pselect()
      /*

        NOTE:
        If all chars have been read into cbuf so far, we should proceed
        reading the cbuf buffer instead of waiting for more chars on tty.
        Because no char will be present, the following pselect() will
        time out.

      ***/
      r = pselect(nfds + 1, &rdfds, &wrfds, NULL, &tspec, NULL);

      //check pselect() error
      if(r == -1)
        EXITFAIL("pselect()");

      //check pselect() timeout
      if(!r) {
        if(fdwr > -1 || fdrd > -1) {
          /*

            Transmission Error (Timeout)

            NOTE:
            This timeout occurs most likely due to a fifo buffer overrun.

            The firmware 'kick' has sent ETX which 'connect' missed to
            catch and which it keeps waiting for. In turn, 'kick'
            is waiting for an ACK or NAK now... that's the trap.

            As 'kick' is using getChar(), it cannot monitor a timeout
            like 'connect' has already done.

            In this situation, sending an ENQ to request the line once
            more gets ourselfs out of the trap.

          ***/
          if(!errcode)  //don't override a previous error
            errcode = ERRC__LINE_TIMEOUT;
          goto FAKE_ETX;
        }
        continue;
      }
      //===============================================================

      //write to stdout
      if(STDOUT_FILENO > -1) {
        if(FD_ISSET(STDOUT_FILENO, &wrfds)) {
          r = write(STDOUT_FILENO, buf_stdout + prd_stdout, pwr_stdout - prd_stdout);
          if(r < 1)
            EXITFAIL("write()");
          prd_stdout += r;
        }
      }

      //write to file
      if(fdwr > -1) {
        if(FD_ISSET(fdwr, &wrfds)) {
          r = write(fdwr, buf_file + prd_file, pwr_file - prd_file);
          if(r < 1)
            EXITFAIL("write()");
          prd_file += r;
        }
      }

      //write to tty
      if(fdtty > -1) {
        if(FD_ISSET(fdtty, &wrfds)) {
          r = write(fdtty, pc, 1);
          if(r < 1)
            EXITFAIL("write()");
        }
      }

      //read from stdin: any keystroke?
      if(STDIN_FILENO > -1) {
        if(FD_ISSET(STDIN_FILENO, &rdfds)) {
          r = read(STDIN_FILENO, &c, 1);
          if(r < 1) {
            EXITFAIL("read()");
          }
          else {
            if(read_stdin) {
              //file to chip
              if(fdrd > -1) {
                switch(c) {
                  case 'q':
                    flag_can = ON;
                    break;
                }
              }
              //chip to file
              else if(fdwr > -1) {
                switch(c) {
                  case 'q':
                    flag_can = ON;
                    break;
                }
              }
              //idle
              else {
                write_tty = ON;
                continue;   //skip the rest
              }
            }
          }
        }
      }

      //read from tty: any char from kick?
      if(fdtty > -1) {
        if(FD_ISSET(fdtty, &rdfds)) {
          r = read(fdtty, buf_rdtty + pwr_rdtty, SIZE_BUF_RDTTY - pwr_rdtty);

          if(r == -1 && errno == EINTR)
            continue;

          if(r == -1)
            EXITFAIL("read() into buf_rdtty");

          /*

            NOTE: This is essential, otherwise we will exit
            if SIZE_BUF_RDTTY is bigger than 1 but still small

          ***/
          if(r == 0)
            continue;

          pwr_rdtty += r;
        }
      }

      //read from file: any file data to be processed?
      if(fdrd > -1) {
        if(FD_ISSET(fdrd, &rdfds)) {
          switch(tx) {
            case START_FILE_READOUT:
              a = 0;
              flag_stx = ON;

            case BYTE_PREVIEW:
              b = a;
              r = read(fdrd, &a, 1);
              if(r == -1) {
                EXITFAIL ("read()");
              }
              else if(!r) {
                a = EM;
                if(b == EM) {
                  tx = SEND_EOT;
                  continue;
                }
              }

            case CHECK_STX:
              if(flag_stx == ON) {
                flag_stx = OFF;
                errcode = ERRC__SUCCESS;  //reset for this line
                pc = &stx;
                write_tty = ON;
                tx = CHECK_CRNL;
                wait_feedback = ON;
                if(file_format == LINETYPE_SREC) {
                  iline_file2chip = SCANNER_INI;
                  isbyte_file = 0;  //b is zero, a is pointing to 'S'
                }
                continue;
              }

            case CHECK_CRNL:
              if(b == CARR_RET) {
                if(a != NEW_LINE) {
                  if(file_format == LINETYPE_HEXD)
                    tx = SEND_CLIM;
                  else
                    tx = SEND_ETX_OR_CAN;
                }
                else
                  tx = BYTE_PREVIEW;
              }
              else if(b == NEW_LINE) {
                if(file_format == LINETYPE_HEXD)
                  tx = SEND_CLIM;
                else
                  tx = SEND_ETX_OR_CAN;
              }
              else
                tx = SEND_BYTE;
              continue;

            case SEND_BYTE:
              tx = BYTE_PREVIEW;
              if(b) {
                if((ispayload_file2chip = lineinfo(file_format, &iline_file2chip, b, NULL, &linesrec, PAYLOAD_HEX))) {
                  if((isbyte_file ^= 1)) {
                    b = (hexdigit2bin(b) << 4) | hexdigit2bin(a);
                    pc = &b;
                    write_tty = ON;
                    wait_feedback = ON;
                  }
                }
                else {
                  pc = &b;
                  write_tty = ON;
                  wait_feedback = ON;
                }
              }
              continue;

            case SEND_CLIM:
              pc = &clim;
              write_tty = ON;
              wait_feedback = ON;
              tx = SEND_ETX_OR_CAN;
              continue;

            case SEND_ETX_OR_CAN:
              if(flag_can) {
                flag_can = OFF;
                pc = &can;
              }
              else
                pc = &etx;
              write_tty = ON;
              tx = WAIT_CHAR;
              iline_file2chip = SCANNER_OFF;
              continue;

            case WAIT_CHAR:
            case RECEIVE_CAN:
            case RECEIVE_NAK:
              continue;

            case RECEIVE_ACK:
              flag_stx = ON;
              tx = BYTE_PREVIEW;
              continue;

            case SEND_EOT:  //do we need wait_feedback = ON here?
              pc = &eot;
              write_tty = ON;
              tx = FLUSH_BUFFERS;
              continue;

            case FLUSH_BUFFERS:
              tx = CLOSE_TX;
              if(errcode != ERRC__SUCCESS) {
                WRITE_BUF_STDOUT('\n');
                write_stdout = screen_output;  //will be served first, thus flushing the buffer
                WRITE_BUF_FILE('\n');
                write_file = ON;    //will be served second
              }
              continue;

            case CLOSE_TX:
              close(fdrd);
              fdrd = -1;
              if(!screen_output)
                printf(" \r");  //clear remaining character of spinwheel
              errout(errcode, 0);
              //reset buffer pointers
              //TODO: why is that necessary?
              prd_stdout = pwr_stdout = 0;
              prd_file = pwr_file = 0;
              continue;
          };
        }
      }
    }
    //is data in tty buffer?
    else if(prd_rdtty < pwr_rdtty) {
      //read from buf_rdtty
      c = buf_rdtty[prd_rdtty++];

      //catch error status
      if(tx == RECEIVE_NAK) {
        errcode = c;
        tx = SEND_EOT;
      }
      else {
        /*

          scan for propeller-load exit sequence
          don't scan when receiving random data
          don't scan when receiving SREC’s binary data

        ***/
        if(!dry_line && \
            (iline_chip2file < SCANNER_INI && iline_file2chip < SCANNER_INI) \
          ) {
          switch(step_exit) {
            case CHECK_A:
              if((unsigned char)c == EXIT_CHAR_A) {
                step_exit++;
                continue;
              }
              break;

            case CHECK_B:
              if(c == EXIT_CHAR_B) {
                step_exit++;
                continue;
              }
              break;

            case EXIT_C:
              return(c);
          }
          step_exit = CHECK_A;    //reset
        }
        //file to chip (data and control chars)
        if(fdrd > -1) {
          if(c == *pc)
            wait_feedback = OFF;

          //binary data transmission (payload only, no control chars)
          if(ispayload_file2chip) {
            if(screen_output == ON) {
              char a, b;
              a = bin2hexdigit((*pc >> 4) & 0x0f, CASE_MODE);
              b = bin2hexdigit(*pc & 0x0f, CASE_MODE);
              WRITE_BUF_STDOUT(a);
              WRITE_BUF_STDOUT(b);
            }
          }
          else {    //hexadecimal data transmission (control chars as well)
            switch(c) {
              case STX:
              case ENQ:
              case SOH:
              case EOT:
              case ETX:
                break;

              case ACK:
                if(tx == WAIT_CHAR) {
                  tx = RECEIVE_ACK;
                  if(!screen_output) {    //provide a spinning wheel if no output on screen available
                    WRITE_BUF_STDOUT(*pspinwheel);
                    if(pspinwheel == spinwheel)
                      pspinwheel = spinwheel + sizeof(spinwheel) - 1;
                    else
                      pspinwheel--;
                    WRITE_BUF_STDOUT('\b');
                    write_stdout = ON;
                  }
                }
                break;

              case NAK:
                if(tx == WAIT_CHAR)
                  tx = RECEIVE_NAK;
                break;

              case CAN:
                if(tx == WAIT_CHAR)
                  tx = RECEIVE_CAN;
                break;

              default:
                switch(file_format) {
                  case LINETYPE_SREC:
                    if(iline_file2chip == SCAN_S) {
                      if(c != 'S')
                        errcode = ERRC__NO_SREC;
                    }
                    else {
                      if(!isxdigit(c) && c != CLIM_CR && c != CLIM_NL)
                        errcode = ERRC__WRONG_CHARACTER;
                    }
                    break;

                  default:
                    if(!isprint(c) && c != CLIM_CR && c != CLIM_NL)
                      errcode = ERRC__WRONG_CHARACTER;
                    break;
                }

                if(errcode) {
                  iline_file2chip = SCANNER_OFF;   //switch line scanner off
                  break;
                }

                if(screen_output == ON) {
                  WRITE_BUF_STDOUT(c);
                  write_stdout = read_stdin;
                  if(c == CLIM_NL)
                    write_stdout = ON;
                }
            }
          }
          /*

            Now, wait_feedback should be zero.
            If not, proceed with wrong character and create timeout error.

          ***/
        }
        //chip to file (data and control chars)
        else if(fdwr > -1) {
          //binary data transmission (payload only, no control chars)
          if(lineinfo(file_format, &iline_chip2file, c, &linehexd, &linesrec, PAYLOAD_BIN)) {
            unsigned char a, b;
            a = bin2hexdigit((c >> 4) & 0x0f, CASE_MODE);
            b = bin2hexdigit(c & 0x0f, CASE_MODE);
            if(screen_output == ON) {
              WRITE_BUF_STDOUT(a);
              WRITE_BUF_STDOUT(b);
            }
            WRITE_BUF_FILE(a);
            WRITE_BUF_FILE(b);
          }
          //hexadecimal data transmission (control chars as well)
          else {
            if(!dry_line) {   //don't scan for control characters when receiving random data
              switch(c) {
                case STX:
                  if(iline_chip2file == SCANNER_OFF) {
                    flag_nodata = 1;  //will be cleared with first valid data character
                    /*

                      switch lineinfo() scanner on,
                      will be switched off in case of ETX,
                      will be switched off in case of timeout error

                    ***/
                    iline_chip2file = SCANNER_INI;
                    break;
                  }

                case EOT:
                  if(iline_chip2file == SCANNER_OFF) {
                    close(fdwr);
                    fdwr = -1;
                    write_file = OFF;
                    if(!errcode && (!screen_output || flag_nodata))
                      printf(" \r");  //clear remaining character of spinwheel
                    errout(errcode, retry_count);
                    break;
                  }

                case ETX:
                  if(flag_can) {
                    flag_can = OFF;
                    errcode = ERRC__JOB_CANCELLATION;
                  }
                  else if(!errcode && !flag_nodata) {
                    switch(file_format) {
                      case LINETYPE_SREC:
                        if(linesrec.checksum != linesrec.chksum_rx)
                          errcode = ERRC__CHECKSUM_MISMATCH;
                        else if(iline_chip2file != linesrec.size - 1)
                          errcode = ERRC__LINE_LENGTH_MISMATCH;
                        break;
                      case LINETYPE_HEXD:
                        if(!linehexd.is_info && (iline_chip2file) != HEXD_LINELEN)
                          errcode = ERRC__LINE_LENGTH_MISMATCH;
                        break;
                    }
                  }
FAKE_ETX:
                  if(errcode <= ERRC__JOB_CANCELLATION && !flag_nodata) {
                    //start Write CLIM_NL
                    c = CLIM_NL;    //temporary use of c
                    WRITE_BUF_FILE(c);
                    if(c == CLIM_NL)
                      write_file = ON;
                    //end Write CLIM_NL
                  }
                  iline_chip2file = SCANNER_OFF;   //switch line scanner off
                  switch(errcode) {
                    case ERRC__SUCCESS:
                      pc = &ack;
                      line_retry = LINE_RETRY;
                      if(!screen_output || flag_nodata) {    //provide a spinning wheel if no screen output
                        WRITE_BUF_STDOUT__MSG_START;
                        WRITE_BUF_STDOUT(*pspinwheel);
                        if(pspinwheel == spinwheel)
                          pspinwheel = spinwheel + sizeof(spinwheel) - 1;
                        else
                          pspinwheel--;
                        WRITE_BUF_STDOUT('\b');
                        WRITE_BUF_STDOUT__MSG_STOP;
                        write_stdout = ON;
                      }
                      break;

                    case ERRC__JOB_CANCELLATION:
                      pc = &can;
                      break;

                    case ERRC__CHECKSUM_MISMATCH:
                    case ERRC__LINE_LENGTH_MISMATCH:
                    case ERRC__WRONG_CHARACTER:
                    case ERRC__LINE_TIMEOUT:
                      prd_stdout = pwr_stdout = 0;
                      prd_file = pwr_file = 0;
                      prd_rdtty = pwr_rdtty = 0;
                      dry_line = OFF;
                      errout(errcode, line_retry);
# ifdef ERROR_MSG_READOUT
                      usleep(WAIT_AFTER_ERROR);
# endif
                      if(line_retry-- == 0)
                        pc = &nak;
                      else {
                        pc = &enq;
                        errcode = ERRC__SUCCESS;    //reset
                        retry_count++;              //count total number
                      }
                      break;

                    default:  //any other error?
                      pc = &nak;
                      break;
                  }
                  write_tty = ON;
                  break;

                default:
                  //check charset
                  switch(file_format) {
                    case LINETYPE_SREC:
                      if((c != 'S')
                          && !isxdigit(c)
                            && (c != CLIM_CR)
                              && (c != CLIM_NL))
                        errcode = ERRC__WRONG_CHARACTER;
                      break;

                    case LINETYPE_HEXD:
                      if(linehexd.is_print) {
                        if(!isprint(c) && (c != CLIM_CR) && (c != CLIM_NL))
                          errcode = ERRC__WRONG_CHARACTER;
                      }
                      else {
                        if(((c != '#') && (c != ':') && (c != ' ') && !isxdigit(c)))
                          errcode = ERRC__WRONG_CHARACTER;
                      }
                      break;
                  }

                  if(errcode) {
                    iline_chip2file = SCANNER_OFF;   //switch line scanner off
                    dry_line = ON;
                    break;
                  }

                  if(screen_output == ON) {
                    WRITE_BUF_STDOUT(c);
                    write_stdout = read_stdin;
                    if(c == CLIM_NL)
                      write_stdout = ON;
                  }


                  /*

                    - Discard CLIM_NL here, write after ETX instead!

                        This allows us to overwrite a broken line in case
                        of line length mismatch.

                    - Discard CLIM_CR here

                        Obtain UNIX style line endings.

                  ***/
                  if((c != CLIM_NL) && (c != CLIM_CR)) {
                    WRITE_BUF_FILE(c);
                    //'~ if(c == CLIM_NL)
                      //'~ write_file = ON;
                  }

                  flag_nodata = 0;
                  break;
              }
            }
          }
        }
        //control commands: menu is active
        else {
          //SOH data
          if(read_SOH == ON) {
            switch(SOH_step) {
              case SOH_GET_COMMAND:
                file_command = c;
                SOH_step = SOH_GET_FORMAT;
                break;

              case SOH_GET_FORMAT:
                file_format = c;
                read_SOH = OFF;
                errcode = ERRC__SUCCESS;   //reset
                flag_can = OFF;   //reset
                screen_output = ON;
                switch(file_command) {
                  case FILE_TO_CHIP_NOSCREEN:
                    screen_output = OFF;

                  case FILE_TO_CHIP:
                    //initiate transmission, stream will go active
                    fdrd = open(fileToSend, O_RDONLY);
                    if(fdrd == -1) {
                      //issue error message
                      errcode = ERRC__NO_SUCH_FILE;
                      errout(errcode, 0);
                      //send EOT
                      pc = &eot;
                      write_tty = ON;
                      continue;
                    }
                    else {
                      tx = START_FILE_READOUT;
                      wait_feedback = OFF;
                      printf(MSG_START);
                      headline('~', HEADER_CONNECT);
                      printf("reading %s, sending data ...\r\n", fileToSend);
                      if(screen_output)   //remove host color setting if spinning wheel is not present
                        printf(MSG_STOP);
                    }
                    break;

                  case CHIP_TO_FILE_NOSCREEN:
                    screen_output = OFF;

                  case CHIP_TO_FILE:
                    line_retry = LINE_RETRY;
                    retry_count = 0;
                    fdwr = open(
                      fileToCatch,
                      O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH
                      );
                    if(fdwr == -1) {
                       EXITFAIL("open()");
                      /*

                        FIXME: This error handling is very basic.
                        It will leave the chipflasher board with its SPI-port active!
                        It would be better if kick would now expect an ACK or NAK before it proceeds.

                      ***/
                    }
                    printf(MSG_START);
                    headline('~', HEADER_CONNECT);
                    printf("receiving data, writing %s ...\r\n", fileToCatch);
                    if(screen_output)   //remove host color setting if spinning wheel is not present
                      printf(MSG_STOP);
                    break;
                }
                break;
            }
          }
          //normal menu
          else {
            switch(c) {
              case ACK:
              case NAK:
              case EOT:
              case CAN:
                continue;

              case STX:
                continue;

              case ETX:
                continue;

              case INPUT_START:
                read_stdin = ON;
                continue;

              case INPUT_STOP:
                read_stdin = OFF;
                continue;

              case ENQ:
                pc = &ack;
                write_tty = ON;
                continue;

              case SOH:   //start of heading for tx, rx
                SOH_step = SOH_GET_COMMAND;
                read_SOH = ON;
                continue;

              default:  //all other chars
                pc = &c;
                if(isprint(c) || isspace(c)) {
                  WRITE_BUF_STDOUT(c);
                  write_stdout = ON;
                }
                continue;
            }
          }
        }
      }
    }
  }

  //this line should never be reached
  EXITFAIL("polling()");
}



void restore_stdout (
  void
)
{
  tcsetattr(STDOUT_FILENO, TCSANOW, &old_stdio);
  tcsetattr(STDOUT_FILENO, TCSAFLUSH, &old_stdio);
}



int connect (
  char * dev,
  const speed_t baudrate,
  unsigned int bps
)
{
  int fdtty;
  struct termios tio;
  struct termios stdio;

  //initialize memory
  memset(&stdio, 0, sizeof(stdio));
  memset(&tio, 0, sizeof(tio));

  //save current stdout terminal settings and restore later
  tcgetattr(STDOUT_FILENO, &old_stdio);

  //set exit function
  {
    int r;
    r = atexit(restore_stdout);
    if(r != 0) {
       fprintf(stderr, MSG_START"cannot set exit function"MSG_STOP"\n");
       exit(EXIT_FAILURE);
    }
  }

  //configure ttyS0
  tio.c_iflag = 0;                  //specify input modes
  tio.c_oflag = 0;                  //specify output modes
  tio.c_cflag = CS8|CREAD|CLOCAL;   //flag constants (8n1, see termios.h for more information)
  tio.c_lflag = 0;                  //local modes
  tio.c_cc[VMIN] = 0;               //minimum of input chars
  tio.c_cc[VTIME] = 0;              //input timeout in deciseconds

  /*
   * FIXME:
   * 1) Open() seems to initialize DTR and RTS to SPACE thus triggering
   *    a reset even if we don't want to. See Notes below.
   * 2) Open() seems to corrupt an existing locked connection.
   */
  if((fdtty = open(dev, O_RDWR|O_NONBLOCK)) != -1) {    //open port
    //'~ lock_tty(fdtty);                          //if locked, tty cannot be reset
    cfsetospeed(&tio, baudrate);              //specify output speed (propeller default)
    cfsetispeed(&tio, baudrate);              //specify input speed (propeller default)
    tcsetattr(fdtty, TCSANOW, &tio);          //apply changes now

    /* Notes:
     *
     * The DTR signal is generated by your workstation and tells the computer or device on the
     * other end that you are ready (a space voltage) or not-ready (a mark voltage). DTR is
     * usually enabled automatically whenever you open the serial interface on the workstation.
     *
     * The RTS signal is set to the space voltage by your workstation to indicate that more data
     * is ready to be sent. Like CTS, RTS helps to regulate the flow of data between your
     * workstation and the computer or device on the other end of the serial cable. Most
     * workstations leave this signal set to the space voltage all the time.
     *
     * Source: https://www.cmrr.umn.edu/~strupp/serial.html#2_5_2
     */

    //configure stdout terminal
    stdio.c_iflag = 0;
    stdio.c_oflag = 0;
    stdio.c_cflag = 0;
    stdio.c_lflag = 0;
    stdio.c_cc[VMIN] = 1;
    stdio.c_cc[VTIME] = 0;
    tcsetattr(STDOUT_FILENO, TCSANOW, &stdio);
    tcsetattr(STDOUT_FILENO, TCSAFLUSH, &stdio);
    fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK);  //make the reads non-blocking

    //disable board reset and trigger the propeller
    usleep(RESET_DURATION);
    set_DTR(fdtty, MARK);
    set_RTS(fdtty, MARK);

    //call polling
    polling(fdtty);

    //close '/dev/ttyS0'
    unlock_tty(fdtty);
    close(fdtty);

    //return status
    return EXIT_SUCCESS;
  }
  //return status
  return EXIT_FAILURE;
}



int main (
  int argc,
  char ** argv
)
{
  /*

    This is main() of `connect`, the host utility.

    The program `connect` has dedicated features, in contrast to a
    standard terminal, set up with `propeller-load`.

    argc    Number of arguments.
    **argv  Pointer to argument buffer.

    Returns status as reported by the Propeller board.
    Remember, the three byte exit sequence used by the Propeller is:
    EXIT_CHAR_A, EXIT_CHAR_B, status.

  ***/


  char *port[] = {
    "/dev/ttyS0",
    "/dev/ttyS1",
    "/dev/ttyS2",
    "/dev/ttyS3",
    "/dev/ttyUSB0",
    "/dev/ttyUSB1"
  };

  struct tag_rate {
    const speed_t macro;
    const char * s;
    const unsigned int bps;   //should be 4 bytes
  } rate[] = {
    { B921600, "B921600", 921600 },
    { B576000, "B576000", 576000 },
    { B500000, "B500000", 500000 },
    { B460800, "B460800", 460800 },
    { B230400, "B230400", 230400 },
    { B115200, "B115200", 115200 },
    { B57600,  "B57600",   57600 },
    { B38400,  "B38400",   38400 }
  };

  int status;
  struct tag_rate * pRate;
  char * pPort;

  //set defaults
  pPort = port[PORT_DEFAULT];
  pRate = &(rate[RATE_DEFAULT]);

  switch(argc) {
    case 5:
      {
        int n;

        //filenames
        fileToCatch = argv[1];
        fileToSend = argv[2];

        //port
        n = SIZE_PORT;
        while(n--) {
          if(!strcmp(argv[3], port[n])) {
            pPort = port[n];
            break;
          }
        }

        //baudrate
        n = SIZE_RATE;
        while(n--) {
          if(!strcmp(argv[4], rate[n].s)) {
            pRate = &(rate[n]);
            break;
          }
        }
      }

    case 1:
      //standard action
      greeting(pPort, pRate->s);
      status = connect(pPort, pRate->macro, pRate->bps);
      if(status != EXIT_SUCCESS) {
        restore_stdout();
        errout(ERRC__PORT_OPEN_FAILURE, 0);
      }
      goodbye();
      return status;

    default:
      //usage
      printf( "%s:\r\n", argv[0]);
      printf( "   version:\r\t\t" VERSION "\r\n");
      printf( "   usage:\r\t\t%s [chip2file file2chip port baudrate]\r\n\n", argv[0]);
      printf( "   chip2file:" \
              "\r\t\tA text file that will be used to store chip readouts\n" \
              "\r\t\tas lines of Motorola S-Record or Hex-Dump.\n" \
              "\r\t\tDefaults to '" FILE_TO_CATCH "'.\r\n");
      printf( "   file2chip:" \
              "\r\t\tA text file that contains data to be flashed as lines\n" \
              "\r\t\tof Hex-Dump or Motorola S-Record.\n" \
              "\r\t\tDefaults to '" FILE_TO_SEND "'.\r\n");
      printf( "   port:" \
              "\r\t\t/dev/ttyS0 (default) | /dev/ttyS1 | /dev/ttyS2 | /dev/ttyS3 |\n" \
              "\r\t\t/dev/ttyUSB0 | /dev/ttyUSB1\r\n");
      printf( "   baudrate:" \
              "\r\t\tB38400 | B57600 | B115200 (default) | B230400 | ... | B921600 \r\n");
      return 0; //should we flag an error?
  }
}
