#include "nt.h" #include "ntrtl.h" #include "nturtl.h" #include "objbase.h" #include // 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(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); }