//
// Copyright (c) Microsoft Corporation.  All rights reserved.
//
//
// Use of this source code is subject to the terms of the Microsoft shared
// source or premium shared source license agreement under which you licensed
// this source code. If you did not accept the terms of the license agreement,
// you are not authorized to use this source code. For the terms of the license,
// please see the license agreement between you and Microsoft or, if applicable,
// see the SOURCE.RTF on your install media or the root of your tools installation.
// THE SOURCE CODE IS PROVIDED "AS IS", WITH NO WARRANTIES OR INDEMNITIES.
//
#include <ssdppch.h>
#pragma hdrstop

#include <bldver.h>
#include "ipsupport.h"

#define BUF_SIZE 40

static LIST_ENTRY listAnnounce;
static CRITICAL_SECTION CSListAnnounce;
static CHAR *AliveHeader = "ssdp:alive";
static CHAR *ByebyeHeader = "ssdp:byebye";
static CHAR *MulticastUri = "*";
static const CHAR c_szHttpLocalHost[] = "http://localhost";
static const CHAR c_szServerVersion[] = "Microsoft-WinCE/%d.%02d UPnP/1.0 UPnP-Device-Host/1.0";

static SSDP_HEADER AliveHeaders[] = {SSDP_HOST, SSDP_CACHECONTROL, SSDP_LOCATION, SSDP_NT, SSDP_NTS, SSDP_SERVER, SSDP_USN, SSDP_LAST, SSDP_LAST, SSDP_LAST};
static SSDP_HEADER ByeByeHeaders[] = {SSDP_HOST, SSDP_NT, SSDP_NTS, SSDP_USN, SSDP_LAST, SSDP_LAST, SSDP_LAST};
static SSDP_HEADER ResponseHeaders[] = {SSDP_CACHECONTROL, SSDP_EXT, SSDP_LOCATION, SSDP_SERVER, SSDP_ST, SSDP_USN, SSDP_LAST, SSDP_LAST, SSDP_LAST};

static BOOL UPNPisDLNA = FALSE;


extern LONG_PTR bShutdown;
extern CHAR g_pszExtensionURI[500];
extern CHAR g_pszHeaderPrefix[10];
extern CHAR g_lpszNls[100];

SVSThreadPool* g_pThreadPool;

BOOL InitializeSsdpRequestFromMessage(SSDP_REQUEST *pRequest,
                                      const SSDP_MESSAGE *pssdpSrc);
VOID PrintList(LIST_ENTRY *pListHead);
VOID PrintSSDPService (const SSDP_SERVICE *pSSDPService);

VOID InitializeListAnnounce()
{
    InitializeCriticalSection(&CSListAnnounce);
    EnterCriticalSection(&CSListAnnounce);
    InitializeListHead(&listAnnounce);
    LeaveCriticalSection(&CSListAnnounce);
}

BOOL SendAnnouncementOrResponse(
    SSDP_SERVICE *pssdpService,
    SOCKET sock,                    // socket to send response (INVALID_SOCKET if multicast announcement)
    PSOCKADDR_STORAGE pSockAddr)    // destination addr (NULL if mulicast announcement)
{
    PSSDPNetwork            pNet = NULL;
    CHAR                   *pszOrigLocation = pssdpService->SsdpRequest.Headers[SSDP_LOCATION];
    CHAR                   *pszBytes = NULL;
    ce::istring             strLocation;
    ce::istring::size_type  nReplaceAt, nReplaceLen, nReplaceAddrAt;

    Assert(pszOrigLocation);
    Assert(pssdpService->SsdpRequest.Headers[SSDP_NLS] == NULL);
    Assert(pssdpService->SsdpRequest.Headers[SSDP_HOST] == NULL);
    Assert(pssdpService->SsdpRequest.Headers[SSDP_OPT] == NULL);

    strLocation = pszOrigLocation;

    nReplaceAt = strLocation.find(c_szReplaceGuid);
    nReplaceLen = strlen(c_szReplaceGuid);

    nReplaceAddrAt = strLocation.find(c_szReplaceAddrGuid);
    assert(nReplaceLen == strlen(c_szReplaceAddrGuid));

    // set OPT header
    if(*g_pszHeaderPrefix && *g_lpszNls)
    {
        if(pssdpService->SsdpRequest.Headers[SSDP_OPT] = (CHAR*)malloc(strlen(g_pszExtensionURI) + strlen(g_pszHeaderPrefix) + sizeof("; ns=") + 1))
        {
            sprintf(pssdpService->SsdpRequest.Headers[SSDP_OPT], "%s; ns=%s", g_pszExtensionURI, g_pszHeaderPrefix);
        }
    }

    GetNetworkLock();

    while(pNet = GetNextNetwork(pNet))
    {
        if(sock != INVALID_SOCKET && pNet->socket != sock)
        {
            continue;
        }

        if(nReplaceAt != ce::istring::npos)
        {
            strLocation = pszOrigLocation;

            // Repalce c_szReplaceGuid with IP address for this network
            strLocation.replace(nReplaceAt, nReplaceLen, pNet->pszIPString);
            
            pssdpService->SsdpRequest.Headers[SSDP_LOCATION] = const_cast<LPSTR>(static_cast<LPCSTR>(strLocation));
        }
        else if(nReplaceAddrAt != ce::istring::npos)
        {
            strLocation = pszOrigLocation;

            // Repalce c_szReplaceAddrGuid with address (IP and port) for this network
            strLocation.replace(nReplaceAddrAt, nReplaceLen, pNet->pszAddressString);

            pssdpService->SsdpRequest.Headers[SSDP_LOCATION] = const_cast<LPSTR>(static_cast<LPCSTR>(strLocation));
        }


        // set NLS header
        if(*g_lpszNls)
        {
            pssdpService->SsdpRequest.Headers[SSDP_NLS] = g_lpszNls;
        }

        if(pSockAddr)
        {
            // ResponseHeaders has space for 3 optional headers: AL, OPT and NLS
            int nHeaders = sizeof(ResponseHeaders)/sizeof(*ResponseHeaders) - 3;

            // add optional AL header if specified
            if(pssdpService->SsdpRequest.Headers[SSDP_AL])
            {
                PREFAST_SUPPRESS(394, "optional header");
                ResponseHeaders[nHeaders++] = SSDP_AL;
            }

            // add optional OPT header if specified
            if(pssdpService->SsdpRequest.Headers[SSDP_OPT])
            {
                PREFAST_SUPPRESS(394, "optional header");
                ResponseHeaders[nHeaders++] = SSDP_OPT;
            }

            // add optional NLS header if specified
            if(pssdpService->SsdpRequest.Headers[SSDP_NLS])
            {
                PREFAST_SUPPRESS(394, "optional header");
                ResponseHeaders[nHeaders++] = SSDP_NLS;
            }

            // compose response message
            ComposeSsdpResponse(&pssdpService->SsdpRequest, ResponseHeaders, nHeaders, &pszBytes);

            // send unicast response
            SocketSend(pszBytes, pNet->socket, (PSOCKADDR)pSockAddr);
        }
        else
        {
            int nHeaders;
            SSDP_HEADER *pIncludedHeaders;

            // set HOST header
            pssdpService->SsdpRequest.Headers[SSDP_HOST] = pNet->pszMulticastAddr;

            // set list of headers to be included in the message
            if(pssdpService->SsdpRequest.Headers[SSDP_NTS] == ByebyeHeader)
            {
                pIncludedHeaders = ByeByeHeaders;
                nHeaders = sizeof(ByeByeHeaders)/sizeof(*ByeByeHeaders);
            }
            else
            {
                assert(pssdpService->SsdpRequest.Headers[SSDP_NTS] == AliveHeader);

                pIncludedHeaders = AliveHeaders;
                nHeaders = sizeof(AliveHeaders)/sizeof(*AliveHeaders);
            }

            // AliveHeaders and ByeByeHeaders has space for 3 optional headers: AL, OPT and NLS
            nHeaders -= 3;

            // add optional AL header if specified
            if(pssdpService->SsdpRequest.Headers[SSDP_AL])
            {
                pIncludedHeaders[nHeaders++] = SSDP_AL;
            }

            // add optional OPT header if specified
            if(pssdpService->SsdpRequest.Headers[SSDP_OPT])
            {
                pIncludedHeaders[nHeaders++] = SSDP_OPT;
            }

            // add optional NLS header if specified
            if(pssdpService->SsdpRequest.Headers[SSDP_NLS])
            {
                pIncludedHeaders[nHeaders++] = SSDP_NLS;
            }

            // compose request message
            ComposeSsdpRequest(&pssdpService->SsdpRequest, pIncludedHeaders, nHeaders, &pszBytes);

            // send multicast announcement
            SocketSend(pszBytes, pNet->socket, pNet->pMulticastAddr);
        }

        if (pszBytes)
        {
            free(pszBytes);
            pszBytes = NULL;
        }
    }

    FreeNetworkLock();

    // restore LOCATION header
    pssdpService->SsdpRequest.Headers[SSDP_LOCATION] = pszOrigLocation;

    // restore NLS header
    pssdpService->SsdpRequest.Headers[SSDP_NLS] = NULL;

    // restore HOST header
    pssdpService->SsdpRequest.Headers[SSDP_HOST] = NULL;

    // free OPT header
    if(pssdpService->SsdpRequest.Headers[SSDP_OPT])
    {
        free(pssdpService->SsdpRequest.Headers[SSDP_OPT]);
        pssdpService->SsdpRequest.Headers[SSDP_OPT] = NULL;
    }

    return TRUE;
}



// Announce all registered services on all active networks
// This is called when there is a network change, for example, 
// an IP address change or a new network card
// This is implemented as a synchronous call, since it is called
// infrequently.
VOID SendAllAnnouncements()
{
    PLIST_ENTRY p;
    PLIST_ENTRY pListHead = &listAnnounce;
    TraceTag(ttidSsdpAnnounce, "SendAllAnnouncements entered");
    EnterCriticalSection(&CSListAnnounce);
    for (p = pListHead->Flink; p != pListHead; p = p->Flink)
    {
        SSDP_SERVICE *pService;

        pService = CONTAINING_RECORD (p, SSDP_SERVICE, linkage);

        EnterCriticalSection(&pService->CSService);
        pService->SsdpRequest.Headers[SSDP_NTS] = AliveHeader;
        SendAnnouncement(pService);
        LeaveCriticalSection(&pService->CSService);

    }

    TraceTag(ttidSsdpAnnounce, "SendAllAnnouncements done");
    LeaveCriticalSection(&CSListAnnounce);
}

DWORD AnnounceTimerProc (VOID *Arg)
{
    SSDP_SERVICE *pssdpService = (SSDP_SERVICE *) Arg;
    unsigned long Timeout;

    EnterCriticalSection(&(pssdpService->CSService));

    TraceTag(ttidSsdpTimer, "Announcement timer of %x expired with count = %d",
             pssdpService,pssdpService->iRetryCount );

    if (pssdpService->state == SERVICE_NO_MASTER_CLEANUP)
    {
        SetEvent(pssdpService->CleanupEvent);
        LeaveCriticalSection(&(pssdpService->CSService));
        return 0;
    }

    pssdpService->SsdpRequest.Headers[SSDP_NTS] = AliveHeader;
    SendAnnouncement(pssdpService);
    pssdpService->iRetryCount--;

    if (pssdpService->iRetryCount == 0)
    {
        // To-Do: The current limit on life time is 49.7 days.  (32 bits in milliseconds)
        // 32 bit in seconds should be enough.
        // Need to add field remaining time ...

        pssdpService->iRetryCount = NUM_RETRIES;
//        Timeout = (pssdpService->iLifeTime - ANNOUNCE_MARGIN) * 1000;
        Timeout = (pssdpService->iLifeTime / 4) * 1000;

        if(!(pssdpService->dwTimerCookie = g_pThreadPool->StartTimer(AnnounceTimerProc, pssdpService, Timeout)))
        {
            TraceTag(ttidError, "Failed to start cache timer for %x.",
                     pssdpService);
        }
        else
        {
            TraceTag(ttidSsdpTimer, "Started cache timer.");
        }
    }
    else
    {
        Timeout = RETRY_INTERVAL;

        if(!(pssdpService->dwTimerCookie = g_pThreadPool->StartTimer(AnnounceTimerProc, pssdpService, Timeout)))
        {
            TraceTag(ttidError, "Failed to start retry timer.");
        }
        else
        {
            TraceTag(ttidSsdpTimer, "Started retry timer.");
        }
    }

    LeaveCriticalSection(&(pssdpService->CSService));

    return 0;
}

DWORD ByebyeTimerProc (VOID *Arg)
{
    SSDP_SERVICE *pssdpService = (SSDP_SERVICE *) Arg;
    unsigned long Timeout;

    if (InterlockedExchange(&bShutdown, bShutdown) != 0)
    {
        FreeSSDPService(pssdpService);
        return 0;
    }

    //GetNetworkLock();

    EnterCriticalSection(&(pssdpService->CSService));

    TraceTag(ttidSsdpTimer, "Byebye timer of %x expired with count = %d",
             pssdpService,pssdpService->iRetryCount );

    pssdpService->SsdpRequest.Headers[SSDP_NTS] = ByebyeHeader;
    SendAnnouncement(pssdpService);

    //FreeNetworkLock();
    pssdpService->iRetryCount--;

    if (pssdpService->iRetryCount == 0)
    {
        TraceTag(ttidSsdpAnnounce, "Done with sending byebyes.");
        LeaveCriticalSection(&(pssdpService->CSService));
        FreeSSDPService(pssdpService);
    }
    else
    {
        Timeout = RETRY_INTERVAL;

        if(!(pssdpService->dwTimerCookie = g_pThreadPool->StartTimer(ByebyeTimerProc, pssdpService, Timeout)))
        {
            TraceTag(ttidError, "Failed to start byebye retry timer.");
        }
        else
        {
            TraceTag(ttidSsdpTimer, "Started byebye retry timer.");
        }

        LeaveCriticalSection(&(pssdpService->CSService));
    }

    return 0;
}

VOID StartAnnounceTimer(PSSDP_SERVICE pssdpSvc, LPTHREAD_START_ROUTINE pCallback)
{
    EnterCriticalSection(&(pssdpSvc->CSService));

    // Assume send will be called once before start the timer
    pssdpSvc->iRetryCount = NUM_RETRIES-1;

    if(!(pssdpSvc->dwTimerCookie = g_pThreadPool->StartTimer(pCallback, pssdpSvc, RETRY_INTERVAL)))
    {
        TraceTag(ttidError, "Announcement timer failed to start "
                 "for service %x", pssdpSvc);
    }
    else
    {
        TraceTag(ttidSsdpTimer, "Announcement timer started for service "
                 "%x", pssdpSvc);
    }

    LeaveCriticalSection(&(pssdpSvc->CSService));
}

VOID StopAnnounceTimer(SSDP_SERVICE *pSSDPSvc)
{
    EnterCriticalSection(&(pSSDPSvc->CSService));

    Assert(pSSDPSvc->state == SERVICE_NO_MASTER_CLEANUP);

    TraceTag(ttidSsdpTimer, "Stopping Announcement timer for service %x", pSSDPSvc);

    if (g_pThreadPool->StopTimer(pSSDPSvc->dwTimerCookie))
    {
        TraceTag(ttidSsdpTimer, "Announcement timer stopped for service %x", pSSDPSvc);
        LeaveCriticalSection(&(pSSDPSvc->CSService));
    }
    else
    {
        // Timer is running, wait for CleanupEvent
        TraceTag(ttidSsdpAnnounce, "Announcement timer is running, wait ...%x", pSSDPSvc);
        LeaveCriticalSection(&(pSSDPSvc->CSService));
        WaitForSingleObject(pSSDPSvc->CleanupEvent, INFINITE);
    }
}


PSSDP_SERVICE AddToListAnnounce(SSDP_MESSAGE *pssdpMsg, DWORD flags, PCONTEXT_HANDLE_TYPE *pphContext)
{
    SSDP_SERVICE *pssdpService;

    // To-Do: Check for duplicates.

    // To-Do: QueryState

    // Create SSDPService from SSDP_MESSAGE

    pssdpService = (SSDP_SERVICE *) malloc (sizeof(SSDP_SERVICE));

    if (pssdpService == NULL)
    {
        return NULL;
    }

    pssdpService->Type = SSDP_SERVICE_SIGNATURE;

    if (InitializeSsdpRequestFromMessage(&(pssdpService->SsdpRequest),
                                         pssdpMsg) == FALSE)
    {
        free(pssdpService);
        return NULL;
    };

    pssdpService->iLifeTime = pssdpMsg->iLifeTime;

    pssdpService->iRetryCount = 0;

    pssdpService->dwTimerCookie = 0;

    pssdpService->CleanupEvent = CreateEvent(NULL, TRUE, FALSE, NULL);

    if (pssdpService->CleanupEvent == NULL)
    {
        free(pssdpService);
        return NULL;
    }

    pssdpService->flags = flags;
    pssdpService->RpcContextHandle = pphContext;




    // Important: Set to NULL to prevent freeing memory.
    pssdpService->SsdpRequest.Headers[SSDP_NTS] = NULL;
   
    pssdpService->SsdpRequest.RequestUri = MulticastUri; // for notify
   
    // To-Do: Query Per Network State Matrix state
    pssdpService->state = SERVICE_ACTIVE_NO_MASTER;

    InitializeCriticalSection(&(pssdpService->CSService));
    InitializeListHead(&(pssdpService->listSearchResponse));

    // ALWAYS get the list lock before the service lock.

    EnterCriticalSection(&CSListAnnounce);

    // read the LIST_ENTRY
    EnterCriticalSection(&(pssdpService->CSService));
    InsertHeadList(&listAnnounce, &(pssdpService->linkage));
    LeaveCriticalSection(&(pssdpService->CSService));

    //PrintList(&listAnnounce);
    TraceTag(ttidSsdpAnnounce, "New SSDP service announcement:\n");
#ifdef DEBUG
    PrintSSDPService(pssdpService);
#endif

    LeaveCriticalSection(&CSListAnnounce);

    return pssdpService;
}

VOID FreeSSDPService(SSDP_SERVICE *pSSDPSvc)
{
    //
    // We have to make sure that no thread has any reference to this service at this point
    // Do we need ref count?
    //
    Assert(pSSDPSvc);
    pSSDPSvc->SsdpRequest.Headers[SSDP_NTS] = NULL; // make sure this header element is not freed.
    pSSDPSvc->SsdpRequest.RequestUri = NULL;        //make sure this header element is not freed
    
    FreeSsdpRequest(&(pSSDPSvc->SsdpRequest));

    DeleteCriticalSection(&(pSSDPSvc->CSService));

    CloseHandle(pSSDPSvc->CleanupEvent);

    free(pSSDPSvc);
}

VOID RemoveFromListAnnounce(SSDP_SERVICE *pssdpSvc)
{
    EnterCriticalSection(&CSListAnnounce);
    EnterCriticalSection(&pssdpSvc->CSService);
    RemoveEntryList(&(pssdpSvc->linkage));
#ifdef DEBUG
    PrintSSDPService(pssdpSvc);
#endif
    LeaveCriticalSection(&pssdpSvc->CSService);
    //PrintList(&listAnnounce);
    LeaveCriticalSection(&CSListAnnounce);
}

BOOL IsInListAnnounce(CHAR *szUSN)
{
    PLIST_ENTRY p;
    LIST_ENTRY *pListHead = &listAnnounce;
    BOOL found = FALSE;

    EnterCriticalSection(&CSListAnnounce);
    for (p = pListHead->Flink; p != pListHead; p = p->Flink)
    {
        SSDP_SERVICE *pService;
        pService = CONTAINING_RECORD (p, SSDP_SERVICE, linkage);

        EnterCriticalSection(&(pService->CSService));

        if (strcmp(pService->SsdpRequest.Headers[SSDP_USN], szUSN) == 0)
        {
            found = TRUE;
            LeaveCriticalSection(&(pService->CSService));
            break;
        }
        LeaveCriticalSection(&(pService->CSService));
    }

    LeaveCriticalSection(&CSListAnnounce);
    return found;
}

// It is necessary to hold the list lock while comparing type and put in the PSRT.

VOID SearchListAnnounce(SSDP_REQUEST *SsdpMessage, SOCKET sockRecv, PSOCKADDR_STORAGE pRemoteAddr)
{

    PLIST_ENTRY p;
    LIST_ENTRY *pListHead = &listAnnounce;

    EnterCriticalSection(&CSListAnnounce);
    for (p = pListHead->Flink; p != pListHead; p = p->Flink)
    {
        SSDP_SERVICE *pService;
        pService = CONTAINING_RECORD (p, SSDP_SERVICE, linkage);

        EnterCriticalSection(&(pService->CSService));

        if (strcmp(SsdpMessage->Headers[SSDP_ST], "ssdp:all") == 0 ||
            strncmp(SsdpMessage->Headers[SSDP_ST], pService->SsdpRequest.Headers[SSDP_NT], strlen(SsdpMessage->Headers[SSDP_ST])) == 0)
        {
            PSSDP_SEARCH_RESPONSE ResponseEntry;

            ResponseEntry = (PSSDP_SEARCH_RESPONSE) malloc(sizeof(SSDP_SEARCH_RESPONSE));

            if (ResponseEntry == NULL)
            {
                TraceTag(ttidError, "Failed to allocate response "
                         "entry.");
                LeaveCriticalSection(&(pService->CSService));
                continue;
            }

            if (InitializeSearchResponseFromRequest(ResponseEntry,
                                                    SsdpMessage,
                                                    sockRecv,
                                                    pRemoteAddr) == FALSE)
            {
                LeaveCriticalSection(&(pService->CSService));
                continue;
            }

            ResponseEntry->Owner = pService;

            InsertHeadList(&(pService->listSearchResponse),
                           &(ResponseEntry->linkage));

            StartSearchResponseTimer(ResponseEntry, SearchResponseTimerProc);

        }
        LeaveCriticalSection(&(pService->CSService));
    }
    LeaveCriticalSection(&CSListAnnounce);
}

PCONTEXT_HANDLE_TYPE * GetServiceByUSN(CHAR *szUSN)
{
    PLIST_ENTRY p;
    LIST_ENTRY *pListHead = &listAnnounce;

    EnterCriticalSection(&CSListAnnounce);
    for (p = pListHead->Flink; p != pListHead; p = p->Flink)
    {
        SSDP_SERVICE *pService;
        pService = CONTAINING_RECORD (p, SSDP_SERVICE, linkage);

        EnterCriticalSection(&(pService->CSService));

        if (strcmp(pService->SsdpRequest.Headers[SSDP_USN], szUSN) == 0)
        {
            LeaveCriticalSection(&(pService->CSService));
            LeaveCriticalSection(&CSListAnnounce);
            return pService->RpcContextHandle;
        }
        else
        {
            LeaveCriticalSection(&(pService->CSService));
        }

    }
    LeaveCriticalSection(&CSListAnnounce);
    return NULL;
}

VOID PrintList(LIST_ENTRY *pListHead)
{
    PLIST_ENTRY p;
    int i = 1;

    TraceTag(ttidSsdpAnnounce, "----- SSDP Announcement List -----");

    EnterCriticalSection(&CSListAnnounce);
    for (p = pListHead->Flink; p != pListHead; p = p->Flink, i++)
    {
        SSDP_SERVICE *pService;

        TraceTag(ttidSsdpAnnounce, "----- SSDP Service Content %d -----", i);
        pService = CONTAINING_RECORD (p, SSDP_SERVICE, linkage);
        PrintSSDPService(pService);
    }
    LeaveCriticalSection(&CSListAnnounce);
}

VOID CleanupAnnounceEntry (SSDP_SERVICE *pService)
{
    EnterCriticalSection(&CSListAnnounce);

    EnterCriticalSection(&pService->CSService);
    RemoveEntryList(&(pService->linkage));
    LeaveCriticalSection(&pService->CSService);

    LeaveCriticalSection(&CSListAnnounce);

    StopAnnounceTimer(pService);

    CleanupListSearchResponse(&(pService->listSearchResponse));
}

VOID CleanupListAnnounce()
{
    PLIST_ENTRY p;
    PLIST_ENTRY pListHead = &listAnnounce;

    TraceTag(ttidSsdpAnnounce, "----- Cleanup SSDP Announcement List -----");

    EnterCriticalSection(&CSListAnnounce);
    for (p = pListHead->Flink; p && p != pListHead;)
    {
        SSDP_SERVICE *pService;

        pService = CONTAINING_RECORD (p, SSDP_SERVICE, linkage);

        p = p->Flink;

        EnterCriticalSection(&pService->CSService);
        pService->state = SERVICE_NO_MASTER_CLEANUP;
        LeaveCriticalSection(&pService->CSService);

        CleanupAnnounceEntry(pService);

        FreeSSDPService(pService);
    }

    LeaveCriticalSection(&CSListAnnounce);
    DeleteCriticalSection(&CSListAnnounce);
}

VOID PrintSSDPService(const SSDP_SERVICE *pSSDPService)
{
    PrintSsdpRequest(&(pSSDPService->SsdpRequest));
}

BOOL InitializeSsdpRequestFromMessage(SSDP_REQUEST *pRequest,
                                      const SSDP_MESSAGE *pssdpSrc)
{
    CHAR buffer[BUF_SIZE];

    InitializeSsdpRequest(pRequest);
    
    pRequest->Method = SSDP_NOTIFY;

    if (pssdpSrc->szType != NULL)
    {
        pRequest->Headers[SSDP_NT] = (CHAR *) malloc(sizeof(CHAR) *
                                                     (strlen(pssdpSrc->szType) + 1));
        if (pRequest->Headers[SSDP_NT]  == NULL)
        {
            TraceTag(ttidError, "Failed to allocate memory for SSDP Headers");
            return FALSE;
        }
        strcpy(pRequest->Headers[SSDP_NT], pssdpSrc->szType);
    }

    if (pssdpSrc->szUSN != NULL)
    {
        pRequest->Headers[SSDP_USN] = (CHAR*) malloc(sizeof(CHAR) *
                                                     (strlen(pssdpSrc->szUSN) + 1));
        if (pRequest->Headers[SSDP_USN]  == NULL)
        {
            TraceTag(ttidError, "Failed to allocate memory for SSDP Headers");
            return FALSE;
        }
        strcpy(pRequest->Headers[SSDP_USN], pssdpSrc->szUSN);
    }

    if (pssdpSrc->szAltHeaders != NULL)
    {
        pRequest->Headers[SSDP_AL] = (CHAR*) malloc(sizeof(CHAR) *
                                                    (strlen(pssdpSrc->szAltHeaders) + 1));
        if (pRequest->Headers[SSDP_AL]  == NULL)
        {
            TraceTag(ttidError, "Failed to allocate memory for SSDP Headers");
            return FALSE;
        }
        strcpy(pRequest->Headers[SSDP_AL], pssdpSrc->szAltHeaders);
    }

    if (pssdpSrc->szNls != NULL)
    {
        pRequest->Headers[SSDP_NLS] = (CHAR*) malloc(sizeof(CHAR) *
                                                    (strlen(pssdpSrc->szNls) + 1));
        if (pRequest->Headers[SSDP_NLS]  == NULL)
        {
            TraceTag(ttidError, "Failed to allocate memory for SSDP Headers");
            return FALSE;
        }
        strcpy(pRequest->Headers[SSDP_NLS], pssdpSrc->szNls);
    }

    if (pssdpSrc->szLocHeader != NULL)
    {
        pRequest->Headers[SSDP_LOCATION] = (CHAR*) malloc(sizeof(CHAR) *
                                                          (strlen(pssdpSrc->szLocHeader) + 1));
        if (pRequest->Headers[SSDP_LOCATION]  == NULL)
        {
            TraceTag(ttidSsdpAnnounce, "Failed to allocate memory for SSDP Headers");
            return FALSE;
        }
        strcpy(pRequest->Headers[SSDP_LOCATION], pssdpSrc->szLocHeader);
    }

    _itoa_s(pssdpSrc->iLifeTime, buffer, _countof(buffer), 10);

    pRequest->Headers[SSDP_CACHECONTROL] = (CHAR *) malloc(sizeof(CHAR) *
                                                           (strlen("max-age=") +
                                                            strlen(buffer) + 1));
    if (pRequest->Headers[SSDP_CACHECONTROL]  == NULL)
    {
        TraceTag(ttidSsdpAnnounce, "Failed to allocate memory for SSDP Headers");
        return FALSE;
    }
    sprintf(pRequest->Headers[SSDP_CACHECONTROL], "max-age=%d",
            pssdpSrc->iLifeTime);


    // need enough space for major version and minor version
    pRequest->Headers[SSDP_SERVER]= (CHAR *) malloc(sizeof(c_szServerVersion)+5+5);
    if (pRequest->Headers[SSDP_SERVER]  == NULL)
    {
        TraceTag(ttidSsdpAnnounce, "Failed to allocate memory for SSDP Headers");
        return FALSE;
    }
    sprintf(pRequest->Headers[SSDP_SERVER],c_szServerVersion,CE_MAJOR_VER, CE_MINOR_VER);

    return TRUE;
}



// Call to SSDP to inform it that we are DLNA compliant.
// This means that SSDP must send byebyes before alives.
// This call is made before the first call to RegisterUpnpServiceImpl 
// The registrations will send out BYEBYEs.
// ALIVEs are postponed.
BOOL BeginSSDPforDLNA()
{
    UPNPisDLNA = TRUE;
    return TRUE;
}

// This call is made after the last call to RegisterUpnpServiceImpl 
// This will trigger the sending of all the postponed ALIVEs 
BOOL EndSSDPforDLNA()
{
    PLIST_ENTRY p;
    LIST_ENTRY *pListHead = &listAnnounce;

    // END only applies if BEGIN was called
    if (!UPNPisDLNA)
    {
        return FALSE;
    }

    // Trigger the ALIVE messages
    EnterCriticalSection(&CSListAnnounce);
    for (p = pListHead->Flink; p != pListHead; p = p->Flink)
    {
        SSDP_SERVICE *pService = CONTAINING_RECORD (p, SSDP_SERVICE, linkage);

        // Send an initial ALIVE message
        EnterCriticalSection(&(pService->CSService));
        pService->SsdpRequest.Headers[SSDP_NTS] = AliveHeader;
        SendAnnouncement(pService);
        LeaveCriticalSection(&(pService->CSService));

        // start the timer for sending more ALIVEs.
        StartAnnounceTimer(pService, AnnounceTimerProc);
    }
    LeaveCriticalSection(&CSListAnnounce);

    // clear flag
    UPNPisDLNA = FALSE;
    return TRUE;
}


BOOL RegisterUpnpServiceIoctl(
    __in ce::marshal_arg<ce::copy_in, PSSDP_MESSAGE>    pSsdpMessage, 
    __in DWORD                                          flags, 
    __out ce::marshal_arg<ce::copy_out, HANDLE*>        phService)
{
    return RegisterUpnpServiceImpl(pSsdpMessage, flags, phService);
}


// Publication
BOOL RegisterUpnpServiceImpl(PSSDP_MESSAGE pSsdpMessage, DWORD flags, HANDLE* phService)
{
    DWORD error = ERROR_SUCCESS;
    PSSDP_SERVICE pSSDPSvc;

    if (InterlockedExchange(&cInitialized, cInitialized) == 0) 
    {
        error = ERROR_NOT_READY; 
    }
    else if (!pSsdpMessage || !phService)
    {
        error = ERROR_INVALID_PARAMETER;
    }
    else if (pSsdpMessage->szUSN == NULL || pSsdpMessage->szType == NULL)
    {
        error = ERROR_INVALID_PARAMETER;
    }
    else if (pSsdpMessage->szAltHeaders == NULL && pSsdpMessage->szLocHeader == NULL)
    {
        error = ERROR_INVALID_PARAMETER;
    }
    else if (IsInListAnnounce(pSsdpMessage->szUSN))
    {
        error = ERROR_DUPLICATE_SERVICE;
    }
    else if ((pSSDPSvc = AddToListAnnounce(pSsdpMessage, flags, NULL /* pphContext */)) == NULL)
    {
        error = ERROR_NOT_ENOUGH_MEMORY;
    }
    else
    {
        if (UPNPisDLNA)
        {
            // In DLNA mode we send a BYEBYE and postpone the ALIVES.
            EnterCriticalSection(&pSSDPSvc->CSService);
            pSSDPSvc->SsdpRequest.Headers[SSDP_NTS] = ByebyeHeader;
            SendAnnouncement(pSSDPSvc);
            LeaveCriticalSection(&pSSDPSvc->CSService);
        }
        else
        {
            // In UPNP mode we send ALIVEs immediately
            EnterCriticalSection(&pSSDPSvc->CSService);
            pSSDPSvc->SsdpRequest.Headers[SSDP_NTS] = AliveHeader;
            SendAnnouncement(pSSDPSvc);
            LeaveCriticalSection(&pSSDPSvc->CSService);

            // start the timer for sending more ALIVEs.
            StartAnnounceTimer(pSSDPSvc, AnnounceTimerProc);
        }

        *phService = pSSDPSvc;
        return TRUE;
    }

    SetLastError(error);

    *phService = INVALID_HANDLE_VALUE;

    return FALSE;
}


BOOL WINAPI DeregisterUpnpService(HANDLE hRegister, BOOL fByebye)
{
    return DeregisterUpnpServiceImpl((ce::PSL_HANDLE)hRegister, fByebye);
}

BOOL DeregisterUpnpServiceImpl(ce::PSL_HANDLE hRegister, BOOL fByebye)
{
    SSDP_SERVICE *pSSDPSvc = (SSDP_SERVICE *) (hRegister);
    DWORD error = ERROR_SUCCESS;

    TraceTag(ttidSsdpAnnounce, "%x is being deregistered.", pSSDPSvc);

    if (InterlockedExchange(&cInitialized, cInitialized) == 0) 
    {
        error = ERROR_NOT_READY; 
    }
    else if (!hRegister || (INVALID_HANDLE_VALUE == hRegister))
    {
        error = ERROR_INVALID_PARAMETER;
    }
    else if (pSSDPSvc->Type != SSDP_SERVICE_SIGNATURE)
    {
        TraceTag(ttidSsdpAnnounce, "%x has type %d, not valid", pSSDPSvc, pSSDPSvc->Type);
        error = ERROR_INVALID_PARAMETER;
    }
    else
    {
        EnterCriticalSection(&(pSSDPSvc->CSService));

        if (pSSDPSvc->state != SERVICE_ACTIVE_NO_MASTER &&
            pSSDPSvc->state != SERVICE_ACTIVE_W_MASTER)
        {
            // Ignore possible duplicate deregistration
            LeaveCriticalSection(&(pSSDPSvc->CSService));
            TraceTag(ttidSsdpAnnounce, "%x is not active.", pSSDPSvc);
            error = ERROR_INVALID_PARAMETER;

        }
        else
        {
            pSSDPSvc->state = SERVICE_NO_MASTER_CLEANUP;

            LeaveCriticalSection(&(pSSDPSvc->CSService));
            CleanupAnnounceEntry(pSSDPSvc);

            // Annoucement timer should be stopped by now and entry is not on listAnnounce.
            // To-Do: What if system service goes down when we are sending byebye?
            // Check for NT service.

            if (fByebye)
            {
                ResetEvent(pSSDPSvc->CleanupEvent);
                
                EnterCriticalSection(&pSSDPSvc->CSService);
                pSSDPSvc->SsdpRequest.Headers[SSDP_NTS] = ByebyeHeader;
                SendAnnouncement(pSSDPSvc);
                LeaveCriticalSection(&pSSDPSvc->CSService);

                StartAnnounceTimer(pSSDPSvc, ByebyeTimerProc);
            }
            else
            {
                FreeSSDPService(pSSDPSvc);
            }
            return TRUE;

        }
    }

    SetLastError(error);
    return FALSE;
}

BOOL WINAPI DeregisterUpnpServiceByUSN(
                              /* [string][in] */ LPSTR szUSN,
                              /* [in] */ BOOL fByebye)
{
    HANDLE handle = (HANDLE)GetServiceByUSN((LPSTR)szUSN);

    if (handle != NULL)
    {
        return DeregisterUpnpService(handle, fByebye);
    }
    else
    {
        SetLastError(ERROR_INVALID_PARAMETER);
        return FALSE;
    }
}

