490 lines
14 KiB
C++
490 lines
14 KiB
C++
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
|
|
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
|
|
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
|
|
// PARTICULAR PURPOSE.
|
|
//
|
|
// Copyright (c) Microsoft Corporation. All rights reserved
|
|
// Module Name:
|
|
// WcnFdHlpr.cpp
|
|
//
|
|
// Abstract:
|
|
// Wcn Function discovery helper implements the necessary interfaces to interact with Function discovery
|
|
// such that the caller can start a Function Discovery search to retrieve a WCN device instance
|
|
// based on a supplied UUID.
|
|
|
|
#include "WcnFdHelper.h"
|
|
|
|
/*--
|
|
This function converts any byte to its corresponding HEX representation. This is used
|
|
for converting the SSID to HEX to be used in FD query.
|
|
--*/
|
|
|
|
HRESULT ConvertStringToHex(
|
|
__in DWORD cbBlob,
|
|
__in_bcount(cbBlob) BYTE const *pbBlob,
|
|
__in DWORD cchHex,
|
|
__out_ecount(cchHex) PWSTR wszHex)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
static WCHAR HEX[] = L"0123456789ABCDEF";
|
|
DWORD i;
|
|
|
|
if (!pbBlob || !wszHex )
|
|
{
|
|
return E_POINTER;
|
|
}
|
|
|
|
if (cchHex < 2*cbBlob + 1)
|
|
{
|
|
hr = HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER);
|
|
goto Cleanup;
|
|
}
|
|
|
|
for (i = 0; i < cbBlob; ++i)
|
|
{
|
|
wszHex[2*i] = HEX[(pbBlob[i] & 0xf0) >> 4];
|
|
wszHex[2*i+1] = HEX[(pbBlob[i] & 0x0f)];
|
|
}
|
|
|
|
wszHex[2*cbBlob] = L'\0';
|
|
|
|
Cleanup:
|
|
|
|
return hr;
|
|
}
|
|
CWcnFdDiscoveryNotify::CWcnFdDiscoveryNotify() :
|
|
m_pFunctionDiscovery(0),
|
|
m_pFunctionInstanceCollectionQuery(0),
|
|
m_pFiCollection(0)
|
|
{
|
|
}
|
|
|
|
CWcnFdDiscoveryNotify::~CWcnFdDiscoveryNotify()
|
|
{
|
|
|
|
if(anySearchEvent)
|
|
{
|
|
CloseHandle(anySearchEvent);
|
|
}
|
|
}
|
|
|
|
//when function discovery finds device during its search look at the device to see if its the one you want
|
|
HRESULT CWcnFdDiscoveryNotify::OnUpdate(
|
|
__in QueryUpdateAction enumQueryUpdateAction,
|
|
__in FDQUERYCONTEXT fdqcQueryContext,
|
|
__in IFunctionInstance *pIFunctionInstance)
|
|
{
|
|
|
|
HRESULT hr = S_OK;
|
|
CComPtr<IPropertyStore> pPropStore;
|
|
PROPVARIANT propVar;
|
|
PropVariantInit(&propVar);
|
|
|
|
WCHAR wszSrchUUID[64] = {0};
|
|
|
|
UNREFERENCED_PARAMETER(pIFunctionInstance);
|
|
UNREFERENCED_PARAMETER(fdqcQueryContext);
|
|
|
|
|
|
switch(enumQueryUpdateAction)
|
|
{
|
|
case QUA_ADD:
|
|
//QUA_ADD is called whenever a new device appears on the network.
|
|
//("New" is defined as "new to you", so at the start of a query, you may
|
|
// see a bunch of QUA_ADDs for all the existing devices).
|
|
//
|
|
//You can inspect the Function Instance's properties by calling
|
|
//IFunctionInstance::OpenPropertyStore. You can also get an IWCNDevice
|
|
//interface from the IFunctionInstance interface pointer by calling
|
|
//IFunctionInstance::QueryService.
|
|
//
|
|
//The IFunctionInstance interface pointer that is passed into this OnUpdate
|
|
//callback doesn't belong to you, so if you want to save it for use beyond
|
|
//the lifetime of this callback, you need to add a reference to it (and
|
|
//possibly marshal it over to your thread). Since this sample uses
|
|
//CComPtr smart pointers, the additional reference count is automatic.
|
|
|
|
//open the property store of this function instance to extract the UUID of the device
|
|
hr = pIFunctionInstance->OpenPropertyStore(STGM_READ,
|
|
&pPropStore);
|
|
|
|
if(FAILED(hr))
|
|
{
|
|
wprintf(L"\nERROR: Failed to open PropertyStore for FI.");
|
|
goto cleanup;
|
|
}
|
|
|
|
|
|
//We need to decide if this IFunctionInstance belongs to the device
|
|
//that we are interested in. This sample demonstrates two different
|
|
//ways of matching an IFunctionInstance:
|
|
// 1. Match an AP's SSID; or
|
|
// 2. Match any device's WPS UUID.
|
|
if (bUseSSID) // 1. Match by SSID
|
|
{
|
|
hr = pPropStore->GetValue(PKEY_WCN_SSID, &propVar);
|
|
if(FAILED(hr))
|
|
{
|
|
wprintf(L"\nERROR: Failed to Get SSID Value");
|
|
goto cleanup;
|
|
}
|
|
|
|
if(propVar.vt != VT_LPWSTR)
|
|
{
|
|
wprintf(L"\nERROR: SSID Id is not VT_LPWSTR");
|
|
goto cleanup;
|
|
}
|
|
|
|
if (_wcsicmp(propVar.pwszVal, wszSSIDInHex) == 0)
|
|
{
|
|
// The SSID matches the one we computed earlier, so save the
|
|
// function instance and tell the main thread it can continue.
|
|
//
|
|
// Note that if you are using an apartment threaded main thread
|
|
// (e.g., it is a GUI thread), you'll have to marshal this pointer
|
|
// so that it isn't smuggled across an apartment boundary.
|
|
// This sample has all threads in the MTA, so we don't need
|
|
// to marshal anything.
|
|
//
|
|
// If you aren't using smart pointers, make sure to do AddRef here.
|
|
m_pFunctionInstance = pIFunctionInstance;
|
|
SetEvent(anySearchEvent);
|
|
}
|
|
}
|
|
else // 2. Match by WPS UUID
|
|
{
|
|
hr = pPropStore->GetValue(PKEY_PNPX_GlobalIdentity,&propVar);
|
|
if(FAILED(hr))
|
|
{
|
|
wprintf(L"\nERROR: Failed to Get UUID Value");
|
|
goto cleanup;
|
|
}
|
|
|
|
if(propVar.vt != VT_LPWSTR)
|
|
{
|
|
wprintf(L"\nERROR: UUID Id is not VT_LPWSTR");
|
|
goto cleanup;
|
|
}
|
|
|
|
// put curly braces around UUID so that you can compare it.
|
|
StringCchCopy(wszSrchUUID+1, RTL_NUMBER_OF(wszSrchUUID)-1, propVar.pwszVal);
|
|
wszSrchUUID[0] = L'{';
|
|
wszSrchUUID[37] = L'}';
|
|
wszSrchUUID[38] = L'\0';
|
|
|
|
//Compare extracted UUID with the desired UUID
|
|
if(_wcsicmp(wszSrchUUID,wszUUID) == 0)
|
|
{
|
|
// The UUID matches, so save the interface. See the comments
|
|
// above about threading and reference counting.
|
|
m_pFunctionInstance = pIFunctionInstance;
|
|
SetEvent(anySearchEvent);
|
|
}
|
|
}
|
|
|
|
break;
|
|
|
|
case QUA_CHANGE:
|
|
// The function instance may have changed one or more of its properties.
|
|
// If you are displaying the properties to the user, you should mark the
|
|
// display as dirty and refresh them soon.
|
|
//
|
|
// This sample code does not display device icons in a GUI, so it doesn't
|
|
// handle QUA_CHANGE.
|
|
break;
|
|
|
|
case QUA_REMOVE:
|
|
// The function instance is about to be removed. You shouldn't take a
|
|
// reference to the IFunctionInstance here, because it points to a device
|
|
// that no longer exists anyway. Instead, check its UUID and see if you
|
|
// were displaying any device with that UUID to the user. If so, remove
|
|
// the device from your display. Make sure to release any COM interfaces
|
|
// that point to the device being removed.
|
|
//
|
|
// This sample code does not display device icons in a GUI, so it doesn't
|
|
// handle QUA_REMOVE.
|
|
break;
|
|
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
cleanup:
|
|
|
|
PropVariantClear(&propVar); //Free PKEY_PNPX_GlobalIdentity if not the one we want otherwise will be freed on cleanup
|
|
return hr;
|
|
}
|
|
|
|
//this callback is invoked if there is a fatal error and the search could not be completed
|
|
HRESULT CWcnFdDiscoveryNotify::OnError(
|
|
__in HRESULT hrFD,
|
|
__in FDQUERYCONTEXT fdqcQueryContext,
|
|
__in const WCHAR *pszProvider)
|
|
{
|
|
|
|
HRESULT hr = S_OK;
|
|
|
|
UNREFERENCED_PARAMETER(fdqcQueryContext);
|
|
|
|
SetEvent(anySearchEvent);
|
|
|
|
wprintf(L"\nERROR: Provider [%s] returned error code [0x%x]",pszProvider,hrFD);
|
|
|
|
|
|
return hr;
|
|
}
|
|
|
|
//used to signal the end of the Function Discovery Search
|
|
HRESULT CWcnFdDiscoveryNotify::OnEvent(
|
|
__in DWORD dwEventID,
|
|
__in FDQUERYCONTEXT fdqcQueryContext,
|
|
__in const WCHAR *pszProvider)
|
|
{
|
|
|
|
HRESULT hr = S_OK;
|
|
|
|
UNREFERENCED_PARAMETER(fdqcQueryContext);
|
|
UNREFERENCED_PARAMETER(pszProvider);
|
|
|
|
if(dwEventID == FD_EVENTID_SEARCHCOMPLETE)
|
|
{
|
|
wprintf(L"\nINFO: FD_EVENTID_SEARCHCOMPLETE");
|
|
SetEvent(anySearchEvent);
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
// create the Function discovery instance and setup an event to be used to signal the end of the Search
|
|
HRESULT CWcnFdDiscoveryNotify::Init(__in BOOL bTurnOnSoftAP)
|
|
{
|
|
|
|
HRESULT hr = ERROR_SUCCESS;
|
|
anySearchEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
|
|
bUseSSID = FALSE;
|
|
|
|
|
|
if (anySearchEvent == 0)
|
|
{
|
|
wprintf(L"ERROR: Failed to create the search event");
|
|
hr = E_FAIL;
|
|
goto cleanup;
|
|
}
|
|
|
|
//This interface is used by client programs to discover function instances, get the default function
|
|
//instance for a category, and create advanced Function Discovery query objects that enable registering
|
|
//Function Discovery defaults, among other things.
|
|
hr = CoCreateInstance(
|
|
CLSID_FunctionDiscovery,
|
|
NULL,
|
|
CLSCTX_INPROC_SERVER | CLSCTX_NO_CODE_DOWNLOAD,
|
|
IID_PPV_ARGS(&m_pFunctionDiscovery));
|
|
|
|
if(hr != S_OK)
|
|
{
|
|
wprintf(L"\nERROR: Failed to create instance of IFunctionDiscovery");
|
|
goto cleanup;
|
|
}
|
|
|
|
//create an WCN instance collection Query for Function Discovery to use
|
|
//This interface implements the asynchronous query for a collection of function instances based on category
|
|
//and subcategory. A pointer to this interface is returned when the collection query is created by the
|
|
//client program.
|
|
hr = m_pFunctionDiscovery->CreateInstanceCollectionQuery(
|
|
FCTN_CATEGORY_WCN,
|
|
NULL,
|
|
FALSE,
|
|
(IFunctionDiscoveryNotification *) this,
|
|
NULL,
|
|
&m_pFunctionInstanceCollectionQuery);
|
|
|
|
if(hr != S_OK)
|
|
{
|
|
wprintf(L"\nFailed to create Function Discovery query.");
|
|
goto cleanup;
|
|
}
|
|
|
|
if (bTurnOnSoftAP)
|
|
{
|
|
// WCN can optionally turn on the SoftAP (aka WLAN Hosted Network) if the PC has a wireless
|
|
// adapter that supports it. You should only turn on SoftAP if you expect a wireless device
|
|
// (like a wireless printer or a wireless picture frame) to connect to the hosted network.
|
|
//
|
|
// This API will return S_OK regardless if the computer supports SoftAP or not. If you really
|
|
// need to know if SoftAP is not supported, call WlanHostedNetworkQueryStatus and check
|
|
// if the returned HostedNetworkState is wlan_hosted_network_unavailable.
|
|
hr = m_pFunctionInstanceCollectionQuery->AddQueryConstraint(
|
|
WCN_QUERY_CONSTRAINT_USE_SOFTAP,
|
|
FD_CONSTRAINTVALUE_TRUE);
|
|
|
|
if (hr != S_OK)
|
|
{
|
|
wprintf(L"\nFailed to add the softap query constraint hr=[0x%x]", hr);
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
|
|
cleanup:
|
|
|
|
return hr;
|
|
}
|
|
|
|
//start the Function Discovery search based on the supplied UUID
|
|
HRESULT CWcnFdDiscoveryNotify::WcnFDSearchStart(__in UUID* pUUID, __in PWSTR pSearchSSID)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
if(m_pFunctionInstanceCollectionQuery == NULL)
|
|
{
|
|
wprintf(L"\nERROR: FD Instance query is NULL.");
|
|
hr = E_FAIL;
|
|
goto cleanup;
|
|
}
|
|
|
|
//prefer the uuid over the SSID
|
|
if (*pUUID != GUID_NULL)
|
|
{
|
|
//convert the UUID to a string
|
|
if(StringFromGUID2(*pUUID, wszUUID, ARRAYSIZE(wszUUID)) == 0)
|
|
{
|
|
wprintf(L"\nERROR: Convert from UUID to STRING Failed");
|
|
goto cleanup;
|
|
}
|
|
bUseSSID = FALSE;
|
|
}
|
|
//USE the SSID to find the Router, NOTE: this will return the first SSID it finds which may not be
|
|
//the rotuer you want.
|
|
else if (pSearchSSID)
|
|
{
|
|
//A SSID is an array of octets. Since Function Discovery cannot filter searches based on
|
|
//a raw byte array, the SSID is actually encoded in hexadecimal before being saved in the
|
|
//FD property bag. If you plan to use the PKEY_WCN_SSID, you should first convert the
|
|
//raw octets to Unicode, then convert the Unicode to hexadecimal.
|
|
BYTE Buf[DOT11_SSID_MAX_LENGTH+1];
|
|
|
|
int r = WideCharToMultiByte(
|
|
CP_ACP,
|
|
WC_NO_BEST_FIT_CHARS,
|
|
pSearchSSID,
|
|
-1,
|
|
(LPSTR)Buf,
|
|
ARRAYSIZE(Buf),
|
|
NULL,
|
|
NULL);
|
|
|
|
if (r == 0)
|
|
{
|
|
hr = HRESULT_FROM_WIN32(GetLastError()) ;
|
|
wprintf(L"WideCharToMultiByte failed to covert the input ssid.");
|
|
goto cleanup;
|
|
}
|
|
|
|
hr = ConvertStringToHex(
|
|
r-1,
|
|
Buf,
|
|
ARRAYSIZE(wszSSIDInHex),
|
|
wszSSIDInHex);
|
|
|
|
if (hr != S_OK)
|
|
{
|
|
wprintf(L"ERROR: Converting the SSID [%s] to HEX failed with error code [%x]", pSearchSSID, hr);
|
|
goto cleanup;
|
|
}
|
|
|
|
bUseSSID = TRUE;
|
|
|
|
}
|
|
else
|
|
{
|
|
wprintf(L"\nERROR: Search UUID and Search SSID are blank.");
|
|
goto cleanup;
|
|
}
|
|
|
|
|
|
//Start the search
|
|
wprintf(L"\nINFO: Stating the Function Discovery Search...");
|
|
|
|
//Performs the query defined by IFunctionDiscovery::CreateInstanceCollectionQuery.
|
|
hr = m_pFunctionInstanceCollectionQuery->Execute(&m_pFiCollection);
|
|
|
|
// We expect asynchronous results.
|
|
if (hr == E_PENDING)
|
|
{
|
|
hr = S_OK;
|
|
}
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
wprintf(L"\nERROR: Function Discovery query failed to run with the following error hr = 0x%x.", hr);
|
|
goto cleanup;
|
|
}
|
|
|
|
cleanup:
|
|
|
|
return hr;
|
|
}
|
|
|
|
//monitor when the Function Discovery events
|
|
BOOL CWcnFdDiscoveryNotify::WaitForAnyDiscoveryEvent(DWORD Timeout_ms)
|
|
{
|
|
DWORD Index = 0;
|
|
|
|
HRESULT hr = CoWaitForMultipleHandles(
|
|
COWAIT_WAITALL,
|
|
Timeout_ms,
|
|
1,
|
|
&anySearchEvent,
|
|
&Index);
|
|
|
|
if (hr == S_OK && Index == 0)
|
|
{
|
|
ResetEvent(anySearchEvent);
|
|
return TRUE;
|
|
}
|
|
else
|
|
{
|
|
wprintf(L"\nERROR: Discovery timeout (after waiting %ums).", Timeout_ms);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
//once Function Discovery finds the instance we are looking for set the WCN Device instance
|
|
BOOL CWcnFdDiscoveryNotify::GetWCNDeviceInstance( __deref_out_opt IWCNDevice** ppWcnDevice)
|
|
{
|
|
HRESULT hr = E_FAIL;
|
|
BOOL returnValue = FALSE;
|
|
|
|
*ppWcnDevice = NULL;
|
|
|
|
//The instance we were looking for was not found.
|
|
if( m_pFunctionInstance == NULL)
|
|
{
|
|
goto cleanup;
|
|
}
|
|
|
|
//Attempt to get IWCNDevice
|
|
//Acts as the factory method for any services exposed through an implementation of
|
|
//IFunctionInstance. QueryService creates and initializes instances of a requested interface
|
|
//if the service from which the interface was requested supports the interface.
|
|
hr = m_pFunctionInstance->QueryService(
|
|
SID_WcnProvider,
|
|
IID_PPV_ARGS(ppWcnDevice));
|
|
|
|
if(hr != S_OK)
|
|
{
|
|
wprintf(L"\nERROR: Failed to get IWCNDevice from the Function Instance hr=[0x%x].",hr);
|
|
returnValue = FALSE;
|
|
goto cleanup;
|
|
}
|
|
|
|
|
|
returnValue = TRUE;
|
|
|
|
cleanup:
|
|
|
|
return returnValue;
|
|
}
|