// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "build/build_config.h"

#if defined(OS_WIN)
// winsock2.h must be included first in order to ensure it is included before
// windows.h.
#include <winsock2.h>
#elif defined(OS_POSIX)
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include "base/message_loop.h"
#include "net/base/net_errors.h"
#if defined(USE_SYSTEM_LIBEVENT)
#include <event.h>
#else
#include "third_party/libevent/event.h"
#endif
#include "base/message_pump_libevent.h"
#endif

#include "base/eintr_wrapper.h"
#include "net/base/telnet_server.h"

#if defined(OS_POSIX)
// Used same name as in Windows to avoid #ifdef where refrenced
#define SOCKET int
const int INVALID_SOCKET = -1;
const int SOCKET_ERROR = -1;
struct event;  // From libevent
#endif

const int kReadBufSize = 200;

// Telnet protocol constants.
class TelnetProtocol {
 public:
  // Telnet command definitions (from arpa/telnet.h).
  enum Commands {
    IAC   = 255,  // Interpret as command.
    DONT  = 254,  // You are not to use option.
    DO    = 253,  // Please, you use option.
    WONT  = 252,  // I won't use option.
    WILL  = 251,  // I will use option.
    SB    = 250,  // Interpret as subnegotiation.
    GA    = 249,  // You may reverse the line.
    EL    = 248,  // Erase the current line.
    EC    = 247,  // Erase the current character.
    AYT   = 246,  // Are you there.
    AO    = 245,  // Abort output--but let prog finish.
    IP    = 244,  // Interrupt process--permanently.
    BREAK = 243,  // Break.
    DM    = 242,  // Data mark--for connect. cleaning.
    NOP   = 241,  // Nop.
    SE    = 240,  // End sub negotiation.
    EOR   = 239,  // End of record (transparent mode).
    ABORT = 238,  // Abort process.
    SUSP  = 237,  // Suspend process.
    XEOF  = 236   // End of file: EOF is already used...
  };

  // Telnet options (from arpa/telnet.h).
  enum Options {
    BINARY =  0,  // 8-bit data path.
    ECHO   =  1,  // Echo.
    SGA    =  3,  // Suppress go ahead.
    NAWS   = 31,  // Window size.
    LFLOW  = 33  // Remote flow control.
  };

  // Fixed character definitions mentioned in RFC 854.
  enum Characters {
    NUL  = 0x00,
    LF   = 0x0A,
    CR   = 0x0D,
    BELL = 0x07,
    BS   = 0x08,
    HT   = 0x09,
    VT   = 0x0B,
    FF   = 0x0C,
    DEL  = 0x7F,
    ESC  = 0x1B
  };
};


///////////////////////

// must run in the IO thread
TelnetServer::TelnetServer(SOCKET s, ListenSocketDelegate* del)
    : ListenSocket(s, del) {
  input_state_ = NOT_IN_IAC_OR_ESC_SEQUENCE;
}

// must run in the IO thread
TelnetServer::~TelnetServer() {
}

void TelnetServer::SendIAC(int command, int option) {
  char data[3];
  data[0] = static_cast<unsigned char>(TelnetProtocol::IAC);
  data[1] = static_cast<unsigned char>(command);
  data[2] = option;
  Send(data, 3);
}

// always fixup \n to \r\n
void TelnetServer::SendInternal(const char* data, int len) {
  int begin_index = 0;
  for (int i = 0; i < len; i++) {
    if (data[i] == TelnetProtocol::LF) {
      // Send CR before LF if missing before.
      if (i == 0 || data[i - 1] != TelnetProtocol::CR) {
        // Send til before LF.
        ListenSocket::SendInternal(data + begin_index, i - begin_index);
        // Send CRLF.
        ListenSocket::SendInternal("\r\n", 2);
        // Continue after LF.
        begin_index = i + 1;
      }
    }
  }
  // Send what is left (the whole string is sent here if CRLF was ok)
  ListenSocket::SendInternal(data + begin_index, len - begin_index);
}

void TelnetServer::Accept() {
  SOCKET conn = ListenSocket::Accept(socket_);
  if (conn == INVALID_SOCKET) {
    // TODO
  } else {
    scoped_refptr<TelnetServer> sock =
        new TelnetServer(conn, socket_delegate_);
#if defined(OS_POSIX)
    sock->WatchSocket(WAITING_READ);
#endif
    // Setup the way we want to communicate
    sock->SendIAC(TelnetProtocol::DO, TelnetProtocol::ECHO);
    sock->SendIAC(TelnetProtocol::DO, TelnetProtocol::NAWS);
    sock->SendIAC(TelnetProtocol::DO, TelnetProtocol::LFLOW);
    sock->SendIAC(TelnetProtocol::WILL, TelnetProtocol::ECHO);
    sock->SendIAC(TelnetProtocol::WILL, TelnetProtocol::SGA);

    // it's up to the delegate to AddRef if it wants to keep it around
    socket_delegate_->DidAccept(this, sock);
  }
}

TelnetServer* TelnetServer::Listen(std::string ip, int port,
                                   ListenSocketDelegate *del) {
  SOCKET s = ListenSocket::Listen(ip, port);
  if (s == INVALID_SOCKET) {
    // TODO (ibrar): error handling
  } else {
    TelnetServer *serv = new TelnetServer(s, del);
    serv->Listen();
    return serv;
  }
  return NULL;
}

void TelnetServer::StateMachineStep(unsigned char c) {
  switch (input_state_) {
    case NOT_IN_IAC_OR_ESC_SEQUENCE:
      if (c == TelnetProtocol::IAC) {
        // Expect IAC command
        input_state_ = EXPECTING_COMMAND;
      } else if (c == TelnetProtocol::ESC) {
        // Expect left suare bracket
        input_state_ = EXPECTING_FIRST_ESC_CHARACTER;
      } else {
        char data[1];
        data[0] = c;
        // handle backspace specially
        if (c == TelnetProtocol::DEL) {
          if (!command_line_.empty()) {
            command_line_.erase(--command_line_.end());
            Send(data, 1);
          }
        } else {
          // Collect command
          if (c >= ' ')
            command_line_ += c;
          // Echo character to client (for now ignore control characters).
          if (c >= ' ' || c == TelnetProtocol::CR) {
            Send(data, 1);
          }
          // Check for line termination
          if (c == TelnetProtocol::CR)
            input_state_ = EXPECTING_NEW_LINE;
        }
      }
      break;
    case EXPECTING_NEW_LINE:
      if (c == TelnetProtocol::LF) {
        Send("\n", 1);
        socket_delegate_->DidRead(this, command_line_);
        command_line_ = "";
      }
      input_state_ = NOT_IN_IAC_OR_ESC_SEQUENCE;
      break;
    case EXPECTING_COMMAND:
      // Read command, expect option.
      iac_command_ = c;
      input_state_ = EXPECTING_OPTION;
      break;
    case EXPECTING_OPTION:
      // Read option
      iac_option_ = c;
      // check for subnegoating if not done reading IAC.
      if (iac_command_ != TelnetProtocol::SB) {
        input_state_ = NOT_IN_IAC_OR_ESC_SEQUENCE;
      } else {
        input_state_ = SUBNEGOTIATION_EXPECTING_IAC;
      }
      break;
    case SUBNEGOTIATION_EXPECTING_IAC:
      // Currently ignore content of subnegotiation.
      if (c == TelnetProtocol::IAC)
        input_state_ = SUBNEGOTIATION_EXPECTING_SE;
      break;
    case SUBNEGOTIATION_EXPECTING_SE:
      // Character must be SE and subnegotiation is finished.
      input_state_ = NOT_IN_IAC_OR_ESC_SEQUENCE;
      break;
    case EXPECTING_FIRST_ESC_CHARACTER:
      if (c == '[') {
        // Expect ESC sequence content.
        input_state_ = EXPECTING_NUMBER_SEMICOLON_OR_END;
      } else if (c == 'O') {
        // VT100 "ESC O" sequence.
        input_state_ = EXPECTING_SECOND_ESC_CHARACTER;
      } else {
        // Unknown ESC sequence - ignore.
      }
      break;
    case EXPECTING_SECOND_ESC_CHARACTER:
      // Ignore ESC sequence content for now.
      input_state_ = NOT_IN_IAC_OR_ESC_SEQUENCE;
      break;
    case EXPECTING_NUMBER_SEMICOLON_OR_END:
      if (isdigit(c) || c ==';') {
        // Ignore ESC sequence content for now.
      } else {
        // Final character in ESC sequence.
        input_state_ = NOT_IN_IAC_OR_ESC_SEQUENCE;
      }
      break;
  }
}

void TelnetServer::Read() {
  char buf[kReadBufSize + 1];
  int len;
  do {
    len = HANDLE_EINTR(recv(socket_, buf, kReadBufSize, 0));

#if defined(OS_WIN)
    if (len == SOCKET_ERROR) {
      int err = WSAGetLastError();
      if (err == WSAEWOULDBLOCK)
        break;
#else
    if (len == SOCKET_ERROR) {
      if (errno == EWOULDBLOCK || errno == EAGAIN)
        break;
#endif
    } else if (len == 0) {
      Close();
    } else {
      const char *data = buf;
      for (int i = 0; i < len; ++i) {
        unsigned char c = static_cast<unsigned char>(*data);
        StateMachineStep(c);
        data++;
      }
    }
  } while (len == kReadBufSize);
}