/*****************************************************************************
*    Open LiteSpeed is an open source HTTP server.                           *
*    Copyright (C) 2013 - 2022  LiteSpeed Technologies, Inc.                 *
*                                                                            *
*    This program 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.                                     *
*                                                                            *
*    This program 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 this program. If not, see http://www.gnu.org/licenses/.      *
*****************************************************************************/
#include "ntwkiolink.h"
#include <config.h>
#include <ls.h>
#include <lsdef.h>
#include <edio/multiplexer.h>
#include <edio/multiplexerfactory.h>
#include <http/clientinfo.h>
#include <http/clientcache.h>
#include <http/connlimitctrl.h>
#include <http/hiohandlerfactory.h>
#include <http/httpaiosendfile.h>
#include <http/httpresourcemanager.h>
#include <http/httprespheaders.h>
#include <http/httplistener.h>
#include <http/httplistenerlist.h>

#include <http/httpstats.h>
#include <log4cxx/logger.h>
#include <lsiapi/lsiapi.h>
#include <lsr/ls_strtool.h>
#include <socket/gsockaddr.h>
#include <sslpp/sslcontext.h>
#include <sslpp/sslerror.h>
#include <util/accessdef.h>
#include <util/datetime.h>
#include <util/stringtool.h>

#include <poll.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/uio.h>
#include <unistd.h>
#include <assert.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <netinet/tcp.h>

#if !defined(NO_SENDFILE)
#include <util/gsendfile.h>
#endif

#define NIOL_THROTTLE_READ    8
#define NIOL_THROTTLE_WRITE   16
#define NIOL_COUNTED          32
#define NIOL_TRY_PROXY        64

//#define HTTP2_PLAIN_DEV

//#define SPDY_PLAIN_DEV

int NtwkIOLink::s_iPrevTmToken = 0;
int NtwkIOLink::s_iTmToken = 0;

class NtwkIOLink::fp_list NtwkIOLink::s_normal
    (
        NtwkIOLink::readEx,
        NtwkIOLink::writevEx,
        NtwkIOLink::onWrite,
        NtwkIOLink::onRead,
        NtwkIOLink::close_,
        NtwkIOLink::onTimer_
    );

class NtwkIOLink::fp_list NtwkIOLink::s_normalSSL
    (
        NtwkIOLink::readExSSL,
        NtwkIOLink::writevExSSL,
        NtwkIOLink::onWriteSSL,
        NtwkIOLink::onReadSSL,
        NtwkIOLink::closeSSL,
        NtwkIOLink::onTimer_
    );

class NtwkIOLink::fp_list NtwkIOLink::s_throttle
    (
        NtwkIOLink::readExT,
        NtwkIOLink::writevExT,
        NtwkIOLink::onWriteT,
        NtwkIOLink::onReadT,
        NtwkIOLink::close_,
        NtwkIOLink::onTimer_T
    );

class NtwkIOLink::fp_list NtwkIOLink::s_throttleSSL
    (
        NtwkIOLink::readExSSL_T,
        NtwkIOLink::writevExSSL_T,
        NtwkIOLink::onWriteSSL_T,
        NtwkIOLink::onReadSSL_T,
        NtwkIOLink::closeSSL,
        NtwkIOLink::onTimerSSL_T
    );

class NtwkIOLink::fp_list_list   NtwkIOLink::s_fp_list_list_normal
    (
        &NtwkIOLink::s_normal,
        &NtwkIOLink::s_normalSSL
    );

class NtwkIOLink::fp_list_list   NtwkIOLink::s_fp_list_list_throttle
    (
        &NtwkIOLink::s_throttle,
        &NtwkIOLink::s_throttleSSL
    );

class NtwkIOLink::fp_list_list  *NtwkIOLink::s_pCur_fp_list_list =
        &NtwkIOLink::s_fp_list_list_normal;

NtwkIOLink::NtwkIOLink()
    : m_pVHostMap(NULL)
    , m_iRemotePort(0)
    , m_iInProcess(0)
    , m_iFlag(0)
    , m_tmToken(0)
    , m_iSslLastWrite(0)
    , m_iHeaderToSend(0)
    , m_pFpList(NULL)
    , m_sessionHooks()
    , m_hasBufferedData(0)
    //, m_aioSFQ()
{
    m_pModuleConfig = NULL;
}


NtwkIOLink::~NtwkIOLink()
{
    LsiapiBridge::releaseModuleData(LSI_DATA_L4, getModuleData());
}


int NtwkIOLink::writev(const struct iovec *vector, int len)
{
    int written = 0;

    if (m_iHeaderToSend > 0)
    {
        int appended = 0;
        if (m_iov.avail() >= len && len > 0)
        {
            memmove(m_iov.end(), vector, sizeof(struct iovec) * len);
            appended = 1;
        }
        written = writev_internal(m_iov.get(), m_iov.len() + len, 0);
        if (written >= m_iHeaderToSend)
        {
            m_iov.clear();
            written -= m_iHeaderToSend;
            m_iHeaderToSend = 0;
            if (appended || !len)
                return written;
        }
        else if (written > 0)
        {
            m_iHeaderToSend -= written;
            m_iov.finish(written);
            return 0;
        }
        else
            return written;
    }
    written = writev_internal(vector, len, 0);

    return written;
}


int NtwkIOLink::writev_internal(const struct iovec *vector, int len,
                                int flush_flag)
{
    const LsiApiHooks *pWritevHooks = LsiApiHooks::getGlobalApiHooks(
                                          LSI_HKPT_L4_SENDING);
    if (!pWritevHooks || m_sessionHooks.isDisabled(LSI_HKPT_L4_SENDING))
        return (*m_pFpList->m_writev_fp)((LsiSession *)this,
                                         (struct iovec *)vector, len);

    int ret;
    lsi_param_t param;
    lsi_hookinfo_t hookInfo;
    param.session = (LsiSession *)this;
    int    flag_out = 0;

    hookInfo.hooks = pWritevHooks;
    hookInfo.enable_array = m_sessionHooks.getEnableArray(
                                LSI_HKPT_L4_SENDING);
    hookInfo.term_fn = (filter_term_fn)m_pFpList->m_writev_fp;
    hookInfo.hook_level = LSI_HKPT_L4_SENDING;
    param.cur_hook = (void *)pWritevHooks->begin();
    param.hook_chain = &hookInfo;
    param.ptr1 = vector;
    param.len1 = len;
    param.flag_out = &flag_out;
    param.flag_in = flush_flag;
    ret = LsiApiHooks::runForwardCb(&param);
    m_hasBufferedData = flag_out;
    LS_DBG_L(this, "[NtwkIOLink::writev] ret %d hasData %d",
             ret, m_hasBufferedData);
    return ret;
}


int NtwkIOLink::read(char *pBuf, int size)
{
    const LsiApiHooks *pReadHooks = LsiApiHooks::getGlobalApiHooks(
                                        LSI_HKPT_L4_RECVING);
    if (!pReadHooks || m_sessionHooks.isDisabled(LSI_HKPT_L4_RECVING))
        return (*m_pFpList->m_read_fp)(this, pBuf, size);

    int ret;
    lsi_param_t param;
    lsi_hookinfo_t hookInfo;
    param.session = (LsiSession *)this;

    hookInfo.hooks = pReadHooks;
    hookInfo.enable_array = m_sessionHooks.getEnableArray(
                                LSI_HKPT_L4_RECVING);
    hookInfo.term_fn = (filter_term_fn)m_pFpList->m_read_fp;
    hookInfo.hook_level = LSI_HKPT_L4_RECVING;
    param.cur_hook = (void *)((lsiapi_hook_t *)pReadHooks->end() - 1);
    param.hook_chain = &hookInfo;
    param.ptr1 = pBuf;
    param.len1 = size;
    param.flag_out = NULL;
    ret = LsiApiHooks::runBackwardCb(&param);
    LS_DBG_L(this, "[NtwkIOLink::read] read %d bytes.", ret);
    return ret;
}


int NtwkIOLink::write(const char *pBuf, int size)
{
    IOVec iov(pBuf, size);
    return writev(iov.get(), iov.len());
}


void NtwkIOLink::enableThrottle(int enable)
{
    if (enable)
        s_pCur_fp_list_list = &NtwkIOLink::s_fp_list_list_throttle;
    else
        s_pCur_fp_list_list = &NtwkIOLink::s_fp_list_list_normal;
}


int NtwkIOLink::setupHandler(HiosProtocol verSpdy)
{
    HioHandler *pHandler;
#ifdef SPDY_PLAIN_DEV
    if (!isSSL() && (verSpdy == HIOS_PROTO_HTTP))
        verSpdy = HIOS_PROTO_SPDY3;
#endif

#ifdef HTTP2_PLAIN_DEV
    if (!isSSL() && (verSpdy == HIOS_PROTO_HTTP))
        verSpdy = HIOS_PROTO_HTTP2;
#endif

    pHandler = HioHandlerFactory::getHandler(verSpdy);
    if (!pHandler)
        return LS_FAIL;

    if (verSpdy != HIOS_PROTO_HTTP)
        clearLogId();
    setProtocol(verSpdy);
    pHandler->attachStream(this);
    pHandler->onInitConnected();
    return 0;
}


int NtwkIOLink::switchToHttp2Handler(HioHandler *pSession)
{
    assert(pSession == getHandler());
    HioHandler *pHandler =
        HioHandlerFactory::getHandler(HIOS_PROTO_HTTP2);
    if (!pHandler)
        return LS_FAIL;

    clearLogId();
    setProtocol(HIOS_PROTO_HTTP2);
    pHandler->attachStream(this);
    pHandler->h2cUpgrade(pSession, NULL, 0);
    return 0;
}

int NtwkIOLink::setLink(HttpListener *pListener,  int fd, ConnInfo *pInfo)
{
    HioStream::reset(DateTime::s_curTime);
    setfd(fd);
    if (pInfo)
    {
        LS_DBG_L("NtwkIOLink::setLink called pInfo is m_pClientInfo %p,  "
                    "m_pCrypto %p, m_pServerAddrInfo %p, m_remotePort %d",
                    pInfo->m_pClientInfo,
                    pInfo->m_pCrypto,
                    pInfo->m_pServerAddrInfo,
                    pInfo->m_remotePort);
    }
    else
        assert(pInfo); // Coverity claims that this must not be NULL!

    setConnInfo(pInfo);
    setState(HIOS_CONNECTED);
    setHandler(NULL);

    assert(LSI_HKPT_L4_BEGINSESSION == 0);
    assert(LSI_HKPT_L4_ENDSESSION == 1);
    assert(LSI_HKPT_L4_RECVING == 2);
    assert(LSI_HKPT_L4_SENDING == 3);

    //Comment: admin listener is not init-ed, so pListener->getSessionHooks() is NULL and
    // then the sessionhooks will be disabled.
    m_sessionHooks.inherit(pListener->getSessionHooks(), 0);

    m_pModuleConfig = pListener->getModuleConfig();

    if (m_sessionHooks.isEnabled(LSI_HKPT_L4_BEGINSESSION))
        m_sessionHooks.runCallbackNoParam(LSI_HKPT_L4_BEGINSESSION, this);

    memset(&m_iInProcess, 0, (char *)&m_ssl - (char *)(&m_iInProcess));
    m_iov.clear();
    HttpStats::incIdleConns();
    m_tmToken = NtwkIOLink::getToken();
    if (MultiplexerFactory::getMultiplexer()->add(this,
            POLLIN | POLLHUP | POLLERR) == -1)
        return LS_FAIL;

    getClientInfo()->incConn();
    LS_DBG_L(this, "concurrent conn: %d", pInfo->m_pClientInfo->getConns());

    if (pInfo->m_pClientInfo->isProtocolProxy())
    {
        LS_DBG_L(this, "Try PROXY protocol");
        m_iFlag |= NIOL_TRY_PROXY;
    }

    //set ssl context
    if (pInfo->m_pSsl)
    {
        ConnLimitCtrl::getInstance().incSSLConn();
        setSSL(pInfo->m_pSsl);
        ((ConnInfo *)getConnInfo())->m_pCrypto = &m_ssl;
        if (pInfo->m_pClientInfo->getFlag(CIF_DISABLE_HTTP2))
        {
            m_ssl.setFlag(SslConnection::F_DISABLE_HTTP2, 1);
        }
        m_ssl.toAccept();
    }
    else
    {
        setNoSSL();
        if (!(m_iFlag & NIOL_TRY_PROXY))
            setupHandler(HIOS_PROTO_HTTP);
    }

    if (pInfo->m_pClientInfo->isFromLocalAddr(
        (const sockaddr *)pInfo->m_pServerAddrInfo->getAddr()))
        setFlag(HIO_FLAG_FROM_LOCAL, 1);

    return 0;
}


void NtwkIOLink::drainReadBuf()
{
    //clear the inbound data buffer
    char achDiscard[4096];
    int len = 4096;
    while (len == 4096)
        len = ::read(getfd(), achDiscard, len);
    if (len <= 0)
        closeSocket();
}


void NtwkIOLink::tryRead()
{
    handleEvents(POLLIN);
}


int NtwkIOLink::handleEvents(short evt)
{
    int event = evt;
    LS_DBG_M(this, "NtwkIOLink::handleEvents() fd: %d, mask=%hd, events=%hd!",
             getfd(), getEvents(), evt);
    if (getState() == HIOS_SHUTDOWN)
    {
        if (event & (POLLHUP | POLLERR))
            closeSocket();
        else
        {
            //resetRevent( POLLIN | POLLOUT );
            if (event & POLLIN)
            {
                if (getFlag(HIO_FLAG_ABORT | HIO_FLAG_PEER_SHUTDOWN))
                    MultiplexerFactory::getMultiplexer()->suspendRead(this);
                else
                    drainReadBuf();
            }
        }
        return 0;
    }
    m_iInProcess = 1;
    if (event & POLLIN)
    {
        //NOTE: force to allow flush output if unexpected POLLIN happens,
        //      flush could detect socket need to be closed.
        if ((getEvents() & POLLIN) == 0)
        {
            setAllowWrite();
            setFlag(HIO_FLAG_PAUSE_WRITE, 0);
        }
        else if (m_iFlag & NIOL_TRY_PROXY)
        {
            int ret = tryProtocolProxy();
            LS_DBG_M(this, "tryProtocolProxy() return %d", ret);
            if (ret != 0)
            {
                m_iFlag &=~NIOL_TRY_PROXY;
                if (!isSSL())
                    setupHandler(HIOS_PROTO_HTTP);
            }
            else
                goto skip_read;
        }
        (*m_pFpList->m_onRead_fp)(this);
    }

skip_read:
    if (event & (POLLHUP | POLLERR))
    {
        m_iInProcess = 0;
        onPeerClose();
        return 0;
    }
    if (event & POLLOUT)
    {
        LS_DBG_L(this, "***clear HIO_FLAG_PAUSE_WRITE flag**");
        setFlag(HIO_FLAG_PAUSE_WRITE, 0);
        (*m_pFpList->m_onWrite_fp)(this);
    }
    m_iInProcess = 0;

    switch(getState())
    {
    case HIOS_CLOSING:
        onPeerClose();
        break;
    case HIOS_SHUTDOWN:
        closeSocket();
        break;
    default:
        break;
    }
    return 0;
}


int NtwkIOLink::UpdateClientInfoByProxyProto(const struct sockaddr *from,
                                             uint16_t from_port)
{
    ClientInfo *pInfo = ClientCache::getInstance().getClientInfo(from);
    if (!pInfo)
        return 0;
    LS_DBG_L(getLogSession(),
             "[PROXY] update client address based on PROXY protocol to %s:%hu, access: %d",
             pInfo->getAddrString(), from_port, (int)pInfo->getAccess());

    ((ConnInfo *)getConnInfo())->m_remotePort = from_port;
    if (getClientInfo() == pInfo)
        return 0;

    getClientInfo()->decConn();
    pInfo->incConn();
    setClientInfo(pInfo);
    if (isLogIdBuilt())
    {
        char buf[128];
        snprintf(buf, sizeof(buf), "%s:%hu", pInfo->getAddrString(), from_port);
        getLogSession()->lockAddOrReplaceFrom('>', buf);
    }
    return 0;
}

union {
    struct {
        char line[108];
    } v1;
    struct {
        uint8_t sig[12];
        uint8_t ver_cmd;
        uint8_t fam;
        uint16_t len;
        union {
            struct {  /* for TCP/UDP over IPv4, len = 12 */
                uint32_t src_addr;
                uint32_t dst_addr;
                uint16_t src_port;
                uint16_t dst_port;
            } ip4;
            struct {  /* for TCP/UDP over IPv6, len = 36 */
                uint8_t  src_addr[16];
                uint8_t  dst_addr[16];
                uint16_t src_port;
                uint16_t dst_port;
            } ip6;
            struct {  /* for AF_UNIX sockets, len = 216 */
                uint8_t src_addr[108];
                uint8_t dst_addr[108];
            } unx;
        } addr;
    } v2;
} hdr;


int NtwkIOLink::process_proxy_v1(char *line, char *end,
                                 struct sockaddr_storage *from,
                                 struct sockaddr_storage *to)
{
    char *p = line;
    int family;
    void *from_ip;
    void *to_ip;
    int from_port;
    int to_port;

    if (strncmp(p, "TCP4 ", 5) == 0)
    {
        from->ss_family = to->ss_family = family = AF_INET;
        from_ip = &((sockaddr_in *)from)->sin_addr;
        to_ip = &((sockaddr_in *)to)->sin_addr;
    }
    else if (strncmp(p, "TCP6 ", 5) == 0)
    {
        from->ss_family = to->ss_family = family = AF_INET6;
        from_ip = &((sockaddr_in6 *)from)->sin6_addr;
        to_ip = &((sockaddr_in6 *)to)->sin6_addr;
    }
    else
        return -1;
    p += 5;
    char *p1;
    p1 = (char *)memchr(p, ' ', end - p);
    if (!p1)
        return -1;
    *p1 = '\0';
    if (inet_pton(family, p, from_ip) != 1)
        return -1;
    p = p1 + 1;

    p1 = (char *)memchr(p, ' ', end - p);
    if (!p1)
        return -1;
    *p1 = '\0';
    if (inet_pton(family, p, to_ip) != 1)
        return -1;
    p = p1 + 1;

    from_port = strtol(p, &p1, 10);
    if (!p1 || p1 == p)
        return -1;
    if (from_port < 0 || from_port > 65535)
        return -1;
    p = p1 + 1;
    to_port = strtol(p, &p1, 10);
    if (!p1 || p1 == p)
        return -1;
    if (to_port < 0 || to_port > 65535)
        return -1;
    UpdateClientInfoByProxyProto((struct sockaddr *)from, from_port);

    return 1;
}



static const char v2sig[13] = "\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A";

/* returns 0 if needs to poll, <0 upon error or >0 if it did the job */
int NtwkIOLink::tryProtocolProxy()
{
    int fd;
    int size, ret;
    struct sockaddr_storage from; /* already filled by accept() */
    struct sockaddr_storage to;   /* already filled by getsockname() */

    fd = getfd();
    do
    {
        ret = recv(fd, &hdr, sizeof(hdr), MSG_PEEK);
    } while (ret == -1 && errno == EINTR);

    if (ret == -1)
        return (errno == EAGAIN) ? 0 : -1;

    if (ret >= 16 && memcmp(&hdr.v2, v2sig, 12) == 0
        && (hdr.v2.ver_cmd & 0xF0) == 0x20)
    {
        int dataLen = ntohs(hdr.v2.len);
        size = 16 + dataLen;
        if (ret < size)
            return -1; /* truncated or too large header */

        switch (hdr.v2.ver_cmd & 0xF)
        {
        case 0x01: /* PROXY command */
            switch (hdr.v2.fam)
            {
            case 0x11:  /* TCPv4 */
                if (dataLen < 12)
                    return -1;
                ((struct sockaddr_in *)&from)->sin_family = AF_INET;
                ((struct sockaddr_in *)&from)->sin_addr.s_addr =
                    hdr.v2.addr.ip4.src_addr;
                ((struct sockaddr_in *)&from)->sin_port =
                    hdr.v2.addr.ip4.src_port;
                ((struct sockaddr_in *)&to)->sin_family = AF_INET;
                ((struct sockaddr_in *)&to)->sin_addr.s_addr =
                    hdr.v2.addr.ip4.dst_addr;
                ((struct sockaddr_in *)&to)->sin_port =
                    hdr.v2.addr.ip4.dst_port;
                {
                    char src_addr[80], dst_addr[80];
                    LS_DBG_M(this, "[PROXY] V2 %s:%hu->%s:%hu",
                        GSockAddr::ntop((struct sockaddr *)&from, src_addr, 80),
                            ntohs(hdr.v2.addr.ip4.src_port),
                        GSockAddr::ntop((struct sockaddr *)&to, dst_addr, 80),
                            ntohs(hdr.v2.addr.ip4.dst_port));
                    UpdateClientInfoByProxyProto((struct sockaddr *)&from,
                            ntohs(hdr.v2.addr.ip4.src_port));
                }
                goto done;
            case 0x21:  /* TCPv6 */
                if (dataLen < 36)
                    return -1;
                ((struct sockaddr_in6 *)&from)->sin6_family = AF_INET6;
                memcpy(&((struct sockaddr_in6 *)&from)->sin6_addr,
                    hdr.v2.addr.ip6.src_addr, 16);
                ((struct sockaddr_in6 *)&from)->sin6_port =
                    hdr.v2.addr.ip6.src_port;
                ((struct sockaddr_in6 *)&to)->sin6_family = AF_INET6;
                memcpy(&((struct sockaddr_in6 *)&to)->sin6_addr,
                    hdr.v2.addr.ip6.dst_addr, 16);
                ((struct sockaddr_in6 *)&to)->sin6_port =
                    hdr.v2.addr.ip6.dst_port;
                {
                    char src_addr[80], dst_addr[80];
                    LS_DBG_M(this, "[PROXY] V2 [%s]:%hu->[%s]:%hu",
                        GSockAddr::ntop((struct sockaddr *)&from, src_addr, 80),
                            ntohs(hdr.v2.addr.ip6.src_port),
                        GSockAddr::ntop((struct sockaddr *)&to, dst_addr, 80),
                            ntohs(hdr.v2.addr.ip6.dst_port));
                    UpdateClientInfoByProxyProto((struct sockaddr *)&from,
                            ntohs(hdr.v2.addr.ip6.src_port));
                }
                goto done;
            }
            /* unsupported protocol, keep local connection address */
            break;
        case 0x00: /* LOCAL command */
            /* keep local connection address for LOCAL */
            LS_DBG_M(this, "[PROXY] LOCAL command, keep local connection address");

            break;
        default:
            return -1; /* not a supported command */
        }
    }
    else if (ret >= 8 && memcmp(hdr.v1.line, "PROXY ", 6) == 0)
    {
        //"PROXY TCP4 255.255.255.255 255.255.255.255 65535 65535\r\n"
        char *end = (char *)memchr(hdr.v1.line, '\r', ret - 1);
        if (!end || end[1] != '\n')
            return -1; /* partial or invalid header */
        *end = '\0'; /* terminate the string to ease parsing */
        size = end + 2 - hdr.v1.line; /* skip header + CRLF */
        LS_DBG_M(this, "[PROXY] v1: '%.*s'", size, hdr.v1.line);
        ret = process_proxy_v1(hdr.v1.line + 6, end, &from, &to);
        if (ret == -1)
            return ret;
        /* parse the V1 header using favorite address parsers like inet_pton.
          * return -1 upon error, or simply fall through to accept.
          */
    }
    else
    {
        /* Wrong protocol */
        return -1;
    }

done:
    /* we need to consume the appropriate amount of data from the socket */
    do
    {
        ret = recv(fd, &hdr, size, 0);
    } while (ret == -1 && errno == EINTR);
    return (ret >= 0) ? 1 : -1;
}


int NtwkIOLink::close()
{
    return (*m_pFpList->m_close_fp)(this);
}


void NtwkIOLink::suspendRead()
{
    LS_DBG_L(this, "NtwkIOLink::suspendRead()...");
    MultiplexerFactory::getMultiplexer()->suspendRead(this);
}


void NtwkIOLink::continueRead()
{
    LS_DBG_L(this, "NtwkIOLink::continueRead()...");
    setFlag(HIO_FLAG_WANT_READ, 1);
    if ((allowRead()))
    {
        LS_DBG_L(this, "Read resumed!");
        MultiplexerFactory::getMultiplexer()->continueRead(this);
    }
}


void NtwkIOLink::suspendWrite()
{
    LS_DBG_L(this, "NtwkIOLink::suspendWrite()...");
    setFlag(HIO_FLAG_WANT_WRITE, 0);
    if (isSSL())
    {
        if (m_ssl.wantWrite() || m_ssl.wpending() > 0)
        {
            LS_DBG_L(this, "Pending SSL data, cannot suspend write.");
            return;
        }
        if (m_hasBufferedData)
        {
            LS_DBG_L(this, "Pending buffered data, cannot suspend write.");
            return;
        }
    }
    MultiplexerFactory::getMultiplexer()->suspendWrite(this);
    LS_DBG_L(this, "Write suspended");
}


void NtwkIOLink::continueWrite()
{
    LS_DBG_L(this, "NtwkIOLink::continueWrite()...");
    //if( getFlag( HIO_FLAG_WANT_WRITE ) )
    //    return;
    setFlag(HIO_FLAG_WANT_WRITE, 1);
    ThrottleUnit *pThrottle = getClientInfo()->getThrottleCtrl().getThrottleOut();
    if (!pThrottle->getAvail())
    {
        if (pThrottle->isUnlimited())
            pThrottle->reset();
        else
        {
            LS_DBG_L(this, "NtwkIOLink::continueWrite() - THROTTLING!!!");
            return;
        }
    }
    else
        LS_DBG_L(this, "Throttled!");

    LS_DBG_L(this, "write resumed!");
    MultiplexerFactory::getMultiplexer()->continueWrite(this);
}


void NtwkIOLink::switchWriteToRead()
{
    setFlag(HIO_FLAG_WANT_READ, 1);
    setFlag(HIO_FLAG_WANT_WRITE, 0);
    if (isSSL() && m_ssl.wpending() > 0)
    {
        LS_DBG_L(this, "[LSTLS] has buffered data, do not stop write.");
        MultiplexerFactory::getMultiplexer()->continueRead(this);
    }
    else
    {
        LS_DBG_L(this, "switchWriteToRead()");
        MultiplexerFactory::getMultiplexer()->switchWriteToRead(this);
    }
}


///////////////////////////////////////////////////////////////////////
// SSL
///////////////////////////////////////////////////////////////////////

void NtwkIOLink::updateSSLEvent()
{
    if (isWantWrite())
    {
        dumpState("updateSSLEvent", "CW");
        MultiplexerFactory::getMultiplexer()->continueWrite(this);
    }
    if (isWantRead())
    {
        dumpState("updateSSLEvent", "CR");
        MultiplexerFactory::getMultiplexer()->continueRead(this);
    }
}


void NtwkIOLink::checkSSLReadRet(int ret)
{
    if (ret > 0)
    {
        bytesRecv(ret);
        HttpStats::incSSLBytesRead(ret);
        setActiveTime(DateTime::s_curTime);
        //updateSSLEvent();
    }
    else if (!ret)
    {
        if (m_ssl.wantWrite())
        {
            dumpState("checkSSLReadRet", "CW");
            MultiplexerFactory::getMultiplexer()->continueWrite(this);
        }
    }
    else
        tobeClosed();

}


int NtwkIOLink::readExSSL(LsiSession *pIS, char *pBuf, int size)
{
    NtwkIOLink *pThis = static_cast<NtwkIOLink *>(pIS);
    int ret;
    assert(pBuf);
    ret = pThis->getSSL()->read(pBuf, size);
    pThis->checkSSLReadRet(ret);
    //DEBUG CODE:
//    if ( ret > 0 )
//        ::write( 1, pBuf, ret );
    return ret;
}


int NtwkIOLink::writevExSSL(LsiSession *pOS, const iovec *vector,
                            int count)
{
    NtwkIOLink *pThis = static_cast<NtwkIOLink *>(pOS);
    int ret = 0;

    const struct iovec *vect;
    const char *pBuf;
    int bufSize;
    int written;

    char *pBufEnd;
    char *pCurEnd;
    char achBuf[4096];
    pBufEnd = achBuf + 4096;
    pCurEnd = achBuf;
    for (int i = 0; i < count ;)
    {
        vect = &vector[i];
        pBuf = (const char *) vect->iov_base;
        bufSize = vect->iov_len;
        if (bufSize < 1024)
        {
            if (pBufEnd - pCurEnd > bufSize)
            {
                memmove(pCurEnd, pBuf, bufSize);
                pCurEnd += bufSize;
                ++i;
                if (i < count)
                    continue;
            }
            pBuf = achBuf;
            bufSize = pCurEnd - pBuf;
            pCurEnd = achBuf;
        }
        else if (pCurEnd != achBuf)
        {
            pBuf = achBuf;
            bufSize = pCurEnd - pBuf;
            pCurEnd = achBuf;
        }
        else
            ++i;
        written = pThis->getSSL()->write(pBuf, bufSize);

        LS_DBG_L(pThis, "SSL write() return %d!", written);

        if (written > 0)
        {
            pThis->bytesSent(written);
            HttpStats::incSSLBytesWritten(written);
            pThis->setActiveTime(DateTime::s_curTime);
            ret += written;
            if (written < bufSize)
            {
                pThis->updateSSLEvent();
                break;
            }
        }
        else if (!written)
        {
            // lslb uses commented out version.
            if (pThis->m_ssl.wantRead())
                MultiplexerFactory::getMultiplexer()->continueRead(pThis);
            //pThis->setSSLAgain();
            break;
        }
        else if (pThis->getState() != HIOS_SHUTDOWN)
        {
            LS_DBG_L(pThis, "SSL_write() failed: %s", SslError().what());

            pThis->setState(HIOS_CLOSING);
            return LS_FAIL;
        }
    }
    return ret;
}


void NtwkIOLink::setSSLAgain()
{
    if (m_ssl.wantRead() || getFlag(HIO_FLAG_WANT_READ))
    {
        dumpState("setSSLAgain", "CR");
        MultiplexerFactory::getMultiplexer()->continueRead(this);
    }
    else
    {
        dumpState("setSSLAgain", "SR");
        MultiplexerFactory::getMultiplexer()->suspendRead(this);
    }

    if (m_ssl.wantWrite() || m_ssl.wpending() > 0 || getFlag(HIO_FLAG_WANT_WRITE))
    {
        dumpState("setSSLAgain", "CW");
        MultiplexerFactory::getMultiplexer()->continueWrite(this);
    }
    else
    {
        dumpState("setSSLAgain", "SW");
        MultiplexerFactory::getMultiplexer()->suspendWrite(this);
    }
}


int NtwkIOLink::flush()
{
    int ret;
    LS_DBG_L(this, "NtwkIOLink::flush...");

//     int nodelay = 1;
//     ::setsockopt( getfd(), IPPROTO_TCP, TCP_NODELAY, &nodelay, sizeof( int ) );

    if (m_hasBufferedData || (m_iHeaderToSend > 0))
    {
        LS_DBG_L(this, "NtwkIOLink::flush buffered data ...");
        ret = writev_internal(m_iov.get(), m_iov.len(), LSI_CBFI_FLUSH);
        if (m_iHeaderToSend > 0)
        {
            if (ret >= m_iHeaderToSend)
            {
                m_iov.clear();
                m_iHeaderToSend = 0;
            }
            else
            {
                if (ret > 0)
                {
                    m_iHeaderToSend -= ret;
                    m_iov.finish(ret);
                    ret = LS_DONE;
                }
                return ret;
            }
        }
        if (m_hasBufferedData)
            return LS_DONE;
    }

    if (!isSSL())
        return LS_DONE;

    //For SSL part
    ret = flushSslWpending();
    switch(ret)
    {
    case 0:
        //setSSLAgain();
        if (m_ssl.wantRead())
        {
            dumpState("flush", "CR");
            MultiplexerFactory::getMultiplexer()->continueRead(this);
        }
        else if (m_ssl.wantWrite())
        {
            dumpState("flush", "CW");
            MultiplexerFactory::getMultiplexer()->continueRead(this);
        }

        return 1;
    case 1:
        return 0;
    case -1:
        tobeClosed();
        break;
    }


    return ret;
}


//return
//   0: not flushed
//   1: flushed
int NtwkIOLink::flushSslWpending()
{
    LS_DBG_L(this, "NtwkIOLink::flushSslWpending()...");

    if (isPauseWrite())
        return 0;
    if ((m_iHeaderToSend > 0))
    {
        LS_DBG_L(this, "Flush buffered header data ...");
        write("", 0);
    }
    if (getFlag(HIO_FLAG_DELAY_FLUSH))
        setFlag(HIO_FLAG_DELAY_FLUSH, 0);
    int ret = 1;
    int pending = m_ssl.wpending();
    LS_DBG_L(this, "SSL wpending: %d", pending);
    if (pending > 0)
        ret = m_ssl.flush();
    if (!ret)
    {
        LS_DBG_L(this, "[SSL] more to flush, continue write.");
        MultiplexerFactory::getMultiplexer()->continueWrite(this);
        setFlag(HIO_FLAG_PAUSE_WRITE, 1);
    }
    else if (ret == -1)
        tobeClosed();
    else
        if (!isWantWrite())
            MultiplexerFactory::getMultiplexer()->suspendWrite(this);
    return ret;
}


inline void NtwkIOLink::setAllowWrite()
{
    m_ssl.setAllowWrite();
}


int NtwkIOLink::onWriteSSL(NtwkIOLink *pThis)
{
    pThis->dumpState("onWriteSSL", "none");
    pThis->setAllowWrite();

    if (pThis->m_ssl.wantWrite())
    {
        if (!pThis->m_ssl.isConnected() || (pThis->m_ssl.lastRead()))
        {
            pThis->SSLAgain();
            return 0;
        }
    }
    int ret = 0;
    if (pThis->m_ssl.isConnected())
        ret = pThis->doWrite();
    pThis->flushSslWpending();
    return ret;
}


int NtwkIOLink::onReadSSL(NtwkIOLink *pThis)
{
    pThis->dumpState("onReadSSL", "none");
    if (!pThis->m_ssl.isConnected())
    {
        pThis->SSLAgain();
        if (!pThis->m_ssl.isConnected() || !pThis->isWantRead()
            || !pThis->m_ssl.hasPendingIn())
            return 0;
    }
    int ret = 0;
    if (pThis->m_ssl.isConnected())
        ret = pThis->doRead();
    pThis->flushSslWpending();
    return ret;
}


int NtwkIOLink::shutdownSsl()
{
    LS_DBG_L(this, "Shutting down SSL ...");
    m_ssl.shutdown(0);
    m_ssl.release();
    ConnLimitCtrl::getInstance().decSSLConn();
    setNoSSL();
    return 0;
}

int NtwkIOLink::closeSSL(NtwkIOLink *pThis)
{
    if (pThis->m_ssl.getSSL())
        pThis->shutdownSsl();
    return close_(pThis);
}


///////////////////////////////////////////////////////////////////////
// Plain
///////////////////////////////////////////////////////////////////////


int NtwkIOLink::shutdown()
{
    if (flushSslWpending() == 0)
    {
        if (!isPeerShutdown())
        {
            releaseHandler();
            if (m_iHeaderToSend > 0)
            {
                m_iov.clear();
                m_iHeaderToSend = 0;
            }
            if (getState() == HIOS_CONNECTED)
                setState(HIOS_CLOSING);
            MultiplexerFactory::getMultiplexer()->continueWrite(this);
            return 0;
        }
    }

    if (getState() == HIOS_SHUTDOWN)
        return 0;
    setState(HIOS_SHUTDOWN);

    if (m_ssl.getSSL())
        shutdownSsl();
    LS_DBG_L(this, "Shutting down out-bound socket ...");

    ::shutdown(getfd(), SHUT_WR);
    return 0;
}

int NtwkIOLink::doneWrite()
{
    if (m_ssl.getSSL() && flushSslWpending() == 0)
    {
        SSL_set_shutdown(m_ssl.getSSL(), SSL_SENT_SHUTDOWN);
    }
    else
        ::shutdown(getfd(), 1);
    return 0;
}


int NtwkIOLink::close_(NtwkIOLink *pThis)
{

    if (pThis->getFlag(HIO_FLAG_PEER_SHUTDOWN | HIO_FLAG_ABORT))
    {
        pThis->setState(HIOS_SHUTDOWN);
        if (!pThis->m_iInProcess)
            pThis->closeSocket();
    }
    else
    {
        pThis->shutdown();
        MultiplexerFactory::getMultiplexer()->switchWriteToRead(pThis);
        if (!(pThis->m_iFlag & NIOL_COUNTED))
        {
            ConnLimitCtrl::getInstance().decConn();
            pThis->getClientInfo()->decConn();
            pThis->m_iFlag |= NIOL_COUNTED;
            LS_DBG_L(pThis, "Available Connections: %d, concurrent conn: %d.",
                     ConnLimitCtrl::getInstance().availConn(),
                     pThis->getClientInfo()->getConns());
        }
    }
//    pThis->closeSocket();
    return LS_FAIL;
}


void NtwkIOLink::closeSocket()
{
    if (getfd() == -1)
        return;

    ConnLimitCtrl &ctrl = ConnLimitCtrl::getInstance();

    LS_DBG_L(this, "Close socket ...");

    if (m_sessionHooks.isEnabled(LSI_HKPT_L4_ENDSESSION))
        m_sessionHooks.runCallbackNoParam(LSI_HKPT_L4_ENDSESSION, this);

    MultiplexerFactory::getMultiplexer()->remove(this);
    if (m_pFpList == s_pCur_fp_list_list->m_pSSL)
    {
        m_ssl.release();
        ctrl.decSSLConn();
        setNoSSL();
    }
    if (!(m_iFlag & NIOL_COUNTED))
    {
        ctrl.decConn();
        getClientInfo()->decConn();
        LS_DBG_L(this, "Available Connections: %d, concurrent conn: %d",
                 ctrl.availConn(), getClientInfo()->getConns());
    }


    //printf( "socket: %d closed\n", getfd() );
    ::close(getfd());
    setfd(-1);
    //m_aioSFQ.pop_all();
    m_hasBufferedData = 0;
    m_pModuleConfig = NULL;
    if (getHandler())
    {
        getHandler()->recycle();
        setHandler(NULL);
    }
    //recycle itself.
    LS_DBG_L(this, "Recycle NtwkIoLink");
    HttpResourceManager::getInstance().recycle(this);
}


int NtwkIOLink::onRead(NtwkIOLink *pThis)
{
    if (pThis->getHandler())
        return pThis->getHandler()->onReadEx();
    return LS_FAIL;
}


int NtwkIOLink::onWrite(NtwkIOLink *pThis)
{
    return pThis->doWrite();
}


static int matchToken(int token)
{
    if (NtwkIOLink::getPrevToken() < NtwkIOLink::getToken())
        return ((token > NtwkIOLink::getPrevToken())
                && (token <= NtwkIOLink::getToken()));
    else
        return ((token > NtwkIOLink::getPrevToken())
                || (token <= NtwkIOLink::getToken()));
}


void NtwkIOLink::releaseIdleSslBuffer()
{
    m_ssl.releaseIdleBuffer();
}


void NtwkIOLink::enableSocketKeepAlive()
{
    int v = 1;
    setsockopt(getfd(), SOL_SOCKET, SO_KEEPALIVE, (char *) &v, sizeof (int));
}


int NtwkIOLink::onTimer()
{
    if (matchToken(this->m_tmToken))
    {
        if (this->hasBufferedData() && this->allowWrite())
            this->flush();
        /*
        if (m_aioSFQ.size())
        {
            Aiosfcb *cb = (Aiosfcb *)m_aioSFQ.begin();
            if (cb->getFlag(AIOSFCB_FLAG_TRYAGAIN))
                addAioSFJob(cb);
        }
        */
        if (m_ssl.getSSL() && m_ssl.getStatus() == SslConnection::ACCEPTING
            && DateTime::s_curTime - getActiveTime() >= 10)
        {
            LS_DBG_L(this, "SSL handshake timed out, close SSL.");
            closeSSL(this);
        }

        if (getState() == HIOS_SHUTDOWN)
        {
            LS_DBG_M(this, "Shutdown time out!");
            closeSocket();
            return 1;
        }
        else if (!detectClose())
        {
            m_iInProcess = 1;
            (*m_pFpList->m_onTimer_fp)(this);
            m_iInProcess = 0;
        }
        if (getState() == HIOS_CLOSING)
        {
            if (flushSslWpending() != 0)
            {
                onPeerClose();
                return 1;
            }
        }
    }
    return 0;
}


void NtwkIOLink::onTimer_(NtwkIOLink *pThis)
{
    if (pThis->getHandler())
        pThis->getHandler()->onTimerEx();
}


int NtwkIOLink::checkReadRet(int ret, int size)
{
    //Note: read return 0 means TCP receive FIN, the other side shutdown the write
    //       side of the socket, should leave it alone. Should get other signals
    //       if it is closed. should I?
    //      Content-length must be present in request header, and client
    //      can not shutdown the write side to indicating the end of the request
    //      body, so it is ok to do it.
    if (ret < size)
        resetRevent(POLLIN);
    if (ret > 0)
    {
        bytesRecv(ret);
        HttpStats::incBytesRead(ret);
        setActiveTime(DateTime::s_curTime);
    }
    else if (ret == 0)
    {
        if (getState() != HIOS_SHUTDOWN)
        {
            LS_DBG_L(this, "End of stream detected, CLOSING!");
            //have the connection closed quickly
            setFlag(HIO_FLAG_PEER_SHUTDOWN, 1);
            setState(HIOS_CLOSING);
        }
        ret = -1;
    }
    else if (ret == -1)
    {
        switch (errno)
        {
        case ECONNRESET:
            //incase client shutdown the writting side after sending the request
            // and waiting for the response, we can't close the connection before
            // we finish write the response back.
            LS_DBG_L(this, "Read error: %s", strerror(errno));
            // fallthru
        case EAGAIN:
        case EINTR:
            ret = 0;
            break;
        default:
            tobeClosed();
            LS_DBG_L(this, "Read error: %s", strerror(errno));
        }
    }
    LS_DBG_L(this, "Read from client: %d", ret);
    return ret;

}


int NtwkIOLink::readEx(LsiSession *pIS, char *pBuf, int size)
{
    NtwkIOLink *pThis = static_cast<NtwkIOLink *>(pIS);
    int ret;
    assert(pBuf);
    ret = ::read(pThis->getfd(), pBuf, size);
    ret = pThis->checkReadRet(ret, size);
//    if ( ret > 0 )
//        ::write( 1, pBuf, ret );
    return ret;
}


#if !defined( NO_SENDFILE )

off_t NtwkIOLink::sendfileSetUp(off_t size)
{
    if (m_iHeaderToSend > 0)
    {
        writev(NULL, 0);
        if (m_iHeaderToSend > 0)
            return 0;
    }
    ThrottleControl *pCtrl = getThrottleCtrl();

    if (pCtrl && !pCtrl->getThrottleOut()->isUnlimited())
    {
        int Quota = pCtrl->getOSQuota();
        if (size > (unsigned int)Quota + (Quota >> 3))
            size = Quota;
    }

    if (size <= 0)
        return 0;
    if (size > LONG_MAX)
        size = LONG_MAX;

    return size;
}


int NtwkIOLink::sendfileFinish(int written)
{
    int len;
    ThrottleControl *pCtrl = getThrottleCtrl();

#if defined(linux) || defined(__linux) || defined(__linux__) || \
    defined(__gnu_linux__)
    if (written == 0)
    {
        written = -1;
        errno = EPIPE;
    }
#endif
    len = checkWriteRet(written);
    if (pCtrl)
    {
        int Quota = pCtrl->getOSQuota();
        if (Quota - len < 10)
        {
            pCtrl->useOSQuota(Quota);
            MultiplexerFactory::getMultiplexer()->suspendWrite(this);
        }
        else
            pCtrl->useOSQuota(len);
    }
    return len;
}


int NtwkIOLink::sendfile(int fdSrc, off_t off, size_t size, int flag)
{
    int written;

    if ((size = sendfileSetUp(size)) == 0)
        return 0;
    written = gsendfile(getfd(), fdSrc, &off, size);

    return sendfileFinish(written);
}

int NtwkIOLink::addAioSFJob(Aiosfcb *cb)
{
    int ret = HttpAioSendFile::getHttpAioSendFile()->addJob(cb);
    if (ret)
    {
        cb->setFlag(AIOSFCB_FLAG_TRYAGAIN);
        LS_DBG_L(this, "Add Job Failed, Try Again Flag Set.");
    }
    else
        cb->clearFlag(AIOSFCB_FLAG_TRYAGAIN);
    return ret;
}

int NtwkIOLink::aiosendfile(Aiosfcb *cb)
{
    size_t size = cb->getSize();

    if ((size = sendfileSetUp(size)) == 0)
        return 0;

    cb->setSize(size);
    cb->setSendFd(getfd());
    m_aioSFQ.append(cb);

    if (m_aioSFQ.size() == 1)
        addAioSFJob(cb);
    else
        LS_DBG_M(this, "Ntwkiolink busy with another session");
    return 1;
}


int NtwkIOLink::aiosendfiledone(Aiosfcb *cb)
{
    int len, written = cb->getRet();
    Aiosfcb *pDone = (Aiosfcb *)m_aioSFQ.pop_front();
    if (pDone != cb)
        return LS_FAIL;

    len = sendfileFinish(written);

    if (m_aioSFQ.size() != 0)
        addAioSFJob((Aiosfcb *)m_aioSFQ.begin());

    return (len < 0 ? -1 : 0);
}


#endif


int NtwkIOLink::writevEx(LsiSession *pOS, const iovec *vector, int count)
{
    NtwkIOLink *pThis = static_cast<NtwkIOLink *>(pOS);
    int len = ::writev(pThis->getfd(), vector, count);
    len = pThis->checkWriteRet(len);
    //if (pThis->wantWrite() && pThis->m_hasBufferedData)
    //    MultiplexerFactory::getMultiplexer()->continueWrite( pThis );

    //TEST: debug code
//    if ( len > 0 )
//    {
//        int left = len;
//        const struct iovec* pVec = vector.get();
//        while( left > 0 )
//        {
//            int writeLen = pVec->iov_len;
//            if ( writeLen > left )
//                writeLen = left;
//            ::write( 1, pVec->iov_base, writeLen );
//            ++pVec;
//            left -= writeLen;
//        }
//    }
    return len;

}


int NtwkIOLink::sendRespHeaders(HttpRespHeaders *pHeader, send_hdr_flag hdr_flag)
{
    if (pHeader)
    {
        pHeader->outputNonSpdyHeaders(&m_iov);
        m_iHeaderToSend = pHeader->getTotalLen();
    }
    return 0;
}


int NtwkIOLink::checkWriteRet(int len)
{
    if (len > 0)
    {
        bytesSent(len);
        HttpStats::incBytesWritten(len);
        setActiveTime(DateTime::s_curTime);
    }
    else if (len == -1)
    {
        switch (errno)
        {
        case EINTR:
        case EAGAIN:
            LS_DBG_L(this, "write error: %s", strerror(errno));
            len = 0;
            break;
        default:
            if (getState() != HIOS_SHUTDOWN)
            {
                if (m_hasBufferedData == 0)
                {
                    setState(HIOS_CLOSING);
                    setFlag(HIO_FLAG_ABORT, 1);
                }
            }
            LS_DBG_L(this, "write error: %s!", strerror(errno));
        }
    }
    LS_DBG_L(this, "Written to client: %d", len);
    return len;
}


int NtwkIOLink::detectClose()
{
    if (getState() == HIOS_SHUTDOWN)
        return 1;
    if (getState() == HIOS_CONNECTED)
    {
        if (getClientInfo()->getAccess() == AC_BLOCK)
        {
            LS_DBG_L(this, "IP being blocked, drop existing connection!\n");
            //have the connection closed faster
            setFlag(HIO_FLAG_PEER_SHUTDOWN | SS_FLAG_DROP, 1);
            setState(HIOS_CLOSING);
            return 1;
        }

        char ch;
        if ((DateTime::s_curTime - getActiveTime() > 10) &&
             (::recv(getfd(), &ch, 1, MSG_PEEK) == 0))
        {
            LS_DBG_L(this, "Peer close connection detected!");
            //have the connection closed faster
            setFlag(HIO_FLAG_PEER_SHUTDOWN, 1);
            setState(HIOS_CLOSING);
            return 1;
        }
    }
    return 0;
}


int NtwkIOLink::detectCloseNow()
{
    char ch;
    if (::recv(getfd(), &ch, 1, MSG_PEEK) == 0)
    {
        LS_DBG_L(this, "Peer close connection detected!");
        //have the connection closed faster
        setFlag(HIO_FLAG_PEER_SHUTDOWN, 1);
        onPeerClose();
        return 1;
    }
    return 0;
}


///////////////////////////////////////////////////////////////////////
// Throttle
///////////////////////////////////////////////////////////////////////

int NtwkIOLink::onReadT(NtwkIOLink *pThis)
{
    return pThis->doReadT();
}


int NtwkIOLink::onWriteT(NtwkIOLink *pThis)
{
    if (pThis->allowWrite())
        return pThis->doWrite();
    else
        MultiplexerFactory::getMultiplexer()->suspendWrite(pThis);
    return 0;
}


void NtwkIOLink::dumpState(const char *pFuncName, const char *action)
{
    LS_DBG_H(this,  "%s(), %s, wantRead: %d, wantWrite: %d, allowWrite: %d,"
             " allowRead: %d, m_ssl.wantRead: %d, m_ssl.wantWrite: %d,"
             " m_ssl.lastRead: %d, m_ssl.lastWrite: %d",
             pFuncName, action, isWantRead(), isWantWrite(),
             allowWrite(), allowRead(),
             m_ssl.wantRead(), m_ssl.wantWrite(),
             m_ssl.lastRead(), m_ssl.lastWrite()
            );

}


void NtwkIOLink::onTimer_T(NtwkIOLink *pThis)
{
//     LS_DBG_H(pThis, "conn token:%d, global Token: %d\n",
//              pThis->m_tmToken, HttpGlobals::s_tmToken);
//     LS_DBG_H(pThis, "output avail:%d. state: %d \n",
//              pThis->getClientInfo()->getThrottleCtrl().getOSQuota(),
//              pThis->getState());

    if (pThis->hasBufferedData() && pThis->allowWrite())
        pThis->flush();

    if (pThis->allowWrite() && pThis->isWantWrite())
    {
        //pThis->doWrite();
        //if (  pThis->allowWrite() && pThis->wantWrite() )
        //pThis->dumpState("onTimer_T", "CW");
        MultiplexerFactory::getMultiplexer()->continueWrite(pThis);
    }
    if (pThis->allowRead() && pThis->isWantRead())
    {
        //if ( pThis->getState() != HSS_WAITING )
        //    pThis->doReadT();
        //if ( pThis->allowRead() && pThis->wantRead() )
        //pThis->dumpState("onTimer_T", "CR");
        MultiplexerFactory::getMultiplexer()->continueRead(pThis);
    }
    if (pThis->getHandler())
        pThis->getHandler()->onTimerEx();

}


int NtwkIOLink::readExT(LsiSession *pIS, char *pBuf, int size)
{
    NtwkIOLink *pThis = static_cast<NtwkIOLink *>(pIS);
    ThrottleControl *pTC = pThis->getThrottleCtrl();
    int iQuota = pTC->getISQuota();
    if (iQuota <= 0)
    {
        pThis->dumpState("readExT", "SR");
        MultiplexerFactory::getMultiplexer()->suspendRead(pThis);
        return 0;
    }
    if (size > iQuota)
        size = iQuota;
    assert(pBuf);
    int ret = ::read(pThis->getfd(), pBuf, size);
    ret = pThis->checkReadRet(ret, size);
    if (ret > 0)
    {
        pTC->useISQuota(ret);
//        ::write( 1, pBuf, ret );
        if (!pTC->getISQuota())
        {
            pThis->dumpState("readExT", "SR");
            MultiplexerFactory::getMultiplexer()->suspendRead(pThis);
        }
    }
    return ret;
}


int NtwkIOLink::writevExT(LsiSession *pOS, const iovec *vector, int count)
{
    NtwkIOLink *pThis = static_cast<NtwkIOLink *>(pOS);
    int len = 0;
    ThrottleControl *pCtrl = pThis->getThrottleCtrl();
    int Quota = pCtrl->getOSQuota();
    if (Quota <= 0)
    {
        pThis->dumpState("writevExT", "SW");
        MultiplexerFactory::getMultiplexer()->suspendWrite(pThis);
        return 0;
    }

    int total = 0;
    for (int i = 0; i < count; ++i)
        total += vector[i].iov_len;

//    LS_DBG_L( pThis, "Quota:%d, to write: %d\n", Quota, total);
    if ((unsigned int)total > (unsigned int)Quota + (Quota >> 3))
    {
        IOVec iov;
        iov.append(vector, count);
        total = iov.shrinkTo(Quota, Quota >> 3);
        len = ::writev(pThis->getfd(), iov.begin(), iov.len());
    }
    else
        len = ::writev(pThis->getfd(), vector, count);

    len = pThis->checkWriteRet(len);
    if (Quota - len < 10)
    {
        pCtrl->useOSQuota(Quota);
        pThis->dumpState("writevExT", "SW");
        MultiplexerFactory::getMultiplexer()->suspendWrite(pThis);
    }
    else
        pCtrl->useOSQuota(len);
    return len;

}


///////////////////////////////////////////////////////////////////////
// Throttle + SSL
///////////////////////////////////////////////////////////////////////


//void NtwkIOLink::onTimerSSL_T( NtwkIOLink * pThis )
//{
//    if ( pThis->detectClose() )
//        return;
//    if ( pThis->allowWrite() &&
//        (( pThis->m_ssl.wantWrite() )||pThis->wantWrite() ))
//        pThis->doWriteT();
//    if ( pThis->allowRead() &&
//        (( pThis->m_ssl.wantRead() )||pThis->wantRead() ))
//    {
//        pThis->doReadT();
//    }
//    pThis->onTimerEx();
//}


void NtwkIOLink::onTimerSSL_T(NtwkIOLink *pThis)
{
    pThis->setAllowWrite();
    if (pThis->flushSslWpending() == 1)
        pThis->releaseIdleSslBuffer();

    if (pThis->allowWrite() && (pThis->m_ssl.wantWrite()))
        onWriteSSL_T(pThis);
    if (pThis->allowRead() && (pThis->m_ssl.wantRead()))
        onReadSSL_T(pThis);
    if (pThis->allowWrite() && pThis->isWantWrite())
    {
        pThis->doWrite();
        if (pThis->allowWrite() && pThis->isWantWrite())
        {
            //pThis->dumpState("onTimerSSL_T", "CW");
            MultiplexerFactory::getMultiplexer()->continueWrite(pThis);
        }
    }
    if (pThis->allowRead() && pThis->isWantRead())
    {
        //if ( pThis->getState() != HSS_WAITING )
        //    pThis->doReadT();
        //if ( pThis->allowRead() && pThis->wantRead() )
        //pThis->dumpState("onTimerSSL_T", "CR");
        MultiplexerFactory::getMultiplexer()->continueRead(pThis);
    }
    if (pThis->getHandler())
        pThis->getHandler()->onTimerEx();
}


int NtwkIOLink::onReadSSL_T(NtwkIOLink *pThis)
{
    //pThis->dumpState( "onReadSSL_t", "none" );
    if (!pThis->m_ssl.isConnected())
    {
        pThis->SSLAgain();
        if (!pThis->m_ssl.isConnected() || !pThis->isWantRead()
            || !pThis->m_ssl.hasPendingIn())
            return 0;
    }
    int ret = 0;
    if (pThis->m_ssl.isConnected())
        ret = pThis->doRead();
    pThis->flushSslWpending();
    return ret;
}


int NtwkIOLink::onWriteSSL_T(NtwkIOLink *pThis)
{
    pThis->setAllowWrite();
    //pThis->dumpState( "onWriteSSL_T", "none" );
    if (pThis->m_ssl.wantWrite())
    {
        int last = pThis->m_ssl.lastRead();
        if (!pThis->m_ssl.isConnected() || (last))
        {
            pThis->SSLAgain();
//            if (( !pThis->m_ssl.isConnected() )||(last ))
            return 0;
        }

    }
    if (pThis->m_ssl.isConnected() && pThis->allowWrite())
    {
        int ret = pThis->doWrite();
        pThis->flushSslWpending();
        return ret;
    }
    else
    {
        pThis->flushSslWpending();
        MultiplexerFactory::getMultiplexer()->suspendWrite(pThis);
    }
    return 0;
}


static char s_errUseSSL[] =
    "HTTP/1.0 200 OK\r\n"
    "Cache-Control: private, no-cache, max-age=0\r\n"
    "Pragma: no-cache\r\n"
    "Connection: Close\r\n\r\n"
    "<html><head><title>400 Bad Request</title>"
    "<style>@media (prefers-color-scheme:dark){body{background-color:#000!important}}</style></head><body>\n"
    "<h2>HTTPS is required</h2>\n"
    "<p>This is an SSL protected page, please use the HTTPS scheme instead of "
    "the plain HTTP scheme to access this URL.<br />\n"
    "<blockquote>Hint: The URL should starts with <b>https</b>://</blockquote> </p>\n"
    "<hr />\n"
    "Powered By LiteSpeed Web Server<br />\n"
    "</body></html>\n";

static char s_redirectSSL1[] =
    "HTTP/1.0 301 Moved Permanently\r\n"
    "Location: https://";

static char s_redirectSSL2[] =
    "\r\nCache-Control: private, no-cache, max-age=0\r\n"
    "Pragma: no-cache\r\n"
    "Server:LiteSpeed\r\n"
    "Content-Length: 0\r\n"
    "Connection: Close\r\n\r\n";


int NtwkIOLink::get_url_from_reqheader(char *buf, int length, char **puri,
                                       int *uri_len, char **phost, int *host_len)
{
    const char *pBufEnd = (const char *)buf + length;
    if (strncasecmp(buf, "GET ", 4) != 0 &&
        strncasecmp(buf, "POST", 4) != 0 &&
        strncasecmp(buf, "HEAD", 4) != 0)
    {
        LS_DBG_L(this, "get_url_from_reqheader not support method: %.*s.",
                 4, buf);
        return LS_FAIL;
    }

    char *pStart = strcasestr(buf, (const char *)"host:");
    if (!pStart || pBufEnd - pStart < 6)
        return -2;

    *phost = pStart + 5;
    char *pEnd = (char *)memchr(*phost, '\n', pBufEnd - *phost);
    if (!pEnd)
        return -3;

    *pEnd = 0x00;
    *phost = StringTool::strTrim(*phost);
    *host_len = strlen(*phost);

    *puri = buf + 4;
    //Must remove the leading space to avoid pEnd to become it.
    skip_leading_space((const char **)puri);

    //Only search the puri(buf +4) to phost area for space
    pEnd = (char *)memchr(*puri, ' ', *phost - *puri);
    if (!pEnd)
        return -4;

    skip_leading_space((const char **)&pEnd);
    if (strncasecmp(pEnd, "HTTP/", 5) != 0)
        return -5;

    *pEnd = 0x00; //set 0 to end of the puri
    *puri = StringTool::strTrim(*puri);
    *uri_len = strlen(*puri);
    return 0;
}

void NtwkIOLink::handle_acceptSSL_EIO_Err()
{
    //The buf is null terminated string
    char buf[8192 + 1] = {0};
    unsigned int length = 0;
    char *p = m_ssl.getRawBuffer((int *)&length);

    if (p)
    {
        if (length > 8192)
            length = 8192;
        memcpy(buf, p, length);
    }
    else
        length = 0;

    //Normally it is 5 bytes in bssl, 11 bytes in ossl, checking with 8192,
    //just to avoid buffer overflow
    if (length < 8192)
    {
        int ret = ::read(getfd(), buf + length, 8192 - length);
        if (ret > 0)
            length += ret;
    }

    int uri_len = 0, host_len = 0;
    char *puri = NULL, *phost = NULL;
    int rc_parse = get_url_from_reqheader(buf, length, &puri, &uri_len, &phost,
                                          &host_len);
    if (rc_parse == 0)
    {
        iovec iov[4] = {{s_redirectSSL1, sizeof(s_redirectSSL1) - 1},
            {phost, (size_t)host_len},
            {puri, (size_t)uri_len},
            {s_redirectSSL2, sizeof(s_redirectSSL2) - 1}
        };
        ::writev(getfd(), iov, 4);
    }
    else
    {
        LS_DBG_L(this,
                 "SSL_accept() failed!: %s, get_url_from_reqheader return %d.",
                 SslError().what(), rc_parse);
        ::write(getfd(), s_errUseSSL, sizeof(s_errUseSSL) - 1);
    }
}


void NtwkIOLink::enableTlsAccel()
{
    m_ssl.setWriteBuffering(1);
}


int NtwkIOLink::acceptSSL()
{
    int ret = m_ssl.accept();
    if (ret == 1)
    {
        LS_DBG_L(this, "[SSL] accepted!");
        if ((ClientInfo::getPerClientHardLimit() < 1000)
            && (getClientInfo()->getAccess() != AC_TRUST)
            && (getClientInfo()->incSslNewConn() >
                (ClientInfo::getPerClientHardLimit() << 1)))
        {
            LS_WARN(this, "[SSL] Too many new SSL connections: %d, "
                    "possible SSL negociation based attack, block!",
                    getClientInfo()->getSslNewConn());
            getClientInfo()->setOverLimitTime(DateTime::s_curTime);
            getClientInfo()->setAccess(AC_BLOCK);
        }
        else
        {
            enableTlsAccel();
        }

    }
    else if (errno == EIO)
        handle_acceptSSL_EIO_Err();

    return ret;
}


int NtwkIOLink::sslSetupHandler()
{
    unsigned int spdyVer = m_ssl.getSpdyVersion();
    if (spdyVer >= HIOS_PROTO_MAX)
    {
        LS_ERROR(this, "Bad ALPN version: %d, will use HTTP", spdyVer);
        spdyVer = HIOS_PROTO_HTTP;
    }
    else
    {
        LS_DBG_L(this, "ALPN result: %s",
                 getProtocolName((HiosProtocol)spdyVer)->ptr);
        if (spdyVer == HIOS_PROTO_HTTP2
            && getClientInfo()->checkStreamLimit() == -1)
        {
            LS_DBG_L(this, "HTTP2 resource abuse detected, close");
            tobeClosed();
            return -1;
        }
    }
    int ret = setupHandler((HiosProtocol)spdyVer);
    if (!m_iInProcess && isWantRead() && m_ssl.hasPendingIn())
        (*m_pFpList->m_onRead_fp)(this);
    return ret;
}


int NtwkIOLink::SSLAgain()
{
    LS_DBG_L(this, "[SSL] SSLAgain()!");
    int ret = 0;
    switch (m_ssl.getStatus())
    {
    case SslConnection::CONNECTING:
        ret = m_ssl.connect();
        break;
    case SslConnection::ACCEPTING:
        ret = acceptSSL();
        if (ret == 1)
            sslSetupHandler();
        break;
    case SslConnection::SHUTDOWN:
        ret = m_ssl.shutdown(1);
        break;
    case SslConnection::CONNECTED:
        if (m_ssl.lastRead())
        {
            if (getHandler())
                return getHandler()->onReadEx();
            else
                return LS_FAIL;
        }
        if (m_ssl.lastWrite())
            return doWrite();
    }
    switch (ret)
    {
    case 0:
        setSSLAgain();
        break;
    case -1:
        tobeClosed();
        break;
    }
    return ret;

}


int NtwkIOLink::readExSSL_T(LsiSession *pIS, char *pBuf, int size)
{
    NtwkIOLink *pThis = static_cast<NtwkIOLink *>(pIS);
    ThrottleControl *pTC = pThis->getThrottleCtrl();
    int iQuota = pTC->getISQuota();
    if (iQuota <= 0)
    {
        MultiplexerFactory::getMultiplexer()->suspendRead(pThis);
        return 0;
    }
    if (size > iQuota)
        size = iQuota;
    pThis->m_iFlag &= ~NIOL_THROTTLE_READ;
    int ret = pThis->getSSL()->read(pBuf, size);
    pThis->checkSSLReadRet(ret);
    if (ret > 0)
    {
        pTC->useISQuota(ret);
        //::write( 1, pBuf, ret );
        if (!pTC->getISQuota())
        {
            MultiplexerFactory::getMultiplexer()->suspendRead(pThis);
            pThis->m_iFlag |= NIOL_THROTTLE_READ;
        }
    }
    return ret;
}


int NtwkIOLink::writevExSSL_T(LsiSession *pOS, const iovec *vector,
                              int count)
{
    NtwkIOLink *pThis = static_cast<NtwkIOLink *>(pOS);
    ThrottleControl *pCtrl = pThis->getThrottleCtrl();
    int Quota = pCtrl->getOSQuota();
    if (Quota <= pThis->m_iSslLastWrite / 2)
    {
        MultiplexerFactory::getMultiplexer()->suspendWrite(pThis);
        return 0;
    }
    unsigned int allowed = (unsigned int)Quota + (Quota >> 3);
    int ret = 0;
    const struct iovec *vect = vector;
    const struct iovec *pEnd = vector + count;

    //Make OpenSSL happy, not to retry with smaller buffer
    if (Quota < pThis->m_iSslLastWrite)
        Quota = allowed = pThis->m_iSslLastWrite;
    char *pBufEnd;
    char *pCurEnd;
    char achBuf[4096];
    pBufEnd = achBuf + 4096;
    pCurEnd = achBuf;
    for (; ret < Quota && vect < pEnd;)
    {
        const char *pBuf = (const char *) vect->iov_base;
        int bufSize;
        // Use "<=" instead of "<", may get access violation
        // when ret = 0, and allowed = vect->iov_len
        if (vect->iov_len <= allowed - ret)
            bufSize = vect->iov_len;
        else
        {
            bufSize = Quota - ret;
            if (*(pBuf + bufSize) == '\n')
                ++bufSize;
        }
        if (bufSize < 1024)
        {
            if (pBufEnd - pCurEnd > bufSize)
            {
                memmove(pCurEnd, pBuf, bufSize);
                pCurEnd += bufSize;
                ++vect;
                if ((vect < pEnd) && (ret + (pCurEnd - achBuf) < Quota))
                    continue;
            }
            pBuf = achBuf;
            bufSize = pCurEnd - pBuf;
            pCurEnd = achBuf;
        }
        else if (pCurEnd != achBuf)
        {
            pBuf = achBuf;
            bufSize = pCurEnd - pBuf;
            pCurEnd = achBuf;
        }
        else
            ++vect;
        int written = pThis->getSSL()->write(pBuf, bufSize);
        LS_DBG_H(pThis, "Need to write %d bytes, wrote %d bytes.",
                 bufSize, written);
        if (written > 0)
        {
            pThis->bytesSent(written);
            HttpStats::incSSLBytesWritten(written);
            pThis->m_iSslLastWrite = 0;
            pThis->setActiveTime(DateTime::s_curTime);
            ret += written;
            if (written < bufSize)
            {
                pThis->updateSSLEvent();
                break;
            }
        }
        else if (!written)
        {
            pThis->m_iSslLastWrite = bufSize;
            pThis->setSSLAgain();
            break;
        }
        else if (pThis->getState() != HIOS_SHUTDOWN)
        {
            LS_DBG_H(pThis, "SSL error: %s, mark connection to be closed.",
                     SslError().what());
            pThis->setState(HIOS_CLOSING);
            return LS_FAIL;
        }
    }
    if (Quota - ret < 10)
    {
        pCtrl->useOSQuota(Quota);
        MultiplexerFactory::getMultiplexer()->suspendWrite(pThis);
        pThis->m_iFlag |= NIOL_THROTTLE_WRITE;
    }
    else
    {
        pCtrl->useOSQuota(ret);
        pThis->m_iFlag &= ~NIOL_THROTTLE_WRITE;
    }
    return ret;
}


void NtwkIOLink::suspendEventNotify()
{
    if (!MultiplexerFactory::s_iMultiplexerType)
    {
        LS_DBG_L(this, "Remove fd %d from multiplexer!", getfd());
        MultiplexerFactory::getMultiplexer()->remove(this);
    }
}


void NtwkIOLink::resumeEventNotify()
{
    if (!MultiplexerFactory::s_iMultiplexerType)
    {
        LS_DBG_L(this, "Add fd %d back to multiplexer!", getfd());
        MultiplexerFactory::getMultiplexer()->add(this, POLLHUP | POLLERR);
    }
}


static const char *s_pProtoString[] = { "", ":H2" };
const char *NtwkIOLink::buildLogId()
{
    m_logId.len = ls_snprintf(m_logId.ptr, MAX_LOGID_LEN, "%s:%u%s",
                              getClientInfo()->getAddrString(),
                              getConnInfo()->m_remotePort,
                              s_pProtoString[(int)getProtocol() ]);
    return m_logId.ptr;
}


int NtwkIOLink::isFromLocalAddr() const
{
    char achAddr[128];
    socklen_t addrlen = 128;
    if (getsockname(getfd(), (struct sockaddr *) achAddr, &addrlen) == -1)
        return 0;
    const struct sockaddr *pServer = (struct sockaddr *) achAddr;
    const struct sockaddr *pClient =  getClientInfo()->getAddr();
    return (GSockAddr::compareAddr(pServer, pClient) == 0);
}

bool NtwkIOLink::allowWrite() const
{
    return getClientInfo()->allowWrite();
}

bool NtwkIOLink::allowRead() const
{
    return getClientInfo()->allowRead();
}

const char *NtwkIOLink::getPeerAddrString() const
{
    return getClientInfo()->getAddrString();
}

int NtwkIOLink::getPeerAddrStrLen() const
{
    return getClientInfo()->getAddrStrLen();
}

const struct sockaddr *NtwkIOLink::getPeerAddr() const
{
    return getClientInfo()->getAddr();
}

ThrottleControl *NtwkIOLink::getThrottleCtrl() const
{
    return  &(getClientInfo()->getThrottleCtrl());
}

