//---------------------------------------------------------------------------
//
//  Module:   fn.cpp
//
//  Description:
//
//
//@@BEGIN_MSINTERNAL
//  Development Team:
//     Mike McLaughlin
//
//  History:   Date   Author      Comment
//
//  To Do:     Date   Author      Comment
//
//@@END_MSINTERNAL
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  Copyright (c) 1996-1999 Microsoft Corporation.  All Rights Reserved.
//
//---------------------------------------------------------------------------

#include "common.h"

EXTERN_C VOID KeAttachProcess(PVOID);
EXTERN_C VOID KeDetachProcess(VOID);

//---------------------------------------------------------------------------
//---------------------------------------------------------------------------

PLIST_FILTER_NODE gplstFilterNode = NULL;
PLIST_MULTI_LOGICAL_FILTER_NODE gplstLogicalFilterNode = NULL;

//---------------------------------------------------------------------------
//---------------------------------------------------------------------------

NTSTATUS
InitializeFilterNode()
{
    if(gplstFilterNode == NULL) {
    gplstFilterNode = new LIST_FILTER_NODE;
    if(gplstFilterNode == NULL) {
        return(STATUS_INSUFFICIENT_RESOURCES);
    }
    }
    if(gplstLogicalFilterNode == NULL) {
    gplstLogicalFilterNode = new LIST_MULTI_LOGICAL_FILTER_NODE;
    if(gplstLogicalFilterNode == NULL) {
        return(STATUS_INSUFFICIENT_RESOURCES);
    }
    }
    return(STATUS_SUCCESS);
}

#pragma PAGEABLE_CODE
#pragma PAGEABLE_DATA

VOID
UninitializeFilterNode()
{
    PFILTER_NODE pFilterNode;

    FOR_EACH_LIST_ITEM_DELETE(gplstFilterNode, pFilterNode) {
        if (pFilterNode->pDeviceNode) {
            pFilterNode->pDeviceNode->pFilterNode=NULL;
        }
        delete pFilterNode;
    DELETE_LIST_ITEM(gplstFilterNode);
    } END_EACH_LIST_ITEM

    delete gplstFilterNode;
    gplstFilterNode = NULL;
    delete gplstLogicalFilterNode;
    gplstLogicalFilterNode = NULL;
}

//---------------------------------------------------------------------------

CFilterNode::CFilterNode(
    ULONG fulType
)
{
    ASSERT(gplstFilterNode != NULL);
    SetType(fulType);
    AddListEnd(gplstFilterNode);
    DPF2(60, "CFilterNode: %08x %s", this, DumpName());
}

CFilterNode::~CFilterNode(
)
{
    PFILTER_NODE pFilterNode;

    Assert(this);
    DPF2(60, "~CFilterNode: %08x %s", this, DumpName());
    RemoveListCheck();
    if (pDeviceNode) {
        pDeviceNode->pFilterNode = NULL;
    }
    if (pFileObject) {
        ::ObDereferenceObject(pFileObject);
        pFileObject = NULL;
    }
    delete pDeviceNode;

    FOR_EACH_LIST_ITEM(gplstFilterNode, pFilterNode) {
    pFilterNode->lstConnectedFilterNode.RemoveList(this);
    } END_EACH_LIST_ITEM

    // Free all other memory
    lstFreeMem.EnumerateList(CListDataItem::Destroy);
}

NTSTATUS
CFilterNode::Create(
    PWSTR pwstrDeviceInterface
)
{
    PFILTER_NODE_INSTANCE pFilterNodeInstance = NULL;
    PKEY_VALUE_FULL_INFORMATION pkvfi = NULL;
    NTSTATUS Status = STATUS_SUCCESS;
    HANDLE hkeyDeviceClass = NULL;
    HANDLE hkeySysaudio = NULL;
    UNICODE_STRING ustrFilterName;
    KSPROPERTY PropertyComponentId;
    KSCOMPONENTID ComponentId;
    ULONG BytesReturned;
    PFILE_OBJECT pFileObject;


    this->pwstrDeviceInterface = new WCHAR[wcslen(pwstrDeviceInterface) + 1];
    if(this->pwstrDeviceInterface == NULL) {
    Status = STATUS_INSUFFICIENT_RESOURCES;
    goto exit;
    }
    wcscpy(this->pwstrDeviceInterface, pwstrDeviceInterface);

    Status = lstFreeMem.AddList(this->pwstrDeviceInterface);
    if(!NT_SUCCESS(Status)) {
    Trap();
    delete this->pwstrDeviceInterface;
    goto exit;
    }

    Status = CFilterNodeInstance::Create(&pFilterNodeInstance, this);
    if(!NT_SUCCESS(Status)) {
    goto exit;
    }
    pFileObject = pFilterNodeInstance->pFileObject;

    // Get the filter's friendly name
    RtlInitUnicodeString(&ustrFilterName, this->pwstrDeviceInterface);

    Status = IoOpenDeviceInterfaceRegistryKey(
      &ustrFilterName,
      KEY_READ,
      &hkeyDeviceClass);

    if(!NT_SUCCESS(Status)) {
        goto exit;
    }

    Status = OpenRegistryKey(L"Sysaudio", &hkeySysaudio, hkeyDeviceClass);
    if(NT_SUCCESS(Status)) {
    Status = QueryRegistryValue(hkeySysaudio, L"Disabled", &pkvfi);
    if(NT_SUCCESS(Status)) {
        if(pkvfi->Type == REG_DWORD) {
        if(*((PULONG)(((PUCHAR)pkvfi) + pkvfi->DataOffset))) {
            Status = STATUS_SERVER_DISABLED;
            delete pkvfi;
            goto exit;
        }
        }
        delete pkvfi;
    }
    Status = QueryRegistryValue(hkeySysaudio, L"Device", &pkvfi);
    if(NT_SUCCESS(Status)) {
        if(pkvfi->Type == REG_SZ && pkvfi->DataLength > 0) {
        Status = lstFreeMem.AddList(pkvfi);
        if(!NT_SUCCESS(Status)) {
            Trap();
            delete pkvfi;
            goto exit;
        }
        Status = AddDeviceInterfaceMatch(
          (PWSTR)(((PUCHAR)pkvfi) + pkvfi->DataOffset));

        if(!NT_SUCCESS(Status)) {
            Trap();
            delete pkvfi;
            goto exit;
        }
        }
        else {
        delete pkvfi;
        }
    }

    Status = QueryRegistryValue(hkeySysaudio, L"Order", &pkvfi);
    if(NT_SUCCESS(Status)) {
        if(pkvfi->Type == REG_DWORD) {
        ulOrder = *((PULONG)(((PUCHAR)pkvfi) + pkvfi->DataOffset));
        }
        delete pkvfi;
    }

    Status = QueryRegistryValue(hkeySysaudio, L"Capture", &pkvfi);
    if(NT_SUCCESS(Status)) {
        if(pkvfi->Type == REG_DWORD) {
        if(*((PULONG)(((PUCHAR)pkvfi) + pkvfi->DataOffset))) {
            ulFlags |= FN_FLAGS_CAPTURE;
        }
        else {
            ulFlags |= FN_FLAGS_NO_CAPTURE;
        }
        }
        delete pkvfi;
    }

    Status = QueryRegistryValue(hkeySysaudio, L"Render", &pkvfi);
    if(NT_SUCCESS(Status)) {
        if(pkvfi->Type == REG_DWORD) {
        if(*((PULONG)(((PUCHAR)pkvfi) + pkvfi->DataOffset))) {
            ulFlags |= FN_FLAGS_RENDER;
        }
        else {
            ulFlags |= FN_FLAGS_NO_RENDER;
        }
        }
        delete pkvfi;
    }
    }

    Status = QueryRegistryValue(hkeyDeviceClass, L"FriendlyName", &pkvfi);
    if(NT_SUCCESS(Status)) {
    if(pkvfi->Type == REG_SZ && pkvfi->DataLength > 0) {
        Status = lstFreeMem.AddList(pkvfi);
        if(!NT_SUCCESS(Status)) {
        Trap();
        delete pkvfi;
        goto exit;
        }
        pwstrFriendlyName = (PWSTR)(((PUCHAR)pkvfi) + pkvfi->DataOffset);
    }
    else {
        delete pkvfi;
    }
    }

    // Get the component Id of the filter
    PropertyComponentId.Set = KSPROPSETID_General;
    PropertyComponentId.Id = KSPROPERTY_GENERAL_COMPONENTID;
    PropertyComponentId.Flags = KSPROPERTY_TYPE_GET;

    Status = KsSynchronousIoControlDevice(
      pFileObject,
      KernelMode,
      IOCTL_KS_PROPERTY,
      &PropertyComponentId,
      sizeof(PropertyComponentId),
      &ComponentId,
      sizeof(ComponentId),
      &BytesReturned);

    // Store the component Id
    if (NT_SUCCESS(Status)) {

        ASSERT(BytesReturned >= sizeof(ComponentId));

        this->ComponentId = new KSCOMPONENTID;
        if (this->ComponentId) {

            RtlCopyMemory(this->ComponentId,
                          &ComponentId,
                          sizeof(KSCOMPONENTID));

            Status = lstFreeMem.AddList(this->ComponentId);
            if(!NT_SUCCESS(Status)) {
                delete this->ComponentId;
                this->ComponentId = NULL;
            }
        }
    }
    else {
        this->ComponentId = NULL;
    }

    Status = this->ProfileFilter(pFileObject);
exit:
    if(hkeySysaudio != NULL) {
        ZwClose(hkeySysaudio);
    }
    if(hkeyDeviceClass != NULL) {
        ZwClose(hkeyDeviceClass);
    }
    if (pFilterNodeInstance) {
        pFilterNodeInstance->Destroy();
    }
    return(Status);
}

NTSTATUS
CFilterNode::ProfileFilter(
    PFILE_OBJECT pFileObject
)
{
    NTSTATUS Status = STATUS_SUCCESS;
    PKSMULTIPLE_ITEM pCategories = NULL;
    PKSMULTIPLE_ITEM pConnections = NULL;
    PKSMULTIPLE_ITEM pNodes = NULL;
    ULONG PinId;
    PPIN_INFO pPinInfo;
    ULONG i;
    KSTOPOLOGY Topology;

    RtlZeroMemory(&Topology, sizeof(Topology));

    // Get the number of pins
    Status = GetPinProperty(
      pFileObject,
      KSPROPERTY_PIN_CTYPES,
      0,			// doesn't matter for ctypes
      sizeof(cPins),
      (PVOID)&cPins);

    if(!NT_SUCCESS(Status)) {
	Trap();
	goto exit;
    }

    // Get the topology of the filter
    Status = GetProperty(
      pFileObject,
      &KSPROPSETID_Topology,
      KSPROPERTY_TOPOLOGY_CATEGORIES,
      0,
      NULL,
      (PVOID*)&pCategories);

    if(!NT_SUCCESS(Status)) {
	Trap();
	goto exit;
    }

    if(pCategories != NULL) {
	Topology.CategoriesCount = pCategories->Count;
	Topology.Categories = (GUID*)(pCategories + 1);

	ULONG fulType = 0;
	for(i = 0; i < pCategories->Count; i++) {
	    GetFilterTypeFromGuid((LPGUID)&Topology.Categories[i], &fulType);
	}
	SetType(fulType);
    }

    Status = GetProperty(
      pFileObject,
      &KSPROPSETID_Topology,
      KSPROPERTY_TOPOLOGY_NODES,
      0,
      NULL,
      (PVOID*)&pNodes);

    if(!NT_SUCCESS(Status)) {
	Trap();
	goto exit;
    }

    if(pNodes != NULL) {
	Status = lstFreeMem.AddList(pNodes);
	if(!NT_SUCCESS(Status)) {
	    Trap();
	    delete pNodes;
	    goto exit;
	}
	Topology.TopologyNodesCount = pNodes->Count;
	Topology.TopologyNodes = (GUID*)(pNodes + 1);
    }

    Status = GetProperty(
      pFileObject,
      &KSPROPSETID_Topology,
      KSPROPERTY_TOPOLOGY_CONNECTIONS,
      0,
      NULL,
      (PVOID*)&pConnections);

    if(!NT_SUCCESS(Status)) {
	Trap();
	goto exit;
    }

    if(pConnections != NULL) {
	Topology.TopologyConnectionsCount = pConnections->Count;
	Topology.TopologyConnections = 
	  (PKSTOPOLOGY_CONNECTION)(pConnections + 1);
    }

    // Each pin loop
    for(PinId = 0; PinId < cPins; PinId++) {
	pPinInfo = new PIN_INFO(this, PinId);
	if(pPinInfo == NULL) {
	    Status = STATUS_INSUFFICIENT_RESOURCES;
	    Trap();
	    goto exit;
	}
	Status = pPinInfo->Create(pFileObject);
	if(!NT_SUCCESS(Status)) {
	    goto exit;
	}
    }

    Status = CreateTopology(this, &Topology);
    if(!NT_SUCCESS(Status)) {
	goto exit;
    }

    Status = lstPinInfo.EnumerateList(CPinInfo::CreatePhysicalConnection);
    if(Status == STATUS_CONTINUE) {
	Status = STATUS_SUCCESS;
    }
    else if(!NT_SUCCESS(Status)) {
	goto exit;
    }

    Status = CLogicalFilterNode::CreateAll(this);
    if(!NT_SUCCESS(Status)) {
	goto exit;
    }
exit:
    delete pCategories;
    delete pConnections;
    return(Status);
}

NTSTATUS
CFilterNode::DuplicateForCapture(
)
{
    PLOGICAL_FILTER_NODE pLogicalFilterNode;
    NTSTATUS Status = STATUS_SUCCESS;
    PFILTER_NODE pFilterNode = NULL;

    if(GetType() & FILTER_TYPE_DUP_FOR_CAPTURE) {

    FOR_EACH_LIST_ITEM(&lstLogicalFilterNode, pLogicalFilterNode) {

        if(!pLogicalFilterNode->IsRenderAndCapture()) {
            ASSERT(NT_SUCCESS(Status));
            goto exit;
        }

    } END_EACH_LIST_ITEM

    pFilterNode = new FILTER_NODE(GetType());
    if(pFilterNode == NULL) {
        Status = STATUS_INSUFFICIENT_RESOURCES;
        Trap();
        goto exit;
    }
    Status = pFilterNode->Create(GetDeviceInterface());
    if(!NT_SUCCESS(Status)) {
        goto exit;
    }
    SetRenderOnly();
    pFilterNode->SetCaptureOnly();
    }
exit:
    if(!NT_SUCCESS(Status)) {
    Trap();
    delete pFilterNode;
    }
    return(Status);
}

BOOL
CFilterNode::IsDeviceInterfaceMatch(
    PDEVICE_NODE pDeviceNode
)
{
    PWSTR pwstr, pwstrDeviceInterface;
    UNICODE_STRING String1, String2;

    Assert(this);
    if(lstwstrDeviceInterfaceMatch.IsLstEmpty()) {
    return(TRUE);
    }
    //
    // The +4 for both the strings is to eliminate the \\.\ differences in
    // user mode device interface names & kernel mode device interface names
    //
    pwstrDeviceInterface = pDeviceNode->GetDeviceInterface()+4;
    RtlInitUnicodeString(&String2, pwstrDeviceInterface);
    FOR_EACH_LIST_ITEM(&lstwstrDeviceInterfaceMatch, pwstr) {
        RtlInitUnicodeString(&String1, (pwstr+4));
        if (RtlEqualUnicodeString(&String1, &String2, TRUE)) {
            return(TRUE);
        }
    } END_EACH_LIST_ITEM

    return(FALSE);
}

VOID
CFilterNode::SetType(
    ULONG fulType
)
{
    this->fulType |= fulType;
    //
    // This is because of left overs (type bridge) in the registry
    // that look like aliases.
    //
    if(this->fulType & FILTER_TYPE_TOPOLOGY) {
    this->fulType = (FILTER_TYPE_AUDIO | FILTER_TYPE_TOPOLOGY);
    }
    GetDefaultOrder(this->fulType, &ulOrder);
}

NTSTATUS
CFilterNode::CreatePin(
    PKSPIN_CONNECT pPinConnect,
    ACCESS_MASK Access,
    PHANDLE pHandle
)
{
    NTSTATUS Status;

    ::KeAttachProcess(this->pProcess);
    Status = KsCreatePin(this->hFileHandle,
                         pPinConnect,
                         Access,
                         pHandle);
    ::KeDetachProcess();
    return(Status);
}

BOOL
CFilterNode::DoesGfxMatch(
    HANDLE hGfx,
    PWSTR pwstrDeviceName,
    ULONG GfxOrder
)
{
    ULONG DeviceCount;
    UNICODE_STRING usInDevice, usfnDevice;
    PWSTR pwstr;


    RtlInitUnicodeString(&usInDevice, (pwstrDeviceName+4));

    //
    // Skip if it is not a GFX
    //
    DPF1(90, "DoesGfxMatch::         fultype=%x", this->fulType);
    if (!(this->fulType & FILTER_TYPE_GFX)) {
        return(FALSE);
    }

    //
    // If it is a valid handle value, check whether the handle matches
    //
    if (hGfx) {
        if (this->hFileHandle != hGfx) {
            return(FALSE);
        }
    }
    //
    // Skip if the order does not match
    //
    DPF1(90, "DoesGfxMatch::         order=%x", this->ulOrder);
    if (GfxOrder != this->ulOrder) {
        return(FALSE);
    }
    //
    // Skip if the match device list is empty :: should not happen with GFX
    //
    if(lstwstrDeviceInterfaceMatch.IsLstEmpty()) {
        ASSERT(!"GFX with no device to attach to!\n");
        return(FALSE);
    }
    //
    // Check if any of the Match device strings matches the device interface
    // passed in. (In case of GFX we should have only one string though)
    //
    DeviceCount = 0;
    FOR_EACH_LIST_ITEM(&lstwstrDeviceInterfaceMatch, pwstr) {
        ASSERT(DeviceCount == 0);
        RtlInitUnicodeString(&usfnDevice, (pwstr+4));
        DPF1(95, "DoesGfxMatch:: new di = %s)", DbgUnicode2Sz(pwstrDeviceName));
        DPF1(95, "DoesGfxMatch:: old di = %s)", DbgUnicode2Sz(pwstr));
        if (RtlEqualUnicodeString(&usInDevice, &usfnDevice, TRUE)) {
            DPF1(90, "Found a duplicate GFX, pFilterNode = %x", this);
            return(TRUE);
        }
        DeviceCount++;
    } END_EACH_LIST_ITEM
    return (FALSE);
}

//---------------------------------------------------------------------------

#ifdef DEBUG

ULONG nFilter = 0;

VOID
DumpSysAudio(
)
{
    if(gplstFilterNode != NULL) {
    gplstFilterNode->Dump();
    }
    if(gplstDeviceNode != NULL) {
    gplstDeviceNode->Dump();
    }
    if(gplstLogicalFilterNode != NULL &&
      (ulDebugFlags & (DEBUG_FLAGS_FILTER |
              DEBUG_FLAGS_DEVICE |
              DEBUG_FLAGS_LOGICAL_FILTER)) ==
              DEBUG_FLAGS_LOGICAL_FILTER) {
    gplstLogicalFilterNode->Dump();
    }
}

ENUMFUNC
CFilterNode::Dump(
)
{
    Assert(this);
    // .sf
    if(ulDebugFlags & (DEBUG_FLAGS_FILTER | DEBUG_FLAGS_OBJECT)) {
    if(ulDebugNumber == MAXULONG || ulDebugNumber == nFilter) {
        if(ulDebugFlags & DEBUG_FLAGS_ADDRESS) {
        dprintf("%d: %08x %s\n", nFilter, this, DumpName());
        }
        else {
        dprintf("%d: %s\n", nFilter, DumpName());
        }
        // .sfv
        if(ulDebugFlags & (DEBUG_FLAGS_VERBOSE | DEBUG_FLAGS_OBJECT)) {
        dprintf("FN: %08x DN %08x cPins %08x ulOrder %08x\n",
          this,
          pDeviceNode,
          cPins,
          ulOrder);
        dprintf("    fulType: %08x ", fulType);
        DumpfulType(fulType);
        dprintf("\n    ulFlags: %08x ", ulFlags);
        if(ulFlags & FN_FLAGS_RENDER) {
            dprintf("RENDER ");
        }
        if(ulFlags & FN_FLAGS_NO_RENDER) {
            dprintf("NO_RENDER ");
        }
        if(ulFlags & FN_FLAGS_CAPTURE) {
            dprintf("CAPTURE ");
        }
        if(ulFlags & FN_FLAGS_NO_CAPTURE) {
            dprintf("NO_CAPTURE ");
        }
        dprintf("\n");
        if(pwstrDeviceInterface != NULL) {
            dprintf("    %s\n", DumpDeviceInterface());
        }
        PWSTR pwstr;

        FOR_EACH_LIST_ITEM(&lstwstrDeviceInterfaceMatch, pwstr) {
            dprintf("    pwstrDevice:\n    %s\n", DbgUnicode2Sz(pwstr));
        } END_EACH_LIST_ITEM

        dprintf("    lstLFN: ");
        lstLogicalFilterNode.DumpAddress();
        dprintf("\n    lstConnFN:");
        lstConnectedFilterNode.DumpAddress();
        dprintf("\n");
        }
        // .sfp
        if(ulDebugFlags & DEBUG_FLAGS_PIN) {
        lstPinInfo.Dump();
        }
        // .sft
        if(ulDebugFlags & DEBUG_FLAGS_TOPOLOGY) {
        lstTopologyNode.Dump();
        // .sftx
        if(ulDebugFlags & DEBUG_FLAGS_DETAILS) {
            lstTopologyConnection.Dump();
        }
        }
        // .sfl
        if(ulDebugFlags & DEBUG_FLAGS_LOGICAL_FILTER) {
        lstLogicalFilterNode.Dump();
        }
        if(ulDebugFlags &
          (DEBUG_FLAGS_VERBOSE | DEBUG_FLAGS_PIN | DEBUG_FLAGS_TOPOLOGY)) {
        dprintf("\n");
        }
    }
    nFilter++;
    }
    return(STATUS_CONTINUE);
}

#endif