/*
Copyright (C) 1996-1997 Id Software, Inc.
Portions Copyright (C) 1999,2000  Nelson Rush.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/
// net_ser.c

#include "quakedef.h"
#include "net_ser.h"
#include "dosisms.h"
#include "crc.h"

#include "net_comx.c"

// serial protocol

#define SERIAL_PROTOCOL_VERSION 3

// The serial protocol is message oriented.  The high level message format is
// a one byte message type (MTYPE_xxx), data, and a 16-bit checksum.  All
// multi-byte fields are sent in network byte order.  There are currently 4
// MTYPEs defined.  Their formats are as follows:
//
// MTYPE_RELIABLE     sequence      data_length   data       checksum   eom
// MTYPE_UNRELIABLE   sequence      data_length   data       checksum   eom
// MTYPE_ACK          sequence      checksum      eom
// MTYPE_CONTROL      data_length   data          checksum   eom
//
// sequence is an 8-bit unsigned value starting from 0
// data_length is a 16-bit unsigned value; it is the length of the data only
// the checksum is a 16-bit value.  the CRC formula used is defined in crc.h.
//              the checksum covers the entire messages, excluding itself
// eom is a special 2 byte sequence used to mark the End Of Message.  This is
//              needed for error recovery.
//
// A lot of behavior is based on knowledge of the upper level Quake network
// layer.  For example, only one reliable message can be outstanding (pending
// reception of an MTYPE_ACK) at a time.
//
// The low level routines used to communicate with the modem are not part of
// this protocol.
//
// The CONTROL messages are only used for session establishment.  They are
// not reliable or sequenced.

#define MTYPE_RELIABLE			0x01
#define MTYPE_UNRELIABLE		0x02
#define MTYPE_CONTROL			0x03
#define MTYPE_ACK				0x04
#define MTYPE_CLIENT			0x80

#define ESCAPE_COMMAND			0xe0
#define ESCAPE_EOM				0x19

static qboolean listening = false;


typedef struct SerialLine_s
{
	struct SerialLine_s	*next;
	qsocket_t			*sock;
	int					lengthStated;
	int					lengthFound;
	int					tty;
	qboolean			connected;
	qboolean			connecting;
	qboolean			client;
	double				connect_time;
	unsigned short		crcStated;
	unsigned short		crcValue;
	byte				currState;
	byte				prevState;
	byte				mtype;
	byte				sequence;
} SerialLine;

#define STATE_READY		0
#define STATE_SEQUENCE	1
#define STATE_LENGTH1	2
#define STATE_LENGTH2	3
#define STATE_DATA		4
#define STATE_CRC1		5
#define STATE_CRC2		6
#define STATE_EOM		7
#define STATE_ESCAPE	8
#define STATE_ABORT		9

SerialLine serialLine[NUM_COM_PORTS];

int myDriverLevel;

static void Serial_SendACK (SerialLine *p, byte sequence);


static void ResetSerialLineProtocol (SerialLine *p)
{
	p->connected = false;
	p->connecting = false;
	p->currState = STATE_READY;
	p->prevState = STATE_READY;
	p->lengthFound = 0;
}


static int ProcessInQueue(SerialLine *p)
{
	int	b;

	while (1)
	{
		b = TTY_ReadByte(p->tty);
		if (b == ERR_TTY_NODATA)
			break;

		if (b == ERR_TTY_LINE_STATUS)
		{
			p->currState = STATE_ABORT;
			continue;
		}
		if (b == ERR_TTY_MODEM_STATUS)
		{
			p->currState = STATE_ABORT;
			return -1;
		}

		if (b == ESCAPE_COMMAND)
			if (p->currState != STATE_ESCAPE)
			{
				p->prevState = p->currState;
				p->currState = STATE_ESCAPE;
				continue;
			}

		if (p->currState == STATE_ESCAPE)
		{
			if (b == ESCAPE_EOM)
			{
				if (p->prevState == STATE_ABORT)
				{
					p->currState = STATE_READY;
					p->lengthFound = 0;
					continue;
				}

				if (p->prevState != STATE_EOM)
				{
					p->currState = STATE_READY;
					p->lengthFound = 0;
					Con_DPrintf("Serial: premature EOM\n");
					continue;
				}

				switch (p->mtype)
				{
					case MTYPE_RELIABLE:
						Con_DPrintf("Serial: sending ack %u\n", p->sequence);
						Serial_SendACK (p, p->sequence);
						if (p->sequence == p->sock->receiveSequence)
						{
							p->sock->receiveSequence = (p->sequence + 1) & 0xff;
							p->sock->receiveMessageLength += p->lengthFound;
						}
						else
							Con_DPrintf("Serial: reliable out of order; got %u wanted %u\n", p->sequence, p->sock->receiveSequence);
						break;

					case MTYPE_UNRELIABLE:
						p->sock->unreliableReceiveSequence = (p->sequence + 1) & 0xff;
						p->sock->receiveMessageLength += p->lengthFound;
						break;

					case MTYPE_ACK:
						Con_DPrintf("Serial: got ack %u\n", p->sequence);
						if (p->sequence == p->sock->sendSequence)
						{
							p->sock->sendSequence = (p->sock->sendSequence + 1) & 0xff;
							p->sock->canSend = true;
						}
						else
							Con_DPrintf("Serial: ack out of order; got %u wanted %u\n",p->sequence, p->sock->sendSequence);
						break;

					case MTYPE_CONTROL:
						p->sock->receiveMessageLength += p->lengthFound;
						break;
					}

				p->currState = STATE_READY;
				p->lengthFound = 0;
				continue;
			}


			if (b != ESCAPE_COMMAND)
			{
				p->currState = STATE_ABORT;
				Con_DPrintf("Serial: Bad escape sequence\n");
				continue;
			}

			// b == ESCAPE_COMMAND
			p->currState = p->prevState;
		}

		p->prevState = p->currState;

//DEBUG
		if (p->sock->receiveMessageLength + p->lengthFound > NET_MAXMESSAGE)
		{
			Con_DPrintf("Serial blew out receive buffer: %u\n", p->sock->receiveMessageLength + p->lengthFound);
			p->currState = STATE_ABORT;
		}
		if (p->sock->receiveMessageLength + p->lengthFound == NET_MAXMESSAGE)
		{
			Con_DPrintf("Serial hit receive buffer limit: %u\n", p->sock->receiveMessageLength + p->lengthFound);
			p->currState = STATE_ABORT;
		}
//end DEBUG

		switch (p->currState)
		{
			case STATE_READY:
				CRC_Init(&p->crcValue);
				CRC_ProcessByte(&p->crcValue, b);
				if (p->client)
				{
					if ((b & MTYPE_CLIENT) != 0)
					{
						p->currState = STATE_ABORT;
						Con_DPrintf("Serial: client got own message\n");
						break;
					}
				}
				else
				{
					if ((b & MTYPE_CLIENT) == 0)
					{
						p->currState = STATE_ABORT;
						Con_DPrintf("Serial: server got own message\n");
						break;
					}
					b &= 0x7f;
				}
				p->mtype = b;
				if (b != MTYPE_CONTROL)
					p->currState = STATE_SEQUENCE;
				else
					p->currState = STATE_LENGTH1;
				if (p->mtype < MTYPE_ACK)
				{
					p->sock->receiveMessage[p->sock->receiveMessageLength] = b;
					p->lengthFound++;
				}
				break;

			case STATE_SEQUENCE:
				p->sequence = b;
				CRC_ProcessByte(&p->crcValue, b);
				if (p->mtype != MTYPE_ACK)
					p->currState = STATE_LENGTH1;
				else
					p->currState = STATE_CRC1;
				break;

			case STATE_LENGTH1:
				p->lengthStated = b * 256;
				CRC_ProcessByte(&p->crcValue, b);
				p->currState = STATE_LENGTH2;
				break;

			case STATE_LENGTH2:
				p->lengthStated += b;
				CRC_ProcessByte(&p->crcValue, b);
				if (p->mtype == MTYPE_RELIABLE && p->lengthStated > MAX_MSGLEN)
				{
					p->currState = STATE_ABORT;
					Con_DPrintf("Serial: bad reliable message length %u\n", p->lengthStated);
				}
				else if (p->mtype == MTYPE_UNRELIABLE && p->lengthStated > MAX_DATAGRAM)
				{
					p->currState = STATE_ABORT;
					Con_DPrintf("Serial: bad unreliable message length %u\n", p->lengthStated);
				}
				else
				{
					p->currState = STATE_DATA;
					if (p->mtype < MTYPE_ACK)
					{
						*(short *)&p->sock->receiveMessage [p->sock->receiveMessageLength + 1] = p->lengthStated;
						p->lengthFound += 2;
					}
				}
				break;

			case STATE_DATA:
				p->sock->receiveMessage[p->sock->receiveMessageLength + p->lengthFound] = b;
				p->lengthFound++;
				CRC_ProcessByte(&p->crcValue, b);
				if (p->lengthFound == p->lengthStated + 3)
					p->currState = STATE_CRC1;
				break;

			case STATE_CRC1:
				p->crcStated = b * 256;
				p->currState = STATE_CRC2;
				break;

			case STATE_CRC2:
				p->crcStated += b;
				if (p->crcStated == CRC_Value(p->crcValue))
				{
					p->currState = STATE_EOM;
				}
				else
				{
					p->currState = STATE_ABORT;
					Con_DPrintf("Serial: Bad crc\n");
				}
				break;

			case STATE_EOM:
				p->currState = STATE_ABORT;
				Con_DPrintf("Serial: Bad message format\n");
				break;

			case STATE_ABORT:
				break;
		}
	}
	return 0;
}


int Serial_Init (void)
{
	int     n;

// LATER do Win32 serial support
#ifdef	_WIN32
	return -1;
#endif

	if (COM_CheckParm("-nolan"))
		return -1;
	if (COM_CheckParm ("-noserial"))
		return -1;

	myDriverLevel = net_driverlevel;

	if (TTY_Init())
		return -1;

	for (n = 0; n < NUM_COM_PORTS; n++)
	{
		serialLine[n].tty = TTY_Open(n);
		ResetSerialLineProtocol (&serialLine[n]);
	}

	Con_Printf("Serial driver initialized\n");
	serialAvailable = true;

	return 0;
}


void Serial_Shutdown (void)
{
	int     n;

	for (n = 0; n < NUM_COM_PORTS; n++)
	{
		if (serialLine[n].connected)
			Serial_Close(serialLine[n].sock);
	}

	TTY_Shutdown();
}


void Serial_Listen (qboolean state)
{
	listening = state;
}


qboolean Serial_CanSendMessage (qsocket_t *sock)
{
	return sock->canSend;
}


qboolean Serial_CanSendUnreliableMessage (qsocket_t *sock)
{
	return TTY_OutputQueueIsEmpty(((SerialLine *)sock->driverdata)->tty);
}


int Serial_SendMessage (qsocket_t *sock, sizebuf_t *message)
{
	SerialLine *p;
	int n;
	unsigned short crc;
	byte b;

	p = (SerialLine *)sock->driverdata;
	CRC_Init (&crc);

	// message type
	b = MTYPE_RELIABLE;
	if (p->client)
		b |= MTYPE_CLIENT;
	TTY_WriteByte(p->tty, b);
	CRC_ProcessByte (&crc, b);

	// sequence
	b = p->sock->sendSequence;
	TTY_WriteByte(p->tty, b);
	if (b == ESCAPE_COMMAND)
		TTY_WriteByte(p->tty, b);
	CRC_ProcessByte (&crc, b);

	// data length
	b = message->cursize >> 8;
	TTY_WriteByte(p->tty, b);
	if (b == ESCAPE_COMMAND)
		TTY_WriteByte(p->tty, b);
	CRC_ProcessByte (&crc, b);
	b = message->cursize & 0xff;
	TTY_WriteByte(p->tty, b);
	if (b == ESCAPE_COMMAND)
		TTY_WriteByte(p->tty, b);
	CRC_ProcessByte (&crc, b);

	// data
	for (n = 0; n < message->cursize; n++)
	{
		b = message->data[n];
		TTY_WriteByte(p->tty, b);
		if (b == ESCAPE_COMMAND)
			TTY_WriteByte(p->tty, b);
		CRC_ProcessByte (&crc, b);
	}

	// checksum
	b = CRC_Value (crc) >> 8;
	TTY_WriteByte(p->tty, b);
	if (b == ESCAPE_COMMAND)
		TTY_WriteByte(p->tty, b);
	b = CRC_Value (crc) & 0xff;
	TTY_WriteByte(p->tty, b);
	if (b == ESCAPE_COMMAND)
		TTY_WriteByte(p->tty, b);

	// end of message
	TTY_WriteByte(p->tty, ESCAPE_COMMAND);
	TTY_WriteByte(p->tty, ESCAPE_EOM);

	TTY_Flush(p->tty);

	// mark sock as busy and save the message for possible retransmit
	sock->canSend = false;
	Q_memcpy(sock->sendMessage, message->data, message->cursize);
	sock->sendMessageLength = message->cursize;
	sock->lastSendTime = net_time;

	return 1;
}


static void ReSendMessage (qsocket_t *sock)
{
	sizebuf_t       temp;

	Con_DPrintf("Serial: re-sending reliable\n");
	temp.data = sock->sendMessage;
	temp.maxsize = sock->sendMessageLength;
	temp.cursize = sock->sendMessageLength;
	Serial_SendMessage (sock, &temp);
}


int Serial_SendUnreliableMessage (qsocket_t *sock, sizebuf_t *message)
{
	SerialLine *p;
	int n;
	unsigned short crc;
	byte b;

	p = (SerialLine *)sock->driverdata;

	if (!TTY_OutputQueueIsEmpty(p->tty))
	{
		TTY_Flush(p->tty);
		return 1;
	}

	CRC_Init (&crc);

	// message type
	b = MTYPE_UNRELIABLE;
	if (p->client)
		b |= MTYPE_CLIENT;
	TTY_WriteByte(p->tty, b);
	CRC_ProcessByte (&crc, b);

	// sequence
	b = p->sock->unreliableSendSequence;
	p->sock->unreliableSendSequence = (b + 1) & 0xff;
	TTY_WriteByte(p->tty, b);
	if (b == ESCAPE_COMMAND)
		TTY_WriteByte(p->tty, b);
	CRC_ProcessByte (&crc, b);

	// data length
	b = message->cursize >> 8;
	TTY_WriteByte(p->tty, b);
	if (b == ESCAPE_COMMAND)
		TTY_WriteByte(p->tty, b);
	CRC_ProcessByte (&crc, b);
	b = message->cursize & 0xff;
	TTY_WriteByte(p->tty, b);
	if (b == ESCAPE_COMMAND)
		TTY_WriteByte(p->tty, b);
	CRC_ProcessByte (&crc, b);

	// data
	for (n = 0; n < message->cursize; n++)
	{
		b = message->data[n];
		TTY_WriteByte(p->tty, b);
		if (b == ESCAPE_COMMAND)
			TTY_WriteByte(p->tty, b);
		CRC_ProcessByte (&crc, b);
	}

	// checksum
	b = CRC_Value (crc) >> 8;
	TTY_WriteByte(p->tty, b);
	if (b == ESCAPE_COMMAND)
		TTY_WriteByte(p->tty, b);
	b = CRC_Value (crc) & 0xff;
	TTY_WriteByte(p->tty, b);
	if (b == ESCAPE_COMMAND)
		TTY_WriteByte(p->tty, b);

	// end of message
	TTY_WriteByte(p->tty, ESCAPE_COMMAND);
	TTY_WriteByte(p->tty, ESCAPE_EOM);

	TTY_Flush(p->tty);

	return 1;
}


static void Serial_SendACK (SerialLine *p, byte sequence)
{
	unsigned short crc;
	byte b;

	CRC_Init (&crc);

	// message type
	b = MTYPE_ACK;
	if (p->client)
		b |= MTYPE_CLIENT;
	TTY_WriteByte(p->tty, b);
	CRC_ProcessByte (&crc, b);

	// sequence
	b = sequence;
	TTY_WriteByte(p->tty, b);
	if (b == ESCAPE_COMMAND)
		TTY_WriteByte(p->tty, b);
	CRC_ProcessByte (&crc, b);

	// checksum
	b = CRC_Value (crc) >> 8;
	TTY_WriteByte(p->tty, b);
	if (b == ESCAPE_COMMAND)
		TTY_WriteByte(p->tty, b);
	b = CRC_Value (crc) & 0xff;
	TTY_WriteByte(p->tty, b);
	if (b == ESCAPE_COMMAND)
		TTY_WriteByte(p->tty, b);

	// end of message
	TTY_WriteByte(p->tty, ESCAPE_COMMAND);
	TTY_WriteByte(p->tty, ESCAPE_EOM);

	TTY_Flush(p->tty);
}


static void Serial_SendControlMessage (SerialLine *p, sizebuf_t *message)
{
	unsigned short crc;
	int n;
	byte b;

	CRC_Init (&crc);

	// message type
	b = MTYPE_CONTROL;
	if (p->client)
		b |= MTYPE_CLIENT;
	TTY_WriteByte(p->tty, b);
	CRC_ProcessByte (&crc, b);

	// data length
	b = message->cursize >> 8;
	TTY_WriteByte(p->tty, b);
	if (b == ESCAPE_COMMAND)
		TTY_WriteByte(p->tty, b);
	CRC_ProcessByte (&crc, b);
	b = message->cursize & 0xff;
	TTY_WriteByte(p->tty, b);
	if (b == ESCAPE_COMMAND)
		TTY_WriteByte(p->tty, b);
	CRC_ProcessByte (&crc, b);

	// data
	for (n = 0; n < message->cursize; n++)
	{
		b = message->data[n];
		TTY_WriteByte(p->tty, b);
		if (b == ESCAPE_COMMAND)
			TTY_WriteByte(p->tty, b);
		CRC_ProcessByte (&crc, b);
	}

	// checksum
	b = CRC_Value (crc) >> 8;
	TTY_WriteByte(p->tty, b);
	if (b == ESCAPE_COMMAND)
		TTY_WriteByte(p->tty, b);
	b = CRC_Value (crc) & 0xff;
	TTY_WriteByte(p->tty, b);
	if (b == ESCAPE_COMMAND)
		TTY_WriteByte(p->tty, b);

	// end of message
	TTY_WriteByte(p->tty, ESCAPE_COMMAND);
	TTY_WriteByte(p->tty, ESCAPE_EOM);

	TTY_Flush(p->tty);
}


static int _Serial_GetMessage (SerialLine *p)
{
	byte	ret;
	short	length;

	if (ProcessInQueue(p))
		return -1;

	if (p->sock->receiveMessageLength == 0)
		return 0;

	ret = p->sock->receiveMessage[0];
	length = *(short *)&p->sock->receiveMessage[1];
	if (ret == MTYPE_CONTROL)
		ret = 1;

	SZ_Clear (&net_message);
	SZ_Write (&net_message, &p->sock->receiveMessage[3], length);

	length += 3;
	p->sock->receiveMessageLength -= length;

	if (p->sock->receiveMessageLength + p->lengthFound)
		Q_memcpy(p->sock->receiveMessage, &p->sock->receiveMessage[length], p->sock->receiveMessageLength + p->lengthFound);

	return ret;
}

int Serial_GetMessage (qsocket_t *sock)
{
	SerialLine *p;
	int		ret;

	p = (SerialLine *)sock->driverdata;

	ret = _Serial_GetMessage (p);

	if (ret == 1)
		messagesReceived++;

	if (!sock->canSend)
		if ((net_time - sock->lastSendTime) > 1.0)
		{
			ReSendMessage (sock);
			sock->lastSendTime = net_time;
		}

	return ret;
}


void Serial_Close (qsocket_t *sock)
{
	SerialLine *p = (SerialLine *)sock->driverdata;
	TTY_Close(p->tty);
	ResetSerialLineProtocol (p);
}


char *com_types[] = {"direct", "modem"};
unsigned com_bauds[] = {9600, 14400, 19200, 28800, 57600};

void Serial_SearchForHosts (qboolean xmit)
{
	int		n;
	SerialLine *p;

	if (sv.active)
		return;

	if (hostCacheCount == HOSTCACHESIZE)
		return;

	// see if we've already answered
	for (n = 0; n < hostCacheCount; n++)
		if (Q_strcmp (hostcache[n].cname, "#") == 0)
			return;

	for (n = 0; n < NUM_COM_PORTS; n++)
		if (TTY_IsEnabled(n))
			break;
	if (n == NUM_COM_PORTS)
		return;
	p = &serialLine[n];

	if (TTY_IsModem(p->tty))
		return;

	snprintf(hostcache[hostCacheCount].name, sizeof(hostcache[hostCacheCount].name), "COM%u", n+1);
	Q_strcpy(hostcache[hostCacheCount].map, "");
	hostcache[hostCacheCount].users = 0;
	hostcache[hostCacheCount].maxusers = 0;
	hostcache[hostCacheCount].driver = net_driverlevel;
	Q_strcpy(hostcache[hostCacheCount].cname, "#");
	hostCacheCount++;

	return;
}


static qsocket_t *_Serial_Connect (char *host, SerialLine *p)
{
	int		ret;
	double	start_time;
	double	last_time;

	p->client = true;
	if (TTY_Connect(p->tty, host))
		return NULL;

	p->sock = NET_NewQSocket ();
	p->sock->driver = myDriverLevel;
	if (p->sock == NULL)
	{
		Con_Printf("No sockets available\n");
		return NULL;
	}
	p->sock->driverdata = p;

	// send the connection request
	start_time = SetNetTime();
	last_time = 0.0;

	SZ_Clear(&net_message);
	MSG_WriteByte(&net_message, CCREQ_CONNECT);
	MSG_WriteString(&net_message, "QUAKE");
	do
	{
		SetNetTime();
		if ((net_time - last_time) >= 1.0)
		{
			Serial_SendControlMessage (p, &net_message);
			last_time = net_time;
			Con_Printf("trying...\n"); SCR_UpdateScreen ();
		}
		ret = _Serial_GetMessage (p);
	}
	while (ret == 0 && (net_time - start_time) < 5.0);

	if (ret == 0)
	{
		Con_Printf("Unable to connect, no response\n");
		goto ErrorReturn;
	}

	if (ret == -1)
	{
		Con_Printf("Connection request error\n");
		goto ErrorReturn;
	}

	MSG_BeginReading ();
	ret = MSG_ReadByte();
	if (ret == CCREP_REJECT)
	{
		Con_Printf(MSG_ReadString());
		goto ErrorReturn;
	}
	if (ret != CCREP_ACCEPT)
	{
		Con_Printf("Unknown connection response\n");
		goto ErrorReturn;
	}

	p->connected = true;
	p->sock->lastMessageTime = net_time;

	Con_Printf ("Connection accepted\n");

	return p->sock;

ErrorReturn:
	TTY_Disconnect(p->tty);
	return NULL;
}

qsocket_t *Serial_Connect (char *host)
{
	int			n;
	qsocket_t	*ret = NULL;

	// see if this looks like a phone number
	if (*host == '#')
		host++;
	for (n = 0; n < Q_strlen(host); n++)
		if (host[n] == '.' || host[n] == ':')
			return NULL;

	for (n = 0; n < NUM_COM_PORTS; n++)
		if (TTY_IsEnabled(n) && !serialLine[n].connected)
			if ((ret = _Serial_Connect (host, &serialLine[n])))
				break;
	return ret;
}


static qsocket_t *_Serial_CheckNewConnections (SerialLine *p)
{
	int	command;

	p->client = false;
	if (!TTY_CheckForConnection(p->tty))
		return NULL;

	if (TTY_IsModem(p->tty))
	{
		if (!p->connecting)
		{
			p->connecting = true;
			p->connect_time = net_time;
		}
		else if ((net_time - p->connect_time) > 15.0)
		{
			p->connecting = false;
			TTY_Disconnect(p->tty);
			return NULL;
		}
	}

	p->sock = NET_NewQSocket ();
	p->sock->driver = myDriverLevel;
	if (p->sock == NULL)
	{
		Con_Printf("No sockets available\n");
		return NULL;
	}
	p->sock->driverdata = p;

	SZ_Clear(&net_message);
	if (_Serial_GetMessage(p) != 1)
	{
		NET_FreeQSocket(p->sock);
		return NULL;
	}

	MSG_BeginReading ();
	command = MSG_ReadByte();

	if (command == CCREQ_SERVER_INFO)
	{
		if (Q_strcmp(MSG_ReadString(), "QUAKE") != 0)
			return NULL;

		if (MSG_ReadByte() != SERIAL_PROTOCOL_VERSION)
			return NULL;

		SZ_Clear(&net_message);
		MSG_WriteByte(&net_message, CCREP_SERVER_INFO);
		MSG_WriteString(&net_message, hostname.string);
		MSG_WriteString(&net_message, sv.name);
		MSG_WriteByte(&net_message, net_activeconnections);
		MSG_WriteByte(&net_message, svs.maxclients);
		Serial_SendControlMessage (p, &net_message);
		SZ_Clear(&net_message);
		return NULL;
	}

	if (command != CCREQ_CONNECT)
		return NULL;

	if (Q_strcmp(MSG_ReadString(), "QUAKE") != 0)
		return NULL;

	// send him back the info about the server connection he has been allocated
	SZ_Clear(&net_message);
	MSG_WriteByte(&net_message, CCREP_ACCEPT);
	Serial_SendControlMessage (p, &net_message);
	SZ_Clear(&net_message);

	p->connected = true;
	p->connecting = false;
	p->sock->lastMessageTime = net_time;
	snprintf(p->sock->address, sizeof(p->sock->address), "COM%u", (int)((p - serialLine) + 1));

	return p->sock;
}

qsocket_t *Serial_CheckNewConnections (void)
{
	int			n;
	qsocket_t	*ret = NULL;

	for (n = 0; n < NUM_COM_PORTS; n++)
		if (TTY_IsEnabled(n) && !serialLine[n].connected)
			if ((ret = _Serial_CheckNewConnections (&serialLine[n])))
				break;
	return ret;
}
