//
// Copyright (c) Microsoft Corporation.  All rights reserved.
//
//
// Use of this source code is subject to the terms of the Microsoft shared
// source or premium shared source license agreement under which you licensed
// this source code. If you did not accept the terms of the license agreement,
// you are not authorized to use this source code. For the terms of the license,
// please see the license agreement between you and Microsoft or, if applicable,
// see the SOURCE.RTF on your install media or the root of your tools installation.
// THE SOURCE CODE IS PROVIDED "AS IS", WITH NO WARRANTIES.
//
/*++


File Name:

    upnphost.cpp

Abstract:

This file implements the UPnP Registrar coclass

Author: GeorgeJ

Created: Nov 2000

--*/

#include "upnphostp.h"
#include "upnpmem.h"
#include "auto_xxx.hxx"
// combook.cpp should be included in this file only
#include "combook.cpp"

#define ttidRegistrar 1

HINSTANCE g_hMod;   // Dll module handle

// map CLSID to GetClassObject/UpdateRegistry routine
BEGIN_COCLASS_TABLE(ClassTable)
    IMPLEMENTS_COCLASS(UPnPRegistrar)
END_COCLASS_TABLE()


// implement ModuleAddRef/ModuleRelease/ModuleIsStopping/ModuleIsIdle
IMPLEMENT_DLL_MODULE_ROUTINES()

// implement DllGetClassObject/DllCanUnloadNow/Dll[Un]RegisterServer
IMPLEMENT_DLL_ENTRY_POINTS(g_hMod, ClassTable, 0, FALSE)


#ifdef DEBUG
DBGPARAM dpCurSettings = {
    TEXT("UPNPHOST"), {
    TEXT("Misc"), TEXT("Registrar"), TEXT("Automation proxy"), TEXT("Events"),
    TEXT(""),TEXT(""),TEXT(""),TEXT(""),
    TEXT(""),TEXT("Control"),TEXT(""),TEXT(""),
    TEXT(""),TEXT(""),TEXT("Trace"),TEXT("Error") },
    0x00008000
};
#endif

extern "C"
BOOL
WINAPI
DllMain(IN PVOID DllHandle,
        IN ULONG Reason,
        IN PVOID Context OPTIONAL)
{
    switch (Reason)
    {
    case DLL_PROCESS_ATTACH:

        if(!UpnpHeapCreate())
            return FALSE;

        DisableThreadLibraryCalls((HINSTANCE)DllHandle);
        
        DEBUGREGISTER((HMODULE)DllHandle);
        g_hMod = (HINSTANCE)DllHandle; 

        break; 


    case DLL_PROCESS_DETACH:

        UpnpHeapDestroy();
        break;

    }
    return TRUE;
}

//+---------------------------------------------------------------------------
//
//  Function:   AddDevice
//
//  Purpose:    Create and publish the UPnP device tree.
//              Register for callbacks from the service layer.
//              The device has a locally unique name specified
//              by member bstrDeviceId
//
//  Arguments:
//      pszXMLDesc [in] XML device description template
//
//  Returns:
//      HRESULT
//

HRESULT
DeviceProxy::AddDevice(LPWSTR pszXMLDesc)
{
    UPNPDEVICEINFO devInfo;
    BOOL fRet;
    devInfo.cbStruct = sizeof(devInfo);
    devInfo.pszDeviceDescription = pszXMLDesc;
    devInfo.pszDeviceName = m_bstrDeviceId;
    // if the UDN is not NULL, it is assumed to be a UUID
    devInfo.pszUDN = m_bstrUDN;
    devInfo.cachecontrol = m_nLifeTime;
    // all callbacks get a pointer to the DeviceProxy instance
    // as the 2nd Parameter
    devInfo.pvUserDevContext = this;
    devInfo.pfCallback = DevCallback;

    fRet = UpnpAddDevice(&devInfo);
    if (fRet)
    {
        // send out the SSDP announcements
        fRet = UpnpPublishDevice(m_bstrDeviceId);
    }
    if (!fRet)
    {
        return HrFromLastWin32Error();
    }
    return S_OK;
}

//+---------------------------------------------------------------------------
//
//  Function:   RemoveDevice
//
//  Purpose:    Unpublish and unregister the device tree.
//
//  Arguments:
//
//  Returns:
//      HRESULT
//
HRESULT
DeviceProxy::RemoveDevice()
{
    BOOL fRet;
    // no need to explicitly call Unpublish
    fRet = UpnpRemoveDevice(m_bstrDeviceId);
    if (!fRet)
        return HrFromLastWin32Error();
    return S_OK;
}

//+---------------------------------------------------------------------------
//
//  Function:  DevCallback (static method) 
//
//  Purpose:    UPNPCALLBACK prototype that is invoked by the
//              device hosting layer when interesting events
//              occur. In particular, the callback is invoked when
//              control requests are received from remote control points.
//
//  Arguments:
//      callbackId [in] specifies the type of callback event
//      pvUserContext [in] DeviceProxy pointer
//      pvSvcParam [in] depends on callbackId
//
//  Returns:
//      TRUE if success; FALSE if something bad happens
//
DWORD DeviceProxy::DevCallback( 
        UPNPCB_ID callbackId, 
        PVOID pvUserContext,    // app context (from UPNPDEVICEINFO)
        PVOID pvSvcParam)   // depends on CALLBACKID
{
    DeviceProxy *pDevProxy = (DeviceProxy *)pvUserContext;
    switch (callbackId)
    {
        case UPNPCB_INIT:
            return pDevProxy->InitCallback();
            break;
        case UPNPCB_SUBSCRIBING:
            return pDevProxy->SubscribeCallback((UPNPSUBSCRIPTION*)pvSvcParam);
            break;
        case UPNPCB_CONTROL:
            return pDevProxy->ControlCallback((UPNPSERVICECONTROL*)pvSvcParam);
            break;
        case UPNPCB_SHUTDOWN:
            return pDevProxy->ShutdownCallback();
            break;
    }
    return TRUE;
}

//+---------------------------------------------------------------------------
//
//  Method: FindDevProxyByName  
//
//  Purpose:    Locate the named device proxy in the registrar
//              list and return a pointer
//
//  Arguments:
//      pszDevId [in] local name for device tree
//
//  Returns:
//      NULL if the object is not found, else pointer to the DeviceProxy
//
DeviceProxy *
UPnPRegistrar::FindDevProxyByName(PCWSTR pszDevId)
{
    LIST_ENTRY *pLink;
    for (pLink = m_DevProxyList.Flink; pLink != &m_DevProxyList; pLink = pLink->Flink)
    {
        DeviceProxy *pDev;
        pDev = CONTAINING_RECORD(pLink, DeviceProxy, m_link);
        if (wcscmp(pszDevId, pDev->Name()) == 0)
            return pDev;
    }
    return NULL;
}

//+---------------------------------------------------------------------------
//
//  Function:   HrCreateDeviceIdentifier
//
//  Purpose:    Generate a Unique Device Identifier string.
//              uses SysAllocString for memory.
//              The identifier is a 64 bit mini-GUID.
//
//  Arguments:
//      pbstrDevId [out] BSTR UDN (without the uuid: prefix)
//
//  Returns:
//      HRESULT
//
HRESULT
UPnPRegistrar::HrCreateDeviceIdentifier(BSTR *pbstrDevId)
{
    WCHAR wsz[64];
    LONGLONG uuid64 = GenerateUUID64();
    *pbstrDevId = NULL;
    // we need a device Id that is unique and a valid filename 
    // so no illegal characters like ':'
    wsprintfW(wsz, L"%04x%04x-%04x-%04x-0000-000000000000", (WORD)uuid64, *((WORD*)&uuid64 + 1), *((WORD*)&uuid64 + 2), *((WORD*)&uuid64 + 3));

    if (*pbstrDevId = SysAllocString(wsz))
        return S_OK;
    else
        return E_OUTOFMEMORY;
}

//+---------------------------------------------------------------------------
//
//  Function:  IUPnPRegistrar::RegisterRunningDevice 
//
//  Purpose:    Publish an already instantiated UPnP device tree.
//              See public docs for more info
//
//  Arguments:
//      bstrXMLDesc [in] XML device description template
//      punkDeviceControl [in] IUPnPDeviceControl interface of the object
//      bstrInitString [in] optional string to be passed back to object on init
//      bstrResourcePath [in] NULL - not used for WinCE. The resources should
//                      be under the \windows\upnp directory.
//      nLifeTime [in] lifetime in seconds. Maybe 0 for system default
//      pbstrDeviceIdentifier [out] id generated by UPnP service for use in
//                  UnregisterDevice and ReregisterDevice
//  Returns:
//      HRESULT
//
STDMETHODIMP
UPnPRegistrar::RegisterRunningDevice(
        /*[in]*/ BSTR     bstrXMLDesc,
        /*[in]*/ IUnknown * punkDeviceControl,
        /*[in]*/ BSTR     bstrInitString,
        /*[in]*/ BSTR     bstrResourcePath,
        /*[in]*/ long     nLifeTime,
        /*[out, retval]*/ BSTR * pbstrDeviceIdentifier)
{
    HRESULT hr = S_OK;
    BSTR bstrDeviceId = NULL;

    *pbstrDeviceIdentifier = NULL;
    // construct pbstrDeviceIdentifier
    hr = HrCreateDeviceIdentifier( &bstrDeviceId);
    if (SUCCEEDED(hr))
    {
        // add device
        hr = ReregisterRunningDevice(
            bstrDeviceId,
            bstrXMLDesc,
            punkDeviceControl,
            bstrInitString,
            bstrResourcePath,
            nLifeTime
            );
        if (SUCCEEDED(hr))
        {
            *pbstrDeviceIdentifier = bstrDeviceId;
        }
        else
        {
            SysFreeString(bstrDeviceId);
        }
    }

    return hr;
}

static const int MaxInstances = 30;

//+---------------------------------------------------------------------------
//
//  Function:  IUPnPRegistrar::RegisterDevice 
//
//  Purpose:    Publish UPnP device tree with delayed instantiaton of the
//              device implementation
//              See public docs for more info
//
//  Arguments:
//      pszXMLDesc [in] XML device description template
//      bstrProgIDDeviceControlClass [in] CLSID of COM device implementation
//      bstrInitString [in] optional string to be passed back to object on init
//      bstrContainerId [in] NULL - not used on WinCE
//      bstrResourcePath [in] NULL - not used for WinCE. The resources should
//                      be under the \windows\upnp directory.
//      nLifeTime [in] lifetime in seconds. Maybe 0 for system default
//      pbstrDeviceIdentifier [out] id generated by UPnP service for use in
//                  UnregisterDevice and ReregisterDevice
//  Returns:
//      HRESULT
//
STDMETHODIMP UPnPRegistrar::RegisterDevice(
    /*[in]*/ BSTR     bstrXMLDesc,
    /*[in]*/ BSTR     bstrProgIDDeviceControlClass,
    /*[in]*/ BSTR     bstrInitString,
    /*[in]*/ BSTR     bstrContainerId,
    /*[in]*/ BSTR     bstrResourcePath,
    /*[in]*/ long     nLifeTime,
    /*[out, retval]*/ BSTR * pbstrDeviceIdentifier)
{
    HRESULT hr = E_FAIL;
    BSTR bstrDeviceId = NULL;

    *pbstrDeviceIdentifier = NULL;

    if (bstrXMLDesc == NULL
        || bstrProgIDDeviceControlClass == NULL
        || pbstrDeviceIdentifier == NULL)
    {
        return E_INVALIDARG;
    }

    // construct pbstrDeviceIdentifier
    hr = HrCreateDeviceIdentifier( &bstrDeviceId);
    if (SUCCEEDED(hr))
    {
        hr = ReregisterDevice(
                bstrDeviceId,
                bstrXMLDesc,
                bstrProgIDDeviceControlClass,
                bstrInitString,
                bstrContainerId,
                bstrResourcePath,
                nLifeTime);
        if (SUCCEEDED(hr))
        {
            *pbstrDeviceIdentifier = bstrDeviceId;
        }
        else
            SysFreeString(bstrDeviceId);

    }
    return hr;
}

static const WCHAR c_szuuidprefix[] = L"uuid:";

//+---------------------------------------------------------------------------
//
//  Function:   
//
//  Purpose:    Create and publish the UPnP device tree.
//
//  Arguments:
//      pszXMLDesc [in] XML device description template
//
//  Returns:
//      HRESULT
//
STDMETHODIMP UPnPRegistrar::ReregisterRunningDevice(
    /*[in]*/ BSTR     bstrDeviceIdentifier,
    /*[in]*/ BSTR     bstrXMLDesc,
    /*[in]*/ IUnknown * punkDeviceControl,
    /*[in]*/ BSTR     bstrInitString,
    /*[in]*/ BSTR     bstrResourcePath,
    /*[in]*/ long     nLifeTime)
{
    HRESULT hr = S_OK;
    DeviceProxy *pDevProxy;
    WCHAR *pszUDN;

    pDevProxy = FindDevProxyByName(bstrDeviceIdentifier);
    if (pDevProxy)
    {
        return E_FAIL;  // TODO: proper error
    }
    // construct UDN as "uuid:<bstrDeviceIdentifier>"
    pszUDN = new WCHAR[wcslen(bstrDeviceIdentifier) + celems(c_szuuidprefix)];
    if (!pszUDN)
    {
        return E_OUTOFMEMORY;
    }
    wcscpy(pszUDN, c_szuuidprefix);
    wcscat(pszUDN, bstrDeviceIdentifier);

    pDevProxy = new DeviceProxy(
                    bstrDeviceIdentifier,
                    bstrInitString,
                    bstrResourcePath,
                    bstrXMLDesc,
                    nLifeTime,
                    punkDeviceControl,
                    pszUDN
                    );
    if (pDevProxy)
    {
        hr = pDevProxy->AddDevice(bstrXMLDesc);
        if (SUCCEEDED(hr))
        {
            pDevProxy->AddToList(&m_DevProxyList);
        }
        else
            delete pDevProxy;
    }
    else
        hr = E_OUTOFMEMORY;

    delete[] pszUDN;

    return hr;
}

#define __OPT_PLAT_FLAG (defined (ARMV4T))
#define __OPT_VER_OFF
#define __OPT_BUGNUMSTRING  "26316"
#include <optimizer.h>

//+---------------------------------------------------------------------------
//
//  Function:   
//
//  Purpose:    Create and publish the UPnP device tree.
//
//  Arguments:
//      pszXMLDesc [in] XML device description template
//
//  Returns:
//      HRESULT
//
STDMETHODIMP UPnPRegistrar::ReregisterDevice(
    /*[in]*/ BSTR     bstrDeviceIdentifier,
    /*[in]*/ BSTR     bstrXMLDesc,
    /*[in]*/ BSTR     bstrProgIDDeviceControlClass,
    /*[in]*/ BSTR     bstrInitString,
    /*[in]*/ BSTR     bstrContainerId,
    /*[in]*/ BSTR     bstrResourcePath,
    /*[in]*/ long     nLifeTime)
{
    HRESULT hr = E_FAIL;
    HANDLE hUPnPHostingEvent;


    TraceTag(ttidRegistrar, "UPnPRegistrar::ReregisterDevice");
    if ( bstrDeviceIdentifier == NULL 
        || bstrXMLDesc == NULL
        || bstrProgIDDeviceControlClass == NULL
        )
    {
        return E_INVALIDARG;
    }
    // make sure the hosting service is around
    hUPnPHostingEvent = CreateEvent(NULL,FALSE,FALSE,UPNPLOADEREVENTNAME);
    if (hUPnPHostingEvent == NULL)
        return E_FAIL;
    // the service is supposed to create the named event
    if (GetLastError() != ERROR_ALREADY_EXISTS)
    {
        CloseHandle(hUPnPHostingEvent);
        return HRESULT_FROM_WIN32(ERROR_SERVICE_NOT_FOUND);
    }


    RegEntry upnpDevicesKey(UPNPDEVICESKEY);
    if (upnpDevicesKey.Success())
    {
        RegEntry upnpDeviceKey(bstrDeviceIdentifier, upnpDevicesKey.GetKey());
        
        if (upnpDeviceKey.Success())
        {
            if ((bstrInitString && (hr = upnpDeviceKey.SetValue(L"InitString",bstrInitString)))
                ||(bstrProgIDDeviceControlClass && (hr = upnpDeviceKey.SetValue(L"ProgId",bstrProgIDDeviceControlClass)))
                || (bstrResourcePath && (hr = upnpDeviceKey.SetValue(L"ResourcePath",bstrResourcePath)))
                || (hr = upnpDeviceKey.SetValue(L"XMLDesc",bstrXMLDesc)) // is this too big for the registry?
                || (hr = upnpDeviceKey.SetValue(L"State", UPNPREG_STARTING))
                || (hr = upnpDeviceKey.SetValue(L"UDN", bstrDeviceIdentifier))
                || (hr = upnpDeviceKey.SetValue(L"LifeTime", nLifeTime))) 
            {
                hr = HRESULT_FROM_WIN32(hr);
                upnpDeviceKey.DeleteKey();
                RegDeleteKey(upnpDevicesKey.GetKey(),bstrDeviceIdentifier);
            }
        }
    }
    if (SUCCEEDED(hr))
    {
        // signal the hosting process
        SetEvent(hUPnPHostingEvent);
    }
    CloseHandle(hUPnPHostingEvent);
    return hr;
}

#define __OPT_PLAT_FLAG (defined (ARMV4T))
#define __OPT_VER_RESTORE
#define __OPT_BUGNUMSTRING  "26316"
#include <optimizer.h>



//+---------------------------------------------------------------------------
//
//  Function:   
//
//  Purpose:    Create and publish the UPnP device tree.
//
//  Arguments:
//      pszXMLDesc [in] XML device description template
//
//  Returns:
//      HRESULT
//
STDMETHODIMP UPnPRegistrar::UnregisterDevice(
    /*[in]*/ BSTR     bstrDeviceIdentifier,
    /*[in]*/ BOOL     fPermanent)
{
    TraceTag(ttidRegistrar, "UPnPRegistrar::UnregisterDevice");
    HRESULT hr = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
    HANDLE hUPnPHostingEvent = NULL;

    if (bstrDeviceIdentifier == NULL || *bstrDeviceIdentifier == 0)
    {
        return E_INVALIDARG;
    }
    RegEntry upnpDevicesKey(UPNPDEVICESKEY);
    if (upnpDevicesKey.Success())
    {
        if (fPermanent)
        {
            hr = RegDeleteKey(upnpDevicesKey.GetKey(),bstrDeviceIdentifier);
        }
        else
        {
            RegEntry upnpDeviceKey(bstrDeviceIdentifier, upnpDevicesKey.GetKey(), FALSE); // open existing device key
            hr = upnpDeviceKey.SetValue(UPNPSTATEVALUE,UPNPREG_DISABLED);
            hr = HRESULT_FROM_WIN32(hr);
        }
        if (SUCCEEDED(hr))
        {
            // this appears to be  registered device. Need to signal the hosting process if its running
            hUPnPHostingEvent = CreateEvent(NULL,FALSE,FALSE,UPNPLOADEREVENTNAME);
            if (hUPnPHostingEvent && GetLastError() != ERROR_ALREADY_EXISTS)
            {
                // the hosting process is not around
                CloseHandle(hUPnPHostingEvent);
                hUPnPHostingEvent = NULL;
            }
        }

    }
    if (!UpnpRemoveDevice(bstrDeviceIdentifier) && hr != 0)
    {
        // return failure if we were not able to remove the device and it was not in the registry
        hr = HRESULT_FROM_WIN32(GetLastError());
    }
    else
        hr = S_OK;

    if (hUPnPHostingEvent)
    {
        // signal the hosting process. Otherwise it may not get to know that the device has been unregistered
        SetEvent(hUPnPHostingEvent);
        CloseHandle(hUPnPHostingEvent);
    }

    return hr;
}



//+---------------------------------------------------------------------------
//
//  Function:   
//
//  Purpose:    Create and publish the UPnP device tree.
//
//  Arguments:
//      pszXMLDesc [in] XML device description template
//
//  Returns:
//      HRESULT
//
STDMETHODIMP UPnPRegistrar::GetUniqueDeviceName(
    /*[in]*/          BSTR   bstrDeviceIdentifier,
    /*[in]*/          BSTR   bstrTemplateUDN,
    /*[out, retval]*/ BSTR * pbstrUDN)
{
    HRESULT hr = S_OK;
    WCHAR szUDN[128];
    DWORD cchUDN = sizeof(szUDN)/sizeof(szUDN[0]);
    if (!UpnpGetUDN(bstrDeviceIdentifier, bstrTemplateUDN, szUDN, &cchUDN))
    {
        hr = HRESULT_FROM_WIN32(GetLastError());
    }
    else
    {
        *pbstrUDN = SysAllocString(szUDN);
        if (!*pbstrUDN)
            hr = E_OUTOFMEMORY;
    }

    return hr;
}

STDMETHODIMP UPnPRegistrar::RegisterDeviceProvider(
    /*[in]*/ BSTR     bstrProviderName,
    /*[in]*/ BSTR     bstrProgIDProviderClass,
    /*[in]*/ BSTR     bstrInitString,
    /*[in]*/ BSTR     bstrContainerId)
{
    return E_NOTIMPL;
}

STDMETHODIMP UPnPRegistrar::UnregisterDeviceProvider(
    /*[in]*/ BSTR     bstrProviderName)
{
    return E_NOTIMPL;
}


//
//  DeviceProxy implementation
//


//+---------------------------------------------------------------------------
//
//  Function:   FreeControlRequest
//
//  Purpose:    Frees resources used by the fields of a UPNP_CONTROL_REQUEST
//              structure
//
//  Arguments:
//      pucr [in] Address of the structure to cleanup
//
//  Returns:
//      (none)
//    This function just frees the resources used by the fields within
//    the passed in structure. It does not free the memory used by the
//    structure itself.
//
static VOID
FreeControlRequest(
    IN UPNP_CONTROL_REQUEST    * pucr)
{
    if (pucr->bstrActionName)
    {
        SysFreeString(pucr->bstrActionName);
        pucr->bstrActionName = NULL;
    }

    if (pucr->rgvarInputArgs)
    {
        for (DWORD i = 0; i < pucr->cInputArgs; i++)
        {
            VariantClear(&pucr->rgvarInputArgs[i]);
        }

        delete [] pucr->rgvarInputArgs;
        pucr->rgvarInputArgs = NULL;
        pucr->cInputArgs = 0;
    }
}



//+---------------------------------------------------------------------------
//
//  Function:   FormatControlRequest
//
//  Purpose:    Copy the action name, parameter names and values from the
//              UPNPSERVICECONTROL struct to the UPNP_CONTROL_REQUEST struct
//              
//              The existence of two different types is possibly avoidable
//              but one meshes with the COM world, and one does not.
//
//  Arguments:
//      pSvcCtl [in] pointer to the populated UPNPSERVICECONTROL struct
//      pucreq  [out] pointer to the empty UPNP_CONTROL_REQUEST struct
//
//  Returns:
//      TRUE if the fields were copied successully to pucreq
//
static
BOOL
FormatControlRequest(UPNPSERVICECONTROL *pSvcCtl, UPNP_CONTROL_REQUEST *pucreq)
{
    BOOL fRet = TRUE;
    UINT i;
    ZeroMemory(pucreq, sizeof(*pucreq));
    pucreq->bstrActionName = SysAllocString(pSvcCtl->pszAction);
    if (pSvcCtl->cInArgs)
    {
        pucreq->rgvarInputArgs = new VARIANT[pSvcCtl->cInArgs];
        if (pucreq->rgvarInputArgs)
        {
            pucreq->cInputArgs = pSvcCtl->cInArgs;
            for (i=0; i< pSvcCtl->cInArgs; i++)
            {
                V_BSTR(&pucreq->rgvarInputArgs[i]) = SysAllocString(pSvcCtl->pInArgs[i].pszValue);
                if (!V_BSTR(&pucreq->rgvarInputArgs[i]))
                {
                    fRet = FALSE;   // is it ok to have a NULL value?
                    break;
                }
                pucreq->rgvarInputArgs[i].vt = VT_BSTR;
            }

        }
        else
            fRet = FALSE;
    }
    if (!fRet)
    {
        // free any strings we allocated
        FreeControlRequest(pucreq); 
    }
    return fRet;
}



//+---------------------------------------------------------------------------
//
//  Function:   FreeControlResponse
//
//  Purpose:    Frees resources used by the fields of a UPNP_CONTROL_RESPONSE
//              structure
//
//  Arguments:
//      pucr [in] Address of the structure to cleanup
//
//  Returns:
//      (none)
//
//  Notes:
//    This function just frees the resources used by the fields within
//    the passed in structure. It does not free the memory used by the
//    structure itself.
//
static VOID
FreeControlResponse(
    IN UPNP_CONTROL_RESPONSE    * pucresp)
{
    if (pucresp->bstrActionName)
    {
        SysFreeString(pucresp->bstrActionName);
        pucresp->bstrActionName = NULL;
    }

    UPNP_CONTROL_RESPONSE_DATA * pucrd = &pucresp->ucrData;

    if (pucresp->fSucceeded)
    {
        if (pucrd->Success.rgvarOutputArgs)
        {
            for (DWORD i = 0; i < pucrd->Success.cOutputArgs; i++)
            {
                VariantClear(&pucrd->Success.rgvarOutputArgs[i]);
            }

            CoTaskMemFree(pucrd->Success.rgvarOutputArgs);
            pucrd->Success.rgvarOutputArgs = NULL;
            pucrd->Success.cOutputArgs = 0;
        }
    }
    else
    {
        if (pucrd->Fault.bstrFaultCode)
        {
            SysFreeString(pucrd->Fault.bstrFaultCode);
            pucrd->Fault.bstrFaultCode = NULL;
        }

        if (pucrd->Fault.bstrFaultString)
        {
            SysFreeString(pucrd->Fault.bstrFaultString);
            pucrd->Fault.bstrFaultString = NULL;
        }

        if (pucrd->Fault.bstrUPnPErrorCode)
        {
            SysFreeString(pucrd->Fault.bstrUPnPErrorCode);
            pucrd->Fault.bstrUPnPErrorCode = NULL;
        }

        if (pucrd->Fault.bstrUPnPErrorString)
        {
            SysFreeString(pucrd->Fault.bstrUPnPErrorString);
            pucrd->Fault.bstrUPnPErrorString = NULL;
        }
    }
}


//+---------------------------------------------------------------------------
//
//  Function:   GetServiceProxy
//
//  Purpose:    Locate the ServiceProxy object by serviceId. If it does not exist
//              attempt to create and initialize one and add it to the list.
//
//
//  Arguments:
//      pszServiceId [in] serviceId string
//
//  Returns:
//      pointer to the ServiceProxy object or NULL if failure.
//      pointer is not AddRef'ed.
//
ServiceProxy *
DeviceProxy::GetServiceProxy(LPCWSTR pszUDN, LPCWSTR pszServiceId)
{
    ServiceProxy    *pSvc;
    HRESULT         hr;
    IDispatch       *pISO;

    for (pSvc=m_pServices;pSvc; pSvc = pSvc->m_pNext)
    {
        if (wcscmp(pszServiceId, pSvc->m_bstrSid) == 0 && wcscmp(pszUDN, pSvc->m_bstrUDN) == 0)
            break;
    }

    if (!pSvc)
    {
        // this may be the first time, so create one
        if (m_pDeviceControl)
        {
            ce::auto_bstr bstrUDN, bstrServiceId;

            bstrUDN = SysAllocString(pszUDN);
            bstrServiceId = SysAllocString(pszServiceId);

            hr = m_pDeviceControl->GetServiceObject(bstrUDN, bstrServiceId, &pISO);

            if (SUCCEEDED(hr))
            {
                WCHAR szSCPDPath[MAX_PATH];

                // get the SCPD
                UpnpGetSCPDPath(m_bstrDeviceId, pszUDN, pszServiceId, szSCPDPath, celems(szSCPDPath));

                pSvc = new ServiceProxy(this);

                if (pSvc)
                {
                    pSvc->AddRef();     // set the ref count to 1

                    hr = pSvc->Initialize(pszUDN, pszServiceId, szSCPDPath, pISO);
                    if (SUCCEEDED(hr))
                    {
                        // add to service list
                        pSvc->m_pNext = m_pServices;
                        m_pServices = pSvc;
                        // submit the initial state variables
                        pSvc->OnStateChanged(0,NULL);   // 0,NULL => all variables
                    }
                    else
                    {
                        pSvc->Release();    // this should delete the object
                        pSvc = NULL;
                    }
                }

                pISO->Release();
            }
        }
    }
    return pSvc;
}

//+---------------------------------------------------------------------------
//
//  Method:   DeviceProxy::SubscribeCallback
//
//  Purpose:    Invoked when a remote subscription request is received.
//
//  Arguments:
//      UPNPSUBSCRIPTION *pUPnPSubscription
//
//  Returns:
//      TRUE if the subscription is accepted
//
BOOL
DeviceProxy::SubscribeCallback(UPNPSUBSCRIPTION *pUPnPSubscription)
{
    ServiceProxy *pServiceProxy;
    // We need to have a pointer to the implementation object by now
    if (!m_pDeviceControl)
    {
        return FALSE;
    }

    // Find the service proxy by serviceId 
    // create if necessary
    pServiceProxy = GetServiceProxy(pUPnPSubscription->pszUDN, pUPnPSubscription->pszSID);

    if (!pServiceProxy)
    {
        TraceTag(ttidError, "SubscribeCallback: No service proxy for Service ID %S\n", pUPnPSubscription->pszSID);
        return FALSE;   
    }

    pServiceProxy->UpdateEventedVariables(FALSE, 0, NULL);  // FALSE -> just update, don't send event; 0, NULL -> all evented variables
    
    return TRUE;
}

//+---------------------------------------------------------------------------
//
//  Function:   ControlCallback
//
//  Purpose:    Invoked when a remote control request is received
//
//  Arguments:
//      pszXMLDesc [in] XML device description template
//
//  Returns:
//      HRESULT
//
BOOL
DeviceProxy::ControlCallback(UPNPSERVICECONTROL *pSvcCtl)
{
    HRESULT hr;
    BOOL fRet = FALSE;
    UPNP_CONTROL_REQUEST ucreq;
    UPNP_CONTROL_RESPONSE ucresp;
    ServiceProxy *pServiceProxy;
    TraceTag(ttidRegistrar, "DeviceProxy::ControlRequest\n");
    Assert(pSvcCtl->pszSID);
    Assert(m_pDeviceControl);
    // We need to have a pointer to the implementation object by now
    if (!m_pDeviceControl)
    {
        return FALSE;
    }

    // Find the service proxy by serviceId 
    // create if necessary
    pServiceProxy = GetServiceProxy(pSvcCtl->pszUDN, pSvcCtl->pszSID);
    if (!pServiceProxy)
    {
        TraceTag(ttidError, "ControlCallback: No service proxy for Service ID %S\n", pSvcCtl->pszSID);
        return FALSE;   
    }

    // Format the parameters in the way the AutomationProxy wants them
    if (!FormatControlRequest(pSvcCtl, &ucreq))
        return FALSE;
    memset(&ucresp,0, sizeof(ucresp));

    hr = pServiceProxy->m_pap->ExecuteRequest(&ucreq, &ucresp);

    if (SUCCEEDED(hr))
    {
        hr = pServiceProxy->SetControlResponse(pSvcCtl, &ucresp);
        if (SUCCEEDED(hr))
        {
            fRet = TRUE;
        }
    }

    // cleanup
    ::FreeControlRequest(&ucreq);
    ::FreeControlResponse(&ucresp);

    // if we return false the UPNP service will return a generic fault
    // which is fine.
    return fRet;
}

//+---------------------------------------------------------------------------
//
//  Function:  InitCallback 
//
//  Purpose:    Invoked before any other device callbacks,
//              when the first subsribe or control request is received.
//
//  Arguments:
//
//  Returns:
//      TRUE if ok, FALSE otherwise
//
BOOL
DeviceProxy::InitCallback()
{
    TraceTag(ttidRegistrar, "DeviceProxy::InitRequest\n");

    // We need to have a pointer to the implementation object by now
    if (!m_pDeviceControl)
    {
        return FALSE;
    }

    m_pDeviceControl->Initialize(m_bstrXMLDesc, m_bstrDeviceId, m_bstrInitString);

    return TRUE;
}

//+---------------------------------------------------------------------------
//
//  Function: ShutdownCallback  
//
//  Purpose: The UPnP service wants to shutdown the device. This
//           is the last callback to be received, so this is where we free
//           the DeviceProxy object
//
//  Arguments:
//
//  Returns:
//      returns TRUE
//
BOOL
DeviceProxy::ShutdownCallback()
{
    TraceTag(ttidRegistrar, "DeviceProxy::ShutdowndRequest\n");
    RemoveDevice();
    RemoveFromList();
    delete this;
    return TRUE;
}

//+---------------------------------------------------------------------------
//
//  Function:   
//
//  Purpose:    Create and publish the UPnP device tree.
//
//  Arguments:
//      pszXMLDesc [in] XML device description template
//
//  Returns:
//      HRESULT
//
DeviceProxy::~DeviceProxy()
{
    ServiceProxy *pSvc = m_pServices;

    while (m_pServices)
    {
        LONG ref;
        pSvc = m_pServices;
        m_pServices = pSvc->m_pNext;
        pSvc->Shutdown();     // unadvise the event source
        ref = pSvc->Release(); // this should delete the service
        Assert(ref == 0);
    }
    SysFreeString(m_bstrDeviceId);
    SysFreeString(m_bstrInitString);
    SysFreeString(m_bstrResourcePath);
    SysFreeString(m_bstrUDN);
    SysFreeString(m_bstrXMLDesc);
    if (m_pDeviceControl)
    {
        m_pDeviceControl->Release();
    }
}

//
// ServiceProxy implementation
//

//+---------------------------------------------------------------------------
//
//  Function:   
//
//  Purpose:    Create and publish the UPnP device tree.
//
//  Arguments:
//      pszXMLDesc [in] XML device description template
//
//  Returns:
//      HRESULT
//
HRESULT
ServiceProxy::Initialize(PCWSTR pszUDN, PCWSTR pszSid, PCWSTR pszSCPD, IUnknown *pUPnPServiceObj)
{
    HRESULT hr;

    m_bstrSid = SysAllocString(pszSid);
    if (!m_bstrSid)
        return E_OUTOFMEMORY;

    m_bstrUDN = SysAllocString(pszUDN);
    if (!m_bstrUDN)
        return E_OUTOFMEMORY;

    hr = CUPnPAutomationProxy::CreateInstance(NULL, IID_IUPnPAutomationProxy, (void **)&m_pap);

    if (SUCCEEDED(hr))
    {
        hr = m_pap->Initialize(pUPnPServiceObj, (PWSTR)pszSCPD);
        m_pap->QueryInterface(IID_IUPnPServiceDescriptionInfo, (void **)&m_psdi);
        if (SUCCEEDED(hr))
        {
            // initialize eventing
            hr = pUPnPServiceObj->QueryInterface(IID_IUPnPEventSource,(void **)&m_pes);
            if (SUCCEEDED(hr))
            {
                hr = m_pes->Advise((IUPnPEventSink *)this);
                if (FAILED(hr))
                {
                    m_pes->Release();
                    m_pes = NULL;
                }
            }
        }
    }
    return hr;
}



//+---------------------------------------------------------------------------
//
//  Function:   
//
//  Purpose:    release the automation proxy and event source objects.
//             Can't put this code in the destructor, because this is where the refcount gets
//             decremented; the destructor gets called only when the refcount goes down to zero
//
//  Arguments:
//
//  Returns:
//      nothing
//
void
ServiceProxy::Shutdown()
{
    LONG cRef;
    if (m_pes)
    {
        m_pes->Unadvise((IUPnPEventSink *)this);
        m_pes->Release();
        m_pes = NULL;
    }
    if (m_pap)
    {
        if (m_psdi)
        {
            cRef = m_psdi->Release();
            m_psdi = NULL;
        }
        cRef = m_pap->Release();
        Assert(cRef == 0);
        m_pap = NULL;
    }
}

ServiceProxy::~ServiceProxy()
{
    Shutdown();     // won't do anything except perhaps in init error cases
    SysFreeString(m_bstrSid);
    SysFreeString(m_bstrUDN);
}



//+---------------------------------------------------------------------------
//
//  Member:     ServiceProxy::OnStateChanged
//
//  Purpose:    Notifies the eventing manager that the state of a service on
//              a hosted device has changed
//
//  Arguments:
//      cChanges        [in]    Number of state variables that have changed
//      rgdispidChanges [in]    Array of DISPIDs for those state variables
//
//  Returns:    S_OK if success, E_OUTOFMEMORY, or any other OLE interface
//              error code otherwise.
//  Notes:  
//
STDMETHODIMP ServiceProxy::OnStateChanged(DWORD cChanges,
                                                  DISPID rgdispidChanges[])
{
    return UpdateEventedVariables(TRUE, cChanges, rgdispidChanges); // TRUE -> update and send events
}

// Undocumented UPnP API
BOOL
WINAPI
UpnpUpdateEventedVariables(
    PCWSTR pszDeviceName,
    PCWSTR pszUDN,
    PCWSTR pszServiceId,
    DWORD nArgs,
    UPNPPARAM *rgArgs);


HRESULT ServiceProxy::UpdateEventedVariables(BOOL bSubmitEvent, DWORD cChanges, DISPID rgdispidChanges[])
{
    HRESULT     hr = S_OK;
    LPWSTR      szBody = NULL;
    DWORD       cVars, ivar;
    LPWSTR *    rgszNames;
    LPWSTR *    rgszTypes;
    VARIANT *   rgvarValues;

    AssertSz(m_bstrSid, "What? Did we not get initialized or something?");
    AssertSz(m_pap, "Automation proxy not initialized?");

    if (!m_pes)
    {
        hr = E_UNEXPECTED;
    }
    else if (cChanges && !rgdispidChanges)
    {
        hr = E_INVALIDARG;
    }
    else
    {
        if (!cChanges && !rgdispidChanges)
        {
            TraceTag(ttidEvents, "Sending all state variables for %S\n", m_bstrSid);
        }
        hr = m_pap->QueryStateVariablesByDispIds(cChanges, rgdispidChanges,
                                                  &cVars, &rgszNames,
                                                  &rgvarValues, &rgszTypes);
        if (SUCCEEDED(hr))
        {
            UPNPPARAM *rgParams = new UPNPPARAM[cVars];
            if (rgParams)
            {
                for (ivar = 0; ivar < cVars; ivar++)
                {
                    rgParams[ivar].pszName = rgszNames[ivar];

                    hr = VariantChangeType(rgvarValues+ivar, rgvarValues+ivar, 0, VT_BSTR);
                    if (FAILED(hr))
                    {
                        TraceError("OnStateChanged:Can't change arg type to BSTR\n", hr);
                        break;
                    }
                    rgParams[ivar].pszValue = V_BSTR(rgvarValues+ivar);
                }
                if (SUCCEEDED(hr))
                {
                    if (bSubmitEvent)
                    {
                        if(!UpnpSubmitPropertyEvent(m_pDevProxy->Name(), m_bstrUDN, m_bstrSid, cVars, rgParams))
                        {
                            TraceTag(ttidError,"OnStateChanged: UpnpSubmitPropertyEvent failed error=%d\n", GetLastError());
                            hr = HrFromLastWin32Error();
                        }
                    }
                    else
                    {
                        if(!UpnpUpdateEventedVariables(m_pDevProxy->Name(), m_bstrUDN, m_bstrSid, cVars, rgParams))
                        {
                            TraceTag(ttidError,"OnStateChanged: UpnpUpdateEventedVariables failed error=%d\n", GetLastError());
                            hr = HrFromLastWin32Error();
                        }
                    }
                }

                delete [] rgParams;
            }
            else
                hr = E_OUTOFMEMORY;



            for (ivar = 0; ivar < cVars; ivar++)
            {
                CoTaskMemFree(rgszTypes[ivar]);
                CoTaskMemFree(rgszNames[ivar]);
                VariantClear(&rgvarValues[ivar]);
            }

            CoTaskMemFree(rgvarValues);
            CoTaskMemFree(rgszTypes);
            CoTaskMemFree(rgszNames);
        }
    }

    TraceError("ServiceProxy::OnStateChanged", hr);
    return hr;
}

//+---------------------------------------------------------------------------
//
//  Member:     CUPnPEventingManager::OnStateChangedSafe
//
//  Purpose:    Same as OnStateChanged, except this is for VB users that need
//              to pass the array of DISPIDs in a SafeArray.
//
//  Arguments:
//      psa     [in]    SafeArray of DISPIDs that have changed
//
//  Returns:    Same as OnStateChanged
//
//  Author:     danielwe   2000/09/21
//
//  Notes:  From Whistler
//
STDMETHODIMP ServiceProxy::OnStateChangedSafe(VARIANT varsadispidChanges)
{
    HRESULT         hr = S_OK;
    SAFEARRAY *psa = V_ARRAY(&varsadispidChanges);
    DISPID HUGEP *  rgdispids;

    // Get a pointer to the elements of the array.
    hr = SafeArrayAccessData(psa, (void HUGEP**)&rgdispids);
    if (SUCCEEDED(hr))
    {
        hr = OnStateChanged(psa->rgsabound[0].cElements, rgdispids);
        SafeArrayUnaccessData(psa);
    }

    TraceError("CUPnPEventingManager::OnStateChangedSafe", hr);
    return hr;
}

//+---------------------------------------------------------------------------
//
//  Function:   
//
//  Purpose:    Create and publish the UPnP device tree.
//
//  Arguments:
//      pszXMLDesc [in] XML device description template
//
//  Returns:
//      HRESULT
//
HRESULT
ServiceProxy::SetControlResponse(UPNPSERVICECONTROL *pSvcCtl, UPNP_CONTROL_RESPONSE *pucresp)
{
    HRESULT hr = S_OK;

    if (pucresp->fSucceeded)
    {
        UPNPPARAM *pParams;
        DWORD i, cOutputArgs = 0;
        VARIANT *pvar;
        BSTR  * rgbstrNames = NULL;
        BSTR  * rgbstrTypes = NULL;

        // success response
        if (!pucresp->ucrData.Success.cOutputArgs)
            return S_OK;    // the UPNP service will return a default success response

        pParams = new UPNPPARAM [pucresp->ucrData.Success.cOutputArgs];
        if (!pParams)
            return E_OUTOFMEMORY;

        Assert(m_psdi);
        if (wcscmp(pSvcCtl->pszAction, L"QueryStateVariable") == 0)
        {
            pParams[0].pszName = L"return";
            pvar = &pucresp->ucrData.Success.rgvarOutputArgs[0];
            hr = VariantChangeType(pvar,pvar,0,VT_BSTR);
            if (SUCCEEDED(hr))
            {
                pParams[0].pszValue = V_BSTR(pvar);
                cOutputArgs = 1;
            }
        }
        else
        {
            // one or more output vars need to be specified
            // unfortunately we don't have the var names in pucresp (that would be too easy)
            // so they need to be determined
            hr = m_psdi->GetOutputArgumentNamesAndTypes(
                    pucresp->bstrActionName,
                    &cOutputArgs,
                    &rgbstrNames,
                    &rgbstrTypes);

            if (SUCCEEDED(hr))
            {
                Assert(cOutputArgs == pucresp->ucrData.Success.cOutputArgs);
                
                for (i=0; i < cOutputArgs; i++)
                {
                    pParams[i].pszName = rgbstrNames[i];
                    pvar = &pucresp->ucrData.Success.rgvarOutputArgs[i];
                    hr = VariantChangeType(pvar, pvar, 0, VT_BSTR);
                    if (FAILED(hr))
                    {
                        TraceError("SetControlResponse:Can't change arg type to BSTR\n", hr);
                        break;
                    }
                    pParams[i].pszValue = V_BSTR(pvar);
                }

            }
        }
        if (SUCCEEDED(hr))
        {
            if (!UpnpSetControlResponse(pSvcCtl, cOutputArgs, pParams))
            {
                TraceTag(ttidError, "UpnpSetControlResponse returned error %d for %S\n",
                    GetLastError(), pSvcCtl->pszAction);

                hr = HrFromLastWin32Error();
            }
        }
        // Clean up.
        if (rgbstrNames)
        {
            for (i = 0; i < cOutputArgs; i++)
            {
                SysFreeString(rgbstrNames[i]);
                rgbstrNames[i] = NULL;
                SysFreeString(rgbstrTypes[i]);
                rgbstrTypes[i] = NULL;
            }
            CoTaskMemFree(rgbstrNames);
            rgbstrNames = NULL;
            CoTaskMemFree(rgbstrTypes);
            rgbstrTypes = NULL;
        }
        delete [] pParams;

    }
    else
    {
        int errCode = 501;  // Action failed
        Assert(pucresp->ucrData.Fault.bstrUPnPErrorCode);
        if (pucresp->ucrData.Fault.bstrUPnPErrorCode)
        {
            errCode = _wtol(pucresp->ucrData.Fault.bstrUPnPErrorCode);
        }
        // error response
        if (!UpnpSetErrorResponse(pSvcCtl, errCode, pucresp->ucrData.Fault.bstrUPnPErrorString))
        {
            TraceTag(ttidError, "UpnpSetControlResponse returned error %d for %S\n",
                GetLastError(), pSvcCtl->pszAction);

            hr = HrFromLastWin32Error();
        }
    }
    return hr;
}

