//+--------------------------------------------------------------------------- // // 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 #pragma hdrstop #include #include "evtapi.h" #include "stdio.h" #include "interfacelist.h" #include #include 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!"); } }