quakeforge/nq/source/net_ser.c
Bill Currie 1fce1ea12e run indent over all the .c files using qw's .indent.pro. The real reason for
this is I mistakenly did so while making some other changes (which I made sure
were NOT in the checkin:)
2001-02-26 06:48:02 +00:00

947 lines
20 KiB
C

/*
net_ser.c
@description@
Copyright (C) 1996-1997 Id Software, Inc.
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:
Free Software Foundation, Inc.
59 Temple Place - Suite 330
Boston, MA 02111-1307, USA
$Id$
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#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;
}