//
// Copyright (c) 1999 Microsoft Corporation
//
// HIDCOM.EXE -- exploratory USB Phone Console Application
//
// audio.cpp -- audio magic
//
// Zoltan Szilagyi, July - August, 1999
//
// Prioritized to-do list:
//
// * Convert printfs to debug tracing, and define debug tracing to
//   printfs for HidCom.Exe use.
//
// * GetInstanceFromDeviceName should look only for audio devices.
//   This should somewhat reduce the 2 sec wave enumeration time.
//   Don't forget to remove timing debug output.
//
// * Consider changing FindWaveIdFromHardwareIdString and its helpers to take a
//   devinst only and computer the hardware ids on the fly rather than storing
//   them in arrays. This would slow it down but make the code simpler.
//
// * Small one-time memory leak: The static arrays of hardware ID
//   strings are leaked. That's a few KB per process, no increase over
//   time. If we make this a class then we'll just deallocate those
//   arrays in the destructor.
//   Also, for PNP events that cause us to recompute the mapping, we will
//   need to destroy the array at some point if the wave devices change.
//   So we need to augment the interface for this.
//




#include <wtypes.h>
#include <stdio.h>

#include "audio.h" // our own prototypes

#include <cfgmgr32.h> // CM_ functions
#include <setupapi.h> // SetupDi functions
#include <mmsystem.h> // wave functions
#include <initguid.h>
#include <devguid.h> // device guids

//
// mmddkp.h -- private winmm header file
// This is in nt\private\inc, but one must ssync and build in
// nt\private\genx\windows\inc before it will show up.
//

#include <mmddkp.h>


#include <crtdbg.h>
#define ASSERT _ASSERTE

#ifdef DBG
    #define STATIC
#else
    #define STATIC static
#endif

extern "C"
{
#include "mylog.h"
}

//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//
//
// Private helper functions
//
//
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////


//////////////////////////////////////////////////////////////////////////////
//
// MapConfigRetToWin32
//
// This routine maps some CM error return codes to Win32 return codes, and
// maps everything else to the value specied by dwDefault. This function is
// adapted almost verbatim from the SetupAPI code.
//
// Arguments:
//     CmReturnCode - IN - specifies the ConfigMgr return code to be mapped
//     dwDefault    - IN - specifies the default value to use if no explicit
//                         mapping applies
//
// Return values:
//     Setup API (Win32) error code
//

STATIC DWORD
MapConfigRetToWin32(
    IN CONFIGRET CmReturnCode,
    IN DWORD     dwDefault
    )
{
    switch(CmReturnCode) {

        case CR_SUCCESS :
            return NO_ERROR;

        case CR_CALL_NOT_IMPLEMENTED :
            return ERROR_CALL_NOT_IMPLEMENTED;

        case CR_OUT_OF_MEMORY :
            return ERROR_NOT_ENOUGH_MEMORY;

        case CR_INVALID_POINTER :
            return ERROR_INVALID_USER_BUFFER;

        case CR_INVALID_DEVINST :
            return ERROR_NO_SUCH_DEVINST;

        case CR_INVALID_DEVICE_ID :
            return ERROR_INVALID_DEVINST_NAME;

        case CR_ALREADY_SUCH_DEVINST :
            return ERROR_DEVINST_ALREADY_EXISTS;

        case CR_INVALID_REFERENCE_STRING :
            return ERROR_INVALID_REFERENCE_STRING;

        case CR_INVALID_MACHINENAME :
            return ERROR_INVALID_MACHINENAME;

        case CR_REMOTE_COMM_FAILURE :
            return ERROR_REMOTE_COMM_FAILURE;

        case CR_MACHINE_UNAVAILABLE :
            return ERROR_MACHINE_UNAVAILABLE;

        case CR_NO_CM_SERVICES :
            return ERROR_NO_CONFIGMGR_SERVICES;

        case CR_ACCESS_DENIED :
            return ERROR_ACCESS_DENIED;

        case CR_NOT_DISABLEABLE:
            return ERROR_NOT_DISABLEABLE;

        default :
            return dwDefault;
    }
}


//////////////////////////////////////////////////////////////////////////////
//
// MapConfigRetToHResult
//
// This routine maps some CM error return codes to HRESULT return codes, and
// maps everything else to the HRESULT value E_FAIL.
//
// Arguments:
//     CmReturnCode - IN - specifies the ConfigMgr return code to be mapped
//
// Return values:
//     HRESULT error code
//

STATIC HRESULT
MapConfigRetToHResult(
    IN CONFIGRET CmReturnCode
    )
{
    DWORD   dwWin32Error;
    HRESULT hr;

    //
    // Map configret --> win32
    //

    dwWin32Error = MapConfigRetToWin32(
        CmReturnCode,
        E_FAIL
        );

    //
    // Map win32 --> HRESULT
    // but don't try to map default E_FAIL, as it is not within the range for
    // a normal win32 error code.
    //

    if ( dwWin32Error == E_FAIL )
    {
        hr = E_FAIL;
    }
    else
    {
        hr = HRESULT_FROM_WIN32( dwWin32Error );
    }

    return hr;
}

//////////////////////////////////////////////////////////////////////////////
//
// CheckIfAncestor
//
// This function determines if one of the specified devnodes (the "proposed
// ancestor") is an ancestor of the other specified devnode (the "proposed
// descendant").
//
// The devnodes are arranged in a tree. If node A is an ancestor of node
// B, it just means that node A is either equal to node B, or has a child
// that is an ancestor of node B. This can also be stated in reverse --
// node C is a descendant of node D if C is equal to D, or if C's parent
// is a descendant of node D.
//
// The algorithm used here to determine ancestry is a straightforward
// application of the definition, although the recursion is removed.
//
// Arguments:
//     dwDevInstProposedAncestor   - IN  - the proposed ancestor (see above)
//     dwDevInstProposedDescendant - IN  - the proposed descendant (see above)
//     pfIsAncestor                - OUT - returns bool value indicating if
//                                          the pa is an ancestor of the pd
//
// Return values:
//     S_OK   - success
//     others - from CM_Get_Parent
//

STATIC HRESULT
CheckIfAncestor(
    IN   DWORD   dwDevInstProposedAnscestor,
    IN   DWORD   dwDevInstProposedDescendant,
    OUT  BOOL  * pfIsAncestor
    )
{
    ASSERT( ! IsBadWritePtr( pfIsAncestor, sizeof( BOOL ) ) );

    DWORD   dwCurrNode;
    HRESULT hr;

    //
    // Initially, the current node is the proposed descendant.
    //

    dwCurrNode = dwDevInstProposedDescendant;

    while ( TRUE )
    {
        //
        // Check if this node is the proposed ancestor.
        // If so, the proposed ancestor is an ancestor of the
        // proposed descendant.
        //

        if ( dwCurrNode == dwDevInstProposedAnscestor )
        {
            *pfIsAncestor = TRUE;

            hr = S_OK;

            break;
        }

        //
        // Replace the current node with the current node's parent.
        //

        CONFIGRET cr;
        DWORD     dwDevInstTemp;
    
        cr = CM_Get_Parent(
            & dwDevInstTemp,   // out: parent's devinst dword
            dwCurrNode,        // in:  child's devinst dword
            0                  // in:  flags: must be zero
            );

        if ( cr == CR_NO_SUCH_DEVNODE )
        {
            //
            // This means we've fallen off the top of the PNP tree -- the
            // proposed ancestor is not found in the proposed descendant's
            // parentage chain.
            //

            * pfIsAncestor = FALSE;

            hr = S_OK;

            break;
        }
        else if ( cr != CR_SUCCESS )
        {
            //
            // Some other error occured.
            //                   

            hr = MapConfigRetToHResult( cr );
            
            break;
        }

        dwCurrNode = dwDevInstTemp;
    }

    return hr;
}


//////////////////////////////////////////////////////////////////////////////
//
// FindClosestCommonAncestor
//
// Given a pair of devnodes identified by devinst DWORDs, this function
// finds the devinst DWORD for the closest common ancestor of the two
// devnodes.
//
// See CheckIfAncestor for a discussion of the concepts of ancestor and
// descendant. Then, devnode C is a common ancestor of devnodes A and B if C
// is an ancestor of A -AND- C is an ancestor of B. Any pair of devnodes has
// at least one common ancestor, that being the root of the PNP tree. A pair
// of devnodes may have more than one common ancestor. The set of common
// ancestors of A and B has one UNIQUE member, called the closest common
// ancestor, such that no other member of the set is a child of that node.
//
// You can compute the closest common ancestor of two nodes A and B by
// constructing a chain of nodes going from the root of the tree to A through
// all A's ancestors, and also doing the same for B. Comparing these chains
// side by side, they must be the same in at least the first node (the root).
// The closest common ancestor for A and B is the last node that is the same
// for both chains.
//
// The algorithm used here is an alternative, relatively stateless approach
// that can take more CPU time but uses less memory, doesn't involve any
// allocations, and is much easier to write (the last being the overriding
// consideration, as the PNP tree is in always shallow). The code simply walks
// up A's chain of ancestors, checking if each node is an ancestor of B. The
// first node for which this is true is the closest common ancestor of
// A and B.
//
// Arguments:
//     dwDevInstOne     - IN  - the first node ('A' above)
//     dwDevInstTwo     - IN  - the other node ('B' above)
//     pdwDevInstResult - OUT - returns the closest common ancestor
//
// Return values:
//     S_OK   - success
//     others - from CM_Get_Parent
//

STATIC HRESULT
FindClosestCommonAncestor(
    IN   DWORD   dwDevInstOne,
    IN   DWORD   dwDevInstTwo,
    OUT  DWORD * pdwDevInstResult
    )
{
    ASSERT( ! IsBadWritePtr( pdwDevInstResult, sizeof( DWORD ) ) );

    HRESULT hr;
    BOOL    fIsAncestor;
    DWORD   dwDevInstCurr;

    //
    // For each node up the chain of #1's parents, starting from #1 itself...
    //

    dwDevInstCurr = dwDevInstOne;

    while ( TRUE )
    {
        //
        // Check if this node is also in the chain of #2's parents.
        //

        hr = CheckIfAncestor(
            dwDevInstCurr,
            dwDevInstTwo,
            & fIsAncestor
            );

        if ( FAILED(hr) )
        {
            return hr;
        }

        if ( fIsAncestor )
        {
            *pdwDevInstResult = dwDevInstCurr;

            return S_OK;
        }

        //
        // Get the next node in the chain of #1's parents.
        //

        CONFIGRET cr;
        DWORD     dwDevInstTemp;
    
        cr = CM_Get_Parent(
            & dwDevInstTemp,   // out: parent's devinst dword
            dwDevInstCurr,     // in:  child's devinst dword
            0                  // in:  flags: must be zero
            );

        if ( cr != CR_SUCCESS )
        {
            //
            // dwDevInst has no parent, or some other error occured.
            //
            // This is always an error, because there must always
            // be a common parent somewhere up the chain -- the root of the PNP
            // tree!
            //                   

            return MapConfigRetToHResult( cr );
        }

        dwDevInstCurr = dwDevInstTemp;
    }
}


//////////////////////////////////////////////////////////////////////////////
//
// TrimHardwareIdString
//
// This function strips off extraneous parts of the hardware ID string as
// it is expected to appear for USB devices. The remaining parts of the string
// are those that identify the vendor, product, and product revision, which
// are together used to match devices as belonging to the same composite or
// compound device.
//
// (Actually, for devices A and B, it's not just A and B that are compared,
// it's A and the closest common parent of A and B. This ensures that the case
// of multiple identical phones in the same system is handled correctly. This
// logic of course lives outside of this funtion, though.)
//
// As an example:
//          "hid\Vid_04a6&Pid_00b9&Rev_0010&Mi_04&Col01"
//  becomes     "Vid_04a6&Pid_00b9&Rev_0010"
//
// Note that this function will routinely be applied to strings for non-USB
// devices that will not be in the same format; that's ok, since those strings
// will never match USB-generated strings, be they trimmed or not.
//
// Also, note that the hardware ID string as read from the registry actually
// consists of multiple concatenated null-terminated strings, all terminated
// by two consecutive null characters. This function just ignores strings
// beyond the first, as the first contains all the info we need.
//
// Arguments:
//     wszHardwareId - IN - the string to trim (in place)
//
// Return values:
//     TRUE    - the string looked like a valid USB hardware ID
//     FALSE   - the string did not look like a valid USB hardware ID
//

STATIC BOOL
TrimHardwareIdString(
    IN   WCHAR * wszHardwareId
    )
{

    ASSERT( ! IsBadStringPtrW( wszHardwareId, (DWORD) -1 ) );

    //
    // "volatile" is needed, otherwise the compiler blatantly ignores the
    // recalculation of dwSize after the first pass.
    //

    volatile DWORD   dwSize;
    DWORD            dwCurrPos;
    BOOL             fValid = FALSE;
    DWORD            dwNumSeparators = 0;

    //
    // Strip off leading characters up to and including the first \ from the
    // front. If there is no \ in the string, it is invalid.
    //

    dwSize = lstrlenW(wszHardwareId);

    for ( dwCurrPos = 0; dwCurrPos < dwSize; dwCurrPos ++ )
    {
        if ( wszHardwareId[ dwCurrPos ] == L'\\' )
        {
            MoveMemory(
                wszHardwareId,                     // dest
                wszHardwareId + dwCurrPos + 1,     // source
                sizeof(WCHAR) * dwSize - dwCurrPos // size, in bytes
                );

            fValid = TRUE;

            break;
        }
    }

    if ( ! fValid )
    {
        return FALSE;
    }

    //
    // Strip off trailing characters starting from the third &.
    // A string with less than two & is rejected.
    //
    // Examples:
    //
    //          Vid_04a6&Pid_00b9&Rev_0010&Mi_04&Col01
    //  becomes Vid_04a6&Pid_00b9&Rev_0010
    //
    //          Vid_04a6&Pid_00b9&Rev_0010&Mi_04
    //  becomes Vid_04a6&Pid_00b9&Rev_0010
    //
    //          CSC6835_DEV
    //  is rejected
    //

    //
    // Must recompute size because we changed it above.
    // (And note that dwSize is declared as 'volatile'.)
    //

    dwSize = lstrlenW(wszHardwareId);

    for ( dwCurrPos = 0; dwCurrPos < dwSize; dwCurrPos ++ )
    {
        if ( wszHardwareId[ dwCurrPos ] == L'&' )
        {
            dwNumSeparators ++;

            if ( dwNumSeparators == 3 )
            {
                wszHardwareId[ dwCurrPos ] = L'\0';

                break;
            }
        }
    }

    if ( dwNumSeparators < 2 )
    {
        return FALSE;
    }
    else
    {
        return TRUE;
    }
}


//////////////////////////////////////////////////////////////////////////////
//
// DevInstGetIdString
//
// This function retrieves an id string or string set for a particular
// devinst dword. The value is obtained from the registry, but the
// Configuration Manager API hides the detail of where in the registry this
// info lives.
//
// Arguments:
//     dwDevInst    - IN  - the devinst dword for which we want info
//     dwProperty   - IN  - the property to retrieve
//     pwszIdString - OUT - returns "new"ed Unicode string or string set.
//
// Return values:
//     S_OK          - success
//     E_OUTOFMEMORY - out of memory during string allocation
//     E_UNEXPECTED  - data type of returned ID is not string or mutli-string
//     others        - from CM_Get_DevNode_Registry_PropertyW
//

STATIC HRESULT
DevInstGetIdString(
    IN   DWORD    dwDevInst,
    IN   DWORD    dwProperty,
    OUT  WCHAR ** pwszIdString
    )
{
    const DWORD INITIAL_STRING_SIZE = 100;

    CONFIGRET   cr;
    DWORD       dwBufferSize = INITIAL_STRING_SIZE;
    DWORD       dwDataType   = 0;

    ASSERT( ! IsBadWritePtr( pwszIdString, sizeof( WCHAR * ) ) );


    do
    {
        //
        // Allocate a buffer to store the returned string.
        //

        *pwszIdString = new WCHAR[ dwBufferSize + 1 ];

        if ( *pwszIdString == NULL )
        {
            return E_OUTOFMEMORY;
        }

        //
        // Try to get the string in the registry; we may not have enough
        // buffer space.
        //

        cr = CM_Get_DevNode_Registry_PropertyW(
             dwDevInst,              // IN  DEVINST     dnDevInst,
             dwProperty,             // IN  ULONG       ulProperty,
             & dwDataType,           // OUT PULONG      pulRegDataType, OPT
             (void *) *pwszIdString, // OUT PVOID       Buffer,         OPT
             & dwBufferSize,         // IN  OUT PULONG  pulLength,
             0                       // IN  ULONG       ulFlags -- must be zero
             );

        if ( cr == CR_SUCCESS )
        {
            if ( ( dwDataType != REG_MULTI_SZ ) && ( dwDataType != REG_SZ ) )
            {
                //
                // Value available, but it is not a string ot multi-string. Ouch!
                //

                delete ( *pwszIdString );

                return E_UNEXPECTED;
            }
            else
            {
                return S_OK;
            }
        }
        else if ( cr != CR_BUFFER_SMALL )
        {
            //
            // It's supposed to fail with this error code because we didn't pass in
            // a buffer. Failed to get registry value type and length.
            //

            delete ( *pwszIdString );

            return MapConfigRetToHResult( cr );
        }
        else // cr == CR_BUFFER_SMALL
        {
            delete ( *pwszIdString );

            //
            // the call filled in dwBufferSize with the needed value
            //
        }

    }
    while ( TRUE );

}


//////////////////////////////////////////////////////////////////////////////
//
// HardwareIdFromDevInst
//
// This function retrieves a trimmed hardware ID string for a particular
// devinst dword. The value is obtained from the helper function
// DevInstGetIdString(), and then trimmed using TrimHardwareIdString.
//
// Arguments:
//     dwDevInst      - IN  - the devinst dword for which we want info
//     pwszHardwareId - OUT - returns "new"ed Unicode string set
//
// Return values:
//     S_OK          - success
//     E_FAIL        - not a valid string in USB format
//     others        - from DevInstGetIdString()
//

STATIC HRESULT
HardwareIdFromDevInst(
    IN   DWORD    dwDevInst,
    OUT  WCHAR ** pwszHardwareId
    )
{
    ASSERT( ! IsBadWritePtr(pwszHardwareId, sizeof( WCHAR * ) ) );

    HRESULT   hr;
    BOOL      fValid;

    hr = DevInstGetIdString(
        dwDevInst,
        CM_DRP_HARDWAREID,
        pwszHardwareId
        );

    if ( FAILED(hr) )
    {
        return hr;
    }

    //    wprintf(L"*** HardwareIdFromDevInst: devinst 0x%08x, RAW hardwareID %s\n",
    //        dwDevInst, *pwszHardwareId);

    fValid = TrimHardwareIdString( *pwszHardwareId );

    if ( ! fValid )
    {
        delete ( * pwszHardwareId );

        return E_FAIL;
    }

    // wprintf(L"HardwareIdFromDevInst: devinst 0x%08x, hardwareID %s\n",
    //    dwDevInst, *pwszHardwareId);

    return S_OK;
}


//////////////////////////////////////////////////////////////////////////////
//
// MatchHardwareIdInArray
//
// This function takes the devinst and hardware ID for a HID device and
// looks in an array of devinsts and hardware IDs for wave devices to find
// the correct wave id to use with the HID device. The correct wave id is
// the one whose hardware ID matches the HID device's hardware ID, and whose
// hardware ID matches the hardware ID for the closest common ancestor of
// itself and the HID device.
//
// Arguments:
//     dwHidDevInst     - IN  - the devinst dword for the HID device
//     wszHidHardwareId - IN  - the trimmed hardware ID string for the
//                              HID device
//     dwNumDevices     - IN  - the size of the arrays -- the number of
//                              wave ids on the system
//     pwszHardwareIds  - IN  - array of trimmed hardware id strings for
//                              the wave devices, indexed by wave ids.
//                              Some entries may be NULL to mark them as
//                              invalid.
//     pdwDevInsts      - IN  - array of devinsts for wave devices,
//                              indexed by wave ids. Some entries may be
//                              (DWORD) -1 to mark them as invalid.
//     pdwMatchedWaveId - OUT - the wave id that matches the hid device
//
// Return values:
//     S_OK   - the devinst was matched
//     E_FAIL - the devinst was not matched
//

STATIC HRESULT
MatchHardwareIdInArray(
    IN   DWORD    dwHidDevInst,
    IN   WCHAR  * wszHidHardwareId,
    IN   DWORD    dwNumDevices,
    IN   WCHAR ** pwszHardwareIds,
    IN   DWORD  * pdwDevInsts,
    OUT  DWORD  * pdwMatchedWaveId
    )
{
    ASSERT( ! IsBadStringPtrW( wszHidHardwareId, (DWORD) -1 ) );

    ASSERT( ! IsBadReadPtr( pwszHardwareIds,
                            sizeof( WCHAR * ) * dwNumDevices ) );

    ASSERT( ! IsBadReadPtr( pdwDevInsts,
                            sizeof( DWORD ) * dwNumDevices ) );

    ASSERT( ! IsBadWritePtr( pdwMatchedWaveId, sizeof(DWORD) ) );

    //
    // For each available wave id...
    //

    DWORD dwCurrWaveId;

    for ( dwCurrWaveId = 0; dwCurrWaveId < dwNumDevices; dwCurrWaveId++ )
    {
        //
        // If this particular wave device has the same stripped hardware
        // ID string as what we are searching for, then we have a match.
        // But non-USB devices have non-parsable hardware ID strings, so
        // they are stored in the array as NULLs.
        //

        if ( pwszHardwareIds[ dwCurrWaveId ] != NULL )
        {
            ASSERT( ! IsBadStringPtrW( pwszHardwareIds[ dwCurrWaveId ], (DWORD) -1 ) );

            if ( ! lstrcmpW( pwszHardwareIds[ dwCurrWaveId ], wszHidHardwareId ) )
            {
                //
                // We have a match, but we must still verify if we're on the same
                // device, not some other device that has the same hardwareID. This
                // is to differentiate between multiple identical phones on the same
                // system.
                //
                // Note: we could make the code more complex, but avoid some work in
                // most cases, by only doing this if there is more than one match based
                // on hardwareIDs alone.
                //

                DWORD     dwCommonAncestor;
                WCHAR   * wszAncestorHardwareId;
                HRESULT   hr;

                hr = FindClosestCommonAncestor(
                    dwHidDevInst,
                    pdwDevInsts[ dwCurrWaveId ],
                    & dwCommonAncestor
                    );
  
                if ( SUCCEEDED(hr) )
                {
                    //
                    // Get the hardware ID for the closest common ancestor.
                    //

                    hr = HardwareIdFromDevInst(
                        dwCommonAncestor,
                        & wszAncestorHardwareId
                        );

                    if ( SUCCEEDED(hr) )
                    {
                        //
                        // Check if they are the same. The closest common ancestor
                        // will be some sort of hub if the audio device is from
                        // some other identical phone other than the one whose HID
                        // device we are looking at.
                        //

                        BOOL fSame;

                        fSame = ! lstrcmpW( wszAncestorHardwareId,
                                            wszHidHardwareId );

                        delete wszAncestorHardwareId;

                        if ( fSame )
                        {
                            *pdwMatchedWaveId = dwCurrWaveId;

                            return S_OK;
                        }
                    }
                }
            }
        }
    }

    //
    // No match.
    //

    return E_FAIL;
}

//////////////////////////////////////////////////////////////////////////////
//
// GetInstanceFromDeviceName
//
// This function retrieves a device instance identifier based on a device
// name string. This works for any device.
//
// Arguments:
//      wszName      - IN  - the device name string
//      pdwInstance  - OUT - returns instance identifier
//
// Return values:
//      S_OK
//      various win32 errors from SetupDi fucntions
//

STATIC HRESULT
GetInstanceFromDeviceName(
    IN   WCHAR *   wszName,
    OUT  DWORD *   pdwInstance,
    IN   HDEVINFO  hDevInfo
    )
{
    ASSERT( ! IsBadStringPtrW( wszName, (DWORD) -1 ) );

    ASSERT( ! IsBadWritePtr( pdwInstance, sizeof(DWORD) ) );

    //
    // Get the interface data for this specific device
    // (based on wszName).
    //

    BOOL     fSuccess;
    DWORD    dwError;

    SP_DEVICE_INTERFACE_DATA interfaceData;
    interfaceData.cbSize = sizeof( SP_DEVICE_INTERFACE_DATA ); // required

    fSuccess = SetupDiOpenDeviceInterfaceW(
        hDevInfo,                          // device info set handle
        wszName,                           // name of the device
        0,                                 // flags, reserved
        & interfaceData                    // OUT: interface data
        );

    if ( ! fSuccess )
    {
        LOG((PHONESP_TRACE, "GetInstanceFromDeviceName - SetupDiOpenDeviceInterfaceW failed: %08x", GetLastError()));

        //
        // Need to clean up, but save the error code first, because
        // the cleanup function calls SetLastError().
        //

        dwError = GetLastError();        

        return HRESULT_FROM_WIN32( dwError );
    }

    //
    // Get the interface detail data from this interface data. This provides
    // more detailed information,including the device instance DWORD that
    // we seek.
    //

    SP_DEVINFO_DATA devinfoData;
    devinfoData.cbSize = sizeof( SP_DEVINFO_DATA ); // required

    fSuccess = SetupDiGetDeviceInterfaceDetail(
        hDevInfo,                           // device info set handle
        & interfaceData,                    // device interface data structure
        NULL,                               // OPT ptr to dev name struct
        0,                                  // OPT avail size of dev name st
        NULL,                               // OPT actual size of devname st
        & devinfoData
        );

    if ( ! fSuccess )
    {
        //
        // It is normal for the above function to fail with
        // ERROR_INSUFFICIENT_BUFFER because we passed in NULL for the
        // device interface detail data (device name) structure.
        //

        dwError = GetLastError();

        if ( dwError != ERROR_INSUFFICIENT_BUFFER )
        {
            LOG((PHONESP_TRACE, "GetInstanceFromDeviceName - SetupDiGetDeviceInterfaceDetail failed: %08x", GetLastError()));
            
            //
            // Can't clean this up earlier, because it does SetLastError().
            //

            return HRESULT_FROM_WIN32( dwError );
        }
    }

    *pdwInstance = devinfoData.DevInst;
    
    //
    // Can't clean this up earlier, because it does SetLastError().
    //

    return S_OK;
}


//////////////////////////////////////////////////////////////////////////////
//
// 
//
// This function constructs an array of devinst DWORDs and an array of
// hardware ID strigs, both indexed by wave id, for either wave in or wave
// out devices. The devinsts are retrieved by (1) using undocumented calls
// to winmm to retrieve device name strings for each wave device, and (2)
// using SetupDi calls to retrieve a DevInst DWORD for each device name
// string (helper function GetInstanceFromDeviceName).
//
// The values are saved in an array because this process takes the bulk of the
// time in the HID --> audio mapping process, and therefore finding the mapping
// for several HID devices can be done in not much more time than for one HID
// device, just by reusing the array.
//
// Arguments:
//     fRender          - IN  - if TRUE, look for wave out devices
//     pdwNumDevices    - OUT - returns number of wave devices found
//     ppwszHardwareIds - OUT - returns "new"ed array of trimmed hardware id
//                              strings. The array is indexed by wave id. If
//                              a hardware id string cannot be determined for
//                              a particular wave id, then the string pointer
//                              in that position is set to NULL. Each string
//                              is "new"ed separately.
//     ppdwDevInsts     - OUT - returns "new"ed array of devinst DWORDs. The
//                              array is indexed by wave id. If a devinst
//                              cannot be determined for a particular wave id,
//                              then the DWORD in that position is set to
//                              (DWORD) -1.
//
// Return values:
//     S_OK          - success
//     E_OUTOFMEMORY - not enough memory to allocate a device name string or
//                     the return array
//

STATIC HRESULT
ConstructWaveHardwareIdCache(
    IN   BOOL      fRender,
    OUT  DWORD *   pdwNumDevices,
    OUT  WCHAR *** ppwszHardwareIds,
    OUT  DWORD **  ppdwDevInsts
    )
{
    ASSERT( ( fRender == TRUE ) || ( fRender == FALSE ) );

    ASSERT( ! IsBadWritePtr( pdwNumDevices, sizeof( DWORD ) ) );

    ASSERT( ! IsBadWritePtr( ppwszHardwareIds, sizeof( WCHAR ** ) ) );

    ASSERT( ! IsBadWritePtr( ppdwDevInsts, sizeof( DWORD * ) ) );

    //
    // Get a device info list
    //

    HDEVINFO hDevInfo;
   
    /*
    hDevInfo = SetupDiGetClassDevs(
        &GUID_DEVCLASS_MEDIA,            // class GUID (which device classes?)
        NULL,                            // optional enumerator to filter
        NULL,                            // HWND (we have none)
        ( DIGCF_PRESENT    |             // only devices that are present
          DIGCF_PROFILE )                // only devices in this hw profile
        );
        */

    hDevInfo = SetupDiCreateDeviceInfoList(&GUID_DEVCLASS_MEDIA, NULL);

    if ( hDevInfo == NULL )
    {
        LOG((PHONESP_TRACE, "ConstructWaveHardwareIdCache - SetupDiCreateDeviceInfoList failed: %08x", GetLastError()));
        return HRESULT_FROM_WIN32(GetLastError());
    }

    //
    // Find the number of available wave devices.
    //

    DWORD     dwNumDevices;
    DWORD     dwCurrDevice;

    if ( fRender )
    {
        dwNumDevices = waveOutGetNumDevs();
    }
    else
    {
        dwNumDevices = waveInGetNumDevs();
    }
    
    //
    // Allocate space for the return arrays.
    //

    *pdwNumDevices    = dwNumDevices;

    *ppwszHardwareIds = new LPWSTR [ dwNumDevices ];

    if ( (*ppwszHardwareIds) == NULL )
    {
        return E_OUTOFMEMORY;
    }

    *ppdwDevInsts = new DWORD [ dwNumDevices ];

    if ( (*ppdwDevInsts) == NULL )
    {
        delete *ppwszHardwareIds;
        *ppwszHardwareIds = NULL;

        return E_OUTOFMEMORY;
    }

    //
    // Loop over the available wave devices.
    //

    for ( dwCurrDevice = 0; dwCurrDevice < dwNumDevices; dwCurrDevice++ )
    {
        //
        // For failure cases, we will return NULL string and -1 devinst
        // for that wave id. Callers should compare against the NULL, not
        // the -1.
        //

        (*ppwszHardwareIds) [ dwCurrDevice ] = NULL;
        (*ppdwDevInsts)     [ dwCurrDevice ] = -1;

        //
        // Get the size of the device path string.
        //
        
        MMRESULT  mmresult;
        ULONG     ulSize;

        if ( fRender )
        {
            mmresult = waveOutMessage( (HWAVEOUT) IntToPtr(dwCurrDevice),
                                       DRV_QUERYDEVICEINTERFACESIZE,
                                       (DWORD_PTR) & ulSize,
                                       0
                                     );
        }
        else
        {
            mmresult = waveInMessage( (HWAVEIN) IntToPtr(dwCurrDevice),
                                      DRV_QUERYDEVICEINTERFACESIZE,
                                      (DWORD_PTR) & ulSize,
                                      0
                                    );
        }

        if ( mmresult != MMSYSERR_NOERROR )
        {
            LOG((PHONESP_TRACE, "ConstructWaveHardwareIdCache - Could not get device string size for device %d; "
                "error = %d", dwCurrDevice, mmresult));
        }
        else if ( ulSize == 0 )
        {
            LOG((PHONESP_TRACE, "ConstructWaveHardwareIdCache - Got zero device string size for device %d",
                dwCurrDevice));
        }
        else
        {
            //
            // Allocate space for the device path string.
            //

            WCHAR * wszDeviceName;

            wszDeviceName = new WCHAR[ (ulSize / 2) + 1 ];

            if ( wszDeviceName == NULL )
            {
                LOG((PHONESP_TRACE, "ConstructWaveHardwareIdCache - Out of memory in device string alloc for device %d;"
                    " requested size is %d\n", dwCurrDevice, ulSize));

                delete *ppwszHardwareIds;
                *ppwszHardwareIds = NULL;

                delete *ppdwDevInsts;
                *ppdwDevInsts = NULL;

                return E_OUTOFMEMORY;
            }

            //
            // Get the device path string from winmm.
            //

            if ( fRender )
            {
                mmresult = waveOutMessage( (HWAVEOUT) IntToPtr(dwCurrDevice),
                                           DRV_QUERYDEVICEINTERFACE,
                                           (DWORD_PTR) wszDeviceName,
                                           (DWORD_PTR) ulSize
                                         );
            }
            else
            {
                mmresult = waveInMessage( (HWAVEIN) IntToPtr(dwCurrDevice),
                                          DRV_QUERYDEVICEINTERFACE,
                                          (DWORD_PTR) wszDeviceName,
                                          (DWORD_PTR) ulSize
                                        );
            }

            if ( mmresult == MMSYSERR_NOERROR )
            {
                //
                // Got the string. Now retrieve a devinst dword based on the
                // string.
                //

                // wprintf(L"\tDevice name string for device %d is:\n"
                //         L"\t\t%ws\n",
                //         dwCurrDevice, wszDeviceName);

                HRESULT hr;
                DWORD   dwInstance;
                
                hr = GetInstanceFromDeviceName(
                    wszDeviceName,
                    & dwInstance,
                    hDevInfo
                    );

                delete wszDeviceName;

                if ( FAILED(hr) )
                {
                    LOG((PHONESP_TRACE, "ConstructWaveHardwareIdCache - Can't get instance DWORD for device %d; "
                        "error 0x%08x\n",
                        dwCurrDevice, hr));
                }
                else
                {
                    //
                    // Based on the devinst dword, retrieve a trimmed
                    // hardware id string.
                    //

                    // printf("\tInstance DWORD for device %d is "
                    //        "0x%08x\n",
                    //        dwCurrDevice, dwInstance);

                    WCHAR * wszHardwareId;

                    hr = HardwareIdFromDevInst(
                        dwInstance,
                        & wszHardwareId
                        );

                    if ( SUCCEEDED(hr) )
                    {
                        (*ppwszHardwareIds) [ dwCurrDevice ] = wszHardwareId;
                        (*ppdwDevInsts)     [ dwCurrDevice ] = dwInstance;
                    }
                }
            }
        }
    }

    SetupDiDestroyDeviceInfoList( hDevInfo );
    
    return S_OK;
}

//////////////////////////////////////////////////////////////////////////////
//
// FindWaveIdFromHardwareIdString
//
// This function finds the wave id for a device whose devinst and hardware id
// string are known.
//
// Constructing the mapping from waveid to devinst and hardwave id string
// takes some time, so the mapping is only constructed once for each
// direction (render/capture), via the helper function
// ConstructWaveHardwareIdCache().
//
// Thereafter, the helper function MatchHardwareIdInArray() is used to run
// the matching algorithm based on the already-computed arrays. See that
// function for a description of how the matching is done.
//
// Arguments:
//     dwHidDevInst     - IN  - the devinst dword to match to a wave id
//     wszHardwareId    - IN  - the hardware id string for the devinst
//     fRender          - IN  - TRUE for wave out, FALSE for wave in
//     pdwMatchedWaveId - OUT - the wave id associated with the devinst
//
// Return values:
//      S_OK - success
//      various errors from ConstructWaveHardwareIdCache() and
//         MatchHardwareIdInArray() helper functions
//

STATIC HRESULT
FindWaveIdFromHardwareIdString(
    IN   DWORD   dwHidDevInst,
    IN   WCHAR * wszHardwareId,
    IN   BOOL    fRender,
    OUT  DWORD * pdwMatchedWaveId
    )
{
    ASSERT( ! IsBadStringPtrW( wszHardwareId, (DWORD) -1 ) );

    ASSERT( ( fRender == TRUE ) || ( fRender == FALSE ) );

    ASSERT( ! IsBadWritePtr(pdwMatchedWaveId, sizeof(DWORD) ) );

    DWORD    dwNumDevices = 0;
    WCHAR ** pwszHardwareIds = NULL;
    DWORD  * pdwDevInsts = NULL;

    HRESULT hr;

    //
    // Need to construct cache of render device hardware IDs.
    //

    hr = ConstructWaveHardwareIdCache(
        fRender,
        & dwNumDevices,
        & pwszHardwareIds,
        & pdwDevInsts
        );
    
    if ( FAILED(hr) )
    {
        return hr;
    }

    //
    // The cache is ready; use it to perform the rest of the matching
    // algorithm.
    //

    hr = MatchHardwareIdInArray(
        dwHidDevInst,
        wszHardwareId,
        dwNumDevices,
        pwszHardwareIds,
        pdwDevInsts,
        pdwMatchedWaveId
        );

    delete pwszHardwareIds;
    delete pdwDevInsts;

    return hr;
}


//////////////////////////////////////////////////////////////////////////////
//
// OutputDeviceInfo
//
// This function is for diagnostic purposes only.
//
// Given a devinst DWORD, this function prints the DeviceDesc string as well
// as the entire (untrimmed) hardware ID string set for the device. Example:
//
//
//
// Arguments:
//     dwDesiredDevInst - IN  - the devinst dword for which we want info
//
// Return values:
//     none
//

STATIC void
OutputDeviceInfo(
    DWORD dwDesiredDevInst
    )
{
    //
    // Get and print the device description string.
    //

    HRESULT   hr;
    WCHAR   * wszDeviceDesc;
    WCHAR   * wszHardwareId;

    hr = DevInstGetIdString(
        dwDesiredDevInst,
        CM_DRP_DEVICEDESC,
        & wszDeviceDesc
        );

    if ( FAILED(hr) )
    {
        LOG((PHONESP_TRACE, "OutputDeviceInfo - [can't get device description string - 0x%08x]", hr));
    }
    else
    {
        LOG((PHONESP_TRACE, "OutputDeviceInfo - [DeviceDesc: %ws]", wszDeviceDesc));

        delete wszDeviceDesc;
    }

    //
    // Get and print hardware ID string set.
    //

    hr = DevInstGetIdString(
        dwDesiredDevInst,
        CM_DRP_HARDWAREID,
        & wszHardwareId
        );

    if ( FAILED(hr) )
    {
        LOG((PHONESP_TRACE, "OutputDeviceInfo - [can't get hardware id - 0x%08x]", hr));
    }
    else
    {
        //
        // Print out all the values in the mutli-string.
        //

        WCHAR * wszCurr = wszHardwareId;

        while ( wszCurr[0] != L'\0' )
        {
            LOG((PHONESP_TRACE, "OutputDeviceInfo - [HardwareId: %ws]", wszCurr));

            wszCurr += lstrlenW(wszCurr) + 1;
        }

        delete wszHardwareId;
    }

}


//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//
//
// Externally-callable functions
//
//
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////


//////////////////////////////////////////////////////////////////////////////
//
// ExamineWaveDevices
//
// This function is for debugging purposes only. It enumerates audio devices
// using the Wave API and prints the device path string as well as the
// device instance DWORD for each render or capture device.
//
// Arguments:
//      fRender - IN - true means examine wave out devices; false = wave in
//
// Return values:
//      E_OUTOFMEMORY
//      S_OK
//

HRESULT
ExamineWaveDevices(
    IN    BOOL fRender
    )
{
    ASSERT( ( fRender == TRUE ) || ( fRender == FALSE ) );

    DWORD     dwNumDevices;
    DWORD     dwCurrDevice;

    //
    // Get a device info list
    //

    HDEVINFO hDevInfo;
   
    /*
    hDevInfo = SetupDiGetClassDevs(
        &GUID_DEVCLASS_MEDIA,            // class GUID (which device classes?)
        NULL,                            // optional enumerator to filter
        NULL,                            // HWND (we have none)
        ( DIGCF_PRESENT    |             // only devices that are present
          DIGCF_PROFILE )                // only devices in this hw profile
        );
        */

    hDevInfo = SetupDiCreateDeviceInfoList(&GUID_DEVCLASS_MEDIA, NULL);

    if ( hDevInfo == NULL )
    {
        LOG((PHONESP_TRACE, "ExamineWaveDevices - SetupDiCreateDeviceInfoList failed: %08x", GetLastError()));
        return HRESULT_FROM_WIN32(GetLastError());
    }

    //
    // Loop over the available wave devices.
    //

    if ( fRender )
    {
        dwNumDevices = waveOutGetNumDevs();
    }
    else
    {
        dwNumDevices = waveInGetNumDevs();
    }

    LOG((PHONESP_TRACE, "ExamineWaveDevices - Found %d audio %s devices.",
        dwNumDevices,
        fRender ? "render" : "capture"));

    for ( dwCurrDevice = 0; dwCurrDevice < dwNumDevices; dwCurrDevice++ )
    {
        MMRESULT  mmresult;
        ULONG     ulSize;

        //
        // Get the size of the device path string.
        //

        if ( fRender )
        {
            mmresult = waveOutMessage( (HWAVEOUT) IntToPtr(dwCurrDevice),
                                       DRV_QUERYDEVICEINTERFACESIZE,
                                       (DWORD_PTR) & ulSize,
                                       0
                                     );
        }
        else
        {
            mmresult = waveInMessage( (HWAVEIN) IntToPtr(dwCurrDevice),
                                      DRV_QUERYDEVICEINTERFACESIZE,
                                      (DWORD_PTR) & ulSize,
                                      0
                                    );
        }

        if ( mmresult != MMSYSERR_NOERROR )
        {
            LOG((PHONESP_TRACE, "ExamineWaveDevices - Could not get device string size for device %d; "
                "error = %d\n", dwCurrDevice, mmresult));
        }
        else if ( ulSize == 0 )
        {
            LOG((PHONESP_TRACE, "ExamineWaveDevices - Got zero device string size for device %d\n",
                dwCurrDevice));
        }
        else
        {
            //
            // Allocate space for the device path string.
            //

            WCHAR * wszDeviceName;

            wszDeviceName = new WCHAR[ (ulSize / 2) + 1 ];

            if ( wszDeviceName == NULL )
            {
                LOG((PHONESP_TRACE, "ExamineWaveDevices - Out of memory in device string alloc for device %d;"
                    " requested size is %d\n", dwCurrDevice, ulSize));

                return E_OUTOFMEMORY;
            }

            //
            // Get the device path string from winmm.
            //

            if ( fRender )
            {
                mmresult = waveOutMessage( (HWAVEOUT) IntToPtr(dwCurrDevice),
                                           DRV_QUERYDEVICEINTERFACE,
                                           (DWORD_PTR) wszDeviceName,
                                           ulSize
                                         );
            }
            else
            {
                mmresult = waveInMessage( (HWAVEIN) IntToPtr(dwCurrDevice),
                                          DRV_QUERYDEVICEINTERFACE,
                                          (DWORD_PTR) wszDeviceName,
                                          ulSize
                                        );
            }

            if ( mmresult == MMSYSERR_NOERROR )
            {
                //
                // Got the string; print it and convert it to a
                // devinst DWORD.
                //

                LOG((PHONESP_TRACE, "ExamineWaveDevices - Device name string for device %d is: %ws",
                    dwCurrDevice, wszDeviceName));

                HRESULT hr;
                DWORD   dwInstance;
                
                hr = GetInstanceFromDeviceName(
                    wszDeviceName,
                    & dwInstance,
                    hDevInfo
                    );

                if ( FAILED(hr) )
                {
                    LOG((PHONESP_TRACE, "ExamineWaveDevices - Can't get instance DWORD for device %d; "
                        "error 0x%08x",
                        dwCurrDevice, hr));
                }
                else
                {
                    LOG((PHONESP_TRACE, "ExamineWaveDevices - Instance DWORD for device %d is "
                        "0x%08x",
                        dwCurrDevice, dwInstance));

                    //
                    // Print various other info about this device.
                    //

                    OutputDeviceInfo( dwInstance );

                    WCHAR * wszHardwareId;

                    hr = HardwareIdFromDevInst(
                        dwInstance,
                        & wszHardwareId
                        );

                    if ( FAILED(hr) )
                    {
                        LOG((PHONESP_TRACE, "ExamineWaveDevices - Can't get hardware id string for device %d; "
                            "error 0x%08x",
                            dwCurrDevice, hr));
                    }
                    else
                    {
                        LOG((PHONESP_TRACE, "ExamineWaveDevices - Hardware ID for device %d is %ws\n",
                            dwCurrDevice, wszHardwareId));

                        delete wszHardwareId;
                    }
                }

                delete wszDeviceName;
            }
        }
    }

    SetupDiDestroyDeviceInfoList( hDevInfo );
    
    return S_OK;
}



//////////////////////////////////////////////////////////////////////////////
//
// DiscoverAssociatedWaveId
//
// This function searches for a wave device to match the HID device in the
// PNP tree location specified in the passed in SP_DEVICE_INTERFACE_DATA
// structure, obtained from the SetupKi API. It returns the wave id for
// the matched device.
//
// It uses the helper function FindWaveIdFromHardwareIdString() to search for
// the wave device based on a devinst DWORD and a hardware ID string. First,
// it must obtain the devinst for the device; it does this by calling a SetupDi
// function and looking up the devinst in a resulting structure. The hardware
// ID string is then retrieved from the registry and trimmed, using the helper
// function HardwareIdFromDevinst().
//
// See FindWaveIdFromHardwareIdString() for further comments on the search
// algorithm.
//
// Arguments:
//     dwDevInst     - IN  - Device Instance of the HID device
//     fRender       - IN  - TRUE for wave out, FALSE for wave in
//     pdwWaveId     - OUT - the wave id associated with this HID device
//
// Return values:
//      S_OK    - succeeded and matched wave id
//      other from helper functions FindWaveIdFromHardwareIdString() or
//            or HardwareIdFromDevinst()
//

HRESULT
DiscoverAssociatedWaveId(
    IN    DWORD                      dwDevInst,
    IN    BOOL                       fRender,
    OUT   DWORD                    * pdwWaveId
    )
{
    ASSERT( ! IsBadWritePtr(pdwWaveId, sizeof(DWORD) ) );

    ASSERT( ( fRender == TRUE ) || ( fRender == FALSE ) );

    //
    // We've got the device instance DWORD for the HID device.
    // Use it to get the trimmed hardware ID string, which tells
    // us the vendor, product, and revision numbers.
    //

    HRESULT   hr;
    WCHAR   * wszHardwareId;

    hr = HardwareIdFromDevInst(
        dwDevInst,
        & wszHardwareId
        );

    if ( FAILED(hr) )
    {
        return hr;
    }

    //
    // Finally, use this information to choose a wave id.
    //

    hr = FindWaveIdFromHardwareIdString(
        dwDevInst,
        wszHardwareId,
        fRender,
        pdwWaveId
        );

    delete wszHardwareId;

    if ( FAILED(hr) )
    {
        return hr;
    }

    return S_OK;
}

//
// eof
//