/* 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; }