
/*	SerialCommDevice.m

	Synopsis:
	This class defines an easy to use, robust, fault-tollerant, full-duplex serial 
	communications protocol.  The implementation uses a packet-based protocol that 
	transmits only printable ascii characters. 
	
	Description:
	The actual transmission protocol only allows for the transfer of printable ascii 
	characters.  When necessary, any non-printable characters are transparently en/decoded 
	to effectively support the transfer of all 8-bits worth of information contained w/in
	each byte.	This encoding scheme isn't particularly efficient in that any non-printable 
	ascii characters end up requiring two characters for their internal representation.  
	For instance, a packet containing all '\0' characters,  would end up taking twice as 
	long to transfer as a packet containing the same number of 'a' characters 8^(.  Future 
	enhancements should use a more efficient encoding scheme or a mechanism for compressing 
	the data before transmission.  Additionally, the protocol and encoding schemes reserve the
	following five printable characters:  '<', '>', '$', '%', and '&'.  Therefore, these 
	characters end up requiring the same two character encoding scheme as the non-printable 
	ascii characters.

	Each packet has the following format:
	
 	Offset 			Size		Description
	------			----		------------------------------------------------------------------
	0				1			SOP_CHAR_CODE '<' Defines start of packet.
	1				1			Packet Type Field.  
	2				1			Packet Sequence character - to distinguish packets @ receiver.
					<0 or more data bytes here>
	n-CSUM_CHARS	CSUM_CHARS	hex-encoded checksum of bytes 1 thru n-1 inclusive.
	n				1			EOP_CHAR_CODE '>' Defines end of packet.

	where the following Packet Types are currently defined:
		D - DATA Packet - 
		A - ACK Packet - Acknowledges reciept of specified pkt. 
		N - NACK Packet - A bad packet was received - request retransmit.
		T - TEST Packet - Receiver just loops data back - sort of a loop-back test.
		O - OOL Packet - Out-Of-Line data packet - gets sent to remote's delegate via -oolMessage:len:
		
	Additionally, three other chars (MAP0_CHAR_CODE thru MAP2_CHAR_CODE are '$', '%', '&' respectively) 
	have special meaning and are used in an encoding scheme that allows all 256 possible character 
	codes to map into the printable set of ASCII characters.

 */
#import "SerialCommDevice.h"
#import <sys/types.h>
#import <sys/ttydev.h>
#import <sys/ioctl.h>
#import <fcntl.h>
#import <stdio.h>
#import <limits.h>
#import <libc.h>
#import <stdarg.h>
#import <ctype.h>
#import <mach/cthreads.h>
#import <ansi/errno.h>
/*	reserved characters: */
#define 		SOP_CHAR_CODE		0x3c	/*	'<' start of packet character. */
#define 		EOP_CHAR_CODE		0x3e	/*	'>' end of packet character. */
#define 		MAP0_CHAR_CODE		(0x24)	/* '$' maps 0-0x1f,SOP, EOP and MAP0-2 CHAR_CODES into 20 thru 5f. */ 
#define 		MAP1_CHAR_CODE		(0x25)	/* '%' maps 0x80-0xbf into 20 thru 5f. */ 
#define 		MAP2_CHAR_CODE		(0x26)	/* '&' maps 0xc0-0xff into 20 thru 5f. */ 
#define 		MAP_CHAR(c)			(((u_char)(c)>=MAP0_CHAR_CODE)&&((u_char)(c)<=MAP2_CHAR_CODE))
#define			PKT_CHAR(c)			(((c)==SOP_CHAR_CODE)||((c)==EOP_CHAR_CODE))
#define			MAP0_RANGE(c)		((((u_char)(c)>=0)&&(((u_char)c)<=0x1f))||PKT_CHAR(c)||MAP_CHAR(c)||((c)==0x7f))
#define			MAP1_RANGE(c)		(((u_char)(c)>=0x80)&&(((u_char)c)<=0xbf))
#define			MAP2_RANGE(c)		(((u_char)(c)>=0xc0)&&(((u_char)c)<=0xff))
#define			NORMAL_CHAR(c)		(((u_char)(c)>=0x20)&&(((u_char)c)<=0x7e)&&!MAP_CHAR(c)&&!PKT_CHAR(c))
#define			ILLEGAL_CHAR(c)		(((u_char)(c)<0x20)||(((u_char)c)>0x7e))
#define 		MAP_MASK 			0x3f
#define 		MAP_OFFSET			0x3f


/*	checksum may be computed with from 1 to 8 bytes - change all four of the following defines at the same time: */
#define 		CSUM_CHARS			4			/* must fit into int (8 max) */
#define 		CSUM_MSB			0x8000	
#define			CSUM_MASK			0xffff
#define			CSUM_FMT			"%04x"

/* defined packet types: */	
#define 		DATA_PKT			'D'
#define 		ACK_PKT				'A'
#define 		NACK_PKT			'N'
#define			TEST_PKT			'T'
#define			OOL_PKT				'O'
#define			VALID_PKT_TYPE(c) ((c == DATA_PKT) || (c == ACK_PKT) || (c == NACK_PKT) || (c == TEST_PKT) || (c == OOL_PKT))

#define 		NEXT_ID_CHAR(pktid)	((pktid++%10)+'0')
#define			NACK_ID				'?'
#define 		RCV_IN_BASKET ((self->dataPktIndex)?1:0)
#define 		RCV_OUT_BASKET (!RCV_IN_BASKET)

/* defaults: */
#define 		XMIT_RETRY_COUNT	10
#define			XMIT_TIMEOUT_MSECS	10000
#define 		RECV_TIMEOUT_MSECS	0

static int 		debugFlag = 		1;
static void 	theListener(SerialCommDevice *self);

/*	stringForCharArray()
	Returns a string with printable representation of array of any ascii chars.  For debug only.
 */
static char *stringForCharArray(char *str, int len)
{
	static char			result[128];
	char				*cp=result;
	int					i;

	for (i=0;i<len;i++) {
		if (isprint(str[i]))
			sprintf(cp,"%c",str[i]);
		else
			sprintf(cp,"<0x%02x>",(unsigned)str[i]);
		cp = index(cp,'\0');
	}
	return result;
}

/*	microTimeStampA() 
	Accumulative time stamp. Returns a positive integer value of microseconds 
	since the since the timer was initialized or a negative number if greater 
	than 2048 seconds have elapsed. oldTime is used to keep track of the start
	time.  It is therefore important that it be consistant between calls. To 
	initialize the timer, call with oldTime.tv_sec == oldTime.tv_usec == 0.
 */
#define MAX_MTSA 2048000000
int microTimeStampA(struct timeval * oldTime)
{
	struct timeval  nt;
	long            elapsedTime = (-1);

	if (gettimeofday(&nt, (struct timezone *) 0) < 0)
		perror("gettimeofday() failed.");

	if (oldTime->tv_sec == 0 && oldTime->tv_usec == 0) {
		elapsedTime = 0;  /* just initing oldTime struct. */
		*oldTime = nt;
	}
	else {
		elapsedTime = (nt.tv_sec - oldTime->tv_sec) * 1000000;
		if (nt.tv_usec < oldTime->tv_usec)
			elapsedTime += nt.tv_usec + 1000000 - oldTime->tv_usec;
		else
			elapsedTime += nt.tv_usec - oldTime->tv_usec;
	}
	if (elapsedTime > MAX_MTSA)
		elapsedTime = (-1);

	return ((int)elapsedTime);
}

static int baudRateCode(baudRateType br)
{
	int			brc;
	switch (br) {
		case br38400:
			brc=EXTB;break;
		case br19200:
			brc=EXTA;break;
		case br9600:
			brc=B9600;break;
		case br4800:
			brc=4800;break;
		case br2400:
			brc=B2400;break;
		case br1200:
			brc=B1200;break;
		case br600:
			brc=B600;break;
		case br300:
			brc=B300;break;
		case br200:
			brc=B9600;break;
		case br150:
			brc=B150;break;
		case br134:
			brc=B134;break;
		case br110:
			brc=B110;break;
		case br75:
			brc=B75;break;
		case br50:
			brc=B50;break;
		default:
			brc=B9600;break;
	}
	return brc;
}

char *portString(sccPortType thePort)
{
	char 		*p;
	
	if (thePort == sccPortB)
		p = "/dev/ttyb";
	else
		p = "/dev/ttya";
	return p;
}


/*	sumCheck()
	add each character to right-rotated previous parital result.
 */
static int sumCheck(u_char *str, int len)
{
	register int 		i,
						checkSum=13;

	for (i=0;i<len;i++) {
		if (str[i] & 0x1)
			checkSum += (((str[i] & 0xff) >> 1) | CSUM_MSB) ;
		else
			checkSum += ((str[i] & 0xff) >> 1);
	}

	return (checkSum & CSUM_MASK);
}


/* 	stringEncode()
	Process the binary input string and map all into printable 
	ascii in the output string. Returns the length of the output string.
 */	
static int stringEncode(char *input, int len, char *output)
{
	register int			i;
	register char			*rPtr, *wPtr;
	
	rPtr = input;
	wPtr = output;
	for (i=0;(i<len)&&((wPtr-input)<(SCD_MAX_PACKETSIZE-(CSUM_CHARS+4)));i++) {
		if (NORMAL_CHAR(*rPtr)) {
			*wPtr++ = *rPtr++;
		} else if (MAP0_RANGE(*rPtr)) {
			*wPtr++ = MAP0_CHAR_CODE;
			*wPtr++ = (*rPtr++ & MAP_MASK) + MAP_OFFSET;
		} else if (MAP1_RANGE(*rPtr)) {
			*wPtr++ = MAP1_CHAR_CODE;
			*wPtr++ = (*rPtr++ & MAP_MASK) + MAP_OFFSET;
		} else if (MAP2_RANGE(*rPtr)) {
			*wPtr++ = MAP2_CHAR_CODE;
			*wPtr++ = (*rPtr++ & MAP_MASK) + MAP_OFFSET;
		}
	}
	return (wPtr-output);
}


/* 	stringDecode()
	Process the printable ascii string, expanding character MAP sequences
	in the output string. Returns the length of the output string.
 */	
static int stringDecode(char *input, int len, char *output)
{
	register int			i;
	register char			*rPtr, *wPtr;
	
	rPtr = input;
	wPtr = output;
	for (i=0;(i<len)&&((wPtr-input)<(SCD_MAX_PACKETSIZE-8));i++,wPtr++,rPtr++) {
		if (NORMAL_CHAR(*rPtr)) {
			*wPtr = *rPtr;
		} else if (*rPtr == MAP0_CHAR_CODE) {
			rPtr++;i++;
			*wPtr = *rPtr - MAP_OFFSET;
			if (*wPtr == 0x3f)
				*wPtr = 0x7f;
		} else if (*rPtr == MAP1_CHAR_CODE) {
			rPtr++;i++;
			*wPtr = (0x80 + *rPtr - MAP_OFFSET);
		} else if (*rPtr == MAP2_CHAR_CODE) {
			rPtr++;i++;
			*wPtr = (0xc0 + *rPtr - MAP_OFFSET);
		}
	}
	return (wPtr-output);
}

@implementation SerialCommDevice

- setRecvTimeoutMsecs:(int)msecs {recvTimeoutMsecs = msecs;return self;}
- (int)recvTimeoutMsecs {return recvTimeoutMsecs;}
- setXmitTimeoutMsecs:(int)msecs {xmitAckTimeoutMsecs = msecs;return self;}
- (int)xmitTimeoutMsecs {return xmitAckTimeoutMsecs;}
- setmaxXmitRetries:(int)retries {maxXmitRetries = retries;return self;}
- (int)maxXmitRetries {return maxXmitRetries;}





/*	write:len:
 *
 *	writes len chars to serial port.  
 *	Returns zero on success or SCD_IO_ERR on failure.
 */			
-(int)write:(char *)buf len:(int)l
{
	int 			retVal=0;
	int				written=0;

	mutex_lock((mutex_t)writeLock);
	while (written < l) {
		if ((written += write(fd, buf, l-written)) < 0) {
			retVal = SCD_IO_ERR;
			break;
		} 
	}
	mutex_unlock((mutex_t)writeLock);
	cthread_yield();
	return retVal;
}


/*	- hangOnReadPort:
 *
 *	Wait for data available or timeout, whichever occurs first. 
 *	Returns 0 for success, SCD_IO_ERR for error, and SCD_TIMEOUT_ERR 
 *	for timeout.
 */
- (int)hangOnReadPort:(int)timeoutMsecs
{
	struct timeval		to;
	fd_set				portList;
	int 				retVal;

	FD_ZERO(&portList);
	FD_SET(fd, &portList);
	if (timeoutMsecs > 0) {
		to.tv_sec = timeoutMsecs / 1000;
		to.tv_usec = (timeoutMsecs % 1000) * 1000;
		retVal = select(fd+1, &portList, 0, 0, &to);
	} else 
		retVal = select(fd+1, &portList, 0, 0, 0);
	if (retVal < 0) {
		retVal = SCD_IO_ERR;
		fprintf(stderr,"\nselect() failed:%s\n", strerror(errno));
	}
	else if (retVal > 0)
		retVal = 0;
	else {
		retVal = SCD_TIMEOUT_ERR;
		if (debugFlag)
			fprintf(stderr,"[RTO]");
	}
	return retVal;
}

/*	buildPacket:withID:data:len:buffer:
 *
 *	Construct the requested packet in the specified buf.  
 *	Returns size of the completed packet. 
 */
-(int)buildPacket:(char)pType withID:(char)pktId data:(char *)data len:(int)len buffer:(char *)buf
{
	int			i, retVal;
	
	buf[0] = SOP_CHAR_CODE;
	buf[1] = pType;
	buf[2] = pktId;
	retVal = stringEncode(data, len, &buf[3]) + 2;
	i = sumCheck((u_char *)&buf[1], retVal++);
	sprintf(&buf[retVal], CSUM_FMT, (unsigned)i);
	buf[retVal+CSUM_CHARS] = EOP_CHAR_CODE;
	return (retVal+1+CSUM_CHARS);
}




/*	sendPkt:data:len:pktId:
 *
 *	Build and send specified pkt.  If not data or test pkt, then send pkt and
 *	return zero if successfull. If data or test pkt, wait for reply and return
 * 	positive microseconds required for transfer if successfull.  If retries
 *	become exhausted, then return SCD_XFER_ERR, if failure return SCD_IO_ERR.
 */
-(int)sendPkt:(char)pType data:(char *)data len:(int)len pktId:(char)asciiPktId
{
	int				retries=maxXmitRetries,elapsedTime=0, pktLen, retVal=0;
	char			buf[SCD_MAX_PACKETSIZE];
	struct timeval  t;

	if ((len+CSUM_CHARS+4) > SCD_MAX_PACKETSIZE) {
		fprintf(stderr, "\nsendPkt: data to big.  Maximum allowable len is:%d\n", SCD_MAX_PACKETSIZE-(CSUM_CHARS+4));
		retVal = SCD_LENGTH_ERR;
	} else if ((pktLen=[self buildPacket:pType withID:asciiPktId data:data len:len buffer:buf])>SCD_MAX_PACKETSIZE){
		fprintf(stderr, "\nsendPkt: data to big.\n");	
		retVal = SCD_LENGTH_ERR;
	} else if ((pType == DATA_PKT) || (pType == TEST_PKT)) { 		/* wait for response w/these. */
		mutex_lock((mutex_t)dataPktLock);
		asciiAckDPktID = -1;
		if (debugFlag>1)
			fprintf(stderr,"\nSending Pkt:%s\n", stringForCharArray(buf,pktLen));
		t = (struct timeval){0,0};
		for (retVal=0;retVal==0;) {
			if (retVal = [self write:buf len:pktLen])
				break;
			elapsedTime = microTimeStampA(&t); /* set start time. */
			for (;!retVal;) {
				if (asciiAckDPktID == asciiPktId) {
					if (elapsedTime < 0)
						retVal = MAX_MTSA;
					else 
						retVal = elapsedTime;
					break;
				}
				else if (nackDPktFlag) {
					retVal=SCD_XFER_ERR;
					if (debugFlag)
//						fprintf(stderr,"[NACK sending:%s]",stringForCharArray(buf,pktLen));
						fprintf(stderr,"[NACK]");
					nackDPktFlag = 0;
					break;
				} else {
					if (xmitAckTimeoutMsecs && (((elapsedTime/1000)>xmitAckTimeoutMsecs) || (elapsedTime < 0))) {
						t = (struct timeval){0,0};
						if (debugFlag)
//							fprintf(stderr,"[XTO sending:%s]",stringForCharArray(buf,pktLen));
							fprintf(stderr,"[XTO]");
						retVal=SCD_XFER_ERR;
						break;
					}
				}
				cthread_yield();
				elapsedTime = microTimeStampA(&t); 	/* get elapsed time. */
			}
			if (!retVal)
				break;
			else if ((retVal==SCD_XFER_ERR)&&(retries-->0)) {
				retVal = 0;
			}
		}
		mutex_unlock((mutex_t)dataPktLock);
	} else {						/* send and forget all other pkt types. */
		if (debugFlag>10)
			fprintf(stderr,"\nSending Pkt:%s\n", stringForCharArray(buf,pktLen));
		retVal = [self write:buf len:pktLen];
	}
	return retVal;
}




/*	xmit:,...
 *
 *	Send string to remote and wait for ack. Returns negative error code if
 *	failure, otherwise returns positive number of milliseconds required for
 *	transfer.
 */
- (int)xmit:(char *)fmt, ...
{
    va_list 		args;
	int				retVal;
	char			localBuf[SCD_MAX_PACKETSIZE];

    va_start(args, fmt);
	vsprintf(localBuf, fmt, args);
	va_end(args);
	retVal = [self sendPkt:DATA_PKT data:localBuf len:strlen(localBuf) pktId:NEXT_ID_CHAR(xmitPktId)];
	return retVal;
}




/*	xmit:len:
 *
 *	Send fixed-length binary character array to remote and wait for ack. Returns 
 *	negative error code if failure, otherwise returns positive number of milliseconds 
 *	required for transfer.
 */
- (int)xmit:(char *)buf len:(int)len
{
	int				retVal;

	retVal = [self sendPkt:DATA_PKT data:buf len:len pktId:NEXT_ID_CHAR(xmitPktId)];
	return retVal;
}


/*	xmitOOL:,...
 *
 *	Send string to remote and wait for ack. Returns negative error code or
 *	positive number of microseconds required for transfer.  If remote has
 *	a delegate, the delegate is sent the data via the -oolMessage:len: msg.
 *
 */
- (int)xmitOOL:(char *)fmt, ...
{
    va_list 		args;
	int				retVal;
	char			localBuf[SCD_MAX_PACKETSIZE];

    va_start(args, fmt);
	vsprintf(localBuf, fmt, args);
	va_end(args);
	retVal = [self sendPkt:OOL_PKT data:localBuf len:strlen(localBuf) pktId:NEXT_ID_CHAR(xmitPktId)];
	return retVal;
}



/*	xmitOOL:len:
 *
 *	Send fixed-length binary character array to remote and wait for ack. Returns 
 *	negative error code if failure, otherwise returns positive number of milliseconds 
 *	required for transfer. If remote has a delegate, the delegate is sent the data 
 *	via the -oolMessage:len: msg.
 */
- (int)xmitOOL:(char *)buf len:(int)len
{
	int				retVal;

	retVal = [self sendPkt:OOL_PKT data:buf len:len pktId:NEXT_ID_CHAR(xmitPktId)];
	return retVal;
}




/*	test:,...
 *
 *	Like xmit:,..., but sends a test packet instead of a data packet.  Test
 *	packets are immediatly acknowledged by the remote's receiving thread (if
 *	it is up and running), and the packet is not forwarded to the receiving 
 *	application.   Returns negative error code or positive number of 
 *	microseconds required for transfer.
 */
- (int)test:(char *)fmt, ...
{
    va_list 		args;
	int				retVal;
	char			localBuf[SCD_MAX_PACKETSIZE];

    va_start(args, fmt);
	vsprintf(localBuf, fmt, args);
	va_end(args);
	retVal = [self sendPkt:TEST_PKT data:localBuf len:strlen(localBuf) pktId:NEXT_ID_CHAR(xmitPktId)];
	return retVal;
}




/*	test:len:
 *
 *	Like xmit:len:, but sends a test packet instead of a data packet.  Test
 *	packets are immediatly acknowledged by the remote's receiving thread (if
 *	it is up and running), and the packet is not forwarded to the receiving 
 *	application.   Returns negative error code or positive number of 
 *	microseconds required for transfer.
 */
- (int)test:(char *)buf len:(int)len
{
	int				retVal;

	retVal = [self sendPkt:TEST_PKT data:buf len:len pktId:NEXT_ID_CHAR(xmitPktId)];
	return retVal;
}





/*	processPkt:len:
 *
 *	returns zero or greater length if valid data packet, -1 otherwise.
 */
-(int)processPkt:(char *)pkt len:(int)len
{
	int				i, 
					sum, 
					retVal = (-1);
	char 			buf[16], 
					*bp;

	bp = pkt+(len-CSUM_CHARS-1);
	for (i=0;i<CSUM_CHARS;i++)
		buf[i] = *bp++;
	buf[i] = '\0';
	sscanf(buf,"%x", (unsigned *)&sum);
	if (sum != sumCheck((u_char *)&pkt[1], len-(CSUM_CHARS+2))) { 		/* bad packet. */
		[self sendPkt:NACK_PKT data:NULL len:0 pktId:NACK_ID];
		if (debugFlag>1)
			fprintf(stderr,"\nReceived Bad Pkt:%s\n", stringForCharArray(pkt,len));
	} else {
		if (pkt[1] == ACK_PKT) {
			asciiAckDPktID = pkt[2];
		} else if (pkt[1] == NACK_PKT) {
			nackDPktFlag++; 
		} else if ((pkt[1] == DATA_PKT) || (pkt[1] == OOL_PKT) || (pkt[1] == TEST_PKT)) {
			[self sendPkt:ACK_PKT data:NULL len:0 pktId:pkt[2]];
			if (((pkt[1] == DATA_PKT) || (pkt[1] == OOL_PKT)) && (asciiRcvDPktID != pkt[2])) {	/* then this is a new data packet. */
				if (debugFlag>1)
					fprintf(stderr,"\nReceived new Pkt(old:%c, new:%c):%s\n", asciiRcvDPktID, pkt[2], stringForCharArray(pkt,len));
				asciiRcvDPktID = pkt[2];
				if (pkt[1] == OOL_PKT) {
					if (delegate) {
						retVal = stringDecode(&pkt[3], len-(CSUM_CHARS+4), pkt); /* warning! in-place de-packetization. */
						pkt[retVal]='\0';
						[delegate oolMessage:pkt len:retVal];
						retVal = (-1);
					}
				} else {
					retVal = stringDecode(&pkt[3], len-(CSUM_CHARS+4), pkt); /* warning! in-place de-packetization. */
					pkt[retVal]='\0';
				}
			} else 	if (debugFlag>1) {
				fprintf(stderr,"\nReceived duplicate Pkt:%s\n", stringForCharArray(pkt,len));
			}
		}
	}

	return retVal;
}




/*	pollRecvBufferReady
 *
 *	Returns 1 if recv Buffer contains data and is ready to be read, otherwise returns 0.
 */
- (int) pollRecvBufferReady
{
	int		retVal;
	if (dataPktLen < 0)
		retVal = 0;
	else 
		retVal = 1;
	return retVal;
}




/*	- recv:maxLen:
 *
 *	Returns negative error code if failure, otherwise returns positive 
 *	number of characters received.
 */
- (int) recv:(char *)str maxLen:(int)len
{
	int					elapsedTime=0;
	int					retVal;
	struct timeval  	t = {0,0};

	microTimeStampA(&t); 				/* Set start time. */
	for (retVal = 0;!retVal;) {
		if (dataPktLen < 0) {
			elapsedTime = microTimeStampA(&t)/1000;
			if (recvTimeoutMsecs && ((elapsedTime > recvTimeoutMsecs) || (elapsedTime < 0))) {
				if (debugFlag)
					fprintf(stderr,"[RTO]");
				retVal=SCD_TIMEOUT_ERR;
			} else {
				cthread_yield();
			}		
		} else {
			break;
		}
	}
	if (!retVal) {
		if (dataPktLen > len) {
			fprintf(stderr,"\nrecv:maxLen: actual packet length:%d exceeds expected:%d\n",dataPktLen, len);
			retVal = SCD_LENGTH_ERR;
		} else {
			bcopy(recvBuf[RCV_OUT_BASKET], str, dataPktLen);
			retVal = dataPktLen;
			if (dataPktLen < (len-1))
				str[dataPktLen] = '\0';
			dataPktLen = (-1);
		}
	}
	return retVal;
}



/*	- setPort:atRate:
 *
 *	Change port and/or transmission parameters.
 *	Return 0 on success and negative error code if fail.
 */
- (int)setPort:(sccPortType)thePort atRate:(baudRateType)theRate
{
	struct sgttyb			sg;
	int						retVal =0;

	if ((fd > 0) && (thePort != port)) {
		close(fd);
		fd = (-1);
	}
	if (fd < 0 ) {
		if ((fd = open(portString(thePort), O_RDWR)) < 0) {
			fprintf(stderr,"\nopen() failed on %s:%s\n", portString(thePort),
					strerror(errno));
			retVal = SCD_OPEN_ERR;
		} else {
    /* prevent any other unauthorized port accesses */
		    if (ioctl(fd, TIOCEXCL, (void*)nil) < 0) {
				fprintf(stderr,
					"\nioctl(TIOCEXCL) failed on %s:%s\n", portString(thePort),
					strerror(errno));
				retVal = SCD_OPEN_ERR;
			}
		}
	}
	
	if (!retVal)  {
		if (ioctl(fd, TIOCGETP, &sg) < 0) {
			fprintf(stderr,
				"\nioctl(TIOCGETP) failed on %s:%s\n", portString(thePort),
				strerror(errno));
			retVal = SCD_OPEN_ERR;
		} else {
			sg.sg_ispeed = baudRateCode(theRate);
			sg.sg_ospeed = baudRateCode(theRate);
			sg.sg_flags = RAW;
			sg.sg_flags |= ANYP;
			if (ioctl(fd, TIOCSETP, &sg) < 0) {
				fprintf(stderr,
					"\nioctl(TIOCSETP) failed on %s:%s\n", portString(thePort),
					strerror(errno));
				retVal = SCD_OPEN_ERR;
			}
		}
	}
	if (!retVal) {
		port = thePort;
		baudRate = theRate;
	} else {
		if (fd > 0)
			close(fd);
		port = noPort;
		baudRate = brNone;
	}
	return retVal;
}
							



/*	- initPort:atRate:
 *
 *	open specified port at specified rate. Returns nil on failure.
 */
- initPort:(sccPortType)thePort atRate:(baudRateType)theRate
{
	[super init];
	dataPktLen = -1;
	(mutex_t)writeLock=mutex_alloc();
	mutex_init((mutex_t)writeLock);
	(mutex_t)dataPktLock=mutex_alloc();
	mutex_init((mutex_t)dataPktLock);
	fd = (-1);
	maxXmitRetries = XMIT_RETRY_COUNT;
	recvTimeoutMsecs = RECV_TIMEOUT_MSECS;
	if (theRate == brNone) {
		baudRateType		rate;
		int					timeout;

		for (timeout=10,rate = BR_MAX;rate >= BR_MIN_AUTO_CONF;rate--,timeout*=2) {
			if ([self setPort:thePort atRate:rate] < 0) {
				fprintf(stderr,"\nsetPort:atRate:%d failed.\n", rate);
			} else {
		   		if (ioctl(fd, TIOCCBRK, (void*)nil) < 0) {	/* clear break bit. */
					fprintf(stderr,
						"\nioctl(TIOCCBRK) failed on %s:%s\n", portString(thePort),
						strerror(errno));
				}
				xmitAckTimeoutMsecs = timeout;
				if ([self test:"Attempting to determine baud rate for remote interface."] >= 0)
					break;
		   		if (ioctl(fd, TIOCSBRK, (void*)nil) < 0) {	/* set break bit. */
					fprintf(stderr,
						"\nioctl(TIOCSBRK) failed on %s:%s\n", portString(thePort),
						strerror(errno));
				}
				cthread_yield();
			}
		}
		if (rate < BR_MIN_AUTO_CONF) {
			if ([self setPort:thePort atRate:BR_MAX] < 0) {
				[self free];
				self = nil;
			}
		}
	} else if ([self setPort:thePort atRate:theRate]) {
		[self free];
		self = nil;
	}

	if (self != nil) {
		xmitAckTimeoutMsecs = XMIT_TIMEOUT_MSECS;
		cthread_detach(cthread_fork((cthread_fn_t)theListener, (any_t)self));
	}
	return self;
}



/*	-initPort:
 *	open specified port and search for match baud rate. Returns nil on failure.
 */
- initPort:(sccPortType)thePort 
{
	return [self initPort:thePort atRate:brNone];
}



/*	- init
 *
 *	open default port (A) and search for match baud rate. Returns nil on failure.
 */
- init
{
	return [self initPort:sccPortA atRate:brNone];
}


-setDelegate:myDelegate
{
	delegate = myDelegate;
	return self;
}

-delegate
{
	return delegate;
}

@end

/*	pktSnooper()
 *
 *	Browses first *size characters in pkt.  If a pkt is found, it is left-justified in the array
 *	and it's length is returned - otherwize zero is returned.
 */
static int pktSnooper(char *head, int *size, id self)
{
	char					*cp, 
							*wp;
	int						retVal, i, garbage;
	
	if (debugFlag > 10)
		fprintf(stderr,"\nSnoopy called with %d for %s\n", *size, stringForCharArray(head, *size));
	if (*size > 0 && *head != SOP_CHAR_CODE) {
		if (debugFlag)
			fprintf(stderr,"\nSnoopy Found packet w/o head\n");
/*		[self sendPkt:NACK_PKT data:NULL len:0 pktId:NACK_ID]; */
		for (garbage=i=0,cp=head;i<*size; i++) {
			if (*cp++ == SOP_CHAR_CODE)
				break;
			garbage++;
		}
		if (debugFlag) 
			fprintf(stderr,"\nNuking %d Garbage chars\n", garbage);
		*size -= garbage;
		for (i=0,wp=head;i<*size;i++)
			*wp++ = *cp++;
	}
	for (wp=cp=head,garbage=i=0;i<*size;i++) {
		if ((*cp == SOP_CHAR_CODE) && (cp > head)) {
			if (debugFlag) 
				fprintf(stderr,"\nNuking %d Garbage chars\n", (int)(cp-head));
			garbage+=(cp-head);
			wp = head;
			*wp++ = *cp++;
		} else if (*cp == EOP_CHAR_CODE) {
			*wp++ = *cp++;
			if (*head == SOP_CHAR_CODE) {
				break;
			}
		} else if (ILLEGAL_CHAR(*cp)) {
		 	garbage++;
		 	cp++;
		} else {
			*wp++ = *cp++;
		}
	}
	if (i < (*size))
		retVal =  i+1;
	else
		retVal =  0;
	*size -= garbage;
	if (debugFlag > 10)
		fprintf(stderr,"\nSnoopy returning %d for %s with size:%d\n", retVal, stringForCharArray(head, *size),*size);
	return retVal;
}


/* theListener()
	An independent background thread that continuously reads the recv port.  Incoming data 
	is read into RCV_IN_BASKET buffer.  Once an entire pkt is in the buffer, processPkt is 
	called to verify the packet's checksum and to build and send an appropriate reply packet.  
	If the packet was a DATA packet, then any escape sequences in the data field are processed 
	to restore any non PASCII characters and the data field is shifted to the front of the 
	buffer and the function returns the length of the data field.  The thread then waits until 
	RCV_OUT_BASKET is empty (The application thread finishes with the buffer and sets 
	dataPktLen= -1)  before swapping IN and OUT baskets.  The last thing the thread does is set 
	dataPktLen to the length of the data buffer to let the application thread know that data is 
	available.
 */
static void theListener(SerialCommDevice *self)
{
	char 			*cp, *pkt;
	int				i,
					len, 
					oldSize, 
					newSize;

	for (oldSize=newSize=0, pkt=self->recvBuf[RCV_IN_BASKET];;) {	
		for (;;) {
			if ((newSize = read(self->fd, pkt+oldSize, SCD_MAX_PACKETSIZE-oldSize)) < 0)
				fprintf(stderr,"\nread() failed on %s:%s\n", portString(self->port), strerror(errno));
			oldSize+=newSize;
			if (debugFlag > 10)
				fprintf(stderr,"\nPkt:%s\n", stringForCharArray(pkt,oldSize));
			if ((len = pktSnooper(pkt, &oldSize, self)) > 0)
				break;
			cthread_yield();
		}
		do {
			cp = &pkt[len];
			oldSize -= len;
			if ((newSize = [self processPkt:pkt len:len]) >= 0) {
				while (self->dataPktLen >= 0)	 /* wait for primary thread to finish with it's buffer. */
					cthread_yield();
				self->dataPktIndex = !self->dataPktIndex; 					/* switch buffers. */
			}
		
			pkt=self->recvBuf[RCV_IN_BASKET];
			for (i=0;i<oldSize;i++)  	/* copy remainder of data into head of buffer. */
				pkt[i] = *cp++;
			if (self->dataPktLen < 0)
				self->dataPktLen = newSize;
			len = pktSnooper(pkt, &oldSize, self);
		} while (len > 0);
	}
}

@implementation SCDDelegate
-oolMessage:(char *)bufr len:(int)l {return self;}
@end
