//+--------------------------------------------------------------------------- // // Microsoft Windows // Copyright (C) Microsoft Corporation, 2000. // // File: A N N O U N C E . C P P // // Contents: SSDP Announcement list // // Notes: // // Author: mbend 8 Nov 2000 // //---------------------------------------------------------------------------- #include #pragma hdrstop #include "announce.h" #include "search.h" #include "ssdpfunc.h" #include "ssdpnetwork.h" #include "ncbase.h" #include "upthread.h" #define BUF_SIZE 40 HRESULT HrInitializeSsdpRequestFromMessage( SSDP_REQUEST * pRequest, const SSDP_MESSAGE *pMsg); static LIST_ENTRY listAnnounce; CRITICAL_SECTION CSListAnnounce; static CHAR *AliveHeader = "ssdp:alive"; static CHAR *ByebyeHeader = "ssdp:byebye"; static CHAR *MulticastUri = "*"; static const DWORD c_dwUPnPMajorVersion = 1; static const DWORD c_dwUPnPMinorVersion = 0; static const DWORD c_dwHostMajorVersion = 1; static const DWORD c_dwHostMinorVersion = 0; CSsdpService::CSsdpService() : m_timer(*this) { ZeroMemory(&m_ssdpRequest, sizeof(m_ssdpRequest)); } CSsdpService::~CSsdpService() { FreeSsdpRequest(&m_ssdpRequest); } PCONTEXT_HANDLE_TYPE * CSsdpService::GetContext() { return m_ppContextHandle; } BOOL CSsdpService::FIsUsn(const char * szUsn) { BOOL bMatch = FALSE; if(m_ssdpRequest.Headers[SSDP_USN]) { if(0 == lstrcmpA(m_ssdpRequest.Headers[SSDP_USN], szUsn)) { bMatch = TRUE; } } return bMatch; } void CSsdpService::OnRundown(CSsdpService * pService) { CSsdpServiceManager::Instance().HrServiceRundown(pService); } void CSsdpService::TimerFired() { HRESULT hr = S_OK; char * szAlive = NULL; hr = m_strAlive.HrGetMultiByteWithAlloc(&szAlive); if(SUCCEEDED(hr)) { GetNetworkLock(); SendOnAllNetworks(szAlive, NULL); FreeNetworkLock(); delete [] szAlive; // Repeat three times --m_nRetryCount; if(0 == m_nRetryCount) { // To-Do: The current limit on life time is 49.7 days. (32 bits in milliseconds) // 32 bit in seconds should be enough. // Need to add field remaining time ... m_nRetryCount = 3; long nTimeout = m_nLifetime * 1000/2 - 2 * (RETRY_INTERVAL * (NUM_RETRIES - 1)); hr = m_timer.HrSetTimerInFired(nTimeout); } else { hr = m_timer.HrSetTimerInFired(RETRY_INTERVAL); } } TraceHr(ttidSsdpAnnounce, FAL, hr, FALSE, "CSsdpService::TimerFired"); } BOOL CSsdpService::TimerTryToLock() { return m_critSec.FTryEnter(); } void CSsdpService::TimerUnlock() { m_critSec.Leave(); } HRESULT CSsdpService::HrInitialize( SSDP_MESSAGE * pssdpMsg, DWORD dwFlags, PCONTEXT_HANDLE_TYPE * ppContextHandle) { HRESULT hr = S_OK; hr = HrInitializeSsdpRequestFromMessage(&m_ssdpRequest, pssdpMsg); if(SUCCEEDED(hr)) { m_nLifetime = pssdpMsg->iLifeTime; m_nRetryCount = 3; m_dwFlags = dwFlags; m_ppContextHandle = ppContextHandle; *m_ppContextHandle = NULL; m_ssdpRequest.RequestUri = MulticastUri; OSVERSIONINFO osvi = {0}; osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); if(!GetVersionEx(&osvi)) { hr = HrFromLastWin32Error(); } if(SUCCEEDED(hr)) { // Yes this is a constant but it is just used temporarily m_ssdpRequest.Headers[SSDP_SERVER] = "Microsoft-Windows-NT/5.1 UPnP/1.0 UPnP-Device-Host/1.0"; char * szAlive = NULL; if(SUCCEEDED(hr)) { m_ssdpRequest.Headers[SSDP_NTS] = AliveHeader; if (!ComposeSsdpRequest(&m_ssdpRequest, &szAlive)) { hr = E_FAIL; TraceHr(ttidSsdpAnnounce, FAL, hr, FALSE, "CSsdpService::HrInitialize - ComposeSsdpRequest failed!"); } else { hr = m_strAlive.HrAssign(szAlive); delete [] szAlive; } } if(SUCCEEDED(hr)) { char * szByebye = NULL; if(SUCCEEDED(hr)) { m_ssdpRequest.Headers[SSDP_NTS] = ByebyeHeader; if(!ComposeSsdpRequest(&m_ssdpRequest, &szByebye)) { hr = E_FAIL; TraceHr(ttidSsdpAnnounce, FAL, hr, FALSE, "CSsdpService::HrInitialize - ComposeSsdpRequest failed!"); } else { hr = m_strByebye.HrAssign(szByebye); delete [] szByebye; } } } if(SUCCEEDED(hr)) { // Generate an empty Ext header on the response to M-SEARCH m_ssdpRequest.Headers[SSDP_EXT] = ""; char * szResponse = NULL; if (!ComposeSsdpResponse(&m_ssdpRequest, &szResponse)) { hr = E_FAIL; TraceHr(ttidSsdpAnnounce, FAL, hr, FALSE, "CSsdpService::HrInitialize - ComposeSsdpResponse failed!"); } else { hr = m_strResponse.HrAssign(szResponse); delete [] szResponse; } } } } // Important: Set to NULL to prevent freeing memory. m_ssdpRequest.Headers[SSDP_NTS] = NULL; m_ssdpRequest.RequestUri = NULL; // Important: Set to NULL to prevent freeing memory. m_ssdpRequest.Headers[SSDP_EXT] = NULL; m_ssdpRequest.Headers[SSDP_SERVER] = NULL; TraceHr(ttidSsdpAnnounce, FAL, hr, FALSE, "CSsdpService::HrInitialize"); return hr; } HRESULT CSsdpService::HrStartTimer() { HRESULT hr = S_OK; CLock lock(m_critSec); hr = m_timer.HrSetTimer(0); if(SUCCEEDED(hr)) { *m_ppContextHandle = this; } TraceHr(ttidSsdpAnnounce, FAL, hr, FALSE, "CSsdpService::HrStartTimer"); return hr; } long CalcMX (SSDP_REQUEST * pSsdpRequest) { LONG lMin, lMX; #define MINIMUM_MX_UUID 1 #define MINIMUM_MX_DST 2 #define MINIMUM_MX_ROOT 3 #define MAXIMUM_MX 12 #define UUID_COLON "uuid:" #define URN_SERVICE "urn:schemas-upnp-org:service:" #define URN_DEVICE "urn:schemas-upnp-org:device:" Assert (pSsdpRequest != NULL); if (strncmp (pSsdpRequest->Headers[SSDP_ST], UUID_COLON, sizeof(UUID_COLON) - 1) == 0) lMin = MINIMUM_MX_UUID; else if (strcmp(pSsdpRequest->Headers[SSDP_ST], "upnp:rootdevice") == 0) lMin = MINIMUM_MX_ROOT; else if (strcmp(pSsdpRequest->Headers[SSDP_ST], "ssdp:all") == 0) lMin = MINIMUM_MX_ROOT; else if ( strncmp (pSsdpRequest->Headers[SSDP_ST], URN_SERVICE, sizeof(URN_SERVICE) - 1) == 0 || strncmp (pSsdpRequest->Headers[SSDP_ST], URN_DEVICE, sizeof(URN_DEVICE) - 1) == 0 ) lMin = MINIMUM_MX_DST; else lMin = MINIMUM_MX_UUID; lMX = atol(pSsdpRequest->Headers[SSDP_MX]); if (lMX > MAXIMUM_MX) lMX = MAXIMUM_MX; else if (lMX < lMin) lMX = lMin; return lMX; } HRESULT CSsdpService::HrAddSearchResponse(SSDP_REQUEST * pSsdpRequest, SOCKET * pSocket, SOCKADDR_IN * pRemoteSocket) { HRESULT hr = S_OK; CLock lock(m_critSec); BOOL bSendRequest = FALSE; if(pSsdpRequest->Headers[SSDP_ST]) { if(0 == lstrcmpA(pSsdpRequest->Headers[SSDP_ST], "ssdp:all")) { bSendRequest = TRUE; } if(!bSendRequest && 0 == lstrcmpA(pSsdpRequest->Headers[SSDP_ST], m_ssdpRequest.Headers[SSDP_NT])) { bSendRequest = TRUE; } } if(bSendRequest) { TraceTag(ttidSsdpAnnounce, "Adding search response for %s", m_ssdpRequest.Headers[SSDP_NT]); char * szResponse = NULL; hr = m_strResponse.HrGetMultiByteWithAlloc(&szResponse); if(SUCCEEDED(hr)) { // Takes ownership of string hr = CSsdpSearchResponse::HrCreate(pSocket, pRemoteSocket, szResponse, CalcMX(pSsdpRequest)); } } TraceHr(ttidSsdpAnnounce, FAL, hr, FALSE, "CSsdpService::HrAddSearchResponse"); return hr; } class CSsdpByebye : public CWorkItem { public: static HRESULT Create(char * szByebye); void TimerFired(); BOOL TimerTryToLock() { return m_critSec.FTryEnter(); } void TimerUnlock() { m_critSec.Leave(); } private: CSsdpByebye(char * szByebye); ~CSsdpByebye(); CSsdpByebye(const CSsdpByebye &); CSsdpByebye & operator=(const CSsdpByebye &); DWORD DwRun(); char * m_szByebye; long m_nRetryCount; CTimer m_timer; CUCriticalSection m_critSec; }; CSsdpByebye::CSsdpByebye(char * szByebye) : m_szByebye(szByebye), m_nRetryCount(3), m_timer(*this) { } CSsdpByebye::~CSsdpByebye() { delete [] m_szByebye; } HRESULT CSsdpByebye::Create(char * szByebye) { HRESULT hr = S_OK; CSsdpByebye * pByebye = new CSsdpByebye(szByebye); if(!pByebye) { hr = E_OUTOFMEMORY; delete [] szByebye; } if(SUCCEEDED(hr)) { hr = pByebye->m_timer.HrSetTimer(0); if(FAILED(hr)) { delete pByebye; } } TraceHr(ttidSsdpAnnounce, FAL, hr, FALSE, "CSsdpByebye::Create"); return hr; } void CSsdpByebye::TimerFired() { HRESULT hr = S_OK; GetNetworkLock(); SendOnAllNetworks(m_szByebye, NULL); FreeNetworkLock(); --m_nRetryCount; // We do this three times and then we kill ourselves if(0 == m_nRetryCount) { // This will queue ourselves to autodelete HrStart(true); } else { hr = m_timer.HrSetTimerInFired(RETRY_INTERVAL); if(FAILED(hr)) { // This will queue ourselves to autodelete HrStart(true); } } TraceHr(ttidSsdpAnnounce, FAL, hr, FALSE, "CSsdpByebye::TimerFired"); } DWORD CSsdpByebye::DwRun() { // Wait for timer to exit before allowing this function to return (which causes delete this to happen) CLock lock(m_critSec); m_timer.HrDelete(INVALID_HANDLE_VALUE); return 0; } HRESULT CSsdpService::HrCleanup(BOOL bByebye) { HRESULT hr = S_OK; CLock lock(m_critSec); hr = m_timer.HrDelete(INVALID_HANDLE_VALUE); if(SUCCEEDED(hr)) { if(bByebye) { char * szByebye = NULL; hr = m_strByebye.HrGetMultiByteWithAlloc(&szByebye); if(SUCCEEDED(hr)) { // This takes ownership of memory CSsdpByebye::Create(szByebye); } } } TraceHr(ttidSsdpAnnounce, FAL, hr, FALSE, "CSsdpService::HrCleanup"); return hr; } BOOL CSsdpService::FIsPersistent() { return (SSDP_SERVICE_PERSISTENT & m_dwFlags) != 0; } CSsdpServiceManager CSsdpServiceManager::s_instance; CSsdpServiceManager::CSsdpServiceManager() { } CSsdpServiceManager::~CSsdpServiceManager() { } CSsdpServiceManager & CSsdpServiceManager::Instance() { return s_instance; } HRESULT CSsdpServiceManager::HrAddService( SSDP_MESSAGE * pssdpMsg, DWORD dwFlags, PCONTEXT_HANDLE_TYPE * ppContextHandle) { HRESULT hr = S_OK; CLock lock(m_critSec); if(pssdpMsg && FindServiceByUsn(pssdpMsg->szUSN)) { hr = E_INVALIDARG; TraceTag(ttidSsdpAnnounce, "CSsdpServiceManager::HrAddService - attempting to add duplicate USN"); } CSsdpService * pService = NULL; if(SUCCEEDED(hr)) { ServiceList serviceList; hr = serviceList.HrPushFrontDefault(); if(SUCCEEDED(hr)) { hr = serviceList.Front().HrInitialize(pssdpMsg, dwFlags, ppContextHandle); if(SUCCEEDED(hr)) { hr = serviceList.Front().HrStartTimer(); if(SUCCEEDED(hr)) { m_serviceList.Prepend(serviceList); pService = &m_serviceList.Front(); } } } } if(pService) { hr = CSsdpRundownSupport::Instance().HrAddRundownItem(pService); } TraceHr(ttidSsdpAnnounce, FAL, hr, FALSE, "CSsdpServiceManager::HrAddService"); return hr; } CSsdpService * CSsdpServiceManager::FindServiceByUsn(char * szUSN) { HRESULT hr = S_OK; CLock lock(m_critSec); CSsdpService * pService = NULL; ServiceList::Iterator iter; if(S_OK == m_serviceList.GetIterator(iter)) { CSsdpService * pServiceIter = NULL; while(S_OK == iter.HrGetItem(&pServiceIter)) { if(pServiceIter->FIsUsn(szUSN)) { pService = pServiceIter; break; } if(S_OK != iter.HrNext()) { break; } } } if(!pService) { TraceTag(ttidSsdpAnnounce, "CSsdpServiceManager::FindServiceByUsn(%s) - not found!", szUSN); } TraceHr(ttidSsdpAnnounce, FAL, hr, FALSE, "CSsdpServiceManager::FindServiceByUsn"); return pService; } HRESULT CSsdpServiceManager::HrAddSearchResponse(SSDP_REQUEST * pSsdpRequest, SOCKET * pSocket, SOCKADDR_IN * pRemoteSocket) { HRESULT hr = S_OK; CLock lock(m_critSec); ServiceList::Iterator iter; if(S_OK == m_serviceList.GetIterator(iter)) { CSsdpService * pService = NULL; while(S_OK == iter.HrGetItem(&pService)) { pService->HrAddSearchResponse(pSsdpRequest, pSocket, pRemoteSocket); if(S_OK != iter.HrNext()) { break; } } } TraceHr(ttidSsdpAnnounce, FAL, hr, FALSE, "CSsdpServiceManager::HrAddSearchResponse"); return hr; } HRESULT CSsdpServiceManager::HrRemoveService(CSsdpService * pService, BOOL bByebye) { HRESULT hr = S_OK; hr = HrRemoveServiceInternal(pService, bByebye, FALSE); TraceHr(ttidSsdpAnnounce, FAL, hr, FALSE, "CSsdpServiceManager::HrRemoveService"); return hr; } HRESULT CSsdpServiceManager::HrServiceRundown(CSsdpService * pService) { HRESULT hr = S_OK; hr = HrRemoveServiceInternal(pService, TRUE, TRUE); TraceHr(ttidSsdpAnnounce, FAL, hr, FALSE, "CSsdpServiceManager::HrCleanupAnouncements"); return hr; } HRESULT CSsdpServiceManager::HrCleanupAnouncements() { HRESULT hr = S_OK; CLock lock(m_critSec); // TODO: Implement this!!!!!!!!!!!!!!!!!!!!!! TraceHr(ttidSsdpAnnounce, FAL, hr, FALSE, "CSsdpServiceManager::HrCleanupAnouncements"); return hr; } HRESULT CSsdpServiceManager::HrRemoveServiceInternal(CSsdpService * pService, BOOL bByebye, BOOL bRundown) { HRESULT hr = S_OK; BOOL bFound = FALSE; ServiceList serviceList; { CLock lock(m_critSec); ServiceList::Iterator iter; if(S_OK == m_serviceList.GetIterator(iter)) { CSsdpService * pServiceIter = NULL; while(S_OK == iter.HrGetItem(&pServiceIter)) { if(pServiceIter == pService) { if(!bRundown || !pService->FIsPersistent()) { iter.HrMoveToList(serviceList); bFound = TRUE; } break; } if(S_OK != iter.HrNext()) { break; } } } } if(bFound) { ServiceList::Iterator iter; if(S_OK == serviceList.GetIterator(iter)) { CSsdpService * pServiceIter = NULL; while(S_OK == iter.HrGetItem(&pServiceIter)) { if(!bRundown) { CSsdpRundownSupport::Instance().RemoveRundownItem(pServiceIter); } hr = pServiceIter->HrCleanup(bByebye); if(S_OK != iter.HrNext()) { break; } } } } TraceHr(ttidSsdpAnnounce, FAL, hr, FALSE, "CSsdpServiceManager::HrRemoveServiceInternal"); return hr; } HRESULT HrInitializeSsdpRequestFromMessage( SSDP_REQUEST * pRequest, const SSDP_MESSAGE *pMsg) { HRESULT hr = S_OK; ZeroMemory(pRequest, sizeof(*pRequest)); pRequest->Method = SSDP_NOTIFY; pRequest->Headers[SSDP_HOST] = reinterpret_cast( midl_user_allocate(sizeof(CHAR) * ( lstrlenA(SSDP_ADDR) + 1 + // colon 6 + // port number 1))); if (!pRequest->Headers[SSDP_HOST]) { hr = E_OUTOFMEMORY; } if(SUCCEEDED(hr)) { wsprintfA(pRequest->Headers[SSDP_HOST], "%s:%d", SSDP_ADDR, SSDP_PORT); hr = HrCopyString(pMsg->szType, &pRequest->Headers[SSDP_NT]); if(SUCCEEDED(hr) || !pMsg->szType) { hr = HrCopyString(pMsg->szUSN, &pRequest->Headers[SSDP_USN]); if(SUCCEEDED(hr) || !pMsg->szUSN) { hr = HrCopyString(pMsg->szAltHeaders, &pRequest->Headers[SSDP_AL]); if(SUCCEEDED(hr) || !pMsg->szAltHeaders) { hr = HrCopyString(pMsg->szLocHeader, &pRequest->Headers[SSDP_LOCATION]); if(SUCCEEDED(hr) || pMsg->szLocHeader) { char szBuf[256]; wsprintfA(szBuf, "max-age=%d", pMsg->iLifeTime); hr = HrCopyString(szBuf, &pRequest->Headers[SSDP_CACHECONTROL]); } } } } } if(FAILED(hr)) { FreeSsdpRequest(pRequest); ZeroMemory(pRequest, sizeof(*pRequest)); } TraceHr(ttidSsdpAnnounce, FAL, hr, FALSE, "HrInitializeSsdpRequestFromMessage"); return hr; }