/*
    File:       IrLAPConn.c

    Contains:   Implementation of the TIrLAPConn class


*/

#include "IrLAPConn.h"
#include "IrLSAPConn.h"
#include "IrGlue.h"
#include "IrLAP.h"
#include "CList.h"
#include "CListIterator.h"
#include "IrDALog.h"

#if (hasTracing > 0 && hasLAPConnTracing > 0)

enum IrLAPConnTraceCodes
{
    kNullEvent = 1,
    kDestroy,
    kInit,
    kDeInit,
    kUnexpectedEvent,
    kLogStateEvent,

    kStandbyConnLstnRequestEvent,
    kStandbyDisconnectRequestEvent,
    kStandbyDisconnectReplyEvent,
    kStandbyDisconnectRequeue,

    kPendingConnLstnRequestEvent,
    kPendingConnLstnDeferRequest,           // jdg
    kPendingConnLstnReplyEvent,
    kPendingDisconnectRequestEvent,
    kPendingDisconnectReplyEvent,
    kPendingDisconnectRequeue,              // jdg

    kActiveConnLstnRequestEvent,
    kActiveConnLstnDeferRequest,            // jdg
    kActiveGetDataRequestEvent,
    kActiveCancelGetRequestEvent,
    kActiveDisconnectRequestEvent,
    kActiveDisconnectReplyEvent,
    kActiveDisconnectRequeue,               // jdg

    kDemuxInvalidHeaderEvent,
    kDemuxGetPendingEvent,
    kDemuxReplyPostedEvent,
    kDemuxReplyPostedEvent2,
    kDemuxNoReceiverEvent,
    kDemuxReleaseBufferEvent,

    kCancelPendingGetReqEvent,
    kCleanupPendingRcvdBufEvent,

    kWantToAdd,
    kAddingToLSAPConnList,
    kAddingLsapToList,

    kLogStartIdleDisconnectTimer,
    kLogStopIdleDisconnectTimer,
    kLogDoIdleDisconnect,
    
    kLogConnWatchDogFired,
    kLogIdleDisconnectFired,

    kLogReset,
    kLogResetLsapConn,
    kLogResetGetRequest,
    kLogResetGetReply,
    kLogResetPendingReq,

    kLogDemux,
    kLogDemuxCheckingGets1,
    kLogDemuxCheckingGets2,
    kLogAddingGetRequest1,
    kLogAddingGetRequest2,
    kLogAddingGetRequest3,
    
    kLogCleanupPendingGetRequestsAndRepliesEntry,
    kLogCleanupPendingGetRequestsAndReplies2,
    kLogCleanupPendingGetRequestsAndReplies3,

    kLogCancelPendingGetRequestsEntry,
    kLogCancelPendingGetRequests,
    kLogCancelPendingGetRequestsExit,
    
    kLogFillInLMPDUHeader1,
    kLogFillInLMPDUHeader2,

    kEnqueueEvent,
    kDequeueEventStart,
    kDequeueEventEnd
};

EventTraceCauseDesc IrLAPConnTraceEvents[] = {
    {kNullEvent,                    "irlapconn: create obj="},
    {kDestroy,                      "irlapconn: destroy obj="},
    {kInit,                         "irlapconn: init"},
    {kDeInit,                       "irlapconn: deinit"},
    {kUnexpectedEvent,              "irlapconn: unexpected event"},
    {kLogStateEvent,                "irlapconn: next state, state=, event="},

    {kStandbyConnLstnRequestEvent,  "irlapconn: standby conn/lstn request"},
    {kStandbyDisconnectRequestEvent,"irlapconn: standby disconnect request"},
    {kStandbyDisconnectReplyEvent,  "irlapconn: standby disconnect reply"},
    {kStandbyDisconnectRequeue,     "irlapconn: standby requeue pending requests"},     // jdg

    {kPendingConnLstnRequestEvent,  "irlapconn: uconnect conn/lstn request"},
    {kPendingConnLstnDeferRequest,  "irlapconn: uconnect conn/lstn defer request"},     // jdg
    {kPendingConnLstnReplyEvent,    "irlapconn: uconnect conn/lstn reply"},
    {kPendingDisconnectRequestEvent,"irlapconn: uconnect disconnect request"},
    {kPendingDisconnectReplyEvent,  "irlapconn: uconnect disconnect reply"},
    {kPendingDisconnectRequeue,     "irlapconn: uconnect requeue pending request"},

    {kActiveConnLstnRequestEvent,   "irlapconn: active conn/lstn request"},
    {kActiveConnLstnDeferRequest,   "irlapconn: active conn/lstn defer request"},       // jdg
    {kActiveGetDataRequestEvent,    "irlapconn: active get data request"},
    {kActiveCancelGetRequestEvent,  "irlapconn: active cancel get request"},
    {kActiveDisconnectRequestEvent, "irlapconn: active disconnect request"},
    {kActiveDisconnectReplyEvent,   "irlapconn: active disconnect reply"},
    {kActiveDisconnectRequeue,      "irlapconn: active requeue pending request"},

    {kDemuxInvalidHeaderEvent,      "irlapconn: invalid header"},
    {kDemuxGetPendingEvent,         "irlapconn: found get pending, event rec="},
    {kDemuxReplyPostedEvent,        "irlapconn: found lsapconn, no get. buf="},
    {kDemuxReplyPostedEvent2,       "irlapconn: found lsapconn, no get. lsapconn="},
    {kDemuxNoReceiverEvent,         "irlapconn: no receiver"},
    {kDemuxReleaseBufferEvent,      "irlapconn: release buffer"},

    {kCancelPendingGetReqEvent,     "irlapconn: cancel pending get request, lsapconn="},
    {kCleanupPendingRcvdBufEvent,   "irlapconn: cleaning up pending recd buffer"},

    {kWantToAdd,                    "irlapconn: ** want to add lsapconn to list"},
    {kAddingToLSAPConnList,         "irlapconn: ** add/del to fLSAPConnList, add=, id="},
    {kAddingLsapToList,             "irlapconn: ** add/del of lsap="},

    {kLogStartIdleDisconnectTimer,  "irlapconn: starting idle disconnect timer"},
    {kLogStopIdleDisconnectTimer,   "irlapconn: stoppng idle disconnect timer"},
    {kLogDoIdleDisconnect,          "irlapconn: doing idle disconnect"},
    
    {kLogConnWatchDogFired,         "irlapconn: conn watchdog timer fired"},
    {kLogIdleDisconnectFired,       "irlapconn: idle disconnect timer fired"},

    {kLogReset,                     "irlapconn: reset. fConnected, fState"},
    {kLogResetLsapConn,             "irlapconn: reset. lsapconn="},
    {kLogResetGetRequest,           "irlapconn: reset. getrequest="},
    {kLogResetGetReply,             "irlapconn: reset. getreply="},
    {kLogResetPendingReq,           "irlapconn: reset. pending request"},

    {kLogDemux,                     "irlapconn: demux input packet, buf="},
    {kLogDemuxCheckingGets1,        "irlapconn: demux, pending get count="},
    {kLogDemuxCheckingGets2,        "irlapconn: demux, checking event rec="},
    {kLogAddingGetRequest1,         "irlapconn: saving get req, event Rec="},
    {kLogAddingGetRequest2,         "irlapconn: saving get req, lsapconn="},
    {kLogAddingGetRequest3,         "irlapconn: saving get req, qlen=, lsapid="},

    {kLogCleanupPendingGetRequestsAndRepliesEntry,  "irlapconn: CleanupPendingGetRequestsAndReplies entry, lsapConn="},
    {kLogCleanupPendingGetRequestsAndReplies2,      "irlapconn: CleanupPendingGetRequestsAndReplies 2"},
    {kLogCleanupPendingGetRequestsAndReplies3,      "irlapconn: CleanupPendingGetRequestsAndReplies, replyBuffer="},
    
    {kLogCancelPendingGetRequestsEntry,         "irlapconn: CancelPendingGetRequestsEntry"},
    {kLogCancelPendingGetRequests,              "irlapconn: CancelPendingGetRequests"},
    {kLogCancelPendingGetRequestsExit,          "irlapconn: CancelPendingGetRequestsExit"},
    
    {kLogFillInLMPDUHeader1,        "irlapconn: putRequest"},
    {kLogFillInLMPDUHeader2,        "irlapconn: buffer"},


    {kEnqueueEvent,                 "irlapconn: Event Queued"},
    {kDequeueEventStart,            "irlapconn: Event Start"},
    {kDequeueEventEnd,              "irlapconn: Event End"}
};

#define XTRACE(x, y, z) IrDALogAdd( x, y, z, IrLAPConnTraceEvents, true )
#else
#define XTRACE(x, y, z) ((void)0)
#endif

#define GetLAP  (fIrDA->GetLAP())

#define super TIrStream
	OSDefineMetaClassAndStructors(TIrLAPConn, TIrStream);

//--------------------------------------------------------------------------------
//      tIrLAPConn
//--------------------------------------------------------------------------------
/*static*/
TIrLAPConn *
TIrLAPConn::tIrLAPConn(TIrGlue* irda)
{
	TIrLAPConn *obj = new TIrLAPConn;
	XTRACE(kNullEvent, (int)obj >> 16, (short)obj);

	if (obj && !obj->Init(irda)) {
		obj->release();
		obj = nil;
	}
	return obj;
}



//--------------------------------------------------------------------------------
//      free
//--------------------------------------------------------------------------------
void
TIrLAPConn::free()
{
    XTRACE(kDestroy, (int)this >> 16, (short)this);
    
    // Free things allocated by TIrLAPConn
    StopIdleDisconnectTimer();              // make sure the timer is off

#define FREE(x) { if (x) { (x)->release(); x = nil; } }
    
    FREE(fLSAPConnList);                // Free the LSAPConn list
    FREE(fPendingGetRequests);          // Free the pending get requests list
    FREE(fUnmatchedGetReplys);          // Free the unmatched get replys list
    FREE(fPendingRequests);             // Free the pending event list


    super::free();
    
} // TIrLAPConn::free


//--------------------------------------------------------------------------------
//      Init
//--------------------------------------------------------------------------------
Boolean TIrLAPConn::Init(TIrGlue* irda)
{
    XTRACE(kInit, (int)this >> 16, (short)this);
    
    
    fState = kIrLAPConnStandby;
    fConnected = false;
    fPeerDevAddr = 0;
    fLSAPConnList = nil;
    fPendingGetRequests = nil;
    fUnmatchedGetReplys = nil;
    fPendingRequests = nil;
    
    fDisconnectPending = false;
    
    
    // Init IrStream
#if (hasTracing > 0 && hasLAPConnTracing > 0)
     if (!super::Init(irda, IrLAPConnTraceEvents, kEnqueueEvent)) return false;
#else
    if (!super::Init(irda)) return false;
#endif


    // Init LSAPConn list
    fLSAPConnList = CList::cList();
    require(fLSAPConnList, Fail);

    // Init the pending get requests list
    fPendingGetRequests = CList::cList();
    require(fPendingGetRequests, Fail);

    // Init the received (but unmatched) get replies
    fUnmatchedGetReplys = CList::cList();
    require(fUnmatchedGetReplys, Fail);

    // Init the list of deferred requests
    fPendingRequests = CList::cList();
    require(fPendingRequests, Fail);
    
    return true;

Fail:

    return false;

} // TIrLAPConn::Init


//--------------------------------------------------------------------------------
//      Reset
//--------------------------------------------------------------------------------
void TIrLAPConn::Reset()
{
    
    XTRACE(kLogReset, fConnected, fState);
    
    // This will force other asserts in HandleStandbyStateEvent which check
    // to see that all of the other fields were properly reset at disconnect.

    fState          = kIrLAPConnStandby;    // Using Reset for Aync disconnect.  There
    fConnected      = false;                // will be no disconnect reply (for now).
    fPeerDevAddr    = 0;
    
    StopIdleDisconnectTimer();              // make sure the timer is off
    
    //
    // JDG: let's loop over our lsap conn list and purge everything.
    // this should pick up pending get requests and pending get replies
    // but for sanities sake, we'll check them too after this iteration.
    //
    if (fLSAPConnList && !fLSAPConnList->Empty()) {     // get rid of all pending listen/connects
	int index;
	TLSAPConn* lsapConn;
	
	XTRACE(kLogResetLsapConn, 1, fLSAPConnList->GetArraySize());
	for (index = fLSAPConnList->GetArraySize() - 1; index >= 0 ; index--) {
	    lsapConn = (TLSAPConn*)fLSAPConnList->At(index);
	    XTRACE(kLogResetLsapConn, (int)lsapConn >> 16, (short)lsapConn);
	    // Complete pending get requests and delete any received buffers intended for this conn
	    CleanupPendingGetRequestsAndReplies(lsapConn, errCancel);
	}
	while (!fLSAPConnList->Empty())
	    fLSAPConnList->RemoveLast();
	XTRACE(kLogResetLsapConn, 0xffff, 0xffff);
    }
    
    if (fPendingGetRequests && !fPendingGetRequests->Empty()) {     // Cancel all pending Get requests
	CListIterator *iter = CListIterator::cListIterator(fPendingGetRequests);
	TIrGetRequest *getRequest;
	
	DebugLog("IrLapConn: reset pending get request. how?"); // shouldn't get here
	for (getRequest = (TIrGetRequest*)iter->FirstItem();
	     iter->More(); getRequest = (TIrGetRequest*)iter->NextItem()) {
	//for (getRequest = OSDynamicCast(TIrGetRequest, (OSObject *)iter->FirstItem());
	//   iter->More(); getRequest = OSDynamicCast(TIrGetRequest, (OSObject *)iter->NextItem())) {
	    XTRACE(kLogResetGetRequest, (int)getRequest >> 16, (short)getRequest);
	    // This is somewhat inefficient, since Cleanup will loop through the pending list
	    // again to match the LSAP.  But that's ok.
	    this->CleanupPendingGetRequestsAndReplies( getRequest->fLSAPConn, errCancel);
	}
	iter->release();
    }

    // fUnmatchedGetReplys is a list of CBufferSegments that have come in, discard them
    if (fUnmatchedGetReplys && !fUnmatchedGetReplys->Empty()) {
	CListIterator *iter = CListIterator::cListIterator(fUnmatchedGetReplys);
	CBufferSegment* replyBuffer;
	
	DebugLog("IrLapConn: reset pending get replies. how?"); // shouldn't get here
	for (replyBuffer = (CBufferSegment*)iter->FirstItem();
	     iter->More(); replyBuffer = (CBufferSegment*)iter->NextItem()) {
	    XTRACE(kLogResetGetReply, (int)replyBuffer >> 16, (short)replyBuffer);
	    GetLAP->ReleaseInputBuffer(replyBuffer);    // give the buffer back to lap
	}
	while (!fUnmatchedGetReplys->Empty())
	    fUnmatchedGetReplys->RemoveLast();
	    
	iter->release();
    }
		
    // if we had pendingRequests, they were waiting for a disconnect, do 'em now!
    if (fPendingRequests && !fPendingRequests->Empty()) {
	CListIterator *iter = CListIterator::cListIterator(fPendingRequests);
	for (TIrEvent* request = (TIrEvent*)iter->FirstItem();
	     iter->More(); request = (TIrEvent*)iter->NextItem()) {
		XTRACE(kLogResetPendingReq, (int)request >> 16, (short)request);
		check(request->fEvent == kIrListenRequestEvent || request->fEvent == kIrConnectRequestEvent);
		this->EnqueueEvent(request);
	}
	while (!fPendingRequests->Empty())
	    fPendingRequests->RemoveLast();
	    
	iter->release();
    }
    fDisconnectPending = false;         // safe to connect/listen again

    // Whew.  I *think* we've fully reset LapConn.  

} // TIrLAPConn::Reset



//--------------------------------------------------------------------------------
//      NextState
//--------------------------------------------------------------------------------
void TIrLAPConn::NextState(ULong event)
{
    XTRACE(kLogStateEvent, fState, event);
    
    switch (fState) {
	case kIrLAPConnStandby:
	    HandleStandbyStateEvent(event);
	    break;

	case kIrLAPConnConnectOrListen:
	    HandleConnectOrListenStateEvent(event);
	    break;

	case kIrLAPConnActive:
	    HandleActiveStateEvent(event);
	    break;

	default:
	    DebugLog("TIrLAPConn::NextState: bad fState");
	    break;
    }

} // TIrLAPConn::NextState


//--------------------------------------------------------------------------------
//      HandleStandbyStateEvent
//--------------------------------------------------------------------------------
void TIrLAPConn::HandleStandbyStateEvent(ULong event)
{
    XASSERT(!fConnected);
    XASSERT(fPeerDevAddr == 0);

    switch (event) {
	case kIrConnectRequestEvent:
	case kIrListenRequestEvent:
	    {
		TIrConnLstnRequest* request = (TIrConnLstnRequest*)GetCurrentEvent();
		XTRACE(kStandbyConnLstnRequestEvent, event, 0);

		XTRACE(kWantToAdd, (int)request->fLSAPConn>>16, (short)request->fLSAPConn);

		// Add the lsapConn to the list of pending conns to connect/listen
		// jdg: the IAS server gets an error on it's listen and immediately issues another
		// listen request.  Since it didn't do a disconnect, it's LSAP is still on our list.
		if (!fLSAPConnList->Contains(request->fLSAPConn)) {
		    fLSAPConnList->InsertLast(request->fLSAPConn);
		}

		// Keep track of the device that we will be connected to
		if (event == kIrConnectRequestEvent) {
		    fPeerDevAddr = request->fDevAddr;
		}

		// Pass the request on to IrLAP
		// ***This can be dangerous if the connect can be cancelled separately
		// from all other pending requests and then the IrLAP will depend on a
		// buffer that no longer is "allocated".  In other words, maybe a separate
		// req buffer should be allocated for this purpose.
		fState = kIrLAPConnConnectOrListen;
		GetLAP->EnqueueEvent(request);
	    }
	    break;

	case kIrDisconnectRequestEvent:
	    {
		// Pass the disconnect on to IrLAP ********* JDG: nope, this is not what we want.  lap is already disconnected
		XTRACE(kStandbyDisconnectRequestEvent, 0, 0);
		
		if (1) {        // jdg testing
		    TIrDisconnectRequest* disconnectRequest = (TIrDisconnectRequest*)GetCurrentEvent();
		    // Remove this lsap from the lsap conn list -- if it's still there
		    if (fLSAPConnList->Contains(disconnectRequest->fLSAPConn)) {    // sigh, it doesn't always ...
			XTRACE(kAddingToLSAPConnList, 0, 
				disconnectRequest->fLSAPConn->GetMyLSAPId() << 8 | 99);
			XTRACE(kAddingLsapToList, (int)disconnectRequest->fLSAPConn >> 16, (short)disconnectRequest->fLSAPConn);        // jdg
		    IrDAErr removeResult = fLSAPConnList->Remove(disconnectRequest->fLSAPConn);
		    ncheck(removeResult);
		    }
		}
		//fIrLAP->EnqueueEvent(GetCurrentEvent());      // no, don't do it -- jdg
		if (1) {        // jdg new
		    TIrDisconnectRequest* disconnectRequest = (TIrDisconnectRequest*)GetCurrentEvent();
		    disconnectRequest->fEvent = kIrDisconnectReplyEvent;        // turn into a reply
		    disconnectRequest->fResult = 0;
		    check(disconnectRequest->fLSAPConn);            // internal disconnect?
		    disconnectRequest->fLSAPConn->EnqueueEvent(disconnectRequest);  // send it back
		}
	    }       
	    break;

	case kIrDisconnectReplyEvent:
	    {
		// Use the request block for the reply
		TIrDisconnectReply* disconnectReply = (TIrDisconnectReply*)GetCurrentEvent();
		XTRACE(kStandbyDisconnectReplyEvent, 0, 0);
		// Already disconnected - nothing to do, except return the reply.
		//check(disconnectReply->fLSAPConn);
		if (disconnectReply->fLSAPConn == nil)              // if internally generated event
		    fIrDA->ReleaseEventBlock(disconnectReply);      // then release it here
		else
		disconnectReply->fLSAPConn->EnqueueEvent(disconnectReply);
		
		// jdg: if any deferred events, requeue them now
		if (fPendingRequests && !fPendingRequests->Empty()) {
		    CListIterator *iter = CListIterator::cListIterator(fPendingRequests);
		    for (TIrEvent* request = (TIrEvent*)iter->FirstItem();
			 iter->More(); request = (TIrEvent*)iter->NextItem()) {
			    XTRACE(kStandbyDisconnectRequeue, (int)request >> 16, (short)request);
			    check(request->fEvent == kIrListenRequestEvent || request->fEvent == kIrConnectRequestEvent);
			    this->EnqueueEvent(request);
		    }
		    while (!fPendingRequests->Empty())
			fPendingRequests->RemoveLast();
			
		    iter->release();
		}
		fDisconnectPending = false;         // safe to connect/listen again
	    }
	    break;

	case kIrGetDataRequestEvent:            // if we get a get/put here, just reject it
	case kIrPutDataRequestEvent:
	    {
		XTRACE(kUnexpectedEvent, fState, event);
		TIrGetRequest* rq = (TIrGetRequest*)GetCurrentEvent();
		if (rq->fEvent == kIrGetDataRequestEvent)       // turn request into reply (could ++ it)
		    rq->fEvent = kIrGetDataReplyEvent;
		else
		    rq->fEvent = kIrPutDataReplyEvent;
		rq->fResult = kIrDAErrWrongState;           // better than crashing, but why are we here?
		check(rq->fLSAPConn);
		rq->fLSAPConn->EnqueueEvent(rq);            // return to sender, bad destination
	    }
	    break;
	    
	default:
	    XTRACE(kUnexpectedEvent, fState, event);
	    DebugLog("TIrLAPConn::HandleStandbyStateEvent: bad event");
	    break;
    }

} // TIrLAPConn::HandleStandbyStateEvent


//--------------------------------------------------------------------------------
//      HandleConnectOrListenStateEvent
//--------------------------------------------------------------------------------
void TIrLAPConn::HandleConnectOrListenStateEvent(ULong event)
{
    XASSERT(!fConnected);

    switch (event) {
	case kIrConnectRequestEvent:
	case kIrListenRequestEvent:
	    {
		TIrConnLstnRequest* request = (TIrConnLstnRequest*)GetCurrentEvent();
		XTRACE(kPendingConnLstnRequestEvent, event, request->fEvent);
	    
		if (fDisconnectPending) {           // oops, hold off on this request unti the disconnect is done
		    XTRACE(kPendingConnLstnDeferRequest, (int)GetCurrentEvent() >> 16, (short)GetCurrentEvent());
		    fPendingRequests->InsertLast(GetCurrentEvent());
		    check(GetCurrentEvent()->fEvent == kIrListenRequestEvent || GetCurrentEvent()->fEvent == kIrConnectRequestEvent);
		    return;
		}
	    
		// ***Here is a problem that probably will have to be dealt with when the
		// IrDA "stack" is used by a mux tool (i.e. multiple endpoints).  If a listen
		// was the first request to initiate a listen to LAP and then a connect request
		// followed, you need to cancel the pending LAP listen request and replace it
		// with a connect request.

		// Shouldn't be called if connecting to a different device, reject the request if it comes in that way
		if (event == kIrConnectRequestEvent) {
		    // This first one checks for the situation described above
		    //XASSERT(fPeerDevAddr != 0);
		    //XASSERT(fPeerDevAddr == request->fDevAddr);
									// JDG: if 1st was connect & this is going to diff device
		    if (fPeerDevAddr && (fPeerDevAddr != request->fDevAddr)) {  // then reject this connect request
			request->fEvent = kIrConnectReplyEvent;
			request->fResult = kIrDAErrGeneric;             // better err available?
			check(request->fLSAPConn);
			request->fLSAPConn->EnqueueEvent(request);      // return to sender, bad destination
			return;
		    }
		    
		    // JDG hacking  ... won't it ever stop?
		    // we have a connect request, was the first request a listen?  if so ....
		    // ask LAP to cancel the pending listen and then send in our connect request.
		    // Foo.  Bletch.  Ugh.
		    if (fPeerDevAddr == 0) {                // if 1st request was a listen ...
			if (GetLAP->CancelPendingListenRequest()) {     // if the lap cancel worked
			    fPeerDevAddr = request->fDevAddr;           // save the destination address
			    GetLAP->EnqueueEvent(request);              // then send connect req to lap
			    // continue and add the lsapconn to our list
			    // note: the listen request is still on our queue and will finish with the connect
			}
			else {      // the cancel of the listen failed, reject the connect request (or?)
			    request->fEvent = kIrConnectReplyEvent;
			    request->fResult = kIrDAErrGeneric;             // better err available?
			    check(request->fLSAPConn);
			    request->fLSAPConn->EnqueueEvent(request);      // return to sender, bad destination
			    return;
			}
		    }       // end first request a listen
		}       // end if connect request
		
		// Add the lsapConn to the list of pending conns to connect/listen
		XASSERT(!fLSAPConnList->IsEmpty());
		XASSERT(!fLSAPConnList->Contains(request->fLSAPConn));
		
		XTRACE(kAddingToLSAPConnList, 1, request->fLSAPConn->GetMyLSAPId() << 8 | 1);
		XTRACE(kAddingLsapToList, (int)request->fLSAPConn >> 16, (short)request->fLSAPConn);
		fLSAPConnList->InsertLast(request->fLSAPConn);

		// No state change
	    }
	    break;

	case kIrConnectReplyEvent:
	case kIrListenReplyEvent:
	    {
		TIrConnLstnReply* reply = (TIrConnLstnReply*)GetCurrentEvent();
		XTRACE(kPendingConnLstnReplyEvent, event, reply->fResult);
		{
		    CListIterator *iter = CListIterator::cListIterator(fLSAPConnList);

		    // Complete all pending connect/listen requests that are in the list
		    for (TLSAPConn* lsapConn = (TLSAPConn*)iter->FirstItem();
			 iter->More(); lsapConn = (TLSAPConn*)iter->NextItem()) {
			TIrConnLstnReply* pendingReply = (TIrConnLstnReply*)lsapConn->GetPendConnLstn();
			//check(pendingReply);      // TEMP TEMP TEMP -- should track this down
			if (pendingReply) {
			    // ***Oops, this mess is because some pending buffers are used for the
			    // ***actual request and others aren't - see other note earlier in the file.
			    if (pendingReply && pendingReply->fEvent == kIrConnectRequestEvent) {
				pendingReply->fEvent = kIrConnectReplyEvent;
			    }
			    else if (pendingReply->fEvent == kIrListenRequestEvent){
				pendingReply->fEvent = kIrListenReplyEvent;
			    }
			    else {
				// Assuming that reply has been set by IrLAP.
				// I.e. this pendingReply == GetCurrentEvent()
				XASSERT((pendingReply->fEvent == kIrConnectReplyEvent) ||
					(pendingReply->fEvent == kIrListenReplyEvent));
			    }
			    pendingReply->fResult = reply->fResult;
			    pendingReply->fDevAddr = reply->fDevAddr;
			    lsapConn->EnqueueEvent(pendingReply);
			}
		    }
		    iter->release();
		    
		} // barney block

		// If connect succeeded, move to the active state (California?)
		if (reply->fResult == noErr) {
		    fPeerDevAddr = reply->fDevAddr; // For listen's benefit
		    fState = kIrLAPConnActive;
		    fConnected = true;
		}

		// If connect failed, clean up after responding to the requestors
		// query: shouldn't the cleanup pending be done first?  and isn't it a nop here?
		else {
		    // Removing from end is much faster (and keeps indices valid as items are removed)
		    XTRACE(kAddingToLSAPConnList, 0, 2);                                    // jdg
		    for (FastInt index = fLSAPConnList->GetArraySize() - 1; index >= 0 ; index--) {
			TLSAPConn* lsapConn = (TLSAPConn*)fLSAPConnList->At(index);
			// Remove this lsap from the lsap conn list
			XTRACE(kAddingToLSAPConnList, 0,  lsapConn->GetMyLSAPId() << 8 | 2);    // jdg
			XTRACE(kAddingLsapToList, (int)lsapConn >> 16, (short)lsapConn);        // jdg
			fLSAPConnList->Remove(lsapConn);
			// Complete pending get requests and delete any received buffers intended for this conn
			CleanupPendingGetRequestsAndReplies(lsapConn, errCancel);
		    }
		    fPeerDevAddr = 0;
		    fState = kIrLAPConnStandby;
		}
	    }
	    break;

	case kIrDisconnectRequestEvent:
	    {
		TIrDisconnectRequest* disconnectRequest = (TIrDisconnectRequest*)GetCurrentEvent();
		XTRACE(kPendingDisconnectRequestEvent, (int)disconnectRequest->fLSAPConn >> 16, (short)disconnectRequest->fLSAPConn);
    /*** this didn't work.  Just do a lap disconnect, and we'll return all the pending listen/connect
	 requests.  Anyone that want's to keep alive can re-issue.
      ... let's try again
    ***/
		// if the requesting lsapconn owns the event we sent to lap, do a lap disconnect
		// else we're free to just take it off our tables w/out bothering lap
		check(disconnectRequest->fLSAPConn);
		if (GetLAP->GetCurrentRequest() == disconnectRequest->fLSAPConn->GetPendConnLstn()) {
		    fDisconnectPending = true;                  // hold off on any new listen/connect requests
		    GetLAP->EnqueueEvent(disconnectRequest);    // tell lap to blow off the current listen/conn req
		}
		else {              // lap doesn't have the event from this lsapconn
	 
		    XTRACE(kAddingToLSAPConnList, 0, disconnectRequest->fLSAPConn->GetMyLSAPId() << 8 | 3);
		    XTRACE(kAddingLsapToList, (int)disconnectRequest->fLSAPConn >> 16, (short)disconnectRequest->fLSAPConn);        // jdg
		    // Remove this lsap from the lsap conn list and
		    // complete pending get requests and delete any received buffers intended for this conn
		    IrDAErr removeResult = fLSAPConnList->Remove(disconnectRequest->fLSAPConn);
		    CleanupPendingGetRequestsAndReplies(disconnectRequest->fLSAPConn, errCancel);
		
		    XTRACE(kPendingDisconnectRequestEvent, 2, 
			(removeResult == noErr) << 2 | (fLSAPConnList->IsEmpty()) << 1 );
			
			
		    // now finish off their original listen/connect request
		    if (disconnectRequest->fLSAPConn->GetPendConnLstn()) {      // if the event is there (should be)
			TIrConnLstnReply* pendingReply = (TIrConnLstnReply*)disconnectRequest->fLSAPConn->GetPendConnLstn();
			if (pendingReply->fEvent == kIrConnectRequestEvent) {
			    pendingReply->fEvent = kIrConnectReplyEvent;
			}
			else if (pendingReply->fEvent == kIrListenRequestEvent) {
				pendingReply->fEvent = kIrListenReplyEvent;
			}
			else check(pendingReply->fEvent == 0x1234);     // force debugger
			pendingReply->fResult = errCancel;
			pendingReply->fLSAPConn->EnqueueEvent(pendingReply);
		    }
		    
		    // Use the request buffer for the reply
		    disconnectRequest->fEvent = kIrDisconnectReplyEvent;
		    disconnectRequest->fResult = errCancel;
		    disconnectRequest->fLSAPConn->EnqueueEvent(disconnectRequest);
		}
	    }
	    break;

	case kIrDisconnectReplyEvent:
	    {
		TIrDisconnectReply* disconnectReply = (TIrDisconnectReply*)GetCurrentEvent();
		XTRACE(kPendingDisconnectReplyEvent, 0, 0);
		fPeerDevAddr = 0;
		fConnected = false;
		fState = kIrLAPConnStandby;
		//check(disconnectReply->fLSAPConn);            // internal disconnect?
		if (disconnectReply->fLSAPConn == nil)          // if lapconn generated disconnect
		    fIrDA->ReleaseEventBlock(disconnectReply);      // then release it here
		else
		disconnectReply->fLSAPConn->EnqueueEvent(disconnectReply);
		
		// jdg: if any deferred events, requeue them now
		if (fPendingRequests && !fPendingRequests->Empty()) {
		    CListIterator *iter = CListIterator::cListIterator(fPendingRequests);
		    for (TIrEvent* request = (TIrEvent*)iter->FirstItem();
			 iter->More(); request = (TIrEvent*)iter->NextItem()) {
			    XTRACE(kPendingDisconnectRequeue, (int)request >> 16, (short)request);
			    check(request->fEvent == kIrListenRequestEvent || request->fEvent == kIrConnectRequestEvent);
			    this->EnqueueEvent(request);
		    }
		    while (!fPendingRequests->Empty())
			fPendingRequests->RemoveLast();
			
		    iter->release();
		}
		fDisconnectPending = false;         // safe to connect/listen again
	    }
	    break;

	default:
	    XTRACE(kUnexpectedEvent, fState, event);
	    DebugLog("TIrLAPConn::HandleConnectOrListenStateEvent: bad event");
	    break;
    }

} // TIrLAPConn::HandleConnectOrListenStateEvent


//--------------------------------------------------------------------------------
//      HandleActiveStateEvent
//--------------------------------------------------------------------------------
void TIrLAPConn::HandleActiveStateEvent(ULong event)
{
    XASSERT(fConnected);
    XASSERT(fPeerDevAddr != 0);

    StopIdleDisconnectTimer();          // always (?) stop the idle timer, we're doing something!
    switch (event) {
	case kIrConnectRequestEvent:
	case kIrListenRequestEvent:
	    {
		TIrConnLstnRequest* request = (TIrConnLstnRequest*)GetCurrentEvent();
		XTRACE(kActiveConnLstnRequestEvent, event, request->fEvent);
		
		if (fDisconnectPending) {           // oops, hold off on this request unti the disconnect is done
		    XTRACE(kActiveConnLstnDeferRequest, (int)GetCurrentEvent() >> 16, (short)GetCurrentEvent());
		    fPendingRequests->InsertLast(GetCurrentEvent());
		    check(GetCurrentEvent()->fEvent == kIrListenRequestEvent || GetCurrentEvent()->fEvent == kIrConnectRequestEvent);
		    return;
		}

		// Shouldn't be called if connecting to a different device
		// jdg: this should reject the request, not punt.
		if (event == kIrConnectRequestEvent) {
		    //XASSERT(fPeerDevAddr == request->fDevAddr);
		    if (fPeerDevAddr != request->fDevAddr) {
			// FIXME -- should add logging here of both addresses to see why the confusion ...
			request->fEvent = kIrConnectReplyEvent;
			request->fResult = kIrDAErrGeneric;             // better err available?
			check(request->fLSAPConn);
			request->fLSAPConn->EnqueueEvent(request);      // return to sender, bad destination
			break;                                          // done with it now
		    }
		}

		// Add the lsapConn to the list of conns associated w/fPeerDevAddr
		XASSERT(!fLSAPConnList->Contains(request->fLSAPConn));
		XTRACE(kAddingToLSAPConnList, 1, request->fLSAPConn->GetMyLSAPId() << 8 | 4);                                                       // jdg
		XTRACE(kAddingLsapToList, (int)request->fLSAPConn >> 16, (short)request->fLSAPConn);        // jdg
		fLSAPConnList->InsertLast(request->fLSAPConn);

		// Already connected, reply to the requestor
		request->fEvent = event == kIrConnectRequestEvent ? kIrConnectReplyEvent : kIrListenReplyEvent;
		request->fDevAddr = fPeerDevAddr;
		check(request->fLSAPConn);
		request->fLSAPConn->EnqueueEvent(request);

		// No state change
	    }
	    break;

	case kIrGetDataRequestEvent:
	    XTRACE(kActiveGetDataRequestEvent, 0, 0);
	    HandleGetDataRequest();
	    break;

	case kIrCancelGetRequestEvent:
	    {
		TIrCancelGetRequest* cancelGetRequest = (TIrCancelGetRequest*)GetCurrentEvent();
		XTRACE(kActiveCancelGetRequestEvent, 0, 0);
		CancelPendingGetRequests(cancelGetRequest->fLSAPConn, kIrDAErrRequestCanceled);
		// Use the request buffer for the reply
		cancelGetRequest->fEvent = kIrCancelGetReplyEvent;
		cancelGetRequest->fResult = noErr;
		check(cancelGetRequest->fLSAPConn);
		cancelGetRequest->fLSAPConn->EnqueueEvent(cancelGetRequest);
	    }
	    break;

	case kIrDisconnectRequestEvent:
	    {
		TIrDisconnectRequest* disconnectRequest = (TIrDisconnectRequest*)GetCurrentEvent();
		XTRACE(kActiveDisconnectRequestEvent, 0, 0);
		
		XTRACE(kAddingToLSAPConnList, 0, disconnectRequest->fLSAPConn->GetMyLSAPId() << 8 | 5);                                                     // jdg
		XTRACE(kAddingLsapToList, (int)disconnectRequest->fLSAPConn >> 16, (short)disconnectRequest->fLSAPConn);        // jdg
		
		// Remove this lsap from the lsap conn list
		IrDAErr removeResult = fLSAPConnList->Remove(disconnectRequest->fLSAPConn);
		// Complete pending get requests and delete any received buffers intended for this conn
		CleanupPendingGetRequestsAndReplies(disconnectRequest->fLSAPConn, kIrDAErrGeneric);
		
		// Note: if I do a disconnect after getting a read complete, this gets called twice, once
		// for the disconnect request, and (I think) once to clean up the pending get request.
		///////////////////////////////////////////////////////////////////////////
		// start of ugly hack to kill off name server if it's all that's left
		//  this gets weird cause it's "the" pending event for LAP.  Grrr.
		if (removeResult == noErr && fLSAPConnList->GetArraySize() == 1) {  // if only one thing left on the list
		    TLSAPConn* lsapConn = (TLSAPConn*)fLSAPConnList->At(0);         // grab it
		    if (lsapConn->GetMyLSAPId() == kNameServerLSAPId) {             // sigh, if the name server
			TIrConnLstnReply* pendingReply = (TIrConnLstnReply*)lsapConn->GetPendConnLstn();    // get it's event
			if (pendingReply && 
			    (pendingReply->fEvent == kIrListenRequestEvent ||           // it was a listen request, but now that
			     pendingReply->fEvent == kIrGetDataRequestEvent)) {         // we're active, it's a get request
	    // NEW: let's start a timer here and do the lap disconnect after N seconds of 
	    // nothing going on ...
			    StartIdleDisconnectTimer();             // start the idle disconnect timer
		//
		// Nuke the name server's listen so we can do a LAP disconnect
		//
		//          check(pendingReply->fEvent == kIrGetDataRequestEvent);          // just making sure
		//          removeResult = fLSAPConnList->Remove(lsapConn);                 // get name server off our list
		//          CancelPendingGetRequests(lsapConn, errCancel);              // this will q up the get response
			}
		    }
		} 
		////////
		//if ((removeResult == noErr) && fLSAPConnList->IsEmpty() && fIrDA->Disconnecting())
		if (disconnectRequest->fEvent == kIrDisconnectRequestEvent) {       // if "the" event is still available
    ////////*** with the idle disconnect timer, we never request a lap disconnect here anymore
		    if (((removeResult == noErr) && fLSAPConnList->IsEmpty())) {
		    // If no more connections [and shutting down] - disconnect IrLAP
			fDisconnectPending = true;                  // hold off on any new listen/connect requests
		    GetLAP->EnqueueEvent(disconnectRequest);
		}
		else {
		    // Use the request buffer for the reply
		    disconnectRequest->fEvent = kIrDisconnectReplyEvent;
		    disconnectRequest->fResult = errCancel;
		    check(disconnectRequest->fLSAPConn);
		    disconnectRequest->fLSAPConn->EnqueueEvent(disconnectRequest);
		}
		}
		// else the event was a get and turned into a get reply already by CleanupPendingGetRequestsAndReplies 
	    }
	    break;

	case kIrDisconnectReplyEvent:
	    {
		TIrDisconnectReply* disconnectReply = (TIrDisconnectReply*)GetCurrentEvent();
		XTRACE(kActiveDisconnectReplyEvent, 0, 0);
		fPeerDevAddr = 0;
		fConnected = false;
		fState = kIrLAPConnStandby;
		
		// new - if this is a disconnect generated by our idle timer, then fLSAPConn will be nil
		if (disconnectReply->fLSAPConn == nil) {            // if we allocated the event
		    fIrDA->ReleaseEventBlock(disconnectReply);      // then release it here
		    //DebugLog(" disconnect due to idle");
		}
		else {      // else send the disconnect complete back to the lsap
		    disconnectReply->fLSAPConn->EnqueueEvent(disconnectReply);
		}
		
		// jdg: if any deferred events, requeue them now
		if (fPendingRequests && !fPendingRequests->Empty()) {
		    CListIterator *iter = CListIterator::cListIterator(fPendingRequests);
		    for (TIrEvent* request = (TIrEvent*)iter->FirstItem();
			 iter->More(); request = (TIrEvent*)iter->NextItem()) {
			    XTRACE(kActiveDisconnectRequeue, (int)request >> 16, (short)request);
			    check(request->fEvent == kIrListenRequestEvent || request->fEvent == kIrConnectRequestEvent);
			    this->EnqueueEvent(request);
		    }
		    while (!fPendingRequests->Empty())
			fPendingRequests->RemoveLast();
		    
		    iter->release();
		}
		fDisconnectPending = false;         // safe to connect/listen again
	    }
	    break;

	case kIdleDisconnectEvent:                  // idle disconnect timer fired
	    {
		if (fLSAPConnList->GetArraySize() == 1) {               // if only one thing on the list
		    TLSAPConn* lsapConn = (TLSAPConn*)fLSAPConnList->At(0);         // grab ias server entry
		    if (lsapConn &&
			lsapConn->GetMyLSAPId() == kNameServerLSAPId) {             // ifthe name server
			TIrConnLstnReply* pendingReply = (TIrConnLstnReply*)lsapConn->GetPendConnLstn();    // get it's event
			if (pendingReply &&                                         // a listen is really a get
			    (pendingReply->fEvent == kIrGetDataRequestEvent)) {     // we're active, it's a get request
			    IrDAErr removeResult;
			    removeResult = fLSAPConnList->Remove(lsapConn);         // get name server off our list
			    CancelPendingGetRequests(lsapConn, errCancel);      // this will q up the get response

			    TIrDisconnectRequest* disconnectRequest = (TIrDisconnectRequest*)
						    fIrDA->GrabEventBlock(kIrDisconnectRequestEvent,
								    sizeof(TIrDisconnectRequest));
			    check(disconnectRequest);       // pretty sad if no events and idle!
			    if (disconnectRequest) {
				disconnectRequest->fLSAPConn = nil;     // lapconn generated event (not LSAPConn)
				fDisconnectPending = true;              // hold off on any new listen/connect requests
				GetLAP->EnqueueEvent(disconnectRequest);
			    }
			}
		    }
		}
	    }
	    break;

	default:
	    XTRACE(kUnexpectedEvent, fState, event);
	    DebugLog("TIrLAPConn::HandleActiveStateEvent: bad event");
	    break;
    }

} // TIrLAPConn::HandleActiveStateEvent


//================================ Helper methods ================================


//--------------------------------------------------------------------------------
//      HandleGetDataRequest
//--------------------------------------------------------------------------------
void TIrLAPConn::HandleGetDataRequest()
{
    Boolean matchFound = false;
    TIrGetRequest* getRequest = (TIrGetRequest*)GetCurrentEvent();

    // Data may have already arrived and is in the unmatched get replys list
    // If it is, then the get data request can complete right now
    CListIterator *iter = CListIterator::cListIterator(fUnmatchedGetReplys);
    for (CBufferSegment* replyBuffer = (CBufferSegment*)iter->FirstItem();
	 iter->More(); replyBuffer = (CBufferSegment*)iter->NextItem()) {
	TLMPDUHeader header;
	ULong headerLength;
	Boolean validFormat;

	validFormat = ExtractHeader(replyBuffer, header, headerLength);
	XASSERT(validFormat);

	if (DataDelivered(getRequest, header, headerLength, replyBuffer)) {
	    fUnmatchedGetReplys->Remove(replyBuffer);
	    matchFound = true;
	    break;
	}
    }
    iter->release();

    // Data is not in one of the buffers, add the request to the pending get requests.
    if (!matchFound) {
	XTRACE(kLogAddingGetRequest1, (int)getRequest >> 16, (short)getRequest);
	XTRACE(kLogAddingGetRequest2, (int)getRequest->fLSAPConn >> 16, (short)getRequest->fLSAPConn);
	fPendingGetRequests->InsertLast(getRequest);
	XTRACE(kLogAddingGetRequest3, fPendingGetRequests->Count(), getRequest->fLSAPConn->GetMyLSAPId());
    }

} // TIrLAPConn::HandleGetDataRequest


//--------------------------------------------------------------------------------
//      CleanupPendingGetRequestsAndReplies
//--------------------------------------------------------------------------------
void TIrLAPConn::CleanupPendingGetRequestsAndReplies(TLSAPConn* lsapConn, IrDAErr returnCode)
{
    XTRACE(kLogCleanupPendingGetRequestsAndRepliesEntry, (int)lsapConn >> 16, (short)lsapConn);
    
    // Complete any pending get requests with an error.  Default is kIRErrGeneric
    CancelPendingGetRequests( lsapConn, returnCode );   // ***FIXME: Better error return?

    XTRACE(kLogCleanupPendingGetRequestsAndReplies2, (int)fUnmatchedGetReplys >> 16, (short)fUnmatchedGetReplys);

    // Free any pending received buffers for this lsap connection
    if (fUnmatchedGetReplys) {
	CListIterator *iter = CListIterator::cListIterator(fUnmatchedGetReplys);
	for (CBufferSegment* replyBuffer = (CBufferSegment*)iter->FirstItem();
	     iter->More(); replyBuffer = (CBufferSegment*)iter->NextItem()) {
	    TLMPDUHeader header;
	    ULong headerLength;

	    //Boolean validFormat = ExtractHeader(replyBuffer, header, headerLength);
	    Boolean validFormat;
	    
	    XTRACE(kLogCleanupPendingGetRequestsAndReplies3, (int)replyBuffer >> 16, (short)replyBuffer);
	    
	    validFormat = ExtractHeader(replyBuffer, header, headerLength);
	    XASSERT(validFormat);

	    // If this buffer is/was for the lsapConn being removed
	    if (lsapConn->YourData(header, true /*justChecking*/)) {
		XTRACE( kCleanupPendingRcvdBufEvent, 0, returnCode );
		fUnmatchedGetReplys->Remove(replyBuffer);
		// Release the buffer.
		GetLAP->ReleaseInputBuffer(replyBuffer);    // give the buffer back to lap
	    }
	}
	iter->release();
    }

} // TIrLAPConn::CleanupPendingGetRequestsAndReplies


//--------------------------------------------------------------------------------
//      CancelPendingGetRequests
//--------------------------------------------------------------------------------
void TIrLAPConn::CancelPendingGetRequests(TLSAPConn* lsapConn, IrDAErr returnCode)
{
    // Complete any pending get requests with an error
    XTRACE(kLogCancelPendingGetRequestsEntry, (int)lsapConn >> 16, (short)lsapConn);
    
    check(lsapConn);
    if (fPendingGetRequests && !fPendingGetRequests->Empty()) {
	CListIterator *iter = CListIterator::cListIterator(fPendingGetRequests);
	for (TIrGetRequest* getRequest = (TIrGetRequest*)iter->FirstItem();
	     iter->More(); getRequest = (TIrGetRequest*)iter->NextItem()) {
	    if (getRequest->fLSAPConn == lsapConn) {
		XTRACE(kLogCancelPendingGetRequests, (int)lsapConn >> 16, (short)lsapConn);
		fPendingGetRequests->Remove(getRequest);    // get this req off the list
		// Send the reply
		getRequest->fEvent = kIrGetDataReplyEvent;
		getRequest->fResult = returnCode;
		lsapConn->EnqueueEvent(getRequest);
		// Theoretically there should only be one outstanding get request for
		// this lsapConn, so you should be able to break out of the loop here.
		// But, who ever believed in theory besides Albert Einstein?
		// Could add some debug only logic and an assert to test this...
		//
		// jdg: I don't trust remove in the middle of an iterate, so let's
		// recurse and return instead of finishing the iteration.  Note this
		// only recurses when we've found and processed an event for this 
		// lsapconn, so we're not going to recurse forever.
		iter->release();            // we're done with this iteration engine
		CancelPendingGetRequests(lsapConn, returnCode);
		return;
	    }
	}
	iter->release();
    }
    XTRACE(kLogCancelPendingGetRequestsExit, (int)lsapConn >> 16, (short)lsapConn);

} // TIrLAPConn::CancelPendingGetRequests


//--------------------------------------------------------------------------------
//      Demultiplexor
//--------------------------------------------------------------------------------
void TIrLAPConn::Demultiplexor(CBufferSegment* inputBuffer)
{
    TLMPDUHeader header;
    ULong headerLength;
    Boolean     validFormat;
    Boolean     matchFound = false;
	
    XTRACE(kLogDemux, (int)inputBuffer >> 16, (short)inputBuffer);
    
    validFormat = ExtractHeader(inputBuffer, header, headerLength);

    if (!validFormat || ((header.fOpCode & ~kLMPDUReplyFlag) == kLMPDUAccessModeRequest)) {
	XTRACE(kDemuxInvalidHeaderEvent, 0, 0);
	// We're responding.  Don't keep looking.
	matchFound = true;
	// Release the buffer
	GetLAP->ReleaseInputBuffer(inputBuffer);
	// Send a response for access requests (ignore the erroneous access confirms)
	if (validFormat && (header.fOpCode == kLMPDUAccessModeRequest) && (header.fMode <= kIrLMPExclusiveMode)) {
	    ReplyToInvalidFrame(header, kLMPDUAccessModeReply, kIrLMPDUControlUnsupported);
	}
    }

    // Some received data has arrived.  If a match can be found in the pending get requests
    // then that request can be completed and the buffer can be freed up for reuse.
    if (!matchFound) {
	XTRACE(kLogDemuxCheckingGets1, 0, fPendingGetRequests->Count());
	CListIterator *iter = CListIterator::cListIterator(fPendingGetRequests);
	for (TIrGetRequest* getRequest = (TIrGetRequest*)iter->FirstItem();
	     iter->More(); getRequest = (TIrGetRequest*)iter->NextItem()) {
	    XTRACE(kLogDemuxCheckingGets2, (int)getRequest >> 16, (short)getRequest);

	    if (DataDelivered(getRequest, header, headerLength, inputBuffer)) {
		fPendingGetRequests->Remove(getRequest);
		XTRACE(kDemuxGetPendingEvent, (int)getRequest >> 16, (short)getRequest);
		matchFound = true;
		break;
	    }
	}
	iter->release();
    }
	
    // No one is waiting for the data.  Is it a potential reply? - cache it until its requested.
    if (!matchFound) {
	CListIterator *iter = CListIterator::cListIterator(fLSAPConnList);
	for (TLSAPConn* lsapConn = (TLSAPConn*)iter->FirstItem();
	     iter->More(); lsapConn = (TLSAPConn*)iter->NextItem()) {
	    if (lsapConn->YourData(header, true /*justChecking*/)) {
		fUnmatchedGetReplys->InsertLast(inputBuffer);
		XTRACE(kDemuxReplyPostedEvent, (int)inputBuffer >> 16, (short)inputBuffer);
		XTRACE(kDemuxReplyPostedEvent2, (int)lsapConn >> 16, (short)lsapConn);
		matchFound = true;
		break;
	    }
	}
	iter->release();
    }

    // No one connected that this could belong to.
    if (!matchFound) {
	XTRACE(kDemuxNoReceiverEvent, 0, 0);
	// Release the buffer
	GetLAP->ReleaseInputBuffer(inputBuffer);
	// Send a disconnect response
	UByte respCode;
	if (header.fOpCode == kLMPDUDataEvent) {
	    respCode = kIrDataSentOnDiscLSAPConn;
	}
	else if (header.fOpCode == kLMPDUConnectRequest) {
	    respCode = kIrNoAvailableLMMuxClient;
	}
	else {
	    respCode = kIrUserRequestedDisconnect;
	}
	ReplyToInvalidFrame(header, kLMPDUDisconnectEvent, respCode);
    }

} // TIrLAPConn::Demultiplexor


//--------------------------------------------------------------------------------
//      ReplyToInvalidFrame
//--------------------------------------------------------------------------------
void TIrLAPConn::ReplyToInvalidFrame(TLMPDUHeader& header, UByte replyOpCode, UByte replyInfo)
{
    TIrPutRequest* putRequest;

    // jdg: note that the check for nil fLSAPConn for this case is
    // in the PutComplete routine of IrLAP
    
    putRequest = (TIrPutRequest*)fIrDA->GrabEventBlock(kIrPutDataRequestEvent, sizeof(TIrPutRequest));
    // Ignore this if no memory to get request block
    if (putRequest != nil) {
	putRequest->fLSAPConn = nil;    // Don't respond to putRequest, just free the block
	putRequest->fData = nil;
	putRequest->fOffset = 0;
	putRequest->fLength = 0;
	putRequest->fDstLSAPId = header.fSrcLSAPId | kLMPDUControlFlag;
	putRequest->fSrcLSAPId = header.fDstLSAPId & ~kLMPDUControlFlag;
	putRequest->fCtrlOpCode = replyOpCode;
	putRequest->fCtrlInfo = replyInfo;
	GetLAP->EnqueueEvent(putRequest);
    }

} // TIrLAPConn::ReplyToInvalidFrame


//--------------------------------------------------------------------------------
//      ExtractHeader
//--------------------------------------------------------------------------------
Boolean TIrLAPConn::ExtractHeader(CBufferSegment* inputBuffer, TLMPDUHeader& header, ULong& length)
{
    ULong headerLength;

    // Need to reseek to 0 as this may be called multiple times
    inputBuffer->Seek(0, kPosBeg);

    // Get the header info
    headerLength = inputBuffer->Getn(&header.fDstLSAPId, sizeof(TLMPDUHeader));
    XASSERT(headerLength >= 2);
    if (headerLength < 2) {
	// LM-PDU header requires dst/src minimum
	return false;
    }
    else if (header.fDstLSAPId & kLMPDUControlFlag) {
	header.fDstLSAPId &= ~kLMPDUControlFlag;
	if (headerLength == 2) {
	    // Control header requires opcode minimum
	    return false;
	} else if (headerLength == 3) {
	    // Set unspecified info field to 0
	    header.fInfo = 0;
	}
	switch (header.fOpCode) {
	    case kLMPDUConnectRequest:
	    case kLMPDUConnectReply:
	    case kLMPDUDisconnectEvent:
		headerLength = Min(headerLength, 4);
		break;

	    case kLMPDUAccessModeRequest:
	    case kLMPDUAccessModeReply:
		break;

	    default:
		return false;
	}
    }
    else {
	headerLength = 2;
	header.fOpCode = kLMPDUDataEvent;
	header.fInfo = 0;
    }

    // Invalid lsap ids
    if ((header.fDstLSAPId > kLastValidLSAPId) || (header.fSrcLSAPId > kLastValidLSAPId)) {
	return false;
    }

    length = headerLength;
    return true;

} // TIrLAPConn::ExtractHeader


//--------------------------------------------------------------------------------
//      DataDelivered
//--------------------------------------------------------------------------------
Boolean TIrLAPConn::DataDelivered(TIrGetRequest* getRequest, TLMPDUHeader& header, ULong headerLength, CBufferSegment* dataBuffer)
{
    if (getRequest->fLSAPConn->YourData(header, false /*justChecking*/)) {
	ULong written = 0;

	UInt32 dataLength = dataBuffer->GetBufferSize() - headerLength;
	if (getRequest->fData && (dataLength > 0)) {
	    //if (getRequest->fLength < dataLength) {       // jdg
	    //  DebugPrintf("About to die, fLength=%d, datalength %d",
	    //      getRequest->fLength, dataLength);
	    //}
	    XASSERT(getRequest->fLength >= dataLength);
	    getRequest->fData->Seek(getRequest->fOffset, kPosBeg);
	    written = getRequest->fData->Putn(dataBuffer->GetBufferPtr() + headerLength, dataLength);
	    XASSERT(written == dataLength);
	}

	// Fill in the fields for the reply
	getRequest->fEvent = kIrGetDataReplyEvent;
	getRequest->fResult = noErr;
	getRequest->fLength = written;
	getRequest->fCtrlOpCode = header.fOpCode;
	getRequest->fCtrlInfo = header.fInfo;

	// Send the reply
	check(getRequest->fLSAPConn);
	getRequest->fLSAPConn->EnqueueEvent(getRequest);

	// Release the buffer
	XTRACE(kDemuxReleaseBufferEvent, (int)dataBuffer >> 16, (short)dataBuffer);
	GetLAP->ReleaseInputBuffer(dataBuffer);

	return true;
    }

    return false;

} // TIrLAPConn::DataDelivered


//--------------------------------------------------------------------------------
//      FillInLMPDUHeader
//--------------------------------------------------------------------------------
ULong TIrLAPConn::FillInLMPDUHeader(TIrPutRequest* putRequest, UByte* buffer)
{
    ULong infoLength;
    TLMPDUHeader* lmPDUHeader = (TLMPDUHeader*)buffer;
    
    XTRACE(kLogFillInLMPDUHeader1, (int)putRequest >> 16, (short)putRequest);
    XTRACE(kLogFillInLMPDUHeader2, (int)buffer >> 16, (short)buffer);

    // Fill out the info
    lmPDUHeader->fDstLSAPId = putRequest->fDstLSAPId;
    lmPDUHeader->fSrcLSAPId = putRequest->fSrcLSAPId;

    if (putRequest->fCtrlOpCode != kLMPDUDataEvent) {
	lmPDUHeader->fDstLSAPId |= kLMPDUControlFlag;
	lmPDUHeader->fOpCode = putRequest->fCtrlOpCode;
	lmPDUHeader->fInfo = putRequest->fCtrlInfo;
	if (putRequest->fCtrlOpCode != kLMPDUAccessModeReply) {
	    infoLength = 4;
	}
	else {
	    // Access mode follows "header" for access mode reply frame
	    buffer[4] = kIrLMPMultiplexedMode;
	    infoLength = 5;
	}
    }
    else {
	infoLength = 2;
    }

    return infoLength;

} // TIrLAPConn::FillInLMPDUHeader

//--------------------------------------------------------------------------------
//      StartIdleDisconnectTimer
//--------------------------------------------------------------------------------
void TIrLAPConn::StartIdleDisconnectTimer()
{
    XTRACE(kLogStartIdleDisconnectTimer, 0, 0);
    // could just overload timer2, but this keeps sanity a little longer
    fIrDA->StartTimer(kTimer_LAPConn, 1 * kSeconds, kIdleDisconnectEvent);

} // TIrLAPConn::StartIdleDisconnectTimer


//--------------------------------------------------------------------------------
//      StopIdleDisconnectTimer
//--------------------------------------------------------------------------------
void TIrLAPConn::StopIdleDisconnectTimer()
{
    XTRACE(kLogStopIdleDisconnectTimer, 0, 0);
    if (fIrDA)                  // if init'd
	fIrDA->StopTimer(kTimer_LAPConn);
} // TIrLAPConn::StopIdleDisconnectTimer

//
// Do idle disconnect ... now, don't wait for the timer
// 
void TIrLAPConn::DoIdleDisconnect()
{
    XTRACE(kLogDoIdleDisconnect, 0, 0);
    
    StopIdleDisconnectTimer();              // first stop the real timer
    if (fState == kIrLAPConnActive)         // and if there is an active connect
	NextState(kIdleDisconnectEvent);    // do the idle disconnect logic
}

//--------------------------------------------------------------------------------
//      TimerComplete
//--------------------------------------------------------------------------------
void TIrLAPConn::TimerComplete(ULong refCon)
{
//#pragma   unused(refCon)
    XASSERT(refCon == kIrConnWatchdogExpiredEvent || refCon == kIdleDisconnectEvent);

    if (refCon == kIrConnWatchdogExpiredEvent) {    // one second timer has fired
	CListIterator *iter = CListIterator::cListIterator(fLSAPConnList);
	XTRACE(kLogConnWatchDogFired, 0, 0);
	
	// Let all of the active lsap conn's know that the timer has fired
	for (TLSAPConn* lsapConn = (TLSAPConn*)iter->FirstItem(); iter->More(); lsapConn = (TLSAPConn*)iter->NextItem()) {
	    lsapConn->OneSecTickerComplete();
	}
	iter->release();
    }
    else {
	XTRACE(kLogIdleDisconnectFired, 0, 0);
	if (refCon == kIdleDisconnectEvent)         // the idle disconnect timer has fired
	    if (fState == kIrLAPConnActive)         // just another sanity check
		NextState(refCon);
    }

} // TIrLAPConn::TimerComplete

