Merged with default protocol5

Sat, 23 Jul 2016 12:34:05 +0300

author
Teemu Piippo <teemu@compsta2.com>
date
Sat, 23 Jul 2016 12:34:05 +0300
branch
protocol5
changeset 169
febc3ed5435c
parent 167
0150f86e68f0 (diff)
parent 168
96ffd13c08a2 (current diff)
child 170
40d8d7231a36

Merged with default

sources/network/rconsession.cpp file | annotate | diff | comparison | revisions
sources/network/rconsession.h file | annotate | diff | comparison | revisions
--- 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
--- 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());
 }
--- 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<int>(length, MAX_NETWORK_STRING);
-	m_position += length;
-	return String::fromBytes(m_data.splice(stringStart, stringStart + length));
+	return result;
 }
 
 /*!
--- /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 <algorithm>
+#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<int> PacketQueue::getLostPackets() const
+{
+	std::set<int> packetsNeeded;
+	std::set<int> 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<int> 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<int> packetsLost;
+	std::set_difference(packetsNeeded.begin(), packetsNeeded.end(),
+	                    packetsInQueue.begin(), packetsInQueue.end(),
+	                    std::inserter(packetsLost, packetsLost.begin()));
+	return packetsLost;
+}
+
+std::set<unsigned int> PacketQueue::getWaitingPackets() const
+{
+	std::set<unsigned int> packetsInQueue;
+
+	// Build the set of packet numbers we currently have.
+	for (auto pair : m_queue)
+		packetsInQueue.insert(pair.first);
+
+	return packetsInQueue;
+}
+
+END_ZFC_NAMESPACE
--- /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 <map>
+#include <set>
+#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<unsigned int> getWaitingPackets() const;
+	std::set<int> getLostPackets() const;
+	int getNextSequenceNumber() const;
+	bool hasPacketsToPop() const;
+	bool isStuck() const;
+	bool popNextPacket(ByteArray& packet);
+
+private:
+	std::map<unsigned int, ByteArray> m_queue;
+	unsigned int m_currentSequenceNumber;
+};
+
+END_ZFC_NAMESPACE
--- 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
--- 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;
 };
 
--- 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<unsigned char*> (HuffmanBuffer),

mercurial