//-----------------------------------------------------------------------------
//
//
//  File: fifqdbg.cpp
//
//  Description:  Implementation for CFifoQueueDbgIterator class.
//
//  Author: Mike Swafford (MikeSwa)
//
//  History:
//      9/13/99 - MikeSwa Created
//
//  Copyright (C) 1999 Microsoft Corporation
//
//-----------------------------------------------------------------------------

#define _ANSI_UNICODE_STRINGS_DEFINED_
#include "aqincs.h"
#ifdef PLATINUM
#include "ptrwinst.h"
#include "ptntdefs.h"
#include "ptntintf.h"
#else //PLATINUM
#include "rwinst.h"
#endif //PLATINUM
#include <fifoqdbg.h>
#include <fifoqimp.h>
#include <smtpconn.h>

#define MIN(x, y) ((x) > (y) ? (y) : (x))

//---[ GetQueueType ]----------------------------------------------------------
//
//
//  Description:
//      Determines the queue type for a given ptr
//  Parameters:
//      hCurrentProcess     Handle to the debuggee process
//      pvAddressOtherProc  Addess of the DMQ in the debugee process
//  Returns:
//      AQ_QUEUE_TYPE_UNKNOWN     Queue type cannot be determined
//      AQ_QUEUE_TYPE_FIFOQ       Queue is a CFifoQ
//      AQ_QUEUE_TYPE_DMQ         Queue is a CDestMsgQueue
//      AQ_QUEUE_TYPE_LMQ         Queue is a CLinkMsgQueue
//  History:
//      10/21/1999 - MikeSwa Created
//
//-----------------------------------------------------------------------------
AQ_QUEUE_TYPE CQueueDbgIterator::GetQueueType(HANDLE hCurrentProcess, PVOID pvAddressOtherProc)
{
    BYTE    pbQueueBuffer[100];

    ZeroMemory(pbQueueBuffer, sizeof(pbQueueBuffer));

    if (!ReadMemory(pvAddressOtherProc, pbQueueBuffer,
         sizeof(pbQueueBuffer), NULL))
        return AQ_QUEUE_TYPE_UNKNOWN;

    if (FIFOQ_SIG == ((CFifoQueue<PVOID *> *)pbQueueBuffer)->m_dwSignature)
        return AQ_QUEUE_TYPE_FIFOQ;

    if (DESTMSGQ_SIG == ((CDestMsgQueue *)pbQueueBuffer)->m_dwSignature)
        return AQ_QUEUE_TYPE_DMQ;

    if (LINK_MSGQ_SIG == ((CLinkMsgQueue *)pbQueueBuffer)->m_dwSignature)
        return AQ_QUEUE_TYPE_LMQ;

    return AQ_QUEUE_TYPE_UNKNOWN;
}



#define pvGetNextPage(pvCurrent) ((PVOID) ((CFifoQueuePage<PVOID> *)pvCurrent)->m_pfqpNext)

CFifoQueueDbgIterator::CFifoQueueDbgIterator(PWINDBG_EXTENSION_APIS pApis)
{
    m_iCurrentPage = 0;
    m_iCurrentIndexInPage = 0;
    m_cPagesLoaded = 0;
    m_iHeadIndex = 0;
    m_iTailIndex = 0;
    pExtensionApis  = pApis;
    ZeroMemory(m_pbQueueBuffer, sizeof(m_pbQueueBuffer));
};

CFifoQueueDbgIterator::~CFifoQueueDbgIterator()
{
    PVOID pvCurrent = NULL;
    PVOID pvNext = NULL;

    pvCurrent = ((CFifoQueue<PVOID> *)m_pbQueueBuffer)->m_pfqpHead;
    while (pvCurrent)
    {
        pvNext = pvGetNextPage(pvCurrent);
        free(pvCurrent);
        pvCurrent = pvNext;
    }
}

BOOL CFifoQueueDbgIterator::fInit(HANDLE hCurrentProcess, PVOID pvAddressOtherProc)
{
    DWORD cbBytes = 0;
    PVOID pvPageOtherProc = NULL;
    PVOID pvPageThisProc = NULL;
    PVOID pvPreviousPageThisProc = NULL;

    //Read the entire queue structure in memory
    if (!ReadMemory(pvAddressOtherProc, m_pbQueueBuffer,
            sizeof(m_pbQueueBuffer), NULL))
        return FALSE;

    //Iterate over the previous pointers from the head page
    pvPageOtherProc = ((CFifoQueue<PVOID> *)m_pbQueueBuffer)->m_pfqpHead;

    ((CFifoQueue<PVOID> *)m_pbQueueBuffer)->m_pfqpHead = NULL;
    while (pvPageOtherProc)
    {
        pvPageThisProc = malloc(sizeof(CFifoQueuePage<PVOID>));
        if (!pvPageThisProc)
            return FALSE;

        if (pvPreviousPageThisProc)
        {
            ((CFifoQueuePage<PVOID> *)pvPreviousPageThisProc)->m_pfqpNext =
                (CFifoQueuePage<PVOID> *) pvPageThisProc;
        }
        else
        {
            ((CFifoQueue<PVOID> *)m_pbQueueBuffer)->m_pfqpHead =
                (CFifoQueuePage<PVOID> *) pvPageThisProc;
        }


        if (!ReadMemory(pvPageOtherProc,
                pvPageThisProc, sizeof(CFifoQueuePage<PVOID>), NULL))
        {
            if (pvPreviousPageThisProc)
                ((CFifoQueuePage<PVOID> *)pvPreviousPageThisProc)->m_pfqpNext = NULL;
            else
                ((CFifoQueue<PVOID> *)m_pbQueueBuffer)->m_pfqpHead = NULL;

            free(pvPageThisProc);
            return FALSE;
        }

        if (!pvPreviousPageThisProc)
        {
            //This is the head page. save index
            m_iHeadIndex = (DWORD) ((DWORD_PTR)
                    (((CFifoQueue<PVOID> *)m_pbQueueBuffer)->m_ppqdataHead -
                    ((CFifoQueuePage<PVOID> *)pvPageOtherProc)->m_rgpqdata));

            m_iCurrentIndexInPage = m_iHeadIndex;
        }

        //save tail index... in case this is the last page
        m_iTailIndex = (DWORD) ((DWORD_PTR)
                (((CFifoQueue<PVOID> *)m_pbQueueBuffer)->m_ppqdataTail -
                ((CFifoQueuePage<PVOID> *)pvPageOtherProc)->m_rgpqdata));

        pvPreviousPageThisProc = pvPageThisProc;

        pvPageOtherProc = pvGetNextPage(pvPageThisProc);
        ((CFifoQueuePage<PVOID> *)pvPreviousPageThisProc)->m_pfqpNext = NULL;
        m_cPagesLoaded++;
    }

    return TRUE;
}

DWORD CFifoQueueDbgIterator::cGetCount()
{
    return ((CFifoQueue<PVOID> *)m_pbQueueBuffer)->m_cQueueEntries;
}

PVOID CFifoQueueDbgIterator::pvGetNext()
{
    PVOID pvCurrentPage = ((CFifoQueue<PVOID> *)m_pbQueueBuffer)->m_pfqpHead;
    PVOID pvData = NULL;
    DWORD i = 0;

    if (!pvCurrentPage)
        return NULL;

    //Loop over empty entries (left by DSN generation) or until
    //we reach the end of the queue
    do
    {
        //Figure out if we are on a page boundary
        if (FIFOQ_QUEUE_PAGE_SIZE == m_iCurrentIndexInPage)
        {
            m_iCurrentIndexInPage = 0;
            m_iCurrentPage++;
        }

        //Get current page
        for (i = 0; i < m_iCurrentPage; i++)
        {
            pvCurrentPage = pvGetNextPage(pvCurrentPage);
            if (!pvCurrentPage)
                return NULL;
        }

        if (!((CFifoQueuePage<PVOID> *)pvCurrentPage)->m_rgpqdata)
            return NULL;

        //Get data from current page
        pvData = ((CFifoQueuePage<PVOID> *)pvCurrentPage)->m_rgpqdata[m_iCurrentIndexInPage];

        if ((m_iCurrentIndexInPage > m_iTailIndex) && !pvGetNextPage(pvCurrentPage))
        {
            //We at the end of data
            return NULL;
        }
        m_iCurrentIndexInPage++;
    } while (!pvData);

    return pvData;
}


//---[ CDMQDbgIterator ]-------------------------------------------------------
//
//
//  Description:
//      Constructor for CDMQDbgIterator
//  Parameters:
//      pApis       A ptr to the PWINDBG_EXTENSION_APIS struct passed in by
//                  the debugger.
//  Returns:
//      -
//  History:
//      10/21/1999 - MikeSwa Created
//
//-----------------------------------------------------------------------------
CDMQDbgIterator::CDMQDbgIterator(PWINDBG_EXTENSION_APIS pApis)
{
    ZeroMemory(m_pbDMQBuffer, sizeof(m_pbDMQBuffer));
    ZeroMemory(m_pvFifoQOtherProc, sizeof(m_pvFifoQOtherProc));
    ZeroMemory(m_szName, sizeof(m_szName));
    m_pdmq = (CDestMsgQueue *)m_pbDMQBuffer;
    m_iCurrentFifoQ = 0;
    m_cCount = 0;
    pExtensionApis = pApis;
    m_cItemsReturnedThisQueue = 0;
}

//---[ CDMQDbgIterator::fInit ]------------------------------------------------
//
//
//  Description:
//      Initializes the iterator (and the iterators for all its queues
//  Parameters:
//      hCurrentProcess     Handle to the debuggee process
//      pvAddressOtherProc  Addess of the DMQ in the debugee process
//  Returns:
//      TRUE on success
//      FALSE otherwise
//  History:
//      10/21/1999 - MikeSwa Created
//
//-----------------------------------------------------------------------------
BOOL CDMQDbgIterator::fInit(HANDLE hCurrentProcess, PVOID pvAddressOtherProc)
{
    DWORD   i = 0;
    PVOID   pvQueue = NULL;
    BOOL    fVerbose = TRUE && pExtensionApis;
    BYTE    pbDomainEntry[sizeof(CDomainEntry)];
    CDomainEntry *pdentry = (CDomainEntry *)pbDomainEntry;

    if (!ReadMemory(pvAddressOtherProc, m_pbDMQBuffer,
         sizeof(m_pbDMQBuffer), NULL))
    {
        if (fVerbose) dprintf("ReadMemory failex 0x%X\n", GetLastError());
        return FALSE;
    }

    if (DESTMSGQ_SIG != m_pdmq->m_dwSignature)
    {
        if (fVerbose) dprintf("Bad signature\n");
        return FALSE;
    }

    //Get domain if possible
    if (ReadMemory(m_pdmq->m_dmap.m_pdentryDomainID,
        pbDomainEntry, sizeof(pbDomainEntry), NULL))
    {

        ReadMemory(pdentry->m_szDomainName, m_szName,
            (DWORD)MIN((sizeof(m_szName)-1), (pdentry->m_cbDomainName+1)), NULL);
    }

    for (i = 0; i < NUM_PRIORITIES; i++)
    {
        m_rgfifoqdbg[i].SetApis(pExtensionApis);
        pvQueue = m_pdmq->m_rgpfqQueues[i];
        m_pvFifoQOtherProc[i] = pvQueue;

        if (pvQueue)
        {
            if (!m_rgfifoqdbg[i].fInit(hCurrentProcess, pvQueue))
            {
                if (fVerbose) dprintf("Cannot init queue %d at 0x%X\n", i, pvQueue);
                return FALSE;
            }
            m_cCount += m_rgfifoqdbg[i].cGetCount();
        }
    }

    //Init retry queue
    m_rgfifoqdbg[NUM_PRIORITIES].SetApis(pExtensionApis);
    pvQueue = (((PBYTE)pvAddressOtherProc) + FIELD_OFFSET(CDestMsgQueue, m_fqRetryQueue));
    m_pvFifoQOtherProc[NUM_PRIORITIES] = pvQueue;

    if (pvQueue)
    {
        if (!m_rgfifoqdbg[NUM_PRIORITIES].fInit(hCurrentProcess, pvQueue))
        {
            if (fVerbose) dprintf("Cannon init retry queue at 0x%X\n", pvQueue);
            return FALSE;
        }

        m_cCount += m_rgfifoqdbg[NUM_PRIORITIES].cGetCount();
    }

    return TRUE;
}

//---[ CDMQDbgIterator::pvGetNext ]--------------------------------------------
//
//
//  Description:
//      Gets the next item from the DMQ
//  Parameters:
//      -
//  Returns:
//      An ptr to the item in the debuggee process on success
//      NULL when there are no more items
//  History:
//      10/21/1999 - MikeSwa Created
//
//-----------------------------------------------------------------------------
PVOID CDMQDbgIterator::pvGetNext()
{
    PVOID pvItem = NULL;

    while (m_iCurrentFifoQ <= NUM_PRIORITIES)
    {
        if (m_pvFifoQOtherProc[m_iCurrentFifoQ])
        {
            if (m_rgfifoqdbg[m_iCurrentFifoQ].cGetCount())
            {
                pvItem = m_rgfifoqdbg[m_iCurrentFifoQ].pvGetNext();

                //If we found an item we are done
                if (pvItem)
                {
                    //If it is the first item annouce this queue
                    if (!m_cItemsReturnedThisQueue && pExtensionApis)
                    {
                        dprintf("Dumping FifoQueue at address 0x%08X:\n",
                                m_pvFifoQOtherProc[m_iCurrentFifoQ]);
                    }

                    m_cItemsReturnedThisQueue++;
                    break;
                }
            }
        }
        m_iCurrentFifoQ++;
        m_cItemsReturnedThisQueue = 0;
    }

    return pvItem;
}


//---[ CQueueDbgIterator ]-----------------------------------------------------
//
//
//  Description:
//      Constructor for CQueueDbgIterator
//  Parameters:
//      pApis       A ptr to the PWINDBG_EXTENSION_APIS struct passed in by
//                  the debugger.
//  Returns:
//      -
//  History:
//      10/21/1999 - MikeSwa Created
//
//-----------------------------------------------------------------------------
CQueueDbgIterator::CQueueDbgIterator(PWINDBG_EXTENSION_APIS pApis)
{
    pExtensionApis = pApis;
    m_pqdbgi = NULL;
    m_QueueType = AQ_QUEUE_TYPE_UNKNOWN;
}

//---[ CQueueDbgIterator::fInit ]----------------------------------------------
//
//
//  Description:
//      Initialized generic queue iterator.  Will determine the type
//      of queue and initialize the correct type-specific iterator.
//  Parameters:
//      hCurrentProcess     Handle to the debuggee process
//      pvAddressOtherProc  Addess of the DMQ in the debugee process
//  Returns:
//      TRUE on success
//      FALSE otherwise
//  History:
//      10/21/1999 - MikeSwa Created
//
//-----------------------------------------------------------------------------
BOOL CQueueDbgIterator::fInit(HANDLE hCurrentProcess, PVOID pvAddressOtherProc)
{
    LPSTR   szQueueType = "unknown";
    m_QueueType = GetQueueType(hCurrentProcess, pvAddressOtherProc);

    switch (m_QueueType)
    {
      case AQ_QUEUE_TYPE_DMQ:
        m_pqdbgi = (IQueueDbgIterator *) &m_dmqdbg;
        szQueueType = "DMQ";
        break;
      case AQ_QUEUE_TYPE_FIFOQ:
        m_pqdbgi = (IQueueDbgIterator *) &m_fifoqdbg;
        szQueueType = "CFifoQueue";
        break;
      case AQ_QUEUE_TYPE_LMQ:
        m_pqdbgi = (IQueueDbgIterator *) &m_lmqdbg;
        szQueueType = "LMQ";
        break;
      default:
        return FALSE;
    }

    if (!m_pqdbgi)
        return FALSE;

    m_pqdbgi->SetApis(pExtensionApis);
    if (!m_pqdbgi->fInit(hCurrentProcess, pvAddressOtherProc))
        return FALSE;

    if (pExtensionApis)
    {
        dprintf("Dumping %s (%s) at address 0x%08X:\n",
            szQueueType, m_pqdbgi->szGetName(), pvAddressOtherProc);
    }

    return TRUE;
}

//---[ CQueueDbgIterator::cGetCount ]------------------------------------------
//
//
//  Description:
//      Returns count of items in queue
//  Parameters:
//      -
//  Returns:
//      count of items in queue
//  History:
//      10/21/1999 - MikeSwa Created
//
//-----------------------------------------------------------------------------
DWORD CQueueDbgIterator::cGetCount()
{
    if (!m_pqdbgi)
        return 0;
    else
        return m_pqdbgi->cGetCount();
}

//---[ CQueueDbgIterator::pvGetNext ]------------------------------------------
//
//
//  Description:
//      Returns the next item pointed to by the iterator
//  Parameters:
//      -
//  Returns:
//      Pointer to next item in debugee process on success
//      NULL if no more items or failure
//  History:
//      10/21/1999 - MikeSwa Created
//
//-----------------------------------------------------------------------------
PVOID CQueueDbgIterator::pvGetNext()
{
    if (!m_pqdbgi)
        return NULL;
    else
        return m_pqdbgi->pvGetNext();
}

//---[ CQueueDbgIterator::szGetName ]------------------------------------------
//
//
//  Description:
//      Returns the name of the iterator
//  Parameters:
//      -
//  Returns:
//      Pointer to string for iterator
//      NULL if no name
//  History:
//      10/22/1999 - MikeSwa Created
//
//-----------------------------------------------------------------------------
LPSTR CQueueDbgIterator::szGetName()
{
    if (!m_pqdbgi)
        return NULL;
    else
        return m_pqdbgi->szGetName();
}

//---[ CLMQDbgIterator::CLMQDbgIterator ]--------------------------------------
//
//
//  Description:
//
//  Parameters:
//
//  Returns:
//
//  History:
//      10/22/1999 - MikeSwa Created
//
//-----------------------------------------------------------------------------
CLMQDbgIterator::CLMQDbgIterator(PWINDBG_EXTENSION_APIS pApis)
{
    ZeroMemory(m_pbLMQBuffer, sizeof(m_pbLMQBuffer));
    ZeroMemory(m_rgpvDMQOtherProc, sizeof(m_rgpvDMQOtherProc));
    ZeroMemory(m_szName, sizeof(m_szName));
    ZeroMemory(m_rgpvItemsPendingDelivery, sizeof(m_rgpvItemsPendingDelivery));
    ZeroMemory(m_rgpvConnectionsOtherProc, sizeof(m_rgpvConnectionsOtherProc));
    m_plmq = (CLinkMsgQueue *)m_pbLMQBuffer;
    m_iCurrentDMQ = 0;
    m_cCount = 0;
    m_cItemsThisDMQ = 0;
    m_cPending = 0;
    pExtensionApis = pApis;
}

//---[ CLMQDbgIterator::fInit ]------------------------------------------------
//
//
//  Description:
//      Initializes iterator for CLinkMsgQueue
//  Parameters:
//      hCurrentProcess     Handle to the debuggee process
//      pvAddressOtherProc  Addess of the DMQ in the debugee process
//  Returns:
//
//  History:
//      10/22/1999 - MikeSwa Created
//
//-----------------------------------------------------------------------------
BOOL CLMQDbgIterator::fInit(HANDLE hCurrentProcess, PVOID pvAddressOtherProc)
{
    DWORD   i = 0;
    PLIST_ENTRY pliCurrent = NULL;
    PLIST_ENTRY pliHead = NULL;
    BYTE    pbConnection[sizeof(CSMTPConn)];
    CSMTPConn *pConn = (CSMTPConn *)pbConnection;
    PVOID   pvPending = NULL;
    PVOID   pvConnOtherProc = NULL;
    BOOL    fVerbose = TRUE && pExtensionApis;

    if (!ReadMemory(pvAddressOtherProc, m_pbLMQBuffer,
         sizeof(m_pbLMQBuffer), NULL))
    {
        if (fVerbose) dprintf("ReadMemory failex 0x%X\n", GetLastError());
        return FALSE;
    }

    if (LINK_MSGQ_SIG != m_plmq->m_dwSignature)
    {
        if (fVerbose) dprintf("Signature does not match\n");
        return FALSE;
    }

    //Read in address of all the queues for this link
    //$$TODO - Support more than 1 quick list
    memcpy(m_rgpvDMQOtherProc, m_plmq->m_qlstQueues.m_rgpvData,
           sizeof(m_rgpvDMQOtherProc));

    //Read in name of link
    ReadMemory(m_plmq->m_szSMTPDomain, m_szName,
        (DWORD)MIN((sizeof(m_szName)-1), (m_plmq->m_cbSMTPDomain+1)), NULL);

    for (i = 0; i < MAX_QUEUES_PER_LMQ; i++)
    {
        if (m_rgpvDMQOtherProc[i])
        {
            m_rgdmqdbg[i].SetApis(pExtensionApis);
            if (!m_rgdmqdbg[i].fInit(hCurrentProcess, m_rgpvDMQOtherProc[i]))
            {
                if (fVerbose)
                    dprintf("Unable to init DMQ at 0x%X\n", m_rgpvDMQOtherProc[i]);
                return FALSE;
            }
            m_cCount += m_rgdmqdbg[i].cGetCount();
        }
    }

    //Get the messages pending on a connection

    pliCurrent = m_plmq->m_liConnections.Flink;

    //Loop through connections and save those with pending messages.
    while (pliHead != pliCurrent)
    {
        pvConnOtherProc = ((PBYTE) pliCurrent)-FIELD_OFFSET(CSMTPConn, m_liConnections);
        if (!ReadMemory(pvConnOtherProc, pbConnection,
            sizeof(pbConnection), NULL))
        {
            break;
        }
        pliCurrent = pConn->m_liConnections.Flink;
        if (!pliHead)
            pliHead = pConn->m_liConnections.Blink;

        pvPending = pConn->m_dcntxtCurrentDeliveryContext.m_pmsgref;
        if (pvPending)
        {
            m_rgpvConnectionsOtherProc[m_cPending] = pvConnOtherProc;
            m_rgpvItemsPendingDelivery[m_cPending] = pvPending;
            m_cPending++;
            m_cCount++;
        }
        if (m_cPending >= MAX_CONNECTIONS_PER_LMQ)
            break;
    }
    return TRUE;
}

//---[ CLMQDbgIterator::pvGetNext ]--------------------------------------------
//
//
//  Description:
//      Gets the next item in the current DMQ.  Moves to next DMQ when that
//      is emtpy
//  Parameters:
//      -
//  Returns:
//      Next item on success
//      NULL when empty or failure
//  History:
//      10/22/1999 - MikeSwa Created
//
//-----------------------------------------------------------------------------
PVOID CLMQDbgIterator::pvGetNext()
{
    PVOID   pvItem = NULL;

    while (m_iCurrentDMQ < MAX_QUEUES_PER_LMQ)
    {
        if (m_rgpvDMQOtherProc[m_iCurrentDMQ])
        {
            if (!m_cItemsThisDMQ && m_rgdmqdbg[m_iCurrentDMQ].cGetCount())
            {
               if (pExtensionApis)
               {
                   dprintf("Dumping DMQ (%s) at address 0x%08X:\n",
                            m_rgdmqdbg[m_iCurrentDMQ].szGetName(),
                            m_rgpvDMQOtherProc[m_iCurrentDMQ]);
               }
            }
            pvItem = m_rgdmqdbg[m_iCurrentDMQ].pvGetNext();
            if (pvItem)
            {
                //Check if this is the first item for this DMQ
                m_cItemsThisDMQ++;
                break;
            }
        }
        m_iCurrentDMQ++;
        m_cItemsThisDMQ = 0;
    }

    //If the queues are empty, dump the connections
    if (!pvItem && m_cPending)
    {
        m_cPending--;
        if (pExtensionApis)
        {
            dprintf("Dumping Connection at address 0x%08X:\n",
                        m_rgpvConnectionsOtherProc[m_cPending]);
        }
        pvItem = m_rgpvItemsPendingDelivery[m_cPending];
    }
    return pvItem;
}