1942 lines
54 KiB
C++
1942 lines
54 KiB
C++
//+---------------------------------------------------------------------------
|
|
//
|
|
// Microsoft Windows
|
|
// Copyright (C) Microsoft Corporation, 1997.
|
|
//
|
|
// File: E V T A P I . C P P
|
|
//
|
|
// Contents: Private low-level APIs dealing with UPnP events.
|
|
//
|
|
// Notes:
|
|
//
|
|
// Author: danielwe 18 Oct 1999
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
#include <pch.h>
|
|
#pragma hdrstop
|
|
|
|
#include <hostinc.h>
|
|
#include "evtapi.h"
|
|
#include "stdio.h"
|
|
#include "interfacelist.h"
|
|
#include <wininet.h>
|
|
#include <winsock.h>
|
|
|
|
HANDLE g_hTimerQ = NULL;
|
|
CRITICAL_SECTION g_csListEventSource;
|
|
UPNP_EVENT_SOURCE * g_pesList = NULL;
|
|
HINTERNET g_hInetSess = NULL;
|
|
static const DWORD c_csecTimeout = 30; // Internet connect
|
|
// timeout (in seconds)
|
|
|
|
// Default subscription timeout (6 hours)
|
|
static const DWORD c_csecDefSubsTimeout = 60 * 60 * 6;
|
|
|
|
// Minimum subscription timeout (10 minutes??)
|
|
static const DWORD c_csecMinSubsTimeout = 60 * 10;
|
|
|
|
VOID FreeEventSourceBlocking(UPNP_EVENT_SOURCE * pes);
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: HrInitEventApi
|
|
//
|
|
// Purpose: Initializes the low-level eventing API
|
|
//
|
|
// Arguments:
|
|
// (none)
|
|
//
|
|
// Returns: Nothing
|
|
//
|
|
// Author: danielwe 4 Aug 2000
|
|
//
|
|
// Notes:
|
|
//
|
|
HRESULT HrInitEventApi()
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
InitializeCriticalSection(&g_csListEventSource);
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
AssertSz(!g_hTimerQ, "Already initialized timer queue?!?");
|
|
|
|
g_hTimerQ = CreateTimerQueue();
|
|
if (!g_hTimerQ)
|
|
{
|
|
hr = HrFromLastWin32Error();
|
|
TraceError("HrInitEventApi: CreateTimerQueue", hr);
|
|
}
|
|
}
|
|
|
|
TraceError("HrInitEventApi", hr);
|
|
return hr;
|
|
}
|
|
|
|
HRESULT HrInitInternetSession()
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
AssertSz(!g_hInetSess, "Already initialized?");
|
|
|
|
g_hInetSess = InternetOpen(L"Mozilla/4.0 (compatible; UPnP/1.0; Windows NT/5.1)",
|
|
INTERNET_OPEN_TYPE_DIRECT,
|
|
NULL, NULL, 0);
|
|
if (g_hInetSess)
|
|
{
|
|
DWORD dwTimeout = c_csecTimeout * 1000;
|
|
|
|
if (!InternetSetOption(g_hInetSess, INTERNET_OPTION_CONNECT_TIMEOUT,
|
|
(LPVOID)&dwTimeout, sizeof(DWORD)))
|
|
{
|
|
hr = HrFromLastWin32Error();
|
|
TraceError("HrFromLastWin32Error: InternetSetOption",
|
|
HrFromLastWin32Error());
|
|
}
|
|
else
|
|
{
|
|
TraceTag(ttidEventServer, "HrFromLastWin32Error: Suscessfully set "
|
|
"internet connect timeout to %d seconds",
|
|
c_csecTimeout);
|
|
}
|
|
if(SUCCEEDED(hr))
|
|
{
|
|
INTERNET_PROXY_INFO ipi;
|
|
ZeroMemory(&ipi, sizeof(ipi));
|
|
ipi.dwAccessType = INTERNET_OPEN_TYPE_DIRECT;
|
|
if(!InternetSetOption(g_hInetSess, INTERNET_OPTION_PROXY, &ipi, sizeof(ipi)))
|
|
{
|
|
hr = HrFromLastWin32Error();
|
|
TraceError("HrFromLastWin32Error: InternetSetOption",
|
|
HrFromLastWin32Error());
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
hr = HrFromLastWin32Error();
|
|
TraceError("HrInitInternetSession: InternetOpen", hr);
|
|
}
|
|
|
|
TraceError("HrInitInternetSession", hr);
|
|
return hr;
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: DeInitEventApi
|
|
//
|
|
// Purpose: De-initializes the low-level eventing API
|
|
//
|
|
// Arguments:
|
|
// (none)
|
|
//
|
|
// Returns: Nothing
|
|
//
|
|
// Author: danielwe 4 Aug 2000
|
|
//
|
|
// Notes: The debug version fills the critsec struct after deleting it
|
|
// to catch use afterwards
|
|
//
|
|
VOID DeInitEventApi()
|
|
{
|
|
UPNP_EVENT_SOURCE * pesCur;
|
|
UPNP_EVENT_SOURCE * pesNext;
|
|
|
|
// Delete any remaining event sources from the list. This will block until
|
|
// all event sources have been deleted
|
|
//
|
|
EnterCriticalSection(&g_csListEventSource);
|
|
|
|
for (pesCur = g_pesList; pesCur; pesCur = pesNext)
|
|
{
|
|
pesNext = pesCur->pesNext;
|
|
|
|
FreeEventSourceBlocking(pesCur);
|
|
}
|
|
|
|
g_pesList = NULL;
|
|
|
|
LeaveCriticalSection(&g_csListEventSource);
|
|
|
|
if (g_hInetSess)
|
|
{
|
|
InternetCloseHandle(g_hInetSess);
|
|
g_hInetSess = NULL;
|
|
}
|
|
|
|
if (g_hTimerQ)
|
|
{
|
|
// This will wait for all callback threads to finish before continuing
|
|
// (in other words, it blocks)
|
|
//
|
|
DeleteTimerQueueEx(g_hTimerQ, INVALID_HANDLE_VALUE);
|
|
|
|
TraceTag(ttidEventServer, "DeInitEventApi: Deleted timer queue");
|
|
|
|
g_hTimerQ = NULL;
|
|
}
|
|
|
|
DeleteCriticalSection(&g_csListEventSource);
|
|
|
|
#if DBG
|
|
FillMemory(&g_csListEventSource, sizeof(CRITICAL_SECTION), 0xDA);
|
|
#endif
|
|
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: FreeSubscriber
|
|
//
|
|
// Purpose: Frees the memory and resources used by a subscriber and frees
|
|
// the subscriber itself
|
|
//
|
|
// Arguments:
|
|
// psub [in] Subscriber to free
|
|
//
|
|
// Returns: Nothing
|
|
//
|
|
// Author: danielwe 4 Aug 2000
|
|
//
|
|
// Notes:
|
|
//
|
|
VOID FreeSubscriber(UPNP_SUBSCRIBER *psub)
|
|
{
|
|
UPNP_EVENT * pevtCur;
|
|
UPNP_EVENT * pevtNext;
|
|
HANDLE hWait = NULL;
|
|
|
|
if (!psub)
|
|
{
|
|
return;
|
|
}
|
|
|
|
#if DBG
|
|
if (psub->szSid)
|
|
{
|
|
TraceTag(ttidEventServer, "Freeing subscriber %S", psub->szSid);
|
|
}
|
|
#endif
|
|
|
|
DWORD isz;
|
|
|
|
for (isz = 0; isz < psub->cszUrl; isz++)
|
|
{
|
|
delete [] psub->rgszUrl[isz];
|
|
}
|
|
|
|
delete [] psub->szSid;
|
|
delete [] psub->rgszUrl;
|
|
|
|
// Free the event queue
|
|
//
|
|
for (pevtCur = psub->pevtQueue;
|
|
pevtCur;
|
|
pevtCur = pevtNext)
|
|
{
|
|
delete [] pevtCur->szBody;
|
|
|
|
pevtNext = pevtCur->pevtNext;
|
|
delete pevtCur;
|
|
}
|
|
|
|
if (psub->hWait)
|
|
{
|
|
TraceTag(ttidEventServer, "About to call UnregisterWaitEx()");
|
|
// This will wait for all callback threads to finish before continuing
|
|
// (in other words, it blocks)
|
|
//
|
|
if (!UnregisterWaitEx(psub->hWait, INVALID_HANDLE_VALUE))
|
|
{
|
|
TraceError("FreeSubscriber: UnregisterWaitEx",
|
|
HrFromLastWin32Error());
|
|
}
|
|
else
|
|
{
|
|
TraceTag(ttidEventServer, "FreeSubscriber: Unregistered wait");
|
|
}
|
|
}
|
|
|
|
if (psub->hEventQ && psub->hEventQ != INVALID_HANDLE_VALUE)
|
|
{
|
|
CloseHandle(psub->hEventQ);
|
|
TraceTag(ttidEventServer, "FreeSubscriber: Closed event handle");
|
|
}
|
|
|
|
if (psub->hTimer)
|
|
{
|
|
AssertSz(g_hTimerQ, "No timer queue??");
|
|
|
|
if (!DeleteTimerQueueTimer(g_hTimerQ, psub->hTimer,
|
|
INVALID_HANDLE_VALUE))
|
|
{
|
|
TraceError("FreeSubscriber: DeleteTimerQueueTimer",
|
|
HrFromLastWin32Error());
|
|
}
|
|
else
|
|
{
|
|
TraceTag(ttidEventServer, "FreeSubscriber: Deleted timer "
|
|
"queue timer");
|
|
}
|
|
}
|
|
|
|
// Delete renewal params
|
|
//
|
|
delete [] psub->ur.szEsid;
|
|
delete [] psub->ur.szSid;
|
|
|
|
// Delete event queue worker wait params
|
|
//
|
|
delete [] psub->uwp.szEsid;
|
|
delete [] psub->uwp.szSid;
|
|
|
|
delete psub;
|
|
}
|
|
|
|
VOID FreeEventSourceBlocking(UPNP_EVENT_SOURCE * pes)
|
|
{
|
|
UPNP_SUBSCRIBER * psubCur;
|
|
UPNP_SUBSCRIBER * psubNext;
|
|
|
|
for (psubCur = pes->psubList;
|
|
psubCur;
|
|
psubCur = psubNext)
|
|
{
|
|
psubNext = psubCur->psubNext;
|
|
|
|
FreeSubscriber(psubCur);
|
|
}
|
|
|
|
delete [] pes->szEsid;
|
|
delete pes;
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: FreeEventSourceWorker
|
|
//
|
|
// Purpose: Worker function to free an event source and the resources it
|
|
// uses
|
|
//
|
|
// Arguments:
|
|
// pvContext [in] Context data = event source to free
|
|
//
|
|
// Returns: 0
|
|
//
|
|
// Author: danielwe 4 Aug 2000
|
|
//
|
|
// Notes: This function is always called from a separate thread
|
|
//
|
|
DWORD WINAPI FreeEventSourceWorker(LPVOID pvContext)
|
|
{
|
|
UPNP_EVENT_SOURCE * pes;
|
|
|
|
pes = (UPNP_EVENT_SOURCE *)pvContext;
|
|
|
|
Assert(pes);
|
|
|
|
FreeEventSourceBlocking(pes);
|
|
|
|
return 0;
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: FreeSubscriberWorker
|
|
//
|
|
// Purpose: Worker function to free a subscriber and the resources it uses
|
|
//
|
|
// Arguments:
|
|
// pvContext [in] Context data = subscriber to free
|
|
//
|
|
// Returns: 0
|
|
//
|
|
// Author: danielwe 4 Aug 2000
|
|
//
|
|
// Notes: This function is always called from a separate thread
|
|
//
|
|
DWORD WINAPI FreeSubscriberWorker(LPVOID pvContext)
|
|
{
|
|
UPNP_SUBSCRIBER * psub;
|
|
|
|
psub = (UPNP_SUBSCRIBER *)pvContext;
|
|
|
|
Assert(psub);
|
|
|
|
TraceTag(ttidEventServer, "FreeSubscriberWorker: Freeing subscriber %S",
|
|
psub->szSid);
|
|
|
|
FreeSubscriber(psub);
|
|
|
|
return 0;
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: FreeEventSource
|
|
//
|
|
// Purpose: Frees an event source structure and the resources it uses
|
|
//
|
|
// Arguments:
|
|
// pes [in] Event source to free
|
|
//
|
|
// Returns: Nothing
|
|
//
|
|
// Author: danielwe 4 Aug 2000
|
|
//
|
|
// Notes: The free is done asynchronously, on a separate thread
|
|
//
|
|
VOID FreeEventSource(UPNP_EVENT_SOURCE * pes)
|
|
{
|
|
if (pes)
|
|
{
|
|
#if DBG
|
|
EnterCriticalSection(&g_csListEventSource);
|
|
|
|
AssertSz(!PesFindEventSource(pes->szEsid), "I will not let you free"
|
|
" an event source that's still in the global list!");
|
|
|
|
LeaveCriticalSection(&g_csListEventSource);
|
|
#endif
|
|
|
|
TraceTag(ttidEventServer, "Queueing a work item to free event "
|
|
"source %S", pes->szEsid);
|
|
|
|
// Now that the event source is off the list and no external function can
|
|
// access it anymore we can queue a work item to do the time consuming stuff
|
|
//
|
|
QueueUserWorkItem(FreeEventSourceWorker, (LPVOID)pes,
|
|
WT_EXECUTELONGFUNCTION);
|
|
}
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: HrRegisterEventSource
|
|
//
|
|
// Purpose: Registers a service as an event source
|
|
//
|
|
// Arguments:
|
|
// szEsid [in] Event source identifier
|
|
//
|
|
// Returns: S_OK if successful, E_OUTOFMEMORY, or Win32 error
|
|
//
|
|
// Author: danielwe 10 Jul 2000
|
|
//
|
|
// Notes:
|
|
//
|
|
HRESULT HrRegisterEventSource(LPCWSTR szEsid)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
UPNP_EVENT_SOURCE * pesNew = NULL;
|
|
|
|
Assert(szEsid && *szEsid);
|
|
|
|
EnterCriticalSection(&g_csListEventSource);
|
|
|
|
if (!PesFindEventSource(szEsid))
|
|
{
|
|
pesNew = new UPNP_EVENT_SOURCE;
|
|
if (pesNew)
|
|
{
|
|
ZeroMemory(pesNew, sizeof(UPNP_EVENT_SOURCE));
|
|
|
|
pesNew->szEsid = WszDupWsz(szEsid);
|
|
if (!pesNew->szEsid)
|
|
{
|
|
hr = E_OUTOFMEMORY;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
hr = E_OUTOFMEMORY;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
TraceTag(ttidEventServer, "HrRegisterEventSource - duplicated event "
|
|
"source %S", szEsid);
|
|
hr = E_INVALIDARG;
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
// Link in this event source at the head of the global list
|
|
//
|
|
pesNew->pesNext = g_pesList;
|
|
g_pesList = pesNew;
|
|
}
|
|
|
|
LeaveCriticalSection(&g_csListEventSource);
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
FreeEventSource(pesNew);
|
|
}
|
|
else
|
|
{
|
|
//DbgDumpListEventSource();
|
|
}
|
|
|
|
TraceError("HrRegisterEventSource", hr);
|
|
return hr;
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: HrDeregisterEventSource
|
|
//
|
|
// Purpose: Deregisters a service as an event source
|
|
//
|
|
// Arguments:
|
|
// szEsid [in] Event source identifier
|
|
//
|
|
// Returns: S_OK if success, E_INVALIDARG
|
|
//
|
|
// Author: danielwe 4 Aug 2000
|
|
//
|
|
// Notes:
|
|
//
|
|
HRESULT HrDeregisterEventSource(LPCWSTR szEsid)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
UPNP_EVENT_SOURCE * pesCur;
|
|
UPNP_EVENT_SOURCE * pesPrev;
|
|
|
|
EnterCriticalSection(&g_csListEventSource);
|
|
|
|
for (pesCur = pesPrev = g_pesList;
|
|
pesCur;
|
|
pesPrev = pesCur, pesCur = pesCur->pesNext)
|
|
{
|
|
if (!lstrcmpi(pesCur->szEsid, szEsid))
|
|
{
|
|
TraceTag(ttidEventServer, "Deregistering event source %S", szEsid);
|
|
|
|
if (pesCur == g_pesList)
|
|
{
|
|
g_pesList = pesCur->pesNext;
|
|
}
|
|
else
|
|
{
|
|
AssertSz(pesPrev != pesCur, "Event sourcelist is messed up!");
|
|
AssertSz(pesCur != g_pesList, "Event sourcelist is messed up!");
|
|
|
|
pesPrev->pesNext = pesCur->pesNext;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (pesCur)
|
|
{
|
|
FreeEventSource(pesCur);
|
|
}
|
|
else
|
|
{
|
|
TraceTag(ttidEventServer, "Event source %S not found!", szEsid);
|
|
hr = E_INVALIDARG;
|
|
}
|
|
|
|
LeaveCriticalSection(&g_csListEventSource);
|
|
|
|
//DbgDumpListEventSource();
|
|
|
|
TraceError("HrDeregisterEventSource", hr);
|
|
return hr;
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: SzGetNewSid
|
|
//
|
|
// Purpose: Returns a new "uuid:{SID}" identifier
|
|
//
|
|
// Arguments:
|
|
// (none)
|
|
//
|
|
// Returns: Newly allocated SID string
|
|
//
|
|
// Author: danielwe 13 Oct 1999
|
|
//
|
|
// Notes: Caller must free the returned string with delete []
|
|
//
|
|
LPWSTR SzGetNewSid()
|
|
{
|
|
WCHAR szSid[256];
|
|
UUID uuid;
|
|
unsigned short *szUuid;
|
|
|
|
if (UuidCreate(&uuid) == RPC_S_OK)
|
|
{
|
|
if (UuidToString(&uuid, &szUuid) == RPC_S_OK)
|
|
{
|
|
wsprintf(szSid, L"uuid:%s", szUuid);
|
|
RpcStringFree(&szUuid);
|
|
return WszDupWsz(szSid);
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: EventQueueWorker
|
|
//
|
|
// Purpose: Worker function to remove an event off the event queue for
|
|
// a specific subscriber and submit it to that subscriber
|
|
//
|
|
// Arguments:
|
|
// pvContext [in] Context data = event source and subscriber
|
|
// fTimeOut [in] UNUSED
|
|
//
|
|
// Returns: Nothing
|
|
//
|
|
// Author: danielwe 4 Aug 2000
|
|
//
|
|
// Notes: This function calls into WinINET
|
|
//
|
|
VOID WINAPI EventQueueWorker(LPVOID pvContext, BOOLEAN fTimeOut)
|
|
{
|
|
UPNP_WAIT_PARAMS * puwp;
|
|
LPWSTR szSid = NULL;
|
|
LPWSTR * rgszUrl = NULL;
|
|
DWORD cszUrl = 0;
|
|
HRESULT hr = S_OK;
|
|
BOOL fLeave = TRUE;
|
|
UPNP_EVENT_SOURCE * pes;
|
|
DWORD isz;
|
|
|
|
puwp = (UPNP_WAIT_PARAMS *)pvContext;
|
|
|
|
Assert(puwp);
|
|
|
|
TraceTag(ttidEventServer, "Event queue worker (%S:%S) entering critsec...",
|
|
puwp->szEsid, puwp->szSid);
|
|
|
|
EnterCriticalSection(&g_csListEventSource);
|
|
|
|
TraceTag(ttidEventServer, "...entered");
|
|
|
|
pes = PesFindEventSource(puwp->szEsid);
|
|
if (pes)
|
|
{
|
|
UPNP_SUBSCRIBER * psub;
|
|
UPNP_EVENT * pevt;
|
|
DWORD iSeq;
|
|
HANDLE hEvent;
|
|
|
|
psub = PsubFindSubscriber(pes, puwp->szSid);
|
|
if (psub)
|
|
{
|
|
if (CUPnPInterfaceList::Instance().FShouldSendOnInterface(psub->dwIpAddr))
|
|
{
|
|
BOOL fEmpty;
|
|
|
|
// Remove first event off the list
|
|
//
|
|
pevt = psub->pevtQueue;
|
|
|
|
TraceTag(ttidEventServer, "Processing event %p", pevt);
|
|
|
|
AssertSz(pevt, "Worker is awake but nothing to do today!");
|
|
|
|
psub->pevtQueue = pevt->pevtNext;
|
|
|
|
// Any more items on the queue?
|
|
fEmpty = !psub->pevtQueue;
|
|
|
|
if (fEmpty)
|
|
{
|
|
psub->pevtQueueTail = NULL;
|
|
}
|
|
|
|
TraceTag(ttidEventServer, "Event queue is%s empty",
|
|
fEmpty ? "" : " NOT");
|
|
|
|
szSid = WszDupWsz(psub->szSid);
|
|
if (!szSid)
|
|
{
|
|
hr = E_OUTOFMEMORY;
|
|
goto cleanup;
|
|
}
|
|
|
|
// Copy the list of URLs so we can access it safely outside of the
|
|
// critsec
|
|
cszUrl = psub->cszUrl;
|
|
|
|
Assert(cszUrl);
|
|
|
|
rgszUrl = new LPWSTR[cszUrl];
|
|
if (!rgszUrl)
|
|
{
|
|
hr = E_OUTOFMEMORY;
|
|
goto cleanup;
|
|
}
|
|
|
|
for (isz = 0; isz < cszUrl; isz++)
|
|
{
|
|
rgszUrl[isz] = WszDupWsz(psub->rgszUrl[isz]);
|
|
if (!rgszUrl[isz])
|
|
{
|
|
hr = E_OUTOFMEMORY;
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
// Wrap sequence number to 1 to avoid overflow
|
|
if (psub->iSeq == MAXDWORD)
|
|
{
|
|
psub->iSeq = 1;
|
|
}
|
|
|
|
// Increment the sequence number after assigning it to a local
|
|
// variable.
|
|
//
|
|
iSeq = psub->iSeq++;
|
|
|
|
TraceTag(ttidEventServer, "New sequence # is %d. About to send "
|
|
"sequence #%d", psub->iSeq, iSeq);
|
|
|
|
// Last thing to do is signal the queue event so another worker
|
|
// can pick up the next event off the queue. Only do this if the
|
|
// event queue is still not empty.
|
|
//
|
|
if (!fEmpty)
|
|
{
|
|
TraceTag(ttidEventServer, "Signalling event again");
|
|
SetEvent(psub->hEventQ);
|
|
}
|
|
|
|
// Don't need the lock anymore
|
|
LeaveCriticalSection(&g_csListEventSource);
|
|
|
|
TraceTag(ttidEventServer, "Released lock on global event source list");
|
|
|
|
fLeave = FALSE;
|
|
|
|
LPWSTR szHeaders;
|
|
|
|
hr = HrComposeUpnpNotifyHeaders(iSeq, szSid, &szHeaders);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = E_FAIL;
|
|
|
|
// Try the list of URLs until either we run out of them, or
|
|
// we succeed
|
|
//
|
|
for (isz = 0; FAILED(hr) && isz < cszUrl; isz++)
|
|
{
|
|
hr = HrSubmitNotifyToSubscriber(szHeaders, pevt->szBody,
|
|
rgszUrl[isz]);
|
|
}
|
|
|
|
delete [] szHeaders;
|
|
}
|
|
|
|
delete [] pevt->szBody;
|
|
delete pevt;
|
|
}
|
|
else
|
|
{
|
|
TraceTag(ttidEventServer, "EventQueueWorker: Not sending to subscriber since it"
|
|
" came in on IP address %s",
|
|
inet_ntoa(*(struct in_addr *)&psub->dwIpAddr));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
TraceTag(ttidEventServer, "EventQueueWorker: Did not find "
|
|
"subscriber %S in event source %S", puwp->szEsid,
|
|
puwp->szSid);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
TraceTag(ttidEventServer, "EventQueueWorker: Did not find "
|
|
"event source %S", puwp->szEsid);
|
|
}
|
|
|
|
cleanup:
|
|
|
|
delete [] szSid;
|
|
|
|
if (rgszUrl)
|
|
{
|
|
for (isz = 0; isz < cszUrl; isz++)
|
|
{
|
|
delete [] rgszUrl[isz];
|
|
}
|
|
}
|
|
|
|
delete [] rgszUrl;
|
|
|
|
if (fLeave)
|
|
{
|
|
LeaveCriticalSection(&g_csListEventSource);
|
|
TraceTag(ttidEventServer, "Release lock (2) on global event source list");
|
|
}
|
|
|
|
TraceError("EventQueueWorker", hr)
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: RenewalCallback
|
|
//
|
|
// Purpose: Callback function that is called when a subscriber's renewal
|
|
// timer has expired, which means it should be removed
|
|
//
|
|
// Arguments:
|
|
// pvContext [in] Context data = event source identifier and subscriber
|
|
// fTimeOut [in] UNUSED
|
|
//
|
|
// Returns: Nothing
|
|
//
|
|
// Author: danielwe 4 Aug 2000
|
|
//
|
|
// Notes:
|
|
//
|
|
VOID WINAPI RenewalCallback(LPVOID pvContext, BOOLEAN fTimeOut)
|
|
{
|
|
UPNP_RENEWAL * pur;
|
|
UPNP_EVENT_SOURCE * pes;
|
|
HRESULT hr = S_OK;
|
|
UPNP_SUBSCRIBER * psubToDelete = NULL;
|
|
|
|
pur = (UPNP_RENEWAL *)pvContext;
|
|
|
|
Assert(pur);
|
|
|
|
TraceTag(ttidEventServer, "RenewalCallback: Called for %S:%S (%d)",
|
|
pur->szEsid, pur->szSid, pur->iRenewal);
|
|
|
|
EnterCriticalSection(&g_csListEventSource);
|
|
|
|
pes = PesFindEventSource(pur->szEsid);
|
|
if (pes)
|
|
{
|
|
UPNP_SUBSCRIBER * psubCur;
|
|
UPNP_SUBSCRIBER * psubPrev;
|
|
|
|
for (psubCur = psubPrev = pes->psubList;
|
|
psubCur;
|
|
psubPrev = psubCur,
|
|
psubCur = psubCur->psubNext)
|
|
{
|
|
if (!lstrcmpi(psubCur->szSid, pur->szSid))
|
|
{
|
|
if (psubCur->cRenewals == pur->iRenewal)
|
|
{
|
|
TraceTag(ttidEventServer, "RenewalCallback: Removing subscriber"
|
|
" %S from event source %S", psubCur->szSid,
|
|
pes->szEsid);
|
|
|
|
// Remove subscriber from the list
|
|
|
|
if (psubCur == pes->psubList)
|
|
{
|
|
// Removal of head item
|
|
pes->psubList = psubCur->psubNext;
|
|
}
|
|
else
|
|
{
|
|
psubPrev->psubNext = psubCur->psubNext;
|
|
}
|
|
|
|
TraceTag(ttidEventServer, "RenewalCallback: Queuing work item"
|
|
" to free subscriber %S", pur->szSid);
|
|
|
|
// Can no longer rely on this because once the subscriber is
|
|
// removed from the list, its owning event source is off limits
|
|
//
|
|
psubCur->pes = NULL;
|
|
|
|
psubToDelete = psubCur;
|
|
}
|
|
else
|
|
{
|
|
TraceTag(ttidEventServer, "RenewalCallback: Found subscriber %S"
|
|
"but renewal counter does not match %d vs. %d",
|
|
psubCur->szSid, psubCur->cRenewals, pur->iRenewal);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
hr = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
|
|
TraceTag(ttidEventServer, "RenewalCallback: Did not find event"
|
|
" source %S", pur->szEsid);
|
|
}
|
|
|
|
LeaveCriticalSection(&g_csListEventSource);
|
|
|
|
if (psubToDelete)
|
|
{
|
|
QueueUserWorkItem(FreeSubscriberWorker, (LPVOID)psubToDelete,
|
|
WT_EXECUTELONGFUNCTION);
|
|
}
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: HrAddSubscriber
|
|
//
|
|
// Purpose: Adds a new subscriber to the list for a particular event source
|
|
//
|
|
// Arguments:
|
|
// szEsid [in] Event source identifier
|
|
// dwIpAddr [in] Local IP address that the subscribe came in on
|
|
// cszUrl [in] Number of callback URLs
|
|
// rgszCallbackUrl [in] Callback URLs of subscriber
|
|
// pcsecTimeout [in out] Subscription timeout requested by subscriber
|
|
// Upon return, receives the timeout chosen by
|
|
// the device host
|
|
// pszSid [out] Returns the newly allocated SID
|
|
//
|
|
// Returns: S_OK if success, E_OUTOFMEMORY,
|
|
// or ERROR_FILE_NOT_FOUND if the event source did not exist
|
|
//
|
|
// Author: danielwe 4 Aug 2000
|
|
//
|
|
// Notes: Caller should free the returned pszSid with delete []
|
|
//
|
|
HRESULT HrAddSubscriber(LPCWSTR szEsid, DWORD dwIpAddr, DWORD cszUrl,
|
|
LPCWSTR *rgszCallbackUrl,
|
|
LPCWSTR szEventBody, DWORD *pcsecTimeout,
|
|
LPWSTR *pszSid)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
UPNP_SUBSCRIBER * psub;
|
|
UPNP_WAIT_PARAMS * puwp;
|
|
UPNP_EVENT_SOURCE * pes;
|
|
LPWSTR szSid = NULL;
|
|
|
|
Assert(pszSid);
|
|
Assert(pcsecTimeout);
|
|
|
|
TraceTag(ttidEventServer, "Adding subscriber from %S (%d) to %S",
|
|
rgszCallbackUrl[0], cszUrl, szEsid);
|
|
|
|
psub = new UPNP_SUBSCRIBER;
|
|
if (!psub)
|
|
{
|
|
hr = E_OUTOFMEMORY;
|
|
goto cleanup;
|
|
}
|
|
|
|
ZeroMemory(psub, sizeof(UPNP_SUBSCRIBER));
|
|
|
|
psub->dwIpAddr = dwIpAddr;
|
|
|
|
psub->hEventQ = CreateEvent(NULL, FALSE, FALSE, NULL);
|
|
if (!psub->hEventQ || psub->hEventQ == INVALID_HANDLE_VALUE)
|
|
{
|
|
hr = HrFromLastWin32Error();
|
|
TraceError("HrAddSubscriber: CreateEvent", hr);
|
|
goto cleanup;
|
|
}
|
|
|
|
psub->szSid = SzGetNewSid();
|
|
if (!psub->szSid)
|
|
{
|
|
hr = E_OUTOFMEMORY;
|
|
goto cleanup;
|
|
}
|
|
|
|
TraceTag(ttidEventServer, "Allocated new SID: %S", psub->szSid);
|
|
|
|
// Make a local copy of this for later use in HrSubmitEventZero() and also
|
|
// so we can return it to the caller
|
|
//
|
|
szSid = WszDupWsz(psub->szSid);
|
|
if (!szSid)
|
|
{
|
|
hr = E_OUTOFMEMORY;
|
|
goto cleanup;
|
|
}
|
|
|
|
DWORD isz;
|
|
|
|
psub->cszUrl = cszUrl;
|
|
psub->rgszUrl = new LPWSTR[cszUrl];
|
|
if (!psub->rgszUrl)
|
|
{
|
|
hr = E_OUTOFMEMORY;
|
|
goto cleanup;
|
|
}
|
|
|
|
for (isz = 0; isz < cszUrl; isz++)
|
|
{
|
|
psub->rgszUrl[isz] = WszDupWsz(rgszCallbackUrl[isz]);
|
|
if (!psub->rgszUrl[isz])
|
|
{
|
|
hr = E_OUTOFMEMORY;
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
psub->uwp.szEsid = WszDupWsz(szEsid);
|
|
if (!psub->uwp.szEsid)
|
|
{
|
|
hr = E_OUTOFMEMORY;
|
|
goto cleanup;
|
|
}
|
|
|
|
psub->uwp.szSid = WszDupWsz(psub->szSid);
|
|
if (!psub->uwp.szSid)
|
|
{
|
|
hr = E_OUTOFMEMORY;
|
|
goto cleanup;
|
|
}
|
|
|
|
// ISSUE-2000/10/2-danielwe: Registering the wait with the
|
|
// WT_EXECUTELONGFUNCTION flag means that a new thread will be created
|
|
// FOR EACH SUBSCRIBER. This may be a bad thing depending on how many
|
|
// subscribers there are expected to be. Creating threads with this flag
|
|
// would be to handle the case where one or more subscribers are timing
|
|
// out sending the NOTIFY to them or they are just plain slow. If this
|
|
// flag is not used, these subscribers will cause the eventing queues to
|
|
// bottleneck because no free threads are available to service them. So,
|
|
// to summarize:
|
|
//
|
|
// Using the WT_EXECUTELONGFUNCTION flag:
|
|
// --------------------------------------
|
|
// Pros: Never a bottleneck sending event notifications. They always
|
|
// arrive when expected.
|
|
// Cons: Will end up with lots of threads if there are lots of subscribers
|
|
// However, once the subscribers unsubscribe, the thread count would
|
|
// eventually go back down again.
|
|
//
|
|
// Not using the flag:
|
|
// -------------------
|
|
// Pros: Efficient. Only create the threads that are needed.
|
|
// Cons: May end up with events backing up in the queue if subscribers
|
|
// time out frequently.
|
|
//
|
|
// Choice is still up in the air. We'll set the flag for now and see how
|
|
// bad this gets during stress time.
|
|
//
|
|
if (!RegisterWaitForSingleObject(&psub->hWait, psub->hEventQ,
|
|
EventQueueWorker, (LPVOID)&psub->uwp,
|
|
INFINITE, WT_EXECUTELONGFUNCTION))
|
|
{
|
|
hr = HrFromLastWin32Error();
|
|
TraceError("HrAddSubscriber: RegisterWaitForSingleObject", hr);
|
|
goto cleanup;
|
|
}
|
|
|
|
EnterCriticalSection(&g_csListEventSource);
|
|
|
|
pes = PesFindEventSource(szEsid);
|
|
if (!pes)
|
|
{
|
|
hr = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
|
|
TraceTag(ttidEventServer, "HrAddSubscriber: Event source %S not found!",
|
|
szEsid);
|
|
LeaveCriticalSection(&g_csListEventSource);
|
|
goto cleanup;
|
|
}
|
|
|
|
psub->ur.iRenewal = psub->cRenewals;
|
|
|
|
psub->ur.szEsid = WszDupWsz(pes->szEsid);
|
|
if (!psub->ur.szEsid)
|
|
{
|
|
hr = E_OUTOFMEMORY;
|
|
LeaveCriticalSection(&g_csListEventSource);
|
|
goto cleanup;
|
|
}
|
|
|
|
psub->ur.szSid = WszDupWsz(psub->szSid);
|
|
if (!psub->ur.szSid)
|
|
{
|
|
hr = E_OUTOFMEMORY;
|
|
LeaveCriticalSection(&g_csListEventSource);
|
|
goto cleanup;
|
|
}
|
|
|
|
if (!*pcsecTimeout)
|
|
{
|
|
*pcsecTimeout = c_csecDefSubsTimeout;
|
|
}
|
|
else
|
|
{
|
|
*pcsecTimeout = max(c_csecMinSubsTimeout, *pcsecTimeout);
|
|
}
|
|
|
|
psub->csecTimeout = *pcsecTimeout;
|
|
|
|
if (!CreateTimerQueueTimer(&psub->hTimer, g_hTimerQ,
|
|
RenewalCallback, (LPVOID)&psub->ur,
|
|
*pcsecTimeout * 1000, 0, WT_EXECUTEINTIMERTHREAD))
|
|
{
|
|
hr = HrFromLastWin32Error();
|
|
TraceError("HrAddSubscriber: CreateTimerQueueTimer", hr);
|
|
LeaveCriticalSection(&g_csListEventSource);
|
|
goto cleanup;
|
|
}
|
|
|
|
psub->pes = pes;
|
|
|
|
// Link in the new subscriber to the event source's list (add at head
|
|
// of list because it's quicker and order doesn't matter one bit)
|
|
//
|
|
if (!pes->psubList)
|
|
{
|
|
pes->psubList = psub;
|
|
}
|
|
else
|
|
{
|
|
psub->psubNext = pes->psubList;
|
|
pes->psubList = psub;
|
|
}
|
|
|
|
TraceTag(ttidEventServer, "Adding psub = %p to list", psub);
|
|
|
|
LeaveCriticalSection(&g_csListEventSource);
|
|
|
|
*pszSid = szSid;
|
|
|
|
TraceTag(ttidEventServer, "Adding event zero notification for %S:%S",
|
|
szEsid, szSid);
|
|
|
|
hr = HrSubmitEventZero(szEsid, szSid, szEventBody);
|
|
|
|
done:
|
|
TraceError("HrAddSubscriber", hr);
|
|
return hr;
|
|
|
|
cleanup:
|
|
|
|
delete [] szSid;
|
|
|
|
FreeSubscriber(psub);
|
|
goto done;
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: HrRenewSubscriber
|
|
//
|
|
// Purpose: Renews the given subscriber's subscription
|
|
//
|
|
// Arguments:
|
|
// szEsid [in] Event source identifier
|
|
// pcsecTimeout [in out] Subscription timeout requested by subscriber
|
|
// Upon return, receives the timeout chosen by
|
|
// the device host
|
|
// szSid [in] Subscriber identifier (SID)
|
|
//
|
|
// Returns: S_OK if success, ERROR_FILE_NOT_FOUND if event source or
|
|
// subscription was not found
|
|
//
|
|
// Author: danielwe 4 Aug 2000
|
|
//
|
|
// Notes:
|
|
//
|
|
HRESULT HrRenewSubscriber(LPCWSTR szEsid, DWORD *pcsecTimeout, LPCWSTR szSid)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
UPNP_EVENT_SOURCE * pes;
|
|
HANDLE hTimerDel = NULL;
|
|
|
|
TraceTag(ttidEventServer, "HrRenewSubscriber: Renewing subscriber with "
|
|
"SID %S for event source %S", szSid, szEsid);
|
|
TraceTag(ttidEventServer, "Tickcount for renewal callback is %d",
|
|
GetTickCount());
|
|
|
|
EnterCriticalSection(&g_csListEventSource);
|
|
|
|
pes = PesFindEventSource(szEsid);
|
|
if (pes)
|
|
{
|
|
UPNP_SUBSCRIBER * psub;
|
|
|
|
psub = PsubFindSubscriber(pes, szSid);
|
|
if (psub)
|
|
{
|
|
// We don't care if the timer is currently executing because we're
|
|
// inside the critsec right now and so if we got here before the
|
|
// timer proc did, then we made it just in time to bump the
|
|
// renewal count so the proc doesn't delete this guy. If the timer
|
|
// proc had acquired the critsec first, then we couldn't possibly
|
|
// be here because it would have removed the subscriber from the
|
|
// list already
|
|
//
|
|
|
|
hTimerDel = psub->hTimer;
|
|
|
|
psub->cRenewals++;
|
|
|
|
// Delete the old renewal structure
|
|
//
|
|
delete [] psub->ur.szEsid;
|
|
delete [] psub->ur.szSid;
|
|
|
|
psub->ur.szEsid = NULL;
|
|
psub->ur.szSid = NULL;
|
|
psub->ur.iRenewal = psub->cRenewals;
|
|
|
|
psub->ur.szEsid = WszDupWsz(pes->szEsid);
|
|
if (!psub->ur.szEsid)
|
|
{
|
|
hr = E_OUTOFMEMORY;
|
|
}
|
|
else
|
|
{
|
|
psub->ur.szSid = WszDupWsz(psub->szSid);
|
|
if (!psub->ur.szSid)
|
|
{
|
|
hr = E_OUTOFMEMORY;
|
|
}
|
|
else
|
|
{
|
|
if (!*pcsecTimeout)
|
|
{
|
|
*pcsecTimeout = c_csecDefSubsTimeout;
|
|
}
|
|
else
|
|
{
|
|
*pcsecTimeout = max(c_csecMinSubsTimeout,
|
|
*pcsecTimeout);
|
|
}
|
|
|
|
psub->csecTimeout = *pcsecTimeout;
|
|
|
|
if (!CreateTimerQueueTimer(&psub->hTimer, g_hTimerQ,
|
|
RenewalCallback,
|
|
(LPVOID)&psub->ur,
|
|
*pcsecTimeout * 1000, 0,
|
|
WT_EXECUTEINTIMERTHREAD))
|
|
{
|
|
hr = HrFromLastWin32Error();
|
|
TraceError("HrRenewSubscriber: CreateTimerQueueTimer", hr);
|
|
}
|
|
else
|
|
{
|
|
TraceTag(ttidEventServer, "Started server renewal "
|
|
"timer for %d seconds at tickcount %d",
|
|
*pcsecTimeout, GetTickCount());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Return 412 Precondition Failed
|
|
hr = HRESULT_FROM_WIN32(ERROR_INVALID_FUNCTION);
|
|
TraceTag(ttidEventServer, "HrRenewSubscriber: Did not find"
|
|
" subscriber %S in event source %S", szSid, szEsid);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Return 404 Not Found
|
|
hr = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
|
|
TraceTag(ttidEventServer, "HrRenewSubscriber: Did not find event"
|
|
" source %S", szEsid);
|
|
}
|
|
|
|
LeaveCriticalSection(&g_csListEventSource);
|
|
|
|
// ISSUE-2000/12/1-danielwe: DeleteTimerQueueTimer() apparently
|
|
// will block if called on a timer that is currently executing
|
|
// its callback. It is unknown whether this is a bug in its
|
|
// implementation or not. To work around this problem, we'll
|
|
// leave the critsec so that the RenewalCallback() function can complete
|
|
// and then delete the timer. After deleting the timer, we signal the
|
|
// event that allows FreeEventSourceWorker() to delete the timer queue
|
|
//
|
|
|
|
if (hTimerDel)
|
|
{
|
|
DeleteTimerQueueTimer(g_hTimerQ, hTimerDel, NULL);
|
|
}
|
|
|
|
TraceError("HrRenewSubscriber", hr);
|
|
return hr;
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: HrRemoveSubscriber
|
|
//
|
|
// Purpose: Removes a subscriber from the list of subscribers to an
|
|
// event source
|
|
//
|
|
// Arguments:
|
|
// szEsid [in] Event source identifier
|
|
// szSid [in] Subscriber identifier (SID)
|
|
//
|
|
// Returns: S_OK if success, ERROR_FILE_NOT_FOUND if event source or
|
|
// subscription was not found
|
|
//
|
|
// Author: danielwe 4 Aug 2000
|
|
//
|
|
// Notes:
|
|
//
|
|
HRESULT HrRemoveSubscriber(LPCWSTR szEsid, LPCWSTR szSid)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
UPNP_EVENT_SOURCE * pes;
|
|
|
|
TraceTag(ttidEventServer, "HrRemoveSubscriber: Removing subscriber with "
|
|
"SID %S for event source %S", szSid, szEsid);
|
|
|
|
EnterCriticalSection(&g_csListEventSource);
|
|
|
|
pes = PesFindEventSource(szEsid);
|
|
if (pes)
|
|
{
|
|
UPNP_SUBSCRIBER * psubCur;
|
|
UPNP_SUBSCRIBER * psubPrev;
|
|
|
|
for (psubCur = psubPrev = pes->psubList;
|
|
psubCur;
|
|
psubPrev = psubCur,
|
|
psubCur = psubCur->psubNext)
|
|
{
|
|
if (!lstrcmpi(psubCur->szSid, szSid))
|
|
{
|
|
TraceTag(ttidEventServer, "HrRemoveSubscriber: Removing subscriber"
|
|
" %S from event source %S", psubCur->szSid, pes->szEsid);
|
|
|
|
// Remove subscriber from the list
|
|
|
|
if (psubCur == pes->psubList)
|
|
{
|
|
// Removal of head item
|
|
pes->psubList = psubCur->psubNext;
|
|
}
|
|
else
|
|
{
|
|
psubPrev->psubNext = psubCur->psubNext;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (psubCur)
|
|
{
|
|
TraceTag(ttidEventServer, "HrRemoveSubscriber: Removing subscriber"
|
|
" %S", szSid);
|
|
|
|
// Can no longer rely on this because once the subscriber is
|
|
// removed from the list, its owning event source is off limits
|
|
//
|
|
psubCur->pes = NULL;
|
|
|
|
QueueUserWorkItem(FreeSubscriberWorker, (LPVOID)psubCur,
|
|
WT_EXECUTELONGFUNCTION);
|
|
}
|
|
else
|
|
{
|
|
hr = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
|
|
TraceTag(ttidEventServer, "HrRemoveSubscriber: Did not find"
|
|
" subscriber %S in event source %S", szSid, szEsid);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
hr = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
|
|
TraceTag(ttidEventServer, "HrRemoveSubscriber: Did not find event"
|
|
" source %S", szEsid);
|
|
}
|
|
|
|
LeaveCriticalSection(&g_csListEventSource);
|
|
|
|
TraceError("HrRemoveSubscriber", hr);
|
|
return hr;
|
|
}
|
|
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: HrSubmitEvent
|
|
//
|
|
// Purpose: Submits an event for an event source
|
|
//
|
|
// Arguments:
|
|
// szEsid [in] Event source identifier
|
|
// szEventBody [in] Full XML body of event message
|
|
//
|
|
// Returns: S_OK if success, E_OUTOFMEMORY, or ERROR_FILE_NOT_FOUND if
|
|
// event source was not found
|
|
//
|
|
// Author: danielwe 4 Aug 2000
|
|
//
|
|
// Notes:
|
|
//
|
|
HRESULT HrSubmitEvent(LPCWSTR szEsid, LPCWSTR szEventBody)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
TraceTag(ttidEventServer, "HrSubmitEvent: Submitting event for %S ",
|
|
szEsid);
|
|
|
|
Assert(szEsid);
|
|
|
|
EnterCriticalSection(&g_csListEventSource);
|
|
|
|
UPNP_EVENT_SOURCE * pes;
|
|
|
|
if (!g_hInetSess)
|
|
{
|
|
hr = HrInitInternetSession();
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
Assert(g_hInetSess);
|
|
|
|
pes = PesFindEventSource(szEsid);
|
|
if (pes)
|
|
{
|
|
UPNP_SUBSCRIBER * psub;
|
|
|
|
for (psub = pes->psubList;
|
|
psub;
|
|
psub = psub->psubNext)
|
|
{
|
|
if (psub->iSeq > 0)
|
|
{
|
|
UPNP_EVENT * pevt;
|
|
|
|
pevt = new UPNP_EVENT;
|
|
if (!pevt)
|
|
{
|
|
hr = E_OUTOFMEMORY;
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
pevt->pevtNext = NULL;
|
|
|
|
pevt->szBody = WszDupWsz(szEventBody);
|
|
if (pevt->szBody)
|
|
{
|
|
AppendToEventQueue(psub, pevt);
|
|
}
|
|
else
|
|
{
|
|
delete pevt;
|
|
hr = E_OUTOFMEMORY;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
hr = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
|
|
TraceTag(ttidEventServer, "HrSubmitEvent: Did not find event"
|
|
" source %S", szEsid);
|
|
}
|
|
}
|
|
|
|
LeaveCriticalSection(&g_csListEventSource);
|
|
|
|
TraceError("HrSubmitEvent", hr);
|
|
return hr;
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: AppendToEventQueue
|
|
//
|
|
// Purpose: Adds the given event structure to the end of the event queue
|
|
// for that subscriber
|
|
//
|
|
// Arguments:
|
|
// psub [in] Subscriber to add event to
|
|
// pevt [in] Event to add
|
|
//
|
|
// Returns: Nothing
|
|
//
|
|
// Author: danielwe 4 Aug 2000
|
|
//
|
|
// Notes:
|
|
//
|
|
VOID AppendToEventQueue(UPNP_SUBSCRIBER * psub, UPNP_EVENT * pevt)
|
|
{
|
|
if (psub->pevtQueue)
|
|
{
|
|
psub->pevtQueueTail->pevtNext = pevt;
|
|
psub->pevtQueueTail = pevt;
|
|
|
|
TraceTag(ttidEventServer, "Adding %p to event queue for sub %S",
|
|
pevt, psub->szSid);
|
|
}
|
|
else
|
|
{
|
|
AssertSz(!psub->pevtQueueTail, "If head is NULL so should tail be too");
|
|
|
|
psub->pevtQueue = pevt;
|
|
psub->pevtQueueTail = pevt;
|
|
|
|
TraceTag(ttidEventServer, "Adding %p to event queue for sub %S and"
|
|
" signalling event", pevt, psub->szSid);
|
|
|
|
// Signal the event that says that a new item is ready on the queue
|
|
//
|
|
SetEvent(psub->hEventQ);
|
|
}
|
|
|
|
Assert(!pevt->pevtNext);
|
|
AssertSz(psub->pevtQueueTail == pevt, "Didn't insert at the tail?");
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: HrSubmitEventZero
|
|
//
|
|
// Purpose: Submits the initial notify event for a subscriber
|
|
//
|
|
// Arguments:
|
|
// szEsid [in] Event source identifier
|
|
// szSid [in] Subscriber to submit the event to
|
|
// szEventBody [in] XML body of event message
|
|
//
|
|
// Returns: S_OK if success, E_OUTOFMEMORY
|
|
//
|
|
// Author: danielwe 4 Aug 2000
|
|
//
|
|
// Notes: The subscriber's event queue MUST be empty when this function
|
|
// is called
|
|
//
|
|
HRESULT HrSubmitEventZero(LPCWSTR szEsid, LPCWSTR szSid, LPCWSTR szEventBody)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
UPNP_EVENT_SOURCE * pes;
|
|
UPNP_SUBSCRIBER * psub;
|
|
|
|
EnterCriticalSection(&g_csListEventSource);
|
|
|
|
if (!g_hInetSess)
|
|
{
|
|
hr = HrInitInternetSession();
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
Assert(g_hInetSess);
|
|
|
|
pes = PesFindEventSource(szEsid);
|
|
if (pes)
|
|
{
|
|
UPNP_EVENT * pevt;
|
|
|
|
psub = PsubFindSubscriber(pes, szSid);
|
|
if (psub)
|
|
{
|
|
pevt = new UPNP_EVENT;
|
|
if (!pevt)
|
|
{
|
|
hr = E_OUTOFMEMORY;
|
|
}
|
|
else
|
|
{
|
|
pevt->pevtNext = NULL;
|
|
|
|
pevt->szBody = WszDupWsz(szEventBody);
|
|
if (pevt->szBody)
|
|
{
|
|
AssertSz(!psub->pevtQueue, "Event queue is not empty!!!");
|
|
AppendToEventQueue(psub, pevt);
|
|
}
|
|
else
|
|
{
|
|
delete pevt;
|
|
hr = E_OUTOFMEMORY;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
TraceTag(ttidEventServer, "Interesting.. Subscriber %S was removed"
|
|
" before event zero was submitted for a subscriber?? Oh well"
|
|
" no big deal.", szSid);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
TraceTag(ttidEventServer, "Interesting.. Event source %S was removed"
|
|
" before event zero was submitted for a subscriber?? Oh well"
|
|
" no big deal.", szEsid);
|
|
}
|
|
|
|
LeaveCriticalSection(&g_csListEventSource);
|
|
}
|
|
|
|
TraceError("HrSubmitEventZero", hr);
|
|
return hr;
|
|
}
|
|
|
|
static const WCHAR c_szHeaderNt[] = L"NT";
|
|
static const WCHAR c_szHeaderNts[] = L"NTS";
|
|
static const WCHAR c_szHeaderSid[] = L"SID";
|
|
static const WCHAR c_szHeaderSeq[] = L"SEQ";
|
|
static const WCHAR c_szHeaderContentType[] = L"Content-Type";
|
|
|
|
const WCHAR c_szNotifyMethod[] = L"NOTIFY";
|
|
|
|
const WCHAR c_szHttpVersion[] = L"HTTP/1.1";
|
|
|
|
static const DWORD c_cchHeaderNt = celems(c_szHeaderNt);
|
|
static const DWORD c_cchHeaderNts = celems(c_szHeaderNts);
|
|
static const DWORD c_cchHeaderSid = celems(c_szHeaderSid);
|
|
static const DWORD c_cchHeaderSeq = celems(c_szHeaderSeq);
|
|
static const DWORD c_cchHeaderContentType = celems(c_szHeaderContentType);
|
|
|
|
static const WCHAR c_szNt[] = L"upnp:event";
|
|
static const WCHAR c_szNts[] = L"upnp:propchange";
|
|
|
|
static const DWORD c_cchNt = celems(c_szNt);
|
|
static const DWORD c_cchNts = celems(c_szNts);
|
|
|
|
static const WCHAR c_szColon[] = L":";
|
|
static const WCHAR c_szCrlf[] = L"\r\n";
|
|
|
|
static const DWORD c_cchColon = celems(c_szColon);
|
|
static const DWORD c_cchCrlf = celems(c_szCrlf);
|
|
|
|
const WCHAR c_szTextXml[] = L"text/xml";
|
|
|
|
const DWORD c_cchTextXml = celems(c_szTextXml);
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: HrComposeUpnpNotifyHeaders
|
|
//
|
|
// Purpose: Composes the headers for a NOTIFY request to be sent to a
|
|
// subscriber.
|
|
//
|
|
// Arguments:
|
|
// iSeq [in] Sequence number of event
|
|
// szSid [in] SID of subscriber
|
|
// pszHeaders [out] Returns newly allocated headers in proper format
|
|
//
|
|
// Returns: S_OK if success or E_OUTOFMEMORY if no memory
|
|
//
|
|
// Author: danielwe 12 Oct 1999
|
|
//
|
|
// Notes: Caller must free pszHeaders with delete []
|
|
//
|
|
HRESULT HrComposeUpnpNotifyHeaders(DWORD iSeq, LPCTSTR szSid,
|
|
LPWSTR *pszHeaders)
|
|
{
|
|
DWORD cchHeaders = 0;
|
|
WCHAR szSeq[32];
|
|
LPWSTR szHeaders;
|
|
DWORD iNumOfBytes = 0;
|
|
HRESULT hr = S_OK;
|
|
|
|
wsprintf(szSeq, L"%d", iSeq);
|
|
|
|
cchHeaders += c_cchHeaderNt + c_cchColon + c_cchNt + c_cchCrlf;
|
|
cchHeaders += c_cchHeaderNts + c_cchColon + c_cchNts + c_cchCrlf;
|
|
cchHeaders += c_cchHeaderSid + c_cchColon + lstrlen(szSid) + c_cchCrlf;
|
|
cchHeaders += c_cchHeaderSeq + c_cchColon + lstrlen(szSeq) + c_cchCrlf;
|
|
cchHeaders += c_cchHeaderContentType + c_cchColon + c_cchTextXml + c_cchCrlf;
|
|
|
|
szHeaders = new WCHAR[cchHeaders + 1];
|
|
if (szHeaders)
|
|
{
|
|
iNumOfBytes += wsprintf(szHeaders + iNumOfBytes, L"%s%s%s%s",
|
|
c_szHeaderNt, c_szColon, c_szNt, c_szCrlf);
|
|
iNumOfBytes += wsprintf(szHeaders + iNumOfBytes, L"%s%s%s%s",
|
|
c_szHeaderNts, c_szColon, c_szNts, c_szCrlf);
|
|
iNumOfBytes += wsprintf(szHeaders + iNumOfBytes, L"%s%s%s%s",
|
|
c_szHeaderSid, c_szColon, szSid, c_szCrlf);
|
|
iNumOfBytes += wsprintf(szHeaders + iNumOfBytes, L"%s%s%s%s",
|
|
c_szHeaderSeq, c_szColon, szSeq, c_szCrlf);
|
|
iNumOfBytes += wsprintf(szHeaders + iNumOfBytes, L"%s%s%s%s",
|
|
c_szHeaderContentType, c_szColon,
|
|
c_szTextXml, c_szCrlf);
|
|
|
|
*pszHeaders = szHeaders;
|
|
}
|
|
else
|
|
{
|
|
hr = E_OUTOFMEMORY;
|
|
}
|
|
|
|
TraceError("HrComposeUpnpNotifyHeaders", hr);
|
|
return hr;
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: HrSubmitNotifyToSubscriber
|
|
//
|
|
// Purpose: Submits a NOTIFY request to the given URL
|
|
//
|
|
// Arguments:
|
|
// szHeaders [in] Headers of request
|
|
// szBody [in] Body of request (in XML)
|
|
// szUrl [in] URL to send request to
|
|
//
|
|
// Returns: S_OK if successful, E_UNEXPECTED if the internet session
|
|
// was not initialized
|
|
//
|
|
// Author: danielwe 7 Aug 2000
|
|
//
|
|
// Notes:
|
|
//
|
|
HRESULT HrSubmitNotifyToSubscriber(LPCWSTR szHeaders, LPCWSTR szBody,
|
|
LPCWSTR szUrl)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
URL_COMPONENTS urlComp = {0};
|
|
WCHAR szHostName[INTERNET_MAX_HOST_NAME_LENGTH];
|
|
WCHAR szUrlPath[INTERNET_MAX_URL_LENGTH];
|
|
|
|
urlComp.dwStructSize = sizeof(URL_COMPONENTS);
|
|
|
|
urlComp.lpszHostName = (LPWSTR) &szHostName;
|
|
urlComp.dwHostNameLength = INTERNET_MAX_HOST_NAME_LENGTH;
|
|
|
|
urlComp.lpszUrlPath = (LPWSTR) &szUrlPath;
|
|
urlComp.dwUrlPathLength = INTERNET_MAX_URL_LENGTH;
|
|
|
|
if (InternetCrackUrl(szUrl, 0, 0, &urlComp))
|
|
{
|
|
// Hack for not able to send to loopback in LocalService
|
|
if(0 == lstrcmp(szHostName, L"127.0.0.1"))
|
|
{
|
|
lstrcpy(szHostName, L"localhost");
|
|
}
|
|
HINTERNET hinC;
|
|
|
|
if (g_hInetSess)
|
|
{
|
|
hinC = InternetConnect(g_hInetSess, szHostName, urlComp.nPort,
|
|
NULL, NULL, INTERNET_SERVICE_HTTP, 0, 0);
|
|
if (hinC)
|
|
{
|
|
HINTERNET hinR;
|
|
|
|
TraceTag(ttidEventServer, "Connected to host %S:%d.",
|
|
szHostName, urlComp.nPort);
|
|
hinR = HttpOpenRequest(hinC, c_szNotifyMethod, szUrlPath,
|
|
c_szHttpVersion, NULL, NULL,
|
|
INTERNET_FLAG_KEEP_CONNECTION, 0);
|
|
if (hinR)
|
|
{
|
|
LPSTR szaBody;
|
|
|
|
TraceTag(ttidEventServer, "Sending the following request to "
|
|
"subscriber at %S:", szUrlPath);
|
|
TraceTag(ttidEventServer, "-------------------------------------------");
|
|
TraceTag(ttidEventServer, "\n%S\n%S", szHeaders, szBody);
|
|
TraceTag(ttidEventServer, "-------------------------------------------");
|
|
|
|
szaBody = Utf8FromWsz(szBody);
|
|
if (szaBody)
|
|
{
|
|
if (!HttpSendRequest(hinR, szHeaders, 0, (LPVOID)szaBody,
|
|
CbOfSza(szaBody)))
|
|
{
|
|
TraceTag(ttidError, "Failed to send request [http://%S:%d%S]",
|
|
szHostName, urlComp.nPort, szUrlPath);
|
|
hr = HrFromLastWin32Error();
|
|
TraceError("HrSubmitNotifyToSubscriber: "
|
|
"HttpSendRequest", hr);
|
|
}
|
|
else
|
|
{
|
|
TraceTag(ttidEventServer, "Request sent successfully!");
|
|
}
|
|
|
|
delete [] szaBody;
|
|
}
|
|
else
|
|
{
|
|
hr = E_OUTOFMEMORY;
|
|
TraceError("HrSubmitNotifyToSubscriber: SzFromWsz", hr);
|
|
}
|
|
|
|
InternetCloseHandle(hinR);
|
|
}
|
|
else
|
|
{
|
|
hr = HrFromLastWin32Error();
|
|
TraceError("HrSubmitNotifyToSubscriber: HttpOpenRequest",
|
|
hr);
|
|
}
|
|
|
|
InternetCloseHandle(hinC);
|
|
}
|
|
else
|
|
{
|
|
hr = HrFromLastWin32Error();
|
|
TraceError("HrSubmitNotifyToSubscriber: InternetConnect",
|
|
hr);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
hr = E_UNEXPECTED;
|
|
TraceError("HrSubmitEventToSubscriber: No internet session!", hr);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
hr = HrFromLastWin32Error();
|
|
TraceError("HrSubmitNotifyToSubscriber: InternetCrackUrl", hr);
|
|
}
|
|
|
|
TraceError("HrSubmitNotifyToSubscriber", hr);
|
|
return hr;
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: PesFindEventSource
|
|
//
|
|
// Purpose: Helper function to return the event source identified by
|
|
// szEsid.
|
|
//
|
|
// Arguments:
|
|
// szEsid [in] Event source identifier
|
|
//
|
|
// Returns: Pointer to event source that matches the identifier passed in
|
|
// or NULL if not found
|
|
//
|
|
// Author: danielwe 4 Aug 2000
|
|
//
|
|
// Notes:
|
|
//
|
|
UPNP_EVENT_SOURCE *PesFindEventSource(LPCWSTR szEsid)
|
|
{
|
|
UPNP_EVENT_SOURCE * pesCur;
|
|
|
|
for (pesCur = g_pesList; pesCur; pesCur = pesCur->pesNext)
|
|
{
|
|
if (!lstrcmpi(pesCur->szEsid, szEsid))
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
return pesCur;
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: PsubFindSubscriber
|
|
//
|
|
// Purpose: Helper function to return the subscriber identified by the
|
|
// SID passed in
|
|
//
|
|
// Arguments:
|
|
// pes [in] Event source to search in
|
|
// szSid [in] Subscription identifier
|
|
//
|
|
// Returns: Pointer to subscriber that matches the SID or NULL if not
|
|
// found
|
|
//
|
|
// Author: danielwe 4 Aug 2000
|
|
//
|
|
// Notes:
|
|
//
|
|
UPNP_SUBSCRIBER *PsubFindSubscriber(UPNP_EVENT_SOURCE *pes, LPCWSTR szSid)
|
|
{
|
|
UPNP_SUBSCRIBER * psubCur;
|
|
|
|
for (psubCur = pes->psubList;
|
|
psubCur;
|
|
psubCur = psubCur->psubNext)
|
|
{
|
|
if (!lstrcmpi(psubCur->szSid, szSid))
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
return psubCur;
|
|
}
|
|
|
|
//
|
|
// Debug functions
|
|
//
|
|
|
|
VOID DbgDumpSubscriber(UPNP_SUBSCRIBER *psub)
|
|
{
|
|
SYSTEMTIME st;
|
|
WCHAR szLocalDate[255];
|
|
WCHAR szLocalTime[255];
|
|
|
|
FileTimeToSystemTime(&psub->ftTimeout, &st);
|
|
GetDateFormat(LOCALE_USER_DEFAULT, DATE_LONGDATE, &st, NULL,
|
|
szLocalDate, 255);
|
|
GetTimeFormat(LOCALE_USER_DEFAULT, 0, &st, NULL,
|
|
szLocalTime, 255);
|
|
|
|
TraceTag(ttidEventServer, "Subscription at address 0x%08X", psub);
|
|
TraceTag(ttidEventServer, "--------------------------------------");
|
|
|
|
TraceTag(ttidEventServer, "Subscription timeout is %d seconds from "
|
|
"now. It expires at %S %S", psub->csecTimeout,
|
|
szLocalDate, szLocalTime);
|
|
|
|
TraceTag(ttidEventServer, "Sequence # : %d", psub->iSeq);
|
|
TraceTag(ttidEventServer, "Callback Url: %S", psub->rgszUrl[0]);
|
|
TraceTag(ttidEventServer, "SID : %S", psub->szSid);
|
|
TraceTag(ttidEventServer, "--------------------------------------");
|
|
}
|
|
|
|
VOID DbgDumpEventSource(UPNP_EVENT_SOURCE *pes)
|
|
{
|
|
DWORD iVar;
|
|
UPNP_SUBSCRIBER * psubCur;
|
|
|
|
TraceTag(ttidEventServer, "Event source 0x%08X - %S", pes, pes->szEsid);
|
|
TraceTag(ttidEventServer, "-------------------------------------------------");
|
|
|
|
if (pes->psubList)
|
|
{
|
|
for (psubCur = pes->psubList; psubCur; psubCur = psubCur->psubNext)
|
|
{
|
|
DbgDumpSubscriber(psubCur);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
TraceTag(ttidEventServer, "NO SUBSCRIBERS");
|
|
}
|
|
|
|
TraceTag(ttidEventServer, "-------------------------------------------------");
|
|
}
|
|
|
|
VOID DbgDumpListEventSource()
|
|
{
|
|
UPNP_EVENT_SOURCE * pesCur;
|
|
|
|
if (g_pesList)
|
|
{
|
|
EnterCriticalSection(&g_csListEventSource);
|
|
|
|
for (pesCur = g_pesList; pesCur; pesCur = pesCur->pesNext)
|
|
{
|
|
DbgDumpEventSource(pesCur);
|
|
}
|
|
|
|
LeaveCriticalSection(&g_csListEventSource);
|
|
}
|
|
else
|
|
{
|
|
TraceTag(ttidEventServer, "Event source list is EMPTY!");
|
|
}
|
|
}
|