/*
 * telnet_hal.cpp
 *
 *      Author: alexrayne <alexraynepe196@gmail.com>
  ------------------------------------------------------------------------
    Copyright (c) alexrayne

   All rights reserved.
   Redistribution and use in source and binary forms, with or without
   modification, are permitted provided that the following conditions are met:
   - Redistributions of source code must retain the above copyright
     notice, this list of conditions and the following disclaimer.
   - Redistributions in binary form must reproduce the above copyright
     notice, this list of conditions and the following disclaimer in the
     documentation and/or other materials provided with the distribution.
   - Neither the name of ARM nor the names of its contributors may be used
     to endorse or promote products derived from this software without
     specific prior written permission.
   *
   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
   AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
   IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
   ARE DISCLAIMED. IN NO EVENT SHALL COPYRIGHT HOLDERS AND CONTRIBUTORS BE
   LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
   CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
   SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
   INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
   CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
   ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
   POSSIBILITY OF SUCH DAMAGE. *
  ------------------------------------------------------------------------
 * this is a telnet protocol filter over stdio device
 */

#include "telnet_hal.hpp"
#include <arpa/telnet.h>
#include <string.h>

#if 1
#define LOG(s) puts(s)
#else
#define LOG(s) 
#endif

TelnetDeviceProxy::TelnetDeviceProxy(io_t* aio)
: _io(aio)
//, HAL_Device(name)
{
    
}

TelnetDeviceProxy::TelnetDeviceProxy(const_string name, io_t* aio)
: _io(aio)
//, HAL_Device(name)
{
    (void)name;
}

void TelnetDeviceProxy::init(io_t* aio){
    _io = aio;
}

// reset telnet negotiated state
void TelnetDeviceProxy::reset(){
    const u8 set_slient_lineedit[2] ={ LM_MODE, MODE_EDIT};
    reply_sub(TELOPT_LINEMODE, set_slient_lineedit, sizeof (set_slient_lineedit) );

    reply(DO, TELOPT_ECHO);
    reply(DO, TELOPT_SGA);
    reply(WILL, TELOPT_SGA);
    put_flush();
}

int TelnetDeviceProxy::get_char ()
{
    int c;
again:
    c = peekchar; peekchar = -1;
    if(c < 0)
        c = io()->get_char ();
    if (c < 0)
        return -1;

    if (c == '\r') {
        peekchar = io()->get_char ();
        if (peekchar < 0)
            return '\n';        /* no characters in buffer */

        if (peekchar == '\0' || peekchar == '\n') {
            peekchar = -1; /* drop character */
            return '\n';
        }
    }
    if (c == IAC) {
        c = io()->get_char ();
        if (c < 0)
            return -1;

        if (c != IAC) {
            command (c);
            goto again;
        }
    }
    return c;
}

// @return - amount of availiable data.
int TelnetDeviceProxy::get_wait(unsigned to){
    return io()->get_wait(to);
}

/*
 * Telnet stream interface is implemented as a proxy to tcp-stream.
 */
int TelnetDeviceProxy::putChar (int c)
{
    if (c == '\n')
        io()->putChar ('\r');
    io()->putChar (c);

    /* IAC -> IAC IAC */
    if (c == IAC)
        io()->putChar (c);
    return 1;
}

//*  неблокирующая печать
int TelnetDeviceProxy::postData ( const void* src, unsigned len){
    assert(src != nullptr);
    assert(io() != nullptr);
    const u8* str = (const u8*)src;
    unsigned least = len;
    while (least > 0){
        const u8* ps = str;
        u8 c;
        //scan str for special char
        for( unsigned ni = least ;ni > 0; --ni, ++ps){
            c = *ps;
            if (c == '\n'){
                break;
            }
            else if (c == IAC){
                break;
            }
            //else if (c == '\0')
            //    break;
        }

        int plen = (ps-str);
        int res = io()->postData( str, plen );
        if (res < 0)
            return (len-least);
        str     += res;
        least   -= res;
        if (plen > res){
            return (str - (const u8*)src);
        }
        if (least <= 0)
            break;


        c = *ps++;
        if (c == '\0') //impossible, we skip tests it
            break;
        else if (c == '\n') {
            res = io()->postData( "\r\n", 2 );
            if (res < 0)
                return (str - (const u8*)src);
        }
        else if (c == IAC) {
            static const u8 iacescape[2] = {IAC, IAC};
            res = io()->postData( iacescape, 2 );
            if (res < 0)
                return (str - (const u8*)src);
        }
    }
    return len-least;
}

//* блокирующая печать
int TelnetDeviceProxy::puts( const char* str){
    return putData(str, strlen(str) );
}

//*  \return - длинна отправленного участка
int TelnetDeviceProxy::putData ( const void* src, unsigned len){
    assert(src != nullptr);
    assert(io() != nullptr);
    const u8* str = (const u8*)src;
    unsigned least = len;
    while (least > 0){
        const u8* ps = str;
        u8 c;
        //scan str for special char
        for( unsigned ni = least ;ni > 0; --ni, ++ps){
            c = *ps;
            if (c == '\n'){
                break;
            }
            else if (c == IAC){
                break;
            }
            //else if (c == '\0')
            //    break;
        }

        int plen = (ps-str);
        if (plen > 0) {
            int res = io()->putData( str, plen );
            if (res < 0)
                return (len-least);
            if (plen > res){
                return (str - (const u8*)src) + res;
            }
        }
        plen++;
        c = *ps++;
        str     += plen;
        least   -= plen;

        if (c == '\0')
            break;
        else if (c == '\n') {
            auto res = io()->putData( "\r\n", 2 );
            if (res < 0)
                return (str - (const u8*)src);
        }
        else if (c == IAC) {
            static const u8 iacescape[2] = {IAC, IAC};
            auto res = io()->putData( iacescape, 2 );
            if (res < 0)
                return (str - (const u8*)src);
        }
    }
    return len-least;
}


//*  ожидание доступности печати
//*  \return - количество байт возможных для неблокирующей печати
int TelnetDeviceProxy::put_wait(unsigned to){
    return io()->put_wait(to);
}

//*  почти тоже put_wait, ждет полного опустошения
int TelnetDeviceProxy::put_flush(unsigned to){
    return io()->put_flush(to);
}

//*  очищает буфер, прерывая текущую отправку
int TelnetDeviceProxy::put_drop(){
    return io()->put_drop();
}


//* монополизация вывода (puts, putData предпочтительно использую ее )
//* \arg onoff - захват/освобождение
//* \return    - состояние захвачн ли вывод
bool TelnetDeviceProxy::put_access(bool onoff, unsigned to){
    return io()->put_access(onoff, to);
}

void TelnetDeviceProxy::reply (int cmd, int opt)
{
    const u8 buf[3] = {IAC, (u8)cmd, (u8)opt};
    io()->putData (buf, 3);
}

// sends subnegotiation 
void TelnetDeviceProxy::reply_sub(int opt, const u8* sub, unsigned len){
    reply(SB, opt);
    if (len > 0)
        io()->putData (sub, len);
    const u8 se_cmd[2] = {IAC, SE};
    io()->putData (se_cmd, 2);
}

void TelnetDeviceProxy::command (int c){
    const u8 nop_cmd[2] = {IAC, NOP};
    int i;

    switch (c) {
    case AYT:
        /* Send reply to AYT ("Are You There") request */
        io()->putData (nop_cmd, 2);
        break;

    case WILL:
    case WONT:
        /* IAC WILL received (get next character) */
        c = io()->get_char ();
        if (c >= 0 && c < 256)
            reply(DONT, c);
        break;

    case DO:
    case DONT:
        /* IAC DO received (get next character) */
        c = io()->get_char ();
        if (c >= 0 && c < 256)
            reply(WONT, c);
        break;

    case SB:
        for (i=0; i<128; i++) {
            c = io()->get_char ();
            if (c == IAC) {
                c = io()->get_char ();
                break;
            }
        }
        break;
    }
}



//---------------------------------------------------------------------

void TelnetDeviceBasic::will_option (int opt)
{
    int cmd;

    switch (opt) {
    case TELOPT_BINARY:
    case TELOPT_ECHO:
    case TELOPT_SGA:
    case TELOPT_LINEMODE:
        remote_option.set(opt, true);
        cmd = DO;
        break;

    case TELOPT_TM:
    default:
        cmd = DONT;
        break;
    }
    reply (cmd, opt);
}

void TelnetDeviceBasic::wont_option (int opt)
{
    switch (opt) {
    case TELOPT_ECHO:
    case TELOPT_BINARY:
    case TELOPT_SGA:
    case TELOPT_LINEMODE:
        remote_option.set (opt, false);
        break;
    }
    reply (DONT, opt);
}

void TelnetDeviceBasic::do_option (int opt)
{
    int cmd;

    switch (opt) {
    case TELOPT_ECHO:
    case TELOPT_BINARY:
    case TELOPT_SGA:
    case TELOPT_LINEMODE:
        local_option.set (opt ,true);
        cmd = WILL;
        break;

    case TELOPT_TM:
    default:
        cmd = WONT;
        break;
    }
    reply (cmd, opt);
}

void TelnetDeviceBasic::dont_option (int opt)
{
    switch (opt) {
    case TELOPT_ECHO:
    case TELOPT_BINARY:
    case TELOPT_SGA:
    case TELOPT_LINEMODE:
        local_option.set (opt, false);
        break;
    }
    reply (WONT, opt);
}

void TelnetDeviceBasic::command (int c)
{
    const u8 nop_cmd[2] = {IAC, NOP};
    int i;

    LOG("COMMAND ");
    
    switch (c) {
    case AYT:
        /* Send reply to AYT ("Are You There") request */
        io()->putData (nop_cmd, 2);
        break;

    case WILL:
        LOG(" WILL ");
        /* IAC WILL received (get next character) */
        c = io()->get_char ();
        if (c >= 0 && c < 256)
        if (!remote_option.test(c))
            will_option (c);
        break;

    case WONT:
        LOG(" WONT ");
        /* IAC WONT received (get next character) */
        c = io()->get_char ();
        if (c >= 0 && c < 256)
        if (remote_option.test (c))
            wont_option (c);
        break;

    case DO:
        LOG(" DO ");
        /* IAC DO received (get next character) */
        c = io()->get_char ();
        if (c >= 0 && c < 256)
        if ( !local_option.test (c))
            do_option (c);
        break;

    case DONT:
        LOG(" DONT ");
        /* IAC DONT received (get next character) */
        c = io()->get_char ();
        if (c >= 0 && c < 256)
        if (local_option.test(c))
            dont_option (c);
        break;

    case SB: {
        LOG(" SB ");
        u8 tmp[128];
        u8 opt = io()->get_char ();
        for (i=0; i<128; i++) {
            c = io()->get_char ();
            tmp[i] = c;
            if (c == IAC) {
                c = io()->get_char ();
                sub_option(opt, tmp, i);
                break;
            }
        }
        }
        break;
    }
}

void TelnetDeviceBasic::sub_option(int opt, u8* data,  unsigned len){
    if (opt == TELOPT_LINEMODE) {
        // basic option is to accepn any line-mode, that client suggest
        if (data[0] == LM_MODE) {
            data[1] |= MODE_ACK;
        }
        reply_sub(opt, data, len);
    }
}


void TelnetDeviceBasic::reset(){
    //const u8 set_slient_lineedit[2] ={ LM_MODE, MODE_EDIT};
    //reply_sub(TELOPT_LINEMODE, set_slient_lineedit, sizeof (set_slient_lineedit) );

    //do_option (TELOPT_LINEMODE);
    will_option (TELOPT_LINEMODE);
    do_option (TELOPT_ECHO);
    do_option (TELOPT_SGA);
    will_option (TELOPT_SGA);
    put_flush();
}




