////////////////////////////////////////////////////////////////////////////////
//                                                                             /
// 2012-2019 (c) Baical                                                        /
//                                                                             /
// This library is free software; you can redistribute it and/or               /
// modify it under the terms of the GNU Lesser General Public                  /
// License as published by the Free Software Foundation; either                /
// version 3.0 of the License, or (at your option) any later version.          /
//                                                                             /
// This library 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           /
// Lesser General Public License for more details.                             /
//                                                                             /
// You should have received a copy of the GNU Lesser General Public            /
// License along with this library.                                            /
//                                                                             /
////////////////////////////////////////////////////////////////////////////////
#include "CommonServer.h"

#if (JOURNAL_MODULE != NULL)
    #pragma message ("ERROR: Journal module is already defined")
#else
    #undef JOURNAL_MODULE
#endif

#define JOURNAL_MODULE m_hLogMod

#define PACKET_PADDING 0xFFF


////////////////////////////////////////////////////////////////////////////////
//CSConnection
 CSConnection::CSConnection(IJournal         *i_pLog, 
                            CBuffers_Pools   *i_pBPool,
                            sockaddr_storage *i_pAddress,
                            tUINT32           i_dwID,
                            tBOOL             i_bBigEndian,
                            tINT32            i_iSocketBufferSize
                           )
    : m_dwReceive_Time(0)
    , m_wClient_ID(0xFFFF)
    , m_dwID(i_dwID)
    , m_pLog(i_pLog)
    , m_hLogMod(NULL)
    , m_dwAddress_Size(0)
    , m_bClosed(FALSE)
    , m_bReinitialized(FALSE)
    , m_bExtensionsSupport(FALSE)
    , m_pBPool(i_pBPool)
    , m_pData_Queue(NULL)
    , m_pData_Wnd(NULL)
    , m_pPacket_Control_Last(NULL)
    , m_pPacket_Control(NULL)
    , m_cPacket_Ack(NULL)
    , m_dwLast_Sent_ID(0)
    , m_dwLast_Recv_ID(0)
    , m_dwData_Offset(0)
    , m_pUser_Data(0)
    , m_eStatus(ESERVER_STATUS_OK)
    , m_dwCreation_Time(0)
    , m_bInitialized(FALSE)
    , m_pList_Cell(NULL)
    , m_dwOut_Packet_Size(0)
    , m_dwOut_Packet_Used(0)
    , m_pOut_Packet_Data(NULL)
    , m_pOut_Packet_Header(NULL)
    , m_qwPull_Count(0)
    , m_bBigEndian(i_bBigEndian)
    , m_iSocketBufferSize(i_iSocketBufferSize)
{
    m_dwCreation_Time = GetTickCount();

    if (m_pLog)
    {
        m_pLog->Add_Ref();
        m_pLog->Register_Module(TM("Srv/Conn"), &m_hLogMod);
    }

    m_cPacket_Ack = new CTPAcknowledgment();

    if (ESERVER_STATUS_OK == m_eStatus)
    {

        LOCK_CREATE(m_hCS_Data);
        LOCK_CREATE(m_hCS_User);
        LOCK_CREATE(m_hCS_Extra); 

        memset(&m_sInfo, 0, sizeof(sP7S_Connection_Info));

        memset(&m_sAddress, 0, sizeof(m_sAddress));
        if (i_pAddress)
        {
            memcpy(&m_sAddress, i_pAddress, sizeof(sockaddr_storage));
        }
        else
        {
            m_eStatus = ESERVER_STATUS_INTERNAL_ERROR;
            JOURNAL_ERROR(m_pLog, TM("[%X] Address is not set"), m_dwID);
        }
    }

    if (ESERVER_STATUS_OK == m_eStatus)
    {
        m_dwOut_Packet_Size = 0x1FFF; //8kb
        m_pOut_Packet_Data = new tUINT8[m_dwOut_Packet_Size];
        if (NULL == m_pOut_Packet_Data)
        {
            m_eStatus = ESERVER_STATUS_INTERNAL_ERROR;
            m_dwOut_Packet_Size = 0;
            JOURNAL_ERROR(m_pLog, TM("[%X] Out packet is not allocated"), m_dwID);
        }
        else
        {
            m_pOut_Packet_Header = (sH_User_Data*)m_pOut_Packet_Data;
            memset(m_pOut_Packet_Header, 0, sizeof(sH_User_Data));
        }
    }

    if (ESERVER_STATUS_OK == m_eStatus)
    {
        m_pData_Queue = new CBList<CTPacket*>;
        if (NULL == m_pData_Queue)
        {
            m_eStatus = ESERVER_STATUS_INTERNAL_ERROR;
            JOURNAL_ERROR(m_pLog, TM("[%X] Data queue is not created"), m_dwID);
        }
    }


    if (ESERVER_STATUS_OK == m_eStatus)
    {
        m_pData_Wnd = new CBList<CTPacket*>;
        if (NULL == m_pData_Wnd)
        {
            m_eStatus = ESERVER_STATUS_INTERNAL_ERROR;
            JOURNAL_ERROR(m_pLog, TM("[%X] Data window is not created"), m_dwID);
        }
    }

    if (ESERVER_STATUS_OK == m_eStatus)
    {
        tXCHAR l_pIP[128] = TM("");
        m_dwAddress_Size  = 0;

        if (!Print_SAddr((sockaddr*)i_pAddress, l_pIP, LENGTH(l_pIP))) 
        {
            PStrCpy(l_pIP, LENGTH(l_pIP), TM("Unknown address format"));
        }

        if (AF_INET == i_pAddress->ss_family)
        {
            m_dwAddress_Size = sizeof(sockaddr_in);
        }
        else if (AF_INET6 == i_pAddress->ss_family)
        {
            m_dwAddress_Size = sizeof(sockaddr_in6);
        }

        JOURNAL_INFO(m_pLog, TM("[%X] Create connection with :%s"), m_dwID, l_pIP);
    }
}//CSConnection


////////////////////////////////////////////////////////////////////////////////
//~CSConnection
CSConnection::~CSConnection()
{
    JOURNAL_INFO(m_pLog, 
                 TM("[%X] On Close: Pull count = %I64d, Data queue len = %d, Window len = %d"), 
                 m_dwID,
                 m_qwPull_Count,
                 m_pData_Queue->Count(),
                 m_pData_Wnd->Count()
                 ); 

    if (m_pData_Queue)
    {
        if (m_pBPool)
        {
            pAList_Cell l_pElement = NULL;

            while ((l_pElement = m_pData_Queue->Get_First()))
            {
                CTPacket *l_pPacket = m_pData_Queue->Get_Data(l_pElement);
                m_pData_Queue->Del(l_pElement, FALSE);
                m_pBPool->Push_Buffer(l_pPacket);
            }

            m_pData_Queue->Clear(TRUE);
        }

        delete m_pData_Queue;
        m_pData_Queue = NULL;
    }

    ////////////////////////////////////////////////////////////////////////////
    LOCK_ENTER(m_hCS_Extra);

    if (m_pBPool)
    {
        pAList_Cell l_pEl = NULL;
        while ((l_pEl = m_cExtra.Get_Next(l_pEl)))
        {
            m_pBPool->Push_Buffer(m_cExtra.Get_Data(l_pEl));
        }
    }

    m_cExtra.Clear(FALSE);

    LOCK_EXIT(m_hCS_Extra);
    ///////////////////////////////////////////////////////////////////

    if (m_pData_Wnd)
    {
        if (m_pBPool)
        {
            pAList_Cell l_pElement = NULL;

            while ((l_pElement = m_pData_Wnd->Get_First()))
            {
                CTPacket *l_pPacket = m_pData_Wnd->Get_Data(l_pElement);
                m_pData_Wnd->Del(l_pElement, FALSE);
                m_pBPool->Push_Buffer(l_pPacket);
            }

            m_pData_Wnd->Clear(TRUE);
        }

        delete m_pData_Wnd;
        m_pData_Wnd = NULL;
    }


    if (m_pLog)
    {
        m_pLog->Release();
        m_pLog = NULL;
    }

    if (m_cPacket_Ack)
    {
        delete m_cPacket_Ack;
        m_cPacket_Ack = NULL;
    }

    if (m_pOut_Packet_Data)
    {
        delete [] m_pOut_Packet_Data;
        m_pOut_Packet_Data = NULL;
    }

    LOCK_DESTROY(m_hCS_Data);
    LOCK_DESTROY(m_hCS_User);
    LOCK_DESTROY(m_hCS_Extra); 
}//~CSConnection


////////////////////////////////////////////////////////////////////////////////
//Get_Status
eServer_Status CSConnection::Get_Status()
{
    return m_eStatus;
}//Get_Status


////////////////////////////////////////////////////////////////////////////////
//Get_Address
sockaddr_storage *CSConnection::Get_Address()
{
    return &m_sAddress;
}//Get_Address


////////////////////////////////////////////////////////////////////////////////
//Get_Address_Size
tUINT32 CSConnection::Get_Address_Size()
{
    return m_dwAddress_Size;
}//Get_Address_Size



////////////////////////////////////////////////////////////////////////////////
//Is_Address
tBOOL CSConnection::Is_Address(sockaddr_storage *i_pAddress)
{
    if (    (NULL == i_pAddress)
         || (i_pAddress->ss_family != m_sAddress.ss_family)
       )
    {
        return FALSE;
    }

    if (    (AF_INET == i_pAddress->ss_family)
         && (((sockaddr_in*)i_pAddress)->sin_port == ((sockaddr_in*)&m_sAddress)->sin_port)
         && (((sockaddr_in*)i_pAddress)->sin_addr.s_addr == ((sockaddr_in*)&m_sAddress)->sin_addr.s_addr)
       )
    {
        return TRUE;
    }
    else if (    (AF_INET6 == i_pAddress->ss_family)
              && (((sockaddr_in6*)i_pAddress)->sin6_port == ((sockaddr_in6*)&m_sAddress)->sin6_port)
              && (0 == memcmp(&((sockaddr_in6*)i_pAddress)->sin6_addr, 
                              &((sockaddr_in6*)&m_sAddress)->sin6_addr, 
                              sizeof(in6_addr)
                             )
                 )
            )
    {
            return TRUE;
    }

    return FALSE;
}//Is_Address


////////////////////////////////////////////////////////////////////////////////
//Process_Incoming_Packet
tBOOL CSConnection::Is_BigEndian()
{
    return m_bBigEndian;
}


////////////////////////////////////////////////////////////////////////////////
//Process_Incoming_Packet
tBOOL CSConnection::Process_Incoming_Packet(CTPacket *i_pPacket)
{
    tBOOL l_bResult    = TRUE;
    tBOOL l_bProcessed = FALSE;

    if (m_pPacket_Control)
    {
        m_pPacket_Control = NULL;
    }

    if (NULL == i_pPacket)
    {
        l_bResult    = FALSE;
        l_bProcessed = TRUE;
    }

    if (FALSE == l_bProcessed)
    {
        //receiving another packet with the same ID mean that client doesn't receive response and we have deliver again
        if (i_pPacket->Get_ID() == m_dwLast_Recv_ID)
        {
            m_pPacket_Control = m_pPacket_Control_Last;
            l_bProcessed = TRUE;

            ATOMIC_SET(&m_dwReceive_Time, GetTickCount());

            JOURNAL_WARNING(m_pLog, TM("[%X] Receive already delivered packet Type=%d, Size=%d, ID=%d"),
                            m_dwID,
                            i_pPacket->Get_Type(),
                            i_pPacket->Get_Size(),
                            i_pPacket->Get_ID()
                            );
        }
        else
        {
            m_pPacket_Control_Last = NULL;
            m_dwLast_Recv_ID       = i_pPacket->Get_ID();
        }
    }
           
    if (    (FALSE == l_bProcessed) 
         && (FALSE == m_bInitialized) 
         && (ETPT_CLIENT_HELLO != i_pPacket->Get_Type())
       )
    {
        l_bProcessed = TRUE;
        l_bResult    = FALSE;

        ATOMIC_SET(&m_dwReceive_Time, GetTickCount());

        Inc_Packet_ID(&m_dwLast_Sent_ID);
        m_pPacket_Control = Create_Ack(i_pPacket->Get_ID(), m_dwLast_Sent_ID, 0, CSConnection::eAckAddData);

        JOURNAL_WARNING(m_pLog, TM("[%X] Receive packet without connection initialization"), m_dwID); 
    }

    if (l_bProcessed) /////////////////////////// cut //////////////////////////////////////////////////////
    {
        goto l_lblExit;
    }

    if (ETPT_CLIENT_DATA == i_pPacket->Get_Type()) ///////////////////////////////////////////////////////// 
    {
        pAList_Cell  l_pWndEl      = NULL;
        tBOOL        l_bDuplicated = FALSE; 
        CTPacket    *l_pWndPacket  = NULL;

        while ((l_pWndEl = m_pData_Wnd->Get_Prev(l_pWndEl)) != NULL)
        {
            l_pWndPacket = m_pData_Wnd->Get_Data(l_pWndEl); 
            if (l_pWndPacket)
            {
                if (i_pPacket->Get_ID() == l_pWndPacket->Get_ID()) //drop duplicated packets
                {
                    l_bDuplicated = TRUE;
                    break;
                }
                else if (i_pPacket->Get_ID() > l_pWndPacket->Get_ID())
                {
                    break;
                }
            }
        }

        if (FALSE == l_bDuplicated)
        {
            m_pData_Wnd->Add_After(l_pWndEl, i_pPacket);
            i_pPacket = NULL;
        }
        else
        {
            l_bResult = FALSE;
        }

        l_bProcessed = TRUE;
        ATOMIC_SET(&m_dwReceive_Time, GetTickCount());
    }
    else if (ETPT_CLIENT_DATA_REPORT == i_pPacket->Get_Type()) /////////////////////////////////////////////
    {
        CTPData_Window l_cClientReport(i_pPacket);

        if (m_bBigEndian)
        {
            l_cClientReport.Chage_Endianness();
        }

        tUINT32     l_dwPacketID    = 0;
        tUINT32     l_dwCountMissed = 0;
        CTPacket   *l_pWndPacket    = NULL;
        pAList_Cell l_pLagoonEl     = m_pData_Wnd->Get_First();


        //Remove all data packets which are older than packets described in 
        //client's report packet. All packet ID are linear - this is responsability
        //of the client.
        while ( (l_pLagoonEl = m_pData_Wnd->Get_First()) != NULL )
        {
            l_pWndPacket = m_pData_Wnd->Get_Data(l_pLagoonEl); 
            if (    (l_pWndPacket) 
                 && (l_pWndPacket->Get_ID() < l_cClientReport.Get_First_ID())
               )
            {
                m_pData_Wnd->Del(l_pLagoonEl, FALSE);
                m_pBPool->Push_Buffer(l_pWndPacket);
            }
            else
            {
                break;
            }
        }

        //JOURNAL_INFO(m_pLog,
        //             TM("[%X] Received client report ID[%d .. %d]"), 
        //             m_dwID, 
        //             l_cClientReport.Get_First_ID(),
        //             l_cClientReport.Get_Last_ID()
        //            );

        m_cPacket_Data_Report.Reset();
        m_cPacket_Data_Report.Clr_IDs();
        l_pLagoonEl = m_pData_Wnd->Get_First();

        //ID sorting. We need just to check one by one
        for (l_dwPacketID = l_cClientReport.Get_First_ID(); 
             l_dwPacketID <= l_cClientReport.Get_Last_ID(); 
             Inc_Packet_ID(&l_dwPacketID)
            )
        {
            l_pWndPacket = m_pData_Wnd->Get_Data(l_pLagoonEl); 
            if (    (l_pWndPacket)
                 && (l_pWndPacket->Get_ID() == l_dwPacketID)
               )
            {
                l_pLagoonEl = m_pData_Wnd->Get_Next(l_pLagoonEl);
            }
            else
            {
                JOURNAL_DEBUG(m_pLog,
                              TM("[%X] Miss data packet, Expected ID = %d, Received ID = %d"),
                              m_dwID,
                              l_dwPacketID,
                              l_pWndPacket ? l_pWndPacket->Get_ID() :0x0);

                m_cPacket_Data_Report.Set_ID(l_cClientReport.Get_First_ID(), l_dwPacketID);
                l_dwCountMissed ++;
            }
        }

        Inc_Packet_ID(&m_dwLast_Sent_ID);

        if (l_dwCountMissed)
        {
            JOURNAL_WARNING(m_pLog,
                            TM("[%X] Data missed count = %d, received = %d"), 
                            m_dwID, 
                            l_dwCountMissed,
                            m_pData_Wnd->Count()
                           );

            m_cPacket_Data_Report.Fill(i_pPacket->Get_ID());
            m_cPacket_Data_Report.Finalize(m_dwLast_Sent_ID, m_wClient_ID, m_bBigEndian);
            m_pPacket_Control = &m_cPacket_Data_Report;
        }
        else
        {
            while ((l_pLagoonEl = m_pData_Wnd->Get_First()) != NULL)
            {
                l_pWndPacket = m_pData_Wnd->Get_Data(l_pLagoonEl); 
                //delete data belonging to range described by report
                if (    (l_pWndPacket)
                     && (l_pWndPacket->Get_ID() >= l_cClientReport.Get_First_ID()) 
                     && (l_pWndPacket->Get_ID() <= l_cClientReport.Get_Last_ID()) 
                   )
                {
                    m_pData_Wnd->Del(l_pLagoonEl, FALSE);
                    Push_Last_Data_Packet(l_pWndPacket);
                }
            }

            m_pPacket_Control = Create_Ack(i_pPacket->Get_ID(), m_dwLast_Sent_ID, 1, CSConnection::eAckAddData);
        }

        l_bProcessed = TRUE;
        ATOMIC_SET(&m_dwReceive_Time, GetTickCount());
    }
    else if (ETPT_CLIENT_HELLO == i_pPacket->Get_Type()) 
    {
        CTPClient_Hello l_cInitial(i_pPacket);

        if (m_bBigEndian)
        {
            l_cInitial.Chage_Endianness();
        }

        if (CLIENT_PROTOCOL_VERSION == l_cInitial.Get_Protocol_Version())
        {
            pAList_Cell l_pElement = NULL;
            CTPacket   *l_pPacket  = NULL;

            if (TPACKET_FLAG_EXTENSION & l_cInitial.Get_Flags())
            {
                m_bExtensionsSupport = TRUE;
            }
            else
            {
                m_bExtensionsSupport = FALSE;
            }

            if (m_bInitialized)
            {
                LOCK_ENTER(m_hCS_User); 
                m_bReinitialized = TRUE;
                LOCK_EXIT(m_hCS_User); 

                JOURNAL_WARNING(m_pLog, TM("[%X] Reinitialize connection"), m_dwID); 
            }
            else
            {
                JOURNAL_INFO(m_pLog, TM("[%X] Initialize connection"), m_dwID); 
            }

            m_bInitialized   = TRUE;
            m_dwLast_Recv_ID = 0;

            l_pElement = NULL;
            while ((l_pElement = m_pData_Wnd->Get_First()))
            {
                l_pPacket = m_pData_Wnd->Get_Data(l_pElement);
                m_pData_Wnd->Del(l_pElement, FALSE);
                m_pBPool->Push_Buffer(l_pPacket);
            }

            if (m_pPacket_Control_Last)
            {
                m_pPacket_Control_Last = NULL;
            }


            LOCK_ENTER(m_hCS_User); 

            m_dwOut_Packet_Used = 0;
            if (m_pOut_Packet_Header)
            {
                memset(m_pOut_Packet_Header, 0, sizeof(sH_User_Data));
            }

            m_dwData_Offset = 0;
            while ((l_pElement = m_pData_Queue->Get_First())) //flush all received data
            {
                l_pPacket = m_pData_Queue->Get_Data(l_pElement);
                m_pData_Queue->Del(l_pElement, FALSE);
                m_pBPool->Push_Buffer(l_pPacket);
            }

            m_sInfo.wProtocol_Version  = l_cInitial.Get_Protocol_Version();
            m_sInfo.dwProcess_ID       = l_cInitial.Get_Process_ID();
            m_sInfo.dwProcess_Time_Hi  = l_cInitial.Get_Process_Time_Hi();
            m_sInfo.dwProcess_Time_Low = l_cInitial.Get_Process_Time_Low();

            //recreate ack basing on max data size supported by client
            if (m_cPacket_Ack)
            {
                delete m_cPacket_Ack;
            }

            m_cPacket_Ack = new CTPAcknowledgment(l_cInitial.Get_Data_Max_Size(), 0);


            memcpy(&m_sInfo.sAddress, &m_sAddress, sizeof(m_sAddress));

        #ifdef UTF8_ENCODING
            Convert_UTF16_To_UTF8(l_cInitial.Get_Process_Name(),
                                  m_sInfo.pProcess_Name,
                                  LENGTH(m_sInfo.pProcess_Name)
                                 );
        #else
            PStrCpy(m_sInfo.pProcess_Name,
                    LENGTH(m_sInfo.pProcess_Name),
                    (const tXCHAR*)l_cInitial.Get_Process_Name()
                   );
        #endif

            LOCK_EXIT(m_hCS_User); 


            Inc_Packet_ID(&m_dwLast_Sent_ID);
            m_pPacket_Control = Create_Ack(i_pPacket->Get_ID(), m_dwLast_Sent_ID, 1, CSConnection::eAckAddExtraSrvInfo);

            l_bProcessed = TRUE;
            ATOMIC_SET(&m_dwReceive_Time, GetTickCount());
        }
        else
        {
            JOURNAL_ERROR(m_pLog, 
                          TM("[%X] Protocol version is wrong, Expect = %d, Receive = %d"), 
                          m_dwID,
                          (tUINT32)CLIENT_PROTOCOL_VERSION,
                          (tUINT32)l_cInitial.Get_Protocol_Version()
                         ); 
        }

    }
    else if (ETPT_CLIENT_PING == i_pPacket->Get_Type()) ////////////////////////////////////////////////////
    {
        Inc_Packet_ID(&m_dwLast_Sent_ID);
        m_pPacket_Control = Create_Ack(i_pPacket->Get_ID(), m_dwLast_Sent_ID, 1, CSConnection::eAckAddData);

        ATOMIC_SET(&m_dwReceive_Time, GetTickCount());
        l_bProcessed = TRUE;
    }
    else if (ETPT_CLIENT_BYE == i_pPacket->Get_Type())//////////////////////////////////////////////////////
    {
        //Set connection to OFF
        //ATOMIC_SET(&m_dwReceive_Time, 0);
        LOCK_ENTER(m_hCS_User); 
        m_bClosed    = TRUE;
        LOCK_EXIT(m_hCS_User); 

        l_bProcessed = TRUE;

        JOURNAL_INFO(m_pLog,
                     TM("[%X] Close connection, Client ID = %d"), 
                     m_dwID,
                     (tUINT32)m_wClient_ID
                     ); 
    }



l_lblExit:
    if (i_pPacket)
    {
        m_pBPool->Push_Buffer(i_pPacket);
    }

    return l_bResult;
}//Process_Incoming_Packet


////////////////////////////////////////////////////////////////////////////////
//Get_Output_Packet
const tUINT8 *CSConnection::Get_Output_Packet(tUINT32 &o_rSize)
{
    tUINT8 *l_pReturn = NULL;
    o_rSize = 0;

    if (m_pPacket_Control)
    {
        l_pReturn = m_pPacket_Control->Get_Buffer();
        o_rSize = (m_bBigEndian) ? ntohs(m_pPacket_Control->Get_Size()) : m_pPacket_Control->Get_Size();

        //if (!m_bBigEndian)
        //{
        //    PACKET_PRINT(m_pLog, TM("[OUT]"), m_pPacket_Control);
        //}

        m_pPacket_Control_Last = m_pPacket_Control;
        m_pPacket_Control      = NULL;
    }

    return l_pReturn;
}//Get_Output_Packet


////////////////////////////////////////////////////////////////////////////////
//Get_Info
tBOOL CSConnection::Get_Info(sP7S_Connection_Info *o_pInfo)
{
    if (NULL == o_pInfo)
    {
        return FALSE;
    }

    LOCK_ENTER(m_hCS_User); 
    memcpy(o_pInfo, &m_sInfo, sizeof(m_sInfo));
    LOCK_EXIT(m_hCS_User); 

    return TRUE;
}//Get_Info


////////////////////////////////////////////////////////////////////////////////
//Get_Status
tBOOL CSConnection::Get_Status(sP7S_Connection_Status *o_pStatus)
{
    if (NULL == o_pStatus)
    {
        return FALSE;
    }

    LOCK_ENTER(m_hCS_User); 

    o_pStatus->dwDuration_Life = CTicks::Difference(GetTickCount(), m_dwCreation_Time);
    o_pStatus->dwDuration_Off  = CTicks::Difference(GetTickCount(), m_dwReceive_Time);

    if (o_pStatus->dwDuration_Off >= SERVER_CONNECTION_TIMEOUT)
    {
        o_pStatus->bConnected = FALSE;
    }
    else
    {
        o_pStatus->bConnected     = TRUE;
        o_pStatus->dwDuration_Off = 0;
    }

    o_pStatus->bReinitialized = m_bReinitialized;
    o_pStatus->bClosed        = m_bClosed;
    o_pStatus->bHas_Data      = (0 < m_pData_Queue->Count());


    if (m_bReinitialized)
    {
        m_bReinitialized = FALSE;
    }

    LOCK_EXIT(m_hCS_User); 

    return TRUE;
}//Get_Status


////////////////////////////////////////////////////////////////////////////////
//Set_User_Data
void CSConnection::Set_User_Data(void *i_pData)
{
    LOCK_ENTER(m_hCS_User); 
    m_pUser_Data = i_pData;
    LOCK_EXIT(m_hCS_User); 
}//Set_User_Data


////////////////////////////////////////////////////////////////////////////////
//Get_User_Data
void *CSConnection::Get_User_Data()
{
    void *l_pReturn = NULL;
    LOCK_ENTER(m_hCS_User); 
    l_pReturn = m_pUser_Data;
    LOCK_EXIT(m_hCS_User); 

    return l_pReturn;
}//Get_User_Data


////////////////////////////////////////////////////////////////////////////////
//Push_Packet
tBOOL CSConnection::Push_Data(tUINT32       i_dwChannel_ID,
                              const tUINT8 *i_pData, 
                              tUINT32       i_dwSize
                             )
{
    tBOOL     l_bResult    = TRUE;
    tUINT8   *l_pData      = NULL;
    //extra packet is packet at the tail of the Ack packet, so this is why max size limitation is looking like that
    tUINT32   l_dwMax_Len  = m_cPacket_Ack->Get_Buffer_Size() - ACKNOWLEDGMENT_SIZE;
    tUINT32   l_qwRequired = i_dwSize + sizeof(sH_User_Data);
    CTPacket *l_pExtra     = NULL;
    CTPData   l_cData(NULL);

    if (m_bExtensionsSupport)
    {
        l_qwRequired += sizeof(sH_Ext);
    }

    if (l_qwRequired >= l_dwMax_Len)
    {
        JOURNAL_ERROR(m_pLog, 
                      TM("[%X] Try to send large extra packet (%d)"),
                      m_dwID,
                      i_dwSize
                     );
        return FALSE;
    }

    LOCK_ENTER(m_hCS_Extra);

    if (SERVER_EXTRA_PER_CONNECTION < m_cExtra.Count())
    {
        JOURNAL_ERROR(m_pLog, 
                      TM("[%X] m_cExtra.Count() is more than limit (%d)"),
                      m_dwID,
                      SERVER_EXTRA_PER_CONNECTION
                     );
        l_bResult = FALSE;
        goto l_lExit;
    }

    //can we add it to last extra packet in addition:
    l_pExtra = m_cExtra.Get_Data(m_cExtra.Get_Last());
    if (l_pExtra)
    {
        l_cData.Attach(l_pExtra);
        if (l_cData.Get_Tail_Size() <= l_qwRequired)
        {
            l_pExtra = NULL;
            l_cData.Detach();
        }
    }

    //if in last packet there is no free space - create new one
    if (NULL == l_pExtra)
    {
        l_pExtra = m_pBPool->Pull_Buffer(l_dwMax_Len);

        //if Pool is empty - sometimes it happens due to heavy network load, in such case
        //create packed dynamically, pool will delete it when packet will be released
        if (!l_pExtra) 
        {
            l_pExtra = new CTPacket(l_dwMax_Len, m_pBPool->Get_Dynamic_Pool_Id()); 
        }

        if (l_pExtra)
        {
            m_cExtra.Add_After(m_cExtra.Get_Last(), l_pExtra);
            l_cData.Attach(l_pExtra);
            l_cData.Initialize();
        }
    }

    if (NULL == l_pExtra)
    {
        l_bResult = FALSE;
        goto l_lExit;
    }

    l_pData = l_cData.Get_Tail();

    if (m_bExtensionsSupport)
    {
        sH_Ext *l_pExt = (sH_Ext *)l_pData;

        if (m_bBigEndian)
        {
            l_pExt->wSize = ntohs((tUINT16)l_qwRequired);
            l_pExt->wType = ntohs((tUINT16)ETPE_USER_DATA);
        }
        else
        {
            l_pExt->wSize = (tUINT16)l_qwRequired;
            l_pExt->wType = (tUINT16)ETPE_USER_DATA;
        }

        l_pData += sizeof(sH_Ext);
    }

    //((sH_User_Data *)l_pData)->dwChannel_ID = i_dwChannel_ID;
    //((sH_User_Data *)l_pData)->dwSize       = i_dwSize + sizeof(sH_User_Data);
    ((sH_User_Raw *)l_pData)->dwBits = INIT_USER_HEADER((i_dwSize + sizeof(sH_User_Data)), i_dwChannel_ID);

    if (m_bBigEndian)
    {
        ((sH_User_Raw *)l_pData)->dwBits = ntohl(((sH_User_Raw *)l_pData)->dwBits);
    }

    l_pData += sizeof(sH_User_Data);

    memcpy(l_pData, i_pData, i_dwSize);
    l_cData.Append_Size(l_qwRequired);

l_lExit:
    LOCK_EXIT(m_hCS_Extra); 

    return l_bResult;
}//Push_Packet


////////////////////////////////////////////////////////////////////////////////
//Pull_Packet
CTPacket *CSConnection::Pull_Packet()
{
    return Pull_Firt_Data_Packet();
}

////////////////////////////////////////////////////////////////////////////////
//Release_Packet
void CSConnection::Release_Packet(CTPacket *i_pPacket)
{
    if (i_pPacket)
    {
        m_pBPool->Push_Buffer(i_pPacket);
    }
}



////////////////////////////////////////////////////////////////////////////////
//Set_Cell
void CSConnection::Set_Cell(pAList_Cell i_pCell)
{
    m_pList_Cell = i_pCell;
}//Set_Cell


////////////////////////////////////////////////////////////////////////////////
//Get_Cell
pAList_Cell CSConnection::Get_Cell()
{
    return m_pList_Cell;
}//Get_Cell


////////////////////////////////////////////////////////////////////////////////
//Get_ID
tUINT32 CSConnection::Get_ID()
{
    return m_dwID;
} //Get_ID


////////////////////////////////////////////////////////////////////////////////
//Get_Client_ID
tUINT16 CSConnection::Get_Client_ID()
{
    return m_wClient_ID;
}//Get_Client_ID


////////////////////////////////////////////////////////////////////////////////
//Set_Client_ID
void CSConnection::Set_Client_ID(tUINT16 i_wClient_ID)
{
    m_wClient_ID = i_wClient_ID;
}//Set_Client_ID


////////////////////////////////////////////////////////////////////////////////
//Print
void CSConnection::Print()
{
    JOURNAL_INFO(m_pLog, 
                 TM("[%X] Active connection, ClientID = %u, Waiting = %u, Closed = %u, Data Count = %u, Window length = %u"), 
                 (tUINT32)m_dwID,
                 (tUINT32)m_wClient_ID,
                 CTicks::Difference(GetTickCount(), m_dwReceive_Time),
                 (tUINT32)m_bClosed,
                 m_pData_Queue->Count(),
                 m_pData_Wnd->Count()
                );
}//Print


////////////////////////////////////////////////////////////////////////////////
//Pull_Firt_Data_Packet
CTPacket *CSConnection::Pull_Firt_Data_Packet()
{
    CTPacket * l_pResult = NULL;
    
    LOCK_ENTER(m_hCS_Data); 

    pAList_Cell l_pElement = m_pData_Queue->Get_First();
    if (l_pElement)
    {
        l_pResult = m_pData_Queue->Get_Data(l_pElement);
        m_pData_Queue->Del(l_pElement, FALSE);
    }

    LOCK_EXIT(m_hCS_Data); 

    return l_pResult;
}//Pull_Firt_Data_Packet


////////////////////////////////////////////////////////////////////////////////
//Push_First_Data_Packet
tBOOL CSConnection::Push_First_Data_Packet(CTPacket *i_pPacket)
{
    if (NULL == i_pPacket)
    {
        return FALSE;
    }

    LOCK_ENTER(m_hCS_Data); 

    m_pData_Queue->Add_After(NULL, i_pPacket);

    LOCK_EXIT(m_hCS_Data); 

    return TRUE;
}//Push_First_Data_Packet


////////////////////////////////////////////////////////////////////////////////
//Push_Last_Data_Packet
tBOOL CSConnection::Push_Last_Data_Packet(CTPacket *i_pPacket)
{
    if (NULL == i_pPacket)
    {
        return FALSE;
    }

    LOCK_ENTER(m_hCS_Data); 

    m_pData_Queue->Add_After(m_pData_Queue->Get_Last(), i_pPacket);

    LOCK_EXIT(m_hCS_Data); 

    return TRUE;
}//Push_Last_Data_Packet


////////////////////////////////////////////////////////////////////////////////
//Inc_Thread_Safe
void CSConnection::Inc_Thread_Safe(tUINT32 *io_pValue, tUINT32 i_dwIncrement)
{
    if (NULL == io_pValue)
    {
        return;
    }

    LOCK_ENTER(m_hCS_User); 
    (*io_pValue) += i_dwIncrement; 
    LOCK_EXIT(m_hCS_User); 
}//Inc_Thread_Safe


////////////////////////////////////////////////////////////////////////////////
//Inc_Packet_ID
void CSConnection::Inc_Packet_ID(tUINT32 *i_pPacketID)
{
    ++(*i_pPacketID);
    if ( TPACKET_MAX_ID < (*i_pPacketID) )
    {
        (*i_pPacketID) = 1;
    }
}//Inc_Packet_ID


////////////////////////////////////////////////////////////////////////////////
//Create_Ack
CTPacket *CSConnection::Create_Ack(tUINT32    i_dwSource_ID,
                                   tUINT32    i_dwPacket_ID, 
                                   tUINT16    i_wResult,
                                   eAckAction i_eAction
                                  )
{
    CTPacket *l_pResult = m_cPacket_Ack;

    m_cPacket_Ack->Reset();
    m_cPacket_Ack->Fill(i_dwSource_ID, i_wResult);
    m_cPacket_Ack->Clr_Extra();

    if (CSConnection::eAckAddData == i_eAction)
    {
        LOCK_ENTER(m_hCS_Extra);

        if (m_cExtra.Count())
        {
            pAList_Cell l_pEl    = m_cExtra.Get_First();
            CTPData     l_cData(m_cExtra.Get_Data(l_pEl));

            m_cPacket_Ack->Add_Extra(l_cData.Get_Data(), 
                                     l_cData.Get_Data_Size(),
                                     m_bExtensionsSupport ? (TPACKET_FLAG_EXTENSION | TPACKET_FLAG_EXTRA_DATA) : TPACKET_FLAG_EXTRA_DATA
                                    );

            m_pBPool->Push_Buffer(m_cExtra.Get_Data(l_pEl));
            m_cExtra.Del(l_pEl, FALSE);
        }

        LOCK_EXIT(m_hCS_Extra); 
    }
    else if (CSConnection::eAckAddExtraSrvInfo == i_eAction)
    {
        if (m_bExtensionsSupport)
        {
            CTPESrv_Info l_cInfo;
            size_t       l_szInfo = l_cInfo.GetBufferSize();

            l_cInfo.Fill(0, CLIENT_PROTOCOL_VERSION, 0, (tUINT32)m_iSocketBufferSize);

            if (m_bBigEndian)
            {
                l_cInfo.Chage_Endianness();
            }

            m_cPacket_Ack->Add_Extra(l_cInfo.GetBuffer(), (tUINT16)l_szInfo, TPACKET_FLAG_EXTENSION);
        }
    }

    m_cPacket_Ack->Finalize(i_dwPacket_ID, m_wClient_ID, m_bBigEndian);

    return l_pResult;
}
