//
// 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 OR INDEMNITIES.
//
#include "StdAfx.h"
#include "ShellProc.h"
#include "NDTNdis.h"
#include "NDTMsgs.h"
#include "NDTError.h"
#include "NDTLog.h"
#include "NDTLib.h"
#include "NDTLibEx.h"
#include "ndt_2c.h"
#include "utils.h"
#include <ndis.h>

extern UINT  g_uiTestPhyMedium;
extern DWORD g_dwSendWaitTimeout;
extern BOOL g_bNoUnbind;

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

TEST_FUNCTION(TestMulticastReceive)
{
    TEST_ENTRY;

    INT rc = TPR_PASS;
    HRESULT hr = S_OK;
    BOOL bOk = TRUE;
    BOOL bForce30 = FALSE;
    //This bFilterWarning is used to indicate that miniport driver + its hardware are indicating unwanted multicast packets.
    //Note that it would not be treated as error but warning.
    BOOL bFilterWarning = FALSE;
    NDIS_MEDIUM ndisMedium = g_ndisMedium;

    UINT   chAdapter = 2;
    HANDLE ahAdapter[2] = {NULL, NULL};
    UINT   ixAdapter = 0;

    HANDLE hSend = NULL;
    ULONG  ulSendPacketsSent = 0;
    ULONG  ulSendPacketsCompleted = 0;
    ULONG ulConversationId = 0;
#ifndef NDT6   
    ULONG  ulSendTime = 0;
#else
    ULONGLONG  ulSendTime = 0;
#endif
    ULONG  ulSendBytesSent = 0;

    HANDLE hReceive = NULL;
    ULONG  ulRecvPacketsReceived = 0;
    ULONG  aulRecvPacketsReceived[8];
#ifndef NDT6   
    ULONG  ulRecvTime = 0;
#else
    ULONGLONG  ulRecvTime = 0;
#endif
    ULONG  ulRecvBytesRecv = 0;

    UINT   cbAddr = 0;
    UINT   cbHeader = 0;
    UINT   uiMaximumPacketSize = 0;
    DWORD  dwReceiveDelay = 0;   

    NDIS_OID oidSize = 0;
    NDIS_OID oidList = 0;

    UINT   cpucMulticastAddr = 256;
    UCHAR* apucMulticastAddr[256];
    UINT   cpucUnusedAddr = 8;
    UCHAR* apucUnusedAddr[264];

    UINT   uiPacketSize = 128;
    UINT   uiPacketsToSend = 10;
    UINT   uiAddressBurst = 0;
    UINT   uiMaxListSize = 0;
    UINT   ix = 0, iy = 0;
    UINT   ixSlot = 0;

    ULONG  ulTotalPacketSent = 0;

    // This test should be skipped for some media types
    if (ndisMedium != NdisMedium802_3 && ndisMedium != NdisMediumFddi) {
        return TPR_SKIP;
    }

    // Intro
    NDTLogMsg(_T("Start 1c_MulticastReceive test"));
    NDTLogMsg(_T("The test adapter is %s"), g_szTestAdapter);
    NDTLogMsg(_T("The support adapter is %s"), g_szHelpAdapter);

    if (rand_s((unsigned int*)&ulConversationId) != 0)   {
        NDTLogErr(g_szFailGetConversationId, 0);
        goto cleanUp;
    }


    // Zero local variables
    memset(ahAdapter, 0, sizeof(ahAdapter));
    memset(apucMulticastAddr, 0, sizeof(apucMulticastAddr));
    memset(apucUnusedAddr, 0, sizeof(apucUnusedAddr));

    // Get some information about a media
    NDTGetMediumInfo(ndisMedium, &cbAddr, &cbHeader);
    dwReceiveDelay = NDTGetReceiveDelay(ndisMedium);

    switch (ndisMedium) {
   case NdisMedium802_3:
       oidSize = OID_802_3_MAXIMUM_LIST_SIZE;
       oidList = OID_802_3_MULTICAST_LIST;
       break;
   case NdisMediumFddi:
       oidSize = OID_FDDI_LONG_MAX_LIST_SIZE;
       oidList = OID_FDDI_LONG_MULTICAST_LIST;
       break;
    }

    // Open adapters
    NDTLogMsg(_T("Opening adapters"));

    // Test
    hr = NDTOpen(g_szTestAdapter, &ahAdapter[0]);
    if (FAILED(hr)) {
        bOk = FALSE;
        NDTLogErr(g_szFailOpen, g_szTestAdapter, hr);
        goto cleanUp;
    }

    // Support
    hr = NDTOpen(g_szHelpAdapter, &ahAdapter[1]);
    if (FAILED(hr)) {
        bOk = FALSE;
        NDTLogErr(g_szFailOpen, g_szHelpAdapter, hr);
        goto cleanUp;
    }

    // Binding adapters
    NDTLogMsg(_T("Binding adapters"));
    for (ixAdapter = 0; ixAdapter < chAdapter; ixAdapter++) {
        hr = NDTBind(ahAdapter[ixAdapter], bForce30, ndisMedium);
        if (FAILED(hr)) {
            bOk = FALSE;
            NDTLogErr(g_szFailBind, hr);
            goto cleanUp;
        }
    }

    // Get basic information
    NDTLogMsg(_T("Get basic adapters info"));

    // Get maximum frame size
    hr = NDTGetMaximumFrameSize(ahAdapter[0], &uiMaximumPacketSize);
    if (FAILED(hr)) {
        bOk = FALSE;
        NDTLogErr(g_szFailGetMaximumFrameSize, hr);
        goto cleanUp;
    }

    hr = NDTQueryInfo(
        ahAdapter[0], oidSize, &uiMaxListSize, sizeof(UINT), NULL, NULL
        );
    if (FAILED(hr)) {
        bOk = FALSE;
        NDTLogErr(g_szFailQueryInfo, hr);
        goto cleanUp;
    }

    if (uiMaxListSize == 0) {
        bOk = FALSE;
        NDTLogErr(_T("List size may not be equal to zero"));
        goto cleanUp;
    }

    NDTLogMsg(_T("%s driver supports %d multicast addresses"), g_szTestAdapter, uiMaxListSize);

    if (uiMaxListSize < 32) {
        NDTLogWrn(_T("Driver must support at least 32 multicast addresses"));
    }

    cpucMulticastAddr = uiMaxListSize;

    if (uiMaxListSize > 256) {
        cpucMulticastAddr = 256;
        NDTLogMsg(
            _T("Driver support %d addresses in list - only %d will be tested"), 
            uiMaxListSize, cpucMulticastAddr
            );
    }

    for (ix = 0; ix < cpucUnusedAddr; ix++) {
        apucUnusedAddr[ix] = NDTGetMulticastAddrEx(ndisMedium, 0x0600 + ix);
    }

    NDTLogMsg(_T("Add multicast addresses"));
    for (ix = 0; ix < cpucMulticastAddr; ix++) {
        // Create multicast addresses
        apucMulticastAddr[ix] = NDTGetMulticastAddrEx(ndisMedium, ix);

        hr = NDTAddMulticastAddr(ahAdapter[0], ndisMedium, apucMulticastAddr[ix]);
        if (FAILED(hr)) {
            //if run with -nounbind then TCP/IP, TCPIP6 must have set their own Mcast addresses.
            //if we get NDIS_STATUS_MULTICAST_FULL then its OK.
            if ((ix) && (g_bNoUnbind) && (NDIS_FROM_HRESULT(hr) == NDIS_STATUS_MULTICAST_FULL))
            {
                delete apucMulticastAddr[ix];
                cpucMulticastAddr = ix;
                NDTLogMsg(_T("NDIS_STATUS_MULTICAST_FULL while adding %d Multicast addr."), (cpucMulticastAddr+1));
                NDTLogMsg(_T("Indicates that %d multicast addresses are already set by other Protocol drivers."), 
                    uiMaxListSize - cpucMulticastAddr);
                break;
            }
            else
            {
                bOk = FALSE;
                NDTLogErr(g_szFailAddMulticastAddr, hr);
                goto cleanUp;
            }
        }
    }      

    NDTLogMsg(_T("Set multicast filter"));
    hr = NDTSetPacketFilter(ahAdapter[0], NDT_FILTER_MULTICAST);
    if (FAILED(hr)) {
        bOk = FALSE;
        NDTLogErr(g_szFailSetPacketFilter, hr);
        goto cleanUp;
    }

    while (cpucMulticastAddr >= 0) {

        NDTLogMsg(
            _T("Sending %d packets on %d unused multicast addresses"), 
            uiPacketsToSend, cpucUnusedAddr
            );

        // Start receive
        hr = NDTReceive(ahAdapter[0], ulConversationId, &hReceive);
        if (FAILED(hr)) {
            bOk = FALSE;
            NDTLogErr(g_szFailReceive, hr);
            goto cleanUp;
        }

        ulTotalPacketSent = 0;
        for (ix = 0; ix < cpucUnusedAddr; ix += 8) {

            uiAddressBurst = cpucUnusedAddr - ix;
            if (uiAddressBurst > 8) uiAddressBurst = 8;
            hr = NDTSend(
                ahAdapter[1], cbAddr, NULL, uiAddressBurst, apucUnusedAddr + ix, 
                NDT_RESPONSE_NONE, NDT_PACKET_FLAG_GROUP | NDT_PACKET_TYPE_FIXED, 
                uiPacketSize, uiPacketsToSend, ulConversationId, 0, 0, &hSend
                );
            if (FAILED(hr)) {
                bOk = FALSE;
                NDTLogErr(g_szFailSend, hr);
                goto cleanUp;
            }

            // Wait till sending is done
            hr = NDTSendWait(
                ahAdapter[1], hSend, g_dwSendWaitTimeout, &ulSendPacketsSent, 
                &ulSendPacketsCompleted, NULL, NULL, NULL, &ulSendTime, 
                &ulSendBytesSent,  NULL
                );
            if (FAILED(hr)) {
                bOk = FALSE;
                NDTLogErr(g_szFailSendWait, hr);
                goto cleanUp;
            }

            ulTotalPacketSent += ulSendPacketsSent;
        }

        NDTLogMsg(_T("Sent %d packets"), ulTotalPacketSent);

        // Wait for while
        Sleep(dwReceiveDelay);

        // Stop receiving and check results
        NDTLogMsg(_T("Stop receiving and checking results"));
        hr = NDTReceiveStop(
            ahAdapter[0], hReceive, &ulRecvPacketsReceived, NULL, NULL, 
            &ulRecvTime,  NULL, &ulRecvBytesRecv
            );
        if (FAILED(hr)) {
            bOk = FALSE;
            NDTLogErr(g_szFailReceiveStop, hr);
            goto cleanUp;
        }

        if (ulRecvPacketsReceived != 0) {
            //it is acceptable if NIC hardware and miniport driver indicates the multicast packets up the stack 
            //those were sent to multicast addresses not set in the multicast address list of the miniport driver. 
            //If NIC hardware and its miniport driver fail to filter out unwanted multicast packets then 
            //the protocol layers like TCP/IP will filter them out. 
            //so don't call it out as error, just do warning.
            //bOk = FALSE;
            NDTLogWrn(_T("Improperly received %d packets"), ulRecvPacketsReceived);
            bFilterWarning = TRUE;
        } else {
            NDTLogMsg(_T("Properly didn't receive packets"));
        }

        // There is nothing to send
        if (cpucMulticastAddr == 0) break;

        NDTLogMsg(
            _T("Sending %d packets on %d multicast addresses"), uiPacketsToSend,
            cpucMulticastAddr
            );

        ulTotalPacketSent = 0;
        for (ix = 0; ix < cpucMulticastAddr; ix += 8) {

            uiAddressBurst = cpucMulticastAddr - ix;
            if (uiAddressBurst > 8) uiAddressBurst = 8;

            // Start receive
            hr = NDTReceiveEx(ahAdapter[0], ulConversationId, cbAddr, uiAddressBurst, apucMulticastAddr + ix, &hReceive);
            if (FAILED(hr)) {
                bOk = FALSE;
                NDTLogErr(g_szFailReceive, hr);
                goto cleanUp;
            }

            hr = NDTSend(
                ahAdapter[1], cbAddr, NULL, uiAddressBurst, apucMulticastAddr + ix, 
                NDT_RESPONSE_NONE, NDT_PACKET_FLAG_GROUP | NDT_PACKET_TYPE_FIXED, 
                uiPacketSize, uiPacketsToSend, ulConversationId, 0, 0, &hSend
                );
            if (FAILED(hr)) {
                bOk = FALSE;
                NDTLogErr(g_szFailSend, hr);
                goto cleanUp;
            }

            // Wait till sending is done
            hr = NDTSendWait(
                ahAdapter[1], hSend, g_dwSendWaitTimeout, &ulSendPacketsSent, 
                &ulSendPacketsCompleted, NULL, NULL, NULL, &ulSendTime, 
                &ulSendBytesSent,  NULL
                );
            if (FAILED(hr)) {
                bOk = FALSE;
                NDTLogErr(g_szFailSendWait, hr);
                goto cleanUp;
            }

            ulTotalPacketSent += ulSendPacketsSent;

            Sleep(dwReceiveDelay/cpucMulticastAddr);

            // Stop receiving and check results
            NDTLogMsg(_T("Stop receiving and checking results"));
            hr = NDTReceiveExStop(
                ahAdapter[0], hReceive, aulRecvPacketsReceived
                );
            if (FAILED(hr)) {
                bOk = FALSE;
                NDTLogErr(g_szFailReceiveStop, hr);
                goto cleanUp;
            }

            for (iy =0; iy < uiAddressBurst; iy++)
            {
                if (NotReceivedOK(aulRecvPacketsReceived[iy],uiPacketsToSend,
                    g_uiTestPhyMedium,NDIS_PACKET_TYPE_MULTICAST)) {
                        bOk = FALSE;
                        NDTLogErr(
                            _T("Address %d: Received %d packets but %d was expected"), ix+iy+1,
                            aulRecvPacketsReceived[iy], cpucMulticastAddr * uiPacketsToSend
                            );
                } else {
                    NDTLogMsg(_T("Address %d: Properly received %d packets"), ix+iy+1, aulRecvPacketsReceived[iy]);
                }
            }

        }

        if (cpucMulticastAddr > 128) {
            ix = 64;
        } else if (cpucMulticastAddr > 64) {
            ix = 16;
        } else if (cpucMulticastAddr > 32) {
            ix = 8;
        } else if (cpucMulticastAddr > 16) {
            ix = 4;
        } else {
            ix = 1;
        }

        NDTLogMsg(_T("Removing randomly %d multicast addresses"), ix);
        while (ix > 0) {
            ixSlot = NDTGetRandom(0, cpucMulticastAddr - 1);
            hr = NDTDeleteMulticastAddr(
                ahAdapter[0], ndisMedium, apucMulticastAddr[ixSlot]
            );
            if (FAILED(hr)) {
                bOk = FALSE;
                NDTLogErr(g_szFailDeleteMulticastAddr, hr);
                goto cleanUp;
            }
            apucUnusedAddr[cpucUnusedAddr++] = apucMulticastAddr[ixSlot++];
            while (ixSlot < cpucMulticastAddr) {
                apucMulticastAddr[ixSlot - 1] = apucMulticastAddr[ixSlot];
                ixSlot++;
            }
            cpucMulticastAddr--;
            ix--;
        }

    }

cleanUp:
    // We have deside about test pass/fail there
    rc = !bOk ? TPR_FAIL : TPR_PASS;

    if ((bOk) && (bFilterWarning))
    {
        NDTLogWrn(_T("WARNING: Adapter %s indicates unwanted multicast packets up the stack"), g_szTestAdapter); 
        NDTLogWrn(_T("It indicates packets sent to multicast addresses not set in its multicast address list"));
    }

    NDTLogMsg(_T("Closing adapters"));
    for (ixAdapter = 0; ixAdapter < chAdapter; ixAdapter++) {
        hr = NDTClose(&ahAdapter[ixAdapter]);
        if (FAILED(hr)) NDTLogErr(g_szFailClose, hr);
    }

    for (ix = 0; ix < cpucMulticastAddr; ix++) delete apucMulticastAddr[ix];
    for (ix = 0; ix < cpucUnusedAddr; ix++) delete apucUnusedAddr[ix];

    return rc;
}

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