diff --git a/openrct2.vcxproj b/openrct2.vcxproj index bf2b6d596d..91d70e7c95 100644 --- a/openrct2.vcxproj +++ b/openrct2.vcxproj @@ -119,6 +119,7 @@ + @@ -438,6 +439,7 @@ + diff --git a/src/network/NetworkServerAdvertiser.cpp b/src/network/NetworkServerAdvertiser.cpp new file mode 100644 index 0000000000..b8629e943d --- /dev/null +++ b/src/network/NetworkServerAdvertiser.cpp @@ -0,0 +1,258 @@ +#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers +/***************************************************************************** + * OpenRCT2, an open source clone of Roller Coaster Tycoon 2. + * + * OpenRCT2 is the work of many authors, a full list can be found in contributors.md + * For more information, visit https://github.com/OpenRCT2/OpenRCT2 + * + * OpenRCT2 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, either version 3 of the License, or + * (at your option) any later version. + * + * A full copy of the GNU General Public License can be found in licence.txt + *****************************************************************************/ +#pragma endregion + +#ifndef DISABLE_NETWORK + +#include +#include "../core/Console.hpp" +#include "../core/String.hpp" +#include "../core/Util.hpp" +#include "network.h" +#include "NetworkServerAdvertiser.h" + +extern "C" +{ + #include "../addresses.h" + #include "../config.h" + #include "../localisation/date.h" + #include "../management/finance.h" + #include "../peep/peep.h" + #include "../world/map.h" + #include "../world/park.h" + #include "http.h" +} + +enum MASTER_SERVER_STATUS +{ + MASTER_SERVER_STATUS_OK = 200, + MASTER_SERVER_STATUS_INVALID_TOKEN = 401, + MASTER_SERVER_STATUS_SERVER_NOT_FOUND = 404, + MASTER_SERVER_STATUS_INTERNAL_ERROR = 500 +}; + +constexpr int MASTER_SERVER_REGISTER_TIME = 120 * 1000; // 2 minutes +constexpr int MASTER_SERVER_HEARTBEAT_TIME = 60 * 1000; // 1 minute + +class NetworkServerAdvertiser : public INetworkServerAdvertiser +{ +private: + uint16 _port; + + ADVERTISE_STATUS _status = ADVERTISE_STATUS_UNREGISTERED; + uint32 _lastAdvertiseTime = 0; + uint32 _lastHeartbeatTime = 0; + + // Our unique token for this server + std::string _token; + + // Key received from the master server + std::string _key; + +public: + NetworkServerAdvertiser(uint16 port) + { + _port = port; + _key = GenerateAdvertiseKey(); + } + + ADVERTISE_STATUS GetStatus() override + { + return _status; + } + + void Update() override + { + switch (_status) { + case ADVERTISE_STATUS_UNREGISTERED: + if (_lastAdvertiseTime == 0 || SDL_TICKS_PASSED(SDL_GetTicks(), _lastAdvertiseTime + MASTER_SERVER_REGISTER_TIME)) + { + SendRegistration(); + } + break; + case ADVERTISE_STATUS_REGISTERED: + if (SDL_TICKS_PASSED(SDL_GetTicks(), _lastHeartbeatTime + MASTER_SERVER_HEARTBEAT_TIME)) + { + SendHeartbeat(); + } + break; + // exhaust enum values to satisfy clang + case ADVERTISE_STATUS_DISABLED: + break; + } + } + +private: + void SendRegistration() + { + _lastAdvertiseTime = SDL_GetTicks(); + + // Send the registration request + http_json_request request; + request.tag = this; + request.url = GetMasterServerUrl(); + request.method = HTTP_METHOD_POST; + + json_t *body = json_object(); + json_object_set_new(body, "key", json_string(_key.c_str())); + json_object_set_new(body, "port", json_integer(_port)); + request.body = body; + + http_request_json_async(&request, [](http_json_response * response) -> void + { + if (response == nullptr) + { + Console::WriteLine("Unable to connect to master server"); + } + else + { + auto advertiser = (NetworkServerAdvertiser *)response->tag; + advertiser->OnRegistrationResponse(response->root); + http_request_json_dispose(response); + } + }); + + json_decref(body); + } + + void SendHeartbeat() + { + http_json_request request; + request.tag = this; + request.url = GetMasterServerUrl(); + request.method = HTTP_METHOD_PUT; + + json_t * jsonBody = GetHeartbeatJson(); + request.body = jsonBody; + + _lastHeartbeatTime = SDL_GetTicks(); + http_request_json_async(&request, [](http_json_response *response) -> void + { + if (response == nullptr) + { + log_warning("Unable to connect to master server"); + } + else + { + auto advertiser = (NetworkServerAdvertiser *)response->tag; + advertiser->OnHeartbeatResponse(response->root); + http_request_json_dispose(response); + } + }); + + json_decref(jsonBody); + } + + void OnRegistrationResponse(json_t * jsonRoot) + { + json_t *jsonStatus = json_object_get(jsonRoot, "status"); + if (json_is_integer(jsonStatus)) + { + int status = (int)json_integer_value(jsonStatus); + if (status == MASTER_SERVER_STATUS_OK) + { + json_t * jsonToken = json_object_get(jsonRoot, "token"); + if (json_is_string(jsonToken)) + { + _token = std::string(json_string_value(jsonToken)); + _status = ADVERTISE_STATUS_REGISTERED; + } + } + else + { + const char * message = "Invalid response from server"; + json_t * jsonMessage = json_object_get(jsonRoot, "message"); + if (json_is_string(jsonMessage)) + { + message = json_string_value(jsonMessage); + } + Console::Error::WriteLine("Unable to advertise: %s", message); + } + } + } + + void OnHeartbeatResponse(json_t * jsonRoot) + { + json_t *jsonStatus = json_object_get(jsonRoot, "status"); + if (json_is_integer(jsonStatus)) + { + int status = (int)json_integer_value(jsonStatus); + if (status == MASTER_SERVER_STATUS_OK) + { + // Master server has successfully updated our server status + } + else if (status == MASTER_SERVER_STATUS_INVALID_TOKEN) + { + _status = ADVERTISE_STATUS_UNREGISTERED; + Console::WriteLine("Master server heartbeat failed: Invalid Token"); + } + } + } + + json_t * GetHeartbeatJson() + { + uint32 numPlayers = network_get_num_players(); + + json_t * root = json_object(); + json_object_set_new(root, "token", json_string(_token.c_str())); + json_object_set_new(root, "players", json_integer(numPlayers)); + + json_t * gameInfo = json_object(); + json_object_set_new(gameInfo, "mapSize", json_integer(gMapSize - 2)); + json_object_set_new(gameInfo, "day", json_integer(gDateMonthTicks)); + json_object_set_new(gameInfo, "month", json_integer(gDateMonthsElapsed)); + json_object_set_new(gameInfo, "guests", json_integer(gNumGuestsInPark)); + json_object_set_new(gameInfo, "parkValue", json_integer(gParkValue)); + if (!(gParkFlags & PARK_FLAGS_NO_MONEY)) + { + money32 cash = DECRYPT_MONEY(gCashEncrypted); + json_object_set_new(gameInfo, "cash", json_integer(cash)); + } + json_object_set_new(root, "gameInfo", gameInfo); + + return root; + } + + static std::string GenerateAdvertiseKey() + { + // Generate a string of 16 random hex characters (64-integer key as a hex formatted string) + static const char hexChars[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; + char key[17]; + for (int i = 0; i < 16; i++) + { + int hexCharIndex = rand() % Util::CountOf(hexChars); + key[i] = hexChars[hexCharIndex]; + } + key[Util::CountOf(key) - 1] = 0; + return key; + } + + static const char * GetMasterServerUrl() + { + const char * result = OPENRCT2_MASTER_SERVER_URL; + if (!String::IsNullOrEmpty(gConfigNetwork.master_server_url)) + { + result = gConfigNetwork.master_server_url; + } + return result; + } +}; + +INetworkServerAdvertiser * CreateServerAdvertiser(uint16 port) +{ + return new NetworkServerAdvertiser(port); +} + +#endif // DISABLE_NETWORK diff --git a/src/network/NetworkServerAdvertiser.h b/src/network/NetworkServerAdvertiser.h new file mode 100644 index 0000000000..b72bb3f60e --- /dev/null +++ b/src/network/NetworkServerAdvertiser.h @@ -0,0 +1,36 @@ +#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers +/***************************************************************************** + * OpenRCT2, an open source clone of Roller Coaster Tycoon 2. + * + * OpenRCT2 is the work of many authors, a full list can be found in contributors.md + * For more information, visit https://github.com/OpenRCT2/OpenRCT2 + * + * OpenRCT2 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, either version 3 of the License, or + * (at your option) any later version. + * + * A full copy of the GNU General Public License can be found in licence.txt + *****************************************************************************/ +#pragma endregion + +#pragma once + +#include "../common.h" + +enum ADVERTISE_STATUS +{ + ADVERTISE_STATUS_DISABLED, + ADVERTISE_STATUS_UNREGISTERED, + ADVERTISE_STATUS_REGISTERED, +}; + +interface INetworkServerAdvertiser +{ + virtual ~INetworkServerAdvertiser() { } + + virtual ADVERTISE_STATUS GetStatus() abstract; + virtual void Update() abstract; +}; + +INetworkServerAdvertiser * CreateServerAdvertiser(uint16 port); diff --git a/src/network/http.cpp b/src/network/http.cpp index 3b8216c342..49878ceb4d 100644 --- a/src/network/http.cpp +++ b/src/network/http.cpp @@ -191,6 +191,7 @@ http_json_response *http_request_json(const http_json_request *request) root = json_loads(writeBuffer.ptr, 0, &error); if (root != NULL) { response = (http_json_response*)malloc(sizeof(http_json_response)); + response->tag = request->tag; response->status_code = (int)httpStatusCode; response->root = root; } @@ -209,6 +210,7 @@ void http_request_json_async(const http_json_request *request, void (*callback)( args->request.url = _strdup(request->url); args->request.method = request->method; args->request.body = json_deep_copy(request->body); + args->request.tag = request->tag; args->callback = callback; SDL_Thread *thread = SDL_CreateThread([](void *ptr) -> int { diff --git a/src/network/http.h b/src/network/http.h index c6c8a87284..0a54635281 100644 --- a/src/network/http.h +++ b/src/network/http.h @@ -22,12 +22,14 @@ #include "../common.h" typedef struct http_json_request { + void *tag; const char *method; const char *url; const json_t *body; } http_json_request; typedef struct http_json_response { + void *tag; int status_code; json_t *root; } http_json_response; diff --git a/src/network/network.cpp b/src/network/network.cpp index 1b90d43a70..eca1c96643 100644 --- a/src/network/network.cpp +++ b/src/network/network.cpp @@ -73,12 +73,6 @@ extern "C" { Network gNetwork; -enum { - ADVERTISE_STATUS_DISABLED, - ADVERTISE_STATUS_UNREGISTERED, - ADVERTISE_STATUS_REGISTERED -}; - enum { MASTER_SERVER_STATUS_OK = 200, MASTER_SERVER_STATUS_INVALID_TOKEN = 401, @@ -108,7 +102,6 @@ Network::Network() status = NETWORK_STATUS_NONE; last_tick_sent_time = 0; last_ping_sent_time = 0; - last_advertise_time = 0; client_command_handlers.resize(NETWORK_COMMAND_MAX, 0); client_command_handlers[NETWORK_COMMAND_AUTH] = &Network::Client_Handle_AUTH; client_command_handlers[NETWORK_COMMAND_MAP] = &Network::Client_Handle_MAP; @@ -178,6 +171,7 @@ void Network::Close() } else if (mode == NETWORK_MODE_SERVER) { delete listening_socket; listening_socket = nullptr; + delete _advertiser; } mode = NETWORK_MODE_NONE; @@ -323,17 +317,9 @@ bool Network::BeginServer(unsigned short port, const char* address) status = NETWORK_STATUS_CONNECTED; listening_port = port; - advertise_status = ADVERTISE_STATUS_DISABLED; - last_advertise_time = 0; - last_heartbeat_time = 0; - advertise_token = ""; - advertise_key = GenerateAdvertiseKey(); - -#ifndef DISABLE_HTTP if (gConfigNetwork.advertise) { - advertise_status = ADVERTISE_STATUS_UNREGISTERED; + _advertiser = CreateServerAdvertiser(listening_port); } -#endif return true; } @@ -400,17 +386,8 @@ void Network::UpdateServer() Server_Send_PINGLIST(); } - switch (advertise_status) { - case ADVERTISE_STATUS_UNREGISTERED: - if (last_advertise_time == 0 || SDL_TICKS_PASSED(SDL_GetTicks(), last_advertise_time + MASTER_SERVER_REGISTER_TIME)) { - AdvertiseRegister(); - } - break; - case ADVERTISE_STATUS_REGISTERED: - if (SDL_TICKS_PASSED(SDL_GetTicks(), last_heartbeat_time + MASTER_SERVER_HEARTBEAT_TIME)) { - AdvertiseHeartbeat(); - } - break; + if (_advertiser != nullptr) { + _advertiser->Update(); } ITcpSocket * tcpSocket = listening_socket->Accept(); @@ -652,102 +629,6 @@ const char *Network::GetMasterServerUrl() } } -void Network::AdvertiseRegister() -{ -#ifndef DISABLE_HTTP - last_advertise_time = SDL_GetTicks(); - - // Send the registration request - http_json_request request; - request.url = GetMasterServerUrl(); - request.method = HTTP_METHOD_POST; - - json_t *body = json_object(); - json_object_set_new(body, "key", json_string(advertise_key.c_str())); - json_object_set_new(body, "port", json_integer(listening_port)); - request.body = body; - - http_request_json_async(&request, [](http_json_response *response) -> void { - if (response == NULL) { - log_warning("Unable to connect to master server"); - return; - } - - json_t *jsonStatus = json_object_get(response->root, "status"); - if (json_is_integer(jsonStatus)) { - int status = (int)json_integer_value(jsonStatus); - if (status == MASTER_SERVER_STATUS_OK) { - json_t *jsonToken = json_object_get(response->root, "token"); - if (json_is_string(jsonToken)) { - gNetwork.advertise_token = json_string_value(jsonToken); - gNetwork.advertise_status = ADVERTISE_STATUS_REGISTERED; - } - } else { - const char *message = "Invalid response from server"; - json_t *jsonMessage = json_object_get(response->root, "message"); - if (json_is_string(jsonMessage)) { - message = json_string_value(jsonMessage); - } - log_warning("Unable to advertise: %s", message); - } - } - http_request_json_dispose(response); - }); - - json_decref(body); -#endif -} - -void Network::AdvertiseHeartbeat() -{ -#ifndef DISABLE_HTTP - // Send the heartbeat request - http_json_request request; - request.url = GetMasterServerUrl(); - request.method = HTTP_METHOD_PUT; - - json_t *body = json_object(); - json_object_set_new(body, "token", json_string(advertise_token.c_str())); - json_object_set_new(body, "players", json_integer(network_get_num_players())); - - json_t *gameInfo = json_object(); - json_object_set_new(gameInfo, "mapSize", json_integer(gMapSize - 2)); - json_object_set_new(gameInfo, "day", json_integer(gDateMonthTicks)); - json_object_set_new(gameInfo, "month", json_integer(gDateMonthsElapsed)); - json_object_set_new(gameInfo, "guests", json_integer(gNumGuestsInPark)); - json_object_set_new(gameInfo, "parkValue", json_integer(gParkValue)); - if (!(gParkFlags & PARK_FLAGS_NO_MONEY)) { - money32 cash = DECRYPT_MONEY(gCashEncrypted); - json_object_set_new(gameInfo, "cash", json_integer(cash)); - } - - json_object_set_new(body, "gameInfo", gameInfo); - request.body = body; - - gNetwork.last_heartbeat_time = SDL_GetTicks(); - http_request_json_async(&request, [](http_json_response *response) -> void { - if (response == NULL) { - log_warning("Unable to connect to master server"); - return; - } - - json_t *jsonStatus = json_object_get(response->root, "status"); - if (json_is_integer(jsonStatus)) { - int status = (int)json_integer_value(jsonStatus); - if (status == MASTER_SERVER_STATUS_OK) { - // Master server has successfully updated our server status - } else if (status == MASTER_SERVER_STATUS_INVALID_TOKEN) { - gNetwork.advertise_status = ADVERTISE_STATUS_UNREGISTERED; - log_warning("Master server heartbeat failed: Invalid Token"); - } - } - http_request_json_dispose(response); - }); - - json_decref(body); -#endif -} - NetworkGroup* Network::AddGroup() { NetworkGroup* addedgroup = nullptr; diff --git a/src/network/network.h b/src/network/network.h index 2d65e0d859..499be32a5f 100644 --- a/src/network/network.h +++ b/src/network/network.h @@ -75,6 +75,7 @@ extern "C" { #include "NetworkKey.h" #include "NetworkPacket.h" #include "NetworkPlayer.h" +#include "NetworkServerAdvertiser.h" #include "NetworkUser.h" #include "TcpSocket.h" @@ -107,8 +108,6 @@ public: void KickPlayer(int playerId); void SetPassword(const char* password); void ShutdownClient(); - void AdvertiseRegister(); - void AdvertiseHeartbeat(); NetworkGroup* AddGroup(); void RemoveGroup(uint8 id); uint8 GetDefaultGroup(); @@ -204,12 +203,8 @@ private: std::vector chunk_buffer; std::string password; bool _desynchronised = false; + INetworkServerAdvertiser * _advertiser = nullptr; uint32 server_connect_time = 0; - uint32 last_advertise_time = 0; - std::string advertise_token; - std::string advertise_key; - int advertise_status = 0; - uint32 last_heartbeat_time = 0; uint8 default_group = 0; SDL_RWops *_chatLogStream; std::string _chatLogPath;