/*
    File:       ttp.cpp

    Contains:   Client interfaces to Tiny TP


*/

#include "ttp.h"
#include "ttppdu.h"
#include "CBufferSegment.h"
#include "IrDALog.h"

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

enum IrTinyTPTraceCodes
{
    kLogFree = 1,
    kLogInit,
    kDiscoverRequest,
    kLookupRequest,
    kListenRequest,
    kConnectRequest,
    kConnectResponse,
    kDisconnectRequest,
    kDataRequest,
    kFlowControl,
    kDataRequestDropped,
    kXmitQueueSize,
    kXmitQueueSize2,
    kXmitQueueSize3,
    kXmitIrDABufsLow,
    kTTPRxDone,
    kDiscardPendingPuts
};

EventTraceCauseDesc gTraceEvents[] = {
    {kLogFree,                      "TinyTP: free obj="},
    {kLogInit,                      "TinyTP: initializing, obj=, credit=, lsap="},
    {kDiscoverRequest,              "TinyTP: discover request"},
    {kLookupRequest,                "TinyTP: lookup request"},
    {kListenRequest,                "TinyTP: listen request"},
    {kConnectRequest,               "TinyTP: connect request"},
    {kConnectResponse,              "TinyTP: connect response"},
    {kDisconnectRequest,            "TinyTP: disconnect request"},
    {kDataRequest,                  "TinyTP: put data request"},
    {kFlowControl,                  "TinyTP: flow control request"},
    {kDataRequestDropped,           "TinyTP: ** Data request dropped ***"},
    {kXmitQueueSize,                "TinyTP: XMIT Queue Size, sendcredit=, depth="},
    {kXmitQueueSize2,               "TinyTP: XMIT Queue Size2, explode_factor, MaxSegSize="},
    {kXmitQueueSize3,               "TinyTP: XMIT Queue Size3, txQDepth=, depth="},
    {kXmitIrDABufsLow,              "TinyTP: XMIT - global buffers low, SendCredit=, CBufCnt="},
    {kTTPRxDone,                    "TinyTP: Client says Rx is done"},
    {kDiscardPendingPuts,           "TinyTP: discard pending puts"}
};

#define XTRACE(x, y, z)  IrDALogAdd( x, y, z, gTraceEvents, true ) 

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

const UInt8 kIASTinyTPLSAPSelAttrStr[]        = "IrDA:TinyTP:LsapSel";

#define super CIrLSAP
	OSDefineMetaClassAndAbstractStructors(TTinyTP, CIrLSAP);

void
TTinyTP::free()
{
    XTRACE(kLogFree, (int)this >> 16, (short)this);
    
    super::free();  
}

Boolean 
TTinyTP::TTPInitialize(             // do real initialization
	TIrGlue *irda,                  // glue
	UInt32  credit,                 // number of concurrent read buffers (hmm?)
	UInt32  desiredLSAPId,          // port to get (kAssignDynamicLSAPId if not listening)
	UInt8 * className,              // name to register with IAS
	ULong   hints)
{
    int i;

    XTRACE(kLogInit, (int)this >> 16, (short)this);
    XTRACE(kLogInit, credit, desiredLSAPId);
    
    
    TxQueue.qFlags = 0;
    TxQueue.qHead = TxQueue.qTail = nil;
    RxQueue.qFlags = 0;
    RxQueue.qHead = RxQueue.qTail = nil;
    AvailQueue.qFlags = 0;
    AvailQueue.qHead = AvailQueue.qTail = nil;


    AvailCredit     = 0;            // lets zero out some fields for sanity's sake
    RemoteCredit    = 0;
    SendCredit      = 0;
    MaxSegSize      = 0;
    TxMaxSduSize    = 0;
    RxMaxSduSize    = 0;
    RxSdu.sarbuf = nil;
    RxSdu.busy = false;

    txQDepth = 0;           // nothing on tx queue yet
    Connected = false;
    discoverPending = false;    // no discover request pending yet.
    
    initial_credit = credit;
    
    // have number of buffers allocated compiled into IrDA "for now"
    // allocate "credit" buffers and hand to Ir?
    //for (i = 0 ; i < (10*credit) ; i++) {
    for (i = 0 ; i < kMaxTTPRequests ; i++) {
	TTPq *tq;
	//tq = (TTPq *)NewPtr(sizeof(TTPq));
	//check(tq);                        // these should work ...
	tq = &ttpqpool[i];          // grab out of our own pool instead of using NewPtr
	tq->qLink = nil;
	tq->qType = 1;
	Enqueue((QElem *)tq, &(this->AvailQueue));  // Prep my free list
    }
    
    return super::Init(irda, desiredLSAPId, className, (UInt8 *) kIASTinyTPLSAPSelAttrStr, hints);      // Init LSAP
}

//
// thin layer over LMP (turn into inlines after debugged)
//
void
TTinyTP::DoDiscoverRequest (int slots)
{
    XTRACE(kDiscoverRequest, (int)this >> 16, (short)this);
    if (!discoverPending) {
	discoverPending = true;
	this->Discover(slots);          // just call LSAP
    }
}

void
TTinyTP::DoLookupRequest (
	unsigned char   *classname,
	UInt32          remoteAddr  )       // name and device to query
{
    XTRACE(kLookupRequest, (int)this >> 16, (short)this);
    
    //fPeerAddr = remoteAddr;       // FOO.  CIrLSAP sets peer addr during lookup
    
    this->LSAPLookup(classname, ( UInt8 * )kIASTinyTPLSAPSelAttrStr, remoteAddr );
}

void
TTinyTP::DoListenRequest(
    TTPBuf  *userData)          // read buffer
{
    XTRACE(kListenRequest, (int)this >> 16, (short)this);
    Listen(userData);           // just call CIrLSAP to start the listen
}

//  ************
//  ************ Connect Request
//  ************   client can release userData upon return
//  ************

/*
void
TTinyTP::DoConnectRequest (
    TTPSAP  sap,                // peer's lsap
    TIrQOS  *qos,               // requested QoS
    int     maxSduSize,
    TTPBuf  *userData)          // size of buffer is how much to send
{
    this->DoConnectRequest(
	fPeerAddr,              // supply the missing remote address (ahem)
	sap, qos, maxSduSize, userData);
}
*/
void
TTinyTP::DoConnectRequest (
    UInt32  remoteAddr,         // peer's address
    TTPSAP  sap,                // peer's lsap
    TIrQOS  *qos,               // requested QoS
    int     maxSduSize,
    TTPBuf  *userData)          // size of buffer is how much to send
{
#pragma unused (qos)
    int n;
    TTPBuf *pduBuf;                 // the buffer for it

    XTRACE(kConnectRequest, (int)this >> 16, (short)this);
    fPeerAddr = remoteAddr;             // save peer's address
    fPeerSAP = sap;                     // save peer's sap address (not used yet)
    
    this->Connected = false;                // Not connected to anything yet
    this->AvailCredit = 0;                  // (needed?)
    this->RxMaxSduSize = maxSduSize;        // Set max inbound SDU size
    //this->RxSdu.size = 0;
    //if (maxSduSize > 0)
    //  this->RxSdu.sarbuf = BufAlloc(maxSduSize);  // alloc a reassembly buffer
    //else
	this->RxSdu.sarbuf = nil;       // else make sure it's nil
    this->RxSdu.busy = false;           // ??
    
    n = this->initial_credit;       // default buffering
    this->SendCredit = 0;
    if (n > 127) {
	AvailCredit = n - 127;
	n = 127;
    }
    RemoteCredit = n;
    pduBuf = ttp_pdu_connect (              // return filled in connect TTP PDU
	    (maxSduSize == 0) ? 0 : 1,      // P flag (1 if sends maxSduSize)
	    n, maxSduSize, userData);
    require(pduBuf, NoMem);

    this->Connect(fPeerAddr, sap, pduBuf);  // ask LSAP to do a connect
    this->CheckTheQueues();                 // see if we have more work to do

NoMem:
    return;
}

//  ************
//  ************ Connect Response (Accept call)
//  ************   client can release userData upon return
//  ************

void
TTinyTP::DoConnectResponse (
    TTPSAP  sap,                // calling TTP SAP
    int     maxSduSize,         // called MaxSduSize
    TTPBuf  *userData)          // called UserData
{
#pragma unused (sap)
    int n;
    TTPBuf *pduBuf;                 // the buf for it
    
    XTRACE(kConnectResponse, (int)this >> 16, (short)this);
    XTRACE(kConnectResponse, sap, maxSduSize);
    XTRACE(kConnectResponse, maxSduSize >> 16, (short)maxSduSize);
    
    this->AvailCredit = 0;
    this->RxMaxSduSize = maxSduSize;
    //this->RxSdu.size = 0;
    //if (maxSduSize > 0)
    //  this->RxSdu.sarbuf = BufAlloc(maxSduSize);  // alloc a reassembly buffer
    //else
	this->RxSdu.sarbuf = nil;           // else make sure it's nil
    this->RxSdu.busy = false;
    
    n = this->initial_credit;       // default buffering
    if (n > 127) {
	this->AvailCredit = n - 127;
	n = 127;
    }
    this->RemoteCredit = n;         // we're extending these many buffers to peer
    pduBuf = ttp_pdu_connect(
	(maxSduSize == 0) ? 0 : 1, n, maxSduSize, userData);
    require(pduBuf, NoMem);
	
    this->Accept(pduBuf);           // tell LSAP to accept (sanity check request parms first?)
    this->Connected = true;
    this->CheckTheQueues();         // see if we have more work to do
NoMem:
    return;
}

//  ************
//  ************ Disconnect Request
//  ************   client can release userData upon return
//  ************

void
TTinyTP::DoDisconnectRequest (
	TTPBuf *userData)           // userdata doesn't make it
{
    XTRACE(kDisconnectRequest, (int)this >> 16, (short)this);
    
    if (Connected == false) {       // if already disconnected
	//TTPDisconnectIndication(0, nil);      // just tell 'em so (again?)
	Disconnect();                           // tell cirlsap to disconnect
    }
    else {
	this->AppendTail(&(this->TxQueue), TTP_Disconnect, 0, userData);
	txQDepth++;
	this->CheckTheQueues();         // see if we have more work to do
    }
}

//  ************
//  ************ Data Request
//  ************   client CAN release userData upon return (until we rewrite it again)
//  ************
extern int queueDepth(QHdr *qh);        // debugging ... return depth of a queue

void
TTinyTP::DoDataRequest (
	TTPBuf *userData)           // data to send
{
    TTPBuf *newbuf;                     // get rid of this soon ....
    //static int dropCount = 0;         // debugging.  count number of drops in a row ...
    
    XTRACE(kDataRequest, (int)this >> 16, (short)this);
    
    check (userData);                   // error if nil buffer ptr
    if (!userData) return;
    //check(this->Connected == true);       // error if we're not connected
    check(BufSize(userData));           // error, if zero size send (ahem, why?)
    if (Connected == false) return;     // error, but don't crash in this version ....
    
    /*************************************************************************
    if (SendCredit == 0) {              // drop if our credit has hit bottom
	XTRACE(kDataRequestDropped, dropCount, queueDepth(&TxQueue));
	dropCount++;
	if (dropCount > 20) {           // 100 was hit
	    DebugStr("\pTTP just dropped 20 sends in a row due to congestion");
	    dropCount = 0;      // may want to keep trying ....
	}
	this->CheckTheQueues();         // this shouldn't be needed ...
	return;                 // return (drop the request)
    }
    else
	dropCount = 0;          // hey, we're still alive!
    *************************************************************************/
    
    if (this->TxMaxSduSize == 0) {              // SAR Disabled
	if (BufSize(userData) > this->MaxSegSize)
	    return;                             // todo: return error: too big & SAR disabled
	newbuf = ttp_pdu_data(false, 0, userData);
	require(newbuf, NoMem);
	this->AppendTail(&(this->TxQueue), TTP_Segment_Last, 0, newbuf);
	txQDepth++;
    }
    else {                                  // SAR enabled
	int i, numsegs;
	if (BufSize(userData) > this->TxMaxSduSize)     // error ...
	    return;                         // todo: return error: too big, even for SAR
	    
	numsegs = ((BufSize(userData) + this->MaxSegSize -1) / this->MaxSegSize);
	if (numsegs == 1) {                 // if fits w/out SAR ... (jdg added)
	    newbuf = ttp_pdu_data(false, 0, userData);          // copy to data pdu
	    require(newbuf, NoMem);
	    this->AppendTail(&(this->TxQueue), TTP_Segment_Last, 0, newbuf);
	    txQDepth++;
	}
	else {                              // else ... gee let's do SAR and split into lots of packets
	    TTPBuf *seg;                        // Get segment just returns pointers into the orig buffer
	    for (i = 1 ; i < numsegs; i++) {    // first N-1 segments have the more bit set
		seg = GetSegment(i, userData);              // get ptrs to the initial segments
		require(seg, NoMem);
		newbuf = ttp_pdu_data(true, 0, seg);        // copy to new buffer w/the ttp header byte
		require(newbuf, NoMem);
		BufFree(seg);                               // this just frees the wrapper (I hope)
		this->AppendTail(&(this->TxQueue), TTP_Segment, 0, newbuf);
		txQDepth++;
	    }
	    seg = GetSegment(numsegs, userData);            // get the last segment
	    require(seg, NoMem);
	    newbuf = ttp_pdu_data(false, 0, seg);           // copy the data out
	    require(newbuf, NoMem);
	    BufFree(seg);                                   // free the wrapper
	    this->AppendTail(&(this->TxQueue), TTP_Segment_Last, 0, newbuf);
	    txQDepth++;
	}
    }
    this->CheckTheQueues();         // see if we have work to do
NoMem:
    return;
}

// we're a zombie, discard any queued up data
void
TTinyTP::TTPDiscardPendingPuts(void)
{
    TTPq *tq;
    IrDAErr err;
    int counter = 0;        // debugging only

    XTRACE(kDiscardPendingPuts, 0x1111, 0);
    // Could do a flushqueue, but I don't want to flush a 
    // disconnect request, just data puts.  oh well.
    //FlushQueue(&TxQueue);     // should be empty anyway

    while ((tq = (TTPq *)TxQueue.qHead) != 0) {
	if (tq->qType != TTP_Segment &&             // if not a segment put
	    tq->qType != TTP_Segment_Last) break;   // then prob a disconnect, stop
	err = Dequeue((QElem *)tq, &TxQueue);
	ncheck(err);                                // really shouldn't happen
	if (tq->buf != 0) BufFree(tq->buf);         // be careful ...
	tq->buf = nil;
	tq->qLink = nil;
	Enqueue((QElem *)tq, &AvailQueue);
	counter++;
    }
    XTRACE(kDiscardPendingPuts, 0xffff, counter);
}

void
TTinyTP::DoUdataRequest (               // not really impl'd
	TTPBuf *userData)               // data to send
{
#pragma unused (userData)
    if (this->Connected == false) return;   // error
    //this->UDataPut(userData);         // send off UData
}

void
TTinyTP::SetLocalFlow (
	TFlowOnOff onOff)       // start/stop flow
{
    XTRACE(kFlowControl, 0, 0);
    
    if (onOff == FlowOn) this->RxSdu.busy = false;
    else                 this->RxSdu.busy = true;
    
    this->CheckTheQueues();         // see if we have work to do
}

//
// Return number of packets the client can send to us w/out worrying
// about running out of send credits (or CBuffers).  Note that this
// is intended to be called from non-def task clients, so there's been
// some work done to avoid race conditions.
//
int
TTinyTP::TTPXmitQueueSize(int maxPacketSize)
{   int depth;
    int explode_factor;             // maxPacketSize could turn into this many frags
    int REVIEW_Unlimited_Buffer_Allocation;
    
    // Check against 8 buffers, one for a new write, and 7 for new get's in case
    // we get a 7-window full set of data packets all at once.
    /**************************
    if (CountFreeCBuffers() <= 8) { // first -- is IrDA running out of buffers?
	XTRACE(kXmitIrDABufsLow, SendCredit, CountFreeCBuffers());
	return 0;                   // if so, then pretend we can't send
    }
    *****************************/
	
    depth = SendCredit;                 // first start with currently available SendCredit

// avoid queueDepth so that we can call this from non-dt tasks!
//  depth -= queueDepth(&TxQueue);      // now subtract out pending writes
//                                      // now have actual packets count available to send
    depth -= txQDepth;                  // subtract out depth of tx queue

    XTRACE(kXmitQueueSize3, txQDepth, depth);
    
    check(depth >= 0);                  // sanity check ...
    if (depth <= 0) return 0;
    
    if (this->TxMaxSduSize == 0) {      // if SAR Disabled
	XTRACE(kXmitQueueSize, SendCredit, depth);
	return depth;                   //  then we're done
    }

    // if we got this far, we have some credit available, let's see what the
    // worst-case scenario looks like
    
    explode_factor = (maxPacketSize + this->MaxSegSize -1) / this->MaxSegSize;
					// else each send could fragment into lots of packets
    depth = depth / explode_factor;     // assuming max packetsize on all packets ...
    
    if (depth == 0) {           // ahem, special case of hitting the limit, let it!
				// we know depth started out >= 1 due to previous test
	depth = 1;              // let it send one more and then go busy
    }

    XTRACE(kXmitQueueSize, SendCredit, depth);
    XTRACE(kXmitQueueSize2, explode_factor, MaxSegSize);
    return depth;       // could be fancier ...
}

void
TTinyTP::TTPRxDone(void)            // move this ...
{
    AvailCredit++;              // new style credit
    XTRACE(kTTPRxDone, RemoteCredit, AvailCredit);
    CheckTheQueues();           // see if we have work to do (eg a dataless credit extension) 
}
