/*++

Copyright (c) 1997  Microsoft Corporation

Module Name:

    confaddr.cpp 

Abstract:

    This module contains implementation of CIPConfMSP.

Author:
    
    Mu Han (muhan)   5-September-1997

--*/
#include "stdafx.h"

#include <initguid.h>
DEFINE_GUID(CLSID_IPConfMSP, 0x0F1BE7F7, 0x45CA, 0x11d2,
            0x83, 0x1F, 0x0, 0xA0, 0x24, 0x4D, 0x22, 0x98);

#ifdef USEIPADDRTABLE

#include <iprtrmib.h>

typedef DWORD (WINAPI * PFNGETIPADDRTABLE)(
                OUT    PMIB_IPADDRTABLE pIPAddrTable,
                IN OUT PDWORD           pdwSize,
                IN     BOOL             bOrder
                );

#define IPHLPAPI_DLL        L"IPHLPAPI.DLL"

#define GETIPADDRTABLE      "GetIpAddrTable"    

#define IsValidInterface(_dwAddr_) \
    (((_dwAddr_) != 0) && \
     ((_dwAddr_) != htonl(INADDR_LOOPBACK)))

#endif

#define IPCONF_WINSOCKVERSION     MAKEWORD(2,0)

HRESULT CIPConfMSP::FinalConstruct()
{
    // initialize winsock stack
    WSADATA wsaData;
    if (WSAStartup(IPCONF_WINSOCKVERSION, &wsaData) != 0)
    {
        LOG((MSP_ERROR, "WSAStartup failed with:%x", WSAGetLastError()));
        return E_FAIL;
    }

    // allocate control socket
    m_hSocket = WSASocket(
        AF_INET,            // af
        SOCK_DGRAM,         // type
        IPPROTO_IP,         // protocol
        NULL,               // lpProtocolInfo
        0,                  // g
        0                   // dwFlags
        );

    // validate handle
    if (m_hSocket == INVALID_SOCKET) {

        LOG((
            MSP_ERROR,
            "error %d creating control socket.\n",
            WSAGetLastError()
            ));

        // failure
		WSACleanup();
     
        return E_FAIL;
    }

    HRESULT hr = CMSPAddress::FinalConstruct();

	if (hr != S_OK)
	{
		// close socket
		closesocket(m_hSocket);

		// shutdown
		WSACleanup();
	}
	
	return hr;
}

void CIPConfMSP::FinalRelease()
{
    CMSPAddress::FinalRelease();

    if (m_hDxmrtp)
    {
        FreeLibrary(m_hDxmrtp);
        m_hDxmrtp = NULL;
    }

    if (m_hSocket != INVALID_SOCKET)
    {
        // close socket
        closesocket(m_hSocket);
    }

    // shutdown
    WSACleanup();
}

DWORD CIPConfMSP::FindLocalInterface(DWORD dwIP)
{

    SOCKADDR_IN DestAddr;
    DestAddr.sin_family         = AF_INET;
    DestAddr.sin_port           = 0;
    DestAddr.sin_addr.s_addr    = htonl(dwIP);

    SOCKADDR_IN LocAddr;

    // query for default address based on destination

    DWORD dwStatus;
    DWORD dwLocAddrSize = sizeof(SOCKADDR_IN);
    DWORD dwNumBytesReturned = 0;

    if ((dwStatus = WSAIoctl(
		    m_hSocket, // SOCKET s
		    SIO_ROUTING_INTERFACE_QUERY, // DWORD dwIoControlCode
		    &DestAddr,           // LPVOID lpvInBuffer
		    sizeof(SOCKADDR_IN), // DWORD cbInBuffer
		    &LocAddr,            // LPVOID lpvOUTBuffer
		    dwLocAddrSize,       // DWORD cbOUTBuffer
		    &dwNumBytesReturned, // LPDWORD lpcbBytesReturned
		    NULL, // LPWSAOVERLAPPED lpOverlapped
		    NULL  // LPWSAOVERLAPPED_COMPLETION_ROUTINE lpComplROUTINE
	    )) == SOCKET_ERROR) 
    {

	    dwStatus = WSAGetLastError();

	    LOG((MSP_ERROR, "WSAIoctl failed: %d (0x%X)", dwStatus, dwStatus));

        return INADDR_NONE;
    } 

    DWORD dwAddr = ntohl(LocAddr.sin_addr.s_addr);

    if (dwAddr == 0x7f000001)
    {
        // it is loopback address, just return none.
        return INADDR_NONE;
    }

    return dwAddr;
}

HRESULT LoadTapiAudioFilterDLL(
    IN  const TCHAR * const strDllName,
    IN OUT HMODULE * phModule,
    IN  const char * const strAudioGetDeviceInfo,
    IN  const char * const strAudioReleaseDeviceInfo,
    OUT PFNAudioGetDeviceInfo * ppfnAudioGetDeviceInfo,
    OUT PFNAudioReleaseDeviceInfo * ppfnAudioReleaseDeviceInfo
    )
/*++

Routine Description:

    This method enumerate loads the tapi video capture dll.

Arguments:

    str DllName - The name of the dll.

    phModule - memory to store returned module handle.

    ppfnAudioGetDeviceInfo - memory to store the address of AudioGetDeviceInfo
        function.

    ppfnAudioReleaseDeviceInfo - memory to store the address of 
        AudioReleaseDeviceInfo function.
    
Return Value:

    S_OK    - success.
    E_FAIL  - failure.

--*/
{
    ENTER_FUNCTION("CIPConfMSP::LoadTapiAudioFilterDLL");

    // dynamically load the video capture filter dll.
    if (*phModule == NULL)
    {
        *phModule = LoadLibrary(strDllName);
    }

    // validate handle
    if (*phModule == NULL) 
    {
        LOG((MSP_ERROR, "%s, could not load %s., error:%d", 
            __fxName, strDllName, GetLastError()));

        return E_FAIL;
    }

    // retrieve function pointer to retrieve addresses
    PFNAudioGetDeviceInfo pfnAudioGetDeviceInfo 
        = (PFNAudioGetDeviceInfo)GetProcAddress(
                *phModule, strAudioGetDeviceInfo
                );

    // validate function pointer
    if (pfnAudioGetDeviceInfo == NULL) 
    {
        LOG((MSP_ERROR, "%s, could not resolve %s, error:%d", 
            __fxName, strAudioGetDeviceInfo, GetLastError()));

        // failure
        return E_FAIL;
    }

    // retrieve function pointer to retrieve addresses
    PFNAudioReleaseDeviceInfo pfnAudioReleaseDeviceInfo 
        = (PFNAudioReleaseDeviceInfo)GetProcAddress(
                *phModule, strAudioReleaseDeviceInfo
                );

    // validate function pointer
    if (pfnAudioReleaseDeviceInfo == NULL) 
    {
        LOG((MSP_ERROR, "%s, could not resolve %s, error:%d", 
            __fxName, strAudioReleaseDeviceInfo, GetLastError()));

        // failure
        return E_FAIL;
    }

    *ppfnAudioGetDeviceInfo = pfnAudioGetDeviceInfo;
    *ppfnAudioReleaseDeviceInfo = pfnAudioReleaseDeviceInfo;
    
    return S_OK;
}

HRESULT CIPConfMSP::CreateAudioCaptureTerminals()
/*++

Routine Description:

    This method creates audio capture terminals. It uses DShow devenum to 
    enumerate all the wavein capture devices first. Then it will enumerate
    all the DSound capture devices and match them up by name.

Arguments:

    nothing
    
Return Value:

S_OK

--*/
{
    const TCHAR * const strAudioCaptureDll = TEXT("DXMRTP");

    ENTER_FUNCTION("CIPConfMSP::CreateAudioCaptureTerminals");

    // dynamically load the audio capture filter dll.
    PFNAudioGetDeviceInfo pfnAudioGetDeviceInfo;
    PFNAudioReleaseDeviceInfo pfnAudioReleaseDeviceInfo;

    HRESULT hr = LoadTapiAudioFilterDLL(
        strAudioCaptureDll,
        &m_hDxmrtp,
        "AudioGetCaptureDeviceInfo",
        "AudioReleaseCaptureDeviceInfo", 
        &pfnAudioGetDeviceInfo,
        &pfnAudioReleaseDeviceInfo
        );

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

    DWORD dwNumDevices;
    AudioDeviceInfo *pDeviceInfo;

    hr = (*pfnAudioGetDeviceInfo)(&dwNumDevices, &pDeviceInfo);
    if (FAILED(hr))
    {
        LOG((MSP_ERROR, "%s, AudioGetDeviceInfo failed. hr=%x", __fxName, hr));

        return hr;
    }

    // for each devie, create a terminal.
    for (DWORD i = 0; i < dwNumDevices; i ++)
    {
        ITTerminal *pTerminal;

        hr = CIPConfAudioCaptureTerminal::CreateTerminal(
            &pDeviceInfo[i],
            (MSP_HANDLE) this,
            &pTerminal
            );

        if (FAILED(hr))
        {
            LOG((MSP_ERROR, "%s, CreateTerminal for device %d failed. hr=%x",
                __fxName, i, hr));
            break;
        }

        if (!m_Terminals.Add(pTerminal))
        {
            hr = E_OUTOFMEMORY;
            LOG((MSP_ERROR, "%s, out of mem in adding a terminal", __fxName));
            break;
        }
    }

    // release the device info
    (*pfnAudioReleaseDeviceInfo)(pDeviceInfo);

    return hr;
}


HRESULT CIPConfMSP::CreateAudioRenderTerminals()
/*++

Routine Description:

    This method enumerate all the audio render devices and creates a 
    terminal for each of them.

Arguments:

    nothing
    
Return Value:

S_OK

--*/
{
    ENTER_FUNCTION("CIPConfMSP::CreateAudioRenderTerminals");
    const TCHAR * const strAudioRenderDll = TEXT("DXMRTP");

    // dynamically load the audio render filter dll.
    PFNAudioGetDeviceInfo pfnAudioGetDeviceInfo;
    PFNAudioReleaseDeviceInfo pfnAudioReleaseDeviceInfo;

    HRESULT hr = LoadTapiAudioFilterDLL(
        strAudioRenderDll,
        &m_hDxmrtp,
        "AudioGetRenderDeviceInfo",
        "AudioReleaseRenderDeviceInfo",
        &pfnAudioGetDeviceInfo,
        &pfnAudioReleaseDeviceInfo
        );

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

    DWORD dwNumDevices;
    AudioDeviceInfo *pDeviceInfo;

    hr = (*pfnAudioGetDeviceInfo)(&dwNumDevices, &pDeviceInfo);
    if (FAILED(hr))
    {
        LOG((MSP_ERROR, "%s, AudioGetDeviceInfo failed. hr=%x", __fxName, hr));

        return hr;
    }

    // for each devie, create a terminal.
    for (DWORD i = 0; i < dwNumDevices; i ++)
    {
        ITTerminal *pTerminal;

        hr = CIPConfAudioRenderTerminal::CreateTerminal(
            &pDeviceInfo[i],
            (MSP_HANDLE) this,
            &pTerminal
            );

        if (FAILED(hr))
        {
            LOG((MSP_ERROR, "%s, CreateTerminal for device %d failed. hr=%x",
                __fxName, i, hr));
            break;
        }

        if (!m_Terminals.Add(pTerminal))
        {
            hr = E_OUTOFMEMORY;
            LOG((MSP_ERROR, "%s, out of mem in adding a terminal", __fxName));
            break;
        }
    }

    // release the device info
    (*pfnAudioReleaseDeviceInfo)(pDeviceInfo);

    return hr;
}

HRESULT LoadTapiVideoCaptureDLL(
    IN OUT HMODULE * phModule,
    OUT PFNGetNumCapDevices * ppfnGetNumCapDevices,
    OUT PFNGetCapDeviceInfo * ppfnGetCapDeviceInfo
    )
/*++

Routine Description:

    This method enumerate loads the tapi video capture dll.

Arguments:

    phModule - memory to store returned module handle.

    ppfnGetNumCapDevices - memory to store the address of GetNumCapDevices
        function.

    ppfnGetCapDeviceInfo - memory to store the address of GetCapDeviceInfo 
        function.
    
Return Value:

    S_OK    - success.
    E_FAIL  - failure.

--*/
{
    const TCHAR * const strVideoCaptureDll = TEXT("DXMRTP");
    const char * const strGetNumCapDevices = "GetNumVideoCapDevices";
    const char * const strGetCapDeviceInfo = "GetVideoCapDeviceInfo";

    ENTER_FUNCTION("CIPConfMSP::LoadTapiVideoCaptureDLL");

    // dynamically load the video capture filter dll.
    if (*phModule == NULL)
    {
        *phModule = LoadLibrary(strVideoCaptureDll);
    }

    // validate handle
    if (*phModule == NULL) 
    {
        LOG((MSP_ERROR, "%s, could not load %s., error:%d", 
            __fxName, strVideoCaptureDll, GetLastError()));

        return E_FAIL;
    }

    // retrieve function pointer to retrieve addresses
    PFNGetNumCapDevices pfnGetNumCapDevices 
        = (PFNGetNumCapDevices)GetProcAddress(*phModule, strGetNumCapDevices);

    // validate function pointer
    if (pfnGetNumCapDevices == NULL) 
    {
        LOG((MSP_ERROR, "%s, could not resolve %s, error:%d", 
            __fxName, strGetNumCapDevices, GetLastError()));

        // failure
        return E_FAIL;
    }

    // retrieve function pointer to retrieve addresses
    PFNGetCapDeviceInfo pfnGetCapDeviceInfo 
        = (PFNGetCapDeviceInfo)GetProcAddress(*phModule, strGetCapDeviceInfo);

    // validate function pointer
    if (pfnGetCapDeviceInfo == NULL) 
    {
        LOG((MSP_ERROR, "%s, could not resolve %s, error:%d", 
            __fxName, strGetCapDeviceInfo, GetLastError()));

        // failure
        return E_FAIL;
    }

    *ppfnGetNumCapDevices = pfnGetNumCapDevices;
    *ppfnGetCapDeviceInfo = pfnGetCapDeviceInfo;
    
    return S_OK;
}

HRESULT CIPConfMSP::CreateVideoCaptureTerminals()
/*++

Routine Description:

    This method is called by UpdateTerminalList to create all the video
    capture terminals. It loads the video capture dll and calls its device
    enumeration code to enumerate the devices.

Arguments:

    nothing
    
Return Value:

S_OK

--*/
{
    ENTER_FUNCTION("CIPConfMSP::CreateVideoCaptureTerminals");

    // dynamically load the video capture filter dll.
    PFNGetNumCapDevices pfnGetNumCapDevices;
    PFNGetCapDeviceInfo pfnGetCapDeviceInfo;

    HRESULT hr = LoadTapiVideoCaptureDLL(
        &m_hDxmrtp,
        &pfnGetNumCapDevices,
        &pfnGetCapDeviceInfo
        );

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

    DWORD dwNumDevices = 0;
    hr = (*pfnGetNumCapDevices)(&dwNumDevices);

    // we have to check against S_OK because the function returns S_FALSE when
    // there is no device.
    if (hr != S_OK)  
    {
        if (FAILED(hr))
        {
            LOG((MSP_ERROR, "%s, GetNumCapDevices failed. hr=%x", __fxName, hr));
        }
        else 
        {
            LOG((MSP_WARN, "%s, There is no video device. hr=%x", __fxName, hr));
        }

        return hr;
    }

    for (DWORD i = 0; i < dwNumDevices; i ++)
    {
        VIDEOCAPTUREDEVICEINFO DeviceInfo;
        hr = (*pfnGetCapDeviceInfo)(i, &DeviceInfo);

        if (FAILED(hr))
        {
            LOG((MSP_ERROR, "%s, GetNumCapDevices for device %d failed. hr=%x",
                __fxName, i, hr));
            break;
        }

        ITTerminal * pTerminal;

        hr = CIPConfVideoCaptureTerminal::CreateTerminal(
            DeviceInfo.szDeviceDescription,
            i, 
            (MSP_HANDLE) this,
            &pTerminal
            );

        if (FAILED(hr))
        {
            LOG((MSP_ERROR, "%s, CreateTerminal for device %d failed. hr=%x",
                __fxName, i, hr));
            break;
        }

        if (!m_Terminals.Add(pTerminal))
        {
            hr = E_OUTOFMEMORY;
            LOG((MSP_ERROR, "%s, out of mem in adding a terminal", __fxName));
            break;
        }
    }

    return hr;
}

HRESULT CIPConfMSP::UpdateTerminalList(void)
/*++

Routine Description:

    This method is called by the base class when it first tries to enumerate
    all the static terminals. We override this function to create our own
    terminals that use our own filters.

Arguments:

    nothing
    
Return Value:

S_OK

--*/
{
    ENTER_FUNCTION("CIPConfMSP::UpdateTerminalList");

    // the failure of one category of terminals doesn't prevent the enumeration
    // of other categories. So we ignore the return code here. If all 
    // categories fail, the app will get an empty list.

    CreateAudioCaptureTerminals();
    CreateAudioRenderTerminals();
    CreateVideoCaptureTerminals();

    //
    // Our list is now complete.
    //
    m_fTerminalsUpToDate = TRUE;
    
    LOG((MSP_TRACE, "%s, exit S_OK", __fxName));

    return S_OK;
}

HRESULT CIPConfMSP::UpdateTerminalListForPnp(IN BOOL bDeviceArrival)
/*++

Routine Description:

    This method is called by the base class when it receives pnp event
    and needs to recreate static terminal list. We override this function
    to create our own terminals that use our own filters. terminal list
    locks was acquired when calling this method.
    
Return Value:

S_OK

--*/
{
    ENTER_FUNCTION ("CIPConfMSP::UpdateTerminalListForPnp");

    LOG ((MSP_TRACE, "%s (%d) entered", __fxName, bDeviceArrival));

    HRESULT hr;
    
    // variables for keeping old terminal info
    INT i, iout, iin, count;
    TERMINAL_DIRECTION *ptd = NULL, td;
    LONG *pmedia = NULL, media;
    BSTR *pbstr = NULL, bstr=NULL;
    ITTerminal **ppterminal = NULL;

    BOOL bmatch;
    MSPEVENTITEM *pevent = NULL;

    count = m_Terminals.GetSize ();
    
    if (count > 0)
    {
        ptd = new TERMINAL_DIRECTION[count];
        pmedia = new LONG[count];
        pbstr = new BSTR[count];
        ppterminal = new ITTerminal* [count];

        if (ptd == NULL || pmedia == NULL || pbstr == NULL || ppterminal == NULL)
        {
            LOG ((MSP_ERROR, "%s out of memory", __fxName));
            hr = E_OUTOFMEMORY;

            if (ptd) delete [] ptd;
            if (pmedia) delete [] pmedia;
            if (pbstr) delete [] pbstr;
            if (ppterminal) delete [] ppterminal;

            return hr;
        }

        memset (pbstr, 0, count * sizeof(BSTR));
        memset (ppterminal, 0, count * sizeof(ITTerminal*));
        
    }

    // for each old terminal, record
    for (i = 0; i < count; i++)
    {
        if (FAILED (hr = m_Terminals[i]->get_Direction (&ptd[i])))
        {
            LOG ((MSP_ERROR, "%s failed to get terminal direction. %x", __fxName, hr));
            goto Cleanup;
        }

        if (FAILED (hr = m_Terminals[i]->get_MediaType (&pmedia[i])))
        {
            LOG ((MSP_ERROR, "%s failed to get terminal mediatype. %x", __fxName, hr));
            goto Cleanup;
        }

        if (FAILED (hr = m_Terminals[i]->get_Name (&pbstr[i])))
        {
            LOG ((MSP_ERROR, "%s failed to get terminal name. %x", __fxName, hr));
            goto Cleanup;
        }

        m_Terminals[i]->AddRef ();
        ppterminal[i] = m_Terminals[i];
    }

    // if we release terminal inside previous loop: recording info
    // we would only release some terminals if there is an error
    for (i = 0; i < count; i++)
    {
        m_Terminals[i]->Release ();
    }
    m_Terminals.RemoveAll ();

    // update terminal list
    /*
    if (FAILED (hr = UpdateTerminalList ()))
    {
        LOG ((MSP_ERROR, "%s failed to update terminal list. %x", __fxName, hr));
        goto Cleanup;
    }
    */

    // copy UpdateTerminalList () here
    CreateAudioCaptureTerminals();
    CreateAudioRenderTerminals();
    CreateVideoCaptureTerminals();

    m_fTerminalsUpToDate = TRUE;

    // fire event to tapi app
    if (bDeviceArrival)
    {
        // outer loop each new terminal
        for (iout = 0; iout < m_Terminals.GetSize (); iout++)
        {
            if (FAILED (hr = m_Terminals[iout]->get_Direction (&td)))
            {
                LOG ((MSP_ERROR, "%s failed to get terminal direction. %x", __fxName, hr));
                goto Cleanup;
            }

            if (FAILED (hr = m_Terminals[iout]->get_MediaType (&media)))
            {
                LOG ((MSP_ERROR, "%s failed to get terminal type. %x", __fxName, hr));
                goto Cleanup;
            }

            if (FAILED (hr = m_Terminals[iout]->get_Name (&bstr)))
            {
                LOG ((MSP_ERROR, "%s failed to get terminal name. %x", __fxName, hr));
                goto Cleanup;
            }

            // inner loop check if the terminal is new
            for (iin = 0, bmatch = FALSE; iin < count; iin ++)
            {
                if (td == ptd[iin] && 
                    media == pmedia[iin] &&
                    0 == wcscmp (bstr, pbstr[iin]))
                {
                    bmatch = TRUE;
                    break;
                }
            }
            
            // fire event if not match
            if (!bmatch)
            {
                LOG ((MSP_TRACE, "%s: new device found. name %ws, td %d, media %d",
                      __fxName, bstr, td, media));

                pevent = AllocateEventItem();
                if (pevent == NULL)
                {
                    LOG ((MSP_ERROR, "%s failed to new msp event item", __fxName));
                    hr = E_OUTOFMEMORY;
                    goto Cleanup;
                }

                m_Terminals[iout]->AddRef ();
                
                pevent->MSPEventInfo.dwSize = sizeof(MSP_EVENT_INFO);
                pevent->MSPEventInfo.Event = ME_ADDRESS_EVENT;
                pevent->MSPEventInfo.MSP_ADDRESS_EVENT_INFO.Type = ADDRESS_TERMINAL_AVAILABLE;
                pevent->MSPEventInfo.MSP_ADDRESS_EVENT_INFO.pTerminal = m_Terminals[iout];

                if (FAILED (hr = PostEvent (pevent)))
                {
                    LOG ((MSP_ERROR, "%s failed to post event. %x", __fxName, hr));
                    
                    m_Terminals[iout]->Release ();
                    FreeEventItem(pevent);
                    pevent = NULL;

                    // we don't return, try next device
                }                
            } // outer loop
            
            SysFreeString (bstr);
            bstr = NULL;
        }
    }
    else // if (bDeviceArrival)
    {
        // outer loop each old device
        for (iout = 0; iout < count; iout++)
        {
            // inner loop check if the device was removed
            for (iin = 0, bmatch = FALSE; iin < m_Terminals.GetSize (); iin++)
            {
                if (FAILED (hr = m_Terminals[iin]->get_Direction (&td)))
                {
                    LOG ((MSP_ERROR, "%s failed to get terminal direction. %x", __fxName, hr));
                    goto Cleanup;
                }
                
                if (FAILED (hr = m_Terminals[iin]->get_MediaType (&media)))
                {
                    LOG ((MSP_ERROR, "%s failed to get terminal type. %x", __fxName, hr));
                    goto Cleanup;
                }

                if (FAILED (hr = m_Terminals[iin]->get_Name (&bstr)))
                {
                    LOG ((MSP_ERROR, "%s failed to get terminal name. %x", __fxName, hr));
                    goto Cleanup;
                }
                    
                if (td == ptd[iout] && 
                    media == pmedia[iout] &&
                    0 == wcscmp (bstr, pbstr[iout]))
                {
                    SysFreeString (bstr);
                    bstr = NULL;
                        
                    bmatch = TRUE;
                    break;
                }
                    
                SysFreeString (bstr);
                bstr = NULL;
            }                
            
            // fire event if not match
            if (!bmatch)
            {
                LOG ((MSP_TRACE, "%s: device removed. name %ws, td %d, media %d",
                  __fxName, pbstr[iout], ptd[iout], pmedia[iout]));

                pevent = AllocateEventItem();
                if (pevent == NULL)
                {
                    LOG ((MSP_ERROR, "%s failed to new msp event item", __fxName));
                    hr = E_OUTOFMEMORY;
                    goto Cleanup;
                }

                ppterminal[iout]->AddRef ();
                
                pevent->MSPEventInfo.dwSize = sizeof(MSP_EVENT_INFO);
                pevent->MSPEventInfo.Event = ME_ADDRESS_EVENT;
                pevent->MSPEventInfo.MSP_ADDRESS_EVENT_INFO.Type = ADDRESS_TERMINAL_UNAVAILABLE;
                pevent->MSPEventInfo.MSP_ADDRESS_EVENT_INFO.pTerminal = ppterminal[iout];

                if (FAILED (hr = PostEvent (pevent)))
                {
                    LOG ((MSP_ERROR, "%s failed to post event. %x", __fxName, hr));
                    
                    ppterminal[iout]->Release ();
                    FreeEventItem(pevent);
                    pevent = NULL;

                    // we don't return, try next device
                }
            }
        } // outer loop
    }

Cleanup:
    if (bstr) SysFreeString (bstr);

    if (count > 0)
    {
        delete [] ptd;
        delete [] pmedia;
        
        for (i = 0; i < count; i++)
        {
            if (pbstr[i]) SysFreeString (pbstr[i]);
            if (ppterminal[i]) ppterminal[i]->Release ();
        }

        delete [] pbstr;
        delete [] ppterminal;
     }

    LOG ((MSP_TRACE, "%s returns", __fxName));

    return hr;
}

STDMETHODIMP CIPConfMSP::CreateTerminal(
    IN      BSTR                pTerminalClass,
    IN      long                lMediaType,
    IN      TERMINAL_DIRECTION  Direction,
    OUT     ITTerminal **       ppTerminal
    )
/*++

Routine Description:

This method is called by TAPI3 to create a dynamic terminal. It asks the 
terminal manager to create a dynamic terminal. 

Arguments:

iidTerminalClass
    IID of the terminal class to be created.

dwMediaType
    TAPI media type of the terminal to be created.

Direction
    Terminal direction of the terminal to be created.

ppTerminal
    Returned created terminal object
    
Return Value:

S_OK

E_OUTOFMEMORY
TAPI_E_INVALIDMEDIATYPE
TAPI_E_INVALIDTERMINALDIRECTION
TAPI_E_INVALIDTERMINALCLASS

--*/
{
    ENTER_FUNCTION("CIPConfMSP::CreateTerminal");
    LOG((MSP_TRACE, "%s - enter", __fxName));

    //
    // Check if initialized.
    //

    // lock the event related data
    m_EventDataLock.Lock();

    if ( m_htEvent == NULL )
    {
        // unlock the event related data
        m_EventDataLock.Unlock();

        LOG((MSP_ERROR,
            "%s, not initialized - returning E_UNEXPECTED", __fxName));
        return E_UNEXPECTED;
    }

    // unlock the event related data
    m_EventDataLock.Unlock();

    //
    // Get the IID from the BSTR representation.
    //

    HRESULT hr;
    IID     iidTerminalClass;

    hr = CLSIDFromString(pTerminalClass, &iidTerminalClass);

    if ( FAILED(hr) )
    {
        LOG((MSP_ERROR, "%s, bad CLSID string", __fxName));
        return E_INVALIDARG;
    }

    //
    // Make sure we support the requested media type.
    // The terminal manager checks the terminal class, terminal direction, 
    // and return pointer.
    //

    //
    // we don't have any specific req's to terminal's media type. 
    // termmgr will check if the media type is valid at all.
    //

    //
    // Use the terminal manager to create the dynamic terminal.
    //

    _ASSERTE( m_pITTerminalManager != NULL );

    hr = m_pITTerminalManager->CreateDynamicTerminal(NULL,
                                                     iidTerminalClass,
                                                     (DWORD) lMediaType,
                                                     Direction,
                                                     (MSP_HANDLE) this,
                                                     ppTerminal);

    if ( FAILED(hr) )
    {
        LOG((MSP_ERROR, 
            "%s create dynamic terminal failed. hr=%x", __fxName, hr));

        return hr;
    }

    const DWORD dwAudioCaptureBitPerSample    = 16;  
    const DWORD dwAudioSampleRate             = 8000;  

    if ((iidTerminalClass == CLSID_MediaStreamTerminal)
        && (lMediaType == TAPIMEDIATYPE_AUDIO))
    {
        // Set the format of the audio to 8KHZ, 16Bit/Sample, MONO.
        hr = ::SetAudioFormat(
            *ppTerminal, 
            dwAudioCaptureBitPerSample, 
            dwAudioSampleRate
            );

        if (FAILED(hr))
        {
            LOG((MSP_WARN, "%s, can't set audio format, %x", __fxName, hr));
        }
    }

    LOG((MSP_TRACE, "%s - exit S_OK", __fxName));

    return S_OK;
}

STDMETHODIMP CIPConfMSP::CreateMSPCall(
    IN      MSP_HANDLE          htCall,
    IN      DWORD               dwReserved,
    IN      DWORD               dwMediaType,
    IN      IUnknown *          pOuterUnknown,
    OUT     IUnknown **         ppMSPCall
    )
/*++

Routine Description:

This method is called by TAPI3 before a call is made or answered. It creates 
a aggregated MSPCall object and returns the IUnknown pointer. It calls the
helper template function defined in mspaddress.h to handle the real creation.

Arguments:

htCall
    TAPI 3.0's identifier for this call.  Returned in events passed back 
    to TAPI.

dwReserved
    Reserved parameter.  Not currently used.

dwMediaType
    Media type of the call being created.  These are TAPIMEDIATYPES and more 
    than one mediatype can be selected (bitwise).

pOuterUnknown
    pointer to the IUnknown interface of the containing object.

ppMSPCall
    Returned MSP call that the MSP fills on on success.
    
Return Value:

    S_OK
    E_OUTOFMEMORY
    E_POINTER
    TAPI_E_INVALIDMEDIATYPE


--*/
{
    LOG((MSP_TRACE, 
        "CreateMSPCall entered. htCall:%x, dwMediaType:%x, ppMSPCall:%x",
        htCall, dwMediaType, ppMSPCall
        ));

    CIPConfMSPCall * pMSPCall = NULL;

    HRESULT hr = ::CreateMSPCallHelper(
        this, 
        htCall, 
        dwReserved, 
        dwMediaType, 
        pOuterUnknown, 
        ppMSPCall,
        &pMSPCall
        );

    if (FAILED(hr))
    {
        LOG((MSP_ERROR, "CreateMSPCallHelper failed:%x", hr));
        return hr;
    }

    // this function doesn't return anything.
    pMSPCall->SetIPInterface(m_dwIPInterface);

    return hr;
}

STDMETHODIMP CIPConfMSP::ShutdownMSPCall(
    IN      IUnknown *   pUnknown
    )
/*++

Routine Description:

This method is called by TAPI3 to shutdown a MSPCall. It calls the helper
function defined in MSPAddress to to the real job.

Arguments:

pUnknown
    pointer to the IUnknown interface of the contained object. It is a
    CComAggObject that contains our call object.
    
Return Value:

    S_OK
    E_POINTER
    TAPI_E_INVALIDMEDIATYPE


--*/
{
    LOG((MSP_TRACE, "ShutDownMSPCall entered. pUnknown:%x", pUnknown));

    if (IsBadReadPtr(pUnknown, sizeof(VOID *) * 3))
    {
        LOG((MSP_ERROR, "ERROR:pUnknow is a bad pointer"));
        return E_POINTER;
    }

    
    CIPConfMSPCall * pMSPCall = NULL;
    HRESULT hr = ::ShutdownMSPCallHelper(pUnknown, &pMSPCall);
    
    if (FAILED(hr))
    {
        LOG((MSP_ERROR, "ShutDownMSPCallhelper failed:: %x", hr));
        return hr;
    }

    return hr;
}

DWORD CIPConfMSP::GetCallMediaTypes(void)
{
    return IPCONFCALLMEDIATYPES;
}

ULONG CIPConfMSP::MSPAddressAddRef(void)
{
    return MSPAddRefHelper(this);
}

ULONG CIPConfMSP::MSPAddressRelease(void)
{
    return MSPReleaseHelper(this);
}

#ifdef USEIPADDRTABLE
PMIB_IPADDRTABLE GetIPTable()
/*++

Routine Description:

This method is used to get the table of local IP interfaces.

Arguments:

Return Value:

    NULL - failed.
    Pointer - a memory buffer that contains the IP interface table.


--*/
{
    // dynamically load iphlpapi.dll
    HMODULE hIPHLPAPI = LoadLibraryW(IPHLPAPI_DLL);

    // validate handle
    if (hIPHLPAPI == NULL) 
    {
        LOG((MSP_ERROR, "could not load %s.\n", IPHLPAPI_DLL));
        // failure
        return NULL;
    }

    PFNGETIPADDRTABLE pfnGetIpAddrTable = NULL;

    // retrieve function pointer to retrieve addresses
    pfnGetIpAddrTable = (PFNGETIPADDRTABLE)GetProcAddress(
                                                hIPHLPAPI, 
                                                GETIPADDRTABLE
                                                );

    // validate function pointer
    if (pfnGetIpAddrTable == NULL) 
    {
        LOG((MSP_ERROR, "could not resolve GetIpAddrTable.\n"));
        // release
        FreeLibrary(hIPHLPAPI);
        // failure
        return NULL;
    }

    PMIB_IPADDRTABLE pIPAddrTable = NULL;
    DWORD dwBytesRequired = 0;
    DWORD dwStatus;

    // determine amount of memory needed for table
    dwStatus = (*pfnGetIpAddrTable)(pIPAddrTable, &dwBytesRequired, FALSE);

    // validate status is what we expect
    if (dwStatus != ERROR_INSUFFICIENT_BUFFER) 
    {
        LOG((MSP_ERROR, "error 0x%08lx calling GetIpAddrTable.\n", dwStatus));
        // release
        FreeLibrary(hIPHLPAPI);
        // failure, but we need to return true to load.
        return NULL;
    }
        
    // attempt to allocate memory for table
    pIPAddrTable = (PMIB_IPADDRTABLE)malloc(dwBytesRequired);

    // validate pointer
    if (pIPAddrTable == NULL) 
    {
        LOG((MSP_ERROR, "could not allocate address table.\n"));
        // release
        FreeLibrary(hIPHLPAPI);
        // failure, but we need to return true to load.
        return NULL;
    }

    // retrieve ip address table from tcp/ip stack via utitity library
    dwStatus = (*pfnGetIpAddrTable)(pIPAddrTable, &dwBytesRequired, FALSE);    

    // validate status
    if (dwStatus != NOERROR) 
    {
        LOG((MSP_ERROR, "error 0x%08lx calling GetIpAddrTable.\n", dwStatus));
        // release table
        free(pIPAddrTable);
        // release
        FreeLibrary(hIPHLPAPI);
        // failure, but we need to return true to load. 
        return NULL;
    }
        
    // release library
    FreeLibrary(hIPHLPAPI);

    return pIPAddrTable;
}

BSTR IPToBstr(
    DWORD dwIP
    )
{
    struct in_addr Addr;
    Addr.s_addr = dwIP;
    
    // convert the interface to a string.
    CHAR *pChar = inet_ntoa(Addr);
    if (pChar == NULL)
    {
        LOG((MSP_ERROR, "bad IP address:%x", dwIP));
        return NULL;
    }

    // convert the ascii string to WCHAR.
    WCHAR szAddressName[MAXIPADDRLEN + 1];
    wsprintfW(szAddressName, L"%hs", pChar);

    // create a BSTR.
    BSTR bAddress = SysAllocString(szAddressName);
    if (bAddress == NULL)
    {
        LOG((MSP_ERROR, "out of mem in allocation address name"));
        return NULL;
    }

    return bAddress;
}

STDMETHODIMP CIPConfMSP::get_DefaultIPInterface(
    OUT     BSTR *         ppIPAddress
    )
{
    LOG((MSP_TRACE, "get_DefaultIPInterface, ppIPAddress:%p", ppIPAddress));

    if (IsBadWritePtr(ppIPAddress, sizeof(BSTR)))
    {
        LOG((MSP_ERROR, 
            "get_DefaultIPInterface, ppIPAddress is bad:%p", ppIPAddress));
        return E_POINTER;
    }

    // get the current local interface.
    m_Lock.Lock();
    DWORD dwIP= m_dwIPInterface;
    m_Lock.Unlock();

    BSTR bAddress = IPToBstr(dwIP);

    if (bAddress == NULL)
    {
        return E_OUTOFMEMORY;
    }

    *ppIPAddress = bAddress;

    LOG((MSP_TRACE, "get_DefaultIPInterface, returning %ws", bAddress));

    return S_OK;
}

STDMETHODIMP CIPConfMSP::put_DefaultIPInterface(
    IN      BSTR            pIPAddress
    )
{
    LOG((MSP_TRACE, "put_DefaultIPInterface, pIPAddress:%p", pIPAddress));

    if (IsBadStringPtrW(pIPAddress, MAXIPADDRLEN))
    {
        LOG((MSP_ERROR, 
            "put_DefaultIPInterface, invalid pointer:%p", pIPAddress));
        return E_POINTER;
    }

    char buffer[MAXIPADDRLEN + 1];

    if (WideCharToMultiByte(
        GetACP(),
        0,
        pIPAddress,
        -1,
        buffer,
        MAXIPADDRLEN,
        NULL,
        NULL
        ) == 0)
    {
        LOG((MSP_ERROR, "put_DefaultIPInterface, can't covert:%ws", pIPAddress));
        return E_INVALIDARG;
    }

    DWORD dwAddr;
    if ((dwAddr = inet_addr(buffer)) == INADDR_NONE)
    {
        LOG((MSP_ERROR, "put_DefaultIPInterface, bad address:%s", buffer));
        return E_INVALIDARG;
    }

    // set the current local interface.
    m_Lock.Lock();
    m_dwIPInterface = dwAddr;
    m_Lock.Unlock();


    LOG((MSP_TRACE, "put_DefaultIPInterface, set to %s", buffer));

    return S_OK;
}

HRESULT CreateBstrCollection(
    IN  BSTR  *     pBstr,
    IN  DWORD       dwCount,
    OUT VARIANT *   pVariant
    )
{
    //
    // create the collection object - see mspcoll.h
    //

    CComObject<CTapiBstrCollection> * pCollection;

    HRESULT hr;

    hr = ::CreateCComObjectInstance(&pCollection);

    if ( FAILED(hr) )
    {
        LOG((MSP_ERROR, "get_IPInterfaces - "
            "can't create collection - exit 0x%08x", hr));

        return hr;
    }

    //
    // get the Collection's IDispatch interface
    //

    IDispatch * pDispatch;

    hr = pCollection->_InternalQueryInterface(__uuidof(IDispatch),
                                              (void **) &pDispatch );

    if ( FAILED(hr) )
    {
        LOG((MSP_ERROR, "get_IPInterfaces - "
            "QI for IDispatch on collection failed - exit 0x%08x", hr));

        delete pCollection;

        return hr;
    }

    //
    // Init the collection using an iterator -- pointers to the beginning and
    // the ending element plus one.
    //

    hr = pCollection->Initialize( dwCount,
                                  pBstr,
                                  pBstr + dwCount);

    if ( FAILED(hr) )
    {
        LOG((MSP_ERROR, "get_IPInterfaces - "
            "Initialize on collection failed - exit 0x%08x", hr));
        
        pDispatch->Release();

        return hr;
    }

    //
    // put the IDispatch interface pointer into the variant
    //

    LOG((MSP_ERROR, "get_IPInterfaces - "
        "placing IDispatch value %08x in variant", pDispatch));

    VariantInit(pVariant);
    pVariant->vt = VT_DISPATCH;
    pVariant->pdispVal = pDispatch;

    LOG((MSP_TRACE, "get_IPInterfaces - exit S_OK"));
 
    return S_OK;
}


STDMETHODIMP CIPConfMSP::get_IPInterfaces(
    OUT     VARIANT *       pVariant
    )
{
    PMIB_IPADDRTABLE pIPAddrTable = GetIPTable();

    if (pIPAddrTable == NULL)
    {
        return E_FAIL;
    }

    BSTR *Addresses = 
        (BSTR *)malloc(sizeof(BSTR *) * pIPAddrTable->dwNumEntries);
    
    if (Addresses == NULL)
    {
        return E_OUTOFMEMORY;
    }

    HRESULT hr = S_OK;
    DWORD dwCount = 0;

    // loop through the interfaces and find the valid ones.
    for (DWORD i = 0; i < pIPAddrTable->dwNumEntries; i++) 
    {
        if (IsValidInterface(pIPAddrTable->table[i].dwAddr))
        {
            DWORD dwIPAddr   = ntohl(pIPAddrTable->table[i].dwAddr);
            Addresses[i] = IPToBstr(dwIPAddr);
            if (Addresses[i] == NULL)
            {
                hr = E_OUTOFMEMORY;
                break;
            }

            dwCount ++;
        }
    }

    // release table memory 
    free(pIPAddrTable);

    if (FAILED(hr))
    {
        // release all the BSTRs and the array.
        for (i = 0; i < dwCount; i ++)
        {
            SysFreeString(Addresses[i]);
        }
        free(Addresses);
        return hr;
    }

    hr = CreateBstrCollection(Addresses, dwCount, pVariant);

    // if the collection is not created, release all the BSTRs.
    if (FAILED(hr))
    {
        for (i = 0; i < dwCount; i ++)
        {
            SysFreeString(Addresses[i]);
        }
    }

    // delete the pointer array.
    free(Addresses);

    return hr;
}

HRESULT CreateBstrEnumerator(
    IN  BSTR *                  begin,
    IN  BSTR *                  end,
    OUT IEnumBstr **           ppIEnum
    )
{
typedef CSafeComEnum<IEnumBstr, &__uuidof(IEnumBstr), BSTR, _CopyBSTR>> CEnumerator;

    HRESULT hr;

    CComObject<CEnumerator> *pEnum = NULL;

    hr = ::CreateCComObjectInstance(&pEnum);

    if (pEnum == NULL)
    {
        LOG((MSP_ERROR, "Could not create enumerator object, %x", hr));
        return hr;
    }

    IEnumBstr * pIEnum;

    // query for the __uuidof(IEnumDirectory) i/f
    hr = pEnum->_InternalQueryInterface(
        __uuidof(IEnumBstr),
        (void**)&pIEnum
        );

    if (FAILED(hr))
    {
        LOG((MSP_ERROR, "query enum interface failed, %x", hr));
        delete pEnum;
        return hr;
    }

    hr = pEnum->Init(begin, end, NULL, AtlFlagTakeOwnership);

    if (FAILED(hr))
    {
        LOG((MSP_ERROR, "init enumerator object failed, %x", hr));
        pIEnum->Release();
        return hr;
    }

    *ppIEnum = pIEnum;

    return hr;
}

STDMETHODIMP CIPConfMSP::EnumerateIPInterfaces(
    OUT     IEnumBstr **   ppIEnumBstr
    )
{
    PMIB_IPADDRTABLE pIPAddrTable = GetIPTable();

    if (pIPAddrTable == NULL)
    {
        return E_FAIL;
    }

    BSTR *Addresses = 
        (BSTR *)malloc(sizeof(BSTR *) * pIPAddrTable->dwNumEntries);
    
    if (Addresses == NULL)
    {
        return E_OUTOFMEMORY;
    }

    HRESULT hr = S_OK;
    DWORD dwCount = 0;

    // loop through the interfaces and find the valid ones.
    for (DWORD i = 0; i < pIPAddrTable->dwNumEntries; i++) 
    {
        if (IsValidInterface(pIPAddrTable->table[i].dwAddr))
        {
            DWORD dwIPAddr   = ntohl(pIPAddrTable->table[i].dwAddr);
            Addresses[i] = IPToBstr(dwIPAddr);
            if (Addresses[i] == NULL)
            {
                hr = E_OUTOFMEMORY;
                break;
            }

            dwCount ++;
        }
    }

    // release table memory 
    free(pIPAddrTable);

    if (FAILED(hr))
    {
        // release all the BSTRs and the array.
        for (i = 0; i < dwCount; i ++)
        {
            SysFreeString(Addresses[i]);
        }
        free(Addresses);
        return hr;
    }

    hr = CreateBstrEnumerator(Addresses, Addresses + dwCount, ppIEnumBstr);

    // if the collection is not created, release all the BSTRs.
    if (FAILED(hr))
    {
        for (i = 0; i < dwCount; i ++)
        {
            SysFreeString(Addresses[i]);
        }
        free(Addresses);
        return hr;
    }

    // the enumerator will destroy the bstr array eventually,
    // so no need to free anything here. Even if we tell it to hand
    // out zero objects, it will delete the array on destruction.

    return hr;
}
#endif