Maelstrom: Implemented the first part of the lobby protocol served by the address server.

https://github.com/libsdl-org/Maelstrom/commit/173d76387ef3f45d65fe580f30428b5f0f64d28a

From 173d76387ef3f45d65fe580f30428b5f0f64d28a Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Sat, 5 Nov 2011 20:45:25 -0400
Subject: [PATCH] Implemented the first part of the lobby protocol served by
 the address server. The hosting and joining players are now talking to each
 other!

---
 MaelstromLobby.cpp   |  57 +++++++++++++++----
 netlogic/lobby.cpp   | 130 +++++++++++++++++++++++++++++++++++++------
 netlogic/lobby.h     |   7 +++
 netlogic/netplay.cpp |   6 +-
 netlogic/packet.h    |  48 +++++++++++-----
 netlogic/protocol.h  |  34 ++++++-----
 6 files changed, 224 insertions(+), 58 deletions(-)

diff --git a/MaelstromLobby.cpp b/MaelstromLobby.cpp
index a997fc25..d2d68b77 100644
--- a/MaelstromLobby.cpp
+++ b/MaelstromLobby.cpp
@@ -26,19 +26,11 @@
 #include "netlogic/protocol.h"
 #include "utils/array.h"
 
-#define MAX_PACKET_SIZE	1024
 
 // 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:
@@ -169,6 +161,7 @@ class GameList
 {
 public:
 	GameList() {
+		m_sock = NULL;
 		m_list = 0;
 		m_free = 0;
 	}
@@ -188,6 +181,10 @@ class GameList
 		}
 	}
 
+	void SetSocket(UDPsocket sock) {
+		m_sock = sock;
+	}
+
 	void ProcessList() {
 		Game *game, *next;
 		Uint32 now;
@@ -209,6 +206,12 @@ class GameList
 	void ProcessPacket(DynamicPacket &packet) {
 		Uint8 cmd;
 
+		if (!packet.Read(cmd)) {
+			return;
+		}
+		if (cmd != LOBBY_MSG) {
+			return;
+		}
 		if (!packet.Read(cmd)) {
 			return;
 		}
@@ -289,11 +292,46 @@ class GameList
 	}
 
 	void ProcessRequestGames(DynamicPacket &packet) {
+		AddressList addresses;
+		Game *game;
+
+		if (!addresses.ReadFromPacket(packet)) {
+			return;
+		}
+
+		// Send back a list of all games
+		int mark;
+		m_reply.StartLobbyMessage(LOBBY_GAME_SERVERS);
+		mark = m_reply.Tell(); 
+		m_reply.Write((Uint8)0);
+		int count;
+		for (game = m_list; game; game = game->Next()) {
+			game->WriteToPacket(m_reply);
+			++count;
+			if (count == 255) {
+				// That's it, I'm cutting you off...
+				break;
+			}
+		}
+		m_reply.Seek(mark);
+		m_reply.Write((Uint8)count);
+		m_reply.address = packet.address;
+		SDLNet_UDP_Send(m_sock, -1, &m_reply);
+
+		// Send the requesting player to all game servers
+		m_reply.StartLobbyMessage(LOBBY_ANNOUNCE_PLAYER);
+		addresses.WriteToPacket(m_reply);
+		for (game = m_list; game; game = game->Next()) {
+			m_reply.address = *game->Address();
+			SDLNet_UDP_Send(m_sock, -1, &m_reply);
+		}
 	}
 
 protected:
+	UDPsocket m_sock;
 	Game *m_list;
 	Game *m_free;
+	DynamicPacket m_reply;
 };
 
 int main(int argc, char *argv[])
@@ -308,8 +346,7 @@ int main(int argc, char *argv[])
 			LOBBY_PORT, SDL_GetError());
 		exit(1);
 	}
-
-	packet.Expand(MAX_PACKET_SIZE);
+	games.SetSocket(sock);
 
 	for ( ; ; ) {
 		games.ProcessList();
diff --git a/netlogic/lobby.cpp b/netlogic/lobby.cpp
index 0f97890d..ddd369b4 100644
--- a/netlogic/lobby.cpp
+++ b/netlogic/lobby.cpp
@@ -110,15 +110,36 @@ LobbyDialogDelegate::OnTick()
 	Uint32 now = SDL_GetTicks();
 	if (!m_lastRefresh ||
 	    (now - m_lastRefresh) > GLOBAL_CHECK_INTERVAL) {
-		if (m_globalGame->IsChecked()) {
-			if (m_hosting) {
-				AdvertiseGame();
-			} else {
-				GetGameList();
-			}
+		if (m_hosting) {
+			AdvertiseGame();
+		} else {
+			GetGameList();
 		}
 		m_lastRefresh = now;
 	}
+
+	// See if there are any packets on the network
+	Uint8 cmd;
+	for ( ; ; ) {
+		m_packet.Reset();
+		if (!SDLNet_UDP_Recv(gNetFD, &m_packet)) {
+			break;
+		}
+		if (!m_packet.Read(cmd)) {
+			continue;
+		}
+		if (cmd != LOBBY_MSG) {
+			continue;
+		}
+		if (!m_packet.Read(cmd)) {
+			continue;
+		}
+		if (m_hosting) {
+			HostingProcessPacket(cmd, m_packet);
+		} else {
+			JoiningProcessPacket(cmd, m_packet);
+		}
+	}
 }
 
 void
@@ -154,19 +175,19 @@ LobbyDialogDelegate::GlobalGameChanged(void*)
 void
 LobbyDialogDelegate::AdvertiseGame()
 {
-	m_packet.Reset();
-	m_packet.Write((Uint8)LOBBY_ANNOUNCE_GAME);
-	PackAddresses(m_packet);
-	m_packet.address = m_globalServer;
+	if (m_globalGame->IsChecked()) {
+		m_packet.StartLobbyMessage(LOBBY_ANNOUNCE_GAME);
+		PackAddresses(m_packet);
+		m_packet.address = m_globalServer;
 
-	SDLNet_UDP_Send(gNetFD, -1, &m_packet);
+		SDLNet_UDP_Send(gNetFD, -1, &m_packet);
+	}
 }
 
 void
 LobbyDialogDelegate::RemoveGame()
 {
-	m_packet.Reset();
-	m_packet.Write((Uint8)LOBBY_REMOVE_GAME);
+	m_packet.StartLobbyMessage(LOBBY_REMOVE_GAME);
 	PackAddresses(m_packet);
 	m_packet.address = m_globalServer;
 
@@ -176,12 +197,13 @@ LobbyDialogDelegate::RemoveGame()
 void
 LobbyDialogDelegate::GetGameList()
 {
-	m_packet.Reset();
-	m_packet.Write((Uint8)LOBBY_REQUEST_GAME_SERVERS);
-	PackAddresses(m_packet);
-	m_packet.address = m_globalServer;
+	if (m_globalGame->IsChecked()) {
+		m_packet.StartLobbyMessage(LOBBY_REQUEST_GAME_SERVERS);
+		PackAddresses(m_packet);
+		m_packet.address = m_globalServer;
 
-	SDLNet_UDP_Send(gNetFD, -1, &m_packet);
+		SDLNet_UDP_Send(gNetFD, -1, &m_packet);
+	}
 }
 
 void
@@ -202,3 +224,75 @@ LobbyDialogDelegate::PackAddresses(DynamicPacket &packet)
 		m_packet.Write(port);
 	}
 }
+
+void
+LobbyDialogDelegate::HostingProcessPacket(Uint8 type, DynamicPacket &packet)
+{
+	if (m_globalGame->IsChecked()) {
+		if (type == LOBBY_ANNOUNCE_PLAYER) {
+			ProcessAnnouncePlayer(packet);
+			return;
+		}
+	}
+}
+
+void
+LobbyDialogDelegate::ProcessAnnouncePlayer(DynamicPacket &packet)
+{
+	Uint8 count;
+	IPaddress address;
+
+	// Open the firewall so this player can contact us.
+	m_reply.StartLobbyMessage(LOBBY_OPEN_FIREWALL);
+
+	if (!packet.Read(count)) {
+		return;
+	}
+	for (Uint8 i = 0; i < count; ++i) {
+		if (!packet.Read(address.host) ||
+		    !packet.Read(address.port)) {
+			return;
+		}
+		m_reply.address = address;
+		
+		SDLNet_UDP_Send(gNetFD, -1, &m_reply);
+	}
+}
+
+void
+LobbyDialogDelegate::JoiningProcessPacket(Uint8 type, DynamicPacket &packet)
+{
+	if (m_globalGame->IsChecked()) {
+		if (type == LOBBY_GAME_SERVERS) {
+			ProcessGameServerList(packet);
+		}
+	}
+}
+
+void
+LobbyDialogDelegate::ProcessGameServerList(DynamicPacket &packet)
+{
+	Uint8 serverCount, count;
+	IPaddress address;
+
+	// Request game information from the servers
+	m_reply.StartLobbyMessage(LOBBY_REQUEST_GAME_INFO);
+
+	if (!packet.Read(serverCount)) {
+		return;
+	}
+	for (Uint8 i = 0; i < serverCount; ++i) {
+		if (!packet.Read(count)) {
+			return;
+		}
+		for (Uint8 j = 0; j < count; ++j) {
+			if (!packet.Read(address.host) ||
+			    !packet.Read(address.port)) {
+				return;
+			}
+			m_reply.address = address;
+			
+			SDLNet_UDP_Send(gNetFD, -1, &m_reply);
+		}
+	}
+}
diff --git a/netlogic/lobby.h b/netlogic/lobby.h
index 258d0c2f..c6700d59 100644
--- a/netlogic/lobby.h
+++ b/netlogic/lobby.h
@@ -58,10 +58,17 @@ class LobbyDialogDelegate : public UIDialogDelegate
 
 	void PackAddresses(DynamicPacket &packet);
 
+	void HostingProcessPacket(Uint8 type, DynamicPacket &packet);
+	void ProcessAnnouncePlayer(DynamicPacket &packet);
+
+	void JoiningProcessPacket(Uint8 type, DynamicPacket &packet);
+	void ProcessGameServerList(DynamicPacket &packet);
+
 protected:
 	IPaddress m_globalServer;
 	array<IPaddress> m_addresses;
 	DynamicPacket m_packet;
+	DynamicPacket m_reply;
 	bool m_hosting;
 	Uint32 m_lastRefresh;
 	UIElementRadioGroup *m_hostOrJoin;
diff --git a/netlogic/netplay.cpp b/netlogic/netplay.cpp
index fd289628..6d2460be 100644
--- a/netlogic/netplay.cpp
+++ b/netlogic/netplay.cpp
@@ -79,7 +79,7 @@ int InitNetData(bool hosting)
 
 	/* Initialize the networking subsystem */
 	if ( SDLNet_Init() < 0 ) {
-		error("NetLogic: Couldn't initialize networking!\n");
+		error("Couldn't initialize networking: %s\n", SDL_GetError());
 		return(-1);
 	}
 
@@ -91,12 +91,12 @@ int InitNetData(bool hosting)
 	}
 	gNetFD = SDLNet_UDP_Open(port);
 	if ( gNetFD == NULL ) {
-		error("Couldn't create bound network socket");
+		error("Couldn't create bound network socket\n");
 		return(-1);
 	}
 	SocketSet = SDLNet_AllocSocketSet(1);
 	if ( SocketSet == NULL ) {
-		error("Couldn't create socket watch set");
+		error("Couldn't create socket watch set\n");
 		return(-1);
 	}
 	SDLNet_UDP_AddSocket(SocketSet, gNetFD);
diff --git a/netlogic/packet.h b/netlogic/packet.h
index 0c4bb736..1b2cf4a5 100644
--- a/netlogic/packet.h
+++ b/netlogic/packet.h
@@ -1,4 +1,3 @@
-
 /*
     Maelstrom: Open Source version of the classic game by Ambrosia Software
     Copyright (C) 1997-2011  Sam Lantinga
@@ -25,13 +24,23 @@
 #define _packet_h
 
 #include "SDL_net.h"
+#include "protocol.h"
+
+// Utility functions to compare IP addresses 
+
+extern inline bool operator==(const IPaddress &lhs, const IPaddress &rhs) {
+	return lhs.host == rhs.host && lhs.port == rhs.port;
+}
+extern inline bool operator!=(const IPaddress &lhs, const IPaddress &rhs) {
+	return !operator==(lhs, rhs);
+}
 
 // A dynamic packet class that takes care of allocating memory and packing data
 
 class DynamicPacket : public UDPpacket
 {
 public:
-	DynamicPacket(int minSize = 32) {
+	DynamicPacket(int minSize = 1024) {
 		SDL_zero(*this);
 		maxlen = minSize;
 		data = (Uint8*)SDL_malloc(minSize);
@@ -40,8 +49,10 @@ class DynamicPacket : public UDPpacket
 		SDL_free(data);
 	}
 
-	void Expand(size_t size) {
-		CheckSize(size - len);
+	void StartLobbyMessage(int msg) {
+		Reset();
+		Write((Uint8)LOBBY_MSG);
+		Write((Uint8)msg);
 	}
 
 	void Reset() {
@@ -49,19 +60,29 @@ class DynamicPacket : public UDPpacket
 		pos = 0;
 	}
 
+	void Seek(int offset) {
+		if (offset < len) {
+			pos = offset;
+		}
+	}
+
+	int Tell() {
+		return pos;
+	}
+
 	void Write(Uint8 value) {
-		CheckSize(sizeof(value));
-		data[len++] = value;
+		Grow(sizeof(value));
+		data[pos++] = value;
 	}
 	void Write(Uint16 value) {
-		CheckSize(sizeof(value));
-		SDLNet_Write16(value, &data[len]);
-		len += sizeof(value);
+		Grow(sizeof(value));
+		SDLNet_Write16(value, &data[pos]);
+		pos += sizeof(value);
 	}
 	void Write(Uint32 value) {
-		CheckSize(sizeof(value));
-		SDLNet_Write32(value, &data[len]);
-		len += sizeof(value);
+		Grow(sizeof(value));
+		SDLNet_Write32(value, &data[pos]);
+		pos += sizeof(value);
 	}
 
 	bool Read(Uint8 &value) {
@@ -89,13 +110,14 @@ class DynamicPacket : public UDPpacket
 	}
 
 protected:
-	void CheckSize(size_t additionalSize) {
+	void Grow(size_t additionalSize) {
 		if (len+additionalSize > (size_t)maxlen) {
 			while (len+additionalSize > (size_t)maxlen) {
 				maxlen *= 2;
 			}
 			data = (Uint8*)SDL_realloc(data, maxlen);
 		}
+		len += additionalSize;
 	}
 
 protected:
diff --git a/netlogic/protocol.h b/netlogic/protocol.h
index d0a7f8cb..e199d74a 100644
--- a/netlogic/protocol.h
+++ b/netlogic/protocol.h
@@ -20,6 +20,8 @@
     slouken@libsdl.org
 */
 
+#ifndef _protocol_h
+#define _protocol_h
 
 /* Architecture
  *
@@ -43,7 +45,7 @@ enum LobbyProtocol {
 	   This is sent periodically to keep the entry refreshed, since
 	   the server will automatically age out entries after 30 seconds.
 
-		BYTE numaddresses
+		Uint8 numaddresses
 		{
 			Uint32 host;
 			Uint16 port;
@@ -54,7 +56,7 @@ enum LobbyProtocol {
 	/* Sent by the hosting game to the lobby server
 	   This is sent when the game is no longer available to join.
 
-		BYTE numaddresses
+		Uint8 numaddresses
 		{
 			Uint32 host;
 			Uint16 port;
@@ -66,7 +68,7 @@ enum LobbyProtocol {
 	   This allows the hosting game to send a packet to the player
 	   requesting to join, opening the firewall for them.
 
-		BYTE numaddresses
+		Uint8 numaddresses
 		{
 			Uint32 host;
 			Uint16 port;
@@ -79,7 +81,7 @@ enum LobbyProtocol {
 	LOBBY_REQUEST_GAME_SERVERS = 10,
 	/* Sent by the joining game, containing a list of it's addresses
 
-		BYTE numaddresses
+		Uint8 numaddresses
 		{
 			Uint32 host;
 			Uint16 port;
@@ -89,7 +91,7 @@ enum LobbyProtocol {
 	LOBBY_GAME_SERVERS,
 	/* Sent by the lobby server containing all the current game addresses
 
-		BYTE numaddresses
+		Uint8 numaddresses
 		{
 			Uint32 host;
 			Uint16 port;
@@ -100,8 +102,8 @@ enum LobbyProtocol {
 	/* Messages between the joining game and the hosting game */
 
 	LOBBY_OPEN_FIREWALL = 20,
-	/* Sent by the both the hosting and the joining game in response
-	   to lobby server messages to open the firewall for communication
+	/* Sent by the hosting game in response to lobby server messages
+	   to open the firewall for communication
 	 */
 
 	LOBBY_REQUEST_GAME_INFO,
@@ -112,7 +114,7 @@ enum LobbyProtocol {
 	/* Sent by the hosting game, if there are slots open
 
 		Uint32 gameID
-		BYTE namelen
+		Uint8 namelen
 		char name[]
 	 */
 
@@ -138,7 +140,7 @@ enum LobbyProtocol {
 
 		Uint32 gameID
 		Uint32 playerID
-		BYTE namelen
+		Uint8 namelen
 		char name[]
 	*/
 
@@ -147,17 +149,20 @@ enum LobbyProtocol {
 
 		Uint32 gameID
 		Uint32 playerID
-		BYTE playerSlot
+		Uint8 playerSlot
 	*/
+
+	/* You can't add any more packets past here, look above for space! */
+	LOBBY_PACKET_MAX = 256
 };
 
 /* Network protocol for synchronization and keystrokes */
 
-#define SYNC_MSG	0x00			/* Sent during game */
+#define LOBBY_MSG	0x00			/* Sent before game */
 #define NEW_GAME	0x01			/* Sent by players at start */
-#define NET_ABORT	0x04			/* Used with address server */
-#define KEY_PRESS	0x80			/* Sent during game */
-#define KEY_RELEASE	0xF0			/* Sent during game */
+#define SYNC_MSG	0x02			/* Sent during game */
+#define KEY_PRESS	0x04			/* Sent during game */
+#define KEY_RELEASE	0x08			/* Sent during game */
 
 /* The default port for Maelstrom games */
 #define LOBBY_PORT	0xAE00			/* port 44544 */
@@ -171,3 +176,4 @@ enum LobbyProtocol {
 */
 #define MAX_PLAYERS		3		/* No more than 255!! */
 
+#endif /* _protocol_h */