/*****************************************************************************
*
*  Copyright (c) 1995 Microsoft Corporation
*
*       @doc
*       @module irlmp.c | Provides IrLMP API
*
*       Author: mbert
*
*       Date: 4/15/95
*
*       @comm
*
*  This module exports the following API's:
*
*       IrlmpOpenLink()
*       IrlmpCloseLink()
*       IrlmpDown()
*       IrlmpUp()
*
*
*                |---------|
*                |   Tdi   |
*                |---------|
*                  /|\  |
*                   |   |
*           TdiUp() |   | IrlmpDown()
*                   |   |
*                   |  \|/
*                |---------|      IrdaTimerStart()     |-------|
*                |         |-------------------------->|       |
*                |  IRLMP  |                           | TIMER |
*                |         |<--------------------------|       |
*                |---------|      ExpFunc()            |-------|
*                  /|\  |
*                   |   |
*         IrlmpUp() |   | IrlapDown()
*                   |   |
*                   |  \|/
*                |---------|
*                |  IRLAP  |
*                |---------|
*
* See irda.h for complete message definitions
*
* Connection context for IRLMP and Tdi are exchanged
* during connection establishment:
*
*   Active connection:
*      +------------+ IRLMP_CONNECT_REQ(TdiContext)           +-------+
*      |            |---------------------------------------->|       |
*      |    Tdi     | IRLMP_CONNECT_CONF(IrlmpContext)        | IRMLP |
*      |            |<----------------------------------------|       |
*      +------------+                                         +-------+
*
*   Passive connection:
*      +------------+ IRLMP_CONNECT_IND(IrlmpContext)         +-------+
*      |            |<----------------------------------------|       |
*      |    Tdi     | IRLMP_CONNECT_RESP(TdiContext)          | IRMLP |
*      |            |---------------------------------------->|       |
*      +------------+                                         +-------+
*
*   
*   Tdi calling IrlmpDown(void *pIrlmpContext, IRDA_MSG *pMsg)
*       pIrlmpContext = NULL for the following:
*       pMsg->Prim = IRLMP_DISCOVERY_REQ,
*                    IRLMP_CONNECT_REQ,
*                    IRLMP_FLOWON_REQ,
*                    IRLMP_GETVALUEBYCLASS_REQ.
*       In all other cases, the pIRLMPContext must be a valid context.
*
*   IRLMP calling TdiUp(void *TdiContext, IRDA_MSG *pMsg)
*       TdiContext = NULL for the following:
*       pMsg->Prim = IRLAP_STATUS_IND,
*                    IRLMP_DISCOVERY_CONF,
*                    IRLMP_CONNECT_IND,
*                    IRLMP_GETVALUEBYCLASS_CONF.
*       In all other cases, the TdiContext will have a valid context.
*/
#include <irda.h>
#include <irioctl.h>
#include <irlap.h>
#include <irlmp.h>
#include <irlmpp.h>

#ifdef ALLOC_DATA_PRAGMA
#pragma data_seg("PAGE")
#endif

// IAS
UCHAR IAS_IrLMPSupport[] = {IAS_IRLMP_VERSION, IAS_SUPPORT_BIT_FIELD, 
                           IAS_LMMUX_SUPPORT_BIT_FIELD};

CHAR IasClassName_Device[]         = "Device";
CHAR IasAttribName_DeviceName[]    = "DeviceName";
CHAR IasAttribName_IrLMPSupport[]  = "IrLMPSupport";
CHAR IasAttribName_TTPLsapSel[]    = "IrDA:TinyTP:LsapSel";
CHAR IasAttribName_IrLMPLsapSel[]  = "IrDA:IrLMP:LsapSel";
CHAR IasAttribName_IrLMPLsapSel2[] = "IrDA:IrLMP:LSAPSel"; // jeez

UCHAR IasClassNameLen_Device        = sizeof(IasClassName_Device)-1;
UCHAR IasAttribNameLen_DeviceName   = sizeof(IasAttribName_DeviceName)-1;
UCHAR IasAttribNameLen_IrLMPSupport = sizeof(IasAttribName_IrLMPSupport)-1;
UCHAR IasAttribNameLen_TTPLsapSel   = sizeof(IasAttribName_TTPLsapSel)-1;
UCHAR IasAttribNameLen_IrLMPLsapSel = sizeof(IasAttribName_IrLMPLsapSel)-1;

UINT NextObjectId;

#ifdef ALLOC_DATA_PRAGMA
#pragma data_seg()
#endif


// Globals
LIST_ENTRY      RegisteredLsaps;
LIST_ENTRY      IasObjects;
LIST_ENTRY      gDeviceList;
IRDA_EVENT      EvDiscoveryReq;
IRDA_EVENT      EvConnectReq;
IRDA_EVENT      EvConnectResp;
IRDA_EVENT      EvLmConnectReq;
IRDA_EVENT      EvIrlmpCloseLink;
IRDA_EVENT      EvRetryIasQuery;
BOOLEAN         DscvReqScheduled;
LIST_ENTRY      IrdaLinkCbList;
KSPIN_LOCK      gSpinLock;

// Prototypes
STATIC UINT CreateLsap(PIRLMP_LINK_CB, IRLMP_LSAP_CB **);
STATIC VOID FreeLsap(IRLMP_LSAP_CB *);
STATIC VOID DeleteLsap(IRLMP_LSAP_CB *pLsapCb);
STATIC VOID TearDownConnections(PIRLMP_LINK_CB, IRLMP_DISC_REASON);
STATIC VOID IrlmpMoreCreditReq(IRLMP_LSAP_CB *, IRDA_MSG *);
STATIC VOID IrlmpDiscoveryReq(IRDA_MSG *pMsg);
STATIC UINT IrlmpConnectReq(IRDA_MSG *);
STATIC UINT IrlmpConnectResp(IRLMP_LSAP_CB *, IRDA_MSG *);
STATIC UINT IrlmpDisconnectReq(IRLMP_LSAP_CB *, IRDA_MSG *);
STATIC VOID IrlmpCloseLsapReq(IRLMP_LSAP_CB *);
STATIC UINT IrlmpDataReqExclusive(IRLMP_LSAP_CB *, IRDA_MSG *);
STATIC UINT IrlmpDataReqMultiplexed(IRLMP_LSAP_CB *, IRDA_MSG *);
STATIC VOID FormatAndSendDataReq(IRLMP_LSAP_CB *, IRDA_MSG *, BOOLEAN, BOOLEAN);
STATIC UINT IrlmpAccessModeReq(IRLMP_LSAP_CB *, IRDA_MSG *);
STATIC void SetupTtp(IRLMP_LSAP_CB *);
STATIC VOID SendCntlPdu(IRLMP_LSAP_CB *, int, int, int, int);
STATIC VOID LsapResponseTimerExp(PVOID);
STATIC VOID IrlapDiscoveryConf(PIRLMP_LINK_CB, IRDA_MSG *);
STATIC void UpdateDeviceList(PIRLMP_LINK_CB, LIST_ENTRY *);
STATIC VOID IrlapConnectInd(PIRLMP_LINK_CB, IRDA_MSG *pMsg);
STATIC VOID IrlapConnectConf(PIRLMP_LINK_CB, IRDA_MSG *pMsg);
STATIC VOID IrlapDisconnectInd(PIRLMP_LINK_CB, IRDA_MSG *pMsg);
STATIC IRLMP_LSAP_CB *GetLsapInState(PIRLMP_LINK_CB, int, int, BOOLEAN);
STATIC IRLMP_LINK_CB *GetIrlmpCb(PUCHAR);
STATIC VOID DiscDelayTimerFunc(PVOID);
STATIC VOID IrlapDataConf(IRDA_MSG *pMsg);
STATIC VOID IrlapDataInd(PIRLMP_LINK_CB, IRDA_MSG *pMsg);
STATIC VOID LmPduConnectReq(PIRLMP_LINK_CB, IRDA_MSG *, int, int, UCHAR *);
STATIC VOID LmPduConnectConf(PIRLMP_LINK_CB, IRDA_MSG *, int, int, UCHAR *);
STATIC VOID LmPduDisconnectReq(PIRLMP_LINK_CB, IRDA_MSG *, int, int, UCHAR *);
STATIC VOID SendCreditPdu(IRLMP_LSAP_CB *);
STATIC VOID LmPduData(PIRLMP_LINK_CB, IRDA_MSG *, int, int);
STATIC VOID SetupTtpAndStoreConnData(IRLMP_LSAP_CB *, IRDA_MSG *);
STATIC VOID LmPduAccessModeReq(PIRLMP_LINK_CB, int, int, UCHAR *, UCHAR *);
STATIC VOID LmPduAccessModeConf(PIRLMP_LINK_CB, int, int, UCHAR *, UCHAR *);
STATIC IRLMP_LSAP_CB *GetLsap(PIRLMP_LINK_CB, int, int);
STATIC VOID UnroutableSendLMDisc(PIRLMP_LINK_CB, int, int);
STATIC VOID ScheduleConnectReq(PIRLMP_LINK_CB);
STATIC void InitiateCloseLink(PVOID Context);
STATIC void InitiateConnectReq(PVOID Context);
STATIC void InitiateDiscoveryReq(PVOID Context);
STATIC void InitiateConnectResp(PVOID Context);
STATIC void InitiateLMConnectReq(PVOID Context);
STATIC void InitiateRetryIasQuery(PVOID Context);
STATIC UINT IrlmpGetValueByClassReq(IRDA_MSG *);
STATIC IAS_OBJECT *IasGetObject(CHAR *pClassName);
STATIC IasGetValueByClass(CONST CHAR *, int, CONST CHAR *, int, void **,
                           int *, UCHAR *);
STATIC VOID IasConnectReq(PIRLMP_LINK_CB, int);
STATIC VOID IasServerDisconnectReq(IRLMP_LSAP_CB *pLsapCb);
STATIC VOID IasClientDisconnectReq(IRLMP_LSAP_CB *pLsapCb, IRLMP_DISC_REASON);
STATIC VOID IasSendQueryResp(IRLMP_LSAP_CB *, IRDA_MSG *);
STATIC VOID IasProcessQueryResp(PIRLMP_LINK_CB, IRLMP_LSAP_CB *, IRDA_MSG *);
STATIC VOID SendGetValueByClassReq(IRLMP_LSAP_CB *);
STATIC VOID SendGetValueByClassResp(IRLMP_LSAP_CB *, IRDA_MSG *);
STATIC VOID RegisterLsapProtocol(int Lsap, BOOLEAN UseTTP);
STATIC UINT IasAddAttribute(IAS_SET *pIASSet, PVOID *pAttribHandle);
STATIC VOID IasDelAttribute(PVOID AttribHandle);
STATIC VOID FlushDiscoveryCache();

#if DBG
TCHAR *LSAPStateStr[] =
{
    TEXT("LSAP_CREATED"),
    TEXT("LSAP_DISCONNECTED"),
    TEXT("LSAP_IRLAP_CONN_PEND"),
    TEXT("LSAP_LMCONN_CONF_PEND"),
    TEXT("LSAP_CONN_RESP_PEND"),
    TEXT("LSAP_CONN_REQ_PEND"),
    TEXT("LSAP_EXCLUSIVEMODE_PEND"),
    TEXT("LSAP_MULTIPLEXEDMODE_PEND"),
    TEXT("LSAP_READY"),
    TEXT("LSAP_NO_TX_CREDIT")
};

TCHAR *LinkStateStr[] =
{
    TEXT("LINK_DOWN"),
    TEXT("LINK_DISCONNECTED"),
    TEXT("LINK_DISCONNECTING"),
    TEXT("LINK_IN_DISCOVERY"),
    TEXT("LINK_CONNECTING"),
    TEXT("LINK_READY")
};
#endif

#ifdef ALLOC_PRAGMA

#pragma alloc_text(INIT, IrlmpInitialize)

#pragma alloc_text(PAGEIRDA, DeleteLsap)
#pragma alloc_text(PAGEIRDA, TearDownConnections)
#pragma alloc_text(PAGEIRDA, IrlmpAccessModeReq)
#pragma alloc_text(PAGEIRDA, SetupTtp)
#pragma alloc_text(PAGEIRDA, LsapResponseTimerExp)
#pragma alloc_text(PAGEIRDA, IrlapConnectInd)
#pragma alloc_text(PAGEIRDA, IrlapConnectConf)
#pragma alloc_text(PAGEIRDA, IrlapDisconnectInd)
#pragma alloc_text(PAGEIRDA, GetLsapInState)
#pragma alloc_text(PAGEIRDA, DiscDelayTimerFunc)
#pragma alloc_text(PAGEIRDA, LmPduConnectReq)
#pragma alloc_text(PAGEIRDA, LmPduConnectConf)
#pragma alloc_text(PAGEIRDA, SetupTtpAndStoreConnData)
#pragma alloc_text(PAGEIRDA, LmPduAccessModeReq)
#pragma alloc_text(PAGEIRDA, LmPduAccessModeConf)
#pragma alloc_text(PAGEIRDA, UnroutableSendLMDisc)
#pragma alloc_text(PAGEIRDA, ScheduleConnectReq)
#pragma alloc_text(PAGEIRDA, InitiateCloseLink)
#pragma alloc_text(PAGEIRDA, InitiateConnectReq)
#pragma alloc_text(PAGEIRDA, InitiateConnectResp)
#pragma alloc_text(PAGEIRDA, InitiateLMConnectReq)
#pragma alloc_text(PAGEIRDA, IrlmpGetValueByClassReq)
#pragma alloc_text(PAGEIRDA, IasGetValueByClass)
#pragma alloc_text(PAGEIRDA, IasConnectReq)
#pragma alloc_text(PAGEIRDA, IasServerDisconnectReq)
#pragma alloc_text(PAGEIRDA, IasClientDisconnectReq)
#pragma alloc_text(PAGEIRDA, IasSendQueryResp)
#pragma alloc_text(PAGEIRDA, IasProcessQueryResp)
#pragma alloc_text(PAGEIRDA, SendGetValueByClassReq)
#pragma alloc_text(PAGEIRDA, SendGetValueByClassResp)
#endif
/*****************************************************************************
*
*/
VOID
IrlmpInitialize()
{
    PAGED_CODE();
    
    InitializeListHead(&RegisteredLsaps);
    InitializeListHead(&IasObjects);
    InitializeListHead(&IrdaLinkCbList);
    InitializeListHead(&gDeviceList);

    KeInitializeSpinLock(&gSpinLock);
        
    DscvReqScheduled = FALSE;
    IrdaEventInitialize(&EvDiscoveryReq, InitiateDiscoveryReq);        
    IrdaEventInitialize(&EvConnectReq, InitiateConnectReq);
    IrdaEventInitialize(&EvConnectResp,InitiateConnectResp);
    IrdaEventInitialize(&EvLmConnectReq, InitiateLMConnectReq);
    IrdaEventInitialize(&EvIrlmpCloseLink, InitiateCloseLink);
    IrdaEventInitialize(&EvRetryIasQuery, InitiateRetryIasQuery);    
}    

/*****************************************************************************
*
*/
VOID
IrdaShutdown()
{
    PIRDA_LINK_CB   pIrdaLinkCb, pIrdaLinkCbNext;
    KIRQL           OldIrql;    
    LARGE_INTEGER   SleepMs;    
    NTSTATUS        Status;
    UINT            Seconds;

    SleepMs.QuadPart = -(10*1000*1000); // 1 second

    KeAcquireSpinLock(&gSpinLock, &OldIrql);
    
    for (pIrdaLinkCb = (PIRDA_LINK_CB) IrdaLinkCbList.Flink;
         (LIST_ENTRY *) pIrdaLinkCb != &IrdaLinkCbList;
         pIrdaLinkCb = pIrdaLinkCbNext)
    {
        pIrdaLinkCbNext = (PIRDA_LINK_CB) pIrdaLinkCb->Linkage.Flink;
        
        KeReleaseSpinLock(&gSpinLock, OldIrql);            
        
        IrlmpCloseLink(pIrdaLinkCb);
        
        KeAcquireSpinLock(&gSpinLock, &OldIrql);        
    }     

    
    KeReleaseSpinLock(&gSpinLock, OldIrql);    
    
    Seconds = 0;
    while (Seconds < 30)
    {
        if (IsListEmpty(&IrdaLinkCbList))
            break;

        KeDelayExecutionThread(KernelMode, FALSE, &SleepMs);
        
        Seconds++;
    }
     
#if DBG
    if (Seconds >= 30)
    {
        DbgPrint("Link left open at shutdown!\n");

        for (pIrdaLinkCb = (PIRDA_LINK_CB) IrdaLinkCbList.Flink;
             (LIST_ENTRY *) pIrdaLinkCb != &IrdaLinkCbList;
            pIrdaLinkCb = pIrdaLinkCbNext)
        {
            DbgPrint("pIrdaLinkCb: %X\n", pIrdaLinkCb);
            DbgPrint("   pIrlmpCb: %X\n", pIrdaLinkCb->IrlmpContext);
            DbgPrint("   pIrlapCb: %X\n", pIrdaLinkCb->IrlapContext);
        }
        ASSERT(0);        
    }  
    else
    {
        DbgPrint("Irda shutdown complete\n");
    }       
#endif     
    
    KeDelayExecutionThread(KernelMode, FALSE, &SleepMs);    

    NdisDeregisterProtocol(&Status, NdisIrdaHandle);    
}
/*****************************************************************************
*
*/
VOID
IrlmpOpenLink(OUT PNTSTATUS       Status,
              IN  PIRDA_LINK_CB   pIrdaLinkCb,  
              IN  UCHAR           *pDeviceName,
              IN  int             DeviceNameLen,
              IN  UCHAR           CharSet)
{
    PIRLMP_LINK_CB   pIrlmpCb;
    ULONG           IASBuf[(sizeof(IAS_SET) + 128)/sizeof(ULONG)];
    IAS_SET         *pIASSet;
    
    *Status = STATUS_SUCCESS;

    if (IRDA_ALLOC_MEM(pIrlmpCb, sizeof(IRLMP_LINK_CB), MT_IRLMPCB) == NULL)
    {
        DEBUGMSG(DBG_ERROR, (TEXT("Alloc failed\n")));
        *Status = STATUS_INSUFFICIENT_RESOURCES;
        return;
    }

    pIrdaLinkCb->IrlmpContext = pIrlmpCb;
    
#if DBG
    pIrlmpCb->DiscDelayTimer.pName = "DiscDelay";
#endif
    IrdaTimerInitialize(&pIrlmpCb->DiscDelayTimer,
                        DiscDelayTimerFunc,
                        IRLMP_DISCONNECT_DELAY_TIMEOUT,
                        pIrlmpCb,
                        pIrdaLinkCb);
    
    InitializeListHead(&pIrlmpCb->LsapCbList);
    InitializeListHead(&pIrlmpCb->DeviceList);

    pIrlmpCb->pIrdaLinkCb       = pIrdaLinkCb;
    pIrlmpCb->ConnReqScheduled  = FALSE;
    pIrlmpCb->LinkState         = LINK_DISCONNECTED;
    pIrlmpCb->pExclLsapCb       = NULL; 
    pIrlmpCb->pIasQuery         = NULL;

    // ConnDevAddrSet is set to true if LINK_IN_DISCOVERY or
    // LINK_DISCONNECTING and an LSAP requests a connection. Subsequent
    // LSAP connection requests check to see if this flag is set. If so
    // the requested device address must match that contained in the
    // IRLMP control block (set by the first connect request)
    pIrlmpCb->ConnDevAddrSet = FALSE;

    // Add device info to IAS        
    pIASSet = (IAS_SET *) IASBuf;
    RtlCopyMemory(pIASSet->irdaClassName, IasClassName_Device, 
                  IasClassNameLen_Device+1);
    RtlCopyMemory(pIASSet->irdaAttribName, IasAttribName_DeviceName,
                  IasAttribNameLen_DeviceName+1);
    pIASSet->irdaAttribType = IAS_ATTRIB_VAL_STRING;
    RtlCopyMemory(pIASSet->irdaAttribute.irdaAttribUsrStr.UsrStr,
                      pDeviceName, 
                      DeviceNameLen + 1);
    pIASSet->irdaAttribute.irdaAttribUsrStr.CharSet = CharSet;
    pIASSet->irdaAttribute.irdaAttribUsrStr.Len = (u_char) DeviceNameLen;        
    IasAddAttribute(pIASSet, &pIrlmpCb->hAttribDeviceName);

    RtlCopyMemory(pIASSet->irdaClassName, IasClassName_Device, 
                  IasClassNameLen_Device+1);
    RtlCopyMemory(pIASSet->irdaAttribName, IasAttribName_IrLMPSupport, 
                  IasAttribNameLen_IrLMPSupport+1);
    pIASSet->irdaAttribType = IAS_ATTRIB_VAL_BINARY;
    RtlCopyMemory(pIASSet->irdaAttribute.irdaAttribOctetSeq.OctetSeq, 
               IAS_IrLMPSupport, sizeof(IAS_IrLMPSupport));
    pIASSet->irdaAttribute.irdaAttribOctetSeq.Len =  
            sizeof(IAS_IrLMPSupport);
            
    IasAddAttribute(pIASSet, &pIrlmpCb->hAttribIrlmpSupport);

    if (*Status != STATUS_SUCCESS)
    {
        IRDA_FREE_MEM(pIrlmpCb);
    }
    else
    {
        ExInterlockedInsertTailList(&IrdaLinkCbList,
                                    &pIrdaLinkCb->Linkage,
                                    &gSpinLock);
    }
    
    DEBUGMSG(DBG_IRLMP, (TEXT("IRLMP initialized, status %x\n"), *Status));
    
    return;
}

/*****************************************************************************
*
*
*
*/
VOID
IrlmpDeleteInstance(PVOID Context)
{
    PIRLMP_LINK_CB  pIrlmpCb = (PIRLMP_LINK_CB) Context;
    PIRLMP_LINK_CB  pIrlmpCb2;
    PIRDA_LINK_CB   pIrdaLinkCb;    
    KIRQL           OldIrql;
    BOOLEAN         RescheduleDiscovery = FALSE;

    DEBUGMSG(DBG_IRLMP, (TEXT("IRLMP: Delete instance %X\n"), Context));
        
    KeAcquireSpinLock(&gSpinLock, &OldIrql);
    
    FlushDiscoveryCache();
        
    // We may have been in the middle of discovery when
    // this link went down. Reschedule the discovery inorder
    // to complete the discovery request.
        
    for (pIrdaLinkCb = (PIRDA_LINK_CB) IrdaLinkCbList.Flink;
         (LIST_ENTRY *) pIrdaLinkCb != &IrdaLinkCbList;
         pIrdaLinkCb = (PIRDA_LINK_CB) pIrdaLinkCb->Linkage.Flink)    
    {
        pIrlmpCb2 = (PIRLMP_LINK_CB) pIrdaLinkCb->IrlmpContext;

        if (pIrlmpCb2->DiscoveryFlags)
        {
            RescheduleDiscovery = TRUE;
            break;
        }
    }
    
    // remove IrdaLinkCb from List
    
    RemoveEntryList(&pIrlmpCb->pIrdaLinkCb->Linkage);

    if (IsListEmpty(&IrdaLinkCbList))
    {
        RescheduleDiscovery = TRUE;    
    }    
    
    ASSERT(IsListEmpty(&pIrlmpCb->LsapCbList));
        
    KeReleaseSpinLock(&gSpinLock, OldIrql);        

    IasDelAttribute(pIrlmpCb->hAttribDeviceName);
    IasDelAttribute(pIrlmpCb->hAttribIrlmpSupport);    
    
    IRDA_FREE_MEM(pIrlmpCb);
    
    if (RescheduleDiscovery)
    {
        DEBUGMSG(DBG_IRLMP, (TEXT("IRLMP: Reschedule discovery, link gone\n")));    
        IrdaEventSchedule(&EvDiscoveryReq, NULL);        
    }    
    
/*        
    // Cleanup registered LSAP
    while (!IsListEmpty(&RegisteredLsaps))
    {
        pPtr = RemoveHeadList(&RegisteredLsaps);
        IRDA_FREE_MEM(pPtr);
    }
    // And the device list
    while (!IsListEmpty(&DeviceList))
    {
        pPtr = RemoveHeadList(&DeviceList);
        IRDA_FREE_MEM(pPtr);
    }    

    // And IAS entries
    for (pObject = (IAS_OBJECT *) IasObjects.Flink;
         (LIST_ENTRY *) pObject != &IasObjects;
         pObject = pNextObject)    
    {
        DEBUGMSG(DBG_IRLMP, (TEXT("IRLMP: Deleting object %s\n"),
                              pObject->pClassName));
        
        // Get the next cuz this ones being deleted
        pNextObject = (IAS_OBJECT *) pObject->Linkage.Flink;

        IasDeleteObject(pObject->pClassName);
    }
 */   
}

/*****************************************************************************
*
*   @func   UINT | IrlmpCloseLink | Shuts down IRDA stack
*
*   @rdesc  SUCCESS or error
*
*/
VOID
IrlmpCloseLink(PIRDA_LINK_CB pIrdaLinkCb)
{
    PIRLMP_LINK_CB  pIrlmpCb = (PIRLMP_LINK_CB) pIrdaLinkCb->IrlmpContext;
    
    DEBUGMSG(1, (TEXT("IRLMP: CloseLink request\n")));

    if (pIrlmpCb->LinkState == LINK_DOWN)
    {
        DEBUGMSG(1, (TEXT("IRLMP: Link already down, ignoring\n")));    
        return;
    }    
    
    if (pIrlmpCb->LinkState == LINK_IN_DISCOVERY)
    {
        // Discovery was interrupted so schedule the next link
        IrdaEventSchedule(&EvDiscoveryReq, NULL);
    }
    
    pIrlmpCb->LinkState = LINK_DOWN;
    
    IrdaEventSchedule(&EvIrlmpCloseLink, pIrdaLinkCb);

    return;
}
/*****************************************************************************
*
*   @func   UINT | IrlmpRegisterLSAPProtocol | Bag to let IRLMP know if
*                                               a connect ind is using TTP
*   @rdesc  SUCCESS or error
*
*   @parm   int | LSAP | LSAP being registered
*   @parm   BOOLEAN | UseTtp | 
*/
VOID
RegisterLsapProtocol(int Lsap, BOOLEAN UseTtp)
{
    PIRLMP_REGISTERED_LSAP     pRegLsap;
    KIRQL                       OldIrql;        
    
    KeAcquireSpinLock(&gSpinLock, &OldIrql);
        
    for (pRegLsap = (PIRLMP_REGISTERED_LSAP) RegisteredLsaps.Flink;
         (LIST_ENTRY *) pRegLsap != &RegisteredLsaps;
         pRegLsap = (PIRLMP_REGISTERED_LSAP) pRegLsap->Linkage.Flink)
    {
        if (pRegLsap->Lsap == Lsap)
        {

            if (UseTtp) {

                pRegLsap->Flags |= LCBF_USE_TTP;

            } else {

                pRegLsap->Flags &= ~LCBF_USE_TTP;
            }

            goto done;
        }
    }

    if (IRDA_ALLOC_MEM(pRegLsap, sizeof(IRLMP_REGISTERED_LSAP), 
                       MT_IRLMP_REGLSAP) == NULL)
    {
        ASSERT(0);
        goto done;
    }
    pRegLsap->Lsap = Lsap;
    pRegLsap->Flags = UseTtp ? LCBF_USE_TTP : 0;    
    InsertTailList(&RegisteredLsaps, &pRegLsap->Linkage);    

done:    
    KeReleaseSpinLock(&gSpinLock, OldIrql);        

    DEBUGMSG(DBG_IRLMP, (TEXT("IRLMP: LSAP %x registered, %s\n"), Lsap,
                          UseTtp ? TEXT("use TTP") : TEXT("no TTP")));
}

VOID
DeregisterLsapProtocol(int Lsap)
{
    PIRLMP_REGISTERED_LSAP     pRegLsap;
    KIRQL                      OldIrql;        
    
    KeAcquireSpinLock(&gSpinLock, &OldIrql);
    
    for (pRegLsap = (PIRLMP_REGISTERED_LSAP) RegisteredLsaps.Flink;
         (LIST_ENTRY *) pRegLsap != &RegisteredLsaps;
         pRegLsap = (PIRLMP_REGISTERED_LSAP) pRegLsap->Linkage.Flink)
    {
        if (pRegLsap->Lsap == Lsap)
        {
            DEBUGMSG(DBG_IRLMP, (TEXT("IRLMP: LSAP %x deregistered\n"),
                        Lsap));
                        
            RemoveEntryList(&pRegLsap->Linkage);                        
            
            IRDA_FREE_MEM(pRegLsap);
            break;
        }
    }
    
    KeReleaseSpinLock(&gSpinLock, OldIrql);            
}

/*****************************************************************************
*
*   @func   UINT | FreeLsap | Delete an Lsap control context and
*
*   @rdesc  pointer to Lsap context or 0 on error
*
*   @parm   void | pLsapCb | pointer to an Lsap control block
*/
void
FreeLsap(IRLMP_LSAP_CB *pLsapCb)
{
    VALIDLSAP(pLsapCb);
   
    ASSERT(pLsapCb->State == LSAP_DISCONNECTED);
     
    ASSERT(IsListEmpty(&pLsapCb->SegTxMsgList));
    
    ASSERT(IsListEmpty(&pLsapCb->TxMsgList));
    
    LOCK_LINK(pLsapCb->pIrlmpCb->pIrdaLinkCb);
    
#ifdef DBG    
    pLsapCb->Sig = 0xdaeddead;
#endif
    
    RemoveEntryList(&pLsapCb->Linkage);

    UNLOCK_LINK(pLsapCb->pIrlmpCb->pIrdaLinkCb);
    
    IrdaTimerStop(&pLsapCb->ResponseTimer);
    
    DEBUGMSG(DBG_IRLMP, (TEXT("IRLMP: Deleting LsapCb:%X (%d,%d)\n"),
             pLsapCb, pLsapCb->LocalLsapSel, pLsapCb->RemoteLsapSel));

    REFDEL(&pLsapCb->pIrlmpCb->pIrdaLinkCb->RefCnt, 'PASL');
    
    IRDA_FREE_MEM(pLsapCb);
}

/*****************************************************************************
*
*   @func   UINT | CreateLsap | Create an LSAP control context and
*/
UINT
CreateLsap(PIRLMP_LINK_CB pIrlmpCb, IRLMP_LSAP_CB **ppLsapCb)
{
    KIRQL           OldIrql;
    
    *ppLsapCb = NULL;

    IRDA_ALLOC_MEM(*ppLsapCb, sizeof(IRLMP_LSAP_CB), MT_IRLMP_LSAP_CB);

    if (*ppLsapCb == NULL)
    {
        return IRLMP_ALLOC_FAILED;
    }
    
    CTEMemSet(*ppLsapCb, 0, sizeof(IRLMP_LSAP_CB));

    (*ppLsapCb)->pIrlmpCb = pIrlmpCb;
    (*ppLsapCb)->State = LSAP_CREATED;
    (*ppLsapCb)->UserDataLen = 0;
    (*ppLsapCb)->DiscReason = IRLMP_NO_RESPONSE_LSAP;

    InitializeListHead(&(*ppLsapCb)->TxMsgList);
    InitializeListHead(&(*ppLsapCb)->SegTxMsgList);
    
    ReferenceInit(&(*ppLsapCb)->RefCnt, *ppLsapCb, FreeLsap);
    REFADD(&(*ppLsapCb)->RefCnt, ' TS1');

#if DBG
    (*ppLsapCb)->ResponseTimer.pName = "ResponseTimer";
    
    (*ppLsapCb)->Sig = LSAPSIG;
    
#endif
    IrdaTimerInitialize(&(*ppLsapCb)->ResponseTimer,
                        LsapResponseTimerExp,
                        LSAP_RESPONSE_TIMEOUT,
                        *ppLsapCb,
                        pIrlmpCb->pIrdaLinkCb);

    // Insert into list in the link control block
    KeAcquireSpinLock(&gSpinLock, &OldIrql);    
    
    InsertTailList(&pIrlmpCb->LsapCbList, &((*ppLsapCb)->Linkage));
    
    KeReleaseSpinLock(&gSpinLock, OldIrql);    
    
    REFADD(&pIrlmpCb->pIrdaLinkCb->RefCnt, 'PASL');

    DEBUGMSG(DBG_IRLMP, (TEXT("IRLMP: New LsapCb:%X\n"),
             *ppLsapCb));    

    return SUCCESS;
}

void
DeleteLsap(IRLMP_LSAP_CB *pLsapCb)
{
    IRDA_MSG IMsg, *pMsg, *pNextMsg, *pSegParentMsg;
    
    PAGED_CODE();
    
    VALIDLSAP(pLsapCb);
    
    if (pLsapCb->RemoteLsapSel == IAS_LSAP_SEL)
    {
        pLsapCb->State = LSAP_CREATED;
        return;
    }
    
    DEBUGMSG(DBG_IRLMP, (TEXT("IRLMP: DeleteLsap:%X\n"), pLsapCb));
    
    if (pLsapCb->State == LSAP_DISCONNECTED)
    {
        ASSERT(0);
        return;
    }    
    
    if (pLsapCb == pLsapCb->pIrlmpCb->pExclLsapCb)
    {
        pLsapCb->pIrlmpCb->pExclLsapCb = NULL;
    }
    
    pLsapCb->State = LSAP_DISCONNECTED;
    
    // Clean up the segmented tx msg list
    while (!IsListEmpty(&pLsapCb->SegTxMsgList))
    {
        pMsg = (IRDA_MSG *) RemoveHeadList(&pLsapCb->SegTxMsgList);
        
        // Decrement the segment counter contained in the parent data request
        pSegParentMsg = pMsg->DataContext;
        pSegParentMsg->IRDA_MSG_SegCount -= 1;
        // IRLMP owns these
        FreeIrdaBuf(IrdaMsgPool, pMsg);
    }

    // return any outstanding data requests (unless there are outstanding segments)
    for (pMsg = (IRDA_MSG *) pLsapCb->TxMsgList.Flink;
         (LIST_ENTRY *) pMsg != &(pLsapCb->TxMsgList);
         pMsg = pNextMsg)
    {
        pNextMsg = (IRDA_MSG *) pMsg->Linkage.Flink;

        if (pMsg->IRDA_MSG_SegCount == 0)
        {
            RemoveEntryList(&pMsg->Linkage);            
            
            if (pLsapCb->TdiContext)
            {
                pMsg->Prim = IRLMP_DATA_CONF;
                pMsg->IRDA_MSG_DataStatus = IRLMP_DATA_REQUEST_FAILED;
                TdiUp(pLsapCb->TdiContext, pMsg);
            }
            else
            {
                CTEAssert(0);
            }        
        }
    }
    
    if (pLsapCb->TdiContext && (pLsapCb->Flags & LCBF_TDI_OPEN))
    {
        IMsg.Prim = IRLMP_DISCONNECT_IND;
        IMsg.IRDA_MSG_DiscReason = pLsapCb->DiscReason;
        IMsg.IRDA_MSG_pDiscData = NULL;
        IMsg.IRDA_MSG_DiscDataLen = 0;

        TdiUp(pLsapCb->TdiContext, &IMsg);
    }    
    
    pLsapCb->LocalLsapSel = -1;
    pLsapCb->RemoteLsapSel = -1;
    
    REFDEL(&pLsapCb->RefCnt, ' TS1');
}

/*****************************************************************************
*
*   @func   void | TearDownConnections | Tears down and cleans up connections
*
*   @parm   IRLMP_DISC_REASONE| DiscReason | The reason connections are being
*                                            torn down. Passed to IRLMP clients
*                                            in IRLMP_DISCONNECT_IND
*/
void
TearDownConnections(PIRLMP_LINK_CB pIrlmpCb, IRLMP_DISC_REASON DiscReason)
{
    IRLMP_LSAP_CB       *pLsapCb, *pLsapCbNext;
    
    PAGED_CODE();
    
    pIrlmpCb->pExclLsapCb = NULL; 
   
    DEBUGMSG(DBG_IRLMP, (TEXT("IRLMP: Tearing down connections\r\n")));
    
    // Clean up each LSAP  
    for (pLsapCb = (IRLMP_LSAP_CB *) pIrlmpCb->LsapCbList.Flink;
         (LIST_ENTRY *) pLsapCb != &pIrlmpCb->LsapCbList;
         pLsapCb = pLsapCbNext)
    {
        pLsapCbNext = (IRLMP_LSAP_CB *) pLsapCb->Linkage.Flink;
        
        DEBUGMSG(DBG_IRLMP, (TEXT("IRLMP: Teardown LsapCb:%X\n"), pLsapCb));
        
        VALIDLSAP(pLsapCb);

        if (pLsapCb->LocalLsapSel == IAS_LSAP_SEL)
        {
            IasServerDisconnectReq(pLsapCb);
            continue;
        }
        
        if (pLsapCb->LocalLsapSel == IAS_LOCAL_LSAP_SEL && 
            pLsapCb->RemoteLsapSel == IAS_LSAP_SEL)
        {
            IasClientDisconnectReq(pLsapCb, DiscReason);
        }
        else
        {
            IrdaTimerStop(&pLsapCb->ResponseTimer);

            if (pLsapCb->State != LSAP_DISCONNECTED)
            {
                DEBUGMSG(DBG_IRLMP, 
                         (TEXT("IRLMP: Sending IRLMP Disconnect Ind\r\n")));
                         
                pLsapCb->DiscReason = DiscReason;
                                         
                DeleteLsap(pLsapCb);                                         
            }
        }
    }
}

/*****************************************************************************
*
*   @func   UINT | IrlmpDown | Message from Upper layer to LMP
*
*   @rdesc  SUCCESS or an error code
*
*   @parm   void * | void_pLsapCb | void pointer to an LSAP_CB. Can be NULL
*   @parm   IRDA_MSG * | pMsg | Pointer to an IRDA Message
*/
UINT
IrlmpDown(void *void_pLsapCb, IRDA_MSG *pMsg)
{
    UINT            rc = SUCCESS;
    PIRDA_LINK_CB   pIrdaLinkCb = NULL;

    IRLMP_LSAP_CB *pLsapCb =
            (IRLMP_LSAP_CB *) void_pLsapCb;
    
    if (pLsapCb)
    {
        VALIDLSAP(pLsapCb);

        pIrdaLinkCb = pLsapCb->pIrlmpCb->pIrdaLinkCb;

        // This could be the last lsap closing so
        // add a reference to the IrdaLinkCb so
        // it won't go away before we have a chance
        // to call UNLOCK_LINK
         
        REFADD(&pIrdaLinkCb->RefCnt, 'NWDI');
        
        LOCK_LINK(pIrdaLinkCb);
    }
    
    switch (pMsg->Prim)
    {
      case IRLMP_DISCOVERY_REQ:
        IrlmpDiscoveryReq(pMsg);
        break;

      case IRLMP_CONNECT_REQ:
        rc = IrlmpConnectReq(pMsg);
        break;

      case IRLMP_CONNECT_RESP:
        if (!pLsapCb) 
        {
            rc = IRLMP_INVALID_LSAP_CB;
            break;
        }
        rc = IrlmpConnectResp(pLsapCb, pMsg);
        break;

      case IRLMP_DISCONNECT_REQ:
        if (!pLsapCb) 
        {
            rc = IRLMP_INVALID_LSAP_CB;
            break;
        }      
        rc = IrlmpDisconnectReq(pLsapCb, pMsg);
        break;
        
      case IRLMP_CLOSELSAP_REQ:
        if (!pLsapCb) 
        {
            rc = IRLMP_INVALID_LSAP_CB;
            break;
        }      
        IrlmpCloseLsapReq(pLsapCb);
        break;        

      case IRLMP_DATA_REQ:
        if (!pLsapCb)
        {
            DEBUGMSG(DBG_ERROR, (TEXT("IRLMP: error IRLMP_DATA_REQ on null LsapCb\n")));
            rc = IRLMP_INVALID_LSAP_CB;
            break;
        }    
        if (pLsapCb->pIrlmpCb->pExclLsapCb != NULL)
        {
            rc = IrlmpDataReqExclusive(pLsapCb, pMsg);
        }
        else
        {
            rc = IrlmpDataReqMultiplexed(pLsapCb, pMsg);
        }
        break;

      case IRLMP_ACCESSMODE_REQ:
        if (!pLsapCb) 
        {
            rc = IRLMP_INVALID_LSAP_CB;
            break;
        }      
        rc = IrlmpAccessModeReq(pLsapCb, pMsg);
        break;
        
      case IRLMP_MORECREDIT_REQ:
        if (!pLsapCb)
        {
            rc = IRLMP_INVALID_LSAP_CB;
            break;
        }    
        IrlmpMoreCreditReq(pLsapCb, pMsg);
        break;

      case IRLMP_GETVALUEBYCLASS_REQ:
        rc = IrlmpGetValueByClassReq(pMsg);
        break;

      case IRLMP_REGISTERLSAP_REQ:
        RegisterLsapProtocol(pMsg->IRDA_MSG_LocalLsapSel,
                             pMsg->IRDA_MSG_UseTtp);
        break;

      case IRLMP_DEREGISTERLSAP_REQ:
        DeregisterLsapProtocol(pMsg->IRDA_MSG_LocalLsapSel);
        break;

      case IRLMP_ADDATTRIBUTE_REQ:
        rc = IasAddAttribute(pMsg->IRDA_MSG_pIasSet, pMsg->IRDA_MSG_pAttribHandle);
        break;

      case IRLMP_DELATTRIBUTE_REQ:
        IasDelAttribute(pMsg->IRDA_MSG_AttribHandle);
        break;
        
      case IRLMP_FLUSHDSCV_REQ:
      {
          KIRQL         OldIrql;

          KeAcquireSpinLock(&gSpinLock, &OldIrql);

          FlushDiscoveryCache();
          
          KeReleaseSpinLock(&gSpinLock, OldIrql);

          break;
      }
        
      case IRLAP_STATUS_REQ:
        if (!pLsapCb) 
        {
            rc = IRLMP_INVALID_LSAP_CB;
            break;
        }      
          IrlapDown(pLsapCb->pIrlmpCb->pIrdaLinkCb->IrlapContext, pMsg);
          break;

      default:
        ASSERT(0);
    }

    if (pIrdaLinkCb)
    {
        UNLOCK_LINK(pIrdaLinkCb);
        
        REFDEL(&pIrdaLinkCb->RefCnt, 'NWDI');        
    }
    
    return rc;
}

VOID
IrlmpMoreCreditReq(IRLMP_LSAP_CB *pLsapCb, IRDA_MSG *pMsg)
{
    int CurrentAvail = pLsapCb->AvailableCredit;

    pLsapCb->AvailableCredit += pMsg->IRDA_MSG_TtpCredits;
    
    if (pLsapCb->Flags & LCBF_USE_TTP)
    {
        if (CurrentAvail == 0)
        {
            // remote peer completely out of credit, send'm some
            SendCreditPdu(pLsapCb);
        }
    }
    else
    {
        if (pLsapCb == pLsapCb->pIrlmpCb->pExclLsapCb)
        {
            pLsapCb->RemoteTxCredit += pMsg->IRDA_MSG_TtpCredits;
            pMsg->Prim = IRLAP_FLOWON_REQ;
            IrlapDown(pLsapCb->pIrlmpCb->pIrdaLinkCb->IrlapContext, pMsg);
        }
    }
}

/*****************************************************************************
*
*   @func   UINT | IrlmpDiscoveryReq | initiates a discovery request
*
*   @rdesc  SUCCESS or an error code
*
*   @parm   IRDA_MSG * | pMsg | Pointer to an IRDA Message
*/
VOID
IrlmpDiscoveryReq(IRDA_MSG *pMsg)
{
    PIRDA_LINK_CB   pIrdaLinkCb;
    PIRLMP_LINK_CB  pIrlmpCb;
    KIRQL           OldIrql;
    
    DEBUGMSG(DBG_DISCOVERY, (TEXT("IRLMP: IRLMP_DISCOVERY_REQ\n")));

    KeAcquireSpinLock(&gSpinLock, &OldIrql);

    if (DscvReqScheduled)
    {
        DEBUGMSG(DBG_IRLMP, (TEXT("IRLMP: Discovery already schedule\n")));
        KeReleaseSpinLock(&gSpinLock, OldIrql);
    }
    else
    {
        // Flag each link for discovery
        for (pIrdaLinkCb = (PIRDA_LINK_CB) IrdaLinkCbList.Flink;
             (LIST_ENTRY *) pIrdaLinkCb != &IrdaLinkCbList;
             pIrdaLinkCb = (PIRDA_LINK_CB) pIrdaLinkCb->Linkage.Flink)    
        {
            pIrlmpCb = (PIRLMP_LINK_CB) pIrdaLinkCb->IrlmpContext;
            pIrlmpCb->DiscoveryFlags = DF_NORMAL_DSCV;

            if (pIrlmpCb->LinkState == LINK_DOWN &&
                !IsListEmpty(&pIrlmpCb->DeviceList))
            {
                FlushDiscoveryCache();

                DEBUGMSG(DBG_ERROR, (TEXT("IRLMP: Flush discovery cache, link down\n")));
                
            }
            
            DscvReqScheduled = TRUE;            
        }
        
        KeReleaseSpinLock(&gSpinLock, OldIrql);

        // Schedule the first link
    
        IrdaEventSchedule(&EvDiscoveryReq, NULL);
    }
}
/*****************************************************************************
*
*   @func   UINT | IrlmpConnectReq | Process IRLMP connect request
*
*   @rdesc  SUCCESS or an error code
*
*   @parm   IRDA_MSG * | pMsg | Pointer to an IRDA Message
*/
UINT
IrlmpConnectReq(IRDA_MSG *pMsg)
{
    PIRLMP_LSAP_CB  pLsapCb = NULL;
    PIRLMP_LINK_CB  pIrlmpCb = GetIrlmpCb(pMsg->IRDA_MSG_RemoteDevAddr);
    UINT            rc = SUCCESS;

    if (pIrlmpCb == NULL)
        return IRLMP_BAD_DEV_ADDR;
        
    LOCK_LINK(pIrlmpCb->pIrdaLinkCb);        
    
    if (pIrlmpCb->pExclLsapCb != NULL)
    {
        DEBUGMSG(DBG_ERROR, (TEXT("IRLMP: IrlmpConnectReq failed, link in exclusive mode\n")));
        rc = IRLMP_IN_EXCLUSIVE_MODE;
    } 
    else if ((pLsapCb = GetLsap(pIrlmpCb, pMsg->IRDA_MSG_LocalLsapSel, 
                                pMsg->IRDA_MSG_RemoteLsapSel)) != NULL &&
              pLsapCb->RemoteLsapSel != IAS_LSAP_SEL)
    {        
        DEBUGMSG(DBG_ERROR, (TEXT("IRLMP: IrlmpConnectReq failed, LsapSel in use\n")));
        rc = IRLMP_LSAP_SEL_IN_USE;
    } 
    else if ((UINT)pMsg->IRDA_MSG_ConnDataLen > IRLMP_MAX_USER_DATA_LEN)
    {
        rc = IRLMP_USER_DATA_LEN_EXCEEDED;
    }    
    else if (pLsapCb == NULL && CreateLsap(pIrlmpCb, &pLsapCb) != SUCCESS)
    {
        rc = 1;
    }    
    
    if (rc != SUCCESS)
    {
        goto exit;
    }    


    // Initialize the LSAP endpoint
    pLsapCb->LocalLsapSel          = pMsg->IRDA_MSG_LocalLsapSel;
    pLsapCb->RemoteLsapSel         = pMsg->IRDA_MSG_RemoteLsapSel;
    pLsapCb->TdiContext            = pMsg->IRDA_MSG_pContext;
    pLsapCb->RxMaxSDUSize          = pMsg->IRDA_MSG_MaxSDUSize;
    pLsapCb->AvailableCredit       = pMsg->IRDA_MSG_TtpCredits;
    pLsapCb->UserDataLen           = pMsg->IRDA_MSG_ConnDataLen;  
    pLsapCb->Flags                |= pMsg->IRDA_MSG_UseTtp ? LCBF_USE_TTP : 0;
    
    RtlCopyMemory(pLsapCb->UserData, pMsg->IRDA_MSG_pConnData,
           pMsg->IRDA_MSG_ConnDataLen);
    
    // TDI can abort this connection before the confirm 
    // from peer is received. TDI will call into LMP to 
    // do this so we must return the Lsap context now.
    // This is the only time we actually return something 
    // in an Irda Message.
    pMsg->IRDA_MSG_pContext = pLsapCb;
    
    DEBUGMSG((DBG_IRLMP | DBG_IRLMP_CONN), 
            (TEXT("IRLMP: IRLMP_CONNECT_REQ (l=%d,r=%d), Tdi:%X LinkState=%s\r\n"),
            pLsapCb->LocalLsapSel, pLsapCb->RemoteLsapSel, pLsapCb->TdiContext,
            pIrlmpCb->LinkState == LINK_DISCONNECTED ? TEXT("DISCONNECTED") :
            pIrlmpCb->LinkState == LINK_IN_DISCOVERY ? TEXT("IN_DISCOVERY") :
            pIrlmpCb->LinkState == LINK_DISCONNECTING? TEXT("DISCONNECTING"):
            pIrlmpCb->LinkState == LINK_READY ? TEXT("READY") : TEXT("oh!")));

    switch (pIrlmpCb->LinkState)
    {
      case LINK_DISCONNECTED:
        RtlCopyMemory(pIrlmpCb->ConnDevAddr, pMsg->IRDA_MSG_RemoteDevAddr,
               IRDA_DEV_ADDR_LEN);

        pLsapCb->State = LSAP_IRLAP_CONN_PEND;
        SetupTtp(pLsapCb);

        pMsg->Prim = IRLAP_CONNECT_REQ;
        rc = IrlapDown(pIrlmpCb->pIrdaLinkCb->IrlapContext, pMsg);
        if (rc == SUCCESS)
        {
            pIrlmpCb->LinkState = LINK_CONNECTING;
        }

        break;

      case LINK_IN_DISCOVERY:
      case LINK_DISCONNECTING:
        if (pIrlmpCb->ConnDevAddrSet == FALSE)
        {
            // Ensure that only the first device to request a connection
            // sets the device address of the remote to be connected to.
            RtlCopyMemory(pIrlmpCb->ConnDevAddr, pMsg->IRDA_MSG_RemoteDevAddr,
                    IRDA_DEV_ADDR_LEN);
            pIrlmpCb->ConnDevAddrSet = TRUE;
        }
        else
        {
            DEBUGMSG(DBG_IRLMP, (TEXT("IRLMP: Link in use!\r\n")));
            
            if (CTEMemCmp(pMsg->IRDA_MSG_RemoteDevAddr,
                          pIrlmpCb->ConnDevAddr,
                          IRDA_DEV_ADDR_LEN) != 0)
            {
                // This LSAP is requesting a connection to another device
                DeleteLsap(pLsapCb);
                rc = IRLMP_LINK_IN_USE;
                break;
            }
        }

        pLsapCb->State = LSAP_CONN_REQ_PEND;
        SetupTtp(pLsapCb);

        // This request will complete when discovery/disconnect ends
        break;

      case LINK_CONNECTING:
        if (CTEMemCmp(pMsg->IRDA_MSG_RemoteDevAddr,
                      pIrlmpCb->ConnDevAddr,
                      IRDA_DEV_ADDR_LEN) != 0)
        {
            DEBUGMSG(DBG_IRLMP, (TEXT("IRLMP: Link in use!\r\n")));
            // This LSAP is requesting a connection to another device,
            // not the one IRLAP is currently connected to
            DeleteLsap(pLsapCb);
            rc = IRLMP_LINK_IN_USE;
            break;
        }

        // The LSAP will be notified when the IRLAP connection that is
        // underway has completed (see IRLAP_ConnectConf)
        pLsapCb->State = LSAP_IRLAP_CONN_PEND; 

        SetupTtp(pLsapCb);

        break;

      case LINK_READY:
        if (CTEMemCmp(pMsg->IRDA_MSG_RemoteDevAddr,
                      pIrlmpCb->ConnDevAddr,
                      IRDA_DEV_ADDR_LEN) != 0)
        {
            DEBUGMSG(DBG_IRLMP, (TEXT("IRLMP: Link in use!\r\n")));
            // This LSAP is requesting a connection to another device
            DeleteLsap(pLsapCb);
            rc = IRLMP_LINK_IN_USE;
            break;
        }
        IrdaTimerRestart(&pLsapCb->ResponseTimer);

        pLsapCb->State = LSAP_LMCONN_CONF_PEND;
        SetupTtp(pLsapCb);

        // Ask remote LSAP for a connection
        SendCntlPdu(pLsapCb, IRLMP_CONNECT_PDU,
                    IRLMP_ABIT_REQUEST, IRLMP_RSVD_PARM, 0);
        break;
    }
    
exit:

    if (pLsapCb)
    {
        if (rc == SUCCESS)
        {
            if (pLsapCb->RemoteLsapSel != IAS_LSAP_SEL)
            {    
                pLsapCb->Flags |= LCBF_TDI_OPEN;
                REFADD(&pLsapCb->RefCnt, 'NEPO');
            }    
        }
        else if (pLsapCb->RemoteLsapSel == IAS_LSAP_SEL)
        {
            DeleteLsap(pLsapCb);
        }
    }    

    UNLOCK_LINK(pIrlmpCb->pIrdaLinkCb);    

    return rc;
}
/*****************************************************************************
*
*   @func   void | SetupTtp | if using TTP, calculate initial credits
*
*   @rdesc  SUCCESS or an error code
*
*   @parm   IRLMP_LSAP_CB * | pLsapCb | pointer LSAP control block
*/
void
SetupTtp(IRLMP_LSAP_CB *pLsapCb)
{
    PAGED_CODE();
    
    VALIDLSAP(pLsapCb);
    
    if (pLsapCb->AvailableCredit > 127)
    {
        pLsapCb->RemoteTxCredit = 127;
        pLsapCb->AvailableCredit -= 127;
    }
    else
    {
        pLsapCb->RemoteTxCredit = pLsapCb->AvailableCredit;
        pLsapCb->AvailableCredit = 0;
    }
    DEBUGMSG(DBG_IRLMP, (TEXT("IRLMP: RemoteTxCredit %d\n"),
                         pLsapCb->RemoteTxCredit));
}
/*****************************************************************************
*
*   @func   UINT | IrlmpConnectResp | Process IRLMP connect response
*
*   @rdesc  SUCCESS or an error code
*
*   @parm   IRLMP_LSAP_CB * | pLsapCb | pointer LSAP control block
*   @parm   IRDA_MSG * | pMsg | Pointer to an IRDA Message
*/
UINT
IrlmpConnectResp(IRLMP_LSAP_CB *pLsapCb, IRDA_MSG *pMsg)
{
    DEBUGMSG(DBG_IRLMP, (TEXT("IRLMP: IRLMP_CONNECT_RESP l=%d r=%d\n"),
             pLsapCb->LocalLsapSel, pLsapCb->RemoteLsapSel));
    
    if (pLsapCb->pIrlmpCb->LinkState != LINK_READY)
    {
        DEBUGMSG(DBG_ERROR, (TEXT("IRLMP: Bad link state\n")));
        ASSERT(0);
        return IRLMP_LINK_BAD_STATE;
    }

    if (pLsapCb->State != LSAP_CONN_RESP_PEND)
    {
        DEBUGMSG(DBG_ERROR, (TEXT("IRLMP: Bad LSAP state\n")));
        ASSERT(0);
        return IRLMP_LSAP_BAD_STATE;
    }

    IrdaTimerStop(&pLsapCb->ResponseTimer);

    if (pMsg->IRDA_MSG_ConnDataLen > IRLMP_MAX_USER_DATA_LEN)
    {
        DEBUGMSG(DBG_ERROR, (TEXT("IRLMP: User data len exceeded\n")));
        return IRLMP_USER_DATA_LEN_EXCEEDED;
    }

    pLsapCb->RxMaxSDUSize       = pMsg->IRDA_MSG_MaxSDUSize;
    pLsapCb->UserDataLen        = pMsg->IRDA_MSG_ConnDataLen;
    pLsapCb->AvailableCredit    = pMsg->IRDA_MSG_TtpCredits;
    RtlCopyMemory(pLsapCb->UserData, pMsg->IRDA_MSG_pConnData,
           pMsg->IRDA_MSG_ConnDataLen);
    
    pLsapCb->TdiContext = pMsg->IRDA_MSG_pContext;
    
    CTEAssert(pLsapCb->TdiContext);    
    
    SetupTtp(pLsapCb);

    pLsapCb->State = LSAP_READY;
    
    pLsapCb->Flags |= LCBF_TDI_OPEN;        
        
    REFADD(&pLsapCb->RefCnt, 'NEPO');

    SendCntlPdu(pLsapCb, IRLMP_CONNECT_PDU, IRLMP_ABIT_CONFIRM,
                IRLMP_RSVD_PARM, 0);

    return SUCCESS;
}



VOID
IrlmpCloseLsapReq(IRLMP_LSAP_CB *pLsapCb)
{
    if (pLsapCb == NULL)
    {
        ASSERT(0);
        return;
    }    
    
    DEBUGMSG((DBG_IRLMP | DBG_IRLMP_CONN), 
            (TEXT("IRLMP: IRLMP_CLOSELSAP_REQ (l=%d,r=%d) Flags:%d State:%s\n"),
             pLsapCb->LocalLsapSel, pLsapCb->RemoteLsapSel,
             pLsapCb->Flags, LSAPStateStr[pLsapCb->State]));
            
    pLsapCb->Flags &= ~LCBF_TDI_OPEN;             
    
    REFDEL(&pLsapCb->RefCnt, 'NEPO');    
}

/*****************************************************************************
*
*   @func   UINT | IrlmpDisconnectReq | Process IRLMP disconnect request
*
*   @rdesc  SUCCESS or an error code
*
*   @parm   IRLMP_LSAP_CB * | pLsapCb | pointer LSAP control block
*   @parm   IRDA_MSG * | pMsg | Pointer to an IRDA Message
*/
UINT
IrlmpDisconnectReq(IRLMP_LSAP_CB *pLsapCb, IRDA_MSG *pMsg)
{
    if (pLsapCb == NULL)
    {
        ASSERT(0);
        return 1;
    }    
    
    DEBUGMSG((DBG_IRLMP | DBG_IRLMP_CONN), 
            (TEXT("IRLMP: IRLMP_DISCONNECT_REQ (l=%d,r=%d) Flags:%d State:%s\n"),
             pLsapCb->LocalLsapSel, pLsapCb->RemoteLsapSel,
             pLsapCb->Flags, LSAPStateStr[pLsapCb->State]));
            

    if (pLsapCb->State == LSAP_DISCONNECTED)
    {
        return SUCCESS;
    }

    if (pLsapCb->State == LSAP_LMCONN_CONF_PEND ||
        pLsapCb->State == LSAP_CONN_RESP_PEND)
    {
        IrdaTimerStop(&pLsapCb->ResponseTimer);
    }

    if (pLsapCb->State == LSAP_CONN_RESP_PEND || pLsapCb->State >= LSAP_READY)
    {
        // Either the LSAP is connected or the peer is waiting for a
        // response from our client

        if (pMsg->IRDA_MSG_DiscDataLen > IRLMP_MAX_USER_DATA_LEN)
        {
            DEBUGMSG(DBG_ERROR, (TEXT("IRLMP: User data len exceeded\n")));
            return IRLMP_USER_DATA_LEN_EXCEEDED;
        }

        pLsapCb->UserDataLen = pMsg->IRDA_MSG_DiscDataLen;
        RtlCopyMemory(pLsapCb->UserData, pMsg->IRDA_MSG_pDiscData,
               pMsg->IRDA_MSG_DiscDataLen);

        // Notify peer of the disconnect request, reason: user request
        // Send on different thread in case TranportAPI calls this on rx thread
        SendCntlPdu(pLsapCb,IRLMP_DISCONNECT_PDU,IRLMP_ABIT_REQUEST,
                    pLsapCb->State == LSAP_CONN_RESP_PEND ? IRLMP_DISC_LSAP :
                    IRLMP_USER_REQUEST, 0);
    }

    IrdaTimerRestart(&pLsapCb->pIrlmpCb->DiscDelayTimer);
    
    DeleteLsap(pLsapCb);
    
    return SUCCESS;
}
/*****************************************************************************
*
*   @func   UINT | IrlmpDataReqExclusive | Process IRLMP data request
*
*   @rdesc  SUCCESS or an error code
*
*   @parm   IRLMP_LSAP_CB * | pLsapCb | pointer LSAP control block
*   @parm   IRDA_MSG * | pMsg | Pointer to an IRDA Message
*/
UINT
IrlmpDataReqExclusive(IRLMP_LSAP_CB *pLsapCb, IRDA_MSG *pMsg)
{
    NDIS_BUFFER         *pNBuf = (NDIS_BUFFER *) pMsg->DataContext;
    NDIS_BUFFER         *pNextNBuf;
    UCHAR                *pData;
    int                 DataLen;

    DEBUGMSG(DBG_IRLMP, (TEXT("IRLMP: Exclusive mode data request\n")));

    if (pLsapCb->pIrlmpCb->LinkState != LINK_READY)
    {
        return IRLMP_LINK_BAD_STATE;
    }

    if (pLsapCb != pLsapCb->pIrlmpCb->pExclLsapCb)
    {
        return IRLMP_INVALID_LSAP_CB;
    }
    
    NdisQueryBuffer(pNBuf, &pData, &DataLen);

    NdisGetNextBuffer(pNBuf, &pNextNBuf);       

    ASSERT(pNextNBuf == NULL);

    pMsg->IRDA_MSG_SegCount = 0; // see DATA_CONF on how I'm using this
    pMsg->IRDA_MSG_SegFlags = SEG_FINAL;

    pMsg->IRDA_MSG_pRead = pData;
    pMsg->IRDA_MSG_pWrite = pData + DataLen;

    FormatAndSendDataReq(pLsapCb, pMsg, FALSE, FALSE);
    
    return SUCCESS;
}
/*****************************************************************************
*
*   @func   UINT | IrlmpDataReqMultiplexed | Process IRLMP data request
*
*   @rdesc  SUCCESS or an error code
*
*   @parm   IRLMP_LSAP_CB * | pLsapCb | pointer LSAP control block
*   @parm   IRDA_MSG * | pMsg | Pointer to an IRDA Message
*/
UINT
IrlmpDataReqMultiplexed(IRLMP_LSAP_CB *pLsapCb, IRDA_MSG *pMsg)
{
    NDIS_BUFFER         *pNBuf = (NDIS_BUFFER *) pMsg->DataContext;
    NDIS_BUFFER         *pNextNBuf;
    UCHAR                *pData;
    int                 DataLen;
    int                 SegLen;
    IRDA_MSG            *pSegMsg;

    if (pLsapCb->State < LSAP_READY)
    {
        return IRLMP_LSAP_BAD_STATE;
    }
    
    // Place this message on the LSAP's TxMsgList. The message remains
    // here until all segments for it are sent and confirmed
    InsertTailList(&pLsapCb->TxMsgList, &pMsg->Linkage);

    pMsg->IRDA_MSG_SegCount = 0;
    // If it fails, this will be changed
    pMsg->IRDA_MSG_DataStatus = IRLMP_DATA_REQUEST_COMPLETED;

    // Segment the message into PDUs. The segment will either be:
    //  1. Sent immediately to IRLAP if the link is not busy
    //  2. If link is busy, placed on TxMsgList contained in IRLMP_LCB
    //  3. If no credit, placed onto this LSAPS SegTxMsgList

    while (pNBuf != NULL)
    {
        NdisQueryBufferSafe(pNBuf, &pData, &DataLen, NormalPagePriority);
        
        if (pData == NULL)
        {
            break;
        }    
        
         // Get the next one now so I know when to set SegFlag to final
        NdisGetNextBuffer(pNBuf, &pNextNBuf);

        while (DataLen != 0)
        {
            if ((pSegMsg = AllocIrdaBuf(IrdaMsgPool))
                == NULL)
            {
                ASSERT(0);
                return IRLMP_ALLOC_FAILED;
            }
            pSegMsg->IRDA_MSG_pOwner = pLsapCb; // MUX routing
            pSegMsg->DataContext = pMsg; // Parent of segment
            pSegMsg->IRDA_MSG_IrCOMM_9Wire = pMsg->IRDA_MSG_IrCOMM_9Wire;

            // Increment segment count contained in original messages
            pMsg->IRDA_MSG_SegCount++;

            if (DataLen > pLsapCb->pIrlmpCb->MaxPDUSize)
            {
                SegLen = pLsapCb->pIrlmpCb->MaxPDUSize;
            }
            else
            {
                SegLen = DataLen;
            }
            
            DEBUGMSG(DBG_IRLMP, (TEXT("IRLMP: Sending SegLen %d\n"),
                                  SegLen));
            
            pSegMsg->IRDA_MSG_pRead = pData;
            pSegMsg->IRDA_MSG_pWrite = pData + SegLen;

            // Indicate this message is part of a segmented message
            pSegMsg->IRDA_MSG_SegCount = pMsg->IRDA_MSG_SegCount;
            
            pData += SegLen;
            DataLen -= SegLen;
            
            if (DataLen == 0 && pNextNBuf == NULL)
            {
                pSegMsg->IRDA_MSG_SegFlags = SEG_FINAL;
            }
            else
            {
                pSegMsg->IRDA_MSG_SegFlags = 0;
            }

            if (pLsapCb->State == LSAP_NO_TX_CREDIT)
            {
                DEBUGMSG(DBG_IRLMP, 
                        (TEXT("IRLMP: Out of credit, placing on SegList\n")));
                InsertTailList(&pLsapCb->SegTxMsgList, &pSegMsg->Linkage);
            }
            else
            {
                FormatAndSendDataReq(pLsapCb, pSegMsg, FALSE, FALSE);
            }
        }
        pNBuf = pNextNBuf;
    }

    return SUCCESS;
}

VOID
FormatAndSendDataReq(IRLMP_LSAP_CB *pLsapCb, 
                     IRDA_MSG *pMsg,
                     BOOLEAN LocallyGenerated,
                     BOOLEAN Expedited)
{
    IRLMP_HEADER        *pLMHeader;
    TTP_DATA_HEADER     *pTTPHeader;
    int                 AdditionalCredit;
    UCHAR                *pLastHdrByte;
    
    VALIDLSAP(pLsapCb);

    // Initialize the header pointers to the end of the header block
    pMsg->IRDA_MSG_pHdrRead  =
    pMsg->IRDA_MSG_pHdrWrite = pMsg->IRDA_MSG_Header + IRDA_HEADER_LEN;

    // Back up the read pointer for the LMP header
    pMsg->IRDA_MSG_pHdrRead -= sizeof(IRLMP_HEADER);

    // Back up header read pointer for TTP
    if (pLsapCb->Flags & LCBF_USE_TTP)
    {
        pMsg->IRDA_MSG_pHdrRead -= (sizeof(TTP_DATA_HEADER));
    }

    // WHACK FOR IRCOMM YUK YUK !!
    if (pMsg->IRDA_MSG_IrCOMM_9Wire == TRUE)
    {
        pMsg->IRDA_MSG_pHdrRead -= 1;
    }

    ASSERT(pMsg->IRDA_MSG_pHdrRead >= pMsg->IRDA_MSG_Header);

    // Build the LMP Header
    pLMHeader = (IRLMP_HEADER *) pMsg->IRDA_MSG_pHdrRead;
    pLMHeader->DstLsapSel = (UCHAR) pLsapCb->RemoteLsapSel;
    pLMHeader->SrcLsapSel = (UCHAR) pLsapCb->LocalLsapSel;
    pLMHeader->CntlBit = IRLMP_DATA_PDU;
    pLMHeader->RsvrdBit = 0;

    pLastHdrByte = (UCHAR *) (pLMHeader + 1);
    
    // Build the TTP Header
    if (pLsapCb->Flags & LCBF_USE_TTP)
    {
        pTTPHeader = (TTP_DATA_HEADER *) (pLMHeader + 1);

        // Credit
        if (pLsapCb->AvailableCredit > 127)
        {
            AdditionalCredit = 127;
            pLsapCb->AvailableCredit -= 127;
        }
        else
        {
            AdditionalCredit = pLsapCb->AvailableCredit;
            pLsapCb->AvailableCredit = 0;
        }

        pTTPHeader->AdditionalCredit = (UCHAR) AdditionalCredit;
        pLsapCb->RemoteTxCredit += AdditionalCredit;

        if (pMsg->IRDA_MSG_pRead != pMsg->IRDA_MSG_pWrite)
        {
            // Only decrement my TxCredit if I'm sending data
            // (may be sending dataless PDU to extend credit to peer)
            pLsapCb->LocalTxCredit -= 1;
        
            if (pLsapCb->LocalTxCredit == 0)
            {
                DEBUGMSG(DBG_IRLMP, 
                         (TEXT("IRLMP: l%d,r%d No credit\n"), pLsapCb->LocalLsapSel,
                          pLsapCb->RemoteLsapSel));
                pLsapCb->State = LSAP_NO_TX_CREDIT;
            }
        }
        
        // SAR
        if (pMsg->IRDA_MSG_SegFlags & SEG_FINAL)
        {
            pTTPHeader->MoreBit = TTP_MBIT_FINAL;
        }
        else
        {
            pTTPHeader->MoreBit = TTP_MBIT_NOT_FINAL;
        }

        pLastHdrByte = (UCHAR *) (pTTPHeader + 1);
    }
    
    // WHACK FOR IRCOMM YUK YUK !!
    if (pMsg->IRDA_MSG_IrCOMM_9Wire == TRUE)
    {
        *pLastHdrByte = 0;
    }

    pMsg->Prim = IRLAP_DATA_REQ;

    pMsg->IRDA_MSG_Expedited = Expedited;

    if (LocallyGenerated)
    {
        pMsg->IRDA_MSG_SegFlags = SEG_LOCAL | SEG_FINAL;
        pMsg->IRDA_MSG_pOwner = 0;
    }
    else
    {
        pMsg->IRDA_MSG_pOwner = pLsapCb;    
        REFADD(&pLsapCb->RefCnt, 'ATAD');
    }    
    
    DEBUGMSG(DBG_IRLMP, (TEXT("IRLMP: Sending Data request pMsg:%X LsapCb:%X\n"),
                        pMsg, pMsg->IRDA_MSG_pOwner));
                        
    if (IrlapDown(pLsapCb->pIrlmpCb->pIrdaLinkCb->IrlapContext, 
                  pMsg) != SUCCESS)
    {
        DEBUGMSG(DBG_ERROR, (TEXT("IRLMP: IRLAP_DATA_REQ failed, faking CONF\n")));
        
        pMsg->IRDA_MSG_DataStatus = IRLAP_DATA_REQUEST_FAILED_LINK_RESET;
        IrlapDataConf(pMsg);
    }        
    
    DEBUGMSG(DBG_IRLMP_CRED,
             (TEXT("IRLMP(l%d,r%d): Tx LocTxCredit %d,RemoteTxCredit %d\n"),
              pLsapCb->LocalLsapSel, pLsapCb->RemoteLsapSel,
              pLsapCb->LocalTxCredit, pLsapCb->RemoteTxCredit));
}
/*****************************************************************************
*
*   @func   UINT | IrlmpAccessModeReq | Processes the IRLMP_ACCESSMODE_REQ
*
*   @rdesc  SUCCESS or an error code
*
*   @parm   IRLMP_LSAP_CB * | pLsapCb | pointer LSAP control block
*   @parm   IRDA_MSG * | pMsg | Pointer to an IRDA Message
*/
UINT
IrlmpAccessModeReq(IRLMP_LSAP_CB *pRequestingLsapCb, IRDA_MSG *pMsg)
{
    IRLMP_LSAP_CB   *pLsapCb;
    PIRLMP_LINK_CB  pIrlmpCb = pRequestingLsapCb->pIrlmpCb;
    
    PAGED_CODE();
    
    if (pIrlmpCb->LinkState != LINK_READY)
    {
        DEBUGMSG(DBG_ERROR, (TEXT("IRLMP: Link bad state %x\n"), 
                              pIrlmpCb->LinkState));        
        return IRLMP_LINK_BAD_STATE;
    }
    if (pRequestingLsapCb->State != LSAP_READY)
    {
        DEBUGMSG(DBG_ERROR, (TEXT("IRLMP: LSAP bad state %x\n"), 
                              pRequestingLsapCb->State));        
        return IRLMP_LSAP_BAD_STATE;
    }
    switch (pMsg->IRDA_MSG_AccessMode)
    {
      case IRLMP_EXCLUSIVE:
        if (pIrlmpCb->pExclLsapCb != NULL)
        {
            // Another LSAP has it already
            return IRLMP_IN_EXCLUSIVE_MODE;
        }
        for (pLsapCb = (IRLMP_LSAP_CB *) pIrlmpCb->LsapCbList.Flink;
             (LIST_ENTRY *) pLsapCb != &pIrlmpCb->LsapCbList;
             pLsapCb = (IRLMP_LSAP_CB *) pLsapCb->Linkage.Flink)
        {
            VALIDLSAP(pLsapCb);
            
            if (pLsapCb->State != LSAP_DISCONNECTED && 
                pLsapCb != pRequestingLsapCb)
            {
                return IRLMP_IN_MULTIPLEXED_MODE;
            }
        }
        
        // OK to request exclusive mode from peer
        pIrlmpCb->pExclLsapCb = pRequestingLsapCb;  
        
        if (pMsg->IRDA_MSG_IrLPTMode == TRUE)
        {
            pMsg->Prim = IRLMP_ACCESSMODE_CONF;
            pMsg->IRDA_MSG_AccessMode = IRLMP_EXCLUSIVE;
            pMsg->IRDA_MSG_ModeStatus = IRLMP_ACCESSMODE_SUCCESS;
            
            TdiUp(pRequestingLsapCb->TdiContext, pMsg);
            return SUCCESS;
        }
        else
        {
            pRequestingLsapCb->State = LSAP_EXCLUSIVEMODE_PEND;

            SendCntlPdu(pRequestingLsapCb, IRLMP_ACCESSMODE_PDU,
                        IRLMP_ABIT_REQUEST, IRLMP_RSVD_PARM,
                        IRLMP_EXCLUSIVE);

            IrdaTimerRestart(&pRequestingLsapCb->ResponseTimer);

        }        
        break;
        
      case IRLMP_MULTIPLEXED:
        if (pIrlmpCb->pExclLsapCb == NULL)
        {
            return IRLMP_IN_MULTIPLEXED_MODE;
        }
        if (pIrlmpCb->pExclLsapCb != pRequestingLsapCb)
        {
            return IRLMP_NOT_LSAP_IN_EXCLUSIVE_MODE;
        }

        if (pMsg->IRDA_MSG_IrLPTMode == TRUE)
        {
            pIrlmpCb->pExclLsapCb = NULL;
            pMsg->Prim = IRLMP_ACCESSMODE_CONF;
            pMsg->IRDA_MSG_AccessMode = IRLMP_MULTIPLEXED;
            pMsg->IRDA_MSG_ModeStatus = IRLMP_ACCESSMODE_SUCCESS;
            return TdiUp(pRequestingLsapCb->TdiContext,
                                   pMsg);
        }
        else
        {
            pRequestingLsapCb->State = LSAP_MULTIPLEXEDMODE_PEND;
        
            SendCntlPdu(pRequestingLsapCb, IRLMP_ACCESSMODE_PDU,
                        IRLMP_ABIT_REQUEST, IRLMP_RSVD_PARM,
                        IRLMP_MULTIPLEXED);     

            IrdaTimerRestart(&pRequestingLsapCb->ResponseTimer);            
        }
        break;
      default:
        return IRLMP_BAD_ACCESSMODE;
    }
    return SUCCESS;
}
/*****************************************************************************
*
*   @func   UINT | SendCntlPdu | Sends connect request to IRLAP
*
*   @rdesc  SUCCESS or an error code
*
*   @parm   IRLMP_LSAP_CB * | pLsapCb | pointer LSAP control block
*/
VOID
SendCntlPdu(IRLMP_LSAP_CB *pLsapCb, int OpCode, int ABit,
            int Parm1, int Parm2)
{
    IRDA_MSG            *pMsg = AllocIrdaBuf(IrdaMsgPool);
    IRLMP_HEADER        *pLMHeader;
    IRLMP_CNTL_FORMAT   *pCntlFormat;
    TTP_CONN_HEADER     *pTTPHeader;
    TTP_CONN_PARM       *pTTPParm;
    UINT                rc = SUCCESS;
    
    VALIDLSAP(pLsapCb);
    
    if (pMsg == NULL)
    {
        DEBUGMSG(DBG_ERROR, (TEXT("IRLMP: Alloc failed\n")));
        ASSERT(0);
        return;// IRLMP_ALLOC_FAILED;
    }

    pMsg->IRDA_MSG_SegFlags = SEG_LOCAL | SEG_FINAL;    

    // Initialize the header pointers to the end of the header block
    pMsg->IRDA_MSG_pHdrRead =
    pMsg->IRDA_MSG_pHdrWrite = pMsg->IRDA_MSG_Header + IRDA_HEADER_LEN;

    // Back up the read pointer for the LMP header
    pMsg->IRDA_MSG_pHdrRead -= (sizeof(IRLMP_HEADER) + \
                             sizeof(IRLMP_CNTL_FORMAT));
    
    // move it forward for non access mode control requests
    // (connect and disconnect don't have a Parm2)
    if (OpCode != IRLMP_ACCESSMODE_PDU)
    {
        pMsg->IRDA_MSG_pHdrRead++;
    }

    // If using Tiny TPP back up the read pointer for its header
    // From LMPs point of view this is where the user data begins.
    // We are sticking it in the header because TTP is now part of IRLMP.

    // TTP connect PDU's are only used for connection establishment
    if ((pLsapCb->Flags & LCBF_USE_TTP) && OpCode == IRLMP_CONNECT_PDU)
    {
        pMsg->IRDA_MSG_pHdrRead -= sizeof(TTP_CONN_HEADER);

        if (pLsapCb->RxMaxSDUSize > 0)
        {
            pMsg->IRDA_MSG_pHdrRead -= sizeof(TTP_CONN_PARM);
        }
    }

    // Build the IRLMP header
    pLMHeader = (IRLMP_HEADER *) pMsg->IRDA_MSG_pHdrRead;
    pLMHeader->DstLsapSel = (UCHAR) pLsapCb->RemoteLsapSel;
    pLMHeader->SrcLsapSel = (UCHAR) pLsapCb->LocalLsapSel;
    pLMHeader->CntlBit = IRLMP_CNTL_PDU;
    pLMHeader->RsvrdBit = 0;
    // Control portion of header
    pCntlFormat = (IRLMP_CNTL_FORMAT *) (pLMHeader + 1);
    pCntlFormat->OpCode = (UCHAR) OpCode; 
    pCntlFormat->ABit = (UCHAR) ABit;
    pCntlFormat->Parm1 = (UCHAR) Parm1;
    if (OpCode == IRLMP_ACCESSMODE_PDU)
    {
        pCntlFormat->Parm2 = (UCHAR) Parm2; // Access mode
    }
    
    // Build the TTP header if needed (we are using TTP and this is a
    // connection request or confirmation not).
    if ((pLsapCb->Flags & LCBF_USE_TTP) && OpCode == IRLMP_CONNECT_PDU)
    {
        // Always using the MaxSDUSize parameter. If the client wishes
        // to disable, MaxSDUSize = 0

        pTTPHeader = (TTP_CONN_HEADER *) (pCntlFormat + 1) - 1;
        // -1, LM-Connect-PDU doesn't use parm2

        /*
           #define TTP_PFLAG_NO_PARMS      0
           #define TTP_PFLAG_PARMS         1
        */

        pTTPHeader->ParmFlag = (pLsapCb->RxMaxSDUSize > 0);
        
        pTTPHeader->InitialCredit = (UCHAR) pLsapCb->RemoteTxCredit;
        
        pTTPParm = (TTP_CONN_PARM *) (pTTPHeader + 1);

        if (pLsapCb->RxMaxSDUSize > 0)
        {
            // HARDCODE-O-RAMA
            pTTPParm->PLen = 6;
            pTTPParm->PI = TTP_MAX_SDU_SIZE_PI;
            pTTPParm->PL = TTP_MAX_SDU_SIZE_PL;
            pTTPParm->PV[3] = (UCHAR) (pLsapCb->RxMaxSDUSize & 0xFF);
            pTTPParm->PV[2] = (UCHAR) ((pLsapCb->RxMaxSDUSize & 0xFF00)
                                      >> 8);
            pTTPParm->PV[1] = (UCHAR) ((pLsapCb->RxMaxSDUSize & 0xFF0000)
                                      >> 16);
            pTTPParm->PV[0] = (UCHAR) ((pLsapCb->RxMaxSDUSize & 0xFF000000)
                                      >> 24);
        }
        
    }

    // Client connection data, Access mode does not include client data
    if (pLsapCb->UserDataLen == 0 || OpCode == IRLMP_ACCESSMODE_PDU) 
    {
        pMsg->IRDA_MSG_pBase =
        pMsg->IRDA_MSG_pRead =
        pMsg->IRDA_MSG_pWrite =
        pMsg->IRDA_MSG_pLimit = NULL;
    }
    else
    {
        pMsg->IRDA_MSG_pBase = pLsapCb->UserData;
        pMsg->IRDA_MSG_pRead = pLsapCb->UserData;
        pMsg->IRDA_MSG_pWrite = pLsapCb->UserData + pLsapCb->UserDataLen;
        pMsg->IRDA_MSG_pLimit = pMsg->IRDA_MSG_pWrite;
    }

    // Message built, send data request to IRLAP
    pMsg->Prim = IRLAP_DATA_REQ;
    
    pMsg->IRDA_MSG_Expedited = TRUE;

    pMsg->IRDA_MSG_pOwner = 0;
    
    DEBUGMSG(DBG_IRLMP,(TEXT("IRLMP: Sending LM_%s_%s for l=%d,r=%d pMsg:%X LsapCb:%X\n"), 
                         (OpCode == IRLMP_CONNECT_PDU ? TEXT("CONNECT") :
                         OpCode == IRLMP_DISCONNECT_PDU ? TEXT("DISCONNECT") :
                         OpCode == IRLMP_ACCESSMODE_PDU ? TEXT("ACCESSMODE") :
                         TEXT("!!oops!!")), 
                         (ABit==IRLMP_ABIT_REQUEST?TEXT("REQ"):TEXT("CONF")),
                         pLsapCb->LocalLsapSel,
                         pLsapCb->RemoteLsapSel,
                         pMsg, pMsg->IRDA_MSG_pOwner));
    
    if (IrlapDown(pLsapCb->pIrlmpCb->pIrdaLinkCb->IrlapContext, 
                  pMsg) != SUCCESS)
    {
        DEBUGMSG(DBG_ERROR, (TEXT("IRLMP: IRLAP_DATA_REQUEST failed\n")));
        ASSERT(0);
    }                  
}
/*****************************************************************************
*
*   @func   UINT | LsapResponseTimerExp | Timer expiration callback
*
*   @rdesc  SUCCESS or an error code
*
*/
VOID
LsapResponseTimerExp(PVOID Context)
{
    IRLMP_LSAP_CB   *pLsapCb = (IRLMP_LSAP_CB *) Context;
    IRDA_MSG        IMsg;
    UINT            rc = SUCCESS;
    PIRLMP_LINK_CB  pIrlmpCb = pLsapCb->pIrlmpCb;
    
    PAGED_CODE();
    
    DEBUGMSG(DBG_IRLMP, (TEXT("IRLMP: LSAP timer expired\n")));

    VALIDLSAP(pLsapCb);
    
    switch (pLsapCb->State)
    {
      case LSAP_LMCONN_CONF_PEND:
        if (pLsapCb->LocalLsapSel == IAS_LSAP_SEL)
        {
            IasServerDisconnectReq(pLsapCb);
            break;
        }
        
        if (pLsapCb->LocalLsapSel == IAS_LOCAL_LSAP_SEL && 
            pLsapCb->RemoteLsapSel == IAS_LSAP_SEL)
        {
            IasClientDisconnectReq(pLsapCb, IRLMP_NO_RESPONSE_LSAP);
        }
        else
        {

            DeleteLsap(pLsapCb);
            
            IrdaTimerRestart(&pIrlmpCb->DiscDelayTimer);
        }
        break;

      case LSAP_CONN_RESP_PEND:
        pLsapCb->UserDataLen = 0; // This will ensure no client data sent in
                                   // Disconnect PDU below

        // Tell remote LSAP that its peer did not respond
        SendCntlPdu(pLsapCb,IRLMP_DISCONNECT_PDU,IRLMP_ABIT_REQUEST,
                    IRLMP_NO_RESPONSE_LSAP, 0);
        IrdaTimerRestart(&pIrlmpCb->DiscDelayTimer);

        DeleteLsap(pLsapCb);
        break;

      case LSAP_MULTIPLEXEDMODE_PEND:
        // Spec says peer can't refuse request to return to multiplex mode
        // but if no answer, go into multiplexed anyway?
      case LSAP_EXCLUSIVEMODE_PEND:
        pIrlmpCb->pExclLsapCb = NULL;
        // Peer didn't respond, maybe we are not connected anymore ???

        pLsapCb->State = LSAP_READY;
        
        IMsg.Prim = IRLMP_ACCESSMODE_CONF;
        IMsg.IRDA_MSG_AccessMode = IRLMP_MULTIPLEXED;
        IMsg.IRDA_MSG_ModeStatus = IRLMP_ACCESSMODE_FAILURE;
        TdiUp(pLsapCb->TdiContext, &IMsg);
        break;
         
      default:
        DEBUGMSG(DBG_IRLMP, (TEXT("Ignoring timer expiry in this state, %d\n"),pLsapCb->State));
        ; // ignore
    }
}
/*****************************************************************************
*
*   @func   UINT | IrlmpUp | Bottom of IRLMP, called by IRLAP with
*                             IRLAP messages. This is the MUX
*
*   @rdesc  SUCCESS or an error code
*
*/
UINT
IrlmpUp(PIRDA_LINK_CB pIrdaLinkCb, IRDA_MSG *pMsg)
{
    PIRLMP_LINK_CB  pIrlmpCb = (PIRLMP_LINK_CB) pIrdaLinkCb->IrlmpContext;

    switch (pMsg->Prim)
    {
      case IRLAP_DISCOVERY_IND:
        UpdateDeviceList(pIrlmpCb, pMsg->IRDA_MSG_pDevList);
        /*
        TDI ignores this
        pMsg->Prim = IRLMP_DISCOVERY_IND;
        pMsg->IRDA_MSG_pDevList = &DeviceList;
        TdiUp(NULL, pMsg);
        */
        return SUCCESS;

      case IRLAP_DISCOVERY_CONF:
        IrlapDiscoveryConf(pIrlmpCb, pMsg);
        return SUCCESS;

      case IRLAP_CONNECT_IND:
        IrlapConnectInd(pIrlmpCb, pMsg);
        return SUCCESS;

      case IRLAP_CONNECT_CONF:
        IrlapConnectConf(pIrlmpCb, pMsg);
        return SUCCESS;

      case IRLAP_DISCONNECT_IND:
        IrlapDisconnectInd(pIrlmpCb, pMsg);
        return SUCCESS;

      case IRLAP_DATA_CONF:
        IrlapDataConf(pMsg);
        return SUCCESS;

      case IRLAP_DATA_IND:
        IrlapDataInd(pIrlmpCb, pMsg);
        if (pIrlmpCb->pExclLsapCb &&
            pIrlmpCb->pExclLsapCb->RemoteTxCredit <=0)
            return IRLMP_LOCAL_BUSY;
        else
            return SUCCESS;

      case IRLAP_UDATA_IND:
        ASSERT(0);
        return SUCCESS;
        
      case IRLAP_STATUS_IND:
        TdiUp(NULL, pMsg);
        return SUCCESS;
    }
    return SUCCESS;
}

/*****************************************************************************
*
*   @func   UINT | IrlapDiscoveryConf | Process the discovery confirm
*/
VOID
IrlapDiscoveryConf(PIRLMP_LINK_CB pIrlmpCb, IRDA_MSG *pMsg)
{
    DEBUGMSG(DBG_DISCOVERY, (TEXT("IRLMP: IRLAP_DISCOVERY_CONF\n")));
    
    if (pIrlmpCb->LinkState != LINK_IN_DISCOVERY)
    {
        DEBUGMSG(DBG_ERROR, (TEXT("IRLMP: Link bad state\n")));

        ASSERT(pIrlmpCb->LinkState == LINK_DOWN);
        
        return;// IRLMP_LINK_BAD_STATE;
    }

    pIrlmpCb->LinkState = LINK_DISCONNECTED;

    if (pMsg->IRDA_MSG_DscvStatus == IRLAP_DISCOVERY_COMPLETED)
    {
        UpdateDeviceList(pIrlmpCb, pMsg->IRDA_MSG_pDevList);
    }
    
    // Initiate discovery on next link
    IrdaEventSchedule(&EvDiscoveryReq, NULL);

    // Initiate a connection if one was requested while in discovery
    ScheduleConnectReq(pIrlmpCb);    
}

void
AddDeviceToGlobalList(IRDA_DEVICE *pNewDevice)
{
    IRDA_DEVICE     *pDevice;
    
    for (pDevice = (IRDA_DEVICE *) gDeviceList.Flink;
         (LIST_ENTRY *) pDevice != &gDeviceList;
         pDevice = (IRDA_DEVICE *) pDevice->Linkage.Flink)
    {
        if (pNewDevice->DscvInfoLen == pDevice->DscvInfoLen &&
            CTEMemCmp(pNewDevice->DevAddr, pDevice->DevAddr,
               IRDA_DEV_ADDR_LEN) == 0 &&
            CTEMemCmp(pNewDevice->DscvInfo, pDevice->DscvInfo,
               (ULONG) pNewDevice->DscvInfoLen) == 0)
        {
            // Device is already in the global list
            
            return;
        }
    }     
    
    if (IRDA_ALLOC_MEM(pDevice, sizeof(IRDA_DEVICE), MT_IRLMP_DEVICE) != NULL)
    {
        RtlCopyMemory(pDevice, pNewDevice, sizeof(IRDA_DEVICE));
        InsertHeadList(&gDeviceList, &pDevice->Linkage);
    }    
}

void
DeleteDeviceFromGlobalList(IRDA_DEVICE *pOldDevice)
{
    IRDA_DEVICE     *pDevice;
    
    for (pDevice = (IRDA_DEVICE *) gDeviceList.Flink;
         (LIST_ENTRY *) pDevice != &gDeviceList;
         pDevice = (IRDA_DEVICE *) pDevice->Linkage.Flink)
    {
        if (pOldDevice->DscvInfoLen == pDevice->DscvInfoLen &&
            CTEMemCmp(pOldDevice->DevAddr, pDevice->DevAddr,
               IRDA_DEV_ADDR_LEN) == 0 &&
            CTEMemCmp(pOldDevice->DscvInfo, pDevice->DscvInfo,
               (ULONG) pOldDevice->DscvInfoLen) == 0)
        {
            RemoveEntryList(&pDevice->Linkage);
            IRDA_FREE_MEM(pDevice);
            return;
        }
    }     
}

/*****************************************************************************
*
*   @func   void | UpdateDeviceList | Determines if new devices need to be
*                                  added or old ones removed from the device
*                                  list maintained by IRLMP
*
*   @parm   LIST_ENTRY * | pDevList | pointer to a list of devices
*/
/*void
UpdateDeviceList(PIRLMP_LINK_CB pIrlmpCb, LIST_ENTRY *pNewDevList)
{
    IRDA_DEVICE     *pNewDevice;
    IRDA_DEVICE     *pOldDevice;
    IRDA_DEVICE     *pDevice;
    BOOLEAN         DeviceInList;
    KIRQL           OldIrql;
    
    KeAcquireSpinLock(&gSpinLock, &OldIrql);
    
    // Add new devices, set not seen count to zero if devices is
    // seen again
    for (pNewDevice = (IRDA_DEVICE *) pNewDevList->Flink;
         (LIST_ENTRY *) pNewDevice != pNewDevList;
         pNewDevice = (IRDA_DEVICE *) pNewDevice->Linkage.Flink)
    {
        DeviceInList = FALSE;
        
        AddDeviceToGlobalList(pNewDevice);

        for (pOldDevice = (IRDA_DEVICE *) pIrlmpCb->DeviceList.Flink;
             (LIST_ENTRY *) pOldDevice != &pIrlmpCb->DeviceList;
             pOldDevice = (IRDA_DEVICE *) pOldDevice->Linkage.Flink)
        {
            if (pNewDevice->DscvInfoLen == pOldDevice->DscvInfoLen &&
                RtlCompareMemory(pNewDevice->DevAddr, pOldDevice->DevAddr,
                          IRDA_DEV_ADDR_LEN) == IRDA_DEV_ADDR_LEN &&
                RtlCompareMemory(pNewDevice->DscvInfo, pOldDevice->DscvInfo,
                          pNewDevice->DscvInfoLen) == 
                          (ULONG) pNewDevice->DscvInfoLen)
            {
                DeviceInList = TRUE;
                pOldDevice->NotSeenCnt = -1; // reset not seen count
                                             // will be ++'d to 0 below
                break;
            }
        }
        if (!DeviceInList)
        {
            // Create an new entry in the list maintained by IRLMP
            IRDA_ALLOC_MEM(pDevice, sizeof(IRDA_DEVICE), MT_IRLMP_DEVICE);

            RtlCopyMemory(pDevice, pNewDevice, sizeof(IRDA_DEVICE));
            pDevice->NotSeenCnt = -1; // will be ++'d to 0 below
            InsertHeadList(&pIrlmpCb->DeviceList, &pDevice->Linkage);
        }
    }

    // Run through the list and remove devices that haven't
    // been seen for awhile

    pOldDevice = (IRDA_DEVICE *) pIrlmpCb->DeviceList.Flink;

    while ((LIST_ENTRY *) pOldDevice != &pIrlmpCb->DeviceList)
    {
        pDevice = (IRDA_DEVICE *) pOldDevice->Linkage.Flink;

        if (++(pOldDevice->NotSeenCnt) >= IRLMP_NOT_SEEN_THRESHOLD)
        {
            DeleteDeviceFromGlobalList(pOldDevice);
            
            RemoveEntryList(&pOldDevice->Linkage);
            IRDA_FREE_MEM(pOldDevice);
        }
        pOldDevice = pDevice; // next
    }
    
    KeReleaseSpinLock(&gSpinLock, OldIrql);
}*/
void
UpdateDeviceList(PIRLMP_LINK_CB pIrlmpCb, LIST_ENTRY *pNewDevList)
{
    IRDA_DEVICE     *pNewDevice;
    IRDA_DEVICE     *pOldDevice;
    IRDA_DEVICE     *pDevice;
    BOOLEAN         DeviceInList;
    KIRQL           OldIrql;
    
    KeAcquireSpinLock(&gSpinLock, &OldIrql);
    
    // Add new devices, set not seen count to zero if devices is
    // seen again
    for (pNewDevice = (IRDA_DEVICE *) pNewDevList->Flink;
         (LIST_ENTRY *) pNewDevice != pNewDevList;
         pNewDevice = (IRDA_DEVICE *) pNewDevice->Linkage.Flink)
    {
        DeviceInList = FALSE;
        
        AddDeviceToGlobalList(pNewDevice);

        for (pOldDevice = (IRDA_DEVICE *) pIrlmpCb->DeviceList.Flink;
             (LIST_ENTRY *) pOldDevice != &pIrlmpCb->DeviceList;
             pOldDevice = (IRDA_DEVICE *) pOldDevice->Linkage.Flink)
        {
            if (pNewDevice->DscvInfoLen == pOldDevice->DscvInfoLen &&
                CTEMemCmp(pNewDevice->DevAddr, pOldDevice->DevAddr,
                          IRDA_DEV_ADDR_LEN) == 0 &&
                CTEMemCmp(pNewDevice->DscvInfo, pOldDevice->DscvInfo,
                          (ULONG) pNewDevice->DscvInfoLen) == 0)
            {
                DeviceInList = TRUE;
                pOldDevice->NotSeenCnt = -1; // reset not seen count
                                             // will be ++'d to 0 below
                break;
            }
        }
        if (!DeviceInList)
        {
            // Create an new entry in the list maintained by IRLMP
            IRDA_ALLOC_MEM(pDevice, sizeof(IRDA_DEVICE), MT_IRLMP_DEVICE);

            if (pDevice)
            {
                RtlCopyMemory(pDevice, pNewDevice, sizeof(IRDA_DEVICE));
                pDevice->NotSeenCnt = -1; // will be ++'d to 0 below
                InsertHeadList(&pIrlmpCb->DeviceList, &pDevice->Linkage);
            }    
        }
    }

    // Run through the list and remove devices that haven't
    // been seen for awhile

    pOldDevice = (IRDA_DEVICE *) pIrlmpCb->DeviceList.Flink;

    while ((LIST_ENTRY *) pOldDevice != &pIrlmpCb->DeviceList)
    {
        pDevice = (IRDA_DEVICE *) pOldDevice->Linkage.Flink;

        pOldDevice->NotSeenCnt += 1;
        
        if (pOldDevice->NotSeenCnt == 1 || pOldDevice->NotSeenCnt == 2)
        {
            pIrlmpCb->DiscoveryFlags = DF_NO_SENSE_DSCV;
        }
        else if (pOldDevice->NotSeenCnt > 2)
        {
            DeleteDeviceFromGlobalList(pOldDevice);
            
            RemoveEntryList(&pOldDevice->Linkage);
            IRDA_FREE_MEM(pOldDevice);
        }
        pOldDevice = pDevice; // next
    }
    
    KeReleaseSpinLock(&gSpinLock, OldIrql);
}
/*****************************************************************************
*
*   @func   UINT | IrlapConnectInd | Process the connect indication from LAP
*
*   @rdesc  SUCCESS or an error code
*
*   @parm   IRDA_MSG * | pMsg | Pointer to an IRDA Message
*/
VOID
IrlapConnectInd(PIRLMP_LINK_CB pIrlmpCb, IRDA_MSG *pMsg)
{
    PAGED_CODE();
    
    if (pIrlmpCb->LinkState != LINK_DISCONNECTED)
    {
        ASSERT(0);
        return;
    }
    
    pIrlmpCb->LinkState = LINK_CONNECTING;
    
    RtlCopyMemory(pIrlmpCb->ConnDevAddr, pMsg->IRDA_MSG_RemoteDevAddr,
           IRDA_DEV_ADDR_LEN);
    RtlCopyMemory(&pIrlmpCb->NegotiatedQOS, pMsg->IRDA_MSG_pQos,
           sizeof(IRDA_QOS_PARMS));
    pIrlmpCb->MaxPDUSize = IrlapGetQosParmVal(vDataSizeTable,
                               pMsg->IRDA_MSG_pQos->bfDataSize, NULL)
                             - sizeof(IRLMP_HEADER) 
                             - sizeof(TTP_DATA_HEADER)
                             - 2 // size of irlap header
                             - 1; // IrComm

    pIrlmpCb->WindowSize = IrlapGetQosParmVal(vWinSizeTable,
                              pMsg->IRDA_MSG_pQos->bfWindowSize, NULL);

    DEBUGMSG(DBG_IRLMP, (TEXT("IRLMP: Connect indication, MaxPDU = %d\n"),
                          pIrlmpCb->MaxPDUSize));
    
    // schedule the response to occur on a differnt thread
    pIrlmpCb->AcceptConnection = TRUE;            
    IrdaEventSchedule(&EvConnectResp, pIrlmpCb->pIrdaLinkCb);
}
/*****************************************************************************
*
*   @func   UINT | IrlapConnectConf | Processes the connect confirm
*
*   @rdesc  SUCCESS or an error code
*
*   @parm   IRDA_MSG * | pMsg | Pointer to an IRDA Message
*/
VOID
IrlapConnectConf(PIRLMP_LINK_CB pIrlmpCb, IRDA_MSG *pMsg)
{
    PAGED_CODE();
    
    ASSERT(pIrlmpCb->LinkState == LINK_CONNECTING);

    // Currently, the connect confirm always returns a successful status
    ASSERT(pMsg->IRDA_MSG_ConnStatus == IRLAP_CONNECTION_COMPLETED);

    // Update Link
    pIrlmpCb->LinkState = LINK_READY;
    RtlCopyMemory(&pIrlmpCb->NegotiatedQOS, pMsg->IRDA_MSG_pQos,
            sizeof(IRDA_QOS_PARMS));
    pIrlmpCb->MaxPDUSize =  IrlapGetQosParmVal(vDataSizeTable,
                                      pMsg->IRDA_MSG_pQos->bfDataSize, NULL)
                             - sizeof(IRLMP_HEADER) 
                             - sizeof(TTP_DATA_HEADER)
                             - 2 // size of irlap header
                             - 1; // IrComm

    DEBUGMSG(DBG_IRLMP, (TEXT("IRLMP: IRLAP_CONNECT_CONF, TxMaxPDU = %d\n"),
                          pIrlmpCb->MaxPDUSize));
    
    IrdaEventSchedule(&EvLmConnectReq, pIrlmpCb->pIrdaLinkCb);
}
/*****************************************************************************
*
*   @func   UINT | IrlapDisconnectInd | Processes the disconnect indication
*
*   @rdesc  SUCCESS or an error code
*
*   @parm   IRDA_MSG * | pMsg | Pointer to an IRDA Message
*/
VOID
IrlapDisconnectInd(PIRLMP_LINK_CB pIrlmpCb, IRDA_MSG *pMsg)
{
    IRLMP_DISC_REASON   DiscReason = IRLMP_UNEXPECTED_IRLAP_DISC;
    
    PAGED_CODE();

    DEBUGMSG(DBG_IRLMP, (TEXT("IRLMP: IRLAP Disconnect Ind, status = %d\n"),
                          pMsg->IRDA_MSG_DiscStatus));
    
    switch (pIrlmpCb->LinkState)
    {
      case LINK_CONNECTING:
        if (pMsg->IRDA_MSG_DiscStatus == MAC_MEDIA_BUSY)
        {
            DiscReason = IRLMP_MAC_MEDIA_BUSY;
        }
        else
        {
            DiscReason = IRLMP_IRLAP_CONN_FAILED;
        }
        
        // Fall through
      case LINK_READY:
        pIrlmpCb->LinkState = LINK_DISCONNECTED;
        TearDownConnections(pIrlmpCb, DiscReason);
        break;

      case LINK_DISCONNECTING:
        pIrlmpCb->LinkState = LINK_DISCONNECTED;
        // Initiate a connection if one was requested while disconnecting
        ScheduleConnectReq(pIrlmpCb);
        break;

      default:
        DEBUGMSG(1, (TEXT("Link STATE %d\n"), pIrlmpCb->LinkState));
        
        //ASSERT(0);
    }
}
/*****************************************************************************
*
*   @func   IRLMP_LSAP_CB * | GetLsapInState | returns the first occurance of
*                                              an LSAP in the specified state
*                                              as long as the link is in the
*                                              specified state.
*
*   @parm   int | LinkState | get LSAP only as long as link is in this state
*
*   @parm   int | LSAP | Return the LSAP that is in this state if InThisState
*                        is TRUE. Else return LSAP if it is not in this state
*                        if InThisState = FALSE
*
*   @parm   BOOLEAN | InThisState | TRUE return LSAP if in this state
*                                FALSE return LSAP if not in this state
*
*   @rdesc  pointer to an LSAP control block or NULL, if an LSAP is returned
*
*/
IRLMP_LSAP_CB *
GetLsapInState(PIRLMP_LINK_CB pIrlmpCb,
               int LinkState,
               int LSAPState,
               BOOLEAN InThisState)
{
    IRLMP_LSAP_CB *pLsapCb;
    
    PAGED_CODE();    

    // Only want to find an LSAP if the link is in the specified state
    if (pIrlmpCb->LinkState != LinkState)
    {
        return NULL;
    }

    for (pLsapCb = (IRLMP_LSAP_CB *) pIrlmpCb->LsapCbList.Flink;
         (LIST_ENTRY *) pLsapCb != &pIrlmpCb->LsapCbList;
         pLsapCb = (IRLMP_LSAP_CB *) pLsapCb->Linkage.Flink)
    {

        VALIDLSAP(pLsapCb);
        
        if ((pLsapCb->State == LSAPState && InThisState == TRUE) ||
            (pLsapCb->State != LSAPState && InThisState == FALSE))
        {
            return pLsapCb;
        }
    }

    return NULL;
}
/*****************************************************************************
*
*/
IRLMP_LINK_CB *
GetIrlmpCb(PUCHAR RemoteDevAddr)
{
    IRDA_DEVICE         *pDevice;
    PIRLMP_LINK_CB      pIrlmpCb;
    KIRQL               OldIrql;    
    PIRDA_LINK_CB       pIrdaLinkCb;
    
    KeAcquireSpinLock(&gSpinLock, &OldIrql);

    for (pIrdaLinkCb = (PIRDA_LINK_CB) IrdaLinkCbList.Flink;
         (LIST_ENTRY *) pIrdaLinkCb != &IrdaLinkCbList;
         pIrdaLinkCb = (PIRDA_LINK_CB) pIrdaLinkCb->Linkage.Flink)
    {
        pIrlmpCb = (PIRLMP_LINK_CB) pIrdaLinkCb->IrlmpContext;
    
        for (pDevice = (IRDA_DEVICE *) pIrlmpCb->DeviceList.Flink;
             (LIST_ENTRY *) pDevice != &pIrlmpCb->DeviceList;
             pDevice = (IRDA_DEVICE *) pDevice->Linkage.Flink)
        {
            if (CTEMemCmp(pDevice->DevAddr, RemoteDevAddr,
                          IRDA_DEV_ADDR_LEN) == 0)
            {
                KeReleaseSpinLock(&gSpinLock, OldIrql);
                        
                return pIrlmpCb;
            }
        }    
    }
    
    KeReleaseSpinLock(&gSpinLock, OldIrql);
    
    return NULL;
}
/*****************************************************************************
*
*   @func   UINT | DiscDelayTimerFunc | Timer expiration callback
*
*   @rdesc  SUCCESS or an error code
*
*/
VOID
DiscDelayTimerFunc(PVOID Context)
{
    IRLMP_LSAP_CB               *pLsapCb;
    IRDA_MSG                    IMsg;
    UINT                        rc = SUCCESS;
    PIRLMP_LINK_CB              pIrlmpCb = (PIRLMP_LINK_CB) Context;
    
    PAGED_CODE();

    DEBUGMSG(DBG_IRLMP, (TEXT("IRLMP: Link timer expired\n")));

    // The timer that expired is the disconnect delay timer. Bring
    // down link if no LSAP connection exists
    if (pIrlmpCb->LinkState == LINK_DISCONNECTED)
    {
        // already disconnected
        return;
    }
    
    // Search for an LSAP that is connected or coming up
    pLsapCb = (IRLMP_LSAP_CB *) pIrlmpCb->LsapCbList.Flink;
    while (&pIrlmpCb->LsapCbList != (LIST_ENTRY *) pLsapCb)
    {
        VALIDLSAP(pLsapCb);
        
        if (pLsapCb->State > LSAP_DISCONNECTED)
        {
            // Don't bring down link, an LSAP is connected or connecting
            return;
        }
        pLsapCb = (IRLMP_LSAP_CB *) pLsapCb->Linkage.Flink;
    }

    DEBUGMSG(DBG_IRLMP, (TEXT(
       "IRLMP: No LSAP connections, disconnecting link\n")));
    // No LSAP connections, bring it down if it is up
    if (pIrlmpCb->LinkState == LINK_READY)
    {
        pIrlmpCb->LinkState = LINK_DISCONNECTING;

        // Request IRLAP to disconnect the link
        IMsg.Prim = IRLAP_DISCONNECT_REQ;
        IrlapDown(pIrlmpCb->pIrdaLinkCb->IrlapContext, &IMsg);
    }

    return;
}
/*****************************************************************************
*
*   @func   UINT | IrlapDataConf | Processes the data confirm
*
*   @rdesc  SUCCESS or an error code
*
*   @parm   IRDA_MSG * | pMsg | Pointer to an IRDA Message
*/
VOID
IrlapDataConf(IRDA_MSG *pMsg)
{
    IRLMP_LSAP_CB   *pLsapCb = pMsg->IRDA_MSG_pOwner;
    IRDA_MSG        *pSegParentMsg;
    BOOLEAN         RequestFailed = FALSE;
    UINT            rc = SUCCESS;

    DEBUGMSG(DBG_IRLMP, (TEXT("IRLMP: Received IRLAP_DATA_CONF pMsg:%X LsapCb:%X\n"),
                pMsg, pMsg->IRDA_MSG_pOwner));

    if (pMsg->IRDA_MSG_DataStatus != IRLAP_DATA_REQUEST_COMPLETED)
    {
        RequestFailed = TRUE;
    }
         
    if (pMsg->IRDA_MSG_SegFlags & SEG_LOCAL)    
    {
        // Locally generated data request
        FreeIrdaBuf(IrdaMsgPool, pMsg);

        if (RequestFailed)
        {
            ; // LOG ERROR
        }
        
        return;
    }
    else
    {
        VALIDLSAP(pLsapCb);    
        
        if (pMsg->IRDA_MSG_SegCount == 0)
        {
            if (!RequestFailed)
            {
                pMsg->IRDA_MSG_DataStatus = IRLMP_DATA_REQUEST_COMPLETED;
            }
        }
        else
        {
            // A segmented message, get its Parent 
            pSegParentMsg = pMsg->DataContext;
            
            // Free the segment
            FreeIrdaBuf(IrdaMsgPool, pMsg);

            if (RequestFailed)
            {
                pSegParentMsg->IRDA_MSG_DataStatus = IRLMP_DATA_REQUEST_FAILED;
            }
            
            if (--(pSegParentMsg->IRDA_MSG_SegCount) != 0)
            {
                // Still outstanding segments
                goto done;
            }
            // No more segments, send DATA_CONF to client
            // First remove it from the LSAPs TxMsgList 
            RemoveEntryList(&pSegParentMsg->Linkage);

            pMsg = pSegParentMsg;
        }

        // If request fails for non-segmented messages, the IRLAP error is
        // returned            
        pMsg->Prim = IRLMP_DATA_CONF;

        TdiUp(pLsapCb->TdiContext, pMsg);
done:
        REFDEL(&pLsapCb->RefCnt, 'ATAD');
    }
}
/*****************************************************************************
*
*   @func   UINT | IrlapDataInd | process the data indication
*
*   @rdesc  SUCCESS or an error code
*
*   @parm   IRDA_MSG * | pMsg | Pointer to an IRDA Message
*
*/
VOID
IrlapDataInd(PIRLMP_LINK_CB pIrlmpCb, IRDA_MSG *pMsg)
{
    IRLMP_HEADER        *pLMHeader;
    IRLMP_CNTL_FORMAT   *pCntlFormat;
    UCHAR                *pCntlParm1;
    UCHAR                *pCntlParm2;

    if ((pMsg->IRDA_MSG_pWrite - pMsg->IRDA_MSG_pRead) < sizeof(IRLMP_HEADER))
    {
        ASSERT(0);
        
        DEBUGMSG(DBG_ERROR, (TEXT("IRLMP: Receive invalid data\n")));
        
        return; // IRLMP_DATA_IND_BAD_FRAME;
    }

    pLMHeader = (IRLMP_HEADER *) pMsg->IRDA_MSG_pRead;

    pMsg->IRDA_MSG_pRead += sizeof(IRLMP_HEADER);

    if (pLMHeader->CntlBit != IRLMP_CNTL_PDU)
    {
        LmPduData(pIrlmpCb, pMsg, (int) pLMHeader->DstLsapSel,
                  (int) pLMHeader->SrcLsapSel);        
    }
    else
    {
        pCntlFormat = (IRLMP_CNTL_FORMAT *) pMsg->IRDA_MSG_pRead;

        // Ensure the control format is included. As per errate, it is
        // valid to exclude the parameter (for LM-connects and only if
        // no user data)        
        if ((UCHAR *) pCntlFormat >= pMsg->IRDA_MSG_pWrite)
        {
            ASSERT(0);
            // Need at least the OpCode portion
            return;// IRLMP_DATA_IND_BAD_FRAME;
        }
        else
        {
            // Initialize control parameters (if exists) and point
            // to beginning of user data
            if (&(pCntlFormat->Parm1) >= pMsg->IRDA_MSG_pWrite)
            {
                pCntlParm1 = NULL;
                pCntlParm2 = NULL;
                pMsg->IRDA_MSG_pRead = &(pCntlFormat->Parm1); // ie none
            }
            else
            {
                pCntlParm1 = &(pCntlFormat->Parm1);
                pCntlParm2 = &(pCntlFormat->Parm2); // Access mode only
                pMsg->IRDA_MSG_pRead = &(pCntlFormat->Parm2); 
            }                
        }        

        switch (pCntlFormat->OpCode)
        {
          case IRLMP_CONNECT_PDU:
            if (pCntlFormat->ABit == IRLMP_ABIT_REQUEST)
            {
                // Connection Request LM-PDU
                LmPduConnectReq(pIrlmpCb, pMsg, 
                                (int) pLMHeader->DstLsapSel,
                                (int) pLMHeader->SrcLsapSel,
                                pCntlParm1);
            }
            else 
            {
                // Connection Confirm LM-PDU
                LmPduConnectConf(pIrlmpCb, pMsg, 
                                 (int) pLMHeader->DstLsapSel,
                                 (int) pLMHeader->SrcLsapSel,
                                 pCntlParm1);
            }
            break;

        case IRLMP_DISCONNECT_PDU:
            if (pCntlFormat->ABit != IRLMP_ABIT_REQUEST)
            {
                ; // LOG ERROR !!!
            }
            else
            {
                LmPduDisconnectReq(pIrlmpCb, pMsg,
                                   (int) pLMHeader->DstLsapSel,
                                   (int) pLMHeader->SrcLsapSel,
                                   pCntlParm1);
            }
            break;
            
          case IRLMP_ACCESSMODE_PDU:
            if (pCntlFormat->ABit == IRLMP_ABIT_REQUEST)
            {
                LmPduAccessModeReq(pIrlmpCb,
                                   (int) pLMHeader->DstLsapSel,
                                   (int) pLMHeader->SrcLsapSel,
                                   pCntlParm1, pCntlParm2);
            }
            else
            {
                LmPduAccessModeConf(pIrlmpCb,
                                    (int) pLMHeader->DstLsapSel,
                                    (int) pLMHeader->SrcLsapSel,
                                    pCntlParm1, pCntlParm2);
            }
            break;
        }
    }
}
/*****************************************************************************
*
*   @func   UINT | LmPduConnectReq | Process the received connect
*                                        request LM-PDU
*
*   @rdesc  SUCCESS or an error code
*
*   @parm   IRDA_MSG * | pMsg | pointer to an IRDA message
*           int | LocalLsapSel | The local LSAP selector, 
*                                 (destination LSAP-SEL in message)
*           int | RemoteLsapSel | The remote LSAP selector,
*                                  (source LSAP-SEL in message)
*           UCHAR * | pRsvdByte | pointer to the reserved parameter
*/
VOID
LmPduConnectReq(PIRLMP_LINK_CB pIrlmpCb, IRDA_MSG *pMsg,
                int LocalLsapSel, int RemoteLsapSel, UCHAR *pRsvdByte)
{
    IRDA_MSG                IMsg;
    IRLMP_LSAP_CB           *pLsapCb = GetLsap(pIrlmpCb,
                                               LocalLsapSel, RemoteLsapSel);
    IRLMP_REGISTERED_LSAP   *pRegLsap;
    BOOLEAN                 LsapRegistered = FALSE;
    
    PAGED_CODE();    
    
    DEBUGMSG((DBG_IRLMP | DBG_IRLMP_CONN), 
             (TEXT("IRLMP: Received LM_CONNECT_REQ for l=%d,r=%d\n"),
              LocalLsapSel, RemoteLsapSel));
    
    if (pRsvdByte != NULL && *pRsvdByte != 0x00)
    {
        // LOG ERROR (bad parm value)
        ASSERT(0);
        return;
    }       

    if (LocalLsapSel == IAS_LSAP_SEL)
    {
        IasConnectReq(pIrlmpCb, RemoteLsapSel);
        return;
    }

    if (pLsapCb == NULL) // Usually NULL, unless receiving 2nd ConnReq
    {
        // No reason to except connection if an LSAP hasn't been registered
        for (pRegLsap = (IRLMP_REGISTERED_LSAP *)
             RegisteredLsaps.Flink;
             (LIST_ENTRY *) pRegLsap != &RegisteredLsaps;
             pRegLsap = (IRLMP_REGISTERED_LSAP *) pRegLsap->Linkage.Flink)
        {
            if (pRegLsap->Lsap == LocalLsapSel)
            {
                LsapRegistered = TRUE;
                break;
            }
        }
        if (!LsapRegistered)
        {
            // No LSAP exists which matches the requested LSAP in the connect
            // packet. IRLMP will decline this connection
            UnroutableSendLMDisc(pIrlmpCb, LocalLsapSel, RemoteLsapSel);
            return;
        }
        else
        {
            // Create a new one
            if (CreateLsap(pIrlmpCb, &pLsapCb) != SUCCESS)
            {
                ASSERT(0);
                return;
            }
            pLsapCb->Flags |= pRegLsap->Flags;
            pLsapCb->TdiContext = NULL;
        }

        // very soon this LSAP will be waiting for a connect response
        // from the upper layer
        pLsapCb->State = LSAP_CONN_RESP_PEND;
    
        pLsapCb->LocalLsapSel = LocalLsapSel;
        pLsapCb->RemoteLsapSel = RemoteLsapSel;
        pLsapCb->UserDataLen = 0;

        SetupTtpAndStoreConnData(pLsapCb, pMsg);

        // Now setup the message to send to the client notifying him
        // of a incoming connection indication
        IMsg.Prim = IRLMP_CONNECT_IND;

        RtlCopyMemory(IMsg.IRDA_MSG_RemoteDevAddr, pIrlmpCb->ConnDevAddr,
               IRDA_DEV_ADDR_LEN);
        IMsg.IRDA_MSG_LocalLsapSel = LocalLsapSel;
        IMsg.IRDA_MSG_RemoteLsapSel = RemoteLsapSel;
        IMsg.IRDA_MSG_pQos = &pIrlmpCb->NegotiatedQOS;
        if (pLsapCb->UserDataLen != 0)
        {
            IMsg.IRDA_MSG_pConnData = pLsapCb->UserData;
            IMsg.IRDA_MSG_ConnDataLen = pLsapCb->UserDataLen;
        }
        else
        {
            IMsg.IRDA_MSG_pConnData = NULL;
            IMsg.IRDA_MSG_ConnDataLen = 0;
        }
    
        IMsg.IRDA_MSG_pContext = pLsapCb;
        IMsg.IRDA_MSG_MaxSDUSize = pLsapCb->TxMaxSDUSize;
        IMsg.IRDA_MSG_MaxPDUSize = pIrlmpCb->MaxPDUSize;

        // The LSAP response timer is the time that we give the Client
        // to respond to this connect indication. If it expires before
        // the client responds, then IRLMP will decline the connect
        IrdaTimerRestart(&pLsapCb->ResponseTimer);
                
        TdiUp(pLsapCb->TdiContext, &IMsg);
        return;
    }
    else
    {
        ASSERT(0);
    }    
    // Ignoring if LSAP already exists
}
/*****************************************************************************
*
*   @func   UINT | LmPduConnectConf | Process the received connect
*                                         confirm LM-PDU
*
*   @rdesc  SUCCESS or an error code
*
*   @parm   IRDA_MSG * | pMsg | pointer to an IRDA message
*           int | LocalLsapSel | The local LSAP selector, 
*                                 (destination LSAP-SEL in message)
*           int | RemoteLsapSel | The remote LSAP selector,
*                                  (source LSAP-SEL in message)
*           BYTE * | pRsvdByte | pointer to the reserved byte parameter
*/
VOID
LmPduConnectConf(PIRLMP_LINK_CB pIrlmpCb,
                 IRDA_MSG *pMsg, int LocalLsapSel, int RemoteLsapSel,
                 UCHAR *pRsvdByte)
{
    IRLMP_LSAP_CB   *pLsapCb = GetLsap(pIrlmpCb,
                                       LocalLsapSel, RemoteLsapSel);

    PAGED_CODE();

    DEBUGMSG((DBG_IRLMP | DBG_IRLMP_CONN), 
             (TEXT("IRLMP: Received LM_CONNECT_CONF for l=%d,r=%d\n"),
              LocalLsapSel, RemoteLsapSel));

    if (pRsvdByte != NULL && *pRsvdByte != 0x00)
    {
        // LOG ERROR, indicate bad parm
        return;
    }

    if (pLsapCb == NULL)
    {
        // This is a connect confirm to a non-existant LSAP
        // LOG SOMETHING HERE !!!
        return; 
    }

    if (pLsapCb->State != LSAP_LMCONN_CONF_PEND)
    {
        // received unsolicited confirm
        // probably timed out
        return;
    }

    IrdaTimerStop(&pLsapCb->ResponseTimer);
    
    pLsapCb->State = LSAP_READY;
    
    if (LocalLsapSel == IAS_LOCAL_LSAP_SEL && RemoteLsapSel == IAS_LSAP_SEL)
    {
        SendGetValueByClassReq(pLsapCb);
        return;
    }
    else
    {
        SetupTtpAndStoreConnData(pLsapCb, pMsg);
    
        pMsg->Prim = IRLMP_CONNECT_CONF;
            
        pMsg->IRDA_MSG_pQos = &pIrlmpCb->NegotiatedQOS;
        if (pLsapCb->UserDataLen != 0)
        {
            pMsg->IRDA_MSG_pConnData = pLsapCb->UserData;
            pMsg->IRDA_MSG_ConnDataLen = pLsapCb->UserDataLen;
        }
        else
        {
            pMsg->IRDA_MSG_pConnData = NULL;
            pMsg->IRDA_MSG_ConnDataLen = 0;
        }
        
        pMsg->IRDA_MSG_pContext = pLsapCb;
        pMsg->IRDA_MSG_MaxSDUSize = pLsapCb->TxMaxSDUSize;
        pMsg->IRDA_MSG_MaxPDUSize = pIrlmpCb->MaxPDUSize;

        TdiUp(pLsapCb->TdiContext, pMsg);
    }
}
/*****************************************************************************
*/
VOID
SetupTtpAndStoreConnData(IRLMP_LSAP_CB *pLsapCb, IRDA_MSG *pMsg)
{
    TTP_CONN_HEADER *pTTPHeader;
    UCHAR            PLen, *pEndParms, PI, PL;

    PAGED_CODE();
    
    VALIDLSAP(pLsapCb);
    
    // Upon entering this function, the pRead pointer points to the
    // TTP header or the beginning of client data

    if (!(pLsapCb->Flags & LCBF_USE_TTP))
    {
        pLsapCb->TxMaxSDUSize = pLsapCb->pIrlmpCb->MaxPDUSize;
    }
    else
    {
        if (pMsg->IRDA_MSG_pRead >= pMsg->IRDA_MSG_pWrite)
        {
            // THIS IS AN ERROR, WE ARE USING TTP. There is no more
            // data in the frame, but we need the TTP header
            // SOME KIND OF WARNING SHOULD BE LOGGED
            return;
        }
        pTTPHeader = (TTP_CONN_HEADER *) pMsg->IRDA_MSG_pRead;
        pLsapCb->LocalTxCredit = (int) pTTPHeader->InitialCredit;

        DEBUGMSG(DBG_IRLMP | DBG_IRLMP_CRED, (TEXT("IRLMP: Initial LocalTxCredit %d\n"),
                              pLsapCb->LocalTxCredit));
                              
        // advance the pointer to the first byte of data
        pMsg->IRDA_MSG_pRead += sizeof(TTP_CONN_HEADER);
        
        pLsapCb->TxMaxSDUSize = 0;
        if (pTTPHeader->ParmFlag == TTP_PFLAG_PARMS)
        {
            // Parameter carrying Connect TTP-PDU
            PLen = *pMsg->IRDA_MSG_pRead++;
            pEndParms = pMsg->IRDA_MSG_pRead + PLen;
        
            // NOTE: This breaks if PI other than MaxSDUSize!!!

            if (PLen < 3 || pEndParms > pMsg->IRDA_MSG_pWrite)
            {
                // LOG ERROR !!!
                return;
            }
            
            PI = *pMsg->IRDA_MSG_pRead++;
            PL = *pMsg->IRDA_MSG_pRead++;
            
            if (PI != TTP_MAX_SDU_SIZE_PI)
            {
                // LOG ERROR !!!
                return;
            }

            for ( ; PL != 0 ; PL--)
            {
                pLsapCb->TxMaxSDUSize <<= 8;
                pLsapCb->TxMaxSDUSize += (int) (*pMsg->IRDA_MSG_pRead);
                pMsg->IRDA_MSG_pRead++;
            }
        }
    }
    
    // if there is any user data with this connection request/conf, place
    // it in the control block. This is just a place to store this
    // information while upper layer is looking at it. It may be over-
    // written by connection data in the response from the client.
    pLsapCb->UserDataLen = 0;
    /*
    
    NOTE: IF USER CONNECTION DATA IS EVER SUPPORTED, VERIFY DATA
          WON'T OVERFLOW UserData BUFFER
          
    if (pMsg->IRDA_MSG_pRead < pMsg->IRDA_MSG_pWrite)
    {
        pLsapCb->UserDataLen = (UINT) (pMsg->IRDA_MSG_pWrite - pMsg->IRDA_MSG_pRead);
        RtlCopyMemory(pLsapCb->UserData, pMsg->IRDA_MSG_pRead,
               pLsapCb->UserDataLen);
    }
    */

    return;
}
/*****************************************************************************
*
*   @func   UINT | LmPduDisconnectReq | Process the received discconnect
*                                           request LM-PDU
*
*   @rdesc  SUCCESS or an error code
*
*   @parm   IRDA_MSG * | pMsg | pointer to an IRDA message
*           int | LocalLsapSel | The local LSAP selector, 
*                                 (destination LSAP-SEL in message)
*           int | RemoteLsapSel | The remote LSAP selector,
*                                  (source LSAP-SEL in message)
*           BYTE * | pReason | pointer to the reason parameter
*/
VOID
LmPduDisconnectReq(PIRLMP_LINK_CB pIrlmpCb, IRDA_MSG *pMsg,
                   int LocalLsapSel, int RemoteLsapSel, UCHAR *pReason)
{
    IRLMP_LSAP_CB       *pLsapCb;
    UINT                rc = SUCCESS;

    
    if (pReason == NULL)
    {
        ASSERT(0);
        return; // LOG ERROR !!! need reason code
    }

    pLsapCb = GetLsap(pIrlmpCb, LocalLsapSel, RemoteLsapSel);

    DEBUGMSG((DBG_IRLMP | DBG_IRLMP_CONN), 
             (TEXT("IRLMP: Received LM_DISCONNECT_REQ LsapCb:%x for l=%d,r=%d\n"),
              pLsapCb, LocalLsapSel, RemoteLsapSel));
    
    if (pLsapCb == NULL)
    {
        return;
    }

    if (pLsapCb->State == LSAP_LMCONN_CONF_PEND)
    {
        IrdaTimerStop(&pLsapCb->ResponseTimer);
    }

    IrdaTimerRestart(&pIrlmpCb->DiscDelayTimer);

    if (LocalLsapSel == IAS_LSAP_SEL)
    {
        IasServerDisconnectReq(pLsapCb);
        return;
    }
    if (LocalLsapSel == IAS_LOCAL_LSAP_SEL && RemoteLsapSel == IAS_LSAP_SEL)
    {
        IasClientDisconnectReq(pLsapCb, *pReason);
        return;
    }

    if (pLsapCb->State != LSAP_DISCONNECTED)
    {
        pLsapCb->UserDataLen = 0;

        /*
        
        NOTE: IF USER CONNECTION DATA IS EVER SUPPORTED, VERIFY DATA
              WON'T OVERFLOW UserData BUFFER
          
        if (pMsg->IRDA_MSG_pRead < pMsg->IRDA_MSG_pWrite)
        {
            // Disconnect User data
            pLsapCb->UserDataLen = (UINT) (pMsg->IRDA_MSG_pWrite - pMsg->IRDA_MSG_pRead);
            RtlCopyMemory(pLsapCb->UserData, pMsg->IRDA_MSG_pRead,
               pLsapCb->UserDataLen);
        }   
        */

        pLsapCb->DiscReason = *pReason;
        
        DeleteLsap(pLsapCb);
    }
}
/*****************************************************************************
*
*   @func   IRLMP_LSAP_CB *| GetLsap | For the LSAP selector pair, return the
*                                      LSAP control block they map to. NULL
*                                      if one does not exist
*
*   @rdesc  pointer to an LSAP control block or NULL
*
*   @parm   int | LocalLsapSel | local LSAP selector
*   @parm   int | RemoteLsapSel | Remote LSAP selector
*
*   if an LSAP is found, its critical section is acquired
*/
IRLMP_LSAP_CB *
GetLsap(PIRLMP_LINK_CB pIrlmpCb, int LocalLsapSel, int RemoteLsapSel)
{
    IRLMP_LSAP_CB *pLsapCb;

    for (pLsapCb = (IRLMP_LSAP_CB *) pIrlmpCb->LsapCbList.Flink;
         (LIST_ENTRY *) pLsapCb != &pIrlmpCb->LsapCbList;
         pLsapCb = (IRLMP_LSAP_CB *) pLsapCb->Linkage.Flink)
    {
        VALIDLSAP(pLsapCb);

        if (pLsapCb->LocalLsapSel == LocalLsapSel &&
            pLsapCb->RemoteLsapSel == RemoteLsapSel)
        {
            return pLsapCb;
        }
    }

    return NULL;
}
/*****************************************************************************
*
*   @func   UINT | SendCreditPdu | Send a dataless PDU to extend credit
*
*   @rdesc  SUCCESS or an error code
*
*   @parm   IRLMP_LSAP_CB * | pLsapCb | pointer to an LSAP control block
*/
VOID
SendCreditPdu(IRLMP_LSAP_CB *pLsapCb)
{
    IRDA_MSG    *pMsg;
    
    VALIDLSAP(pLsapCb);
    
    if (pLsapCb->AvailableCredit == 0)
    {
        // No credit to give
        return;
    }
    
    if ((pMsg = AllocIrdaBuf(IrdaMsgPool)) == NULL)
    {
        ASSERT(0);
        return;
    }

    // No Data
    pMsg->IRDA_MSG_pBase =
    pMsg->IRDA_MSG_pLimit =
    pMsg->IRDA_MSG_pRead = 
    pMsg->IRDA_MSG_pWrite = pMsg->IRDA_MSG_Header + IRDA_HEADER_LEN;

    pMsg->IRDA_MSG_IrCOMM_9Wire = FALSE;
    
    pMsg->IRDA_MSG_SegFlags = SEG_FINAL;
    
    FormatAndSendDataReq(pLsapCb, pMsg, TRUE, FALSE);
}
/*****************************************************************************
*
*   @func   VOID | LmPduData | Process the received data (indication)
*                                  LM-PDU
*
*   @rdesc  SUCCESS or an error code
*
*   @parm   IRDA_MSG * | pMsg | pointer to an IRDA message
*           int | LocalLsapSel | The local LSAP selector, 
*                                 (destination LSAP-SEL in message)
*           int | RemoteLsapSel | The remote LSAP selector,
*                                  (source LSAP-SEL in message)
*/
VOID
LmPduData(PIRLMP_LINK_CB pIrlmpCb, IRDA_MSG *pMsg,
          int LocalLsapSel, int RemoteLsapSel)
{
    IRLMP_LSAP_CB       *pLsapCb = GetLsap(pIrlmpCb,
                                           LocalLsapSel, RemoteLsapSel);
    TTP_DATA_HEADER     *pTTPHeader;
    BOOLEAN             DataPDUSent = FALSE;
    BOOLEAN             FinalSeg = TRUE;
   
    if (pLsapCb == NULL)
    {
        // Unroutable, send disconnect
        DEBUGMSG(DBG_IRLMP, (TEXT("IRLMP: Data sent to bad Lsap (%d,%d)\n"),
                    LocalLsapSel, RemoteLsapSel));
                    
        //UnroutableSendLMDisc(pIrlmpCb, LocalLsapSel, RemoteLsapSel);
        return;
    }

    if (LocalLsapSel == IAS_LSAP_SEL)
    {
        IasSendQueryResp(pLsapCb, pMsg);
        return;
    }
    if (LocalLsapSel == IAS_LOCAL_LSAP_SEL && RemoteLsapSel == IAS_LSAP_SEL)
    {
        IasProcessQueryResp(pIrlmpCb, pLsapCb, pMsg);
        return;
    }
    
    if (pLsapCb->Flags & LCBF_USE_TTP)
    {
        if (pMsg->IRDA_MSG_pRead >= pMsg->IRDA_MSG_pWrite)
        {
            DEBUGMSG(DBG_ERROR, (TEXT("IRLMP: Missing TTP Header!\n")));
            
            // NEED TTP HEADER, LOG ERROR !!!
            return;
        }
            
        pTTPHeader = (TTP_DATA_HEADER *) pMsg->IRDA_MSG_pRead;
        pMsg->IRDA_MSG_pRead += sizeof(TTP_DATA_HEADER);

        pLsapCb->LocalTxCredit += (int) pTTPHeader->AdditionalCredit;

        DEBUGMSG(DBG_IRLMP_CRED, 
                    (TEXT("IRLMP(l%d,r%d): Rx LocalTxCredit:+%d=%d\n"),
                    pLsapCb->LocalLsapSel, pLsapCb->RemoteLsapSel,                    
                    pTTPHeader->AdditionalCredit, pLsapCb->LocalTxCredit));

        if (pTTPHeader->MoreBit == TTP_MBIT_NOT_FINAL)
        {
            FinalSeg = FALSE;
        }
    }
    
    REFADD(&pLsapCb->RefCnt, ' DNI'); 

    if (pMsg->IRDA_MSG_pRead < pMsg->IRDA_MSG_pWrite)
    {
        // PDU containing data. Decrement remotes Tx Credit
        pLsapCb->RemoteTxCredit--;
        
        if (pLsapCb->State >= LSAP_READY)
        {
            pMsg->Prim = IRLMP_DATA_IND;
            pMsg->IRDA_MSG_SegFlags = FinalSeg ? SEG_FINAL : 0;
        
            TdiUp(pLsapCb->TdiContext, pMsg);
        }    
    }
    // else no user data, this was a dataless TTP-PDU to extend credit.
      
    if (pLsapCb->State != LSAP_DISCONNECTED)
    {      
        // Did we get some credit?
        if ((pLsapCb->Flags & LCBF_USE_TTP) && 
            pLsapCb->LocalTxCredit > 0 && 
            pLsapCb->State == LSAP_NO_TX_CREDIT)
        {
            DEBUGMSG(DBG_IRLMP, (TEXT("IRLMP: l%d,r%d flow on\n"),
                              pLsapCb->LocalLsapSel, pLsapCb->RemoteLsapSel));
        
            pLsapCb->State = LSAP_READY;
        }
        DEBUGMSG(DBG_IRLMP,
             (TEXT("IRLMP(l%d,r%d): Rx LocTxCredit %d,RemoteTxCredit %d\n"),
              pLsapCb->LocalLsapSel, pLsapCb->RemoteLsapSel,             
              pLsapCb->LocalTxCredit, pLsapCb->RemoteTxCredit));

        while (!IsListEmpty(&pLsapCb->SegTxMsgList) && 
               pLsapCb->State == LSAP_READY)
        {
            pMsg = (IRDA_MSG *) RemoveHeadList(&pLsapCb->SegTxMsgList);
        
            FormatAndSendDataReq(pLsapCb, pMsg, FALSE, FALSE);

            DataPDUSent = TRUE;
        }   
    
        // Do I need to extend credit to peer in a dataless PDU?
        if ((pLsapCb->Flags & LCBF_USE_TTP) && 
            !DataPDUSent && 
            pLsapCb->RemoteTxCredit <= pIrlmpCb->WindowSize + 1)
        {   
            SendCreditPdu(pLsapCb);
        }
    }  
    REFDEL(&pLsapCb->RefCnt, ' DNI');       
}
/*****************************************************************************
*
*   @func   UINT | LmPduAccessModeReq | process access mode request 
*                                           from peer
*
*   @rdesc  SUCCESS
*
*   @parm   int | LocalLsapSel | Local LSAP selector
*   @parm   int | LocalLsapSel | Local LSAP selector
*   @parm   BYTE * | pRsvdByte | Reserved byte in the Access mode PDU
*   @parm   BYTE * | pMode | Mode byte in Access mode PDU
*/
VOID
LmPduAccessModeReq(PIRLMP_LINK_CB pIrlmpCb,
                   int LocalLsapSel, int RemoteLsapSel, 
                   UCHAR *pRsvdByte, UCHAR *pMode)
{
    IRLMP_LSAP_CB   *pRequestedLsapCb = GetLsap(pIrlmpCb,
                                                 LocalLsapSel,RemoteLsapSel);
    IRLMP_LSAP_CB   *pLsapCb;
    IRDA_MSG        IMsg;
    
    if (pRequestedLsapCb==NULL || pRequestedLsapCb->State != LSAP_READY)
    {
        UnroutableSendLMDisc(pIrlmpCb, LocalLsapSel, RemoteLsapSel);
        return;
    }
    
    if (pRsvdByte == NULL || *pRsvdByte != 0x00 || pMode == NULL)
    {
        // LOG ERROR, indicate bad parm
        return;
    }
    
    switch (*pMode)
    {
      case IRLMP_EXCLUSIVE:
        if (pIrlmpCb->pExclLsapCb != NULL)
        {
            if (pIrlmpCb->pExclLsapCb == pRequestedLsapCb)
            {
                // Already has exclusive mode, confirm it again I guess
                // but I'm not telling my client again
                SendCntlPdu(pRequestedLsapCb, IRLMP_ACCESSMODE_PDU,
                            IRLMP_ABIT_CONFIRM, IRLMP_STATUS_SUCCESS,
                            IRLMP_EXCLUSIVE);
                return;
            }
            else
            {
                // This is what spec says...
                SendCntlPdu(pRequestedLsapCb, IRLMP_ACCESSMODE_PDU,
                            IRLMP_ABIT_CONFIRM, IRLMP_STATUS_FAILURE,
                            IRLMP_MULTIPLEXED);
                return;
            }
        }

        // Are there any other LSAPs connections? If so, NACK peer
        for (pLsapCb = (IRLMP_LSAP_CB *) pIrlmpCb->LsapCbList.Flink;
             (LIST_ENTRY *) pLsapCb != &pIrlmpCb->LsapCbList;
             pLsapCb = (IRLMP_LSAP_CB *) pLsapCb->Linkage.Flink)
        {
            if (pLsapCb->State != LSAP_DISCONNECTED && 
                pLsapCb != pRequestedLsapCb)
            {
                SendCntlPdu(pRequestedLsapCb, IRLMP_ACCESSMODE_PDU,
                            IRLMP_ABIT_CONFIRM, IRLMP_STATUS_FAILURE,
                            IRLMP_MULTIPLEXED);
                return;
            }
        }      
        // OK to go into exclusive mode
        pIrlmpCb->pExclLsapCb = pRequestedLsapCb;
        // Send confirmation to peer
        SendCntlPdu(pRequestedLsapCb, IRLMP_ACCESSMODE_PDU,
                    IRLMP_ABIT_CONFIRM, IRLMP_STATUS_SUCCESS,
                    IRLMP_EXCLUSIVE);
        // Notify client
        IMsg.Prim = IRLMP_ACCESSMODE_IND;
        IMsg.IRDA_MSG_AccessMode = IRLMP_EXCLUSIVE;
        TdiUp(pRequestedLsapCb->TdiContext, &IMsg);
        return;
        
      case IRLMP_MULTIPLEXED:
        if (pRequestedLsapCb != pIrlmpCb->pExclLsapCb)
        {
            // Log Error here
            return;
        }
        pIrlmpCb->pExclLsapCb = NULL;
        // Send confirmation to peer
        SendCntlPdu(pRequestedLsapCb, IRLMP_ACCESSMODE_PDU,
                    IRLMP_ABIT_CONFIRM, IRLMP_STATUS_SUCCESS,
                    IRLMP_MULTIPLEXED);
        // Notify client
        IMsg.Prim = IRLMP_ACCESSMODE_IND;
        IMsg.IRDA_MSG_AccessMode = IRLMP_MULTIPLEXED;
        TdiUp(pRequestedLsapCb->TdiContext, &IMsg);
        return;
        
      default:
        ASSERT(0);
    }
}
/*****************************************************************************
*
*   @func   UINT | LmPduAccessModeReq | process access mode request 
*                                           from peer
*
*   @rdesc  SUCCESS
*
*   @parm   int | LocalLsapSel | Local LSAP selector
*   @parm   int | LocalLsapSel | Local LSAP selector
*   @parm   BYTE * | pStatus | Status byte in the Access mode PDU
*   @parm   BYTE * | pMode | Mode byte in Access mode PDU
*/
VOID
LmPduAccessModeConf(PIRLMP_LINK_CB pIrlmpCb,
                    int LocalLsapSel, int RemoteLsapSel, 
                    UCHAR *pStatus, UCHAR *pMode)
{
    IRLMP_LSAP_CB   *pRequestedLsapCb = GetLsap(pIrlmpCb,
                                                 LocalLsapSel,RemoteLsapSel);
    IRDA_MSG        IMsg;

    DEBUGMSG(DBG_IRLMP, (TEXT("IRLMP: ACCESSMODE_CONF\r\n")));
    
    if (pRequestedLsapCb==NULL)
    {
        UnroutableSendLMDisc(pIrlmpCb, LocalLsapSel, RemoteLsapSel);
        return;
    }
    if (pStatus == NULL || pMode == NULL)
    {
        // LOG ERROR
        return;
    }
    
    switch (*pMode)
    {
      case IRLMP_EXCLUSIVE:
        if (pRequestedLsapCb != pIrlmpCb->pExclLsapCb || 
            pRequestedLsapCb->State != LSAP_EXCLUSIVEMODE_PEND)
        {
            // LOG ERROR
            return;
        }
        if (*pStatus != IRLMP_STATUS_SUCCESS)
        {
            pIrlmpCb->pExclLsapCb = NULL;
            return; // protocol error, 
                    // wouldn't have Exclusive mode != SUCCESS
        }
        else
        {
            pRequestedLsapCb->State = LSAP_READY;

            IMsg.Prim = IRLMP_ACCESSMODE_CONF;
            IMsg.IRDA_MSG_AccessMode = IRLMP_EXCLUSIVE;
            IMsg.IRDA_MSG_ModeStatus = IRLMP_ACCESSMODE_SUCCESS;

            TdiUp(pRequestedLsapCb->TdiContext, &IMsg);
            return;
        }
        
      case IRLMP_MULTIPLEXED:
        if (pRequestedLsapCb != pIrlmpCb->pExclLsapCb || 
            (pRequestedLsapCb->State != LSAP_EXCLUSIVEMODE_PEND &&
             pRequestedLsapCb->State != LSAP_MULTIPLEXEDMODE_PEND))
        {
            return;
        }

        pIrlmpCb->pExclLsapCb = NULL;
        pRequestedLsapCb->State = LSAP_READY;
            
        IMsg.Prim = IRLMP_ACCESSMODE_CONF;
        IMsg.IRDA_MSG_AccessMode = *pMode;
        if (*pStatus == IRLMP_STATUS_SUCCESS)
        {
            IMsg.IRDA_MSG_ModeStatus = IRLMP_ACCESSMODE_SUCCESS;
        }
        else
        {
            IMsg.IRDA_MSG_ModeStatus = IRLMP_ACCESSMODE_FAILURE;
        }            
        TdiUp(pRequestedLsapCb->TdiContext, &IMsg);
        return;
        
      default:
        ASSERT(0);
    }
}
/*****************************************************************************
*
*   @func   UINT | UnroutableSendLMDisc | Sends an LM-Disconnect to peer with
*                                         reason = "received LM packet on 
*                                         disconnected LSAP"
*   @parm   int | LocalLsapSel | the local LSAP selector in LM-PDU
*   @parm   int | RemoteLsapSel | the remote LSAP selector in LM-PDU
*/
VOID
UnroutableSendLMDisc(PIRLMP_LINK_CB pIrlmpCb, int LocalLsapSel, int RemoteLsapSel)
{
    IRLMP_LSAP_CB   FakeLsapCb;
    
    DEBUGMSG(DBG_ERROR, (TEXT("IRLMP: received unroutabled Pdu LocalLsap:%d RemoteLsap:%d\n"),
                        LocalLsapSel, RemoteLsapSel));
    FakeLsapCb.Flags            = 0;
    FakeLsapCb.LocalLsapSel     = LocalLsapSel;
    FakeLsapCb.RemoteLsapSel    = RemoteLsapSel;
    FakeLsapCb.UserDataLen      = 0;
    FakeLsapCb.pIrlmpCb         = pIrlmpCb;
#ifdef DBG    
    FakeLsapCb.Sig              = LSAPSIG;
#endif    
        
    SendCntlPdu(&FakeLsapCb,IRLMP_DISCONNECT_PDU,
                IRLMP_ABIT_REQUEST, IRLMP_DISC_LSAP, 0);
    return;
}

/*****************************************************************************
*
*   @func   UINT | InitiateDiscovoryReq | A deferred processing routine that sends
*                                    an IRLAP discovery request
*/
void
InitiateDiscoveryReq(PVOID Context)
{
    IRDA_MSG        IMsg;
    UINT            rc;
    PIRLMP_LINK_CB  pIrlmpCb = NULL;
    PIRLMP_LINK_CB  pIrlmpCb2 = NULL;
    PIRDA_LINK_CB   pIrdaLinkCb;
    KIRQL           OldIrql;
    BOOLEAN         ScheduleNextLink = TRUE;
    BOOLEAN         MediaSense = TRUE;

    KeAcquireSpinLock(&gSpinLock, &OldIrql);

    DEBUGMSG(DBG_DISCOVERY, (TEXT("IRLMP: InitDscvReq event\n")));
    
    // Find the next link to start discovery on
    for (pIrdaLinkCb = (PIRDA_LINK_CB) IrdaLinkCbList.Flink;
        (LIST_ENTRY *) pIrdaLinkCb != &IrdaLinkCbList;
         pIrdaLinkCb = (PIRDA_LINK_CB) pIrdaLinkCb->Linkage.Flink)    
    {
        pIrlmpCb2 = (PIRLMP_LINK_CB) pIrdaLinkCb->IrlmpContext;

        if (pIrlmpCb2->DiscoveryFlags)
        {
            if (pIrlmpCb2->DiscoveryFlags == DF_NO_SENSE_DSCV)
            {
                MediaSense = FALSE;
            }
            pIrlmpCb2->DiscoveryFlags = 0;
            pIrlmpCb = pIrlmpCb2;
            break;
        }
    }

    // No more links on which to discover, send confirm up
    if (pIrlmpCb == NULL)
    {        
        if (pIrlmpCb2 == NULL)
        {
            IMsg.IRDA_MSG_DscvStatus = IRLMP_NO_RESPONSE;        
        }
        else
        {
            IMsg.IRDA_MSG_DscvStatus = IRLAP_DISCOVERY_COMPLETED;        
        }
            
        DscvReqScheduled = FALSE;
        
        IMsg.Prim = IRLMP_DISCOVERY_CONF;
        IMsg.IRDA_MSG_pDevList = &gDeviceList;

        // Hold the spin lock to protect list while TDI is copying it.
        
        TdiUp(NULL, &IMsg);
        
        KeReleaseSpinLock(&gSpinLock, OldIrql);
                
        return;
    }
    
    // Add a reference so link won't be removed from underneath us here
    // (was happening coming out of hibernation)
    
    REFADD(&pIrlmpCb->pIrdaLinkCb->RefCnt, 'VCSD');

    KeReleaseSpinLock(&gSpinLock, OldIrql);    

    LOCK_LINK(pIrlmpCb->pIrdaLinkCb);    

    if (pIrlmpCb->LinkState == LINK_DISCONNECTED &&
        !pIrlmpCb->ConnReqScheduled)
    {
        IMsg.Prim = IRLAP_DISCOVERY_REQ;
        IMsg.IRDA_MSG_SenseMedia = MediaSense;

        DEBUGMSG(DBG_DISCOVERY,
                 (TEXT
                  ("IRLMP: Sent IRLAP_DISCOVERY_REQ, New LinkState=LINK_IN_DISCOVERY\n")));
        
        if ((rc = IrlapDown(pIrlmpCb->pIrdaLinkCb->IrlapContext, &IMsg)) != SUCCESS)
        {
            if (rc != IRLAP_REMOTE_DISCOVERY_IN_PROGRESS_ERR &&
                rc != IRLAP_REMOTE_CONNECTION_IN_PROGRESS_ERR)
            {
                ASSERT(0);
            }
            else
            {                
                DEBUGMSG(DBG_DISCOVERY, (TEXT("IRLAP_DISCOVERY_REQ failed, link busy\n")));
            }
        }
        else
        {
            pIrlmpCb->LinkState = LINK_IN_DISCOVERY;
            // The next link will be schedule to run discovery when
            // the DISCOVERY_CONF for this link is received
            ScheduleNextLink = FALSE;
        }
    }
    
    UNLOCK_LINK(pIrlmpCb->pIrdaLinkCb);

    REFDEL(&pIrlmpCb->pIrdaLinkCb->RefCnt, 'VCSD');

    // Discovery failed on this link or it was not in the disconnected
    // state, schedule the next one

    if (ScheduleNextLink)
    {
        IrdaEventSchedule(&EvDiscoveryReq, NULL);
    }
}

VOID
ScheduleConnectReq(PIRLMP_LINK_CB pIrlmpCb)
{
    IRLMP_LSAP_CB   *pLsapCb;
    
    // Schedule the ConnectReq event if not already scheduled and if an LSAP
    // has a connect pending
    if (pIrlmpCb->ConnReqScheduled == FALSE)
    {       
        for (pLsapCb = (IRLMP_LSAP_CB *) pIrlmpCb->LsapCbList.Flink;
             (LIST_ENTRY *) pLsapCb != &pIrlmpCb->LsapCbList;
             pLsapCb = (IRLMP_LSAP_CB *) pLsapCb->Linkage.Flink)
        {
            VALIDLSAP(pLsapCb);
            
            if (pLsapCb->State == LSAP_CONN_REQ_PEND)
            {
                IrdaEventSchedule(&EvConnectReq, pIrlmpCb->pIrdaLinkCb);

                pIrlmpCb->ConnReqScheduled = TRUE;
                return;
            }
        }
    }
}

/*****************************************************************************
*
*   @func   UINT | InitiateConnectReq | A deferred processing routine that sends
*                                   IRLAP a connect request
*   This is scheduled after an IRLMP discovery confirm or a disconnect 
*   indication has been received via IRLMP_Up(). This allows the possible
*   IRLAP connect request to be made in a different context.
*/
void
InitiateConnectReq(PVOID Context)
{
    IRLMP_LSAP_CB   *pLsapCb;
    BOOLEAN         ConnectIrlap = FALSE;
    IRDA_MSG        IMsg;
    UINT            rc;
    PIRDA_LINK_CB   pIrdaLinkCb = (PIRDA_LINK_CB) Context;
    PIRLMP_LINK_CB  pIrlmpCb = (PIRLMP_LINK_CB) pIrdaLinkCb->IrlmpContext;

    PAGED_CODE();
    
    LOCK_LINK(pIrdaLinkCb);
    
    DEBUGMSG(DBG_IRLMP, (TEXT("IRLMP: InitiateConnectReq()!\n")));

    pIrlmpCb->ConnReqScheduled = FALSE;

    if (pIrlmpCb->LinkState != LINK_DISCONNECTED &&
        pIrlmpCb->LinkState != LINK_CONNECTING)
    {
        UNLOCK_LINK(pIrdaLinkCb);           
        ASSERT(0);
        return;
    }

    // Check for LSAP in the connect request pending state.
    // If one or more exists, place them in IRLAP connect pending state
    // and initiate an IRLAP connection
    for (pLsapCb = (IRLMP_LSAP_CB *) pIrlmpCb->LsapCbList.Flink;
        (LIST_ENTRY *) pLsapCb != &pIrlmpCb->LsapCbList;
        pLsapCb = (IRLMP_LSAP_CB *) pLsapCb->Linkage.Flink)
    {
        VALIDLSAP(pLsapCb);
        
        if (pLsapCb->State == LSAP_CONN_REQ_PEND)
        {
            pLsapCb->State = LSAP_IRLAP_CONN_PEND;
            ConnectIrlap = TRUE;
        }
    }

    if (ConnectIrlap && pIrlmpCb->LinkState == LINK_DISCONNECTED)
    {
        DEBUGMSG(DBG_IRLMP, 
           (TEXT("IRLMP: IRLAP_CONNECT_REQ, State=LINK CONNECTING\r\n")));
        
        pIrlmpCb->LinkState = LINK_CONNECTING;

        pIrlmpCb->ConnDevAddrSet = FALSE; // This was previously set by
                                          // the LSAP which set the remote
                                          // device address. This is the
                                          // first opportunity to clear
                                          // the flag

        // Get the connection address out of the IRLMP control block
        RtlCopyMemory(IMsg.IRDA_MSG_RemoteDevAddr, pIrlmpCb->ConnDevAddr,
               IRDA_DEV_ADDR_LEN);
        IMsg.Prim = IRLAP_CONNECT_REQ;
        if ((rc = IrlapDown(pIrlmpCb->pIrdaLinkCb->IrlapContext, &IMsg))
            != SUCCESS)
        {
            DEBUGMSG(DBG_IRLMP, 
             (TEXT("IRLMP: IRLAP_CONNECT_REQ failed, State=LINK_DISCONNECTED\r\n")));

            pIrlmpCb->LinkState = LINK_DISCONNECTED;

            ASSERT(rc == IRLAP_REMOTE_DISCOVERY_IN_PROGRESS_ERR);

            TearDownConnections(pIrlmpCb, IRLMP_IRLAP_REMOTE_DISCOVERY_IN_PROGRESS);
        }
    }

    UNLOCK_LINK(pIrdaLinkCb);
    
    return;
}

void
InitiateConnectResp(PVOID Context)
{
    IRDA_MSG    IMsg;
    PIRDA_LINK_CB   pIrdaLinkCb = (PIRDA_LINK_CB) Context;
    PIRLMP_LINK_CB  pIrlmpCb = (PIRLMP_LINK_CB) pIrdaLinkCb->IrlmpContext;
    
    PAGED_CODE();    
    
    LOCK_LINK(pIrdaLinkCb);
    
    ASSERT(pIrlmpCb->LinkState == LINK_CONNECTING);
    
    if (pIrlmpCb->AcceptConnection)
    {
        IMsg.Prim = IRLAP_CONNECT_RESP;

        IrlapDown(pIrdaLinkCb->IrlapContext, &IMsg);

        pIrlmpCb->LinkState = LINK_READY;
        
        // Disconnect the link if no LSAP connection after a bit
        IrdaTimerRestart(&pIrlmpCb->DiscDelayTimer);                
        
        IrdaEventSchedule(&EvLmConnectReq, pIrlmpCb->pIrdaLinkCb);
        
    }
    else
    {
        pIrlmpCb->LinkState = LINK_DISCONNECTED;
        IMsg.Prim = IRLAP_DISCONNECT_REQ;
        IrlapDown(pIrdaLinkCb->IrlapContext, &IMsg);        
    }
    
    UNLOCK_LINK(pIrdaLinkCb);    

    return;
}

void
InitiateLMConnectReq(PVOID Context)
{
    PIRDA_LINK_CB   pIrdaLinkCb = (PIRDA_LINK_CB) Context;
    PIRLMP_LINK_CB  pIrlmpCb = (PIRLMP_LINK_CB) pIrdaLinkCb->IrlmpContext;
    IRLMP_LSAP_CB   *pLsapCb;
    
    LOCK_LINK(pIrdaLinkCb);

    DEBUGMSG(DBG_IRLMP, (TEXT("IRLMP: InitiateLMConnectReq()!\n")));    

    // Send the connect request PDU to peer LSAPs
    while ((pLsapCb = GetLsapInState(pIrlmpCb, LINK_READY,
                                      LSAP_IRLAP_CONN_PEND, TRUE)) != NULL)
    {
        pLsapCb->State = LSAP_LMCONN_CONF_PEND;

        // Ask remote LSAP for a connection
        SendCntlPdu(pLsapCb, IRLMP_CONNECT_PDU, IRLMP_ABIT_REQUEST,
                    IRLMP_RSVD_PARM, 0);
        
        IrdaTimerRestart(&pLsapCb->ResponseTimer);
    }
    
    UNLOCK_LINK(pIrdaLinkCb);
}

void
InitiateCloseLink(PVOID Context)
{
    PIRDA_LINK_CB   pIrdaLinkCb = (PIRDA_LINK_CB) Context;
    PIRLMP_LINK_CB  pIrlmpCb = (PIRLMP_LINK_CB) pIrdaLinkCb->IrlmpContext;
    IRDA_MSG        IMsg;
    LARGE_INTEGER   SleepMs;    
    PIRLMP_LSAP_CB  pLsapCb;
    
    PAGED_CODE();
        
//    //Sleep(500); // This sleep allows time for LAP to send any 
                // LM_DISCONNECT_REQ's that may be sitting on its TxQue.                
    
    DEBUGMSG(DBG_IRLMP, (TEXT("IRLMP: InitiateCloseLink()!\n")));    
    
    LOCK_LINK(pIrdaLinkCb);    

    // Stop link timer
    IrdaTimerStop(&pIrlmpCb->DiscDelayTimer);

    // Bring down the link...
    IMsg.Prim = IRLAP_DISCONNECT_REQ;
    IrlapDown(pIrdaLinkCb->IrlapContext, &IMsg);

    UNLOCK_LINK(pIrdaLinkCb);    
    
    // Allow LAP time to disconnect link
    SleepMs.QuadPart = -(10*1000*1000);

    KeDelayExecutionThread(KernelMode, FALSE, &SleepMs);

    LOCK_LINK(pIrlmpCb->pIrdaLinkCb);    
    
    IrlapCloseLink(pIrdaLinkCb);
   
    TearDownConnections(pIrlmpCb, IRLMP_UNSPECIFIED_DISC);
    
    // Delete the ias entry if it exists
    for (pLsapCb = (IRLMP_LSAP_CB *) pIrlmpCb->LsapCbList.Flink;
         (LIST_ENTRY *) pLsapCb != &pIrlmpCb->LsapCbList;
         pLsapCb = (IRLMP_LSAP_CB *) pLsapCb->Linkage.Flink)
    {
        if (pLsapCb->RemoteLsapSel == IAS_LSAP_SEL)
        {
            pLsapCb->RemoteLsapSel = 1; // DeleteLsap ignore IAS_LSAP_SEL
            
            DeleteLsap(pLsapCb);
            
            break;
        }
    }
        
    DEBUGMSG(DBG_IRLMP, (TEXT("IRLMP Shutdown\n")));

    UNLOCK_LINK(pIrdaLinkCb);
    
    return;
}

// IAS

// Oh my God! I'm out of time, no more function hdrs

int StringLen(char *p)
{
    int i = 0;
    
    while (*p++ != 0)
    {
        i++;
    }
    
    return i;
}

int StringCmp(char *p1, char *p2)
{
    while (1)
    {
        if (*p1 != *p2)
            break;
        
        if (*p1 == 0)
            return 0;
        p1++, p2++;
    }
    return 1;
}   


UINT
IrlmpGetValueByClassReq(IRDA_MSG *pReqMsg)
{
    UINT            rc = SUCCESS;
    PIRLMP_LINK_CB  pIrlmpCb = GetIrlmpCb(pReqMsg->IRDA_MSG_pIasQuery->irdaDeviceID);
    IRDA_MSG        IMsg;
    
    DEBUGMSG(DBG_IRLMP_IAS, (TEXT("IRLMP: IRLMP_GETVALUEBYCLASS_REQ\n")));
    
    PAGED_CODE();    

    if (pIrlmpCb == NULL)
    {
        DEBUGMSG(DBG_ERROR, (TEXT("IRLMP: Null IrlmpCb\n")));
        return IRLMP_BAD_DEV_ADDR;;
    }

    LOCK_LINK(pIrlmpCb->pIrdaLinkCb);
    
    if (pIrlmpCb->pIasQuery != NULL)
    {
        DEBUGMSG(DBG_ERROR, 
                 (TEXT("IRLMP: ERROR query already in progress\n")));
        rc = IRLMP_IAS_QUERY_IN_PROGRESS;
        
        UNLOCK_LINK(pIrlmpCb->pIrdaLinkCb);            
    }
    else
    {
        // Save the pointer to the query in the control block
        // and then request a connection to the remote IAS LSAP

        // Save it
        pIrlmpCb->pIasQuery             = pReqMsg->IRDA_MSG_pIasQuery;
        pIrlmpCb->AttribLen             = pReqMsg->IRDA_MSG_AttribLen;
        pIrlmpCb->AttribLenWritten      = 0;
        pIrlmpCb->FirstIasRespReceived  = FALSE;
        pIrlmpCb->IasRetryCnt           = 0;
        
        UNLOCK_LINK(pIrlmpCb->pIrdaLinkCb);

        // request connection
        IMsg.Prim                      = IRLMP_CONNECT_REQ;
        IMsg.IRDA_MSG_RemoteLsapSel    = IAS_LSAP_SEL;
        IMsg.IRDA_MSG_LocalLsapSel     = IAS_LOCAL_LSAP_SEL;
        IMsg.IRDA_MSG_pQos             = NULL;
        IMsg.IRDA_MSG_pConnData        = NULL;
        IMsg.IRDA_MSG_ConnDataLen      = 0;
        IMsg.IRDA_MSG_UseTtp           = FALSE;
        IMsg.IRDA_MSG_pContext         = NULL;

        RtlCopyMemory(pIrlmpCb->IasQueryDevAddr, 
                      pReqMsg->IRDA_MSG_pIasQuery->irdaDeviceID,
                      IRDA_DEV_ADDR_LEN);
        
        RtlCopyMemory(IMsg.IRDA_MSG_RemoteDevAddr,
                      pReqMsg->IRDA_MSG_pIasQuery->irdaDeviceID,
                      IRDA_DEV_ADDR_LEN);
    
        if ((rc = IrlmpConnectReq(&IMsg)) != SUCCESS)
        {
            DEBUGMSG(DBG_IRLMP, (TEXT("IRLMP: Retry IasQuery at start\n")));
            
            IrdaEventSchedule(&EvRetryIasQuery, 
                              pIrlmpCb->pIrdaLinkCb);
            rc = SUCCESS;
        }
    }

    return rc;
}

VOID
SendGetValueByClassReq(IRLMP_LSAP_CB *pLsapCb)
{
    IRDA_MSG            *pMsg;
    IAS_CONTROL_FIELD   *pControl;
    int                 ClassNameLen;
    int                 AttribNameLen;
    PIRLMP_LINK_CB      pIrlmpCb = (PIRLMP_LINK_CB) pLsapCb->pIrlmpCb;
    
    PAGED_CODE();    

    if (pIrlmpCb->pIasQuery == NULL)
    {
        return;
    }

    ClassNameLen = StringLen(pIrlmpCb->pIasQuery->irdaClassName);
    AttribNameLen = StringLen(pIrlmpCb->pIasQuery->irdaAttribName);
    
    DEBUGMSG(DBG_IRLMP_IAS, (TEXT("IRLMP: Send GetValueByClassReq(%hs,%hs)\n"),
            pIrlmpCb->pIasQuery->irdaClassName, pIrlmpCb->pIasQuery->irdaAttribName));

    // Alloc a message for data request that will contain the query
    if ((pMsg = AllocIrdaBuf(IrdaMsgPool)) == NULL)
    {
        ASSERT(0);
        return;
    }

    pMsg->IRDA_MSG_pHdrRead = 
    pMsg->IRDA_MSG_pHdrWrite = pMsg->IRDA_MSG_Header + IRDA_HEADER_LEN;
    
    pMsg->IRDA_MSG_pRead  = \
    pMsg->IRDA_MSG_pWrite = \
    pMsg->IRDA_MSG_pBase  = pMsg->IRDA_MSG_pHdrWrite;
    pMsg->IRDA_MSG_pLimit = pMsg->IRDA_MSG_pBase +
        IRDA_MSG_DATA_SIZE_INTERNAL - sizeof(IRDA_MSG) - 1;    

    // Build the query and then send it in a LAP data req

    pControl = (IAS_CONTROL_FIELD *) pMsg->IRDA_MSG_pRead;

    pControl->Last   = TRUE;
    pControl->Ack    = FALSE;
    pControl->OpCode = IAS_OPCODE_GET_VALUE_BY_CLASS;
    
    *(pMsg->IRDA_MSG_pRead + 1) = (UCHAR) ClassNameLen;
    RtlCopyMemory(pMsg->IRDA_MSG_pRead + 2,
           pIrlmpCb->pIasQuery->irdaClassName, 
           ClassNameLen);
    *(pMsg->IRDA_MSG_pRead + ClassNameLen + 2) = (UCHAR) AttribNameLen;
    RtlCopyMemory(pMsg->IRDA_MSG_pRead + ClassNameLen + 3,
           pIrlmpCb->pIasQuery->irdaAttribName,
           AttribNameLen);

    pMsg->IRDA_MSG_pWrite = pMsg->IRDA_MSG_pRead + ClassNameLen + AttribNameLen + 3;

    pMsg->IRDA_MSG_IrCOMM_9Wire = FALSE;
    
    FormatAndSendDataReq(pLsapCb, pMsg, TRUE, TRUE);
}

VOID
IasConnectReq(PIRLMP_LINK_CB pIrlmpCb, int RemoteLsapSel)
{
    IRLMP_LSAP_CB           *pLsapCb = GetLsap(pIrlmpCb,
                                               IAS_LSAP_SEL, RemoteLsapSel);

    PAGED_CODE();
    
    DEBUGMSG(DBG_IRLMP_IAS, (TEXT("IRLMP: Received IAS connect request\n")));
    
    if (pLsapCb == NULL)
    {
        if (CreateLsap(pIrlmpCb, &pLsapCb) != SUCCESS)
            return;
        
        pLsapCb->State          = LSAP_READY;
        pLsapCb->LocalLsapSel   = IAS_LSAP_SEL;
        pLsapCb->RemoteLsapSel  = RemoteLsapSel;
    }
    
    SendCntlPdu(pLsapCb, IRLMP_CONNECT_PDU, IRLMP_ABIT_CONFIRM,
                IRLMP_RSVD_PARM, 0);
}

VOID
IasServerDisconnectReq(IRLMP_LSAP_CB *pLsapCb)
{
    PAGED_CODE();
    
    DEBUGMSG(DBG_IRLMP_IAS, (TEXT("IRLMP: Received disconnect request IAS\n")));
    
    DeleteLsap(pLsapCb);
    
    return;
}

VOID
IasClientDisconnectReq(IRLMP_LSAP_CB *pLsapCb, IRLMP_DISC_REASON DiscReason)
{
    IRDA_MSG        IMsg;
    PIRLMP_LINK_CB  pIrlmpCb = (PIRLMP_LINK_CB) pLsapCb->pIrlmpCb;
    
    PAGED_CODE();    

    DeleteLsap(pLsapCb);    
    
    if (pIrlmpCb->pIasQuery != NULL)
    {
        if (DiscReason != IRLMP_UNSPECIFIED_DISC)
        {
            DEBUGMSG(DBG_IRLMP, (TEXT("IRLMP: Retry IasQuery as timeout\n")));
            
            IrdaEventSchedule(&EvRetryIasQuery, 
                              pIrlmpCb->pIrdaLinkCb);
        }
        else
        {
            pIrlmpCb->pIasQuery = NULL; 

            // Disconnect link
            IrdaTimerRestart(&pIrlmpCb->DiscDelayTimer);
        
            IMsg.Prim = IRLMP_GETVALUEBYCLASS_CONF;
            IMsg.IRDA_MSG_IASStatus = DiscReason;
    
            TdiUp(NULL, &IMsg);
        }    
    }
}

VOID
IasSendQueryResp(IRLMP_LSAP_CB *pLsapCb, IRDA_MSG *pMsg)
{
    IAS_CONTROL_FIELD   *pCntl = (IAS_CONTROL_FIELD *) pMsg->IRDA_MSG_pRead++;
    
    PAGED_CODE();    

    if (pCntl->OpCode != IAS_OPCODE_GET_VALUE_BY_CLASS)
    {
        return;// IRLMP_UNSUPPORTED_IAS_OPERATION;
    }

    SendGetValueByClassResp(pLsapCb, pMsg);
}

VOID
IasProcessQueryResp(PIRLMP_LINK_CB pIrlmpCb,
                    IRLMP_LSAP_CB *pLsapCb, IRDA_MSG *pMsg)
{
    IAS_CONTROL_FIELD   *pCntl      = (IAS_CONTROL_FIELD *) pMsg->IRDA_MSG_pRead++;
    UCHAR                ReturnCode;
    int                 ObjID;
    
    PAGED_CODE();    

    if (pIrlmpCb->pIasQuery == NULL)
    {
        return;
        // return IRLMP_UNSOLICITED_IAS_RESPONSE;
    }

    if (pIrlmpCb->FirstIasRespReceived == FALSE)
    {
        pIrlmpCb->FirstIasRespReceived = TRUE;

        ReturnCode = *pMsg->IRDA_MSG_pRead++;

        if (ReturnCode != IAS_SUCCESS)
        {
            if (ReturnCode == IAS_NO_SUCH_OBJECT)
            {
                pMsg->IRDA_MSG_IASStatus = IRLMP_IAS_NO_SUCH_OBJECT;
            }
            else
            {
                pMsg->IRDA_MSG_IASStatus = IRLMP_IAS_NO_SUCH_ATTRIB;
            }

            // Disconnect LSAP
            SendCntlPdu(pLsapCb,IRLMP_DISCONNECT_PDU,IRLMP_ABIT_REQUEST,
                        IRLMP_USER_REQUEST, 0);

            DeleteLsap(pLsapCb);

            // Disconnect link
            IrdaTimerRestart(&pIrlmpCb->DiscDelayTimer);
            
            pMsg->Prim = IRLMP_GETVALUEBYCLASS_CONF;
            pMsg->IRDA_MSG_pIasQuery = pIrlmpCb->pIasQuery;
            pIrlmpCb->pIasQuery = NULL;

            TdiUp(NULL, pMsg);
            return;
        }

        pIrlmpCb->QueryListLen =  ((int)(*pMsg->IRDA_MSG_pRead++)) << 8;
        pIrlmpCb->QueryListLen += (int) *pMsg->IRDA_MSG_pRead++;        

        // What I am going to do with this?
        ObjID = ((int)(*pMsg->IRDA_MSG_pRead++)) << 8;
        ObjID += (int) *pMsg->IRDA_MSG_pRead++;

        pIrlmpCb->pIasQuery->irdaAttribType = (int) *pMsg->IRDA_MSG_pRead++;
    
        switch (pIrlmpCb->pIasQuery->irdaAttribType)
        {
          case IAS_ATTRIB_VAL_MISSING:
            break;
            
          case IAS_ATTRIB_VAL_INTEGER:
            pIrlmpCb->pIasQuery->irdaAttribute.irdaAttribInt = 0;
            pIrlmpCb->pIasQuery->irdaAttribute.irdaAttribInt += 
                ((int) (*pMsg->IRDA_MSG_pRead++) << 24) & 0xFF000000;
            pIrlmpCb->pIasQuery->irdaAttribute.irdaAttribInt += 
                ((int) (*pMsg->IRDA_MSG_pRead++) << 16) & 0xFF0000;
            pIrlmpCb->pIasQuery->irdaAttribute.irdaAttribInt += 
                ((int) (*pMsg->IRDA_MSG_pRead++) << 8) & 0xFF00;
            pIrlmpCb->pIasQuery->irdaAttribute.irdaAttribInt += 
                (int) (*pMsg->IRDA_MSG_pRead++) & 0xFF;
            break;
        
          case IAS_ATTRIB_VAL_BINARY:
            pIrlmpCb->pIasQuery->irdaAttribute.irdaAttribOctetSeq.Len = 0;
            pIrlmpCb->pIasQuery->irdaAttribute.irdaAttribOctetSeq.Len += 
                         ((int )(*pMsg->IRDA_MSG_pRead++) << 8) & 0xFF00;
            pIrlmpCb->pIasQuery->irdaAttribute.irdaAttribOctetSeq.Len += 
                         ((int) *pMsg->IRDA_MSG_pRead++) & 0xFF;        
            break;
            

          case IAS_ATTRIB_VAL_STRING:
            // char set
            pIrlmpCb->pIasQuery->irdaAttribute.irdaAttribUsrStr.CharSet =
                *pMsg->IRDA_MSG_pRead++;                   
            
            pIrlmpCb->pIasQuery->irdaAttribute.irdaAttribUsrStr.Len =
                (int) *pMsg->IRDA_MSG_pRead++;
            break;
            
        }

    }
    
    switch (pIrlmpCb->pIasQuery->irdaAttribType)
    {
      case IAS_ATTRIB_VAL_BINARY:    
        while (pMsg->IRDA_MSG_pRead < pMsg->IRDA_MSG_pWrite &&
               pIrlmpCb->AttribLenWritten < pIrlmpCb->AttribLen &&
               pIrlmpCb->AttribLenWritten < pIrlmpCb->pIasQuery->irdaAttribute.irdaAttribOctetSeq.Len)
        {   
            pIrlmpCb->pIasQuery->irdaAttribute.irdaAttribOctetSeq.OctetSeq[pIrlmpCb->AttribLenWritten++] = *pMsg->IRDA_MSG_pRead++;
        }
        
        break;
        
      case IAS_ATTRIB_VAL_STRING:
        while (pMsg->IRDA_MSG_pRead < pMsg->IRDA_MSG_pWrite &&
               pIrlmpCb->AttribLenWritten < pIrlmpCb->AttribLen &&
               pIrlmpCb->AttribLenWritten < pIrlmpCb->pIasQuery->irdaAttribute.irdaAttribUsrStr.Len)
        {
            pIrlmpCb->pIasQuery->irdaAttribute.irdaAttribUsrStr.UsrStr[pIrlmpCb->AttribLenWritten++] = *pMsg->IRDA_MSG_pRead++;
        }
    }
    
    if (pCntl->Last == TRUE)
    {
        pMsg->IRDA_MSG_pIasQuery = pIrlmpCb->pIasQuery;
        
        // Done with query
        pIrlmpCb->pIasQuery = NULL;

        // Disconnect LSAP
        SendCntlPdu(pLsapCb,IRLMP_DISCONNECT_PDU,IRLMP_ABIT_REQUEST,
                    IRLMP_USER_REQUEST, 0);

        DeleteLsap(pLsapCb);

        // Disconnect link
        IrdaTimerRestart(&pIrlmpCb->DiscDelayTimer);        

        pMsg->Prim = IRLMP_GETVALUEBYCLASS_CONF;

        if (pIrlmpCb->QueryListLen > 1)
        {
            pMsg->IRDA_MSG_IASStatus = IRLMP_IAS_SUCCESS_LISTLEN_GREATER_THAN_ONE;
        }
        else
        {
            pMsg->IRDA_MSG_IASStatus = IRLMP_IAS_SUCCESS;
        }
    
        TdiUp(NULL, pMsg);
        return;
    }
}

UINT
NewQueryMsg(PIRLMP_LINK_CB pIrlmpCb, LIST_ENTRY *pList, IRDA_MSG **ppMsg)
{
    IRDA_MSG *pMsg;
    
    if ((*ppMsg = AllocIrdaBuf(IrdaMsgPool)) == NULL)
    {
        pMsg = (IRDA_MSG *) RemoveHeadList(pList);
        
        while (pMsg != (IRDA_MSG *) pList)
        {
            FreeIrdaBuf(IrdaMsgPool, pMsg);
            pMsg = (IRDA_MSG *) RemoveHeadList(pList);            
        }
        return IRLMP_ALLOC_FAILED;
    }
    (*ppMsg)->IRDA_MSG_pHdrRead  = \
    (*ppMsg)->IRDA_MSG_pHdrWrite = (*ppMsg)->IRDA_MSG_Header+IRDA_HEADER_LEN;
        
    (*ppMsg)->IRDA_MSG_pRead  = \
    (*ppMsg)->IRDA_MSG_pWrite = \
    (*ppMsg)->IRDA_MSG_pBase  = (*ppMsg)->IRDA_MSG_pHdrWrite;
    (*ppMsg)->IRDA_MSG_pLimit = (*ppMsg)->IRDA_MSG_pBase +
        IRDA_MSG_DATA_SIZE_INTERNAL - sizeof(IRDA_MSG) - 1;
    
    InsertTailList(pList, &( (*ppMsg)->Linkage) );    

    // reserve space for the IAS control field.
    (*ppMsg)->IRDA_MSG_pWrite += sizeof(IAS_CONTROL_FIELD);   

    return SUCCESS;
}

VOID
SendGetValueByClassResp(IRLMP_LSAP_CB *pLsapCb, IRDA_MSG *pReqMsg)
{  
    int                 ClassNameLen, AttribNameLen;
    CHAR                *pClassName, *pAttribName;
    IRDA_MSG            *pQMsg, *pNextMsg;
    IAS_OBJECT          *pObject;
    IAS_ATTRIBUTE       *pAttrib;
    LIST_ENTRY          QueryList;
    IAS_CONTROL_FIELD   *pControl;
    UCHAR               *pReturnCode;
    UCHAR               *pListLen;
    UCHAR               *pBPtr;
    int                 ListLen = 0;
    BOOLEAN             ObjectFound = FALSE;
    BOOLEAN             AttribFound = FALSE;
    int                 i;
#if DBG
    char                ClassStr[128];
    char                AttribStr[128];
#endif        
    
    PAGED_CODE();
        
    DEBUGMSG(DBG_IRLMP, 
             (TEXT("IRLMP: Remote GetValueByClass query received\n")));

    ClassNameLen = (int) *pReqMsg->IRDA_MSG_pRead;
    pClassName = (CHAR *) (pReqMsg->IRDA_MSG_pRead + 1);

    AttribNameLen =  (int) *(pClassName + ClassNameLen);
    pAttribName = pClassName + ClassNameLen + 1;
    
#if DBG
    RtlCopyMemory(ClassStr, pClassName, ClassNameLen);
    ClassStr[ClassNameLen] = 0;
    RtlCopyMemory(AttribStr, pAttribName, AttribNameLen);
    AttribStr[AttribNameLen] = 0;
#endif    

    if (pReqMsg->IRDA_MSG_pWrite != (UCHAR *) (pAttribName + AttribNameLen))
    {
        // The end of the message didn't point to where the end of
        // the parameters.
        
        // LOG ERROR.
        //return IRLMP_BAD_IAS_QUERY_FROM_REMOTE;
        return;
    }

    // The query may require multiple frames to transmit, build a list
    InitializeListHead(&QueryList);

    // Create the first message
    if (NewQueryMsg(pLsapCb->pIrlmpCb, &QueryList, &pQMsg) != SUCCESS)
    {
        ASSERT(0);
        return;
    }

    pReturnCode = pQMsg->IRDA_MSG_pWrite++;
    pListLen = pQMsg->IRDA_MSG_pWrite++;
    pQMsg->IRDA_MSG_pWrite++; // list len get 2 bytes
    
    for (pObject = (IAS_OBJECT *) IasObjects.Flink;
         (LIST_ENTRY *) pObject != &IasObjects;
         pObject = (IAS_OBJECT *) pObject->Linkage.Flink)    
    {
        DEBUGMSG(DBG_IRLMP_IAS, (TEXT("  compare object %hs with %hs\n"),
                        ClassStr, pObject->pClassName));
                        
        if (ClassNameLen == StringLen(pObject->pClassName) &&
            CTEMemCmp(pClassName, pObject->pClassName, (ULONG) ClassNameLen) == 0)
        {
            DEBUGMSG(DBG_IRLMP_IAS, (TEXT("  Object found\n")));
        
            ObjectFound = TRUE;

            pAttrib = pObject->pAttributes;
            while (pAttrib != NULL)
            {
                DEBUGMSG(DBG_IRLMP_IAS, (TEXT("  compare attrib %hs with %hs\n"),
                        pAttrib->pAttribName, AttribStr));
                        
                if (AttribNameLen == StringLen(pAttrib->pAttribName) &&
                    CTEMemCmp(pAttrib->pAttribName, pAttribName, (ULONG) AttribNameLen) == 0)
                {
                    DEBUGMSG(DBG_IRLMP_IAS, (TEXT("  Attrib found\n")));
                
                    AttribFound = TRUE;

                    ListLen++;
                    
                    if (pQMsg->IRDA_MSG_pWrite + 1 > pQMsg->IRDA_MSG_pLimit)
                    {
                        // I need 2 bytes for object ID, don't want to
                        // split 16 bit field up 
                        if (NewQueryMsg(pLsapCb->pIrlmpCb, &QueryList,
                                        &pQMsg) != SUCCESS)
                        {
                            ASSERT(0);
                            return;
                        }
                    }
                    
                    *pQMsg->IRDA_MSG_pWrite++ = 
                        (UCHAR) (((pObject->ObjectId) & 0xFF00) >> 8);
                    *pQMsg->IRDA_MSG_pWrite++ = 
                        (UCHAR) ((pObject->ObjectId) & 0xFF);
                    
                    if (pQMsg->IRDA_MSG_pWrite > pQMsg->IRDA_MSG_pLimit)
                    {
                        if (NewQueryMsg(pLsapCb->pIrlmpCb, &QueryList,
                                        &pQMsg) != SUCCESS)
                        {
                            ASSERT(0);
                            return;
                        }                        
                    }
                    
                    switch (pAttrib->AttribValType)
                    {
                      case IAS_ATTRIB_VAL_INTEGER:
                        DEBUGMSG(DBG_IRLMP, (TEXT("IRLMP: integer query %d\n"),
                                              *((int *) pAttrib->pAttribVal)));
                        
                        if (pQMsg->IRDA_MSG_pWrite + 4 > pQMsg->IRDA_MSG_pLimit)
                        {
                            if (NewQueryMsg(pLsapCb->pIrlmpCb,
                                            &QueryList, &pQMsg) != SUCCESS)
                            {
                                ASSERT(0);
                                return;
                            }
                        }
                        *pQMsg->IRDA_MSG_pWrite++ = IAS_ATTRIB_VAL_INTEGER;
                        *pQMsg->IRDA_MSG_pWrite++ = (UCHAR)
                            ((*((int *) pAttrib->pAttribVal) & 0xFF000000) >> 24);
                        *pQMsg->IRDA_MSG_pWrite++ = (UCHAR)
                            ((*((int *) pAttrib->pAttribVal) & 0xFF0000) >> 16);
                        *pQMsg->IRDA_MSG_pWrite++ = (UCHAR)
                            ((*((int *) pAttrib->pAttribVal) & 0xFF00) >> 8);
                        *pQMsg->IRDA_MSG_pWrite++ = (UCHAR)
                            (*((int *) pAttrib->pAttribVal) & 0xFF); 
                        break;

                      case IAS_ATTRIB_VAL_BINARY:
                      case IAS_ATTRIB_VAL_STRING:
                        if (pQMsg->IRDA_MSG_pWrite + 2 > pQMsg->IRDA_MSG_pLimit)
                        {
                            if (NewQueryMsg(pLsapCb->pIrlmpCb,
                                            &QueryList, &pQMsg) != SUCCESS)
                            {
                                ASSERT(0);
                                return;
                            }
                        }
                        
                        *pQMsg->IRDA_MSG_pWrite++ = (UCHAR) pAttrib->AttribValType;

                        if (pAttrib->AttribValType == IAS_ATTRIB_VAL_BINARY)
                        {
                            *pQMsg->IRDA_MSG_pWrite++ = 
                                  (UCHAR) ((pAttrib->AttribValLen & 0xFF00) >> 8);
                            *pQMsg->IRDA_MSG_pWrite++ = 
                                  (UCHAR) (pAttrib->AttribValLen & 0xFF);;
                        }
                        else
                        {
                            *pQMsg->IRDA_MSG_pWrite++ = 
                                   (UCHAR) pAttrib->CharSet;
                            *pQMsg->IRDA_MSG_pWrite++ = 
                                  (UCHAR) pAttrib->AttribValLen;
                        }

                        pBPtr = (UCHAR *) pAttrib->pAttribVal;
                        
                        for (i=0; i < pAttrib->AttribValLen; i++)
                        {
                            if (pQMsg->IRDA_MSG_pWrite > pQMsg->IRDA_MSG_pLimit)
                            {
                                if (NewQueryMsg(pLsapCb->pIrlmpCb,
                                                &QueryList, &pQMsg) != SUCCESS)
                                {
                                    ASSERT(0);
                                    return;
                                }
                            }
                            *pQMsg->IRDA_MSG_pWrite++ = *pBPtr++;
                        }
                        break;
                    }

                    break; // Break out of loop, only look for single 
                           // attrib per object (??)
                }
                pAttrib = pAttrib->pNext;
            }             
        }                                                   
    }

    // Send the query
    if (!ObjectFound)
    {
        *pReturnCode = IAS_NO_SUCH_OBJECT;
    }
    else
    {
        if (!AttribFound)
        {
            *pReturnCode = IAS_NO_SUCH_ATTRIB;
        }
        else
        {
            *pReturnCode = IAS_SUCCESS;
            *pListLen++ =  (UCHAR) ((ListLen & 0xFF00) >> 8);
            *pListLen = (UCHAR) (ListLen & 0xFF);
        }
    }

    if (!IsListEmpty(&QueryList))
    {
        pQMsg = (IRDA_MSG *) RemoveHeadList(&QueryList);
    }
    else
    {
        pQMsg = NULL;
    }
    
    while (pQMsg)
    {
        if (!IsListEmpty(&QueryList))
        {
            pNextMsg = (IRDA_MSG *) RemoveHeadList(&QueryList);
        }
        else
        {
            pNextMsg = NULL;
        }

        // Build the control field
        pControl = (IAS_CONTROL_FIELD *) pQMsg->IRDA_MSG_pRead;
        pControl->OpCode = IAS_OPCODE_GET_VALUE_BY_CLASS;
        pControl->Ack = FALSE;
        if (pNextMsg == NULL)
        {
            pControl->Last = TRUE;
        }
        else
        {
            pControl->Last = FALSE;
        }

        pQMsg->IRDA_MSG_IrCOMM_9Wire = FALSE;               

        FormatAndSendDataReq(pLsapCb, pQMsg, TRUE, TRUE);

        pQMsg = pNextMsg;
    }
}

IAS_OBJECT *
IasGetObject(CHAR *pClassName)
{
    IAS_OBJECT  *pObject;
    
    for (pObject = (IAS_OBJECT *) IasObjects.Flink;
         (LIST_ENTRY *) pObject != &IasObjects;
         pObject = (IAS_OBJECT *) pObject->Linkage.Flink)    
    {
        if (StringCmp(pObject->pClassName, pClassName) == 0)
        {
            return pObject;
        }
    }
    return NULL;
}

UINT
IasAddAttribute(IAS_SET *pIASSet, PVOID *pAttribHandle)
{
    IAS_OBJECT      *pObject = NULL;
    IAS_ATTRIBUTE   *pAttrib = NULL;
    CHAR            *pClassName = NULL;
    CHAR            ClassNameLen;
    CHAR            *pAttribName = NULL;
    CHAR            AttribNameLen;
    int             AttribValLen;
    void            *pAttribVal = NULL;
    UINT            cAttribs = 0;    
    KIRQL           OldIrql;
    BOOLEAN         NewObject = FALSE;
    BOOLEAN         NewObjectOnList = FALSE;
    UINT            rc = SUCCESS;
    
    *pAttribHandle = NULL;
    
    KeAcquireSpinLock(&gSpinLock, &OldIrql);    

    if ((pObject = IasGetObject(pIASSet->irdaClassName)) == NULL)
    {
        if (IRDA_ALLOC_MEM(pObject, sizeof(IAS_OBJECT), MT_IRLMP_IAS_OBJECT)
            == NULL)
        {
            rc = IRLMP_ALLOC_FAILED;
            goto done;
        }
        
        NewObject = TRUE;

        ClassNameLen = StringLen(pIASSet->irdaClassName) + 1;
        
        if (IRDA_ALLOC_MEM(pClassName, ClassNameLen, MT_IRLMP_IAS_CLASSNAME)
            == NULL)
        {
            rc = IRLMP_ALLOC_FAILED;
            goto done;
        }

        RtlCopyMemory(pClassName, pIASSet->irdaClassName, ClassNameLen);
        
        pObject->pClassName     = pClassName;
        pObject->pAttributes    = NULL;    
        
        NewObjectOnList = TRUE;

        InsertTailList(&IasObjects, &pObject->Linkage);    
        
        pObject->ObjectId = NextObjectId++;
    }

    // Does the attribute already exist?
    for (pAttrib = pObject->pAttributes; pAttrib != NULL; 
         pAttrib = pAttrib->pNext)
    {
        if (StringCmp(pAttrib->pAttribName, pIASSet->irdaAttribName) == 0)
        {
            break;
        }
        cAttribs++;
    }
    
    if (pAttrib != NULL)
    {
        DEBUGMSG(DBG_ERROR, (TEXT("IRLMP: Attribute alreay exists\r\n")));
        pAttrib = NULL;        
        rc = IRLMP_IAS_ATTRIB_ALREADY_EXISTS;
        goto done;
    }
    else
    {       
        // Only allowed to add 256 attributes to an object.
        if (cAttribs >= 256)
        {
            rc = IRLMP_IAS_MAX_ATTRIBS_REACHED;
            goto done;
        }

        if (IRDA_ALLOC_MEM(pAttrib, sizeof(IAS_ATTRIBUTE), MT_IRLMP_IAS_ATTRIB)
             == NULL)
        {
            rc = IRLMP_ALLOC_FAILED;
            goto done;
        }

        AttribNameLen = StringLen(pIASSet->irdaAttribName) + 1;
        
        if (IRDA_ALLOC_MEM(pAttribName, AttribNameLen, MT_IRLMP_IAS_ATTRIBNAME)
            == NULL)
        {
            rc = IRLMP_ALLOC_FAILED;
            goto done;
        }

        RtlCopyMemory(pAttribName, pIASSet->irdaAttribName, AttribNameLen);
        
    }

    switch (pIASSet->irdaAttribType)
    {
      case IAS_ATTRIB_VAL_INTEGER:
        AttribValLen = sizeof(int);
        if (IRDA_ALLOC_MEM(pAttribVal, AttribValLen, MT_IRLMP_IAS_ATTRIBVAL)
            == NULL)
        {
            rc = IRLMP_ALLOC_FAILED;
            goto done;
        }

        *((int *) pAttribVal) = pIASSet->irdaAttribute.irdaAttribInt;
        break;
        
      case IAS_ATTRIB_VAL_BINARY:
        AttribValLen = pIASSet->irdaAttribute.irdaAttribOctetSeq.Len;
        if (IRDA_ALLOC_MEM(pAttribVal, AttribValLen, MT_IRLMP_IAS_ATTRIBVAL)
            == NULL)
        {
            rc = IRLMP_ALLOC_FAILED;
            goto done;
        }

        RtlCopyMemory(pAttribVal, pIASSet->irdaAttribute.irdaAttribOctetSeq.OctetSeq,
               AttribValLen);
        break;
        
      case IAS_ATTRIB_VAL_STRING:
        AttribValLen = pIASSet->irdaAttribute.irdaAttribUsrStr.Len;
        if (IRDA_ALLOC_MEM(pAttribVal, AttribValLen, MT_IRLMP_IAS_ATTRIBVAL)
            == NULL)
        {
            rc = IRLMP_ALLOC_FAILED;
            goto done;
        }

        RtlCopyMemory(pAttribVal, pIASSet->irdaAttribute.irdaAttribUsrStr.UsrStr, 
               AttribValLen);
        break;
        
      default:
        DEBUGMSG(DBG_ERROR, (TEXT("IRLMP: IasAddAttribute, invalid type\n %d\n"),
                 pIASSet->irdaAttribType));
        rc = IRLMP_NO_SUCH_IAS_ATTRIBUTE;
        goto done;               
    }
    
    pAttrib->pAttribName        = pAttribName;
    pAttrib->pAttribVal         = pAttribVal;
    pAttrib->AttribValLen       = AttribValLen;
    pAttrib->AttribValType      = (UCHAR) pIASSet->irdaAttribType;
    pAttrib->CharSet            = pIASSet->irdaAttribute.irdaAttribUsrStr.CharSet;
    pAttrib->pNext              = pObject->pAttributes;
    
    pObject->pAttributes = pAttrib;

    *pAttribHandle = pAttrib;
    
done:    
    
    if (rc == SUCCESS)
    {
        DEBUGMSG(DBG_IRLMP_IAS, (TEXT("IRLMP: Added attrib(%x) %s to class %s\n"),
                pAttrib, pAttrib->pAttribName, pObject->pClassName));
        ;        
    }    
    else
    {
        DEBUGMSG(DBG_ERROR, (TEXT("IRLMP: Failed to add Ias attribute\n")));
        
        if (pObject && NewObjectOnList) RemoveEntryList(&pObject->Linkage);    
        if (pObject && NewObject) IRDA_FREE_MEM(pObject);
        if (pClassName) IRDA_FREE_MEM(pClassName);
        if (pAttrib) IRDA_FREE_MEM(pAttrib);
        if (pAttribName) IRDA_FREE_MEM(pAttribName);
        if (pAttribVal) IRDA_FREE_MEM(pAttribVal);
    }        

    KeReleaseSpinLock(&gSpinLock, OldIrql);
        
    return rc;
}

VOID
IasDelAttribute(PVOID AttribHandle)
{
    KIRQL           OldIrql;
    IAS_OBJECT      *pObject;
    IAS_ATTRIBUTE   *pAttrib, *pPrevAttrib;

    KeAcquireSpinLock(&gSpinLock, &OldIrql);

    DEBUGMSG(DBG_IRLMP_IAS, (TEXT("IRLMP: Delete attribHandle %x\n"),
             AttribHandle));   
    
    for (pObject = (IAS_OBJECT *) IasObjects.Flink;
         (LIST_ENTRY *) pObject != &IasObjects;
         pObject = (IAS_OBJECT *) pObject->Linkage.Flink)    
    {
        pPrevAttrib = NULL;
        
        for (pAttrib = pObject->pAttributes;
             pAttrib != NULL;
             pAttrib = pAttrib->pNext)    
        {
            if (pAttrib == AttribHandle)
            {
                DEBUGMSG(DBG_IRLMP_IAS, (TEXT("IRLMP: attrib %hs deleted\n"),
                         pAttrib->pAttribName));
                                       
                if (pAttrib == pObject->pAttributes)
                {
                    pObject->pAttributes = pAttrib->pNext;
                }
                else
                {
                    ASSERT(pPrevAttrib);
                    pPrevAttrib->pNext = pAttrib->pNext;                    
                }
                
                IRDA_FREE_MEM(pAttrib->pAttribName);
                IRDA_FREE_MEM(pAttrib->pAttribVal);                
                IRDA_FREE_MEM(pAttrib);
                
                if (pObject->pAttributes == NULL)
                {
                    DEBUGMSG(DBG_IRLMP_IAS, (TEXT("IRLMP: No attributes associated with class %hs, deleting\n"),
                            pObject->pClassName));
                            
                    RemoveEntryList(&pObject->Linkage);    
                    IRDA_FREE_MEM(pObject->pClassName);
                    IRDA_FREE_MEM(pObject);
                }
                
                goto done;
            }
            
            pPrevAttrib = pAttrib;
        }
    }
    
done:
    
    KeReleaseSpinLock(&gSpinLock, OldIrql);
}

void
InitiateRetryIasQuery(PVOID Context)
{
    PIRDA_LINK_CB   pIrdaLinkCb = (PIRDA_LINK_CB) Context;
    PIRLMP_LINK_CB  pIrlmpCb = (PIRLMP_LINK_CB) pIrdaLinkCb->IrlmpContext;
    IRDA_MSG        IMsg;
    LARGE_INTEGER   SleepMs;    


    IMsg.Prim                      = IRLMP_CONNECT_REQ;
    IMsg.IRDA_MSG_RemoteLsapSel    = IAS_LSAP_SEL;
    IMsg.IRDA_MSG_LocalLsapSel     = IAS_LOCAL_LSAP_SEL;
    IMsg.IRDA_MSG_pQos             = NULL;
    IMsg.IRDA_MSG_pConnData        = NULL;
    IMsg.IRDA_MSG_ConnDataLen      = 0;
    IMsg.IRDA_MSG_UseTtp           = FALSE;
    IMsg.IRDA_MSG_pContext         = NULL;

    RtlCopyMemory(IMsg.IRDA_MSG_RemoteDevAddr,
                  pIrlmpCb->IasQueryDevAddr,
                  IRDA_DEV_ADDR_LEN);

    while (pIrlmpCb->IasRetryCnt < 4)
    {
        pIrlmpCb->IasRetryCnt++;        
            
        DEBUGMSG(DBG_IRLMP, (TEXT("IRLMP: Retry count is %d\n"), pIrlmpCb->IasRetryCnt));
    
        SleepMs.QuadPart = -(5*1000*1000); // .5 second    
        KeDelayExecutionThread(KernelMode, FALSE, &SleepMs);
                
        if (IrlmpConnectReq(&IMsg) == SUCCESS)
        {
            return;
        }
        
    }
    
    // retrying failed
    
    DEBUGMSG(DBG_IRLMP, (TEXT("IRLMP: Retry ias failed\n")));
    
    pIrlmpCb->pIasQuery = NULL;
      
    IMsg.Prim = IRLMP_GETVALUEBYCLASS_CONF;
    IMsg.IRDA_MSG_IASStatus = IRLMP_MAC_MEDIA_BUSY;
    
    TdiUp(NULL, &IMsg);    
}

VOID
DeleteDeviceList(LIST_ENTRY *pDeviceList)
{
    IRDA_DEVICE         *pDevice, *pDeviceNext;

    for (pDevice = (IRDA_DEVICE *) pDeviceList->Flink;
         (LIST_ENTRY *) pDevice != pDeviceList;
         pDevice = pDeviceNext)
    {
        pDeviceNext = (IRDA_DEVICE *) pDevice->Linkage.Flink;
         
        IRDA_FREE_MEM(pDevice);            
    }    
        
    InitializeListHead(pDeviceList);
}

VOID
FlushDiscoveryCache()
{
    PIRDA_LINK_CB       pIrdaLinkCb;
    PIRLMP_LINK_CB      pIrlmpCb;
   
    // Assumes global spinlock held

    // Flush the per link cache
    
    for (pIrdaLinkCb = (PIRDA_LINK_CB) IrdaLinkCbList.Flink;
         (LIST_ENTRY *) pIrdaLinkCb != &IrdaLinkCbList;
         pIrdaLinkCb = (PIRDA_LINK_CB) pIrdaLinkCb->Linkage.Flink)
    {                  
        pIrlmpCb = (PIRLMP_LINK_CB) pIrdaLinkCb->IrlmpContext;
        
        DEBUGMSG(DBG_DISCOVERY, (TEXT("IRLMP: Deleting IrlmpCb:%X discovery cache\n"),
                 pIrlmpCb));        
    
        DeleteDeviceList(&pIrlmpCb->DeviceList);
    }
    
    // And the global cache

    DEBUGMSG(DBG_DISCOVERY, (TEXT("IRLMP: Deleting global discovery cache\n")));
    
    DeleteDeviceList(&gDeviceList);    
}

VOID
IrlmpGetPnpContext(
    PVOID   IrlmpContext,
    PVOID   *pPnpContext)
{
    IRLMP_LSAP_CB *pLsapCb = (IRLMP_LSAP_CB *) IrlmpContext;
    PIRDA_LINK_CB pIrdaLinkCb = NULL;

    *pPnpContext = NULL;
    
    if (pLsapCb == NULL)
    {
        return;
    }

    VALIDLSAP(pLsapCb);
    
    pIrdaLinkCb = pLsapCb->pIrlmpCb->pIrdaLinkCb;
    
    if (pIrdaLinkCb) 
    {
        *pPnpContext = pIrdaLinkCb->PnpContext;
    }
}