summaryrefslogtreecommitdiff
path: root/src/network/network_coordinator.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/network/network_coordinator.cpp')
-rw-r--r--src/network/network_coordinator.cpp239
1 files changed, 235 insertions, 4 deletions
diff --git a/src/network/network_coordinator.cpp b/src/network/network_coordinator.cpp
index b3d304952..8bd81b6f6 100644
--- a/src/network/network_coordinator.cpp
+++ b/src/network/network_coordinator.cpp
@@ -1,4 +1,3 @@
-
/*
* This file is part of OpenTTD.
* OpenTTD 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, version 2.
@@ -27,13 +26,41 @@
static const auto NETWORK_COORDINATOR_DELAY_BETWEEN_UPDATES = std::chrono::seconds(30); ///< How many time between updates the server sends to the Game Coordinator.
ClientNetworkCoordinatorSocketHandler _network_coordinator_client; ///< The connection to the Game Coordinator.
ConnectionType _network_server_connection_type = CONNECTION_TYPE_UNKNOWN; ///< What type of connection the Game Coordinator detected we are on.
+std::string _network_server_invite_code = ""; ///< Our invite code as indicated by the Game Coordinator.
+
+/** Connect to a game server by IP:port. */
+class NetworkDirectConnecter : public TCPConnecter {
+private:
+ std::string token; ///< Token of this connection.
+ uint8 tracking_number; ///< Tracking number of this connection.
+
+public:
+ /**
+ * Try to establish a direct (hostname:port based) connection.
+ * @param hostname The hostname of the server.
+ * @param port The port of the server.
+ * @param token The token as given by the Game Coordinator to track this connection attempt.
+ * @param tracking_number The tracking number as given by the Game Coordinator to track this connection attempt.
+ */
+ NetworkDirectConnecter(const std::string &hostname, uint16 port, const std::string &token, uint8 tracking_number) : TCPConnecter(hostname, port), token(token), tracking_number(tracking_number) {}
+
+ void OnFailure() override
+ {
+ _network_coordinator_client.ConnectFailure(this->token, this->tracking_number);
+ }
+
+ void OnConnect(SOCKET s) override
+ {
+ _network_coordinator_client.ConnectSuccess(this->token, s);
+ }
+};
/** Connect to the Game Coordinator server. */
class NetworkCoordinatorConnecter : TCPConnecter {
public:
/**
* Initiate the connecting.
- * @param address The address of the Game Coordinator server.
+ * @param connection_string The address of the Game Coordinator server.
*/
NetworkCoordinatorConnecter(const std::string &connection_string) : TCPConnecter(connection_string, NETWORK_COORDINATOR_SERVER_PORT) {}
@@ -73,6 +100,20 @@ bool ClientNetworkCoordinatorSocketHandler::Receive_GC_ERROR(Packet *p)
this->CloseConnection();
return false;
+ case NETWORK_COORDINATOR_ERROR_INVALID_INVITE_CODE: {
+ /* Find the connecter based on the invite code. */
+ auto connecter_it = this->connecter_pre.find(detail);
+ if (connecter_it == this->connecter_pre.end()) return true;
+ this->connecter_pre.erase(connecter_it);
+
+ /* Mark the server as offline. */
+ NetworkGameList *item = NetworkGameListAddItem(detail);
+ item->online = false;
+
+ UpdateNetworkGameWindow();
+ return true;
+ }
+
default:
Debug(net, 0, "Invalid error type {} received from Game Coordinator", error);
this->CloseConnection();
@@ -85,12 +126,21 @@ bool ClientNetworkCoordinatorSocketHandler::Receive_GC_REGISTER_ACK(Packet *p)
/* Schedule sending an update. */
this->next_update = std::chrono::steady_clock::now();
+ _settings_client.network.server_invite_code = p->Recv_string(NETWORK_INVITE_CODE_LENGTH);
+ _settings_client.network.server_invite_code_secret = p->Recv_string(NETWORK_INVITE_CODE_SECRET_LENGTH);
_network_server_connection_type = (ConnectionType)p->Recv_uint8();
if (_network_server_connection_type == CONNECTION_TYPE_ISOLATED) {
ShowErrorMessage(STR_NETWORK_ERROR_COORDINATOR_ISOLATED, STR_NETWORK_ERROR_COORDINATOR_ISOLATED_DETAIL, WL_ERROR);
}
+ /* Users can change the invite code in the settings, but this has no effect
+ * on the invite code as assigned by the server. So
+ * _network_server_invite_code contains the current invite code,
+ * and _settings_client.network.server_invite_code contains the one we will
+ * attempt to re-use when registering again. */
+ _network_server_invite_code = _settings_client.network.server_invite_code;
+
SetWindowDirty(WC_CLIENT_LIST, 0);
if (_network_dedicated) {
@@ -107,7 +157,10 @@ bool ClientNetworkCoordinatorSocketHandler::Receive_GC_REGISTER_ACK(Packet *p)
Debug(net, 3, "Your server is now registered with the Game Coordinator:");
Debug(net, 3, " Game type: Public");
Debug(net, 3, " Connection type: {}", connection_type);
+ Debug(net, 3, " Invite code: {}", _network_server_invite_code);
Debug(net, 3, "----------------------------------------");
+ } else {
+ Debug(net, 3, "Game Coordinator registered our server with invite code '{}'", _network_server_invite_code);
}
return true;
@@ -130,7 +183,7 @@ bool ClientNetworkCoordinatorSocketHandler::Receive_GC_LISTING(Packet *p)
NetworkGameInfo ngi = {};
DeserializeNetworkGameInfo(p, &ngi);
- /* Now we know the join-key, we can add it to our list. */
+ /* Now we know the connection string, we can add it to our list. */
NetworkGameList *item = NetworkGameListAddItem(connection_string);
/* Clear any existing GRFConfig chain. */
@@ -149,6 +202,58 @@ bool ClientNetworkCoordinatorSocketHandler::Receive_GC_LISTING(Packet *p)
return true;
}
+bool ClientNetworkCoordinatorSocketHandler::Receive_GC_CONNECTING(Packet *p)
+{
+ std::string token = p->Recv_string(NETWORK_TOKEN_LENGTH);
+ std::string invite_code = p->Recv_string(NETWORK_INVITE_CODE_LENGTH);
+
+ /* Find the connecter based on the invite code. */
+ auto connecter_it = this->connecter_pre.find(invite_code);
+ if (connecter_it == this->connecter_pre.end()) {
+ this->CloseConnection();
+ return false;
+ }
+
+ /* Now store it based on the token. */
+ this->connecter[token] = connecter_it->second;
+ this->connecter_pre.erase(connecter_it);
+
+ return true;
+}
+
+bool ClientNetworkCoordinatorSocketHandler::Receive_GC_CONNECT_FAILED(Packet *p)
+{
+ std::string token = p->Recv_string(NETWORK_TOKEN_LENGTH);
+
+ auto connecter_it = this->connecter.find(token);
+ if (connecter_it != this->connecter.end()) {
+ connecter_it->second->SetFailure();
+ this->connecter.erase(connecter_it);
+ }
+
+ /* Close all remaining connections. */
+ this->CloseToken(token);
+
+ return true;
+}
+
+bool ClientNetworkCoordinatorSocketHandler::Receive_GC_DIRECT_CONNECT(Packet *p)
+{
+ std::string token = p->Recv_string(NETWORK_TOKEN_LENGTH);
+ uint8 tracking_number = p->Recv_uint8();
+ std::string hostname = p->Recv_string(NETWORK_HOSTNAME_LENGTH);
+ uint16 port = p->Recv_uint16();
+
+ /* Ensure all other pending connection attempts are killed. */
+ if (this->game_connecter != nullptr) {
+ this->game_connecter->Kill();
+ this->game_connecter = nullptr;
+ }
+
+ this->game_connecter = new NetworkDirectConnecter(hostname, port, token, tracking_number);
+ return true;
+}
+
void ClientNetworkCoordinatorSocketHandler::Connect()
{
/* We are either already connected or are trying to connect. */
@@ -172,13 +277,15 @@ NetworkRecvStatus ClientNetworkCoordinatorSocketHandler::CloseConnection(bool er
_network_server_connection_type = CONNECTION_TYPE_UNKNOWN;
this->next_update = {};
+ this->CloseAllTokens();
+
SetWindowDirty(WC_CLIENT_LIST, 0);
return NETWORK_RECV_STATUS_OKAY;
}
/**
- * Register our server to receive our join-key.
+ * Register our server to receive our invite code.
*/
void ClientNetworkCoordinatorSocketHandler::Register()
{
@@ -193,6 +300,13 @@ void ClientNetworkCoordinatorSocketHandler::Register()
p->Send_uint8(NETWORK_COORDINATOR_VERSION);
p->Send_uint8(SERVER_GAME_TYPE_PUBLIC);
p->Send_uint16(_settings_client.network.server_port);
+ if (_settings_client.network.server_invite_code.empty() || _settings_client.network.server_invite_code_secret.empty()) {
+ p->Send_string("");
+ p->Send_string("");
+ } else {
+ p->Send_string(_settings_client.network.server_invite_code);
+ p->Send_string(_settings_client.network.server_invite_code_secret);
+ }
this->SendPacket(p);
}
@@ -230,6 +344,123 @@ void ClientNetworkCoordinatorSocketHandler::GetListing()
}
/**
+ * Join a server based on an invite code.
+ * @param invite_code The invite code of the server to connect to.
+ * @param connecter The connecter of the request.
+ */
+void ClientNetworkCoordinatorSocketHandler::ConnectToServer(const std::string &invite_code, TCPServerConnecter *connecter)
+{
+ assert(StrStartsWith(invite_code, "+"));
+
+ if (this->connecter_pre.find(invite_code) != this->connecter_pre.end()) {
+ /* If someone is hammering the refresh key, one can sent out two
+ * requests for the same invite code. There isn't really a great way
+ * of handling this, so just ignore this request. */
+ connecter->SetFailure();
+ return;
+ }
+
+ /* Initially we store based on invite code; on first reply we know the
+ * token, and will start using that key instead. */
+ this->connecter_pre[invite_code] = connecter;
+
+ this->Connect();
+
+ Packet *p = new Packet(PACKET_COORDINATOR_CLIENT_CONNECT);
+ p->Send_uint8(NETWORK_COORDINATOR_VERSION);
+ p->Send_string(invite_code);
+
+ this->SendPacket(p);
+}
+
+/**
+ * Callback from a Connecter to let the Game Coordinator know the connection failed.
+ * @param token Token of the connecter that failed.
+ * @param tracking_number Tracking number of the connecter that failed.
+ */
+void ClientNetworkCoordinatorSocketHandler::ConnectFailure(const std::string &token, uint8 tracking_number)
+{
+ /* Connecter will destroy itself. */
+ this->game_connecter = nullptr;
+
+ Packet *p = new Packet(PACKET_COORDINATOR_SERCLI_CONNECT_FAILED);
+ p->Send_uint8(NETWORK_COORDINATOR_VERSION);
+ p->Send_string(token);
+ p->Send_uint8(tracking_number);
+
+ this->SendPacket(p);
+
+ auto connecter_it = this->connecter.find(token);
+ assert(connecter_it != this->connecter.end());
+
+ connecter_it->second->SetFailure();
+ this->connecter.erase(connecter_it);
+}
+
+/**
+ * Callback from a Connecter to let the Game Coordinator know the connection
+ * to the game server is established.
+ * @param token Token of the connecter that succeeded.
+ * @param sock The socket that the connecter can now use.
+ */
+void ClientNetworkCoordinatorSocketHandler::ConnectSuccess(const std::string &token, SOCKET sock)
+{
+ /* Connecter will destroy itself. */
+ this->game_connecter = nullptr;
+
+ assert(!_network_server);
+
+ Packet *p = new Packet(PACKET_COORDINATOR_CLIENT_CONNECTED);
+ p->Send_uint8(NETWORK_COORDINATOR_VERSION);
+ p->Send_string(token);
+ this->SendPacket(p);
+
+ auto connecter_it = this->connecter.find(token);
+ assert(connecter_it != this->connecter.end());
+
+ connecter_it->second->SetConnected(sock);
+ this->connecter.erase(connecter_it);
+
+ /* Close all remaining connections. */
+ this->CloseToken(token);
+}
+
+/**
+ * Close everything related to this connection token.
+ * @param token The connection token to close.
+ */
+void ClientNetworkCoordinatorSocketHandler::CloseToken(const std::string &token)
+{
+ /* Ensure all other pending connection attempts are also killed. */
+ if (this->game_connecter != nullptr) {
+ this->game_connecter->Kill();
+ this->game_connecter = nullptr;
+ }
+}
+
+/**
+ * Close all pending connection tokens.
+ */
+void ClientNetworkCoordinatorSocketHandler::CloseAllTokens()
+{
+ /* Ensure all other pending connection attempts are also killed. */
+ if (this->game_connecter != nullptr) {
+ this->game_connecter->Kill();
+ this->game_connecter = nullptr;
+ }
+
+ /* Mark any pending connecters as failed. */
+ for (auto &[token, it] : this->connecter) {
+ it->SetFailure();
+ }
+ for (auto &[invite_code, it] : this->connecter_pre) {
+ it->SetFailure();
+ }
+ this->connecter.clear();
+ this->connecter_pre.clear();
+}
+
+/**
* Check whether we received/can send some data from/to the Game Coordinator server and
* when that's the case handle it appropriately.
*/