/* * Copyright 2006 Serge van den Boom * * 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 */ #define PORT_WANT_ERRNO #include "port.h" #define NETCONNECTION_INTERNAL #include "netplay.h" #include "packethandlers.h" #include "netinput.h" #include "netmisc.h" #include "packetsenders.h" #include "proto/npconfirm.h" #include "proto/ready.h" #include "proto/reset.h" #include "libs/log.h" #include "../../controls.h" // for BATTLE_INPUT_STATE #include "../../init.h" // for NUM_PLAYERS #include "../../globdata.h" // for GLOBAL #include "../melee.h" // for various update functions. #include "../meleeship.h" // for MeleeShip #include "../pickmele.h" // for various update functions. #include "libs/mathlib.h" // for TFB_SeedRandom #include static bool testNetState(bool condition, PacketType type) { if (!condition) { log_add(log_Error, "Packet of type '%s' received from wrong " "state.", packetTypeData[type].name); errno = EBADMSG; } return condition; } static int versionCompare(int major1, int minor1, int patch1, int major2, int minor2, int patch2) { if (major1 < major2) return -1; if (major1 > major2) return 1; if (minor1 < minor2) return -1; if (minor1 > minor2) return 1; if (patch1 < patch2) return -1; if (patch1 > patch2) return 1; return 0; } int PacketHandler_Init(NetConnection *conn, const Packet_Init *packet) { if (conn->stateFlags.reset.localReset) return 0; if (conn->stateFlags.reset.remoteReset) { errno = EBADMSG; return -1; } if (!testNetState(conn->state == NetState_init && !conn->stateFlags.ready.remoteReady, PACKET_INIT)) return -1; // errno is set if (packet->protoVersion.major != NETPLAY_PROTOCOL_VERSION_MAJOR || packet->protoVersion.minor != NETPLAY_PROTOCOL_VERSION_MINOR) { sendAbort (conn, AbortReason_versionMismatch); abortFeedback(conn, AbortReason_versionMismatch); log_add(log_Error, "Protocol version %d.%d not supported.", packet->protoVersion.major, packet->protoVersion.minor); errno = ENOSYS; return -1; } if (versionCompare(packet->uqmVersion.major, packet->uqmVersion.minor, packet->uqmVersion.patch, NETPLAY_MIN_UQM_VERSION_MAJOR, NETPLAY_MIN_UQM_VERSION_MINOR, NETPLAY_MIN_UQM_VERSION_PATCH) < 0) { sendAbort (conn, AbortReason_versionMismatch); abortFeedback(conn, AbortReason_versionMismatch); log_add(log_Error, "Remote side is running a version of UQM that " "is too old (%d.%d.%d; %d.%d.%d is required).", packet->uqmVersion.major, packet->uqmVersion.minor, packet->uqmVersion.patch, NETPLAY_MIN_UQM_VERSION_MAJOR, NETPLAY_MIN_UQM_VERSION_MINOR, NETPLAY_MIN_UQM_VERSION_PATCH); errno = ENOSYS; return -1; } Netplay_remoteReady(conn); return 0; } int PacketHandler_Ping(NetConnection *conn, const Packet_Ping *packet) { if (!testNetState(conn->state > NetState_init, PACKET_PING)) return -1; // errno is set sendAck(conn, packet->id); return 0; } int PacketHandler_Ack(NetConnection *conn, const Packet_Ack *packet) { if (!testNetState(conn->state > NetState_init, PACKET_ACK)) return -1; // errno is set (void) conn; (void) packet; return 0; } // Convert the side indication relative to a remote party to // a local player number. static inline int localSide(NetConnection *conn, NetplaySide side) { if (side == NetplaySide_local) { // "local" relative to the remote party. return conn->player; } return 1 - conn->player; } int PacketHandler_Ready(NetConnection *conn, const Packet_Ready *packet) { if (conn->stateFlags.reset.localReset) return 0; if (conn->stateFlags.reset.remoteReset) { errno = EBADMSG; return -1; } if (!testNetState(readyFlagsMeaningful(conn->state) && !conn->stateFlags.ready.remoteReady, PACKET_READY)) return -1; // errno is set Netplay_remoteReady(conn); (void) packet; // Its contents is not interesting. return 0; } int PacketHandler_Fleet(NetConnection *conn, const Packet_Fleet *packet) { uint16 numShips = ntoh16(packet->numShips); size_t i; size_t len; int player; BattleStateData *battleStateData; if (conn->stateFlags.reset.localReset) return 0; if (conn->stateFlags.reset.remoteReset) { errno = EBADMSG; return -1; } if (!testNetState(conn->state == NetState_inSetup, PACKET_FLEET)) return -1; // errno is set player = localSide(conn, (NetplaySide) packet->side); len = packetLength((const Packet *) packet); if (sizeof packet + numShips * sizeof(packet->ships[0]) > len) { // There is not enough room in the packet to contain all // the ships it says it contains. log_add(log_Warning, "Invalid fleet size. Specified size is %d, " "actual size = %d", numShips, (int) ((len - sizeof packet) / sizeof(packet->ships[0]))); errno = EBADMSG; return -1; } battleStateData = (BattleStateData *) NetConnection_getStateData(conn); if (conn->stateFlags.handshake.localOk) { Netplay_cancelConfirmation(conn); confirmationCancelled(battleStateData->meleeState, conn->player); } for (i = 0; i < numShips; i++) { MeleeShip ship = (MeleeShip) packet->ships[i].ship; FleetShipIndex index = (FleetShipIndex) packet->ships[i].index; if (!MeleeShip_valid(ship)) { log_add (log_Warning, "Invalid ship type number %d (max = %d).\n", ship, NUM_MELEE_SHIPS - 1); errno = EBADMSG; return -1; } if (index >= MELEE_FLEET_SIZE) { log_add (log_Warning, "Invalid ship position number %d " "(max = %d).\n", index, MELEE_FLEET_SIZE - 1); errno = EBADMSG; return -1; } Melee_RemoteChange_ship (battleStateData->meleeState, conn, player, index, ship); } // Padding data may follow; it is ignored. return 0; } int PacketHandler_TeamName(NetConnection *conn, const Packet_TeamName *packet) { size_t nameLen; int side; BattleStateData *battleStateData; if (conn->stateFlags.reset.localReset) return 0; if (conn->stateFlags.reset.remoteReset) { errno = EBADMSG; return -1; } if (!testNetState(conn->state == NetState_inSetup, PACKET_FLEET)) return -1; // errno is set battleStateData = (BattleStateData *) NetConnection_getStateData(conn); if (conn->stateFlags.handshake.localOk) { Netplay_cancelConfirmation(conn); confirmationCancelled(battleStateData->meleeState, conn->player); } side = localSide(conn, (NetplaySide) packet->side); nameLen = packetLength((const Packet *) packet) - sizeof (Packet_TeamName) - 1; // The -1 is for not counting the terminating '\0'. { char buf[MAX_TEAM_CHARS + 1]; if (nameLen > MAX_TEAM_CHARS) nameLen = MAX_TEAM_CHARS; memcpy (buf, (const char *) packet->name, nameLen); buf[nameLen] = '\0'; Melee_RemoteChange_teamName(battleStateData->meleeState, conn, side, buf); } // Padding data may follow; it is ignored. return 0; } static void handshakeComplete(NetConnection *conn) { assert(!conn->stateFlags.handshake.localOk); assert(!conn->stateFlags.handshake.remoteOk); assert(!conn->stateFlags.handshake.canceling); assert(conn->state == NetState_inSetup); NetConnection_setState(conn, NetState_preBattle); } int PacketHandler_Handshake0(NetConnection *conn, const Packet_Handshake0 *packet) { if (conn->stateFlags.reset.localReset) return 0; if (conn->stateFlags.reset.remoteReset) { errno = EBADMSG; return -1; } if (!testNetState(handshakeMeaningful(conn->state) && !conn->stateFlags.handshake.remoteOk, PACKET_HANDSHAKE0)) return -1; // errno is set conn->stateFlags.handshake.remoteOk = true; if (conn->stateFlags.handshake.localOk && !conn->stateFlags.handshake.canceling) sendHandshake1(conn); (void) packet; // Its contents is not interesting. return 0; } int PacketHandler_Handshake1(NetConnection *conn, const Packet_Handshake1 *packet) { if (conn->stateFlags.reset.localReset) return 0; if (conn->stateFlags.reset.remoteReset) { errno = EBADMSG; return -1; } if (!testNetState(handshakeMeaningful(conn->state) && (conn->stateFlags.handshake.localOk || conn->stateFlags.handshake.canceling), PACKET_HANDSHAKE1)) return -1; // errno is set if (conn->stateFlags.handshake.canceling) { conn->stateFlags.handshake.remoteOk = true; } else { bool remoteWasOk = conn->stateFlags.handshake.remoteOk; conn->stateFlags.handshake.localOk = false; conn->stateFlags.handshake.remoteOk = false; if (!remoteWasOk) { // Received Handshake1 without prior Handshake0. // A Handshake0 is implied, but we still need to confirm // it with a Handshake1. sendHandshake1(conn); } handshakeComplete(conn); } (void) packet; // Its contents is not interesting. return 0; } int PacketHandler_HandshakeCancel(NetConnection *conn, const Packet_HandshakeCancel *packet) { if (conn->stateFlags.reset.localReset) return 0; if (conn->stateFlags.reset.remoteReset) { errno = EBADMSG; return -1; } if (!testNetState(handshakeMeaningful(conn->state) && conn->stateFlags.handshake.remoteOk, PACKET_HANDSHAKECANCEL)) return -1; // errno is set conn->stateFlags.handshake.remoteOk = false; sendHandshakeCancelAck(conn); (void) packet; // Its contents is not interesting. return 0; } int PacketHandler_HandshakeCancelAck(NetConnection *conn, const Packet_HandshakeCancelAck *packet) { if (conn->stateFlags.reset.localReset) return 0; if (conn->stateFlags.reset.remoteReset) { errno = EBADMSG; return -1; } if (!testNetState(handshakeMeaningful(conn->state) && conn->stateFlags.handshake.canceling, PACKET_HANDSHAKECANCELACK)) return -1; // errno is set conn->stateFlags.handshake.canceling = false; if (conn->stateFlags.handshake.localOk) { if (conn->stateFlags.handshake.remoteOk) { sendHandshake1(conn); } else sendHandshake0(conn); } (void) packet; // Its contents is not interesting. return 0; } int PacketHandler_SeedRandom(NetConnection *conn, const Packet_SeedRandom *packet) { BattleStateData *battleStateData; if (conn->stateFlags.reset.localReset) return 0; if (conn->stateFlags.reset.remoteReset) { errno = EBADMSG; return -1; } if (!testNetState(conn->state == NetState_preBattle && !conn->stateFlags.discriminant, PACKET_SEEDRANDOM)) return -1; // errno is set battleStateData = (BattleStateData *) NetConnection_getStateData(conn); updateRandomSeed (battleStateData->meleeState, conn->player, ntoh32(packet->seed)); conn->stateFlags.agreement.randomSeed = true; return 0; } int PacketHandler_InputDelay(NetConnection *conn, const Packet_InputDelay *packet) { BattleStateData *battleStateData; uint32 delay; if (conn->stateFlags.reset.localReset) return 0; if (conn->stateFlags.reset.remoteReset) { errno = EBADMSG; return -1; } if (!testNetState(conn->state == NetState_preBattle, PACKET_INPUTDELAY)) return -1; // errno is set battleStateData = (BattleStateData *) NetConnection_getStateData(conn); delay = ntoh32(packet->delay); if (delay > BATTLE_FRAME_RATE) { log_add(log_Error, "NETPLAY: [%d] Received absurdly large " "input delay value (%d).", conn->player, delay); return -1; } conn->stateFlags.inputDelay = delay; return 0; } int PacketHandler_SelectShip(NetConnection *conn, const Packet_SelectShip *packet) { bool updateResult; BattleStateData *battleStateData; if (conn->stateFlags.reset.localReset) return 0; if (conn->stateFlags.reset.remoteReset) { errno = EBADMSG; return -1; } if (!testNetState(conn->state == NetState_selectShip, PACKET_SELECTSHIP)) return -1; // errno is set battleStateData = (BattleStateData *) NetConnection_getStateData(conn); updateResult = updateMeleeSelection(battleStateData->getMeleeState, conn->player, ntoh16(packet->ship)); if (!updateResult) { errno = EBADMSG; return -1; } return 0; } int PacketHandler_BattleInput(NetConnection *conn, const Packet_BattleInput *packet) { BATTLE_INPUT_STATE input; BattleInputBuffer *bib; if (conn->stateFlags.reset.localReset) return 0; if (conn->stateFlags.reset.remoteReset) { errno = EBADMSG; return -1; } if (!testNetState(conn->state == NetState_inBattle || conn->state == NetState_endingBattle || conn->state == NetState_endingBattle2, PACKET_BATTLEINPUT)) return -1; // errno is set input = (BATTLE_INPUT_STATE) packet->state; bib = getBattleInputBuffer(conn->player); if (!BattleInputBuffer_push(bib, input)) { // errno is set return -1; } return 0; } int PacketHandler_FrameCount(NetConnection *conn, const Packet_FrameCount *packet) { BattleStateData *battleStateData; BattleFrameCounter frameCount; if (conn->stateFlags.reset.localReset) return 0; if (conn->stateFlags.reset.remoteReset) { errno = EBADMSG; return -1; } if (!testNetState(conn->state == NetState_endingBattle, PACKET_FRAMECOUNT)) return -1; // errno is set frameCount = (BattleFrameCounter) ntoh32(packet->frameCount); #ifdef NETPLAY_DEBUG log_add(log_Debug, "NETPLAY: [%d] <== Received battleFrameCount %u.", conn->player, (unsigned int) frameCount); #endif battleStateData = (BattleStateData *) NetConnection_getStateData(conn); if (frameCount > battleStateData->endFrameCount) battleStateData->endFrameCount = frameCount; Netplay_remoteReady(conn); return 0; } int PacketHandler_Checksum(NetConnection *conn, const Packet_Checksum *packet) { #ifdef NETPLAY_CHECKSUM uint32 frameNr; uint32 checksum; size_t delay; size_t interval; #endif if (conn->stateFlags.reset.localReset) return 0; if (conn->stateFlags.reset.remoteReset) { errno = EBADMSG; return -1; } if (!testNetState(NetState_battleActive(conn->state), PACKET_CHECKSUM)) return -1; // errno is set #ifdef NETPLAY_CHECKSUM frameNr = ntoh32(packet->frameNr); checksum = ntoh32(packet->checksum); interval = NetConnection_getChecksumInterval(conn); delay = getBattleInputDelay(); if (frameNr % interval != 0) { log_add(log_Warning, "NETPLAY: [%d] <== Received checksum " "for frame %u, while we only expect checksums on frames " "divisable by %u -- discarding.", conn->player, (unsigned int) frameNr, (unsigned int) interval); return 0; // No need to close the connection; checksums are not // essential. } // The checksum is sent at the beginning of a frame. // If we're in frame n and have sent our input already, // the remote side has got enough input to progress delay + 1 frames from // frame n. The next frame is then n + delay + 1, for which we can // receive a checksum. if (frameNr > battleFrameCount + delay + 1) { log_add(log_Warning, "NETPLAY: [%d] <== Received checksum " "for a frame too far in the future (frame %u, current " "is %u, input delay is %u) -- discarding.", conn->player, (unsigned int) frameNr, (unsigned int) battleFrameCount, (unsigned int) delay); return 0; // No need to close the connection; checksums are not // essential. } // We can progress delay more frames after the last frame for which we // received input. If we call that frame n, we can complete frames // n through n + delay - 1. While we are waiting for the next input, // in frame n + delay, we will first receive the checksum that the // remote side sent at the start of frame n + 1. // In this situation frameNr is n + 1, and battleFrameCount is // n + delay. if (frameNr + delay < battleFrameCount) { log_add(log_Warning, "NETPLAY: [%d] <== Received checksum " "for a frame too far in the past (frame %u, current " "is %u, input delay is %u) -- discarding.", conn->player, (unsigned int) frameNr, (unsigned int) battleFrameCount, (unsigned int) delay); return 0; // No need to close the connection; checksums are not // essential. } addRemoteChecksum(conn, frameNr, checksum); #endif #ifndef NETPLAY_CHECKSUM (void) packet; #endif return 0; } int PacketHandler_Abort(NetConnection *conn, const Packet_Abort *packet) { abortFeedback(conn, packet->reason); return -1; // Close connection. } int PacketHandler_Reset(NetConnection *conn, const Packet_Reset *packet) { NetplayResetReason reason; if (!testNetState(!conn->stateFlags.reset.remoteReset, PACKET_RESET)) return -1; // errno is set reason = ntoh16(packet->reason); Netplay_remoteReset(conn, reason); return 0; }