/*
 * 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 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;
    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)
        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 (!strcmp (hostcache[n].cname, "#"))
            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;

    sprintf (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 < 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 (strcmp (MSG_ReadString (), "QUAKE"))
            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 (strcmp (MSG_ReadString (), "QUAKE"))
        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;
    sprintf (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;
}
