//
// MiscXmodem.m: spawn and control a thread performing a XMODEM file transfer
//		Written by Eric Norum Copyright (c) 1994 by Eric Norum.
//				Version 1.0.  All rights reserved.
//		This notice may not be removed from this source code.
//
//	This object is included in the MiscKit by permission from the author
//	and its use is governed by the MiscKit license, found in the file
//	"LICENSE.rtf" in the MiscKit distribution.  Please refer to that file
//	for a list of all applicable permissions and restrictions.
//	

/*
 * Xmodem file transfer
 *
 * Based on ``XMODEM/YMODEM PROTOCOL REFERENCE -- A compendium of documents
 * describing the XMODEM and YMODEM File Transfer Protocols'', Chuck Forsberg,
 * Omen Technology Incorporated, Portland, Oregon, 1988.
 */
#import <misckit/misckit.h>
/* #import "MiscXmodem.h" /* */

/*
 * All timeouts are in seconds
 */
#define TRANSMIT_TIMEOUT				60
#define TRANSMIT_BAD_NAK_LIMIT			5
#define RECEIVE_PACKET_TIMEOUT			10
#define RECEIVE_CHARACTER_TIMEOUT		1
#define RECEIVE_CRC_ADAPT_TIMEOUT		3
#define RECEIVE_PACKET_RETRY_LIMIT		6
#define RECEIVE_CRC_ADAPT_RETRY_LIMIT	7

#define SOH		'\001'
#define STX		'\002'
#define EOT		'\004'
#define ACK		'\006'
#define NAK		'\025'
#define CTRLZ	'\032'
#define CRC_NAK	'C'

#define MESSAGETYPE_HAVE_PACKET	0
#define MESSAGETYPE_WANT_PACKET	1
#define MESSAGETYPE_RETRY		2
#define MESSAGETYPE_FINAL		3

#define MAX_REPLY_SIZE		200

struct packetMessage {
	msg_header_t	h;
};

struct retryMessage {
	msg_header_t	h;
	msg_type_t		t1;
	int				retry;
};

struct finalMessage {
	msg_header_t	h;
	msg_type_t		t1;
	char			cbuf[1];
};

/*
 * See makecrc.c for details.
 */
#define UPDATE_CRC(c,crc) (crctab[(((crc)>>8) & 0xFF)] ^ (((crc)<<8) | (c)))
static const unsigned short crctab[256] = {
      0x0000,  0x1021,  0x2042,  0x3063,  0x4084,  0x50A5,  0x60C6,  0x70E7,
      0x8108,  0x9129,  0xA14A,  0xB16B,  0xC18C,  0xD1AD,  0xE1CE,  0xF1EF,
      0x1231,  0x0210,  0x3273,  0x2252,  0x52B5,  0x4294,  0x72F7,  0x62D6,
      0x9339,  0x8318,  0xB37B,  0xA35A,  0xD3BD,  0xC39C,  0xF3FF,  0xE3DE,
      0x2462,  0x3443,  0x0420,  0x1401,  0x64E6,  0x74C7,  0x44A4,  0x5485,
      0xA56A,  0xB54B,  0x8528,  0x9509,  0xE5EE,  0xF5CF,  0xC5AC,  0xD58D,
      0x3653,  0x2672,  0x1611,  0x0630,  0x76D7,  0x66F6,  0x5695,  0x46B4,
      0xB75B,  0xA77A,  0x9719,  0x8738,  0xF7DF,  0xE7FE,  0xD79D,  0xC7BC,
      0x48C4,  0x58E5,  0x6886,  0x78A7,  0x0840,  0x1861,  0x2802,  0x3823,
      0xC9CC,  0xD9ED,  0xE98E,  0xF9AF,  0x8948,  0x9969,  0xA90A,  0xB92B,
      0x5AF5,  0x4AD4,  0x7AB7,  0x6A96,  0x1A71,  0x0A50,  0x3A33,  0x2A12,
      0xDBFD,  0xCBDC,  0xFBBF,  0xEB9E,  0x9B79,  0x8B58,  0xBB3B,  0xAB1A,
      0x6CA6,  0x7C87,  0x4CE4,  0x5CC5,  0x2C22,  0x3C03,  0x0C60,  0x1C41,
      0xEDAE,  0xFD8F,  0xCDEC,  0xDDCD,  0xAD2A,  0xBD0B,  0x8D68,  0x9D49,
      0x7E97,  0x6EB6,  0x5ED5,  0x4EF4,  0x3E13,  0x2E32,  0x1E51,  0x0E70,
      0xFF9F,  0xEFBE,  0xDFDD,  0xCFFC,  0xBF1B,  0xAF3A,  0x9F59,  0x8F78,
      0x9188,  0x81A9,  0xB1CA,  0xA1EB,  0xD10C,  0xC12D,  0xF14E,  0xE16F,
      0x1080,  0x00A1,  0x30C2,  0x20E3,  0x5004,  0x4025,  0x7046,  0x6067,
      0x83B9,  0x9398,  0xA3FB,  0xB3DA,  0xC33D,  0xD31C,  0xE37F,  0xF35E,
      0x02B1,  0x1290,  0x22F3,  0x32D2,  0x4235,  0x5214,  0x6277,  0x7256,
      0xB5EA,  0xA5CB,  0x95A8,  0x8589,  0xF56E,  0xE54F,  0xD52C,  0xC50D,
      0x34E2,  0x24C3,  0x14A0,  0x0481,  0x7466,  0x6447,  0x5424,  0x4405,
      0xA7DB,  0xB7FA,  0x8799,  0x97B8,  0xE75F,  0xF77E,  0xC71D,  0xD73C,
      0x26D3,  0x36F2,  0x0691,  0x16B0,  0x6657,  0x7676,  0x4615,  0x5634,
      0xD94C,  0xC96D,  0xF90E,  0xE92F,  0x99C8,  0x89E9,  0xB98A,  0xA9AB,
      0x5844,  0x4865,  0x7806,  0x6827,  0x18C0,  0x08E1,  0x3882,  0x28A3,
      0xCB7D,  0xDB5C,  0xEB3F,  0xFB1E,  0x8BF9,  0x9BD8,  0xABBB,  0xBB9A,
      0x4A75,  0x5A54,  0x6A37,  0x7A16,  0x0AF1,  0x1AD0,  0x2AB3,  0x3A92,
      0xFD2E,  0xED0F,  0xDD6C,  0xCD4D,  0xBDAA,  0xAD8B,  0x9DE8,  0x8DC9,
      0x7C26,  0x6C07,  0x5C64,  0x4C45,  0x3CA2,  0x2C83,  0x1CE0,  0x0CC1,
      0xEF1F,  0xFF3E,  0xCF5D,  0xDF7C,  0xAF9B,  0xBFBA,  0x8FD9,  0x9FF8,
      0x6E17,  0x7E36,  0x4E55,  0x5E74,  0x2E93,  0x3EB2,  0x0ED1,  0x1EF0
};

@implementation MiscXmodem

/*
 * Send a message to the `top end'
 */
static void
sendPacketMessage (struct MiscXmodem *p, int type)
{
	struct packetMessage msg;
	kern_return_t r;

	msg.h.msg_id = type;
	msg.h.msg_size = sizeof msg;
	msg.h.msg_simple = TRUE;
	msg.h.msg_type = MSG_TYPE_NORMAL;
	msg.h.msg_remote_port = p->messagePort;
	msg.h.msg_local_port = p->replyPort;

	r = msg_send ((msg_header_t *)&msg, MSG_OPTION_NONE, 0);
	if (r != SEND_SUCCESS)
		cthread_exit (NULL);

	/*
	 * Wait for the top end to finish with the packet
	 */
	r = msg_receive ((msg_header_t *)&msg, MSG_OPTION_NONE, 0);
	if (r != RCV_SUCCESS)
		cthread_exit (NULL);
}

static void
sendRetryMessage (struct MiscXmodem *p, int retryNumber)
{
	struct retryMessage msg;
	kern_return_t r;

	msg.h.msg_id = MESSAGETYPE_RETRY;
	msg.h.msg_size = sizeof msg;
	msg.h.msg_simple = TRUE;
	msg.h.msg_type = MSG_TYPE_NORMAL;
	msg.h.msg_remote_port = p->messagePort;
	msg.h.msg_local_port = PORT_NULL;

	msg.t1.msg_type_name = MSG_TYPE_INTEGER_32;
	msg.t1.msg_type_size = 32;
	msg.t1.msg_type_inline = TRUE;
	msg.t1.msg_type_longform = FALSE;
	msg.t1.msg_type_deallocate = FALSE;
	msg.retry = retryNumber;

	r = msg_send ((msg_header_t *)&msg, MSG_OPTION_NONE, 0);
	if (r != SEND_SUCCESS)
		cthread_exit (NULL);
}

static void
sendFinalMessage (struct MiscXmodem *p, char *aString)
{
	int length, size;
	struct finalMessage *msgp;

	length = strlen (aString) + 1;
	size = sizeof *msgp  - 1 + length;
	msgp = alloca (size);
	msgp->h.msg_id = MESSAGETYPE_FINAL;
	msgp->h.msg_size = size;
	msgp->h.msg_simple = TRUE;
	msgp->h.msg_type = MSG_TYPE_NORMAL;
	msgp->h.msg_remote_port = p->messagePort;
	msgp->h.msg_local_port = PORT_NULL;

	msgp->t1.msg_type_name = MSG_TYPE_STRING;
	msgp->t1.msg_type_size = 8 * length;
	msgp->t1.msg_type_inline = TRUE;
	msgp->t1.msg_type_longform = FALSE;
	msgp->t1.msg_type_deallocate = FALSE;
	strcpy (msgp->cbuf, aString);

	msg_send ((msg_header_t *)msgp, MSG_OPTION_NONE, 0);
	cthread_exit (0);
}

/*
 * Report a failed system call
 */
static void
failedSysCall (struct MiscXmodem *p, const char *aString)
{
	char cbuf[200];

	sprintf (cbuf, "%s failed: %s", aString, strerror (cthread_errno ()));
	sendFinalMessage (p, cbuf);
}

/*
 * Get characters from TTY
 */
static BOOL
readWithTimeout(struct MiscXmodem *p, unsigned char *cp, int length, int seconds)
{
	int i;
	struct timeval tv;
	fd_set readFdSet;

	while (length) {
		tv.tv_usec = 0;
		tv.tv_sec = seconds;
		readFdSet = p->checkFdSet;
		i = select (p->fd+1, &readFdSet, NULL, NULL, &tv);
		if (i < 0)
			failedSysCall (p, "Select");
		if (i == 0)
			return (NO);
		i = read (p->fd, cp, length);
		if (i < 0)
			failedSysCall (p, "Read");
		if (i == 0)
			sendFinalMessage (p, "Unexpected EOF");
		cp += i;
		length -= i;
	}
	return YES;
}
		
static int
getCharacterWithTimeout(struct MiscXmodem *p, int seconds)
{
	unsigned char c;

	if (readWithTimeout (p, &c, 1, seconds) == NO)
		return EOF;
	return c;
}
		
/*
 * Clean out TTY read queue
 */
static void
flushTTY (struct MiscXmodem *p)
{
	while (getCharacterWithTimeout (p, 1) != EOF)
		continue;
}

/*
 * Send a character
 */
static void
sendChar (struct MiscXmodem *p, unsigned char c)
{
	if (write (p->fd, &c, 1) != 1)
		failedSysCall (p, "Character write");
}
	
static int
computePacketSize (struct MiscXmodem *p)
{
	switch (p->packetBuffer[0]) {
	case SOH:
		return 3 + 128 + (p->useCRC ? 2 : 1);
		break;

	case STX:
		return 3 + 1024 + (p->useCRC ? 2 : 1);
		break;

	case EOT:
		return 1;
		break;

	default:
		return 0;
	}
}

/*
 * Get packet
 */
static BOOL
getPacket (struct MiscXmodem *p)
{
	int c;
	int nleft;

	c = EOF;
	if (p->isDynamicCheckPacket) {
		int pass;
		for (pass = 0 ; pass < RECEIVE_CRC_ADAPT_RETRY_LIMIT ; pass++) {
			sendChar (p, CRC_NAK);
			c = getCharacterWithTimeout (p, RECEIVE_CRC_ADAPT_TIMEOUT);
			if (c != EOF)
				break;
		}
		p->isDynamicCheckPacket = NO;
		if (c == EOF) {
			p->useCRC = NO;
			sendChar (p, NAK);
		}
	}
	if (c == EOF) {
		c = getCharacterWithTimeout (p, RECEIVE_PACKET_TIMEOUT);
		if (c == EOF)
			return NO;
	}
	p->packetBuffer[0] = c;
	nleft = computePacketSize (p) - 1;
	if (nleft < 0)
		return NO;
	if (readWithTimeout (p, p->packetBuffer+1, nleft, RECEIVE_CHARACTER_TIMEOUT) == NO)
		return NO;
	return YES;
}

/*
 * Compute checksum/CRC
 */
static void
computeChecksum (unsigned char *result, unsigned char *cp, int length)
{
	int checksum = 0;

	while (length--)
		checksum += *cp++;
	*result = checksum;
}

static void
computeCRC (unsigned char *hi, unsigned char *lo, unsigned char *cp, int length)
{
	int crc = 0;

	while (length--) {
		unsigned char c = *cp++;
		crc = UPDATE_CRC (c, crc);
	}
	*hi = crc >> 8;
	*lo = crc;
}

/*
 * Check that packet is valid
 */
static BOOL
verifyPacket (struct MiscXmodem *p)
{
	unsigned char *cp = p->packetBuffer;
	int dataCount;
	int packetNumber;

	switch (*cp) {
	default:
		return NO;

	case EOT:
		return YES;

	case SOH:
		dataCount = 128;
		break;

	case STX:
		dataCount = 1024;
		break;
	}
	cp++;
	packetNumber = *cp;
	if (packetNumber != (~*(cp+1) & 0xFF))
		return NO;
	if (packetNumber == p->lastPacketNumber)
		return YES;
	if (p->lastPacketNumber < 0)
		p->lastPacketNumber = 0;
	if (packetNumber != ((p->lastPacketNumber + 1) & 0xFF))
		return NO;
	cp += 2;
	if (p->useCRC) {
		unsigned char lo, hi;
		computeCRC (&hi, &lo, cp, dataCount+2);
		if (hi || lo)
			return NO;
	}
	else {
		unsigned char checksum;
		computeChecksum (&checksum, cp, dataCount);
		cp += dataCount;
		if (*cp != checksum)
			return NO;
	}
	p->lastPacketNumber = packetNumber;
	return YES;
}

/*
 * Xmodem thread entry point to receive data
 */
static void *
getFile (void *ap)
{
	struct MiscXmodem *p = (struct MiscXmodem *)ap;
	int retry;

	for (;;) {
		retry = 0;
		for (;;) {
			if (retry != 0)
				sendRetryMessage (p, retry);
			if (getPacket (p) && verifyPacket (p)) {
				sendChar (p, ACK);
				if (p->packetBuffer[0] == EOT)
					sendFinalMessage (p, "");
				sendPacketMessage (p, MESSAGETYPE_HAVE_PACKET);
				break;
			}
			if (retry++ == RECEIVE_PACKET_RETRY_LIMIT)
				sendFinalMessage (p, "Too many bad packetts");
			flushTTY (p);
			sendChar (p, NAK);
		}
	}
}

/*
 * Send packet
 */
static void
sendPacket (struct MiscXmodem *p)
{
	int pksize;
	int retry;
	int badNakCount;

	pksize = computePacketSize (p);
	if (pksize > 1) {
		p->packetBuffer[1] = ++p->lastPacketNumber;
		p->packetBuffer[2] = ~p->packetBuffer[1];
		if (p->useCRC) {
			p->packetBuffer[pksize-2] = 0;
			p->packetBuffer[pksize-1] = 0;
			computeCRC (p->packetBuffer+pksize-2, p->packetBuffer+pksize-1,
												p->packetBuffer+3, pksize-3);
		}
		else {
			computeChecksum (p->packetBuffer+pksize-1, p->packetBuffer+3, pksize-3);
		}
	}
	retry = 0;
	badNakCount = 0;
	for (;;) {
		if (write (p->fd, p->packetBuffer, pksize) != pksize)
			failedSysCall (p, "Packet write");
		switch (getCharacterWithTimeout (p, TRANSMIT_TIMEOUT)) {
		case EOF:
			sendFinalMessage (p, "Timed out waiting for reply");

		case ACK:
			return;

		case NAK:
			badNakCount = 0;
			break;

		default:
			if (++badNakCount == TRANSMIT_BAD_NAK_LIMIT)
				sendFinalMessage (p, "Too many bad NAK's");
			break;
		}
		sendRetryMessage (p, ++retry);
	}
}

/*
 * Wait for receiver to start
 */
static void
awaitReceiver (struct MiscXmodem *p)
{
	for (;;) {
		switch (getCharacterWithTimeout (p, TRANSMIT_TIMEOUT)) {
		case EOF:
			sendFinalMessage (p, "Timed out waiting for receiver to start");

		case NAK:
			p->useCRC = NO;
			return;

		case CRC_NAK:
			p->useCRC = YES;
			return;
		}
	}
}

/*
 * Xmodem thread entry point to send data
 */
static void *
putFile (void *ap)
{
	struct MiscXmodem *p = (struct MiscXmodem *)ap;

	p->lastPacketNumber = 0;
	awaitReceiver (p);
	sendPacketMessage (p, MESSAGETYPE_WANT_PACKET);
	for (;;) {
		sendPacket (p);
		if (p->packetBuffer[0] == EOT)
			sendFinalMessage (p, "");
		sendPacketMessage (p, MESSAGETYPE_WANT_PACKET);
	}
}

/*
 * `Top End' routines
 */
- (void)sendSmallPacket
{
	int ncopy;

	if (smallPacketCount >= 128)
		ncopy = 128;
	else
		ncopy = smallPacketCount;
	packetBuffer[0] = SOH;
	memcpy (packetBuffer+3, smallPacketData+smallPacketIndex, ncopy);
	if (ncopy < 128)
		memset (packetBuffer+3+ncopy, CTRLZ, 128-ncopy);
	smallPacketIndex += ncopy;
	smallPacketCount -= ncopy;
	kbytes += 128.0 / 1024.0;
}

/*
 * Handle message from the file transfer thread
 */
- (void)handleMessage:(msg_header_t *)msg
{
	kern_return_t r;

	switch (msg->msg_id) {
	case MESSAGETYPE_HAVE_PACKET:
		{
		struct packetMessage *msgp = (struct packetMessage *)msg;
		int count = ((packetBuffer[0] == SOH) ? 128 : 1024);

		goodPacketCount++;
		kbytes += (float)count / 1024.0;
		if (isText) {
			unsigned char *cp = packetBuffer + 3;

			while (count--) {
				if (*cp == CTRLZ)
					break;
				if (*cp != '\r')
					NXPutc (stream, *cp);
				cp++;
			}
		}
		else {
			NXWrite (stream, packetBuffer+3, count);
		}
		msgp->h.msg_local_port = PORT_NULL;
		r = msg_send ((msg_header_t *)msgp, MSG_OPTION_NONE, 0);
		if (r != SEND_SUCCESS)
			abort ();
		if ([delegate respondsTo:@selector(didTransferPacket:kbytes:)])
			[delegate didTransferPacket:goodPacketCount kbytes:kbytes];
		}
		break;

	case MESSAGETYPE_WANT_PACKET:
		{
		struct packetMessage *msgp = (struct packetMessage *)msg;
		int nread;
		int fullPacketSize = use1kPackets ? 1024 : 128;
		if (goodPacketCount && [delegate respondsTo:@selector(didTransferPacket:kbytes:)])
			[delegate didTransferPacket:goodPacketCount kbytes:kbytes];
		if (smallPacketCount) {
			[self sendSmallPacket];
		}
		else {
			if (isText) {
				unsigned char *cp = packetBuffer + 3;
				int c;

				nread = 0;
				while (nread < fullPacketSize) {
					if (newlineFlag) {
						c = '\n';
						newlineFlag = NO;
					}
					else {
						c = NXGetc (stream);
						if (c == '\n') {
							newlineFlag = YES;
							c = '\r';
						}
					}
					if (c == EOF)
						break;
					*cp++ = c;
					nread++;
				}
			}
			else {
				nread = NXRead (stream, packetBuffer+3, fullPacketSize);
			}
			if (nread == 0) {
				packetBuffer[0] = EOT;
			}
			else if (nread == fullPacketSize) {
				packetBuffer[0] = use1kPackets ? STX : SOH;
				kbytes += use1kPackets ? 1.0 : 1.0 / 8.0;
			}
			else {
				smallPacketIndex = 0;
				smallPacketCount = nread;
				memcpy (smallPacketData, packetBuffer+3, nread);
				[self sendSmallPacket];
			}
		}
		msgp->h.msg_local_port = PORT_NULL;
		r = msg_send ((msg_header_t *)msgp, MSG_OPTION_NONE, 0);
		if (r != SEND_SUCCESS)
			abort ();
		goodPacketCount++;
		}
		break;

	case MESSAGETYPE_RETRY:
		{
		struct retryMessage *msgp = (struct retryMessage *)msg;
		if ([delegate respondsTo:@selector(didRetryNumber:)])
			[delegate didRetryNumber:msgp->retry];
		}
		break;

	case MESSAGETYPE_FINAL:
		{
		struct finalMessage *msgp = (struct finalMessage *)msg;
		DPSRemovePort (messagePort);
		port_deallocate (task_self(), messagePort);
		port_deallocate (task_self(), replyPort);
		cthread_join (xmodemThread);
		isActive = NO;
		if ([delegate respondsTo:@selector(didFinish:)])
			[delegate didFinish:msgp->cbuf];
		}
		if (serialPortWasSuspended == NO)
			[serialPort continue];
		break;
	}
}

/*
 * Get message from `Bottom end'
 */
static void
messageHandler (msg_header_t *msg, void *ap)
{
	[(MiscXmodem *)ap handleMessage:msg];
}

/*
 * Transfer file in appropriate direction
 */
- transferBetweenStream:(NXStream *)aStream serialPort:aSerialPort toStream:(BOOL)toStream
{
	if (isActive)
		return nil;
	if (isDynamicCheck) {
		useCRC = YES;
		isDynamicCheckPacket = YES;
	}
	else  {
		isDynamicCheckPacket = NO;
	}
	isActive = YES;
	newlineFlag = NO;
	lastPacketNumber = -1;
	goodPacketCount = 0;
	smallPacketIndex = smallPacketCount = 0;
	kbytes = 0;

	stream = aStream;
	serialPort = aSerialPort;
	if ((serialPortWasSuspended = [serialPort suspended]) == NO)
		[serialPort suspend];
	fd = [serialPort filedes];
	FD_ZERO (&checkFdSet);
	FD_SET (fd, &checkFdSet);

	port_allocate (task_self(), &messagePort);
	port_allocate (task_self(), &replyPort);
	DPSAddPort (messagePort, messageHandler, MAX_REPLY_SIZE, self, NX_BASETHRESHOLD);
	if (toStream)
		xmodemThread = cthread_fork (getFile, self);
	else
		xmodemThread = cthread_fork (putFile, self);
	return self;
}
	
- sendToStream:(NXStream *)aStream fromSerialPort:aSerialPort
{
	return [self transferBetweenStream:aStream serialPort:aSerialPort toStream:YES];
}

- sendToSerialPort:aSerialPort fromStream:(NXStream *)aStream
{
	return [self transferBetweenStream:aStream serialPort:aSerialPort toStream:NO];
}

- init
{
	[super init];
	isText = YES;
	use1kPackets = YES;
	isDynamicCheck = YES;
	useCRC = YES;
	return self;
}

- free
{
	if (isActive) {
		DPSRemovePort (messagePort);
		port_deallocate (task_self(), messagePort);
		port_deallocate (task_self(), replyPort);

		/*
		 * I hope this works.  I wish they had supplied `cthread_terminate'.
		 */
		cthread_abort (xmodemThread);
		cthread_join (xmodemThread);

		if (serialPortWasSuspended == NO)
			[serialPort continue];
	}
	[super free];
	return self;
}

- setFileTypeText:(BOOL)flag		{ isText = flag; return self; }
- xmodemSet1kPackets:(BOOL)flag			{ use1kPackets = flag; return self; }
- xmodemSetCheckTypeDynamic:(BOOL)flag	{ isDynamicCheck = flag; return self; }
- xmodemSetCheckTypeCRC:(BOOL)flag		{ useCRC = flag; return self; }
- (BOOL)xmodemIsActive				{ return isActive; }
- (BOOL)xmodemIsFileTypeText		{ return isText; }
- (BOOL)xmodemIs1kPackets			{ return use1kPackets; }
- (BOOL)xmodemIsCheckTypeDynamic	{ return isDynamicCheck; }
- (BOOL)xmodemIsCheckTypeCRC		{ return useCRC; }

/*
 * Delegate methods
 */
- setDelegate:id
{
	delegate = id;
	return self;
}

- delegate
{
	return delegate;
}

- didTransferPacket:(int)anInt kbytes:(float)aFloat { return self; }
- didRetryNumber:(int)anInt { return self; }
- didFinish:(const char *)failureCode { return self; }

@end
