# HG changeset patch # User Teemu Piippo # Date 1469266445 -10800 # Node ID febc3ed5435c1d2791e5d48c1b05f0911c398c69 # Parent 0150f86e68f02653a9aa1625d255c888d5ca79b8# Parent 96ffd13c08a261717e860126d33ac5eb4826b71d Merged with default diff -r 96ffd13c08a2 -r febc3ed5435c CMakeLists.txt --- a/CMakeLists.txt Sat Jul 23 12:32:23 2016 +0300 +++ b/CMakeLists.txt Sat Jul 23 12:34:05 2016 +0300 @@ -20,6 +20,7 @@ sources/version.cpp sources/network/bytestream.cpp sources/network/ipaddress.cpp + sources/network/packetqueue.cpp sources/network/rconsession.cpp sources/network/udpsocket.cpp ) @@ -35,6 +36,7 @@ sources/mystring.h sources/network/bytestream.h sources/network/ipaddress.h + sources/network/packetqueue.h sources/network/rconsession.h sources/network/udpsocket.h sources/range.h diff -r 96ffd13c08a2 -r febc3ed5435c sources/coloredline.cpp diff -r 96ffd13c08a2 -r febc3ed5435c sources/coloredline.h diff -r 96ffd13c08a2 -r febc3ed5435c sources/interface.cpp --- a/sources/interface.cpp Sat Jul 23 12:32:23 2016 +0300 +++ b/sources/interface.cpp Sat Jul 23 12:34:05 2016 +0300 @@ -1094,6 +1094,13 @@ endwin(); throw Exitception(); } + else if (command == "watch") + { + if (not args.is_empty()) + m_session.requestWatch(args); + else + printError("No CVars to watch.\n"); + } else printError("Unknown command: %s\n", command.chars()); } diff -r 96ffd13c08a2 -r febc3ed5435c sources/interface.h diff -r 96ffd13c08a2 -r febc3ed5435c sources/mystring.cpp diff -r 96ffd13c08a2 -r febc3ed5435c sources/mystring.h diff -r 96ffd13c08a2 -r febc3ed5435c sources/network/bytestream.cpp --- a/sources/network/bytestream.cpp Sat Jul 23 12:32:23 2016 +0300 +++ b/sources/network/bytestream.cpp Sat Jul 23 12:34:05 2016 +0300 @@ -129,28 +129,15 @@ */ String Bytestream::readString() { - ByteArray::Iterator stringEndIterator; + String result; - // Where's the end of the string? - for (stringEndIterator = getCurrentIterator(); *stringEndIterator != '\0'; ++stringEndIterator) + for (char byte; (byte = readByte()) != '\0';) { - if (stringEndIterator == m_data.end()) - { - // Past the end of the buffer - throw IOError("unterminated or too long string in packet"); - } + if (result.length() < MAX_NETWORK_STRING) + result += byte; } - // Skip past the null terminator. - if (*stringEndIterator == '\0') - stringEndIterator += 1; - - // Build and return the string, and advance the position. - int stringStart = m_position; - unsigned int length = stringEndIterator - getCurrentIterator(); - length = min(length, MAX_NETWORK_STRING); - m_position += length; - return String::fromBytes(m_data.splice(stringStart, stringStart + length)); + return result; } /*! diff -r 96ffd13c08a2 -r febc3ed5435c sources/network/packetqueue.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sources/network/packetqueue.cpp Sat Jul 23 12:34:05 2016 +0300 @@ -0,0 +1,154 @@ +/* + Copyright 2016 Teemu Piippo + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include +#include "packetqueue.h" +BEGIN_ZFC_NAMESPACE + +/*! + * \brief Constructs an empty packet queue. + */ +PacketQueue::PacketQueue() : + m_currentSequenceNumber(0) {} + +/*! + * \brief Inserts the packet into the queue, unless the packet is the next packet to be processed. + * \param sequenceNumber Sequence number of the packet. + * \param data Payload of the packet. + * \return True, if the packet was stored, false if the packet should be processed immediately. + */ +bool PacketQueue::addPacket(unsigned int sequenceNumber, const ByteArray& data) +{ + // Check whether this packet is the one we're supposed to process next. + if (sequenceNumber != m_currentSequenceNumber) + { + // It is not, therefore store it for later. + m_queue[sequenceNumber] = data; + return true; + } + else + { + // It is, therefore the caller processes it, and we can advance to the next packet right away. + m_currentSequenceNumber = getNextSequenceNumber(); + return false; + } +} + +/*! + * \returns whether there are packets in queue that cannot be processed due to missing in-between packets. If true, the + * \returns caller should initiate packet recovery protocol. + */ +bool PacketQueue::isStuck() const +{ + return m_queue.size() > 0 and m_queue.find(m_currentSequenceNumber) == m_queue.end(); +} + +/*! + * \returns whether or not there are packets awaiting processing. + */ +bool PacketQueue::hasPacketsToPop() const +{ + return m_queue.size() > 0 and m_queue.find(m_currentSequenceNumber) != m_queue.end(); +} + +/*! + * \brief Retrieves the next packet to be processed, and removes it from the queue. + * \param packet Reference to a byte array to store the packet payload into. + * \returns whether the next packet was successfully popped from the queue, or not. + */ +bool PacketQueue::popNextPacket(ByteArray& packet) +{ + // Find the packet matching our current sequence number. + auto iterator = m_queue.find(m_currentSequenceNumber); + + if (iterator != m_queue.end()) + { + // We found the packet we were looking for. Pass it to the caller. + packet = iterator->second; + // Remove the packet from the queue. + m_queue.erase(iterator); + // We can now advance to the next packet. + m_currentSequenceNumber = getNextSequenceNumber(); + return true; + } + else + { + // We did not find the next packet. + return false; + } +} + +/*! + * \returns the sequence number for the next packet. + */ +int PacketQueue::getNextSequenceNumber() const +{ + return (m_currentSequenceNumber + 1) % 1024; +} + +/*! + * \returns a list of packets that have to be requested from the server. + */ +std::set PacketQueue::getLostPackets() const +{ + std::set packetsNeeded; + std::set packetsInQueue; + + // Build the set of packet numbers we currently have. + for (auto pair : m_queue) + packetsInQueue.insert(pair.first); + + // Build the set of packets we wish to process. To do this we need the smallest and largest numbers in + // packetsInQueue. + Range packetRange(min(packetsInQueue), max(packetsInQueue)); + + for (int i : packetRange) + packetsNeeded.insert(i); + + // The set of lost packets is now the set of packets we want, minus the packets we have. + std::set packetsLost; + std::set_difference(packetsNeeded.begin(), packetsNeeded.end(), + packetsInQueue.begin(), packetsInQueue.end(), + std::inserter(packetsLost, packetsLost.begin())); + return packetsLost; +} + +std::set PacketQueue::getWaitingPackets() const +{ + std::set packetsInQueue; + + // Build the set of packet numbers we currently have. + for (auto pair : m_queue) + packetsInQueue.insert(pair.first); + + return packetsInQueue; +} + +END_ZFC_NAMESPACE diff -r 96ffd13c08a2 -r febc3ed5435c sources/network/packetqueue.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sources/network/packetqueue.h Sat Jul 23 12:34:05 2016 +0300 @@ -0,0 +1,62 @@ +/* + Copyright 2016 Teemu Piippo + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once +#include +#include +#include "../main.h" +#include "../list.h" +BEGIN_ZFC_NAMESPACE + +/*! + * \brief The PacketQueue class stores packets awaiting processing. + * + * The UDP protocol makes no guarantees of packet delivery. Packets may wind up being dropped, or delivered in the wrong + * order. This class mitigates that issue by storing the packets into a queue, and popping packets in order. + */ +class PacketQueue +{ +public: + PacketQueue(); + + bool addPacket(unsigned int sequenceNumber, const ByteArray &data); + std::set getWaitingPackets() const; + std::set getLostPackets() const; + int getNextSequenceNumber() const; + bool hasPacketsToPop() const; + bool isStuck() const; + bool popNextPacket(ByteArray& packet); + +private: + std::map m_queue; + unsigned int m_currentSequenceNumber; +}; + +END_ZFC_NAMESPACE diff -r 96ffd13c08a2 -r febc3ed5435c sources/network/rconsession.cpp --- a/sources/network/rconsession.cpp Sat Jul 23 12:32:23 2016 +0300 +++ b/sources/network/rconsession.cpp Sat Jul 23 12:34:05 2016 +0300 @@ -33,12 +33,19 @@ #include "../interface.h" BEGIN_ZFC_NAMESPACE +struct PacketHeader +{ + int32_t header; + int sequenceNumber; +}; + // ------------------------------------------------------------------------------------------------- // RCONSession::RCONSession() : m_state(RCON_DISCONNECTED), m_lastPing(0), m_adminCount(0), + m_lastMissingPacketRequest(0), m_interface(nullptr) { if (not m_socket.set_blocking(false)) @@ -111,12 +118,64 @@ } } + // Check for new packets in our socket for (Datagram datagram; m_socket.read(datagram);) { - // Only process packets that originate from the game server. - if (datagram.address == m_address) + // Packet came from the wrong address, ignore + if (datagram.address != m_address) + continue; + + // Parse and cut off the header. + PacketHeader header; + { + // Read the header, and find the sequence number + Bytestream stream(datagram.message); + header.header = stream.readLong(); + header.sequenceNumber = (header.header != 0) ? stream.readLong() : -1; + datagram.message = datagram.message.splice(stream.position(), datagram.message.size()); + } + + // Try to store this packet into the queue. However, do not try to store packets without a sequence number. + bool stored = false; + + if (header.sequenceNumber != -1) + stored = m_packetQueue.addPacket(header.sequenceNumber, datagram.message); + + // If the packet was not stored, we are to just process it right away. + if (stored == false) handlePacket(datagram.message); } + + // Check if we can now also process some packets from the queue. + if (m_packetQueue.hasPacketsToPop()) + { + ByteArray message; + while (m_packetQueue.popNextPacket(message)) + handlePacket(message); + } + + // Check whether there are packets stuck in the queue. If this is the case, we have lost some packets and need to + // ask the game server to re-send them. + if (m_packetQueue.isStuck() and m_lastMissingPacketRequest + 1 < time(NULL)) + { + m_interface->printWarning("Missing packets detected. Packets currently in queue:\n"); + + for (int packetNumber : m_packetQueue.getWaitingPackets()) + m_interface->printWarning("- %d:\n", packetNumber); + + m_lastMissingPacketRequest = time(NULL); + ByteArray message; + Bytestream stream(message); + stream.writeByte(CLRC_MISSINGPACKET); + + for (int packetNumber : m_packetQueue.getLostPackets()) + { + m_interface->printWarning("Requesting lost packet %d\n", packetNumber); + stream.writeLong(packetNumber); + } + + send(message); + } } // ------------------------------------------------------------------------------------------------- @@ -182,6 +241,10 @@ } m_interface->print("End of previous messages.\n"); + + // Watch sv_hostname so that we can update the titlebar when it changes. + requestWatch("sv_hostname"); + m_interface->print ("Watch requested.\n"); break; case SVRC_UPDATE: @@ -221,6 +284,40 @@ } } break; + + case SVRC_WATCHINGCVAR: + m_interface->print ("You are now watching %s\n", stream.readString().chars()); + m_interface->print ("Its value is: %s\n", stream.readString().chars()); + break; + + case SVRC_ALREADYWATCHINGCVAR: + m_interface->print ("You are already watching %s\n", stream.readString().chars()); + break; + + case SVRC_WATCHCVARNOTFOUND: + m_interface->print ("CVar %s not found\n", stream.readString().chars()); + break; + + case SVRC_CVARCHANGED: + { + String name = stream.readString(); + String value = stream.readString(); + m_interface->print ("The value of CVar %s", name.chars()); + m_interface->print (" is now %s\n", value.chars()); + + // If sv_hostname changes, update the titlebar + if (name == "sv_hostname") + { + m_hostname = value; + m_interface->setTitle(m_hostname); + } + } + break; + + case SVRC_YOUREDISCONNECTED: + m_interface->print ("You have been disconnected: %s\n", stream.readString().chars()); + m_interface->disconnected(); + break; } } } @@ -389,4 +486,28 @@ m_interface = interface; } +// ------------------------------------------------------------------------------------------------- +// +void RCONSession::requestWatch(const String& cvar) +{ + StringList cvars; + cvars.append(cvar); + requestWatch(cvars); +} + +// ------------------------------------------------------------------------------------------------- +// +void RCONSession::requestWatch(const StringList& cvars) +{ + ByteArray message; + Bytestream stream(message); + stream.writeByte(CLRC_WATCHCVAR); + + for (const String& cvar : cvars) + stream.writeString(cvar.normalized()); + + stream.writeString(""); + send(message); +} + END_ZFC_NAMESPACE diff -r 96ffd13c08a2 -r febc3ed5435c sources/network/rconsession.h --- a/sources/network/rconsession.h Sat Jul 23 12:32:23 2016 +0300 +++ b/sources/network/rconsession.h Sat Jul 23 12:34:05 2016 +0300 @@ -32,6 +32,7 @@ #include "ipaddress.h" #include "udpsocket.h" #include "bytestream.h" +#include "packetqueue.h" BEGIN_ZFC_NAMESPACE // ------------------------------------------------------------------------------------------------- @@ -54,6 +55,11 @@ SVRC_UPDATE, SVRC_TABCOMPLETE, SVRC_TOOMANYTABCOMPLETES, + SVRC_WATCHINGCVAR, + SVRC_ALREADYWATCHINGCVAR, + SVRC_WATCHCVARNOTFOUND, + SVRC_CVARCHANGED, + SVRC_YOUREDISCONNECTED, }; // ------------------------------------------------------------------------------------------------- @@ -66,6 +72,8 @@ CLRC_PONG, CLRC_DISCONNECT, CLRC_TABCOMPLETE, + CLRC_WATCHCVAR, + CLRC_MISSINGPACKET, }; // ------------------------------------------------------------------------------------------------- @@ -107,6 +115,8 @@ bool isActive() const; void processServerUpdates(Bytestream& packet); void requestTabCompletion(const String& part); + void requestWatch (const String& cvar); + void requestWatch (const StringList& cvars); void send(const ByteArray& packet); bool sendCommand(const String& commandString); void sendHello(); @@ -119,6 +129,7 @@ RCONSessionState m_state; IPAddress m_address; UDPSocket m_socket; + PacketQueue m_packetQueue; time_t m_lastPing; String m_password; String m_salt; @@ -127,6 +138,7 @@ int m_adminCount; String m_level; String m_lastTabComplete; + time_t m_lastMissingPacketRequest; class Interface* m_interface; }; diff -r 96ffd13c08a2 -r febc3ed5435c sources/network/udpsocket.cpp --- a/sources/network/udpsocket.cpp Sat Jul 23 12:32:23 2016 +0300 +++ b/sources/network/udpsocket.cpp Sat Jul 23 12:34:05 2016 +0300 @@ -129,6 +129,12 @@ return false; } + if (length < 4) + { + m_error = "The server sent a too short packet"; + return false; + } + unsigned char decodedPacket[MAX_DATAGRAM_LENGTH]; int decodedLength = sizeof decodedPacket; HUFFMAN_Decode (reinterpret_cast (HuffmanBuffer),