#include #pragma hdrstop #include #include "ssdpfunc.h" #include "ssdpnetwork.h" #include "ncbase.h" #include "cache.h" #include "upthread.h" #include "notify.h" #define BUF_SIZE 100 #define CACHE_RESULT_SIZE 2 static const int g_cMaxCacheDefault = 1000; // default max size of SSDP cache static const int g_cMaxCacheMin = 10; // min allowed max size of SSDP cache static const int g_cMaxCacheMax = 30000; // max allowed max size of SSDP cache CSsdpCacheEntry::CSsdpCacheEntry() : m_timer(*this), m_bTimerFired(FALSE) { ZeroMemory(&m_ssdpRequest, sizeof(m_ssdpRequest)); } CSsdpCacheEntry::~CSsdpCacheEntry() { FreeSsdpRequest(&m_ssdpRequest); } class CSsdpCacheEntryByebye : public CWorkItem { public: static HRESULT HrCreate(CSsdpCacheEntry * pEntry); private: CSsdpCacheEntryByebye(CSsdpCacheEntry * pEntry); ~CSsdpCacheEntryByebye(); CSsdpCacheEntryByebye(const CSsdpCacheEntryByebye &); CSsdpCacheEntryByebye & operator=(const CSsdpCacheEntryByebye &); DWORD DwRun(); CSsdpCacheEntry * m_pEntry; }; CSsdpCacheEntryByebye::CSsdpCacheEntryByebye(CSsdpCacheEntry * pEntry) : m_pEntry(pEntry) { } CSsdpCacheEntryByebye::~CSsdpCacheEntryByebye() { } HRESULT CSsdpCacheEntryByebye::HrCreate(CSsdpCacheEntry * pEntry) { HRESULT hr = S_OK; CSsdpCacheEntryByebye * pByebye = new CSsdpCacheEntryByebye(pEntry); if(!pByebye) { hr = E_OUTOFMEMORY; } if(SUCCEEDED(hr)) { hr = pByebye->HrStart(TRUE); if(FAILED(hr)) { delete pByebye; } } TraceHr(ttidSsdpCache, FAL, hr, FALSE, "CSsdpCacheEntryByebye::HrCreate"); return hr; } DWORD CSsdpCacheEntryByebye::DwRun() { HRESULT hr = S_OK; hr = CSsdpCacheEntryManager::Instance().HrRemoveEntry(m_pEntry); TraceHr(ttidSsdpCache, FAL, hr, FALSE, "CSsdpCacheEntryByebye::DwRun"); return 0; } void CSsdpCacheEntry::TimerFired() { HRESULT hr = S_OK; if(!ConvertToByebyeNotify(&m_ssdpRequest)) { hr = E_FAIL; TraceHr(ttidSsdpCache, FAL, hr, FALSE, "CSsdpCacheEntry::TimerFired - ConvertToByebyeNotify failed!"); } // Mark that the timer has fired in case a renew comes in m_bTimerFired = TRUE; if(SUCCEEDED(hr)) { // Queue the delete to happen later hr = CSsdpCacheEntryByebye::HrCreate(this); } } BOOL CSsdpCacheEntry::TimerTryToLock() { return m_critSec.FTryEnter(); } void CSsdpCacheEntry::TimerUnlock() { m_critSec.Leave(); } HRESULT CSsdpCacheEntry::HrInitialize(const SSDP_REQUEST * pRequest) { HRESULT hr = S_OK; if(!CopySsdpRequest(&m_ssdpRequest, pRequest)) { hr = E_FAIL; TraceHr(ttidSsdpCache, FAL, hr, FALSE, "CSsdpCacheEntry::HrInitialize - CopySsdpRequest failed!"); } TraceHr(ttidSsdpCache, FAL, hr, FALSE, "CSsdpCacheEntry::HrInitialize"); return hr; } HRESULT CSsdpCacheEntry::HrStartTimer(const SSDP_REQUEST * pRequest) { HRESULT hr = S_OK; hr = HrUpdateExpireTime(pRequest); TraceHr(ttidSsdpCache, FAL, hr, FALSE, "CSsdpCacheEntry::HrStartTimer"); return hr; } BOOL CSsdpCacheEntry::FTimerFired() { CLock lock(m_critSec); return m_bTimerFired; } HRESULT CSsdpCacheEntry::HrShutdown(BOOL bExpired) { HRESULT hr = S_OK; CLock lock(m_critSec); Assert(FImplies(bExpired, m_bTimerFired)); hr = m_timer.HrDelete(INVALID_HANDLE_VALUE); if(bExpired) { hr = CSsdpNotifyRequestManager::Instance().HrCheckListNotifyForAliveOrByebye(&m_ssdpRequest); } TraceHr(ttidSsdpCache, FAL, hr, FALSE, "CSsdpCacheEntry::HrShutdown"); return hr; } HRESULT CSsdpCacheEntry::HrUpdateExpireTime(const SSDP_REQUEST * pRequest) { HRESULT hr = S_OK; CLock lock(m_critSec); if(!pRequest->Headers[SSDP_CACHECONTROL]) { hr = E_INVALIDARG; TraceHr(ttidSsdpCache, FAL, hr, FALSE, "CSsdpCacheEntry::HrUpdateExpireTime - No cache-control header"); } if(SUCCEEDED(hr)) { DWORD dwTimeout = GetMaxAgeFromCacheControl(pRequest->Headers[SSDP_CACHECONTROL]); hr = m_timer.HrResetTimer(dwTimeout * 1000); m_bTimerFired = FALSE; } TraceHr(ttidSsdpCache, FAL, hr, FALSE, "CSsdpCacheEntry::HrUpdateExpireTime"); return hr; } HRESULT CSsdpCacheEntry::HrUpdateEntry(const SSDP_REQUEST * pRequest, BOOL * pbCheckListNotify) { HRESULT hr = S_OK; CLock lock(m_critSec); hr = HrUpdateExpireTime(pRequest); if(S_OK == hr) { if(!CompareSsdpRequest(&m_ssdpRequest, pRequest)) { *pbCheckListNotify = TRUE; // Requests don't match. Check location headers. If different, // then we need to fake a byebye then allow the new alive to be // notified if (lstrcmpi(m_ssdpRequest.Headers[SSDP_LOCATION], pRequest->Headers[SSDP_LOCATION])) { if(!ConvertToByebyeNotify(&m_ssdpRequest)) { hr = E_FAIL; TraceHr(ttidSsdpCache, FAL, hr, FALSE, "CSsdpCacheEntry::HrUpdateEntry - " "ConvertToByebyeNotify failed!"); } else { hr = CSsdpNotifyRequestManager::Instance().HrCheckListNotifyForAliveOrByebye(&m_ssdpRequest); } } } FreeSsdpRequest(&m_ssdpRequest); if(!CopySsdpRequest(&m_ssdpRequest, pRequest)) { hr = E_OUTOFMEMORY; } } TraceHr(ttidSsdpCache, FAL, hr, FALSE, "CSsdpCacheEntry::HrUpdateEntry"); return hr; } BOOL CSsdpCacheEntry::FIsMatchUSN(const char * szUSN) { CLock lock(m_critSec); return 0 == lstrcmpA(m_ssdpRequest.Headers[SSDP_USN], szUSN); } BOOL CSsdpCacheEntry::FIsSearchMatch(const char * szType) { CLock lock(m_critSec); if(0 == lstrcmpiA(szType, "ssdp:all")) { return TRUE; } if(m_ssdpRequest.Headers[SSDP_NT] && 0 == lstrcmpiA(m_ssdpRequest.Headers[SSDP_NT], szType)) { return TRUE; } if(m_ssdpRequest.Headers[SSDP_ST] && 0 == lstrcmpiA(m_ssdpRequest.Headers[SSDP_ST], szType)) { return TRUE; } return FALSE; } HRESULT CSsdpCacheEntry::HrGetRequest(SSDP_REQUEST * pRequest) { HRESULT hr = S_OK; CLock lock(m_critSec); if(!CopySsdpRequest(pRequest, &m_ssdpRequest)) { hr = E_OUTOFMEMORY; } TraceHr(ttidSsdpCache, FAL, hr, FALSE, "CSsdpCacheEntry::HrCacheEntryExpired"); return hr; } void CSsdpCacheEntry::PrintItem() { TraceTag(ttidSsdpCache, " %s - %s - %s", m_ssdpRequest.Headers[SSDP_USN], m_ssdpRequest.Headers[SSDP_NT], m_ssdpRequest.Headers[SSDP_LOCATION]); } BOOL CSsdpCacheEntry::FCheckForDirtyInterfaceGuids(long nCount, GUID * arGuidInterfaces) { BOOL bDirty = FALSE; HRESULT hr = S_OK; CLock lock(m_critSec); for(long n = 0; n < nCount; ++n) { if(arGuidInterfaces[n] == m_ssdpRequest.guidInterface) { bDirty = TRUE; if(ConvertToByebyeNotify(&m_ssdpRequest)) { hr = CSsdpNotifyRequestManager::Instance().HrCheckListNotifyForAliveOrByebye(&m_ssdpRequest); } else { TraceTag(ttidError, "CSsdpCacheEntry::FCheckForDirtyInterfaceGuids - ConvertToByebyeNotify failed"); } break; } } TraceHr(ttidSsdpCache, FAL, hr, FALSE, "CSsdpCacheEntry::FCheckForDirtyInterfaceGuids"); return bDirty; } CSsdpCacheEntryManager CSsdpCacheEntryManager::s_instance; CSsdpCacheEntryManager::CSsdpCacheEntryManager():m_cCacheEntries(0), m_cMaxCacheEntries(1000) { } CSsdpCacheEntryManager::~CSsdpCacheEntryManager() { } HRESULT CSsdpCacheEntryManager::HrInitialize() { HRESULT hr = S_OK; HKEY hkey; DWORD dwMaxCache = g_cMaxCacheDefault; if (ERROR_SUCCESS == RegOpenKeyEx(HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Services" "\\SSDPSRV\\Parameters", 0, KEY_READ, &hkey)) { DWORD cbSize = sizeof(DWORD); // ignore failure. In that case, we'll use default (VOID) RegQueryValueEx(hkey, "MaxCache", NULL, NULL, (BYTE *)&dwMaxCache, &cbSize); RegCloseKey(hkey); } dwMaxCache = max(dwMaxCache, g_cMaxCacheMin); dwMaxCache = min(dwMaxCache, g_cMaxCacheMax); m_cMaxCacheEntries = dwMaxCache; m_cCacheEntries = 0; TraceTag(ttidSsdpCache, "CSsdpCacheEntryManager::HrInitialize - Max Cache %d",m_cMaxCacheEntries); TraceHr(ttidSsdpCache, FAL, hr, FALSE, "CSsdpCacheEntryManager::HrInitialize"); return hr; } CSsdpCacheEntryManager & CSsdpCacheEntryManager::Instance() { return s_instance; } BOOL CSsdpCacheEntryManager::IsCacheListNotFull() { CLock lock(m_critSec); if (m_cCacheEntries < m_cMaxCacheEntries) return TRUE; else return FALSE; } HRESULT CSsdpCacheEntryManager::HrShutdown() { HRESULT hr = S_OK; CLock lock(m_critSec); CacheEntryList::Iterator iter; if(S_OK == m_cacheEntryList.GetIterator(iter)) { CSsdpCacheEntry * pEntryIter = NULL; while(S_OK == iter.HrGetItem(&pEntryIter)) { hr = pEntryIter->HrShutdown(FALSE); if(S_OK != iter.HrErase()) { break; } } } m_cCacheEntries = 0; TraceHr(ttidSsdpCache, FAL, hr, FALSE, "CSsdpCacheEntryManager::HrShutdown"); return hr; } HRESULT CSsdpCacheEntryManager::HrRemoveEntry(CSsdpCacheEntry * pEntry) { HRESULT hr = S_OK; CacheEntryList cacheEntryListRemove; { CLock lock(m_critSec); CacheEntryList::Iterator iter; if(S_OK == m_cacheEntryList.GetIterator(iter)) { CSsdpCacheEntry * pEntryIter = NULL; while(S_OK == iter.HrGetItem(&pEntryIter)) { if(pEntryIter == pEntry) { if(pEntry->FTimerFired()) { iter.HrMoveToList(cacheEntryListRemove); m_cCacheEntries--; TraceTag(ttidSsdpCache, "CSsdpCacheEntryManager::HrRemoveEntry - Cache Entries %d", m_cCacheEntries); } break; } if(S_OK != iter.HrNext()) { break; } } } } // Remove outside of lock CacheEntryList::Iterator iter; if(S_OK == cacheEntryListRemove.GetIterator(iter)) { CSsdpCacheEntry * pEntryIter = NULL; while(S_OK == iter.HrGetItem(&pEntryIter)) { hr = pEntryIter->HrShutdown(TRUE); if(S_OK != iter.HrNext()) { break; } } } TraceHr(ttidSsdpCache, FAL, hr, FALSE, "CSsdpCacheEntryManager::HrRemoveEntry"); return hr; } #ifdef DBG #define CACHECONTENTTRACE DBG #endif // DBG HRESULT CSsdpCacheEntryManager::HrUpdateCacheList(const SSDP_REQUEST * pRequest, BOOL bIsSubscribed) { HRESULT hr = S_OK; BOOL bIsByebye = FALSE; BOOL bFound = FALSE; // Debugging only #if CACHECONTENTTRACE BOOL bAdded = FALSE; BOOL bRemoved = FALSE; #endif //CACHECONTENTTRACE BOOL bCheckListNotify = FALSE; CacheEntryList cacheEntryListRemove; if(pRequest->Headers[SSDP_NTS] && 0 == lstrcmpA(pRequest->Headers[SSDP_NTS], "ssdp:byebye")) { bIsByebye = TRUE; } if(!bIsByebye && !pRequest->Headers[SSDP_CACHECONTROL]) { // Not cacheable hr = S_FALSE; if(S_OK == hr) { if(!bIsByebye && !pRequest->Headers[SSDP_SERVER]) { // No server header hr = S_FALSE; } } } { CLock lock(m_critSec); if(S_OK == hr) { CacheEntryList::Iterator iter; if(S_OK == m_cacheEntryList.GetIterator(iter)) { CSsdpCacheEntry * pEntryIter = NULL; while(S_OK == iter.HrGetItem(&pEntryIter)) { if(pEntryIter->FIsMatchUSN(pRequest->Headers[SSDP_USN])) { bFound = TRUE; if(bIsByebye) { bCheckListNotify = TRUE; iter.HrMoveToList(cacheEntryListRemove); m_cCacheEntries--; TraceTag(ttidSsdpCache, "CSsdpCacheEntryManager::HrUpdateCacheList - Bye Bye - Cache Entries %d", m_cCacheEntries); #if CACHECONTENTTRACE bRemoved = TRUE; #endif //CACHECONTENTTRACE } else { hr = pEntryIter->HrUpdateEntry(pRequest, &bCheckListNotify); } break; } if(S_OK != iter.HrNext()) { break; } } } } if(S_OK == hr && !bFound && ! bIsByebye && bIsSubscribed) { if(IsCacheListNotFull()) { CacheEntryList cacheEntryList; hr = cacheEntryList.HrPushFrontDefault(); if(SUCCEEDED(hr)) { hr = cacheEntryList.Front().HrInitialize(pRequest); if(SUCCEEDED(hr)) { bCheckListNotify = TRUE; hr = cacheEntryList.Front().HrStartTimer(pRequest); if(SUCCEEDED(hr)) { m_cacheEntryList.Prepend(cacheEntryList); m_cCacheEntries++; TraceTag(ttidSsdpCache, "CSsdpCacheEntryManager::HrUpdateCacheList - Cache Entries %d", m_cCacheEntries); #if CACHECONTENTTRACE bAdded = TRUE; #endif //CACHECONTENTTRACE } } } } else { TraceTag(ttidSsdpCache, "CSsdpCacheEntryManager::HrUpdateCacheList - Cache Entries Reached MAX"); } } #if CACHECONTENTTRACE if(bAdded || bRemoved) { TraceTag(ttidSsdpCache, "CSsdpCacheEntryManager::HrUpdateCacheList - Cache Contents"); long nCount = 0; CacheEntryList::Iterator iter; if(S_OK == m_cacheEntryList.GetIterator(iter)) { CSsdpCacheEntry * pEntryIter = NULL; while(S_OK == iter.HrGetItem(&pEntryIter)) { pEntryIter->PrintItem(); ++nCount; if(S_OK != iter.HrNext()) { break; } } } TraceTag(ttidSsdpCache, "CSsdpCacheEntryManager::HrUpdateCacheList - Cache Content Count: %d", nCount); if(bAdded) { TraceTag(ttidSsdpCache, "CSsdpCacheEntryManager::HrUpdateCacheList - Added: %s", pRequest->Headers[SSDP_USN]); } if(bRemoved) { TraceTag(ttidSsdpCache, "CSsdpCacheEntryManager::HrUpdateCacheList - Removed: %s", pRequest->Headers[SSDP_USN]); } } #endif // CACHECONTENTTRACE } if(bCheckListNotify) { CSsdpNotifyRequestManager::Instance().HrCheckListNotifyForAliveOrByebye(pRequest); } // Remove outside of lock CacheEntryList::Iterator iter; if(S_OK == cacheEntryListRemove.GetIterator(iter)) { CSsdpCacheEntry * pEntryIter = NULL; while(S_OK == iter.HrGetItem(&pEntryIter)) { hr = pEntryIter->HrShutdown(FALSE); if(S_OK != iter.HrNext()) { break; } } } TraceHr(ttidSsdpCache, FAL, hr, FALSE, "CSsdpCacheEntryManager::HrUpdateCacheList"); return hr; } HRESULT CSsdpCacheEntryManager::HrSearchListCache(char * szType, MessageList ** ppSvcList) { HRESULT hr = S_OK; CLock lock(m_critSec); *ppSvcList = NULL; typedef CUList RequestList; RequestList requestList; SSDP_REQUEST ssdpRequest; long nItems = 0; CacheEntryList::Iterator iter; if(S_OK == m_cacheEntryList.GetIterator(iter)) { CSsdpCacheEntry * pEntryIter = NULL; while(S_OK == iter.HrGetItem(&pEntryIter)) { if(pEntryIter->FIsSearchMatch(szType)) { hr = pEntryIter->HrGetRequest(&ssdpRequest); if(SUCCEEDED(hr)) { hr = requestList.HrPushFront(ssdpRequest); if(SUCCEEDED(hr)) { ++nItems; } if(FAILED(hr)) { FreeSsdpRequest(&ssdpRequest); } } if(FAILED(hr)) { break; } } if(S_OK != iter.HrNext()) { break; } } } if(SUCCEEDED(hr)) { MessageList * pSvcList = new MessageList; if(!pSvcList) { hr = E_OUTOFMEMORY; } if(SUCCEEDED(hr)) { if(nItems) { pSvcList->list = new SSDP_REQUEST[nItems]; if(!pSvcList->list) { hr = E_OUTOFMEMORY; } else { ZeroMemory(pSvcList->list, sizeof(SSDP_REQUEST) * nItems); } } else { pSvcList->list = NULL; } pSvcList->size = nItems; if(SUCCEEDED(hr)) { RequestList::Iterator iter; if(S_OK == requestList.GetIterator(iter)) { SSDP_REQUEST * pRequestDst = pSvcList->list; SSDP_REQUEST * pRequest = NULL; while(S_OK == iter.HrGetItem(&pRequest)) { CopyMemory(pRequestDst, pRequest, sizeof(SSDP_REQUEST)); ++pRequestDst; if(S_OK != iter.HrNext()) { break; } } } *ppSvcList = pSvcList; } } } if(FAILED(hr)) { RequestList::Iterator iter; if(S_OK == requestList.GetIterator(iter)) { SSDP_REQUEST * pRequest = NULL; while(S_OK == iter.HrGetItem(&pRequest)) { FreeSsdpRequest(pRequest); if(S_OK != iter.HrNext()) { break; } } } } TraceHr(ttidSsdpCache, FAL, hr, FALSE, "CSsdpCacheEntryManager::HrSearchListCache"); return hr; } HRESULT CSsdpCacheEntryManager::HrClearDirtyInterfaceGuids(long nCount, GUID * arGuidInterfaces) { HRESULT hr = S_OK; CacheEntryList cacheEntryListRemove; { CLock lock(m_critSec); CacheEntryList::Iterator iter; if(S_OK == m_cacheEntryList.GetIterator(iter)) { CSsdpCacheEntry * pEntryIter = NULL; while(S_OK == iter.HrGetItem(&pEntryIter)) { if(pEntryIter->FCheckForDirtyInterfaceGuids(nCount, arGuidInterfaces)) { m_cCacheEntries--; TraceTag(ttidSsdpCache, "CSsdpCacheEntryManager::HrClearDirtyInterfaceGuids - Cache Entries %d", m_cCacheEntries); if(S_OK != iter.HrMoveToList(cacheEntryListRemove)) { break; } } if(S_OK != iter.HrNext()) { break; } } } } // Remove outside of lock CacheEntryList::Iterator iter; if(S_OK == cacheEntryListRemove.GetIterator(iter)) { CSsdpCacheEntry * pEntryIter = NULL; while(S_OK == iter.HrGetItem(&pEntryIter)) { hr = pEntryIter->HrShutdown(FALSE); if(S_OK != iter.HrNext()) { break; } } } TraceHr(ttidSsdpCache, FAL, hr, FALSE, "CSsdpCacheEntryManager::HrClearDirtyInterfaceGuids"); return hr; }