1
0
mirror of https://github.com/OpenRCT2/OpenRCT2 synced 2026-01-16 19:43:06 +01:00
Files
OpenRCT2/src/openrct2/network/NetworkServerAdvertiser.cpp
2018-06-11 11:19:04 +02:00

283 lines
9.1 KiB
C++

#pragma region Copyright (c) 2014-2018 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 <string>
#include "../core/Console.hpp"
#include "../core/String.hpp"
#include "../core/Util.hpp"
#include "network.h"
#include "NetworkServerAdvertiser.h"
#include "../config/Config.h"
#include "../localisation/Date.h"
#include "../management/Finance.h"
#include "../peep/Peep.h"
#include "../platform/platform.h"
#include "../util/Util.h"
#include "../world/Map.h"
#include "../world/Park.h"
#include "Http.h"
using namespace OpenRCT2::Network;
#ifndef DISABLE_HTTP
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 sint32 MASTER_SERVER_REGISTER_TIME = 120 * 1000; // 2 minutes
constexpr sint32 MASTER_SERVER_HEARTBEAT_TIME = 60 * 1000; // 1 minute
class NetworkServerAdvertiser final : 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;
// See https://github.com/OpenRCT2/OpenRCT2/issues/6277 and 4953
bool _forceIPv4 = false;
public:
explicit NetworkServerAdvertiser(uint16 port)
{
_port = port;
_key = GenerateAdvertiseKey();
}
ADVERTISE_STATUS GetStatus() const override
{
return _status;
}
void Update() override
{
switch (_status) {
case ADVERTISE_STATUS::UNREGISTERED:
if (_lastAdvertiseTime == 0 || platform_get_ticks() > _lastAdvertiseTime + MASTER_SERVER_REGISTER_TIME)
{
SendRegistration(_forceIPv4);
}
break;
case ADVERTISE_STATUS::REGISTERED:
if (platform_get_ticks() > _lastHeartbeatTime + MASTER_SERVER_HEARTBEAT_TIME)
{
SendHeartbeat();
}
break;
// exhaust enum values to satisfy clang
case ADVERTISE_STATUS::DISABLED:
break;
}
}
private:
void SendRegistration(bool forceIPv4)
{
_lastAdvertiseTime = platform_get_ticks();
// Send the registration request
Http::Request request;
request.url = GetMasterServerUrl();
request.method = Http::Method::POST;
request.forceIPv4 = forceIPv4;
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 = json_dumps(body, JSON_COMPACT);
request.header["Content-Type"] = "application/json";
Http::DoAsync(request, [&](Http::Response response) -> void {
if (response.status != Http::Status::OK)
{
Console::WriteLine("Unable to connect to master server");
return;
}
json_t * root = Json::FromString(response.body);
this->OnRegistrationResponse(root);
});
json_decref(body);
}
void SendHeartbeat()
{
Http::Request request;
request.url = GetMasterServerUrl();
request.method = Http::Method::POST;
json_t * body = GetHeartbeatJson();
request.body = json_dumps(body, JSON_COMPACT);
request.header["Content-Type"] = "application/json";
_lastHeartbeatTime = platform_get_ticks();
Http::DoAsync(request, [&](Http::Response response) -> void {
if (response.status != Http::Status::OK)
{
Console::WriteLine("Unable to connect to master server");
return;
}
json_t * root = Json::FromString(response.body);
this->OnHeartbeatResponse(root);
});
json_decref(body);
}
void OnRegistrationResponse(json_t * jsonRoot)
{
json_t *jsonStatus = json_object_get(jsonRoot, "status");
if (json_is_integer(jsonStatus))
{
sint32 status = (sint32)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 (%d): %s", status, message);
// Hack for https://github.com/OpenRCT2/OpenRCT2/issues/6277
// Master server may not reply correctly if using IPv6, retry forcing IPv4,
// don't wait the full timeout.
if (!_forceIPv4 && status == 500)
{
_forceIPv4 = true;
_lastAdvertiseTime = 0;
log_info("Retry with ipv4 only");
}
}
}
}
void OnHeartbeatResponse(json_t * jsonRoot)
{
json_t *jsonStatus = json_object_get(jsonRoot, "status");
if (json_is_integer(jsonStatus))
{
sint32 status = (sint32)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))
{
json_object_set_new(gameInfo, "cash", json_integer(gCash));
}
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 constexpr const char hexChars[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
char key[17];
for (sint32 i = 0; i < 16; i++)
{
sint32 hexCharIndex = util_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);
}
#else // DISABLE_HTTP
class DummyNetworkServerAdvertiser final : public INetworkServerAdvertiser
{
public:
virtual ADVERTISE_STATUS GetStatus() const override { return ADVERTISE_STATUS::DISABLED; };
virtual void Update() override {};
};
INetworkServerAdvertiser * CreateServerAdvertiser(uint16 port)
{
return new DummyNetworkServerAdvertiser();
}
#endif // DISABLE_HTTP
#endif // DISABLE_NETWORK