//+---------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright (C) Microsoft Corporation, 2000.
//
//  File:       D E V I C E P E R S I S T E N C E M A N A G E R . C P P 
//
//  Contents:   Persistence for UPnP device host registrar settings to registry
//
//  Notes:      
//
//  Author:     mbend   6 Sep 2000
//
//----------------------------------------------------------------------------

#include "pch.h"
#pragma hdrstop

#include "uhbase.h"
#include "hostp.h"
#include "DevicePersistenceManager.h"
#include "uhsync.h"
#include "ncreg.h"
#include "Array.h"
#include "ComUtility.h"
#include "uhutil.h"
#include "uhcommon.h"

// String constants
const wchar_t c_szDevices[] = L"Devices";
const wchar_t c_szProviders[] = L"Providers";
const wchar_t c_szProgId[] = L"ProgId";
const wchar_t c_szInitString[] = L"Init String";
const wchar_t c_szContainerId[] = L"Container Id";
const wchar_t c_szResourcePath[] = L"Resource Path";
const wchar_t c_szLifeTime[] = L"Life Time";
const wchar_t c_szProgIdProviderClass[] = L"Provider ProgId";

// Helper functions

HRESULT HrCreateOrOpenDevicesKey(HKEY * phKeyDevices)
{
    CHECK_POINTER(phKeyDevices);
    HRESULT hr = S_OK;

    HKEY hKeyDeviceHost;
    hr = HrCreateOrOpenDeviceHostKey(&hKeyDeviceHost);
    if(SUCCEEDED(hr))
    {
        HKEY hKeyDevices;
        DWORD dwDisposition = 0;
        hr = HrRegCreateKeyEx(hKeyDeviceHost, c_szDevices, 0, KEY_ALL_ACCESS, NULL, &hKeyDevices, &dwDisposition);
        if(SUCCEEDED(hr))
        {
            *phKeyDevices = hKeyDevices;
        }
        RegCloseKey(hKeyDeviceHost);
    }

    TraceHr(ttidError, FAL, hr, FALSE, "HrCreateOrOpenDevicesKey");
    return hr;
}

HRESULT HrCreateOrOpenProvidersKey(HKEY * phKeyProviders)
{
    CHECK_POINTER(phKeyProviders);
    HRESULT hr = S_OK;

    HKEY hKeyDeviceHost;
    hr = HrCreateOrOpenDeviceHostKey(&hKeyDeviceHost);
    if(SUCCEEDED(hr))
    {
        HKEY hKeyProviders;
        DWORD dwDisposition = 0;
        hr = HrRegCreateKeyEx(hKeyDeviceHost, c_szProviders, 0, KEY_ALL_ACCESS, NULL, &hKeyProviders, &dwDisposition);
        if(SUCCEEDED(hr))
        {
            *phKeyProviders = hKeyProviders;
        }
        RegCloseKey(hKeyDeviceHost);
    }

    TraceHr(ttidError, FAL, hr, FALSE, "HrCreateOrOpenProvidersKey");
    return hr;
}

CDevicePersistenceManager::CDevicePersistenceManager()
{
}

CDevicePersistenceManager::~CDevicePersistenceManager()
{
}

STDMETHODIMP CDevicePersistenceManager::SavePhyisicalDevice(
    /*[in]*/ REFGUID guidPhysicalDeviceIdentifier,
    /*[in, string]*/ const wchar_t * szProgIdDeviceControlClass,
    /*[in, string]*/ const wchar_t * szInitString,
    /*[in, string]*/ const wchar_t * szContainerId,
    /*[in, string]*/ const wchar_t * szResourcePath,
    /*[in]*/ long nLifeTime)
{
    HRESULT hr = S_OK;

    // Create the string to use
    CUString strUuid;

    hr = HrIsAllowedCOMCallLocality((CALL_LOCALITY) CALL_LOCALITY_INPROC);
    
    if (SUCCEEDED(hr))
    {
        hr = strUuid.HrInitFromGUID(guidPhysicalDeviceIdentifier);
    }

    if(SUCCEEDED(hr))
    {
        HKEY hKeyDevices;
        hr = HrCreateOrOpenDevicesKey(&hKeyDevices);
        if(SUCCEEDED(hr))
        {
            // Create key to house values
            HKEY hKeyPid;
            DWORD dwDisposition = 0;
            hr = HrRegCreateKeyEx(hKeyDevices, strUuid, 0, KEY_ALL_ACCESS, NULL, &hKeyPid, &dwDisposition);
            if(SUCCEEDED(hr))
            {
                // Save all of the values
                hr = HrRegSetSz(hKeyPid, c_szProgId, szProgIdDeviceControlClass);
                if(SUCCEEDED(hr))
                {
                    hr = HrRegSetSz(hKeyPid, c_szInitString, szInitString);
                    if(SUCCEEDED(hr))
                    {
                        hr = HrRegSetSz(hKeyPid, c_szContainerId, szContainerId);
                        if(SUCCEEDED(hr))
                        {
                            hr = HrRegSetSz(hKeyPid, c_szResourcePath, szResourcePath);
                            if(SUCCEEDED(hr))
                            {
                                hr = HrRegSetDword(hKeyPid, c_szLifeTime, nLifeTime);
                            }
                        }
                    }
                }
                RegCloseKey(hKeyPid);
                if(FAILED(hr))
                {
                    // If anything fails, remove the whole tree
                    HrRegDeleteKeyTree(hKeyDevices, strUuid);
                }
            }
            RegCloseKey(hKeyDevices);
        }
    }

    TraceHr(ttidError, FAL, hr, FALSE, "CDevicePersistenceManager::SavePhyisicalDevice");
    return hr;
}

STDMETHODIMP CDevicePersistenceManager::LookupPhysicalDevice(
    /*[in]*/ REFGUID guidPhysicalDeviceIdentifier,
    /*[out, string]*/ wchar_t ** pszProgIdDeviceControlClass,
    /*[out, string]*/ wchar_t ** pszInitString,
    /*[out, string]*/ wchar_t ** pszContainerId,
    /*[out, string]*/ wchar_t ** pszResourcePath,
    /*[out]*/ long * pnLifeTime)
{
    CHECK_POINTER(pszProgIdDeviceControlClass);
    CHECK_POINTER(pszInitString);
    CHECK_POINTER(pszContainerId);
    CHECK_POINTER(pszResourcePath);
    CHECK_POINTER(pnLifeTime);
    HRESULT hr = S_OK;

    // Create the string to use
    CUString strUuid;
    CUString strProgIdDeviceControlClass;
    CUString strInitString;
    CUString strContainerId;
    CUString strResourcePath;
    DWORD dwLifeTime;

    hr = HrIsAllowedCOMCallLocality((CALL_LOCALITY) CALL_LOCALITY_INPROC);
    
    if (SUCCEEDED(hr))
    {
        hr = strUuid.HrInitFromGUID(guidPhysicalDeviceIdentifier);
    }

    if(SUCCEEDED(hr))
    {
        HKEY hKeyDevices;
        hr = HrCreateOrOpenDevicesKey(&hKeyDevices);
        if(SUCCEEDED(hr))
        {
            // Open the key housing the values
            HKEY hKeyPid;
            DWORD dwDisposition = 0;
            hr = HrRegOpenKeyEx(hKeyDevices, strUuid, KEY_ALL_ACCESS, &hKeyPid);
            if(SUCCEEDED(hr))
            {
                // Load all of the values
                hr = HrRegQueryString(hKeyPid, c_szProgId, strProgIdDeviceControlClass);
                if(SUCCEEDED(hr))
                {
                    hr = HrRegQueryString(hKeyPid, c_szInitString, strInitString);
                    if(SUCCEEDED(hr))
                    {
                        hr = HrRegQueryString(hKeyPid, c_szContainerId, strContainerId);
                        if(SUCCEEDED(hr))
                        {
                            hr = HrRegQueryString(hKeyPid, c_szResourcePath, strResourcePath);
                            if(SUCCEEDED(hr))
                            {
                                hr = HrRegQueryDword(hKeyPid, c_szLifeTime, &dwLifeTime);
                            }
                        }
                    }
                }
                RegCloseKey(hKeyPid);
            }
            RegCloseKey(hKeyDevices);
        }
    }
    // Set strings to NULL
    *pszProgIdDeviceControlClass = NULL;
    *pszInitString = NULL;
    *pszContainerId = NULL;
    *pszResourcePath = NULL;
    // On success set the out params
    if(SUCCEEDED(hr))
    {
        hr = strProgIdDeviceControlClass.HrGetCOM(pszProgIdDeviceControlClass);
        if(SUCCEEDED(hr))
        {
            hr = strInitString.HrGetCOM(pszInitString);
            if(SUCCEEDED(hr))
            {
                hr = strContainerId.HrGetCOM(pszContainerId);
                if(SUCCEEDED(hr))
                {
                    hr = strResourcePath.HrGetCOM(pszResourcePath);
                    if(SUCCEEDED(hr))
                    {
                        *pnLifeTime = dwLifeTime;
                    }
                }
            }
        }
        if(FAILED(hr))
        {
            // If one fails, they all fail
            if(pszInitString)
            {
                CoTaskMemFree(pszInitString);
                *pszInitString = NULL;
            }
            if(pszContainerId)
            {
                CoTaskMemFree(pszContainerId);
                *pszContainerId = NULL;
            }
            if(pszResourcePath)
            {
                CoTaskMemFree(pszResourcePath);
                *pszResourcePath = NULL;
            }
        }
    }

    TraceHr(ttidError, FAL, hr, FALSE, "CDevicePersistenceManager::LookupPhysicalDevice");
    return hr;
}

STDMETHODIMP CDevicePersistenceManager::RemovePhysicalDevice(
    /*[in]*/ REFGUID guidPhysicalDeviceIdentifier)
{
    HRESULT hr = S_OK;

    // Create the string to use
    CUString strUuid;

    hr = HrIsAllowedCOMCallLocality((CALL_LOCALITY) CALL_LOCALITY_INPROC);
    
    if (SUCCEEDED(hr))
    {
        hr = strUuid.HrInitFromGUID(guidPhysicalDeviceIdentifier);
    }

    if(SUCCEEDED(hr))
    {
        HKEY hKeyDevices;
        hr = HrCreateOrOpenDevicesKey(&hKeyDevices);
        if(SUCCEEDED(hr))
        {
            hr = HrRegDeleteKeyTree(hKeyDevices, strUuid);
            RegCloseKey(hKeyDevices);
        }
    }

    TraceHr(ttidError, FAL, hr, FALSE, "CDevicePersistenceManager::RemovePhysicalDevice");
    return hr;
}

STDMETHODIMP CDevicePersistenceManager::GetPhysicalDevices(
    /*[out]*/ long * pnDevices,
    /*[out, size_is(,*pnDevices)]*/
        GUID ** parguidPhysicalDeviceIdentifiers)
{
    CHECK_POINTER(pnDevices);
    CHECK_POINTER(parguidPhysicalDeviceIdentifiers);
    
    // Set this to NULL at the beginning
    *parguidPhysicalDeviceIdentifiers = NULL;

    HRESULT hr = S_OK;

    // Do work in an array
    CUArray<GUID> arPids;
    HKEY hKeyDevices;

    hr = HrIsAllowedCOMCallLocality((CALL_LOCALITY) CALL_LOCALITY_INPROC);
    
    if (SUCCEEDED(hr))
    {
        // Open devices key
        hr = HrCreateOrOpenDevicesKey(&hKeyDevices);
    }

    if(SUCCEEDED(hr))
    {
        DWORD dwSize;
        wchar_t szBuf[_MAX_PATH];
        FILETIME ft;
        DWORD dwIndex = 0;
        UUID uuid;
        while(SUCCEEDED(hr))
        {
            // Enumerate all of the keys
            dwSize = _MAX_PATH;
            hr = HrRegEnumKeyEx(hKeyDevices, dwIndex, szBuf, &dwSize, NULL, NULL, &ft);
            ++dwIndex;
            if(S_OK == hr)
            {
                hr = CLSIDFromString(szBuf, &uuid);
                if(SUCCEEDED(hr))
                {
                    hr = arPids.HrPushBack(uuid);
                }
                else
                {
                    TraceHr(ttidRegistrar, FAL, hr, FALSE, "CDevicePersistenceManager::GetPhysicalDevices - CLSIDFromString failed!");
                    hr = S_OK;
                    // Ignore and skip it - this shouldn't happen
                    continue;
                }
            }
            else
            {
                // This is not an error, we are out of subkeys
                hr = S_OK;
                break;
            }
        }
        RegCloseKey(hKeyDevices);
    }

    // Attempt to copy to output parameters
    if(SUCCEEDED(hr))
    {
        long nCount = arPids.GetCount();
        if(nCount)
        {
            // Allocate output array
            HrCoTaskMemAllocArray(nCount, parguidPhysicalDeviceIdentifiers);
            // Fill in array
            for(long n = 0; n < nCount; ++n)
            {
                (*parguidPhysicalDeviceIdentifiers)[n] = arPids[n];
            }
        }
        else
        {
            *parguidPhysicalDeviceIdentifiers = NULL;
        }
        *pnDevices = nCount;
    }
    if(FAILED(hr))
    {
        *pnDevices = 0;
        if(*parguidPhysicalDeviceIdentifiers)
        {
            CoTaskMemFree(*parguidPhysicalDeviceIdentifiers);
        }
        *parguidPhysicalDeviceIdentifiers = NULL;
    }

    TraceHr(ttidError, FAL, hr, FALSE, "CDevicePersistenceManager::GetPhysicalDevices");
    return hr;
}

STDMETHODIMP CDevicePersistenceManager::SaveDeviceProvider(
    /*[in, string]*/ const wchar_t * szProviderName,
    /*[in, string]*/ const wchar_t * szProgIdProviderClass,
    /*[in, string]*/ const wchar_t * szInitString,
    /*[in, string]*/ const wchar_t * szContainerId)
{
    CHECK_POINTER(szProviderName);
    CHECK_POINTER(szProgIdProviderClass);
    CHECK_POINTER(szInitString);
    CHECK_POINTER(szContainerId);

    HRESULT hr = S_OK;

    HKEY hKeyProviders;

    hr = HrIsAllowedCOMCallLocality((CALL_LOCALITY) CALL_LOCALITY_INPROC);
    
    if (SUCCEEDED(hr))
    {
        hr = HrCreateOrOpenProvidersKey(&hKeyProviders);
    }

    if(SUCCEEDED(hr))
    {
        // Create key to house values
        HKEY hKeyProviderName;
        DWORD dwDisposition = 0;
        hr = HrRegCreateKeyEx(hKeyProviders, szProviderName, 0, KEY_ALL_ACCESS, NULL, &hKeyProviderName, &dwDisposition);
        if(SUCCEEDED(hr))
        {
            // Save all of the values
            hr = HrRegSetSz(hKeyProviderName, c_szProgIdProviderClass, szProgIdProviderClass);
            if(SUCCEEDED(hr))
            {
                hr = HrRegSetSz(hKeyProviderName, c_szInitString, szInitString);
                if(SUCCEEDED(hr))
                {
                    hr = HrRegSetSz(hKeyProviderName, c_szContainerId, szContainerId);
                }
            }
            RegCloseKey(hKeyProviderName);
            if(FAILED(hr))
            {
                // If anything fails, remove the whole tree
                HrRegDeleteKeyTree(hKeyProviders, szProviderName);
            }
        }
        RegCloseKey(hKeyProviders);
    }

    TraceHr(ttidError, FAL, hr, FALSE, "CDevicePersistenceManager::SaveDeviceProvider");
    return hr;
}

STDMETHODIMP CDevicePersistenceManager::LookupDeviceProvider(
    /*[in, string]*/ const wchar_t * szProviderName,
    /*[out, string]*/ wchar_t ** pszProgIdProviderClass,
    /*[out, string]*/ wchar_t ** pszInitString,
    /*[out, string]*/ wchar_t ** pszContainerId)
{
    CHECK_POINTER(szProviderName);
    CHECK_POINTER(pszProgIdProviderClass);
    CHECK_POINTER(pszInitString);
    CHECK_POINTER(pszContainerId);

    HRESULT hr = S_OK;

    CUString strProgIdProviderClass;
    CUString strInitString;
    CUString strContainerId;
        
    HKEY hKeyProviders;

    hr = HrIsAllowedCOMCallLocality((CALL_LOCALITY) CALL_LOCALITY_INPROC);
    
    if (SUCCEEDED(hr))
    {
        hr = HrCreateOrOpenProvidersKey(&hKeyProviders);
    }

    if(SUCCEEDED(hr))
    {
        // Open the key housing the values
        HKEY hKeyProviderName;
        DWORD dwDisposition = 0;
        hr = HrRegOpenKeyEx(hKeyProviders, szProviderName, KEY_ALL_ACCESS, &hKeyProviderName);
        if(SUCCEEDED(hr))
        {
            // Load all of the values
            hr = HrRegQueryString(hKeyProviderName, c_szProgIdProviderClass, strProgIdProviderClass);
            if(SUCCEEDED(hr))
            {
                hr = HrRegQueryString(hKeyProviderName, c_szInitString, strInitString);
                if(SUCCEEDED(hr))
                {
                    hr = HrRegQueryString(hKeyProviderName, c_szContainerId, strContainerId);
                }
            }
            RegCloseKey(hKeyProviderName);
        }
        RegCloseKey(hKeyProviders);
    }
    // Set strings to NULL
    *pszProgIdProviderClass = NULL;
    *pszInitString = NULL;
    *pszContainerId = NULL;
    // On success set the out params
    if(SUCCEEDED(hr))
    {
        hr = strProgIdProviderClass.HrGetCOM(pszProgIdProviderClass);
        if(SUCCEEDED(hr))
        {
            hr = strInitString.HrGetCOM(pszInitString);
            if(SUCCEEDED(hr))
            {
                hr = strContainerId.HrGetCOM(pszContainerId);
            }
        }
        if(FAILED(hr))
        {
            // If one fails, they all fail
            if(pszInitString)
            {
                CoTaskMemFree(pszInitString);
                *pszInitString = NULL;
            }
            if(pszContainerId)
            {
                CoTaskMemFree(pszContainerId);
                *pszContainerId = NULL;
            }
        }
    }

    TraceHr(ttidError, FAL, hr, FALSE, "CDevicePersistenceManager::LookupDeviceProvider");
    return hr;
}

STDMETHODIMP CDevicePersistenceManager::RemoveDeviceProvider(
    /*[in, string]*/ const wchar_t * szProviderName)
{
    HRESULT hr = S_OK;

    HKEY hKeyProviders;

    hr = HrIsAllowedCOMCallLocality((CALL_LOCALITY) CALL_LOCALITY_INPROC);
    
    if (SUCCEEDED(hr))
    {
        hr = HrCreateOrOpenProvidersKey(&hKeyProviders);
    }

    if(SUCCEEDED(hr))
    {
        hr = HrRegDeleteKeyTree(hKeyProviders, szProviderName);
        RegCloseKey(hKeyProviders);
    }

    TraceHr(ttidError, FAL, hr, FALSE, "CDevicePersistenceManager::RemoveDeviceProvider");
    return hr;
}

STDMETHODIMP CDevicePersistenceManager::GetDeviceProviders(
    /*[out]*/ long * pnProviders,
    /*[out, string, size_is(,*pnProviders,)]*/
        wchar_t *** parszProviderNames)
{
    CHECK_POINTER(pnProviders);
    CHECK_POINTER(parszProviderNames);
    
    // Set this to NULL at the beginning
    *parszProviderNames = NULL;

    HRESULT hr = S_OK;

    // Do work in an array
    CUArray<wchar_t*> arszProviders;
    HKEY hKeyProviders;

    hr = HrIsAllowedCOMCallLocality((CALL_LOCALITY) CALL_LOCALITY_INPROC);
    
    if (SUCCEEDED(hr))
    {
        // Open devices key
      
        hr = HrCreateOrOpenProvidersKey(&hKeyProviders);
    }

    if(SUCCEEDED(hr))
    {
        DWORD dwSize;
        wchar_t szBuf[_MAX_PATH];
        FILETIME ft;
        DWORD dwIndex = 0;
        while(SUCCEEDED(hr))
        {
            // Enumerate all of the keys
            dwSize = _MAX_PATH;
            hr = HrRegEnumKeyEx(hKeyProviders, dwIndex, szBuf, &dwSize, NULL, NULL, &ft);
            if(S_OK == hr)
            {
                wchar_t * sz = NULL;
                hr = HrCoTaskMemAllocString(szBuf, &sz);
                if(SUCCEEDED(hr))
                {
                    // Insert pointer to dynamically allocated string
                    hr = arszProviders.HrPushBack(sz);
                }
            }
            else
            {
                // This is not an error, we have no more subkeys
                hr = S_OK;
                break;
            }
            ++dwIndex;
        }
        RegCloseKey(hKeyProviders);
    }

    // Attempt to copy to output parameters
    if(SUCCEEDED(hr))
    {
        long nCount = arszProviders.GetCount();
        if(nCount)
        {
            // Allocate output array
            HrCoTaskMemAllocArray(nCount, parszProviderNames);
            // Fill in array
            for(long n = 0; n < nCount; ++n)
            {
                (*parszProviderNames)[n] = arszProviders[n];
            }
        }
        else
        {
            *parszProviderNames = NULL;
        }
        *pnProviders = nCount;
    }
    if(FAILED(hr))
    {
        *pnProviders = 0;
        if(*parszProviderNames)
        {
            CoTaskMemFree(*parszProviderNames);
        }
        *parszProviderNames = NULL;
        // Cleanup dynamically allocated strings
        long nCount = arszProviders.GetCount();
        for(long n = 0; n < nCount; ++n)
        {
            CoTaskMemFree(arszProviders[n]);
        }
    }

    TraceHr(ttidError, FAL, hr, FALSE, "CDevicePersistenceManager::GetDeviceProviders");
    return hr;
}