text: // Copyright 2015 by Thorsten von Eicken, see LICENSE.txt
  
  #include "esp8266.h"
  
  #include "uart.h"
  #include "crc16.h"
  #include "serbridge.h"
  #include "serled.h"
  #include "config.h"
  #include "console.h"
  #include "slip.h"
  #include "cmd.h"
  #ifdef SYSLOG
  #include "syslog.h"
  #else
  #define syslog(X1...)
  #endif
  
  #define SKIP_AT_RESET
  
  static struct espconn serbridgeConn1; // plain bridging port
  static struct espconn serbridgeConn2; // programming port
  static esp_tcp serbridgeTcp1, serbridgeTcp2;
  static int8_t mcu_reset_pin, mcu_isp_pin;
  
  extern uint8_t slip_disabled;   // disable slip to allow flashing of attached MCU
  
  void (*programmingCB)(char *buffer, short length) = NULL;
  
  // Connection pool
  serbridgeConnData connData[MAX_CONN];
  
  //===== TCP -> UART
  
  // Telnet protocol characters
  #define IAC        255  // escape
  #define WILL       251  // negotiation
  #define SB         250  // subnegotiation begin
  #define SE         240  // subnegotiation end
  #define ComPortOpt  44  // COM port options
  #define SetControl   5  // Set control lines
  #define DTR_ON       8  // used here to reset microcontroller
  #define DTR_OFF      9
  #define RTS_ON      11  // used here to signal ISP (in-system-programming) to uC
  #define RTS_OFF     12
  
  // telnet state machine states
  enum { TN_normal, TN_iac, TN_will, TN_start, TN_end, TN_comPort, TN_setControl };
  
  // process a buffer-full on a telnet connection and return the ending telnet state
  static uint8_t ICACHE_FLASH_ATTR
  telnetUnwrap(uint8_t *inBuf, int len, uint8_t state)
  {
    for (int i=0; i<len; i++) {
      uint8_t c = inBuf[i];
      switch (state) {
      default:
      case TN_normal:
        if (c == IAC) state = TN_iac; // escape char: see what's next
        else uart0_write_char(c);     // regular char
        break;
      case TN_iac:
        switch (c) {
        case IAC:                     // second escape -> write one to outbuf and go normal again
          state = TN_normal;
          uart0_write_char(c);
          break;
        case WILL:                    // negotiation
          state = TN_will;
          break;
        case SB:                      // command sequence begin
          state = TN_start;
          break;
        case SE:                      // command sequence end
          state = TN_normal;
          break;
        default:                      // not sure... let's ignore
          uart0_write_char(IAC);
          uart0_write_char(c);
        }
        break;
      case TN_will:
        state = TN_normal;            // yes, we do COM port options, let's go back to normal
        break;
      case TN_start:                  // in command seq, now comes the type of cmd
        if (c == ComPortOpt) state = TN_comPort;
        else state = TN_end;          // an option we don't know, skip 'til the end seq
        break;
      case TN_end:                    // wait for end seq
        if (c == IAC) state = TN_iac; // simple wait to accept end or next escape seq
        break;
      case TN_comPort:
        if (c == SetControl) state = TN_setControl;
        else state = TN_end;
        break;
      case TN_setControl:             // switch control line and delay a tad
        switch (c) {
        case DTR_ON:
          if (mcu_reset_pin >= 0) {
  #ifdef SERBR_DBG
            os_printf("MCU reset gpio%d\n", mcu_reset_pin);
  #endif
            GPIO_OUTPUT_SET(mcu_reset_pin, 0);
            os_delay_us(100L);
          }
  #ifdef SERBR_DBG
          else { os_printf("MCU reset: no pin\n"); }
  #endif
          break;
        case DTR_OFF:
          if (mcu_reset_pin >= 0) {
            GPIO_OUTPUT_SET(mcu_reset_pin, 1);
            os_delay_us(100L);
          }
          break;
        case RTS_ON:
          if (mcu_isp_pin >= 0) {
  #ifdef SERBR_DBG
            os_printf("MCU ISP gpio%d\n", mcu_isp_pin);
  #endif
            GPIO_OUTPUT_SET(mcu_isp_pin, 0);
            os_delay_us(100L);
          }
  #ifdef SERBR_DBG
          else { os_printf("MCU isp: no pin\n"); }
  #endif
          slip_disabled++;
          break;
        case RTS_OFF:
          if (mcu_isp_pin >= 0) {
            GPIO_OUTPUT_SET(mcu_isp_pin, 1);
            os_delay_us(100L);
          }
          if (slip_disabled > 0) slip_disabled--;
          break;
        }
        state = TN_end;
        break;
      }
    }
    return state;
  }
  
  // Generate a reset pulse for the attached microcontroller
  void ICACHE_FLASH_ATTR
  serbridgeReset()
  {
    if (mcu_reset_pin >= 0) {
  #ifdef SERBR_DBG
      os_printf("MCU reset gpio%d\n", mcu_reset_pin);
  #endif
      GPIO_OUTPUT_SET(mcu_reset_pin, 0);
      os_delay_us(2000L); // esp8266 needs at least 1ms reset pulse, it seems...
      GPIO_OUTPUT_SET(mcu_reset_pin, 1);
    }
  #ifdef SERBR_DBG
    else { os_printf("MCU reset: no pin\n"); }
  #endif
  }
  
  // Receive callback
  static void ICACHE_FLASH_ATTR
  serbridgeRecvCb(void *arg, char *data, unsigned short len)
  {
    serbridgeConnData *conn = ((struct espconn*)arg)->reverse;
    //os_printf("Receive callback on conn %p\n", conn);
    if (conn == NULL) return;
  
    bool startPGM = false;
  
    // at the start of a connection we're in cmInit mode and we wait for the first few characters
    // to arrive in order to decide what type of connection this is.. The following if statements
    // do this dispatch. An issue here is that we assume that the first few characters all arrive
    // in the same TCP packet, which is true if the sender is a program, but not necessarily
    // if the sender is a person typing (although in that case the line-oriented TTY input seems
    // to make it work too). If this becomes a problem we need to buffer the first few chars...
    if (conn->conn_mode == cmInit) {
  
      // If the connection starts with the Arduino or ARM reset sequence we perform a RESET
      if ((len == 2 && strncmp(data, "0 ", 2) == 0) ||
          (len == 2 && strncmp(data, "?\n", 2) == 0) ||
          (len == 3 && strncmp(data, "?\r\n", 3) == 0)) {
        startPGM = true;
        conn->conn_mode = cmPGM;
  
      // If the connection starts with a telnet negotiation we will do telnet
      }
      else if (len >= 3 && strncmp(data, (char[]){IAC, WILL, ComPortOpt}, 3) == 0) {
        conn->conn_mode = cmTelnet;
        conn->telnet_state = TN_normal;
        // note that the three negotiation chars will be gobbled-up by telnetUnwrap
  #ifdef SERBR_DBG
        os_printf("telnet mode\n");
  #endif
  
      // looks like a plain-vanilla connection!
      }
      else {
        conn->conn_mode = cmTransparent;
      }
  
    // if we start out in cmPGM mode due to a connection to the second port we need to do the
    // reset dance right away
    } else if (conn->conn_mode == cmPGMInit) {
      conn->conn_mode = cmPGM;
      startPGM = true;
    }
  
    // do the programming reset dance
    if (startPGM) {
  #ifdef SERBR_DBG
      os_printf("MCU Reset=gpio%d ISP=gpio%d\n", mcu_reset_pin, mcu_isp_pin);
      os_delay_us(2*1000L); // time for os_printf to happen
  #endif
      // send reset to arduino/ARM, send "ISP" signal for the duration of the programming
      if (mcu_reset_pin >= 0) GPIO_OUTPUT_SET(mcu_reset_pin, 0);
      os_delay_us(100L);
      if (mcu_isp_pin >= 0) GPIO_OUTPUT_SET(mcu_isp_pin, 0);
      os_delay_us(2000L);
      if (mcu_reset_pin >= 0) GPIO_OUTPUT_SET(mcu_reset_pin, 1);
      //os_delay_us(100L);
      //if (mcu_isp_pin >= 0) GPIO_OUTPUT_SET(mcu_isp_pin, 1);
      os_delay_us(1000L); // wait a millisecond before writing to the UART below
      conn->conn_mode = cmPGM;
      slip_disabled++; // disable SLIP so it doesn't interfere with flashing
  #ifdef SKIP_AT_RESET
      serledFlash(50); // short blink on serial LED
      return;
  #endif
    }
  
  
    // write the buffer to the uart
    if (conn->conn_mode == cmTelnet) {
      conn->telnet_state = telnetUnwrap((uint8_t *)data, len, conn->telnet_state);
    } else {
      uart0_tx_buffer(data, len);
    }
  
    serledFlash(50); // short blink on serial LED
  }
  
  //===== UART -> TCP
  
  // Send all data in conn->txbuffer
  // returns result from espconn_sent if data in buffer or ESPCONN_OK (0)
  // Use only internally from espbuffsend and serbridgeSentCb
  static sint8 ICACHE_FLASH_ATTR
  sendtxbuffer(serbridgeConnData *conn)
  {
    sint8 result = ESPCONN_OK;
    if (conn->txbufferlen != 0) {
      //os_printf("TX %p %d\n", conn, conn->txbufferlen);
      conn->readytosend = false;
      result = espconn_sent(conn->conn, (uint8_t*)conn->txbuffer, conn->txbufferlen);
      conn->txbufferlen = 0;
      if (result != ESPCONN_OK) {
        os_printf("sendtxbuffer: espconn_sent error %d on conn %p\n", result, conn);
        conn->txbufferlen = 0;
        if (!conn->txoverflow_at) conn->txoverflow_at = system_get_time();
      } else {
        conn->sentbuffer = conn->txbuffer;
        conn->txbuffer = NULL;
        conn->txbufferlen = 0;
      }
    }
    return result;
  }
  
  // espbuffsend adds data to the send buffer. If the previous send was completed it calls
  // sendtxbuffer and espconn_sent.
  // Returns ESPCONN_OK (0) for success, -128 if buffer is full or error from  espconn_sent
  // Use espbuffsend instead of espconn_sent as it solves the problem that espconn_sent must
  // only be called *after* receiving an espconn_sent_callback for the previous packet.
  static sint8 ICACHE_FLASH_ATTR
  espbuffsend(serbridgeConnData *conn, const char *data, uint16 len)
  {
    if (conn->txbufferlen >= MAX_TXBUFFER) goto overflow;
  
    // make sure we indeed have a buffer
    if (conn->txbuffer == NULL) conn->txbuffer = os_zalloc(MAX_TXBUFFER);
    if (conn->txbuffer == NULL) {
      os_printf("espbuffsend: cannot alloc tx buffer\n");
      return -128;
    }
  
    // add to send buffer
    uint16_t avail = conn->txbufferlen+len > MAX_TXBUFFER ? MAX_TXBUFFER-conn->txbufferlen : len;
    os_memcpy(conn->txbuffer + conn->txbufferlen, data, avail);
    conn->txbufferlen += avail;
  
    // try to send
    sint8 result = ESPCONN_OK;
    if (conn->readytosend) result = sendtxbuffer(conn);
  
    if (avail < len) {
      // some data didn't fit into the buffer
      if (conn->txbufferlen == 0) {
        // we sent the prior buffer, so try again
        return espbuffsend(conn, data+avail, len-avail);
      }
      goto overflow;
    }
    return result;
  
  overflow:
    if (conn->txoverflow_at) {
      // we've already been overflowing
      if (system_get_time() - conn->txoverflow_at > 10*1000*1000) {
        // no progress in 10 seconds, kill the connection
        os_printf("serbridge: killing overlowing stuck conn %p\n", conn);
        espconn_disconnect(conn->conn);
      }
      // else be silent, we already printed an error
    } else {
      // print 1-time message and take timestamp
      os_printf("serbridge: txbuffer full, conn %p\n", conn);
      conn->txoverflow_at = system_get_time();
    }
    return -128;
  }
  
  //callback after the data are sent
  static void ICACHE_FLASH_ATTR
  serbridgeSentCb(void *arg)
  {
    serbridgeConnData *conn = ((struct espconn*)arg)->reverse;
    //os_printf("Sent CB %p\n", conn);
    if (conn == NULL) return;
    //os_printf("%d ST\n", system_get_time());
    if (conn->sentbuffer != NULL) os_free(conn->sentbuffer);
    conn->sentbuffer = NULL;
    conn->readytosend = true;
    conn->txoverflow_at = 0;
    sendtxbuffer(conn); // send possible new data in txbuffer
  }
  
  void ICACHE_FLASH_ATTR
  console_process(char *buf, short len)
  {
    // push buffer into web-console
    for (short i=0; i<len; i++)
      console_write_char(buf[i]);
    // push the buffer into each open connection
    for (short i=0; i<MAX_CONN; i++) {
      if (connData[i].conn) {
        espbuffsend(&connData[i], buf, len);
      }
    }
  }
  
  // callback with a buffer of characters that have arrived on the uart
  void ICACHE_FLASH_ATTR
  serbridgeUartCb(char *buf, short length)
  {
    if (programmingCB) {
      programmingCB(buf, length);
    } else if (!flashConfig.slip_enable || slip_disabled > 0) {
      //os_printf("SLIP: disabled got %d\n", length);
      console_process(buf, length);
    } else {
      slip_parse_buf(buf, length);
    }
  
    serledFlash(50); // short blink on serial LED
  }
  
  //===== Connect / disconnect
  
  // Disconnection callback
  static void ICACHE_FLASH_ATTR
  serbridgeDisconCb(void *arg)
  {
    serbridgeConnData *conn = ((struct espconn*)arg)->reverse;
    if (conn == NULL) return;
    // Free buffers
    if (conn->sentbuffer != NULL) os_free(conn->sentbuffer);
    conn->sentbuffer = NULL;
    if (conn->txbuffer != NULL) os_free(conn->txbuffer);
    conn->txbuffer = NULL;
    conn->txbufferlen = 0;
    // Send reset to attached uC if it was in programming mode
    if (conn->conn_mode == cmPGM && mcu_reset_pin >= 0) {
      if (mcu_isp_pin >= 0) GPIO_OUTPUT_SET(mcu_isp_pin, 1);
      os_delay_us(100L);
      GPIO_OUTPUT_SET(mcu_reset_pin, 0);
      os_delay_us(100L);
      GPIO_OUTPUT_SET(mcu_reset_pin, 1);
    }
    conn->conn = NULL;
  }
  
  // Connection reset callback (note that there will be no DisconCb)
  static void ICACHE_FLASH_ATTR
  serbridgeResetCb(void *arg, sint8 err)
  {
    os_printf("serbridge: connection reset err=%d\n", err);
    serbridgeDisconCb(arg);
  }
  
  // New connection callback, use one of the connection descriptors, if we have one left.
  static void ICACHE_FLASH_ATTR
  serbridgeConnectCb(void *arg)
  {
    struct espconn *conn = arg;
    // Find empty conndata in pool
    int i;
    for (i=0; i<MAX_CONN; i++) if (connData[i].conn==NULL) break;
  #ifdef SERBR_DBG
    os_printf("Accept port %d, conn=%p, pool slot %d\n", conn->proto.tcp->local_port, conn, i);
  #endif
    syslog(SYSLOG_FAC_USER, SYSLOG_PRIO_NOTICE, "esp-link", "Accept port %d, conn=%p, pool slot %d\n", conn->proto.tcp->local_port, conn, i);
    if (i==MAX_CONN) {
  #ifdef SERBR_DBG
      os_printf("Aiee, conn pool overflow!\n");
  #endif
  	syslog(SYSLOG_FAC_USER, SYSLOG_PRIO_WARNING, "esp-link", "Aiee, conn pool overflow!\n");
      espconn_disconnect(conn);
      return;
    }
  
    os_memset(connData+i, 0, sizeof(struct serbridgeConnData));
    connData[i].conn = conn;
    conn->reverse = connData+i;
    connData[i].readytosend = true;
    connData[i].conn_mode = cmInit;
    // if it's the second port we start out in programming mode
    if (conn->proto.tcp->local_port == serbridgeConn2.proto.tcp->local_port)
      connData[i].conn_mode = cmPGMInit;
  
    espconn_regist_recvcb(conn, serbridgeRecvCb);
    espconn_regist_disconcb(conn, serbridgeDisconCb);
    espconn_regist_reconcb(conn, serbridgeResetCb);
    espconn_regist_sentcb(conn, serbridgeSentCb);
  
    espconn_set_opt(conn, ESPCONN_REUSEADDR|ESPCONN_NODELAY);
  }
  
  //===== Initialization
  
  void ICACHE_FLASH_ATTR
  serbridgeInitPins()
  {
    mcu_reset_pin = flashConfig.reset_pin;
    mcu_isp_pin = flashConfig.isp_pin;
  #ifdef SERBR_DBG
    os_printf("Serbridge pins: reset=%d isp=%d swap=%d\n",
        mcu_reset_pin, mcu_isp_pin, flashConfig.swap_uart);
  #endif
  
    if (flashConfig.swap_uart) {
      PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTCK_U, 4); // RX
      PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTDO_U, 4); // TX
      PIN_PULLUP_DIS(PERIPHS_IO_MUX_MTDO_U);
      if (flashConfig.rx_pullup) PIN_PULLUP_EN(PERIPHS_IO_MUX_MTCK_U);
      else                       PIN_PULLUP_DIS(PERIPHS_IO_MUX_MTCK_U);
      system_uart_swap();
    } else {
      PIN_FUNC_SELECT(PERIPHS_IO_MUX_U0TXD_U, 0);
      PIN_FUNC_SELECT(PERIPHS_IO_MUX_U0RXD_U, 0);
      PIN_PULLUP_DIS(PERIPHS_IO_MUX_U0TXD_U);
      if (flashConfig.rx_pullup) PIN_PULLUP_EN(PERIPHS_IO_MUX_U0RXD_U);
      else                       PIN_PULLUP_DIS(PERIPHS_IO_MUX_U0RXD_U);
      system_uart_de_swap();
    }
  
    // set both pins to 1 before turning them on so we don't cause a reset
    if (mcu_isp_pin >= 0)   GPIO_OUTPUT_SET(mcu_isp_pin, 1);
    if (mcu_reset_pin >= 0) GPIO_OUTPUT_SET(mcu_reset_pin, 1);
    // switch pin mux to make these pins GPIO pins
    if (mcu_reset_pin >= 0) makeGpio(mcu_reset_pin);
    if (mcu_isp_pin >= 0)   makeGpio(mcu_isp_pin);
  }
  
  // Start transparent serial bridge TCP server on specified port (typ. 23)
  void ICACHE_FLASH_ATTR
  serbridgeInit(int port1, int port2)
  {
    serbridgeInitPins();
  
    os_memset(connData, 0, sizeof(connData));
    os_memset(&serbridgeTcp1, 0, sizeof(serbridgeTcp1));
    os_memset(&serbridgeTcp2, 0, sizeof(serbridgeTcp2));
  
    // set-up the primary port for plain bridging
    serbridgeConn1.type = ESPCONN_TCP;
    serbridgeConn1.state = ESPCONN_NONE;
    serbridgeTcp1.local_port = port1;
    serbridgeConn1.proto.tcp = &serbridgeTcp1;
  
    espconn_regist_connectcb(&serbridgeConn1, serbridgeConnectCb);
    espconn_accept(&serbridgeConn1);
    espconn_tcp_set_max_con_allow(&serbridgeConn1, MAX_CONN);
    espconn_regist_time(&serbridgeConn1, SER_BRIDGE_TIMEOUT, 0);
  
    // set-up the secondary port for programming
    serbridgeConn2.type = ESPCONN_TCP;
    serbridgeConn2.state = ESPCONN_NONE;
    serbridgeTcp2.local_port = port2;
    serbridgeConn2.proto.tcp = &serbridgeTcp2;
  
    espconn_regist_connectcb(&serbridgeConn2, serbridgeConnectCb);
    espconn_accept(&serbridgeConn2);
    espconn_tcp_set_max_con_allow(&serbridgeConn2, MAX_CONN);
    espconn_regist_time(&serbridgeConn2, SER_BRIDGE_TIMEOUT, 0);
  }
  