windows-nt/Source/XPSP1/NT/net/upnp/ssdp/ssdpapi/notifyc.cpp
2020-09-26 16:20:57 +08:00

846 lines
24 KiB
C++

#include "nt.h"
#include "ntrtl.h"
#include "nturtl.h"
#include "objbase.h"
#include <rpcasync.h> // I_RpcExceptionFilter
#include "ssdp.h"
#include "status.h"
#include "list.h"
#include "ssdpapi.h"
#include "common.h"
#include "ncmem.h"
#include "ncdefine.h"
#include "ncdebug.h"
#include "ssdpfuncc.h"
#include "ssdpparser.h"
#include "nccom.h"
#include "ncstring.h"
static LIST_ENTRY listNotify;
PCONTEXT_HANDLE_TYPE g_pSyncContext = NULL;
static HANDLE g_hListNotify = INVALID_HANDLE_VALUE;
static long g_fExiting = 0; // set to 1 when get notification thread is exiting
static long g_fSyncInited = FALSE; // TRUE if SyncHandle is initialized
static HANDLE g_hThread = INVALID_HANDLE_VALUE;
HANDLE g_hLaunchEvent = INVALID_HANDLE_VALUE;
RTL_RESOURCE g_rsrcReg;
static LONG g_lNotKey = 0;
extern LONG cInitialized;
// To-do: Rpc error server is too busy after GetNotificationRpc is in process.
// signal the semaphore in rundown.
VOID AddToListClientNotify(PSSDP_CLIENT_NOTIFY NotifyRequest);
DWORD WINAPI GetNotificationLoop(LPVOID lpvThreadParam);
VOID CallbackOnNotification(MessageList *list);
// Purpose: takes the g_hListNotify mutex and returns
// Note: This should be used only by code that lives on the Notify thread.
// Code that can be executed when servicing an SSDP api call must use
// MsgEnterListNotify instead. The purpose of having two functions
// is to save the notify thread (which has no message loop) from
// going through extra layers of method-call goop.
VOID EnterListNotify()
{
DWORD dwResult;
TraceTag(ttidSsdpCNotify, "Entering g_hListNotify...");
dwResult = ::WaitForSingleObject(g_hListNotify, INFINITE);
TraceTag(ttidSsdpCNotify, "...acquired g_hListNotify");
AssertSz(WAIT_TIMEOUT != dwResult,
"EnterListNotify: unexpected return value");
AssertSz(WAIT_ABANDONED != dwResult,
"EnterListNotify: invalid mutex state");
AssertSz(WAIT_OBJECT_0 == dwResult,
"EnterListNotify: unknown return value");
}
// Purpose: takes the g_hListNotify mutex and returns, servicing the message
// pump while waiting
// Note: This must be used instead of EnterListNotify by any code that
// can be executed on the client thread. Code that lives exclusively
// on the Notify thread should use EnterListNotify() instead.
VOID MsgEnterListNotify()
{
HRESULT hr;
DWORD dwResult;
TraceTag(ttidSsdpCNotify, "Entering HrMyWaitForMultipleHandles");
hr = HrMyWaitForMultipleHandles(0,
INFINITE,
1,
&g_hListNotify,
&dwResult);
// We shouldn't get RPC_S_CALLPENDING because we're waiting forever
//
Assert(SUCCEEDED(hr));
}
// Purpose: frees the g_hListNotify mutex and returns
VOID LeaveListNotify()
{
BOOL fResult;
TraceTag(ttidSsdpCNotify, "Releasing Mutex");
fResult = ::ReleaseMutex(g_hListNotify);
TraceTag(ttidSsdpCNotify, "Mutex is released");
if (!fResult)
{
TraceLastWin32Error("LeaveListNotify");
}
}
VOID FinishExitNotificationThread()
{
TraceTag(ttidSsdpCNotify, "Removing Sync Handle %x", g_pSyncContext);
RpcTryExcept
{
RemoveSyncHandle(&g_pSyncContext);
}
RpcExcept(I_RpcExceptionFilter(RpcExceptionCode()))
{
unsigned long ExceptionCode = RpcExceptionCode();
TraceTag(ttidSsdpCNotify, "FinishExit... reported exception 0x%lx = %ld",
ExceptionCode, ExceptionCode);
}
RpcEndExcept
InterlockedExchange(&g_fSyncInited, 0);
}
VOID CleanupNotificationThread()
{
INT nStatus;
TraceTag(ttidSsdpCNotify, "Cleaning up notif thread: %d", g_hThread);
PLIST_ENTRY p;
PLIST_ENTRY pListHead = &listNotify;
TraceTag(ttidSsdpCNotify, "----- Cleanup SSDP Client Notify List -----");
MsgEnterListNotify();
p = pListHead->Flink;
while (p != pListHead)
{
PSSDP_CLIENT_NOTIFY NotifyRequest;
NotifyRequest = CONTAINING_RECORD (p, SSDP_CLIENT_NOTIFY, linkage);
p = p->Flink;
DeregisterNotification(NotifyRequest);
}
LeaveListNotify();
if (g_hThread && g_hThread != INVALID_HANDLE_VALUE)
{
TraceTag(ttidSsdpCNotify, "Incrementing g_fExiting");
InterlockedExchange(&g_fExiting, 1);
RpcTryExcept
{
TraceTag(ttidSsdpCNotify, "Wakie wakie!");
nStatus = WakeupGetNotificationRpc(g_pSyncContext);
TraceTag(ttidSsdpCNotify, "Wake up returned status %x\n", nStatus);
if (nStatus != 0 )
{
// Big problem, damage control
FinishExitNotificationThread();
}
}
RpcExcept(I_RpcExceptionFilter(RpcExceptionCode()))
{
unsigned long ExceptionCode = RpcExceptionCode();
SetLastError(ExceptionCode);
TraceTag(ttidSsdpCNotify, "Wakeup: Runtime reported exception 0x%lx = %ld", ExceptionCode, ExceptionCode);
FinishExitNotificationThread();
}
RpcEndExcept
// note: we have to wait for our notify thread to exit _before_ we
// destroy g_hListNotify, as the notify thread might be holding it
// Wait for the thread to exit since we've just told it to wake up and die
TraceTag(ttidSsdpCNotify, "Waiting for the notification loop thread to exit.\n");
DWORD dwResult = 0;
HrMyWaitForMultipleHandles(
0,
INFINITE,
1,
&g_hThread,
&dwResult);
CloseHandle(g_hThread);
g_hThread = INVALID_HANDLE_VALUE;
}
{
BOOL fResult;
fResult = ::CloseHandle(g_hListNotify);
AssertSz(fResult, "CleanupListNotify: CloseHandle(g_hListNotify) failed");
g_hListNotify = INVALID_HANDLE_VALUE;
}
}
HANDLE WINAPI RegisterNotification (NOTIFY_TYPE nt, CHAR * szType,
CHAR *szEventUrl,
SERVICE_CALLBACK_FUNC fnCallback,
VOID *pContext)
{
PSSDP_CLIENT_NOTIFY ClientNotify = NULL;
INT Size = sizeof(SSDP_CLIENT_NOTIFY);
PCONTEXT_HANDLE_TYPE phContext;
INT status;
DWORD ThreadId;
SSDP_REGISTER_INFO info = {0};
SSDP_REGISTER_INFO *pinfo = &info;
BOOL fHoldingListNotify = FALSE;
BOOL bHoldingResource = FALSE;
if (!cInitialized)
{
SetLastError(ERROR_NOT_READY);
return INVALID_HANDLE_VALUE;
}
switch (nt)
{
case NOTIFY_PROP_CHANGE:
if (szEventUrl == NULL || szType != NULL)
{
SetLastError(ERROR_INVALID_PARAMETER);
return INVALID_HANDLE_VALUE;
}
break;
case NOTIFY_ALIVE:
if (szType == NULL || szEventUrl != NULL)
{
SetLastError(ERROR_INVALID_PARAMETER);
return INVALID_HANDLE_VALUE;
}
break;
default:
SetLastError(ERROR_INVALID_PARAMETER);
return INVALID_HANDLE_VALUE;
}
if (fnCallback == NULL)
{
SetLastError(ERROR_INVALID_PARAMETER);
return INVALID_HANDLE_VALUE;
}
ClientNotify = (PSSDP_CLIENT_NOTIFY) malloc(Size);
if (ClientNotify == NULL)
{
TraceTag(ttidSsdpCNotify, "Couldn't allocate memory for "
"ClientNotifyRequest for %s", szType);
SetLastError(ERROR_NOT_ENOUGH_MEMORY);
return INVALID_HANDLE_VALUE;
}
ZeroMemory(ClientNotify, sizeof(SSDP_CLIENT_NOTIFY));
switch (nt)
{
case NOTIFY_PROP_CHANGE:
ClientNotify->Type = SSDP_CLIENT_EVENT_SIGNATURE;
ClientNotify->szType = NULL;
ClientNotify->szEventUrl = (CHAR *) malloc(strlen(szEventUrl)+1);
if (ClientNotify->szEventUrl == NULL)
{
TraceTag(ttidSsdpCNotify, "Couldn't allocate memory for "
"szEventUrl for %s", szEventUrl);
SetLastError(ERROR_NOT_ENOUGH_MEMORY);
free(ClientNotify);
return INVALID_HANDLE_VALUE;
}
strcpy(ClientNotify->szEventUrl, szEventUrl);
break;
case NOTIFY_ALIVE:
ClientNotify->Type = SSDP_CLIENT_NOTIFY_SIGNATURE;
ClientNotify->szEventUrl = NULL;
ClientNotify->szType = (CHAR *) malloc(strlen(szType)+1);
if (ClientNotify->szType == NULL)
{
TraceTag(ttidSsdpCNotify, "Couldn't allocate memory for "
"szType for %s", szType);
SetLastError(ERROR_NOT_ENOUGH_MEMORY);
free(ClientNotify);
return INVALID_HANDLE_VALUE;
}
strcpy(ClientNotify->szType, szType);
break;
default:
ASSERT(FALSE);
return INVALID_HANDLE_VALUE;
}
if (InterlockedCompareExchange(&g_fSyncInited, 1, 0) == 0)
{
// First time ever going into this function
Assert(IsListEmpty(&listNotify));
RpcTryExcept
{
status = InitializeSyncHandle(&g_pSyncContext);
TraceTag(ttidSsdpCNotify, "InitializeSyncHandler returned %d.",
status);
if (status)
{
SetLastError(status);
InterlockedExchange(&g_fSyncInited, 0);
SetEvent(g_hLaunchEvent);
goto cleanup;
}
}
RpcExcept(I_RpcExceptionFilter(RpcExceptionCode()))
{
unsigned long ExceptionCode = RpcExceptionCode();
SetLastError(ExceptionCode);
TraceTag(ttidSsdpCNotify, "Runtime reported exception 0x%lx = %ld",
ExceptionCode, ExceptionCode);
InterlockedExchange(&g_fSyncInited, 0);
SetEvent(g_hLaunchEvent);
goto cleanup;
}
RpcEndExcept
// create the thread to continuously get notifications
g_hThread = (HANDLE) CreateThread(NULL, 0, GetNotificationLoop,
(LPVOID) g_pSyncContext, 0, &ThreadId);
if (!g_hThread || g_hThread == INVALID_HANDLE_VALUE)
{
FinishExitNotificationThread();
InterlockedExchange(&g_fSyncInited, 0);
SetLastError(ERROR_OUTOFMEMORY);
}
else
{
// reset this flag!
InterlockedExchange(&g_fExiting, 0);
}
// Let other threads go
SetEvent(g_hLaunchEvent);
}
else
{
DWORD dwResult;
dwResult = WaitForSingleObject(g_hLaunchEvent, INFINITE);
Assert(WAIT_OBJECT_0 == dwResult);
}
// Somehow the thread wasn't created
if (!InterlockedExchange(&g_fSyncInited, g_fSyncInited))
{
TraceTag(ttidSsdpCNotify, "Thread wasn't created! Aborting...");
SetLastError(ERROR_NOT_READY);
goto cleanup;
}
bHoldingResource = RtlAcquireResourceShared(&g_rsrcReg, TRUE);
if(bHoldingResource)
{
__try
{
RpcTryExcept
{
status = RegisterNotificationRpc(&(ClientNotify->HandleServer),
g_pSyncContext, nt, szType,
szEventUrl, &pinfo);
if (status)
{
TraceTag(ttidError, "RegisterNotification: "
"RegisterNotificationRpc failed! %d", status);
SetLastError(status);
goto cleanup;
}
}
RpcExcept(I_RpcExceptionFilter(RpcExceptionCode()))
{
unsigned long ExceptionCode = RpcExceptionCode();
SetLastError(ExceptionCode);
TraceTag(ttidSsdpCNotify, "Runtime reported exception 0x%lx = %ld", ExceptionCode, ExceptionCode);
goto cleanup;
}
RpcEndExcept
// add to the client notify request list
ClientNotify->Size = Size;
ClientNotify->Callback = fnCallback;
ClientNotify->Context = pContext;
if (pinfo)
{
Assert(pinfo->szSid);
ClientNotify->szSid = SzaDupSza(pinfo->szSid);
ClientNotify->csecTimeout = pinfo->csecTimeout;
// Done with this
midl_user_free(pinfo->szSid);
}
MsgEnterListNotify();
TraceTag(ttidSsdpCNotify, "Adding %p to list", ClientNotify);
InsertHeadList(&listNotify, &(ClientNotify->linkage));
TraceTag(ttidSsdpCNotify, "Leaving mutex @382");
LeaveListNotify();
}
__finally
{
RtlReleaseResource(&g_rsrcReg);
}
}
TraceTag(ttidSsdpCNotify, "RegisterNotification returning %p", ClientNotify);
return ClientNotify;
cleanup:
if (ClientNotify != NULL)
{
free(ClientNotify->szType);
free(ClientNotify->szEventUrl);
free(ClientNotify->szSid);
free(ClientNotify);
}
return INVALID_HANDLE_VALUE;
}
BOOL WINAPI DeregisterNotification(HANDLE hNotification)
{
INT status = 0;
BOOL fLast = FALSE;
BOOL fRet = FALSE;
PSSDP_CLIENT_NOTIFY ClientNotify = (PSSDP_CLIENT_NOTIFY) hNotification;
if (!ClientNotify)
{
SetLastError(ERROR_INVALID_PARAMETER);
return FALSE;
}
TraceTag(ttidSsdpCNotify, "DeregisterNotification was passed %p:%p",
ClientNotify, &ClientNotify->HandleServer);
if (!cInitialized)
{
SetLastError(ERROR_NOT_READY);
return FALSE;
}
MsgEnterListNotify();
_try
{
if ((ClientNotify->Type != SSDP_CLIENT_NOTIFY_SIGNATURE &&
ClientNotify->Type != SSDP_CLIENT_EVENT_SIGNATURE) ||
ClientNotify->Size != sizeof(SSDP_CLIENT_NOTIFY))
{
LeaveListNotify();
SetLastError(ERROR_INVALID_PARAMETER);
return FALSE;
}
}
_except (1)
{
LeaveListNotify();
unsigned long ExceptionCode = _exception_code();
TraceTag(ttidSsdpCNotify, "Exception 0x%lx = %ld occurred in DeregisterNotification", ExceptionCode, ExceptionCode);
SetLastError(ExceptionCode);
return FALSE;
}
TraceTag(ttidSsdpCNotify, "Removing %p from list", ClientNotify);
RemoveEntryList(&ClientNotify->linkage);
LeaveListNotify();
RpcTryExcept
{
status = DeregisterNotificationRpc(&ClientNotify->HandleServer, fLast);
if (status != 0)
{
TraceTag(ttidSsdpCNotify, "Deregister returned %d", status);
}
ABORT_ON_FAILURE(status);
}
RpcExcept(I_RpcExceptionFilter(RpcExceptionCode()))
{
unsigned long ExceptionCode = RpcExceptionCode();
SetLastError(ExceptionCode);
TraceTag(ttidSsdpCNotify, "Runtime reported exception 0x%lx = %ld", ExceptionCode, ExceptionCode);
goto cleanup;
}
RpcEndExcept
TraceTag(ttidSsdpCNotify, "Checking if listNotify is empty\n");
fRet = TRUE;
cleanup:
free(ClientNotify->szType);
free(ClientNotify->szSid);
free(ClientNotify->szEventUrl);
free(ClientNotify);
return fRet;
}
VOID FreeMessageList(MessageList *list)
{
INT i;
if (list != NULL)
{
for (i = 0; i < list->size; i++)
{
SSDP_REQUEST *pSsdpRequest;
pSsdpRequest = list->list+i;
FreeSsdpRequest(pSsdpRequest);
}
free(list->list);
free(list);
}
}
const DWORD c_cRetryMax = 3;
DWORD WINAPI GetNotificationLoop(LPVOID lpvThreadParam)
{
DWORD cRetries = c_cRetryMax;
ULONG ulExceptionCode = 0;
while (1)
{
MessageList *list = NULL;
PCONTEXT_HANDLE_TYPE pSemaphore = (PCONTEXT_HANDLE_TYPE) lpvThreadParam;
if (!cRetries)
{
break;
}
// reset this to make sure
ulExceptionCode = 0;
// To-do: Check if we are exiting?
// To-do: Check memory leak.
RpcTryExcept
{
TraceTag(ttidSsdpCNotify, "Calling GetNotificationRpc...");
GetNotificationRpc(pSemaphore, &list);
}
RpcExcept(I_RpcExceptionFilter(RpcExceptionCode()))
{
ulExceptionCode = RpcExceptionCode();
TraceTag(ttidSsdpCNotify, "GetNotif: Runtime reported exception "
"0x%lx = %ld", ulExceptionCode, ulExceptionCode);
}
RpcEndExcept
if (InterlockedExchange(&g_fExiting, g_fExiting) != 0)
{
TraceTag(ttidSsdpCNotify, "GetNotif is exiting.");
FreeMessageList(list);
break;
}
if (list != 0)
{
PrintSsdpMessageList(list);
CallbackOnNotification(list);
}
// Decrement the retry count if there is an error
//
if (NOERROR != ulExceptionCode)
{
cRetries--;
}
else
{
cRetries = c_cRetryMax;
}
}
FinishExitNotificationThread();
TraceTag(ttidSsdpCNotify, "Thread is exiting");
return 0;
}
BOOL InitializeListNotify()
{
Assert(INVALID_HANDLE_VALUE == g_hListNotify);
g_hListNotify = ::CreateMutex(NULL, TRUE, NULL);
if (!g_hListNotify)
{
return FALSE;
}
// note: we don't need to call EnterListNotify() since we passed
// bInitialOwner == TRUE above
//
InitializeListHead(&listNotify);
LeaveListNotify();
return TRUE;
}
BOOL IsInListNotify(CHAR *szType)
{
PLIST_ENTRY p;
PLIST_ENTRY pListHead = &listNotify;
MsgEnterListNotify();
p = pListHead->Flink;
while (p != pListHead)
{
PSSDP_CLIENT_NOTIFY NotifyRequest;
NotifyRequest = CONTAINING_RECORD (p, SSDP_CLIENT_NOTIFY, linkage);
p = p->Flink;
if (NotifyRequest->szType && !lstrcmpi(NotifyRequest->szType, szType))
{
LeaveListNotify();
return TRUE;
}
}
LeaveListNotify();
return FALSE;
}
VOID CallbackOnNotification(MessageList *list)
{
INT i;
PLIST_ENTRY p;
PLIST_ENTRY pListHead = &listNotify;
TraceTag(ttidSsdpCNotify, "Callback on notification list.");
TraceTag(ttidSsdpCNotify, "Trying to get the exclusive lock...");
// Yeah this is a big hack, but it should work. If this is triggered by a call
// to RegisterNotificationRpc then we want to wait before that call has
// finished before allowing this guy to start. We just want to wait, not to
// synchronize access (which the list notify already does).
RtlAcquireResourceExclusive(&g_rsrcReg, TRUE);
TraceTag(ttidSsdpCNotify, "...got it!");
RtlReleaseResource(&g_rsrcReg);
TraceTag(ttidSsdpCNotify, "Released it!");
struct CallbackInfo
{
SERVICE_CALLBACK_FUNC m_pfnCallback;
SSDP_CALLBACK_TYPE m_ssdpCallbackType;
PSSDP_MESSAGE m_pssdpMessage;
void * m_pvContext;
};
long nCallbackCount = 0;
EnterListNotify();
// Go through list once to get a count of items
for (i = 0; i < list->size; i++)
{
SSDP_REQUEST *pSsdpRequest;
pSsdpRequest = list->list+i;
p = pListHead->Flink;
TraceTag(ttidSsdpCNotify, "Searching list to callback...");
while (p != pListHead)
{
TraceTag(ttidSsdpCNotify, "Found an item to check...");
PSSDP_CLIENT_NOTIFY NotifyRequest;
BOOL fShouldCallback;
NotifyRequest = CONTAINING_RECORD (p, SSDP_CLIENT_NOTIFY, linkage);
if (NotifyRequest->Type == SSDP_CLIENT_EVENT_SIGNATURE)
{
// Match the SID in the NOTIFY to the SID in any local
// subscribers
//
fShouldCallback = (pSsdpRequest->Headers[GENA_SID] &&
!lstrcmp(pSsdpRequest->Headers[GENA_SID], NotifyRequest->szSid));
}
else
{
fShouldCallback = (pSsdpRequest->Headers[SSDP_NT] &&
!lstrcmp(pSsdpRequest->Headers[SSDP_NT], NotifyRequest->szType)) ||
(pSsdpRequest->Headers[SSDP_ST] &&
!lstrcmp(pSsdpRequest->Headers[SSDP_ST], NotifyRequest->szType));
}
if (fShouldCallback)
{
++nCallbackCount;
}
p = p->Flink;
}
}
CallbackInfo * arCallbackInfo = NULL;
if(nCallbackCount)
{
arCallbackInfo = reinterpret_cast<CallbackInfo*>(malloc(nCallbackCount * sizeof(CallbackInfo)));
}
long nCallback = 0;
// Go through list again to store callback info
for (i = 0; i < list->size && arCallbackInfo && nCallback < nCallbackCount; i++)
{
SSDP_REQUEST *pSsdpRequest;
pSsdpRequest = list->list+i;
p = pListHead->Flink;
TraceTag(ttidSsdpCNotify, "Searching list to callback...");
while (p != pListHead)
{
TraceTag(ttidSsdpCNotify, "Found an item to check...");
PSSDP_CLIENT_NOTIFY NotifyRequest;
BOOL fShouldCallback;
NotifyRequest = CONTAINING_RECORD (p, SSDP_CLIENT_NOTIFY, linkage);
if (NotifyRequest->Type == SSDP_CLIENT_EVENT_SIGNATURE)
{
// Match the SID in the NOTIFY to the SID in any local
// subscribers
//
fShouldCallback = (pSsdpRequest->Headers[GENA_SID] &&
!lstrcmp(pSsdpRequest->Headers[GENA_SID], NotifyRequest->szSid));
}
else
{
fShouldCallback = (pSsdpRequest->Headers[SSDP_NT] &&
!lstrcmp(pSsdpRequest->Headers[SSDP_NT], NotifyRequest->szType)) ||
(pSsdpRequest->Headers[SSDP_ST] &&
!lstrcmp(pSsdpRequest->Headers[SSDP_ST], NotifyRequest->szType));
}
if (fShouldCallback)
{
PSSDP_MESSAGE pSsdpMessage;
pSsdpMessage = (PSSDP_MESSAGE) malloc(sizeof(SSDP_MESSAGE));
if (pSsdpMessage != NULL)
{
if (InitializeSsdpMessageFromRequest(pSsdpMessage,
pSsdpRequest) == TRUE)
{
SSDP_CALLBACK_TYPE CallbackType = SSDP_ALIVE;
if (!lstrcmpi(pSsdpRequest->Headers[SSDP_NTS],
"ssdp:byebye"))
{
CallbackType = SSDP_BYEBYE;
}
else if (!lstrcmpi(pSsdpRequest->Headers[SSDP_NTS],
"upnp:propchange"))
{
CallbackType = SSDP_EVENT;
}
else if (!lstrcmpi(pSsdpRequest->Headers[SSDP_NTS],
"upnp:dead"))
{
CallbackType = SSDP_DEAD;
}
arCallbackInfo[nCallback].m_pfnCallback = NotifyRequest->Callback;
arCallbackInfo[nCallback].m_ssdpCallbackType = CallbackType;
arCallbackInfo[nCallback].m_pssdpMessage = pSsdpMessage;
arCallbackInfo[nCallback].m_pvContext = NotifyRequest->Context;
++nCallback;
}
}
else
{
--nCallbackCount;
TraceTag(ttidSsdpNotify, "Failed to allocate memory for "
"SsdpMessage.");
}
}
p = p->Flink;
}
}
LeaveListNotify();
FreeMessageList(list);
// Make calls without list locked
for(long n = 0; n < nCallbackCount; ++n)
{
arCallbackInfo[n].m_pfnCallback(
arCallbackInfo[n].m_ssdpCallbackType,
arCallbackInfo[n].m_pssdpMessage,
arCallbackInfo[n].m_pvContext);
FreeSsdpMessage(arCallbackInfo[n].m_pssdpMessage);
}
free(arCallbackInfo);
}