Maelstrom: The Maelstrom lobby server now maintains a list of games.

https://github.com/libsdl-org/Maelstrom/commit/5e8f01ffdfb3f5dee955cb424770dc1deee52188

From 5e8f01ffdfb3f5dee955cb424770dc1deee52188 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Sat, 5 Nov 2011 19:02:03 -0400
Subject: [PATCH] The Maelstrom lobby server now maintains a list of games.

---
 MaelstromLobby.cpp  | 289 ++++++++++++++++++++++++++++++++++++++++++--
 netlogic/lobby.cpp  |  65 ++++++----
 netlogic/lobby.h    |   5 +-
 netlogic/packet.h   |  47 +++++--
 netlogic/protocol.h |  17 ++-
 5 files changed, 376 insertions(+), 47 deletions(-)

diff --git a/MaelstromLobby.cpp b/MaelstromLobby.cpp
index 5636f0b4..a997fc25 100644
--- a/MaelstromLobby.cpp
+++ b/MaelstromLobby.cpp
@@ -22,19 +22,285 @@
 
 #include "SDL_net.h"
 
+#include "netlogic/packet.h"
 #include "netlogic/protocol.h"
+#include "utils/array.h"
 
 #define MAX_PACKET_SIZE	1024
 
-void ProcessPacket(UDPpacket *packet)
-{
-	printf("Received packet from %s\n", SDLNet_ResolveIP(&packet->address));
+// We'll let games stick around for 10 seconds before aging them out
+#define GAME_LIFETIME	10000
+
+
+bool operator==(const IPaddress &lhs, const IPaddress &rhs) {
+	return lhs.host == rhs.host && lhs.port == rhs.port;
 }
+bool operator!=(const IPaddress &lhs, const IPaddress &rhs) {
+	return !operator==(lhs, rhs);
+}
+
+class AddressList : public array<IPaddress>
+{
+public:
+	AddressList() : array<IPaddress>() { }
+
+	bool ReadFromPacket(DynamicPacket &packet) {
+		Uint8 count;
+		IPaddress address;
+
+		if (!packet.Read(count) || !count) {
+			return false;
+		}
+
+		clear();
+		SDL_zero(address);
+		for (Uint8 i = 0; i < count; ++i) {
+			if (!packet.Read(address.host)) {
+				return false;
+			}
+			if (!packet.Read(address.port)) {
+				return false;
+			}
+			add(address);
+		}
+
+		// Add the address that we saw this packet come from
+		if (!find(packet.address)) {
+			insert(packet.address, 0);
+		}
+
+		return true;
+	}
+
+	void WriteToPacket(DynamicPacket &packet) {
+		packet.Write((Uint8)length());
+		for (int i = 0; i < length(); ++i) {
+			packet.Write(m_data[i].host);
+			packet.Write(m_data[i].port);
+		}
+	}
+
+	bool operator ==(const AddressList &rhs) const {
+		if (length() != rhs.length()) {
+			return false;
+		}
+		for (int i = 0; i < length(); ++i) {
+			if (m_data[i] != rhs.m_data[i]) {
+				return false;
+			}
+		}
+		return true;
+	}
+};
+
+class Game
+{
+public:
+	Game() {
+		m_timestamp = 0;
+		m_list = 0;
+		m_prev = 0;
+		m_next = 0;
+	}
+
+	bool ReadFromPacket(DynamicPacket &packet) {
+		return m_addresses.ReadFromPacket(packet);
+	}
+
+	void WriteToPacket(DynamicPacket &packet) {
+		m_addresses.WriteToPacket(packet);
+	}
+
+	bool operator ==(const Game &rhs) {
+		return m_addresses == rhs.m_addresses;
+	}
+
+	bool TimedOut(Uint32 now) {
+		return (now - m_timestamp) > GAME_LIFETIME;
+	}
+
+	void Refresh() {
+		m_timestamp = SDL_GetTicks();
+	}
+
+	const IPaddress *Address() const {
+		return &m_addresses[0];
+	}
+
+	void Link(Game *&list) {
+		Unlink();
+		if (list) {
+			list->m_prev = this;
+		}
+		m_next = list;
+		list = this;
+		m_list = &list;
+	}
+
+	void Unlink() {
+		if (m_prev) {
+			m_prev->m_next = m_next;
+		} else if (m_list) {
+			*m_list = m_next;
+		}
+		if (m_next) {
+			m_next->m_prev = m_prev;
+		}
+		m_list = 0;
+		m_prev = 0;
+		m_next = 0;
+	}
+
+	Game *Prev() {
+		return m_prev;
+	}
+	Game *Next() {
+		return m_next;
+	}
+
+protected:
+	Uint32 m_timestamp;
+	AddressList m_addresses;
+	Game **m_list;
+	Game *m_prev, *m_next;
+};
+
+class GameList
+{
+public:
+	GameList() {
+		m_list = 0;
+		m_free = 0;
+	}
+
+	~GameList() {
+		Game *game;
+
+		while (m_list) {
+			game = m_list;
+			game->Unlink();
+			delete game;
+		}
+		while (m_free) {
+			game = m_free;
+			game->Unlink();
+			delete game;
+		}
+	}
+
+	void ProcessList() {
+		Game *game, *next;
+		Uint32 now;
+
+		now = SDL_GetTicks();
+		game = m_list;
+		while (game) {
+			next = game->Next();
+			if (game->TimedOut(now)) {
+				printf("Expiring game from %s:%d\n",
+					SDLNet_ResolveIP(game->Address()),
+					SDL_SwapBE16(game->Address()->port));
+				game->Link(m_free);
+			}
+			game = next;
+		}
+	}
+
+	void ProcessPacket(DynamicPacket &packet) {
+		Uint8 cmd;
+
+		if (!packet.Read(cmd)) {
+			return;
+		}
+
+		switch (cmd) {
+			case LOBBY_ANNOUNCE_GAME:
+				ProcessAnnounceGame(packet);
+				break;
+			case LOBBY_REMOVE_GAME:
+				ProcessRemoveGame(packet);
+				break;
+			case LOBBY_REQUEST_GAME_SERVERS:
+				ProcessRequestGames(packet);
+				break;
+		}
+	}
+
+	void ProcessAnnounceGame(DynamicPacket &packet) {
+		Game *newGame;
+
+		if (m_free) {
+			newGame = m_free;
+		} else {
+			newGame = new Game;
+		}
+		if (!newGame->ReadFromPacket(packet)) {
+			printf("Invalid game from %s:%d\n",
+				SDLNet_ResolveIP(&packet.address),
+				SDL_SwapBE16(packet.address.port));
+
+			newGame->Link(m_free);
+			return;
+		}
+
+		for (Game *game = m_list; game; game = game->Next()) {
+			if (*game == *newGame) {
+				//printf("Refreshing game from %s:%d\n",
+				//	SDLNet_ResolveIP(&packet.address),
+				//	SDL_SwapBE16(packet.address.port));
+
+				game->Refresh();
+				newGame->Link(m_free);
+				return;
+			}
+		}
+
+		printf("Adding game from %s:%d\n",
+			SDLNet_ResolveIP(&packet.address),
+			SDL_SwapBE16(packet.address.port));
+
+		newGame->Refresh();
+		newGame->Link(m_list);
+	}
+
+	void ProcessRemoveGame(DynamicPacket &packet) {
+		Game *newGame;
+
+		if (m_free) {
+			newGame = m_free;
+		} else {
+			newGame = new Game;
+		}
+		if (!newGame->ReadFromPacket(packet)) {
+			newGame->Link(m_free);
+			return;
+		}
+
+		for (Game *game = m_list; game; game = game->Next()) {
+			if (*game == *newGame) {
+				printf("Removing game from %s:%d\n",
+					SDLNet_ResolveIP(&packet.address),
+					SDL_SwapBE16(packet.address.port));
+				game->Link(m_free);
+				break;
+			}
+		}
+		newGame->Link(m_free);
+	}
+
+	void ProcessRequestGames(DynamicPacket &packet) {
+	}
+
+protected:
+	Game *m_list;
+	Game *m_free;
+};
 
 int main(int argc, char *argv[])
 {
 	UDPsocket sock;
-	UDPpacket *packet;
+	DynamicPacket packet;
+	GameList games;
 
 	sock = SDLNet_UDP_Open(LOBBY_PORT);
 	if (!sock) {
@@ -43,17 +309,14 @@ int main(int argc, char *argv[])
 		exit(1);
 	}
 
-	packet = SDLNet_AllocPacket(MAX_PACKET_SIZE);
-	if (!packet) {
-		fprintf(stderr, "Couldn't allocate packet of size %d: %s\n",
-			MAX_PACKET_SIZE, SDL_GetError());
-		SDLNet_UDP_Close(sock);
-		exit(1);
-	}
+	packet.Expand(MAX_PACKET_SIZE);
 
 	for ( ; ; ) {
-		while (SDLNet_UDP_Recv(sock, packet)) {
-			ProcessPacket(packet);
+		games.ProcessList();
+
+		while (SDLNet_UDP_Recv(sock, &packet)) {
+			games.ProcessPacket(packet);
+			packet.Reset();
 		}
 
 		SDL_Delay(100);
diff --git a/netlogic/lobby.cpp b/netlogic/lobby.cpp
index f1af81cc..0f97890d 100644
--- a/netlogic/lobby.cpp
+++ b/netlogic/lobby.cpp
@@ -107,17 +107,17 @@ LobbyDialogDelegate::OnTick()
 		return;
 	}
 
-	if (m_globalGame->IsChecked()) {
-		Uint32 now = SDL_GetTicks();
-		if (!m_lastGlobalCheck ||
-		    (now - m_lastGlobalCheck) > GLOBAL_CHECK_INTERVAL) {
+	Uint32 now = SDL_GetTicks();
+	if (!m_lastRefresh ||
+	    (now - m_lastRefresh) > GLOBAL_CHECK_INTERVAL) {
+		if (m_globalGame->IsChecked()) {
 			if (m_hosting) {
 				AdvertiseGame();
 			} else {
 				GetGameList();
 			}
-			m_lastGlobalCheck = now;
 		}
+		m_lastRefresh = now;
 	}
 }
 
@@ -133,28 +133,41 @@ LobbyDialogDelegate::SetHostOrJoin(void*, int value)
 		if (InitNetData(m_hosting) < 0) {
 			m_hostOrJoin->SetValue(0);
 		}
-		m_lastGlobalCheck = 0;
+		m_lastRefresh = 0;
 	}
 }
 
 void
 LobbyDialogDelegate::GlobalGameChanged(void*)
 {
-	m_lastGlobalCheck = 0;
+	m_lastRefresh = 0;
+
+	if (!m_globalGame->IsChecked()) {
+		if (m_hosting) {
+			RemoveGame();
+		} else {
+			ClearGameList();
+		}
+	}
 }
 
 void
 LobbyDialogDelegate::AdvertiseGame()
 {
-	int i;
+	m_packet.Reset();
+	m_packet.Write((Uint8)LOBBY_ANNOUNCE_GAME);
+	PackAddresses(m_packet);
+	m_packet.address = m_globalServer;
 
+	SDLNet_UDP_Send(gNetFD, -1, &m_packet);
+}
+
+void
+LobbyDialogDelegate::RemoveGame()
+{
 	m_packet.Reset();
-	m_packet << (Uint8)LOBBY_ANNOUNCE_GAME;
-	m_packet << (Uint8)m_addresses.length();
-	for (i = 0; i < m_addresses.length(); ++i) {
-		m_packet << m_addresses[i].host;
-		m_packet << m_addresses[i].port;
-	}
+	m_packet.Write((Uint8)LOBBY_REMOVE_GAME);
+	PackAddresses(m_packet);
 	m_packet.address = m_globalServer;
 
 	SDLNet_UDP_Send(gNetFD, -1, &m_packet);
@@ -163,15 +176,9 @@ LobbyDialogDelegate::AdvertiseGame()
 void
 LobbyDialogDelegate::GetGameList()
 {
-	int i;
-
 	m_packet.Reset();
-	m_packet << (Uint8)LOBBY_REQUEST_GAME_SERVERS;
-	m_packet << (Uint8)m_addresses.length();
-	for (i = 0; i < m_addresses.length(); ++i) {
-		m_packet << m_addresses[i].host;
-		m_packet << m_addresses[i].port;
-	}
+	m_packet.Write((Uint8)LOBBY_REQUEST_GAME_SERVERS);
+	PackAddresses(m_packet);
 	m_packet.address = m_globalServer;
 
 	SDLNet_UDP_Send(gNetFD, -1, &m_packet);
@@ -181,3 +188,17 @@ void
 LobbyDialogDelegate::ClearGameList()
 {
 }
+
+void
+LobbyDialogDelegate::PackAddresses(DynamicPacket &packet)
+{
+	Uint16 port;
+
+	port = SDLNet_UDP_GetPeerAddress(gNetFD, -1)->port;
+
+	m_packet.Write((Uint8)m_addresses.length());
+	for (int i = 0; i < m_addresses.length(); ++i) {
+		m_packet.Write(m_addresses[i].host);
+		m_packet.Write(port);
+	}
+}
diff --git a/netlogic/lobby.h b/netlogic/lobby.h
index 027e2b88..258d0c2f 100644
--- a/netlogic/lobby.h
+++ b/netlogic/lobby.h
@@ -52,15 +52,18 @@ class LobbyDialogDelegate : public UIDialogDelegate
 	void GlobalGameChanged(void*);
 
 	void AdvertiseGame();
+	void RemoveGame();
 	void GetGameList();
 	void ClearGameList();
 
+	void PackAddresses(DynamicPacket &packet);
+
 protected:
 	IPaddress m_globalServer;
 	array<IPaddress> m_addresses;
 	DynamicPacket m_packet;
 	bool m_hosting;
-	Uint32 m_lastGlobalCheck;
+	Uint32 m_lastRefresh;
 	UIElementRadioGroup *m_hostOrJoin;
 	UIElementCheckbox *m_globalGame;
 };
diff --git a/netlogic/packet.h b/netlogic/packet.h
index 2ef0dae5..0c4bb736 100644
--- a/netlogic/packet.h
+++ b/netlogic/packet.h
@@ -32,7 +32,7 @@ class DynamicPacket : public UDPpacket
 {
 public:
 	DynamicPacket(int minSize = 32) {
-		len = 0;
+		SDL_zero(*this);
 		maxlen = minSize;
 		data = (Uint8*)SDL_malloc(minSize);
 	}
@@ -40,35 +40,66 @@ class DynamicPacket : public UDPpacket
 		SDL_free(data);
 	}
 
+	void Expand(size_t size) {
+		CheckSize(size - len);
+	}
+
 	void Reset() {
 		len = 0;
+		pos = 0;
 	}
 
-	DynamicPacket& operator <<(Uint8 value) {
+	void Write(Uint8 value) {
 		CheckSize(sizeof(value));
 		data[len++] = value;
-		return *this;
 	}
-	DynamicPacket& operator <<(Uint16 value) {
+	void Write(Uint16 value) {
 		CheckSize(sizeof(value));
 		SDLNet_Write16(value, &data[len]);
 		len += sizeof(value);
-		return *this;
 	}
-	DynamicPacket& operator <<(Uint32 value) {
+	void Write(Uint32 value) {
 		CheckSize(sizeof(value));
 		SDLNet_Write32(value, &data[len]);
 		len += sizeof(value);
-		return *this;
+	}
+
+	bool Read(Uint8 &value) {
+		if (pos+sizeof(value) > (size_t)len) {
+			return false;
+		}
+		value = data[pos++];
+		return true;
+	}
+	bool Read(Uint16 &value) {
+		if (pos+sizeof(value) > (size_t)len) {
+			return false;
+		}
+		value = SDLNet_Read16(&data[pos]);
+		pos += sizeof(value);
+		return true;
+	}
+	bool Read(Uint32 &value) {
+		if (pos+sizeof(value) > (size_t)len) {
+			return false;
+		}
+		value = SDLNet_Read32(&data[pos]);
+		pos += sizeof(value);
+		return true;
 	}
 
 protected:
 	void CheckSize(size_t additionalSize) {
 		if (len+additionalSize > (size_t)maxlen) {
-			maxlen *= 2;
+			while (len+additionalSize > (size_t)maxlen) {
+				maxlen *= 2;
+			}
 			data = (Uint8*)SDL_realloc(data, maxlen);
 		}
 	}
+
+protected:
+	int pos;
 };
 
 #endif // _packet_h
diff --git a/netlogic/protocol.h b/netlogic/protocol.h
index 8095028d..d0a7f8cb 100644
--- a/netlogic/protocol.h
+++ b/netlogic/protocol.h
@@ -50,6 +50,17 @@ enum LobbyProtocol {
 		} addresses[]
 	 */
 
+	LOBBY_REMOVE_GAME,
+	/* Sent by the hosting game to the lobby server
+	   This is sent when the game is no longer available to join.
+
+		BYTE numaddresses
+		{
+			Uint32 host;
+			Uint16 port;
+		} addresses[]
+	 */
+
 	LOBBY_ANNOUNCE_PLAYER,
 	/* Sent by the lobby server when a player requests the game list.
 	   This allows the hosting game to send a packet to the player
@@ -148,9 +159,9 @@ enum LobbyProtocol {
 #define KEY_PRESS	0x80			/* Sent during game */
 #define KEY_RELEASE	0xF0			/* Sent during game */
 
-/* The default port for Maelstrom games.  What is 0xAEAE?? *shrug* :) */
-#define LOBBY_PORT	0xAFAF			/* port 44975 */
-#define NETPLAY_PORT	0xAEAE			/* port 44718 */
+/* The default port for Maelstrom games */
+#define LOBBY_PORT	0xAE00			/* port 44544 */
+#define NETPLAY_PORT	0xAF00			/* port 44800 */
 
 /* The minimum length of a new packet buffer */
 #define NEW_PACKETLEN	(3+3*4)