/*
    File:       IrQOS.c

    Contains:   Implementation of IrDA Quality Of Service class

*/


#include "IrQOS.h"
#include "CBufferSegment.h"
#include "IrDALog.h"
#include "AppleIrDA.h"

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

enum IrLogCodes
{
    kLogNew = 1,
    kLogFree,
    
    kLogReset,
    kLogReset_Baud,
    kLogReset_Data,
    kLogReset_BOF,
    kLogReset_Disconnect,
    
    kLogSetBaud1,
    kLogSetBaud2,
    kLogSetData1,
    kLogSetData2,
    
    kLogSetWindow,
    kLogSetDisconnect,
    
    kLogGetBaud,
    kLogGetBaudFailed,
    kLogGetMaxTurn,
    kLogGetMaxTurnFailed,
    
    kLogGetDataSize,
    kLogGetDataSizeFailed,
    kLogGetWindowSize,
    kLogGetWindowSizeFailed,
    
    kLogGetBofs,
    kLogGetBofsFailed,
    kLogGetMin,
    kLogGetMinFailed,
    kLogGetDisconnect,
    kLogGetDisconnectFailed,
    
    kLogAddInfo,
    kLogAddInfoFailed,
    kLogExtract,
    kLogExtractData,
    kLogNegotiateBaud1,
    kLogNegotiateBaud2,
    
    kLogNormMinTurn,
    kLogNormMaxLine,
    kLogNormWindow,
    kLogNormData,
    kLogNormRequested,
    kLogNormSmallerWindow,
    kLogNormSmallerData
    
};

static
EventTraceCauseDesc IrLogEvents[] = {
    {kLogNew,               "IrQOS: new, obj="},
    {kLogFree,              "IrQOS: free, obj="},
    
    {kLogReset,             "IrQOS: reset"},
    {kLogReset_Baud,        "IrQOS: reset max turn=, baud bitmap="},
    {kLogReset_Data,        "IrQOS: reset datasize=, window bits="},
    {kLogReset_BOF,         "IrQOS: reset bof=, min turn="},
    {kLogReset_Disconnect,  "IrQOS: reset disconnect link="},
    
    {kLogSetBaud1,          "IrQOS: set baud, bps="},
    {kLogSetBaud2,          "IrQOS: set baud, result=, fBaudRate="},
    {kLogSetData1,          "IrQOS: set data, buffersize="},
    {kLogSetData2,          "IrQOS: set data, result=, fDataSize="},
    
    {kLogSetWindow,         "IrQOS: set windowsize, ct=, bitmap="},
    {kLogSetDisconnect,     "IrQOS: set disconnect threshold, seconds=, mask="},
    
    {kLogGetBaud,           "IrQOS: get baud, bitpos=, bps="},
    {kLogGetBaudFailed,     "IrQOS: get baud failed, logic error!"},
    {kLogGetMaxTurn,        "IrQOS: get max turnaround, bitpos=, ms="},
    {kLogGetMaxTurnFailed,  "IrQOS: get max failed, logic error!"},
    
    {kLogGetDataSize,       "IrQOS: get data size, size="},
    {kLogGetDataSizeFailed, "IrQOS: get data size failed, logic error"},
    {kLogGetWindowSize,     "IrQOS: get window size, count="},
    {kLogGetWindowSizeFailed,   "IrQOS: get window size logic error!"},
    
    {kLogGetBofs,               "IrQOS: get bofs adjusted for speed, ct="},
    {kLogGetBofsFailed,         "IrQOS: get bofs failed, logic error"},
    {kLogGetMin,                "IrQOS: get min turnaround, delay="},
    {kLogGetMinFailed,          "IrQOS: get min turnaround logic error!"},
    {kLogGetDisconnect,         "IrQOS: get disconnect time, ms="},
    {kLogGetDisconnectFailed,   "IrQOS: get disconnect time failed, logic error"},
    
    {kLogAddInfo,               "IrQOS: add info, buffersize="},
    {kLogAddInfoFailed,         "IrQOS: add info failed logic error!"},
    {kLogExtract,               "IrQOS: extract info from buffer, addr="},
    {kLogExtractData,           "IrQOS: extract info, id=, len | value="},
    {kLogNegotiateBaud1,        "IrQOS: negotiate speed, input=, input="},
    {kLogNegotiateBaud2,        "IrQOS: negotiate speed, result="},
    
    {kLogNormMinTurn,           "IrQOS: min turn, speedBit | minturnBit, turn in bytes="},
    {kLogNormMaxLine,           "IrQOS: max line capacity="},
    {kLogNormWindow,            "IrQOS: had to normalize window mask, old=, new="},
    {kLogNormData,              "IrQOS: had to normalize data size mask, old=, new="},
    {kLogNormRequested,         "IrQOS: requested data line capacity="},
    {kLogNormSmallerWindow,     "IrQOS: shrinking window size to fit, new="},
    {kLogNormSmallerData,       "IrQOS: shrinking data size to fit, new="}
};

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

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


#define kMicroseconds -1

static const ULong IrBaudRateTable[] = {    k9600bps,   k19200bps,  k38400bps,  k57600bps, 
					    k115200bps, k576000bps, k1Mbps,     k4Mbs       };

static const UByte IrExtraBOFsTable[8] = {48, 24, 12, 6, 3, 2, 1, 0};

static const TTimeout IrMaxTurnTimeTable[4] = {500 * kMilliseconds,
					250 * kMilliseconds,
					100 * kMilliseconds,
					 50 * kMilliseconds};

static const TTimeout IrMinTurnTimeTable[8] = { 10 * kMilliseconds,
					  5 * kMilliseconds,
					  1 * kMilliseconds,
					500 * kMicroseconds,
					100 * kMicroseconds,
					 50 * kMicroseconds,
					 10 * kMicroseconds,
					  0};

static const TTimeout IrLinkDiscThreshold[8] = {  3 * kSeconds,
						  8 * kSeconds,
						 12 * kSeconds,
						 16 * kSeconds,
						 20 * kSeconds,
						 25 * kSeconds,
						 30 * kSeconds,
						 40 * kSeconds};
						 
// The following tables are from 6.6.11 of IrLAP 1.1
    
//                                         10ms  5ms  1ms  .5ms .1ms .05ms .01ms 0ms
const UInt16 IrMinTurnInBytesTable[8][8] = {{ 10,    5,   1,   0,   0,   0,    0,   0},     //   9600 bps
					    { 20,   10,   2,   1,   0,   0,    0,   0},     //  19200 bps
					   {  40,   20,   4,   2,   0,   0,    0,   0},     //  38400 bps
					   {  58,   29,   6,   3,   1,   0,    0,   0},     //  57600 bps
					   { 115,   58,  12,   6,   1,   1,    0,   0},     // 115200 bps
					   { 720,  360,  72,  36,   7,   4,    2,   0},     // 576000 bps
					  { 1440,  720, 144,  72,  14,   7,    4,   0},     //  1.152 Mbps
					  { 5000, 2500, 500, 250,  50,   25,   5,   0}};    //      4 Mbps
					  
// This is the max line capacity table for baud rates below 115.2k (500ms maxTurn assumed)
const ULong IrMaxLineCapacityTable1[4] = {400, 800, 1600, 2360};

// This is the max line capacity table for 115.2k (based on maxTurn of 500/250/100/50 ms) 
// Includes 20% overhead for transpearency, 
const ULong IrMaxLineCapacityTable2[4] = {4800, 2400, 960, 480};

// FIR capacity with no overhead for bit stuffing

const ULong IrMaxLineCapacityTable576[4]    = { 28800,   11520,  5760,  2880 };
const ULong IrMaxLineCapacityTable1Mbps[4]  = { 57600,   28800, 11520,  5760 };
const ULong IrMaxLineCapacityTable4Mbps[4]  = { 200000, 100000, 40000, 20000 };

//--------------------------------------------------------------------------------
#define super OSObject
    OSDefineMetaClassAndStructors(TIrQOS, OSObject);
//--------------------------------------------------------------------------------


//--------------------------------------------------------------------------------
//      TIrQOS
//--------------------------------------------------------------------------------
/*static*/
TIrQOS *
TIrQOS::tIrQOS(USBIrDAQoS *qos)
{
    TIrQOS *obj = new TIrQOS;
    
    XTRACE(kLogNew, (int)obj >> 16, (short)obj);
    
    if (obj && !obj->init(qos)) {
	obj->release();
	obj = nil;
    }
    return obj;
}

//--------------------------------------------------------------------------------
//      init
//--------------------------------------------------------------------------------
bool TIrQOS::init(USBIrDAQoS *qos)
{
    fDeviceQOS = qos;       // save the qos from usb
    
    Reset();                // reset to initial values
    
    return super::init();   // done
    
} // TIrQOS::init


//--------------------------------------------------------------------------------
//      Free
//--------------------------------------------------------------------------------
void TIrQOS::free(void)
{
    XTRACE(kLogFree, (int)this >> 16, (short)this);     // only reason we have a free
    super::free();                                      // is for the log
}


//--------------------------------------------------------------------------------
//      Reset
//--------------------------------------------------------------------------------
void TIrQOS::Reset()
{
#ifdef THROTTLE_SPEED
    int TESTING_ONLY_BAUD_THROTTLED;
    UInt16 THROTTLE = THROTTLE_SPEED;       // 0x3 = 9600 or slower, 0x7 = 19.2, 0f=38.4
#endif

    XTRACE(kLogReset, (int)this >> 16, (short)this);
    
    // todo - could init from kext prefs file instead of compiled-in

    fBaudRate           = kQOSDefaultBaudRates;
    fMaxTurnAroundTime  = kQOSDefaultMaxTurnTime;
    fDataSize           = kQOSDefaultDataSizes;
    fWindowSize         = kQOSDefaultWindowSize;
    fNumExtraBOFs       = kQOSDefaultExtraBOFs;
    fMinTurnAroundTime  = kQOSDefaultMinTurnTime;
    fLinkDiscThreshold  = kQOSDefaultDiscThresholds;
    
    if (fDeviceQOS) {
	fDataSize           = fDeviceQOS->datasize;     // bytes per frame supported
	fWindowSize         = fDeviceQOS->windowsize;   // 1 thru 7 frames per ack
	fMinTurnAroundTime  = fDeviceQOS->minturn;      // min turnaround time
	fBaudRate           = fDeviceQOS->baud1 << 8 | fDeviceQOS->baud2;       // 16 bits of baud
#ifdef THROTTLE_SPEED
	{                                           // if want to slow down the connection
	    if (fBaudRate & THROTTLE) {             // if a valid result, then throttle back for testing
		fBaudRate &= THROTTLE;
		IOLog("Baud throttled, bitmap now set to 0x%x\n", fBaudRate);
	    }
	    else
		IOLog("baud not throttled, would have been zero, 0x%x, 0x%x\n", fBaudRate, THROTTLE);
	}
#endif
	fNumExtraBOFs       = fDeviceQOS->bofs;         // number of bofs the pod wants
    }
    
    // Log all the new values
    XTRACE(kLogReset_Baud, fMaxTurnAroundTime, fBaudRate);
    XTRACE(kLogReset_Data, fDataSize, fWindowSize);
    XTRACE(kLogReset_BOF,  fNumExtraBOFs, fMinTurnAroundTime);
    XTRACE(kLogReset_Disconnect, 0, fLinkDiscThreshold);

} // TIrQOS::Reset


//--------------------------------------------------------------------------------
//      SetBaudRate
//--------------------------------------------------------------------------------
IrDAErr TIrQOS::SetBaudRate(BitRate bitsPerSec)
{
    ULong newBaudRate = 0;
    IrDAErr result;

    switch(bitsPerSec) {
	// NOTE: Each case falls thru on purpose to build the bit mask
	case k4Mbs:
	    newBaudRate |= kQOS4Mbps;       
	
	case k1Mbps:
	    newBaudRate |= kQOS1Mbps;       
	
	case k576000bps:
	    newBaudRate |= kQOS576000bps;       
	
	case k115200bps:
	    newBaudRate |= kQOS115200bps;

	case k57600bps:
	    newBaudRate |= kQOS57600bps;

	case k38400bps:
	    newBaudRate |= kQOS38400bps;

	case k19200bps:
	    newBaudRate |= kQOS19200bps;

	case k9600bps:
	    newBaudRate |= kQOS9600bps;

	    fBaudRate = (UByte)newBaudRate;
	    result = noErr;
	    break;

	default:
	    result = errBadArg;
	    break;
    }

    XTRACE(kLogSetBaud1, bitsPerSec >> 16, (short)bitsPerSec);
    XTRACE(kLogSetBaud2, result, fBaudRate);
    
    return result;

} // TIrQOS::SetBaudRate



//--------------------------------------------------------------------------------
//      SetDataSize
//--------------------------------------------------------------------------------
IrDAErr TIrQOS::SetDataSize(ULong bufferSize)
{
    ULong newDataSize = 0;
    IrDAErr result;

    switch(bufferSize) {
	// NOTE: Each case falls thru on purpose to build the bit mask
	case 2048:
	    newDataSize |= kQOS2048Bytes;

	case 1024:
	    newDataSize |= kQOS1024Bytes;

	case 512:
	    newDataSize |= kQOS512Bytes;

	case 256:
	    newDataSize |= kQOS256Bytes;

	case 128:
	    newDataSize |= kQOS128Bytes;

	case 64:
	    newDataSize |= kQOS64Bytes;

	    fDataSize = (UByte)newDataSize;
	    result = noErr;
	    break;

	default:
	    result = errBadArg;
	    break;
    }

    XTRACE(kLogSetData1, bufferSize >> 16, (short)bufferSize);
    XTRACE(kLogSetData2, result, fDataSize);
    return result;

} // TIrQOS::SetDataSize


//--------------------------------------------------------------------------------
//      SetWindowSize
//--------------------------------------------------------------------------------
IrDAErr TIrQOS::SetWindowSize(ULong numFrames)
{
    // Only acceptible values are 1 thru 7
    if (--numFrames >= 7) return errBadArg;

    fWindowSize = (UByte)((kQOSValidWindowSizes >> (6 - numFrames)) & kQOSValidWindowSizes);

    XTRACE(kLogSetWindow, numFrames, fWindowSize);
    return noErr;

} // TIrQOS::SetWindowSize



//--------------------------------------------------------------------------------
//      SetLinkDiscThresholdTime
//--------------------------------------------------------------------------------
IrDAErr TIrQOS::SetLinkDiscThresholdTime(TTimeout linkDiscThresholdTime)
{
    ULong newLinkDiscThreshold = 0;
    IrDAErr result;

    switch(linkDiscThresholdTime / kSeconds) {
	// NOTE: Each case falls thru on purpose to build the bit mask
	case 40:
	    newLinkDiscThreshold |= kQOSDiscAfter40secs;

	case 30:
	    newLinkDiscThreshold |= kQOSDiscAfter30secs;

	case 25:
	    newLinkDiscThreshold |= kQOSDiscAfter25secs;

	case 20:
	    newLinkDiscThreshold |= kQOSDiscAfter20secs;

	case 16:
	    newLinkDiscThreshold |= kQOSDiscAfter16secs;

	case 12:
	    newLinkDiscThreshold |= kQOSDiscAfter12secs;

	case 8:
	    newLinkDiscThreshold |= kQOSDiscAfter8secs;

	case 3:
	    newLinkDiscThreshold |= kQOSDiscAfter3secs;

	    fLinkDiscThreshold = (UByte)newLinkDiscThreshold;
	    result = noErr;
	    break;

	default:
	    result = errBadArg;
	    break;
    }

    XTRACE(kLogSetDisconnect, linkDiscThresholdTime / kSeconds, fLinkDiscThreshold);
    return result;

} // TIrQOS::SetLinkDiscThresholdTime


//--------------------------------------------------------------------------------
//      GetBaudRate
//--------------------------------------------------------------------------------
BitRate TIrQOS::GetBaudRate()
{
    ULong bitPos;

    // note: the -1 and +7 are due to 2400 baud not being in our table
    
    if( fBaudRate>>8 & kQOSValidBaudRatesHigh ) {
	bitPos = HighestBitOn(fBaudRate>>8 ) + 7;   // Look at next byte
    }
    else {
	bitPos = HighestBitOn(fBaudRate) - 1;       // Look at the low byte
    }
    require(bitPos < sizeof(IrBaudRateTable) / sizeof(IrBaudRateTable[0]), Fail);
    
    XTRACE(kLogGetBaud, bitPos, IrBaudRateTable[bitPos]);
    
    return IrBaudRateTable[bitPos];

Fail:
    XTRACE(kLogGetBaudFailed, 0xffff, 9600);
    return k9600bps;        // better than crashing, but not much

} // TIrQOS::GetBaudRate


//--------------------------------------------------------------------------------
//      GetMaxTurnAroundTime
//--------------------------------------------------------------------------------
TTimeout TIrQOS::GetMaxTurnAroundTime()
{
    ULong bitPos = HighestBitOn(fMaxTurnAroundTime);
    require(bitPos < sizeof(IrMaxTurnTimeTable) / sizeof(IrMaxTurnTimeTable[0]), Fail);
    
    XTRACE(kLogGetMaxTurn, bitPos, IrMaxTurnTimeTable[bitPos]);
    
    return IrMaxTurnTimeTable[bitPos];

Fail:
    XTRACE(kLogGetMaxTurnFailed, 0xffff, 500);
    
    return (500 * kMilliseconds);

} // TIrQOS::GetMaxTurnAroundTime


//--------------------------------------------------------------------------------
//      GetDataSize
//--------------------------------------------------------------------------------
ULong TIrQOS::GetDataSize()
{
    ULong size;
    
    require(fDataSize, Fail);
    
    size = 64 * (1 << HighestBitOn(fDataSize));
    
    XTRACE(kLogGetDataSize, 0, size);
    
    return size;

Fail:
    XTRACE(kLogGetDataSizeFailed, 0xffff, 64);
    return kQOS64Bytes;

} // TIrQOS::GetDataSize


//--------------------------------------------------------------------------------
//      GetWindowSize
//--------------------------------------------------------------------------------
ULong TIrQOS::GetWindowSize()
{
    ULong size;
    
    require(fWindowSize, Fail);
    
    size = HighestBitOn(fWindowSize) + 1;
    
    XTRACE(kLogGetWindowSize, 0, size);
    
    return size;
    
Fail:
    XTRACE(kLogGetWindowSizeFailed, 0xffff, 1);
    return 1;

} // TIrQOS::GetWindowSize


//--------------------------------------------------------------------------------
//      GetExtraBOFs
//--------------------------------------------------------------------------------
ULong TIrQOS::GetExtraBOFs()
{
    ULong result;
    ULong bofs;
    
    require(fNumExtraBOFs, Fail);
    
    bofs = IrExtraBOFsTable[HighestBitOn(fNumExtraBOFs)];       // map to actual bof count
    
    result = (bofs * GetBaudRate()) / 115200;                   // normalize per 115k bps base
    
    XTRACE(kLogGetBofs, 0, result);
    
    // Return extra BOFs adjusted for the actual baud rate
    return result;

Fail:
    XTRACE(kLogGetBofsFailed, 0xffff, 48);
    return 48;

} // TIrQOS::GetExtraBOFs


//--------------------------------------------------------------------------------
//      GetMinTurnAroundTime
//--------------------------------------------------------------------------------
TTimeout TIrQOS::GetMinTurnAroundTime()
{
    ULong bitpos;
    TTimeout result;
    
    require(fMinTurnAroundTime, Fail);
    
    bitpos = HighestBitOn(fMinTurnAroundTime);
    result = IrMinTurnTimeTable[bitpos];
    XTRACE(kLogGetMin, result >> 16, (short)result);
    return result;
    
Fail:
    XTRACE(kLogGetMinFailed, 0, IrMinTurnTimeTable[0]);
    return IrMinTurnTimeTable[0];

} // TIrQOS::GetMinTurnAroundTime


//--------------------------------------------------------------------------------
//      GetLinkDiscThresholdTime
//--------------------------------------------------------------------------------
TTimeout TIrQOS::GetLinkDiscThresholdTime()
{
    TTimeout result;
    ULong bitpos;
    
    require(fLinkDiscThreshold, Fail);
    
    bitpos = HighestBitOn(fLinkDiscThreshold);
    result = IrLinkDiscThreshold[bitpos];
    XTRACE(kLogGetDisconnect, result >> 16, (short)result);
    
    return result;
    
Fail:
    XTRACE(kLogGetDisconnectFailed, 0xffff, IrLinkDiscThreshold[0]);
    return IrLinkDiscThreshold[0];

} // TIrQOS::GetLinkDiscThresholdTime


//--------------------------------------------------------------------------------
//      AddInfoToBuffer
//--------------------------------------------------------------------------------
ULong TIrQOS::AddInfoToBuffer(UByte* buffer, ULong maxBytes)
{
    // Make sure my parms are consistent before sending them out
    IrDAErr result;
    
    XTRACE(kLogAddInfo, 0, maxBytes);
    
    result = NormalizeInfo();
    require(result == noErr, Bogus);
    require(maxBytes >= (kQOSNumberOfIdentifiers * 3 + 1), Bogus);      // Add the extra byte for the baud rate
    
    // Add baud rate
    *buffer++ = kQOSBaudRateId;
    *buffer++ = 2;
    *buffer++ = ( UInt8 )fBaudRate;                 // Low byte goes out first (2400 - 1Mbps)
    *buffer++ = ( UInt8 )( fBaudRate >> 8 );        // High byte second (4Mbps+)

    // Add max turn around time
    *buffer++ = kQOSMaxTurnAroundTimeId;
    *buffer++ = 1;
    *buffer++ = fMaxTurnAroundTime;

    // Add data size
    *buffer++ = kQOSDataSizeId;
    *buffer++ = 1;
    *buffer++ = fDataSize;

    // Add window size
    *buffer++ = kQOSWindowSizeId;
    *buffer++ = 1;
    *buffer++ = fWindowSize;

    // Add num extra BOFs
    *buffer++ = kQOSNumberOfExtraBOFsId;
    *buffer++ = 1;
    *buffer++ = fNumExtraBOFs;

    // Add min turn around time
    *buffer++ = kQOSMinTurnAroundTimeId;
    *buffer++ = 1;
    *buffer++ = fMinTurnAroundTime;

    // Add link disconnect/threshold
    *buffer++ = kQOSLinkDiscThresholdId;
    *buffer++ = 1;
    *buffer++ = fLinkDiscThreshold;
    
    return kQOSNumberOfIdentifiers * 3 + 1;

Bogus:
    XTRACE(kLogAddInfoFailed, 0xffff, 0);
    
    return 0;

} // TIrQOS::AddInfoToBuffer


//--------------------------------------------------------------------------------
//      ExtractInfoFromBuffer
//--------------------------------------------------------------------------------
IrDAErr TIrQOS::ExtractInfoFromBuffer(CBufferSegment* buffer)
{
    ULong value;
    UByte idLenVal[3];      // id byte, length byte, value byte
    
    XTRACE(kLogExtract, (int)buffer >> 16, (short)buffer);

    // Preset fields to default values (in case some values are not provided)
    fBaudRate           = kQOS9600bps;
    fMaxTurnAroundTime  = kQOSMaxTurnTime500ms;
    fDataSize           = kQOS64Bytes;
    fWindowSize         = kQOS1Frame;
    fNumExtraBOFs       = kQOS48ExtraBOFs;
    fMinTurnAroundTime  = kQOSMinTurnTime10ms;
    fLinkDiscThreshold  = kQOSDefaultDiscThresholds;

    // Basically, repeat: read id byte, read len byte, read len value bytes, update value.

    while (true) {
	// Need another 3 bytes minimum to continue
	if (buffer->Getn(&idLenVal[0], sizeof(idLenVal)) != sizeof(idLenVal)) break;
	
	XTRACE(kLogExtractData, idLenVal[0], (idLenVal[1] << 8) | idLenVal[2]);

	// Put value into a local (compiler wasn't smart enough, so I'm helping it)
	value = idLenVal[2];

	// Assign the value to the appropriate field.  Mask off unknown bits first.
	// If value after masking is 0, then don't change the field - leave as default.
	switch(idLenVal[0]) {
	    case kQOSBaudRateId:
		// Baud rate is the only field that can have more than one byte in the value
		// The low order byte is sent first (< 4Mbps), followed by the high order byte (4Mbps or greater) 
		if (value & kQOSValidBaudRatesLow) {
		    fBaudRate = (UByte)(value & kQOSValidBaudRatesLow);
		}
		if( idLenVal[1] == 2 ) {                // Check if 4 Mbps or greater field was sent
		    value = buffer->Peek();             // Get it out of the buffer, but don't advance
		    value &= kQOSValidBaudRatesHigh;    // that's done later. Mask off unused bits
		    fBaudRate += value << 8;            // Put it into the high byte of the baud rate member
		}
		break;

	    case kQOSMaxTurnAroundTimeId:
		if (value & kQOSValidMaxTurnTimes) {
		    fMaxTurnAroundTime = (UByte)(value & kQOSValidMaxTurnTimes);
		}
		break;

	    case kQOSDataSizeId:
		if (value & kQOSValidDataSizes) {
		    fDataSize = (UByte)(value & kQOSValidDataSizes);
		}
		break;

	    case kQOSWindowSizeId:
		if (value & kQOSValidWindowSizes) {
		    fWindowSize = (UByte)(value & kQOSValidWindowSizes);
		}
		break;

	    case kQOSNumberOfExtraBOFsId:
		if (value & kQOSValidExtraBOFs) {
		    fNumExtraBOFs = (UByte)(value & kQOSValidExtraBOFs);
		}
		break;

	    case kQOSMinTurnAroundTimeId:
		if (value & kQOSValidMinTurnTimes) {
		    fMinTurnAroundTime = (UByte)(value & kQOSValidMinTurnTimes);
		}
		break;

	    case kQOSLinkDiscThresholdId:
		if (value & kQOSValidDiscThresholds) {
		    fLinkDiscThreshold = (UByte)(value & kQOSValidDiscThresholds);
		}
		break;

	    default:
		// Ignore other negotiation parameters I don't understand
		break;
	}

	// If length is something other than 1 then skip additional value fields
	if (idLenVal[1] > 1) {
	    buffer->Seek(idLenVal[1] - 1, kPosCur);
	}
    }

    return noErr;

} // TIrQOS::ExtractInfoFromBuffer


//--------------------------------------------------------------------------------
//      NegotiateWith
//--------------------------------------------------------------------------------
IrDAErr TIrQOS::NegotiateWith(TIrQOS* peerDeviceQOS)
{
    require(peerDeviceQOS, Fail);
    
    XTRACE(kLogNegotiateBaud1, peerDeviceQOS->fBaudRate, fBaudRate);
    XTRACE(kLogNegotiateBaud2, 0, fBaudRate & peerDeviceQOS->fBaudRate);
    
    // Baud rate is intersection of my values and peer devices values
    fBaudRate &= peerDeviceQOS->fBaudRate;

    // Link disconnect/threshold is intersection of my and peer devices values
    fLinkDiscThreshold &= peerDeviceQOS->fLinkDiscThreshold;

    // Can't connect if no agreement on baud rate and/or link disconnect threshold
    if ((fBaudRate == 0) || (fLinkDiscThreshold == 0)) {
	return errIncompatibleRemote;
    }

    // Make sure that my parms are still consistent in case baud rate changed
    return NormalizeInfo();

Fail:
    return errIncompatibleRemote;

} // TIrQOS::NegotiateWith


//--------------------------------------------------------------------------------
//      NormalizeInfo
//--------------------------------------------------------------------------------
IrDAErr TIrQOS::NormalizeInfo()
{
    IrDAErr result = noErr;
    ULong minTurnTimeInBytes;
    ULong maxLineCapacity;
    ULong extraBOFs;
    ULong maxWindowsBit;
    Boolean firSpeed = GetBaudRate() > k115200bps;
    
    require(fBaudRate, Bogus);
    require(fMinTurnAroundTime, Bogus);
    require(fMaxTurnAroundTime, Bogus);
    require(fWindowSize, Bogus);
    require(fDataSize, Bogus);

    // Lookup minimum turnaround time in bytes from table (based on baud rate)
    {
	ULong speed;    // bit position speed
	ULong minturn;  // bit position min turnaround
	speed = HighestBitOn(fBaudRate >> 1);           // shifting out 2400 bps and shifting in 4mbit
	minturn = HighestBitOn(fMinTurnAroundTime);
	minTurnTimeInBytes = IrMinTurnInBytesTable[speed][minturn]; // convert to byte count
	
	XTRACE(kLogNormMinTurn, (speed << 8) | minturn, minTurnTimeInBytes);
    }

    // Lookup maximum line capacity
    {
	ULong   maxTurn = HighestBitOn(fMaxTurnAroundTime);
	require(maxTurn < 4, Bogus);
	
	switch( GetBaudRate() )
	{
	    case k4Mbs:
		maxLineCapacity = IrMaxLineCapacityTable4Mbps[maxTurn];
		break;
		
	    case k1Mbps:
		maxLineCapacity = IrMaxLineCapacityTable1Mbps[maxTurn];
		break;
		
	    case k576000bps:
		maxLineCapacity = IrMaxLineCapacityTable576[maxTurn];
		break;
		
	    case k115200bps:
		maxLineCapacity = IrMaxLineCapacityTable2[maxTurn];
		break;
		
	    default: 
		maxLineCapacity = IrMaxLineCapacityTable1[HighestBitOn(fBaudRate>>1)];
	}
	XTRACE(kLogNormMaxLine, maxLineCapacity >> 16, (short)maxLineCapacity);
    }   

    extraBOFs = GetExtraBOFs();                 // Don't need these for FIR 

    // make sure windowsize and datasize are proper bitmaps
    {
	UInt8 old_windowsize, old_datasize;         // debugging
	UInt8 bitpos;
	
	old_windowsize = fWindowSize;
	old_datasize   = fDataSize;
	
	bitpos = HighestBitOn(fWindowSize);         // All window sizes below maxWindow are valid
	if (bitpos > 0)                             // if max windowsize > 1
	    fWindowSize |= (1 << bitpos) -1;        // then turn on all the lower bits too
	
	bitpos = HighestBitOn(fDataSize);           // get max packet size
	if (bitpos > 0)                             // if more than min size (64 bytes)
	    fDataSize |= (1 << bitpos) -1;          // then turn on all the lower bits too
	    
	if (old_windowsize != fWindowSize)
	    XTRACE(kLogNormWindow, old_windowsize, fWindowSize);
	    
	if (old_datasize != fDataSize)
	    XTRACE(kLogNormData, old_datasize, fDataSize);
    }

    // Pare things down until they fit
    while (true) {
	ULong requestedLineCapacity;
	if( firSpeed )
	    requestedLineCapacity = ( ( GetDataSize() + 4 ) * GetWindowSize() ) + minTurnTimeInBytes;
	else
	    requestedLineCapacity = ((GetDataSize() + 6 + extraBOFs) * GetWindowSize()) + minTurnTimeInBytes;
	
	XTRACE(kLogNormRequested, requestedLineCapacity >> 16, (short)requestedLineCapacity); 
	
	if (requestedLineCapacity < maxLineCapacity) break;

	// First decrement window (if more than one choice specified)
	maxWindowsBit = HighestBitOn(fWindowSize);
	if (maxWindowsBit != 0) {               // if more than a single window left
						// Turn off high bit (reduce window count by 1)
	    fWindowSize &= (UByte)(~(1 << maxWindowsBit));
	    XTRACE(kLogNormSmallerWindow, 0, fWindowSize);
	    require(fWindowSize, Bogus);            // sanity check, should never hit this
	}

	// If at only one window left, try decrementing buffer size instead
	else {
	    // Turn off high bit
	    fDataSize &= (UByte)(~(1 << HighestBitOn(fDataSize)));
	    XTRACE(kLogNormSmallerData, 0, fDataSize);
	    if (fDataSize == 0) {
		result = errIncompatibleRemote;
		break;
	    }
	}
    }

    return result;

Bogus:
    return errIncompatibleRemote;

} // TIrQOS::NormalizeInfo


//--------------------------------------------------------------------------------
//      HighestBitOn
//--------------------------------------------------------------------------------
ULong TIrQOS::HighestBitOn(UByte aByte)
{
    ULong bitPosition;
    UByte bitMask = 0x80;
    
    require(aByte != 0, Fail);

    for (bitPosition = 7, bitMask = 0x80; bitMask != 0; bitPosition--, bitMask >>= 1) {
	if ((aByte & bitMask) != 0) {
	    break;
	}
    }

    return bitPosition;

Fail:
    return (ULong)-1;

} // TIrQOS::HighestBitOn

