//+---------------------------------------------------------------------------
//
//  Copyright (C) Microsoft Corporation, 1999.
//
//  File:       E V E N T S R V . C P P
//
//  Contents:   UPnP GENA server.
//
//  Notes:
//
//  Author:     Ting Cai   Dec. 1999
//
//  Email:      tingcai@microsoft.com
//
//----------------------------------------------------------------------------

#include <pch.h>
#pragma hdrstop

#include <winsock2.h>
#include "wininet.h"
#include "eventsrv.h"
#include "ssdpfunc.h"
#include "ssdptypes.h"
#include "ssdpnetwork.h"
#include "ncbase.h"
#define LISTEN_BACKLOG  5


VOID ProcessSsdpRequest(PSSDP_REQUEST pSsdpRequest, RECEIVE_DATA *pData);

static LIST_ENTRY g_listOpenConn;
static CRITICAL_SECTION g_csListOpenConn;

static int  g_cOpenConnections;
static long g_cMaxOpenConnections = 150;

static const long c_cMaxOpenDefault = 150;          // default maximum
static const long c_cMaxOpenMin = 5;                // absolute minimum
static const long c_cMaxOpenMax = 1500;             // absolute maximum

static long  cQueuedAccepts = 0;


static SOCKET HttpSocket;
LONG bCreated = 0;

VOID InitializeListOpenConn()
{

    HKEY    hkey;
    DWORD dwMaxConns = c_cMaxOpenDefault;

    if (ERROR_SUCCESS == RegOpenKeyEx(HKEY_LOCAL_MACHINE,
                                      "SYSTEM\\CurrentControlSet\\Services"
                                      "\\SSDPSRV\\Parameters", 0,
                                      KEY_READ, &hkey))
    {
        DWORD   cbSize = sizeof(DWORD);

        // ignore failure. In that case, we'll use default
        (VOID) RegQueryValueEx(hkey, "MaxEventConnects", NULL, NULL, (BYTE *)&dwMaxConns, &cbSize);

        RegCloseKey(hkey);
    }

    dwMaxConns = max(dwMaxConns, c_cMaxOpenMin);
    dwMaxConns = min(dwMaxConns, c_cMaxOpenMax);
    g_cMaxOpenConnections = dwMaxConns;

    InitializeCriticalSection(&g_csListOpenConn);
    EnterCriticalSection(&g_csListOpenConn);
    InitializeListHead(&g_listOpenConn);
    g_cOpenConnections = 0;
    LeaveCriticalSection(&g_csListOpenConn);

    TraceTag(ttidEventServer, "Initializing Max Connections %d ", g_cOpenConnections);
}

VOID FreeOpenConnection(POPEN_TCP_CONN pOpenConn)
{
    Assert(OPEN_TCP_CONN_SIGNATURE == (pOpenConn->iType));

    FreeSsdpRequest(&pOpenConn->ssdpRequest);
    free(pOpenConn->szData);
    pOpenConn->szData = NULL;
    pOpenConn->state = CONNECTION_INIT;
    pOpenConn->cbData = 0;
    pOpenConn->cbHeaders = 0;
}

VOID CloseOpenConnection(SOCKET socketPeer)
{
    closesocket(socketPeer);
    RemoveOpenConn(socketPeer);
    
}
POPEN_TCP_CONN CreateOpenConnection(SOCKET socketPeer)
{
    POPEN_TCP_CONN pOpenConn = (POPEN_TCP_CONN) malloc(sizeof(OPEN_TCP_CONN));

    if (pOpenConn == NULL)
    {
        TraceTag(ttidEventServer, "Couldn't allocate memory for %d", socketPeer);
        return NULL;
    }
    pOpenConn->iType = OPEN_TCP_CONN_SIGNATURE;
    pOpenConn->socketPeer = socketPeer;
    pOpenConn->szData = NULL;
    pOpenConn->state = CONNECTION_INIT;
    pOpenConn->cbData = 0;
    pOpenConn->cbHeaders = 0;

    InitializeSsdpRequest(&pOpenConn->ssdpRequest);

    return pOpenConn;
}

VOID AddToListOpenConn(POPEN_TCP_CONN pOpenConn)
{
    EnterCriticalSection(&g_csListOpenConn);
    InsertHeadList(&g_listOpenConn, &(pOpenConn->linkage));
    g_cOpenConnections++;
    LeaveCriticalSection(&g_csListOpenConn);
    TraceTag(ttidEventServer, "AddToListOpenConn - Connections %d ", g_cOpenConnections);
}

VOID CleanupListOpenConn()
{
    PLIST_ENTRY p;
    PLIST_ENTRY pListHead = &g_listOpenConn;

    TraceTag(ttidEventServer, "----- Cleanup Open Connection List -----");

    EnterCriticalSection(&g_csListOpenConn);
    for (p = pListHead->Flink; p != pListHead;)
    {

        POPEN_TCP_CONN pOpenConn;

        pOpenConn = CONTAINING_RECORD (p, OPEN_TCP_CONN, linkage);

        p = p->Flink;

        TraceTag(ttidEventServer, "Removing Open Conn %x -----", pOpenConn);

        RemoveEntryList(&(pOpenConn->linkage));
        g_cOpenConnections--;
        TraceTag(ttidEventServer, "CleanupListOpenConn - Connections %d ", g_cOpenConnections);
        closesocket(pOpenConn->socketPeer);

        FreeOpenConnection(pOpenConn);

        // just to be sure we don't use this again
        pOpenConn->iType = -1;

        free(pOpenConn);
    }

    LeaveCriticalSection(&g_csListOpenConn);
    DeleteCriticalSection(&g_csListOpenConn);

    TraceTag(ttidEventServer, "----- Finished Cleanup Open Connection List -----");
}

// Pre-condition: WSAStartup was successful.
// Post-Condtion: HttpSocket is created. GetNetworks can proceed.

SOCKET CreateHttpSocket()
{
    SOCKADDR_IN sockaddrLocal;
    int iRet;

    HttpSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (HttpSocket == INVALID_SOCKET)
    {
        TraceTag(ttidEventServer, "Failed to create http socket. Error code (%d).", GetLastError());
        return INVALID_SOCKET;
    }

    // Bind

    sockaddrLocal.sin_family = AF_INET;
    sockaddrLocal.sin_addr.s_addr = INADDR_ANY;
    sockaddrLocal.sin_port = htons(EVENT_PORT);

    iRet = bind(HttpSocket, (struct sockaddr *)&sockaddrLocal, sizeof(sockaddrLocal));
    if (iRet == SOCKET_ERROR)
    {
        TraceTag(ttidEventServer, "Failed to bind http socket. Error code (%d).", GetLastError());
        closesocket(HttpSocket);
        HttpSocket = INVALID_SOCKET;
        return INVALID_SOCKET;
    }

    InterlockedIncrement(&bCreated);

    return HttpSocket;
}

INT StartHttpServer(SOCKET HttpSocket, HWND hWnd, u_int wMsg)
{
    INT iRet;

    iRet = listen(HttpSocket, LISTEN_BACKLOG);
    if (iRet == SOCKET_ERROR)
    {
        iRet = GetLastError();
        closesocket(HttpSocket);
        HttpSocket = INVALID_SOCKET;
        TraceTag(ttidEventServer, "Failed to listen on http socket.  Error code (%d).", iRet);
        return iRet;
    }

    iRet = WSAAsyncSelect(HttpSocket, hWnd, wMsg, FD_ACCEPT | FD_CONNECT | FD_READ | FD_CLOSE);

    if (iRet == SOCKET_ERROR)
    {
        iRet = GetLastError();
        closesocket(HttpSocket);
        HttpSocket = INVALID_SOCKET;
        TraceTag(ttidEventServer, "----- select failed with error code %d -----", iRet);
        return iRet;
    }
    else
    {
        TraceTag(ttidEventServer, "Ready to accept tcp connections.");
        return 0;
    }
}

VOID CleanupHttpSocket()
{
    if (InterlockedExchange(&bCreated, bCreated) != 0)
    {
        if (HttpSocket != INVALID_SOCKET)
        {
            closesocket(HttpSocket);
        }
    }
}

VOID DoAccept(SOCKET socket)
{
    SOCKADDR_IN sockaddrFrom;
    SOCKET socketPeer;
    int iLen;
    POPEN_TCP_CONN pOpenTcpConn;

    iLen = sizeof(SOCKADDR_IN);

    Assert(socket == HttpSocket);

    // AcceptEx
    socketPeer = accept(socket, (LPSOCKADDR)&sockaddrFrom, &iLen);
    TraceTag(ttidEventServer, "DoAccept - Before Adding to List Connections %d ", g_cOpenConnections);
    if (socketPeer == SOCKET_ERROR)
    {
        TraceTag(ttidEventServer, "----- accept failed with error code %d -----", GetLastError());
        return;
    }

    pOpenTcpConn = CreateOpenConnection(socketPeer);

    if (pOpenTcpConn)
    {
        AddToListOpenConn(pOpenTcpConn);
    }
    else
    {
        TraceError("Couldn't add new connection. Out of memory!",
                   E_OUTOFMEMORY);
    }
}

VOID DelayAccept()
{
    InterlockedIncrement(&cQueuedAccepts);
    TraceTag(ttidEventServer, "----- DelayAccept %d -----", cQueuedAccepts);
}
VOID DoDelayedAccept()
{
    InterlockedDecrement(&cQueuedAccepts);
    TraceTag(ttidEventServer, "----- DoDelayedAccept %d -----", cQueuedAccepts);

    DoAccept(HttpSocket);
}


VOID HandleAccept(SOCKET socket)
{
    if ((g_cOpenConnections > g_cMaxOpenConnections) && (socket == HttpSocket))
    {
        DelayAccept();
    }
    else
    {
        DoAccept(socket);
    }
}


// Pre-Condition:
// The cs for open connection list is held

BOOL FProcessTcpReceiveBuffer(POPEN_TCP_CONN pOpenConn, RECEIVE_DATA *pData)
{
    Assert(pOpenConn);
    Assert(pData);
    Assert(pData->szBuffer);
    Assert(OPEN_TCP_CONN_SIGNATURE == (pOpenConn->iType));

    int iLen = 1;
    CHAR *szBuf = NULL;
    CHAR *pCurrent;
    CHAR *szHeaders;
    BOOL fNeedToLeave = TRUE;

   TraceTag(ttidEventServer, "Partying on pOpenConn 0x%08X, pData->szBuffer='%s'", pOpenConn, pData->szBuffer);


    if ( pOpenConn->cbData > MAX_EVENT_BUF_THROTTLE_SIZE ) {
        
        pOpenConn->state = CONNECTION_ERROR_FORCED_CLOSE;

        SocketSendErrorResponse(pData->socket, HTTP_STATUS_BAD_REQUEST);
         // Gracefully shutdown. Open Conn will be removed in FD_CLOSe
        shutdown(pData->socket, SD_SEND);  
        TraceTag(ttidEventServer, "FProcessTcpReceiveBuffer - Exceeds MAX_EVENT_BUF_THROTTLE_SIZE");
        
         // Try to tear down the connection..
    }
    else {
    // Accumulate data
    iLen += strlen(pData->szBuffer);

    if (pOpenConn->szData != NULL)
    {
        iLen += strlen(pOpenConn->szData);
    }

    szBuf = (CHAR *) malloc(iLen * sizeof(CHAR));

    if (!szBuf)
    {
        TraceError("FProcessTcpReceiveBuffer", E_OUTOFMEMORY);
        return FALSE;
    }

    szBuf[0] = '\0';

    if (pOpenConn->szData)
    {
        strcpy(szBuf, pOpenConn->szData);
        free(pOpenConn->szData);
    }
    strcat(szBuf, pData->szBuffer);

    pOpenConn->cbData += pData->cbBuffer;

    pOpenConn->szData = szBuf;

    }
       TraceTag(ttidEventServer, "FProcessTcpReceiveBuffer - Buff Recv %d",pOpenConn->cbData);
    switch (pOpenConn->state)
    {
    case CONNECTION_INIT:
        pCurrent = IsHeadersComplete(pOpenConn->szData);
        if((pCurrent == NULL) && pOpenConn->cbData > MAX_EVENT_NOTIFY_HEADER_THROTTLE_SIZE )
        {
            pOpenConn->state = CONNECTION_ERROR_FORCED_CLOSE;

            SocketSendErrorResponse(pData->socket, HTTP_STATUS_BAD_REQUEST);
            // Gracefully shutdown. Open Conn will be removed in FD_CLOSe
            shutdown(pData->socket, SD_SEND);  
            TraceTag(ttidEventServer, "FProcessTcpReceiveBuffer - Exceeds MAX_EVENT_NOTIFY_HEADER_THROTTLE_SIZE");
        
         // Try to tear down the connection..    
        }
        if ( pCurrent != NULL)
        {
            pOpenConn->cbHeaders = (DWORD)(pCurrent - (pOpenConn->szData)) + 4;

            szHeaders = ParseRequestLine(pOpenConn->szData, &(pOpenConn->ssdpRequest));
            if ((szHeaders != NULL) && ( pOpenConn->ssdpRequest.Method == SSDP_NOTIFY ))
            {
                CHAR *szContent;

                szContent = ParseHeaders(szHeaders, &(pOpenConn->ssdpRequest));
                if (szContent == NULL)
                {
                     TraceTag(ttidEventServer, "ParseHeaders returned NULL for socket %d",
                              pOpenConn->socketPeer);

                    // We've reached a terminal error.  Since there might be
                    // other received data for this connection already in the
                    // queue, transition to this error state, so that we know
                    // not to process any more data.
                    pOpenConn->state = CONNECTION_ERROR_CLOSING;

                    FreeOpenConnection(pOpenConn);

                    SocketSendErrorResponse(pData->socket, HTTP_STATUS_BAD_REQUEST);
                    // Gracefully shutdown. Open Conn will be removed in FD_CLOSe
                    shutdown(pData->socket, SD_SEND);
                }
                else
                {
                    if (VerifySsdpHeaders(&(pOpenConn->ssdpRequest)) == FALSE)
                    {
                        TraceTag(ttidEventServer, "Verified headers returned false for %d",
                                 pOpenConn->socketPeer);

                        pOpenConn->state = CONNECTION_ERROR_CLOSING;

                        SocketSendErrorResponse(pData->socket, HTTP_STATUS_BAD_REQUEST);
                        // Gracefully shutdown. Open Conn will be removed in FD_CLOSe
                        shutdown(pData->socket, SD_SEND);

                        return TRUE;
                    }

                    // else

                    if (ParseContent(szContent,
                                     (pOpenConn->cbData - pOpenConn->cbHeaders),
                                      &(pOpenConn->ssdpRequest)) == TRUE)
                    {
                        PSSDP_REQUEST pRequest;

                        pRequest = (PSSDP_REQUEST) malloc(sizeof(SSDP_REQUEST));

                        if (pRequest != NULL &&
                            CopySsdpRequest(pRequest, &(pOpenConn->ssdpRequest)) != FALSE)
                        {
                            fNeedToLeave = FALSE;
                            FreeOpenConnection(pOpenConn);
                            LeaveCriticalSection(&g_csListOpenConn);
                            ProcessSsdpRequest(pRequest, pData);
                            free(pRequest);
                        }
                        else
                        {
                            FreeOpenConnection(pOpenConn);
                            if (pRequest)
                            {
                                FreeSsdpRequest(pRequest);
                                free(pRequest);
                            }
                        }
                    }
                    else
                    {
                        // HTTP request not complete

                        CHAR *szTemp;

                        szTemp = SzaDupSza(szContent);
                        free(pOpenConn->szData);
                        pOpenConn->szData = szTemp;

                        pOpenConn->state = CONNECTION_HEADERS_READY;

                        // Done for now, wait for more data
                    }
                }
            }
            else
            {
                pOpenConn->state = CONNECTION_ERROR_FORCED_CLOSE; 
                SocketSendErrorResponse(pData->socket, HTTP_STATUS_BAD_REQUEST);

                // Gracefully shutdown. Open Conn will be removed in FD_CLOSe
                shutdown(pData->socket, SD_SEND);
            }
        }
        break;

    case CONNECTION_HEADERS_READY:
        if (ParseContent(pOpenConn->szData,
                         (pOpenConn->cbData - pOpenConn->cbHeaders),
                          &(pOpenConn->ssdpRequest)) == TRUE)
        {
            SSDP_REQUEST ssdpRequest;

            if (CopySsdpRequest(&ssdpRequest, &(pOpenConn->ssdpRequest)) != FALSE)
            {
                fNeedToLeave = FALSE;
                FreeOpenConnection(pOpenConn);
                LeaveCriticalSection(&g_csListOpenConn);
                ProcessSsdpRequest(&ssdpRequest, pData);
            }
            else
            {
                FreeOpenConnection(pOpenConn);
                FreeSsdpRequest(&ssdpRequest);
            }
        }
        else
        {
            TraceTag(ttidEventServer, "ParseContent failed!");
        }
        break;

    case CONNECTION_ERROR_CLOSING:
        // we've already failed to process this socket but it hasn't yet
        // been closed.  Don't do anything here.
        //
        TraceTag(ttidEventServer,
                 "FProcessTcpReceiveBuffer: "
                 "connection closing from error, ignoring pData->szBuffer");
        break;
    }

    TraceTag(ttidEventServer, "Done partying on pOpenConn 0x%08X", pOpenConn);

    return fNeedToLeave;
}

DWORD LookupListOpenConn(LPVOID pvData)
{
    PLIST_ENTRY     p;
    PLIST_ENTRY     pListHead = &g_listOpenConn;
    BOOL            fLeave = TRUE;
    BOOL            fCloseConn = FALSE;
    RECEIVE_DATA *  pData = (RECEIVE_DATA *)pvData;

    Assert(pData);

    TraceTag(ttidEventServer, "----- Search Open Connections List -----");

    AssertSz(pData->szBuffer != NULL, "SocketReceive should have allocated the buffer");

    EnterCriticalSection(&g_csListOpenConn);
    for (p = pListHead->Flink; p != pListHead;)
    {
        POPEN_TCP_CONN pOpenConn;

        pOpenConn = CONTAINING_RECORD (p, OPEN_TCP_CONN, linkage);

        p = p->Flink;

        if (pOpenConn->socketPeer == pData->socket)
        {
            fLeave = FProcessTcpReceiveBuffer(pOpenConn, pData);
            fCloseConn = (pOpenConn->state == CONNECTION_ERROR_FORCED_CLOSE)?TRUE:FALSE;
            break;
        }
    }

    if (fLeave)
    {
        LeaveCriticalSection(&g_csListOpenConn);
    }

    if(fCloseConn)
    {
        CloseOpenConnection(pData->socket);            
    }
  
    
    free(pData->szBuffer);
    free(pData);

    return 0;
}

VOID RemoveOpenConn(SOCKET socket)
{
    PLIST_ENTRY p;
    PLIST_ENTRY pListHead = &g_listOpenConn;
    int     cFound = 0;

    TraceTag(ttidEventServer, "----- Search Open Connections List to remove -----");

    EnterCriticalSection(&g_csListOpenConn);
    for (p = pListHead->Flink; p != pListHead;)
    {

        POPEN_TCP_CONN pOpenConn;

        pOpenConn = CONTAINING_RECORD (p, OPEN_TCP_CONN, linkage);

        p = p->Flink;

        if (pOpenConn->socketPeer == socket)
        {
            RemoveEntryList(&pOpenConn->linkage);
            g_cOpenConnections--;
            
            FreeOpenConnection(pOpenConn);
            free(pOpenConn);

            cFound++;
        }
    }

    LeaveCriticalSection(&g_csListOpenConn);
    TraceTag(ttidEventServer, "RemoveOpenConn - Found %d",cFound);
    while (cFound > 0)
    {
        if (InterlockedExchange(&cQueuedAccepts, cQueuedAccepts) > 0)
        {
            DoDelayedAccept();
        }
        cFound--;
    }
    
    TraceTag(ttidEventServer, "RemoveOpenConn - Num of Connections %d",g_cOpenConnections);

}