2025-11-28 00:35:46 +09:00

659 lines
21 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.
//////////////////////////////////////////////////////////////////////////////
#include <windows.h>
#include <stdlib.h>
#include <strsafe.h>
#include <string.h>
#include <stdio.h>
#include <conio.h>
#include <time.h>
#include <wsdapi.h>
#include "Common.h"
#include "ClientNotificationSink.h"
#include "Client.h"
//Usages: WSDiscoveryClient.exe [/a EndpointReference] | [/sr | /sc] scope [...]
//You can specify exactly one of /a, /sr or /sc - not a combination of them.
//
//An EndpointReference is the ID of the target service, which must be a valid
//URI as per Section 2.6 of the WS-Discovery Specifications. Specifically, it must
//begin with one of http://, https://, uri:, urn:uuid: or uuid:.
//
//Examples:
//
//To find all WS-Discovery enabled target services: (Probe)
//WSDiscoveryClient.exe
//
//To find a device with a given EndpointReference: (Resolve)
//WSDiscoveryClient.exe /a urn:uuid:91022ed0-7d3d-11de-8a39-0800200c9a66
//
//To find a device with two scopes using the RFC2396 scope matching rule: (Probe)
//WSDiscoveryClient.exe /sr http://www.contoso.com/wsd/scope1 http://www.contoso.com/wsd/scope2
//
//To find a device with a given scope using the custom scope matching rule: (Probe)
//WSDiscoveryClient.exe /sc http://www.contoso.com/wsd/special/123
int _cdecl wmain
( int argc
, _In_reads_( argc ) LPWSTR *argv
)
{
IWSDiscoveryProvider *provider = NULL;
IWSDiscoveryProviderNotify *tempNotify = NULL;
CClientNotificationSink *sink = NULL;
LPCWSTR tag = NULL;
LPCWSTR epr = NULL;
LPCWSTR matchByRule = NULL;
WSD_URI_LIST *scopesList = NULL;
BOOL isSinkAttached = FALSE;
HRESULT hr = S_OK;
HRESULT hr2 = S_OK;
wprintf( L"----------------------------------------\r\n" );
wprintf( L"WS-Discovery SDK Sample Client\r\n" );
wprintf( L"Copyright (c) Microsoft Corporation. All rights reserved.\r\n" );
wprintf( L"----------------------------------------\r\n" );
hr = ParseArguments(
argc, argv, &epr, &scopesList, &matchByRule );
if ( S_OK != hr )
{
PrintErrorMessage( L"Failed to parse arguments", hr );
}
if ( S_OK == hr && NULL != epr )
{
// If an EndpointReference is specified, make sure that the
// EndpointReference is a valid one before proceeding.
// (This is purely for cosmetic purposes.)
hr = ValidateEndpointReference( epr );
}
if ( S_OK == hr )
{
// Create the discovery provider.
// Note: This sample does not include searching with Types. If you would like
// to search using Types, you will need to create an IWSDXMLContext and pass
// the parameter in WSDCreateDiscoveryProvider. IWSDXMLContext is later used
// to build the WSD_NAME_LIST structures.
wprintf( L"Creating the discovery provider...\r\n" );
hr = WSDCreateDiscoveryProvider( NULL, &provider );
if ( S_OK != hr )
{
PrintErrorMessage( L"Failed to create discovery provider", hr );
}
}
if ( S_OK == hr )
{
// Create the notification sink for callbacks
// in the provider
wprintf( L"Creating the notification sink...\r\n" );
hr = CreateClientNotificationSink(&sink);
if ( S_OK != hr )
{
PrintErrorMessage( L"Failed to create notification sink", hr );
}
}
if ( S_OK == hr )
{
// Obtain the discovery provider notify (callback object)
// needed to be attached to the provider
wprintf( L"Obtaining the IWSDiscoveryProviderNotify interface " );
wprintf( L"from the notification sink...\r\n" );
hr = sink->QueryInterface(
__uuidof( IWSDiscoveryProviderNotify ), (void**)&tempNotify );
if ( S_OK != hr )
{
PrintErrorMessage( L"Failed to query interface for IWSDiscoveryProviderNotify", hr );
}
}
if ( S_OK == hr )
{
// Attach the notification sink to the provider to receive
// callbacks of Hello, ProbeMatches and Bye messages. Once the
// sink is attached successfully, the notification sink begins
// to listen to callbacks. If you have custom routines in the
// notification sink that requires resources such as internal
// variables, you should ensure that those variables are properly
// initialized before attaching the sink to the provider.
wprintf( L"Attaching the notification sink...\r\n" );
hr = provider->Attach( tempNotify );
if ( S_OK != hr )
{
PrintErrorMessage(
L"Failed to attach notification sink to the provider", hr );
}
}
if ( S_OK == hr )
{
// Set flag to true so that the sink will be detached on exit.
isSinkAttached = TRUE;
// Generate tag for use with the search query.
// Note that the tag generation is done for demo purposes only.
// In reality, any string may be used as a tag. It may even be
// a hardcoded string. The tag is sent as part of the Probe
// or Resolve message, and will be returned in the ProbeMatches
// and ResolveMatches messages, making it easier for you to
// distinguish between what messages are responses to your search
// requests. The use of a tag, however, is optional.
wprintf( L"Generating a tag for use in this client: " );
hr = GenerateTag( &tag );
if ( S_OK != hr )
{
PrintErrorMessage( L"Failed to generate tag", hr );
}
else
{
wprintf( L"%s\r\n", tag );
}
}
if ( S_OK == hr )
{
wprintf( L"\r\n" );
wprintf( L"----------------------------------------\r\n" );
wprintf( L"Search begins - press any key to terminate the client...\r\n" );
wprintf( L"----------------------------------------\r\n" );
// A general client message pattern looks like the following.
//
// Client sends a Probe message (IWSDiscoveryProvider::SearchByType)
// to search for the types of target services (or all services) within
// a given number of scopes (or all scopes available).
//
// (IWSDiscoveryProvider::SearchByType searches both type and scope.
// In this sample, only the scope is used.)
//
// Client receives a ProbeMatches message from those target services
// that matches (IWSDiscoveryProviderNotify::Add).
//
// If the received ProbeMatches message does not contain an XAddrs list,
// then the client, if it wishes, shall send a Resolve message
// (IWSDiscoveryProvider::SearchById) to request the given target service
// to provide an XAddrs list of transport addresses.
//
// Client receives a ResolveMatches message from the given target service
// (IWSDiscoveryProviderNotify::Add).
//
// Note that this sample does not implement the full message pattern listed above.
// It only executes either IWSDiscoveryProvider::SearchById or
// IWSDiscoveryProvider::SearchByType one at a time depending on what
// the command line arguments are given to this application.
// epr != NULL - /a - SearchById - Resolve
if ( NULL != epr )
{
// Calls SearchById to send a Resolve message. The notification
// sink will listen to ResolveMatches message, and shall call
// IWSDiscoveryProviderNotify::Add (implemented by
// CClientNotificationSink::Add in this case) when a ResolveMatches
// message arrives.
//
// Note that SearchById is an asynchronous call.
// The return code for this is solely based on whether that
// async call has been started successfully. The results of the
// search are done through callback methods implemented in the
// notification sink.
hr = provider->SearchById( epr, tag );
if ( S_OK != hr )
{
PrintErrorMessage( L"SearchById failed...", hr );
}
}
// case 1 - matchByRule != NULL && scopesList != NULL (they both will be non-null)
// - /sc or /sr - SearchByType - Probe
// case 2 - they are all NULL
// - no arguments - SearchByType - Probe
else
{
// Calls SearchByType to send a Probe message. Types, Scopes and
// MatchBy rule are included in the Probe message if they are provided.
// If none of them are provided, the Probe message searches for all
// WS-Discovery enabled target services. The notification sink will
// listen to ProbeMatches messages, and shall call IWSDiscoveryProviderNotify::Add
// (implemented by CClientNotificationSink::Add in this case) when
// a ProbeMatches message arrives.
//
// Note that Types, Scopes and MatchBy rule are all optional.
// If scopes are specified but MatchBy rule is not, then RFC2396
// scope matching rule is used as defined by WS-Discovery specifications.
//
// Also note that this sample does not cover the usage of Types.
// If you need to send a Probe message with Types, you should
// include the IWSDXMLContext when calling WSDCreateDiscoveryProvider
// above, and build your own WSD_NAME_LIST structure here. Building
// this structure requires you to call IWSDXMLContext::AddNameToNamespace
// method in order to extract WSDXML_NAME objects from the XML context.
//
// Please see MSDN documentation for details.
//
// SearchByType is also an async call. See above for comments.
hr = provider->SearchByType( NULL, scopesList , matchByRule, tag );
if ( S_OK != hr )
{
PrintErrorMessage( L"SearchByType", hr );
}
}
(void)_getch(); // Wait for key to be pressed for client termination
}
// Key has been pressed to terminate client
if ( NULL != provider && isSinkAttached )
{
// This detaches the notification sink. We do this regardless whether
// S_OK == hr or not, and it is important to do this upon exit if a
// notification sink has previously been attached to the provider. Not
// doing so may result in an access violation when the provider is being
// destroyed. When this method returns, all callback methods would have
// been completed, and will cease to listen to any more messages.
//
// Use hr2 so that the original value of hr is not lost.
hr2 = provider->Detach();
if ( S_OK != hr2 )
{
PrintErrorMessage( L"Failed to detach", hr2 );
}
}
// clean up and exit routines below
if ( NULL != tag )
{
delete[] tag;
tag = NULL;
}
if ( NULL != sink )
{
sink->Release();
sink = NULL;
}
if ( NULL != tempNotify )
{
tempNotify->Release();
tempNotify = NULL;
}
if ( NULL != provider )
{
provider->Release();
provider = NULL;
}
if ( NULL != epr )
{
delete[] epr;
epr = NULL;
}
if ( NULL != matchByRule )
{
delete[] matchByRule;
matchByRule = NULL;
}
if ( NULL != scopesList )
{
WSDFreeLinkedMemory( scopesList );
scopesList = NULL;
}
if ( S_OK != hr )
{
// display usage if it fails for any reason
wprintf( L"\r\n" );
DisplayUsages();
wprintf( L"\r\n" );
}
wprintf( L"WS-Discovery SDK Sample Client Terminated\r\n" );
wprintf( L"Press any key to exit..." );
(void)_getch();
return 0;
}
void DisplayUsages()
{
wprintf( L"Usages: WSDiscoveryClient.exe [/a EndpointReference] | [/sr | /sc] scope [...]\r\n" );
wprintf( L"You can specify exactly one of /a, /sr or /sc - not a combination of them.\r\n" );
wprintf( L"\r\n" );
wprintf( L"An EndpointReference is the ID of the target service, which must be a valid\r\n" );
wprintf( L"URI as per Section 2.6 of the WS-Discovery Specifications. Specifically, it must\r\n" );
wprintf( L"begin with one of http://, https://, uri:, urn:uuid: or uuid:.\r\n" );
wprintf( L"\r\n" );
wprintf( L"Examples:\r\n" );
wprintf( L"\r\n" );
wprintf( L"To find all WS-Discovery enabled target services: (Probe)\r\n" );
wprintf( L"WSDiscoveryClient.exe\r\n" );
wprintf( L"\r\n" );
wprintf( L"To find a device with a given EndpointReference: (Resolve)\r\n" );
wprintf( L"WSDiscoveryClient.exe /a urn:uuid:91022ed0-7d3d-11de-8a39-0800200c9a66\r\n" );
wprintf( L"\r\n" );
wprintf( L"To find a device with two scopes using the RFC2396 scope matching rule: (Probe)\r\n" );
wprintf( L"WSDiscoveryClient.exe /sr http://www.contoso.com/wsd/scope1 " );
wprintf( L"http://www.contoso.com/wsd/scope2 \r\n" );
wprintf( L"\r\n" );
wprintf( L"To find a device with a given scope using the custom scope matching rule: (Probe)\r\n" );
wprintf( L"WSDiscoveryClient.exe /sc http://www.contoso.com/wsd/special/123 \r\n" );
}
// Generates a tag name of the following format:
// Tag0000
// where 0000 is a random number between 0000 to 9999.
// The caller should call delete[] on the string when
// it is no longer needed.
_Success_( return == S_OK )
HRESULT GenerateTag
( _Outptr_ LPCWSTR *generatedTag
)
{
HRESULT hr = S_OK;
LPWSTR tempTag = NULL;
int randomNum = 0;
if ( NULL == generatedTag )
{
hr = E_POINTER;
}
else
{
*generatedTag = NULL;
}
if ( S_OK == hr )
{
// 12345678
// Tag0000
tempTag = new WCHAR[8];
if ( NULL == tempTag )
{
hr = E_OUTOFMEMORY;
}
}
if ( S_OK == hr )
{
// generate a random number between 0000 to 9999
srand( (unsigned int)time( NULL ) );
randomNum = rand() % 10000;
// create the tag string
// (StringCchPrintfW always NULL terminates the string)
hr = StringCchPrintfW( tempTag, 8, L"Tag%04d", randomNum );
}
if ( S_OK == hr )
{
// outside pointer now owns temp string
*generatedTag = tempTag;
tempTag = NULL;
}
if ( NULL != tempTag )
{
delete[] tempTag;
tempTag = NULL;
}
return hr;
}
// Parses the command line arguments according to rules
// specified in the DisplayUsages() function.
// The caller should pass in argc and argv as they were
// being passed into the wmain() function.
// The caller should call WSDFreeLinkedMemory on the
// returned scopesList, and delete[] on epr and
// matchByRule when they are no longer needed.
_Success_( return == S_OK )
HRESULT ParseArguments
( _In_ int argc
, _In_reads_( argc ) LPWSTR* argv
, _Outptr_result_maybenull_ LPCWSTR *epr
, _Outptr_result_maybenull_ WSD_URI_LIST **scopesList
, _Outptr_result_maybenull_ LPCWSTR *matchByRule
)
{
HRESULT hr = S_OK;
LPWSTR tempEpr = NULL;
LPWSTR tempMatchByRule = NULL;
WSD_URI_LIST *tempScopesList = NULL;
// should be at least 1 - 1st arg = exe name
if ( 1 > argc || NULL == argv || NULL == *argv )
{
hr = E_INVALIDARG;
}
else if ( NULL == epr ||
NULL == scopesList ||
NULL == matchByRule )
{
hr = E_POINTER;
}
else
{
*epr = NULL;
*scopesList = NULL;
*matchByRule = NULL;
}
// expecting arguments to be in this format:
// argv[0] = executable name - ignore
// argv[1] = if present, must be one of /a, /sr or /sc
// if not present, then argc = 1
// if argv[1] = /a,
// then argv[2] = EndpointReference
// argc must be = 3
// if argv[1] = /sr or /sc,
// then argv[2] and above will be the scopes
// to search for
// argc must be >= 3
if ( 1 >= argc )
{
// do nothing - no parsing required
// will discover all WS-Discovery enabled services
// Probe
}
else if ( 0 == wcscmp( L"/a", argv[1] ) )
{
// search using endpoint reference address
// Resolve
// argc must be = 3
if ( 3 != argc )
{
hr = E_INVALIDARG; // fail to parse
}
if ( S_OK == hr )
{
hr = DeepCopyString(argv[2], &tempEpr); // deep copy epr
}
if ( S_OK == hr )
{
// outside pointer now owns temp epr
*epr = tempEpr;
tempEpr = NULL;
}
}
else if ( 0 == wcscmp( L"/sr", argv[1] ) ||
0 == wcscmp( L"/sc", argv[1] ) )
{
// search using scopes
// Probe
// argc must be >= 3
if ( 3 > argc )
{
hr = E_INVALIDARG; // fail to parse
}
if ( S_OK == hr )
{
// /sr - use RFC2396
// /sc - use Customs Rule
if ( L'r' == argv[1][2] ) // /sr
{
hr = DeepCopyString( MATCHBY_RFC2396, &tempMatchByRule );
}
else // /sc
{
hr = DeepCopyString( MATCHBY_CUSTOM, &tempMatchByRule );
}
}
if ( S_OK == hr )
{
// parse the scopes beginning with argv[2] and above
hr = ParseScopes( argc, argv, 2, &tempScopesList );
}
if ( S_OK == hr )
{
// outside pointer now owns the temp list
// and the match by rule
*scopesList = tempScopesList;
tempScopesList = NULL;
*matchByRule = tempMatchByRule;
tempMatchByRule = NULL;
}
}
else
{
hr = E_INVALIDARG;
}
if ( NULL != tempScopesList )
{
WSDFreeLinkedMemory( tempScopesList );
tempScopesList = NULL;
}
if ( NULL != tempEpr )
{
delete[] tempEpr;
tempEpr = NULL;
}
if ( NULL != tempMatchByRule )
{
delete[] tempMatchByRule;
tempMatchByRule = NULL;
}
return hr;
}
HRESULT ValidateEndpointReference
( _In_ LPCWSTR epr
)
{
HRESULT hr = S_OK;
LPCWSTR tempEpr = NULL; // (soft copy) used to determine if this is a logical or physical address
if ( NULL == epr )
{
hr = E_INVALIDARG;
}
if ( S_OK == hr )
{
// Determine whether this EndpointReference begins with one of
// http://, https:// or uri:
if ( NULL != wcsstr( epr, L"http://" ) ||
NULL != wcsstr( epr, L"https://" ) ||
NULL != wcsstr( epr, L"uri:" ) )
{
// this is a valid EndpointReference beginning with
// http://, https:// or uri:
tempEpr = epr;
}
}
if ( S_OK == hr && NULL == tempEpr )
{
// Determine whether this EndpointReference begins with urn:uuid:
tempEpr = wcsstr( epr, L"urn:uuid:" );
if ( NULL != tempEpr )
{
// The EndpointReference begins with urn:uuid:.
// 1 2 3 4
// 123456789012345678901234567890123456789012345
// urn:uuid:f452f1ae-fbb4-11de-a6bb-00cc30bfc300
// It must therefore be exactly 45 characters long
if ( wcslen( tempEpr ) != 45 )
{
hr = E_INVALIDARG;
}
}
}
if ( S_OK == hr && NULL == tempEpr )
{
// Determine whether this EndpointReference begins with urn:uuid:
tempEpr = wcsstr( epr, L"uuid:" );
if ( NULL != tempEpr )
{
// The EndpointReference begins with uuid:.
// 1 2 3 4
// 12345678901234567890123456789012345678901
// uuid:f452f1ae-fbb4-11de-a6bb-00cc30bfc300
// It must therefore be exactly 41 characters long
if ( wcslen( tempEpr ) != 41 )
{
hr = E_INVALIDARG;
}
}
}
if ( S_OK == hr && NULL == tempEpr )
{
// The EndpointReference does not begin with one of the
// appropriate schemes.
hr = E_INVALIDARG;
}
tempEpr = NULL;
return hr;
}