1
0
Fork 0
mirror of https://github.com/luanti-org/luanti.git synced 2025-08-21 18:11:11 +00:00

Move some files to src/server/

This commit is contained in:
sfan5 2024-01-24 19:13:03 +01:00
parent c0f852e016
commit 5dbc1d4c08
15 changed files with 12 additions and 12 deletions

View file

@ -1,9 +1,13 @@
set(server_SRCS
${CMAKE_CURRENT_SOURCE_DIR}/activeobjectmgr.cpp
${CMAKE_CURRENT_SOURCE_DIR}/ban.cpp
${CMAKE_CURRENT_SOURCE_DIR}/clientiface.cpp
${CMAKE_CURRENT_SOURCE_DIR}/luaentity_sao.cpp
${CMAKE_CURRENT_SOURCE_DIR}/mods.cpp
${CMAKE_CURRENT_SOURCE_DIR}/player_sao.cpp
${CMAKE_CURRENT_SOURCE_DIR}/serveractiveobject.cpp
${CMAKE_CURRENT_SOURCE_DIR}/serverinventorymgr.cpp
${CMAKE_CURRENT_SOURCE_DIR}/serverlist.cpp
${CMAKE_CURRENT_SOURCE_DIR}/unit_sao.cpp
${CMAKE_CURRENT_SOURCE_DIR}/rollback.cpp
PARENT_SCOPE)

142
src/server/ban.cpp Normal file
View file

@ -0,0 +1,142 @@
/*
Minetest
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
Copyright (C) 2018 nerzhul, Loic BLOT <loic.blot@unix-experience.fr>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "ban.h"
#include <fstream>
#include "threading/mutex_auto_lock.h"
#include <sstream>
#include <set>
#include "util/strfnd.h"
#include "util/string.h"
#include "log.h"
#include "filesys.h"
BanManager::BanManager(const std::string &banfilepath):
m_banfilepath(banfilepath)
{
try {
load();
} catch(SerializationError &e) {
infostream << "BanManager: creating "
<< m_banfilepath << std::endl;
}
}
BanManager::~BanManager()
{
save();
}
void BanManager::load()
{
MutexAutoLock lock(m_mutex);
infostream<<"BanManager: loading from "<<m_banfilepath<<std::endl;
std::ifstream is(m_banfilepath.c_str(), std::ios::binary);
if (!is.good()) {
infostream<<"BanManager: failed loading from "<<m_banfilepath<<std::endl;
throw SerializationError("BanManager::load(): Couldn't open file");
}
while (!is.eof() && is.good()) {
std::string line;
std::getline(is, line, '\n');
Strfnd f(line);
std::string ip = trim(f.next("|"));
std::string name = trim(f.next("|"));
if(!ip.empty()) {
m_ips[ip] = name;
}
}
m_modified = false;
}
void BanManager::save()
{
MutexAutoLock lock(m_mutex);
infostream << "BanManager: saving to " << m_banfilepath << std::endl;
std::ostringstream ss(std::ios_base::binary);
for (const auto &ip : m_ips)
ss << ip.first << "|" << ip.second << "\n";
if (!fs::safeWriteToFile(m_banfilepath, ss.str())) {
infostream << "BanManager: failed saving to " << m_banfilepath << std::endl;
throw SerializationError("BanManager::save(): Couldn't write file");
}
m_modified = false;
}
bool BanManager::isIpBanned(const std::string &ip)
{
MutexAutoLock lock(m_mutex);
return m_ips.find(ip) != m_ips.end();
}
std::string BanManager::getBanDescription(const std::string &ip_or_name)
{
MutexAutoLock lock(m_mutex);
std::string s;
for (const auto &ip : m_ips) {
if (ip.first == ip_or_name || ip.second == ip_or_name
|| ip_or_name.empty()) {
s += ip.first + "|" + ip.second + ", ";
}
}
s = s.substr(0, s.size() - 2);
return s;
}
std::string BanManager::getBanName(const std::string &ip)
{
MutexAutoLock lock(m_mutex);
StringMap::iterator it = m_ips.find(ip);
if (it == m_ips.end())
return "";
return it->second;
}
void BanManager::add(const std::string &ip, const std::string &name)
{
MutexAutoLock lock(m_mutex);
m_ips[ip] = name;
m_modified = true;
}
void BanManager::remove(const std::string &ip_or_name)
{
MutexAutoLock lock(m_mutex);
for (StringMap::iterator it = m_ips.begin(); it != m_ips.end();) {
if ((it->first == ip_or_name) || (it->second == ip_or_name)) {
m_ips.erase(it++);
m_modified = true;
} else {
++it;
}
}
}
bool BanManager::isModified()
{
MutexAutoLock lock(m_mutex);
return m_modified;
}

49
src/server/ban.h Normal file
View file

@ -0,0 +1,49 @@
/*
Minetest
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#pragma once
#include "util/string.h"
#include "threading/thread.h"
#include "exceptions.h"
#include <map>
#include <string>
#include <mutex>
class BanManager
{
public:
BanManager(const std::string &banfilepath);
~BanManager();
void load();
void save();
bool isIpBanned(const std::string &ip);
// Supplying ip_or_name = "" lists all bans.
std::string getBanDescription(const std::string &ip_or_name);
std::string getBanName(const std::string &ip);
void add(const std::string &ip, const std::string &name);
void remove(const std::string &ip_or_name);
bool isModified();
private:
std::mutex m_mutex;
std::string m_banfilepath = "";
StringMap m_ips;
bool m_modified = false;
};

963
src/server/clientiface.cpp Normal file
View file

@ -0,0 +1,963 @@
/*
Minetest
Copyright (C) 2010-2014 celeron55, Perttu Ahola <celeron55@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <sstream>
#include "clientiface.h"
#include "debug.h"
#include "network/connection.h"
#include "network/serveropcodes.h"
#include "remoteplayer.h"
#include "settings.h"
#include "mapblock.h"
#include "serverenvironment.h"
#include "map.h"
#include "emerge.h"
#include "server/luaentity_sao.h"
#include "server/player_sao.h"
#include "log.h"
#include "util/srp.h"
#include "util/string.h"
#include "face_position_cache.h"
static std::string string_sanitize_ascii(const std::string &s, u32 max_length)
{
std::string out;
for (char c : s) {
if (out.size() >= max_length)
break;
if (c > 32 && c < 127)
out.push_back(c);
}
return out;
}
const char *ClientInterface::statenames[] = {
"Invalid",
"Disconnecting",
"Denied",
"Created",
"HelloSent",
"AwaitingInit2",
"InitDone",
"DefinitionsSent",
"Active",
"SudoMode",
};
std::string ClientInterface::state2Name(ClientState state)
{
return statenames[state];
}
RemoteClient::RemoteClient() :
m_max_simul_sends(g_settings->getU16("max_simultaneous_block_sends_per_client")),
m_min_time_from_building(
g_settings->getFloat("full_block_send_enable_min_time_from_building")),
m_max_send_distance(g_settings->getS16("max_block_send_distance")),
m_block_optimize_distance(g_settings->getS16("block_send_optimize_distance")),
m_block_cull_optimize_distance(g_settings->getS16("block_cull_optimize_distance")),
m_max_gen_distance(g_settings->getS16("max_block_generate_distance")),
m_occ_cull(g_settings->getBool("server_side_occlusion_culling"))
{
}
void RemoteClient::ResendBlockIfOnWire(v3s16 p)
{
// if this block is on wire, mark it for sending again as soon as possible
if (m_blocks_sending.find(p) != m_blocks_sending.end()) {
SetBlockNotSent(p);
}
}
LuaEntitySAO *getAttachedObject(PlayerSAO *sao, ServerEnvironment *env)
{
if (!sao->isAttached())
return nullptr;
int id;
std::string bone;
v3f dummy;
bool force_visible;
sao->getAttachment(&id, &bone, &dummy, &dummy, &force_visible);
ServerActiveObject *ao = env->getActiveObject(id);
while (id && ao) {
ao->getAttachment(&id, &bone, &dummy, &dummy, &force_visible);
if (id)
ao = env->getActiveObject(id);
}
return dynamic_cast<LuaEntitySAO *>(ao);
}
void RemoteClient::GetNextBlocks (
ServerEnvironment *env,
EmergeManager * emerge,
float dtime,
std::vector<PrioritySortedBlockTransfer> &dest)
{
// Increment timers
m_nothing_to_send_pause_timer -= dtime;
m_map_send_completion_timer += dtime;
if (m_map_send_completion_timer > g_settings->getFloat("server_unload_unused_data_timeout") * 0.8f) {
infostream << "Server: Player " << m_name << ", peer_id=" << peer_id
<< ": full map send is taking too long ("
<< m_map_send_completion_timer
<< "s), restarting to avoid visible blocks being unloaded."
<< std::endl;
m_map_send_completion_timer = 0.0f;
m_nearest_unsent_d = 0;
}
if (m_nothing_to_send_pause_timer >= 0)
return;
RemotePlayer *player = env->getPlayer(peer_id);
// This can happen sometimes; clients and players are not in perfect sync.
if (!player)
return;
PlayerSAO *sao = player->getPlayerSAO();
if (!sao)
return;
// Won't send anything if already sending
if (m_blocks_sending.size() >= m_max_simul_sends) {
//infostream<<"Not sending any blocks, Queue full."<<std::endl;
return;
}
v3f playerpos = sao->getBasePosition();
// if the player is attached, get the velocity from the attached object
LuaEntitySAO *lsao = getAttachedObject(sao, env);
const v3f &playerspeed = lsao? lsao->getVelocity() : player->getSpeed();
v3f playerspeeddir(0,0,0);
if (playerspeed.getLength() > 1.0f * BS)
playerspeeddir = playerspeed / playerspeed.getLength();
// Predict to next block
v3f playerpos_predicted = playerpos + playerspeeddir * (MAP_BLOCKSIZE * BS);
v3s16 center_nodepos = floatToInt(playerpos_predicted, BS);
v3s16 center = getNodeBlockPos(center_nodepos);
// Camera position and direction
v3f camera_pos = sao->getEyePosition();
v3f camera_dir = v3f(0,0,1);
camera_dir.rotateYZBy(sao->getLookPitch());
camera_dir.rotateXZBy(sao->getRotation().Y);
if (sao->getCameraInverted())
camera_dir = -camera_dir;
u16 max_simul_sends_usually = m_max_simul_sends;
/*
Check the time from last addNode/removeNode.
Decrease send rate if player is building stuff.
*/
m_time_from_building += dtime;
if (m_time_from_building < m_min_time_from_building) {
max_simul_sends_usually
= LIMITED_MAX_SIMULTANEOUS_BLOCK_SENDS;
}
/*
Number of blocks sending + number of blocks selected for sending
*/
u32 num_blocks_selected = m_blocks_sending.size();
/*
next time d will be continued from the d from which the nearest
unsent block was found this time.
This is because not necessarily any of the blocks found this
time are actually sent.
*/
s32 new_nearest_unsent_d = -1;
// Get view range and camera fov (radians) from the client
s16 fog_distance = sao->getPlayer()->getSkyParams().fog_distance;
s16 wanted_range = sao->getWantedRange() + 1;
if (fog_distance >= 0) {
// enforce if limited by mod
wanted_range = std::min<unsigned>(wanted_range, std::ceil((float)fog_distance / MAP_BLOCKSIZE));
}
float camera_fov = sao->getFov();
/*
Get the starting value of the block finder radius.
*/
if (m_last_center != center) {
m_nearest_unsent_d = 0;
m_last_center = center;
m_map_send_completion_timer = 0.0f;
}
// reset the unsent distance if the view angle has changed more that 10% of the fov
// (this matches isBlockInSight which allows for an extra 10%)
if (camera_dir.dotProduct(m_last_camera_dir) < std::cos(camera_fov * 0.1f)) {
m_nearest_unsent_d = 0;
m_last_camera_dir = camera_dir;
m_map_send_completion_timer = 0.0f;
}
if (m_nearest_unsent_d > 0) {
// make sure any blocks modified since the last time we sent blocks are resent
for (const v3s16 &p : m_blocks_modified) {
m_nearest_unsent_d = std::min(m_nearest_unsent_d, center.getDistanceFrom(p));
}
}
m_blocks_modified.clear();
s16 d_start = m_nearest_unsent_d;
// Distrust client-sent FOV and get server-set player object property
// zoom FOV (degrees) as a check to avoid hacked clients using FOV to load
// distant world.
// (zoom is disabled by value 0)
float prop_zoom_fov = sao->getZoomFOV() < 0.001f ?
0.0f :
std::max(camera_fov, sao->getZoomFOV() * core::DEGTORAD);
const s16 full_d_max = std::min(adjustDist(m_max_send_distance, prop_zoom_fov),
wanted_range);
const s16 d_opt = std::min(adjustDist(m_block_optimize_distance, prop_zoom_fov),
wanted_range);
const s16 d_cull_opt = std::min(adjustDist(m_block_cull_optimize_distance, prop_zoom_fov),
wanted_range);
// f32 to prevent overflow, it is also what isBlockInSight(...) expects
const f32 d_blocks_in_sight = full_d_max * BS * MAP_BLOCKSIZE;
s16 d_max_gen = std::min(adjustDist(m_max_gen_distance, prop_zoom_fov),
wanted_range);
s16 d_max = full_d_max;
// Don't loop very much at a time
s16 max_d_increment_at_time = 2;
if (d_max > d_start + max_d_increment_at_time)
d_max = d_start + max_d_increment_at_time;
// cos(angle between velocity and camera) * |velocity|
// Limit to 0.0f in case player moves backwards.
f32 dot = rangelim(camera_dir.dotProduct(playerspeed), 0.0f, 300.0f);
// Reduce the field of view when a player moves and looks forward.
// limit max fov effect to 50%, 60% at 20n/s fly speed
camera_fov = camera_fov / (1 + dot / 300.0f);
s32 nearest_emerged_d = -1;
s32 nearest_emergefull_d = -1;
s32 nearest_sent_d = -1;
//bool queue_is_full = false;
const v3s16 cam_pos_nodes = floatToInt(camera_pos, BS);
s16 d;
for (d = d_start; d <= d_max; d++) {
/*
Get the border/face dot coordinates of a "d-radiused"
box
*/
const auto &list = FacePositionCache::getFacePositions(d);
for (auto li = list.begin(); li != list.end(); ++li) {
v3s16 p = *li + center;
/*
Send throttling
- Don't allow too many simultaneous transfers
- EXCEPT when the blocks are very close
Also, don't send blocks that are already flying.
*/
// Start with the usual maximum
u16 max_simul_dynamic = max_simul_sends_usually;
// If block is very close, allow full maximum
if (d <= BLOCK_SEND_DISABLE_LIMITS_MAX_D)
max_simul_dynamic = m_max_simul_sends;
/*
Do not go over max mapgen limit
*/
if (blockpos_over_max_limit(p))
continue;
// If this is true, inexistent block will be made from scratch
bool generate = d <= d_max_gen;
/*
Don't generate or send if not in sight
FIXME This only works if the client uses a small enough
FOV setting. The default of 72 degrees is fine.
Also retrieve a smaller view cone in the direction of the player's
movement.
(0.1 is about 5 degrees)
*/
f32 dist;
if (!(isBlockInSight(p, camera_pos, camera_dir, camera_fov,
d_blocks_in_sight, &dist) ||
(playerspeed.getLength() > 1.0f * BS &&
isBlockInSight(p, camera_pos, playerspeeddir, 0.1f,
d_blocks_in_sight)))) {
continue;
}
/*
Check if map has this block
*/
MapBlock *block = env->getMap().getBlockNoCreateNoEx(p);
if (block) {
// First: Reset usage timer, this block will be of use in the future.
block->resetUsageTimer();
}
// Don't select too many blocks for sending
if (num_blocks_selected >= max_simul_dynamic) {
//queue_is_full = true;
goto queue_full_break;
}
// Don't send blocks that are currently being transferred
if (m_blocks_sending.find(p) != m_blocks_sending.end())
continue;
/*
Don't send already sent blocks
*/
if (m_blocks_sent.find(p) != m_blocks_sent.end())
continue;
if (block) {
/*
If block is not generated and generating new ones is
not wanted, skip block.
*/
if (!block->isGenerated() && !generate)
continue;
/*
If block is not close, don't send it unless it is near
ground level.
Block is near ground level if night-time mesh
differs from day-time mesh.
*/
if (d >= d_opt) {
if (!block->getIsUnderground() && !block->getDayNightDiff())
continue;
}
}
/*
Check occlusion cache first.
*/
if (m_blocks_occ.find(p) != m_blocks_occ.end())
continue;
/*
Note that we do this even before the block is loaded as this does not depend on its contents.
*/
if (m_occ_cull &&
env->getMap().isBlockOccluded(p * MAP_BLOCKSIZE, cam_pos_nodes, d >= d_cull_opt)) {
m_blocks_occ.insert(p);
continue;
}
/*
Add inexistent block to emerge queue.
*/
if (!block || !block->isGenerated()) {
if (emerge->enqueueBlockEmerge(peer_id, p, generate)) {
if (nearest_emerged_d == -1)
nearest_emerged_d = d;
} else {
if (nearest_emergefull_d == -1)
nearest_emergefull_d = d;
goto queue_full_break;
}
// get next one.
continue;
}
if (nearest_sent_d == -1)
nearest_sent_d = d;
/*
Add block to send queue
*/
PrioritySortedBlockTransfer q((float)dist, p, peer_id);
dest.push_back(q);
num_blocks_selected += 1;
}
}
queue_full_break:
// If nothing was found for sending and nothing was queued for
// emerging, continue next time browsing from here
if (nearest_emerged_d != -1) {
new_nearest_unsent_d = nearest_emerged_d;
} else if (nearest_emergefull_d != -1) {
new_nearest_unsent_d = nearest_emergefull_d;
} else {
if (d > full_d_max) {
new_nearest_unsent_d = 0;
m_nothing_to_send_pause_timer = 2.0f;
infostream << "Server: Player " << m_name << ", peer_id=" << peer_id
<< ": full map send completed after " << m_map_send_completion_timer
<< "s, restarting" << std::endl;
m_map_send_completion_timer = 0.0f;
} else {
if (nearest_sent_d != -1)
new_nearest_unsent_d = nearest_sent_d;
else
new_nearest_unsent_d = d;
}
}
if (new_nearest_unsent_d != -1 && m_nearest_unsent_d != new_nearest_unsent_d) {
m_nearest_unsent_d = new_nearest_unsent_d;
// if the distance has changed, clear the occlusion cache
m_blocks_occ.clear();
}
}
void RemoteClient::GotBlock(v3s16 p)
{
if (m_blocks_sending.find(p) != m_blocks_sending.end()) {
m_blocks_sending.erase(p);
// only add to sent blocks if it actually was sending
// (it might have been modified since)
m_blocks_sent.insert(p);
} else {
m_excess_gotblocks++;
}
}
void RemoteClient::SentBlock(v3s16 p)
{
if (m_blocks_sending.find(p) == m_blocks_sending.end())
m_blocks_sending[p] = 0.0f;
else
infostream<<"RemoteClient::SentBlock(): Sent block"
" already in m_blocks_sending"<<std::endl;
}
void RemoteClient::SetBlockNotSent(v3s16 p)
{
m_nothing_to_send_pause_timer = 0;
// remove the block from sending and sent sets,
// and mark as modified if found
if (m_blocks_sending.erase(p) + m_blocks_sent.erase(p) > 0)
m_blocks_modified.insert(p);
}
void RemoteClient::SetBlocksNotSent(const std::vector<v3s16> &blocks)
{
m_nothing_to_send_pause_timer = 0;
for (v3s16 p : blocks) {
// remove the block from sending and sent sets,
// and mark as modified if found
if (m_blocks_sending.erase(p) + m_blocks_sent.erase(p) > 0)
m_blocks_modified.insert(p);
}
}
void RemoteClient::notifyEvent(ClientStateEvent event)
{
std::ostringstream myerror;
switch (m_state)
{
case CS_Invalid:
//intentionally do nothing
break;
case CS_Created:
switch (event) {
case CSE_Hello:
m_state = CS_HelloSent;
break;
case CSE_Disconnect:
m_state = CS_Disconnecting;
break;
case CSE_SetDenied:
m_state = CS_Denied;
break;
/* GotInit2 SetDefinitionsSent SetMediaSent */
default:
myerror << "Created: Invalid client state transition! " << event;
throw ClientStateError(myerror.str());
}
break;
case CS_Denied:
/* don't do anything if in denied state */
break;
case CS_HelloSent:
switch(event)
{
case CSE_AuthAccept:
m_state = CS_AwaitingInit2;
resetChosenMech();
break;
case CSE_Disconnect:
m_state = CS_Disconnecting;
break;
case CSE_SetDenied:
m_state = CS_Denied;
resetChosenMech();
break;
default:
myerror << "HelloSent: Invalid client state transition! " << event;
throw ClientStateError(myerror.str());
}
break;
case CS_AwaitingInit2:
switch(event)
{
case CSE_GotInit2:
confirmSerializationVersion();
m_state = CS_InitDone;
break;
case CSE_Disconnect:
m_state = CS_Disconnecting;
break;
case CSE_SetDenied:
m_state = CS_Denied;
break;
/* Init SetDefinitionsSent SetMediaSent */
default:
myerror << "InitSent: Invalid client state transition! " << event;
throw ClientStateError(myerror.str());
}
break;
case CS_InitDone:
switch(event)
{
case CSE_SetDefinitionsSent:
m_state = CS_DefinitionsSent;
break;
case CSE_Disconnect:
m_state = CS_Disconnecting;
break;
case CSE_SetDenied:
m_state = CS_Denied;
break;
/* Init GotInit2 SetMediaSent */
default:
myerror << "InitDone: Invalid client state transition! " << event;
throw ClientStateError(myerror.str());
}
break;
case CS_DefinitionsSent:
switch(event)
{
case CSE_SetClientReady:
m_state = CS_Active;
break;
case CSE_Disconnect:
m_state = CS_Disconnecting;
break;
case CSE_SetDenied:
m_state = CS_Denied;
break;
/* Init GotInit2 SetDefinitionsSent */
default:
myerror << "DefinitionsSent: Invalid client state transition! " << event;
throw ClientStateError(myerror.str());
}
break;
case CS_Active:
switch(event)
{
case CSE_SetDenied:
m_state = CS_Denied;
break;
case CSE_Disconnect:
m_state = CS_Disconnecting;
break;
case CSE_SudoSuccess:
m_state = CS_SudoMode;
resetChosenMech();
break;
/* Init GotInit2 SetDefinitionsSent SetMediaSent SetDenied */
default:
myerror << "Active: Invalid client state transition! " << event;
throw ClientStateError(myerror.str());
break;
}
break;
case CS_SudoMode:
switch(event)
{
case CSE_SetDenied:
m_state = CS_Denied;
break;
case CSE_Disconnect:
m_state = CS_Disconnecting;
break;
case CSE_SudoLeave:
m_state = CS_Active;
break;
default:
myerror << "Active: Invalid client state transition! " << event;
throw ClientStateError(myerror.str());
break;
}
break;
case CS_Disconnecting:
/* we are already disconnecting */
break;
}
}
void RemoteClient::resetChosenMech()
{
if (auth_data) {
srp_verifier_delete((SRPVerifier *) auth_data);
auth_data = nullptr;
}
chosen_mech = AUTH_MECHANISM_NONE;
}
void RemoteClient::setEncryptedPassword(const std::string& pwd)
{
FATAL_ERROR_IF(!str_starts_with(pwd, "#1#"), "must be srp");
enc_pwd = pwd;
// We just set SRP encrypted password, we accept only it now
allowed_auth_mechs = AUTH_MECHANISM_SRP;
}
void RemoteClient::setVersionInfo(u8 major, u8 minor, u8 patch, const std::string &full)
{
m_version_major = major;
m_version_minor = minor;
m_version_patch = patch;
m_full_version = string_sanitize_ascii(full, 64);
}
void RemoteClient::setLangCode(const std::string &code)
{
m_lang_code = string_sanitize_ascii(code, 12);
}
ClientInterface::ClientInterface(const std::shared_ptr<con::Connection> & con)
:
m_con(con),
m_env(nullptr)
{
}
ClientInterface::~ClientInterface()
{
/*
Delete clients
*/
{
RecursiveMutexAutoLock clientslock(m_clients_mutex);
for (auto &client_it : m_clients) {
// Delete client
delete client_it.second;
}
}
}
std::vector<session_t> ClientInterface::getClientIDs(ClientState min_state)
{
std::vector<session_t> reply;
RecursiveMutexAutoLock clientslock(m_clients_mutex);
for (const auto &m_client : m_clients) {
if (m_client.second->getState() >= min_state)
reply.push_back(m_client.second->peer_id);
}
return reply;
}
void ClientInterface::markBlocksNotSent(const std::vector<v3s16> &positions)
{
RecursiveMutexAutoLock clientslock(m_clients_mutex);
for (const auto &client : m_clients) {
if (client.second->getState() >= CS_Active)
client.second->SetBlocksNotSent(positions);
}
}
/**
* Verify if user limit was reached.
* User limit count all clients from HelloSent state (MT protocol user) to Active state
* @return true if user limit was reached
*/
bool ClientInterface::isUserLimitReached()
{
return getClientIDs(CS_HelloSent).size() >= g_settings->getU16("max_users");
}
void ClientInterface::step(float dtime)
{
m_print_info_timer += dtime;
if (m_print_info_timer >= 30.0f) {
m_print_info_timer = 0.0f;
UpdatePlayerList();
}
m_check_linger_timer += dtime;
if (m_check_linger_timer < 1.0f)
return;
m_check_linger_timer = 0;
RecursiveMutexAutoLock clientslock(m_clients_mutex);
for (const auto &it : m_clients) {
auto state = it.second->getState();
if (state >= CS_HelloSent)
continue;
if (it.second->uptime() <= LINGER_TIMEOUT)
continue;
// CS_Created means nobody has even noticed the client is there
// (this is before on_prejoinplayer runs)
// CS_Invalid should not happen
// -> log those as warning, the rest as info
std::ostream &os = state == CS_Created || state == CS_Invalid ?
warningstream : infostream;
try {
Address addr = m_con->GetPeerAddress(it.second->peer_id);
os << "Disconnecting lingering client from "
<< addr.serializeString() << " (state="
<< state2Name(state) << ")" << std::endl;
m_con->DisconnectPeer(it.second->peer_id);
} catch (con::PeerNotFoundException &e) {
}
}
}
void ClientInterface::UpdatePlayerList()
{
if (m_env) {
std::vector<session_t> clients = getClientIDs();
m_clients_names.clear();
if (!clients.empty())
infostream<<"Players:"<<std::endl;
for (session_t i : clients) {
RemotePlayer *player = m_env->getPlayer(i);
if (player == NULL)
continue;
infostream << "* " << player->getName() << "\t";
{
RecursiveMutexAutoLock clientslock(m_clients_mutex);
RemoteClient* client = lockedGetClientNoEx(i);
if (client)
client->PrintInfo(infostream);
}
m_clients_names.emplace_back(player->getName());
}
}
}
void ClientInterface::send(session_t peer_id, NetworkPacket *pkt)
{
auto &ccf = clientCommandFactoryTable[pkt->getCommand()];
FATAL_ERROR_IF(!ccf.name, "packet type missing in table");
m_con->Send(peer_id, ccf.channel, pkt, ccf.reliable);
}
void ClientInterface::sendCustom(session_t peer_id, u8 channel, NetworkPacket *pkt, bool reliable)
{
// check table anyway to prevent mistakes
FATAL_ERROR_IF(!clientCommandFactoryTable[pkt->getCommand()].name,
"packet type missing in table");
m_con->Send(peer_id, channel, pkt, reliable);
}
void ClientInterface::sendToAll(NetworkPacket *pkt)
{
RecursiveMutexAutoLock clientslock(m_clients_mutex);
for (auto &client_it : m_clients) {
RemoteClient *client = client_it.second;
if (client->net_proto_version != 0) {
auto &ccf = clientCommandFactoryTable[pkt->getCommand()];
FATAL_ERROR_IF(!ccf.name, "packet type missing in table");
m_con->Send(client->peer_id, ccf.channel, pkt, ccf.reliable);
}
}
}
RemoteClient* ClientInterface::getClientNoEx(session_t peer_id, ClientState state_min)
{
RecursiveMutexAutoLock clientslock(m_clients_mutex);
RemoteClientMap::const_iterator n = m_clients.find(peer_id);
// The client may not exist; clients are immediately removed if their
// access is denied, and this event occurs later then.
if (n == m_clients.end())
return NULL;
if (n->second->getState() >= state_min)
return n->second;
return NULL;
}
RemoteClient* ClientInterface::lockedGetClientNoEx(session_t peer_id, ClientState state_min)
{
RemoteClientMap::const_iterator n = m_clients.find(peer_id);
// The client may not exist; clients are immediately removed if their
// access is denied, and this event occurs later then.
if (n == m_clients.end())
return NULL;
if (n->second->getState() >= state_min)
return n->second;
return NULL;
}
ClientState ClientInterface::getClientState(session_t peer_id)
{
RecursiveMutexAutoLock clientslock(m_clients_mutex);
RemoteClientMap::const_iterator n = m_clients.find(peer_id);
// The client may not exist; clients are immediately removed if their
// access is denied, and this event occurs later then.
if (n == m_clients.end())
return CS_Invalid;
return n->second->getState();
}
void ClientInterface::setPlayerName(session_t peer_id, const std::string &name)
{
RecursiveMutexAutoLock clientslock(m_clients_mutex);
RemoteClientMap::iterator n = m_clients.find(peer_id);
// The client may not exist; clients are immediately removed if their
// access is denied, and this event occurs later then.
if (n != m_clients.end())
n->second->setName(name);
}
void ClientInterface::DeleteClient(session_t peer_id)
{
RecursiveMutexAutoLock conlock(m_clients_mutex);
// Error check
RemoteClientMap::iterator n = m_clients.find(peer_id);
// The client may not exist; clients are immediately removed if their
// access is denied, and this event occurs later then.
if (n == m_clients.end())
return;
/*
Mark objects to be not known by the client
*/
//TODO this should be done by client destructor!!!
RemoteClient *client = n->second;
// Handle objects
for (u16 id : client->m_known_objects) {
// Get object
ServerActiveObject* obj = m_env->getActiveObject(id);
if(obj && obj->m_known_by_count > 0)
obj->m_known_by_count--;
}
// Delete client
delete m_clients[peer_id];
m_clients.erase(peer_id);
}
void ClientInterface::CreateClient(session_t peer_id)
{
RecursiveMutexAutoLock conlock(m_clients_mutex);
// Error check
RemoteClientMap::iterator n = m_clients.find(peer_id);
// The client shouldn't already exist
if (n != m_clients.end()) return;
// Create client
RemoteClient *client = new RemoteClient();
client->peer_id = peer_id;
m_clients[client->peer_id] = client;
}
void ClientInterface::event(session_t peer_id, ClientStateEvent event)
{
{
RecursiveMutexAutoLock clientlock(m_clients_mutex);
// Error check
RemoteClientMap::iterator n = m_clients.find(peer_id);
// No client to deliver event
if (n == m_clients.end())
return;
n->second->notifyEvent(event);
}
if ((event == CSE_SetClientReady) ||
(event == CSE_Disconnect) ||
(event == CSE_SetDenied))
{
UpdatePlayerList();
}
}
u16 ClientInterface::getProtocolVersion(session_t peer_id)
{
RecursiveMutexAutoLock conlock(m_clients_mutex);
// Error check
RemoteClientMap::iterator n = m_clients.find(peer_id);
// No client to get version
if (n == m_clients.end())
return 0;
return n->second->net_proto_version;
}
void ClientInterface::setClientVersion(session_t peer_id, u8 major, u8 minor, u8 patch,
const std::string &full)
{
RecursiveMutexAutoLock conlock(m_clients_mutex);
// Error check
RemoteClientMap::iterator n = m_clients.find(peer_id);
// No client to set versions
if (n == m_clients.end())
return;
n->second->setVersionInfo(major, minor, patch, full);
}

561
src/server/clientiface.h Normal file
View file

@ -0,0 +1,561 @@
/*
Minetest
Copyright (C) 2010-2014 celeron55, Perttu Ahola <celeron55@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#pragma once
#include "irr_v3d.h" // for irrlicht datatypes
#include "constants.h"
#include "serialization.h" // for SER_FMT_VER_INVALID
#include "network/networkpacket.h"
#include "network/networkprotocol.h"
#include "network/address.h"
#include "porting.h"
#include "threading/mutex_auto_lock.h"
#include "clientdynamicinfo.h"
#include <list>
#include <vector>
#include <set>
#include <unordered_set>
#include <memory>
#include <mutex>
class MapBlock;
class ServerEnvironment;
class EmergeManager;
/*
* State Transitions
Start
(peer connect)
|
v
/-----------------\
| |
| Created |
| |
\-----------------/
| depending of the incoming packet
----------------------------------------
v
+-----------------------------+
|IN: |
| TOSERVER_INIT |
+-----------------------------+
| invalid playername
| or denied by mod
v
+-----------------------------+
|OUT: |
| TOCLIENT_HELLO |
+-----------------------------+
|
|
v
/-----------------\ /-----------------\
| | | |
| AwaitingInit2 |<--------- | HelloSent |
| | | | |
\-----------------/ | \-----------------/
| | |
+-----------------------------+ | *-----------------------------* Auth fails
|IN: | | |Authentication, depending on |------------------
| TOSERVER_INIT2 | | | packet sent by client | |
+-----------------------------+ | *-----------------------------* |
| | | |
| | | Authentication |
v | | successful |
/-----------------\ | v |
| | | +-----------------------------+ |
| InitDone | | |OUT: | |
| | | | TOCLIENT_AUTH_ACCEPT | |
\-----------------/ | +-----------------------------+ |
| | | |
+-----------------------------+ --------------------- |
|OUT: | |
| TOCLIENT_MOVEMENT | |
| TOCLIENT_ITEMDEF | |
| TOCLIENT_NODEDEF | |
| TOCLIENT_ANNOUNCE_MEDIA | |
| TOCLIENT_DETACHED_INVENTORY | |
| TOCLIENT_TIME_OF_DAY | |
+-----------------------------+ |
| |
| |
| ----------------------------- |
v | | |
/-----------------\ v |
| | +-----------------------------+ |
| DefinitionsSent | |IN: | |
| | | TOSERVER_REQUEST_MEDIA | |
\-----------------/ | | |
| +-----------------------------+ |
| ^ | |
| ----------------------------- |
v v
+-----------------------------+ --------------------------------+
|IN: | | ^
| TOSERVER_CLIENT_READY | v |
+-----------------------------+ +------------------------+ |
| |OUT: | |
v | TOCLIENT_ACCESS_DENIED | |
+-----------------------------+ +------------------------+ |
|OUT: | | |
| TOCLIENT_MOVE_PLAYER | v |
| TOCLIENT_PRIVILEGES | /-----------------\ |
| TOCLIENT_INVENTORY_FORMSPEC | | | |
| UpdateCrafting | | Denied | |
| TOCLIENT_INVENTORY | | | |
| TOCLIENT_HP (opt) | \-----------------/ |
| TOCLIENT_BREATH | |
| TOCLIENT_DEATHSCREEN | |
+-----------------------------+ |
| |
v |
/-----------------\ async mod action (ban, kick) |
| |---------------------------------------------------------------
---->| Active |
| | |----------------------------------------------
| \-----------------/ timeout v
| | | +-----------------------------+
| | | |OUT: |
| | | | TOCLIENT_DISCONNECT |
| | | +-----------------------------+
| | | |
| | v v
| | +-----------------------------+ /-----------------\
| | |IN: | | |
| | | TOSERVER_DISCONNECT |------------------->| Disconnecting |
| | +-----------------------------+ | |
| | \-----------------/
| | any auth packet which was
| | allowed in TOCLIENT_AUTH_ACCEPT
| v
| *-----------------------------* Auth +-------------------------------+
| |Authentication, depending on | succeeds |OUT: |
| | packet sent by client |---------->| TOCLIENT_ACCEPT_SUDO_MODE |
| *-----------------------------* +-------------------------------+
| | |
| | Auth fails /-----------------\
| v | |
| +-------------------------------+ | SudoMode |
| |OUT: | | |
| | TOCLIENT_DENY_SUDO_MODE | \-----------------/
| +-------------------------------+ |
| | v
| | +-----------------------------+
| | sets password accordingly |IN: |
-------------------+-------------------------------| TOSERVER_FIRST_SRP |
+-----------------------------+
*/
namespace con {
class Connection;
}
// Also make sure to update the ClientInterface::statenames
// array when modifying these enums
enum ClientState
{
CS_Invalid,
CS_Disconnecting,
CS_Denied,
CS_Created,
CS_HelloSent,
CS_AwaitingInit2,
CS_InitDone,
CS_DefinitionsSent,
CS_Active,
CS_SudoMode
};
enum ClientStateEvent
{
CSE_Hello,
CSE_AuthAccept,
CSE_GotInit2,
CSE_SetDenied,
CSE_SetDefinitionsSent,
CSE_SetClientReady,
CSE_SudoSuccess,
CSE_SudoLeave,
CSE_Disconnect
};
/*
Used for queueing and sorting block transfers in containers
Lower priority number means higher priority.
*/
struct PrioritySortedBlockTransfer
{
PrioritySortedBlockTransfer(float a_priority, const v3s16 &a_pos, session_t a_peer_id)
{
priority = a_priority;
pos = a_pos;
peer_id = a_peer_id;
}
bool operator < (const PrioritySortedBlockTransfer &other) const
{
return priority < other.priority;
}
float priority;
v3s16 pos;
session_t peer_id;
};
class RemoteClient
{
public:
// peer_id=0 means this client has no associated peer
// NOTE: If client is made allowed to exist while peer doesn't,
// this has to be set to 0 when there is no peer.
// Also, the client must be moved to some other container.
session_t peer_id = PEER_ID_INEXISTENT;
// The serialization version to use with the client
u8 serialization_version = SER_FMT_VER_INVALID;
//
u16 net_proto_version = 0;
/* Authentication information */
std::string enc_pwd = "";
bool create_player_on_auth_success = false;
AuthMechanism chosen_mech = AUTH_MECHANISM_NONE;
void *auth_data = nullptr;
u32 allowed_auth_mechs = 0;
void resetChosenMech();
bool isMechAllowed(AuthMechanism mech)
{ return allowed_auth_mechs & mech; }
void setEncryptedPassword(const std::string& pwd);
RemoteClient();
~RemoteClient() = default;
/*
Finds block that should be sent next to the client.
Environment should be locked when this is called.
dtime is used for resetting send radius at slow interval
*/
void GetNextBlocks(ServerEnvironment *env, EmergeManager* emerge,
float dtime, std::vector<PrioritySortedBlockTransfer> &dest);
void GotBlock(v3s16 p);
void SentBlock(v3s16 p);
void SetBlockNotSent(v3s16 p);
void SetBlocksNotSent(const std::vector<v3s16> &blocks);
/**
* tell client about this block being modified right now.
* this information is required to requeue the block in case it's "on wire"
* while modification is processed by server
* @param p position of modified block
*/
void ResendBlockIfOnWire(v3s16 p);
u32 getSendingCount() const { return m_blocks_sending.size(); }
bool isBlockSent(v3s16 p) const
{
return m_blocks_sent.find(p) != m_blocks_sent.end();
}
bool markMediaSent(const std::string &name) {
auto insert_result = m_media_sent.emplace(name);
return insert_result.second; // true = was inserted
}
void PrintInfo(std::ostream &o)
{
o<<"RemoteClient "<<peer_id<<": "
<<"m_blocks_sent.size()="<<m_blocks_sent.size()
<<", m_blocks_sending.size()="<<m_blocks_sending.size()
<<", m_nearest_unsent_d="<<m_nearest_unsent_d
<<", m_excess_gotblocks="<<m_excess_gotblocks
<<std::endl;
m_excess_gotblocks = 0;
}
// Time from last placing or removing blocks
float m_time_from_building = 9999;
/*
List of active objects that the client knows of.
*/
std::set<u16> m_known_objects;
ClientState getState() const { return m_state; }
const std::string &getName() const { return m_name; }
void setName(const std::string &name) { m_name = name; }
/* update internal client state */
void notifyEvent(ClientStateEvent event);
/* set expected serialization version */
void setPendingSerializationVersion(u8 version)
{ m_pending_serialization_version = version; }
void setDeployedCompressionMode(u16 byteFlag)
{ m_deployed_compression = byteFlag; }
void confirmSerializationVersion()
{ serialization_version = m_pending_serialization_version; }
/* get uptime */
u64 uptime() const { return porting::getTimeS() - m_connection_time; }
/* set version information */
void setVersionInfo(u8 major, u8 minor, u8 patch, const std::string &full);
/* read version information */
u8 getMajor() const { return m_version_major; }
u8 getMinor() const { return m_version_minor; }
u8 getPatch() const { return m_version_patch; }
const std::string &getFullVer() const { return m_full_version; }
void setLangCode(const std::string &code);
const std::string &getLangCode() const { return m_lang_code; }
void setCachedAddress(const Address &addr) { m_addr = addr; }
const Address &getAddress() const { return m_addr; }
void setDynamicInfo(const ClientDynamicInfo &info) { m_dynamic_info = info; }
const ClientDynamicInfo &getDynamicInfo() const { return m_dynamic_info; }
private:
// Version is stored in here after INIT before INIT2
u8 m_pending_serialization_version = SER_FMT_VER_INVALID;
/* current state of client */
ClientState m_state = CS_Created;
// Cached here so retrieval doesn't have to go to connection API
Address m_addr;
// Client-sent language code
std::string m_lang_code;
// Client-sent dynamic info
ClientDynamicInfo m_dynamic_info{};
/*
Blocks that have been sent to client.
- These don't have to be sent again.
- A block is cleared from here when client says it has
deleted it from it's memory
List of block positions.
No MapBlock* is stored here because the blocks can get deleted.
*/
std::unordered_set<v3s16> m_blocks_sent;
/*
Cache of blocks that have been occlusion culled at the current distance.
As GetNextBlocks traverses the same distance multiple times, this saves
significant CPU time.
*/
std::unordered_set<v3s16> m_blocks_occ;
s16 m_nearest_unsent_d = 0;
v3s16 m_last_center;
v3f m_last_camera_dir;
const u16 m_max_simul_sends;
const float m_min_time_from_building;
const s16 m_max_send_distance;
const s16 m_block_optimize_distance;
const s16 m_block_cull_optimize_distance;
const s16 m_max_gen_distance;
const bool m_occ_cull;
/*
Set of media files the client has already requested
We won't send the same file twice to avoid bandwidth consumption attacks.
*/
std::unordered_set<std::string> m_media_sent;
/*
Blocks that are currently on the line.
This is used for throttling the sending of blocks.
- The size of this list is limited to some value
Block is added when it is sent with BLOCKDATA.
Block is removed when GOTBLOCKS is received.
Value is time from sending. (not used at the moment)
*/
std::unordered_map<v3s16, float> m_blocks_sending;
/*
Blocks that have been modified since blocks were
sent to the client last (getNextBlocks()).
This is used to reset the unsent distance, so that
modified blocks are resent to the client.
List of block positions.
*/
std::unordered_set<v3s16> m_blocks_modified;
/*
Count of excess GotBlocks().
There is an excess amount because the client sometimes
gets a block so late that the server sends it again,
and the client then sends two GOTBLOCKs.
This is reset by PrintInfo()
*/
u32 m_excess_gotblocks = 0;
// CPU usage optimization
float m_nothing_to_send_pause_timer = 0.0f;
// measure how long it takes the server to send the complete map
float m_map_send_completion_timer = 0.0f;
/*
name of player using this client
*/
std::string m_name = "";
/*
client information
*/
u8 m_version_major = 0;
u8 m_version_minor = 0;
u8 m_version_patch = 0;
std::string m_full_version = "unknown";
u16 m_deployed_compression = 0;
/*
time this client was created
*/
const u64 m_connection_time = porting::getTimeS();
};
typedef std::unordered_map<u16, RemoteClient*> RemoteClientMap;
class ClientInterface {
public:
friend class Server;
ClientInterface(const std::shared_ptr<con::Connection> &con);
~ClientInterface();
/* run sync step */
void step(float dtime);
/* get list of active client id's */
std::vector<session_t> getClientIDs(ClientState min_state=CS_Active);
/* mark blocks as not sent on all active clients */
void markBlocksNotSent(const std::vector<v3s16> &positions);
/* verify is server user limit was reached */
bool isUserLimitReached();
/* get list of client player names */
const std::vector<std::string> &getPlayerNames() const { return m_clients_names; }
/* send to one client */
void send(session_t peer_id, NetworkPacket *pkt);
/* send to one client, deviating from the standard params */
void sendCustom(session_t peer_id, u8 channel, NetworkPacket *pkt, bool reliable);
/* send to all clients */
void sendToAll(NetworkPacket *pkt);
/* delete a client */
void DeleteClient(session_t peer_id);
/* create client */
void CreateClient(session_t peer_id);
/* get a client by peer_id */
RemoteClient *getClientNoEx(session_t peer_id, ClientState state_min = CS_Active);
/* get client by peer_id (make sure you have list lock before!*/
RemoteClient *lockedGetClientNoEx(session_t peer_id, ClientState state_min = CS_Active);
/* get state of client by id*/
ClientState getClientState(session_t peer_id);
/* set client playername */
void setPlayerName(session_t peer_id, const std::string &name);
/* get protocol version of client */
u16 getProtocolVersion(session_t peer_id);
/* set client version */
void setClientVersion(session_t peer_id, u8 major, u8 minor, u8 patch,
const std::string &full);
/* event to update client state */
void event(session_t peer_id, ClientStateEvent event);
/* Set environment. Do not call this function if environment is already set */
void setEnv(ServerEnvironment *env)
{
assert(m_env == NULL); // pre-condition
m_env = env;
}
static std::string state2Name(ClientState state);
protected:
class AutoLock {
public:
AutoLock(ClientInterface &iface): m_lock(iface.m_clients_mutex) {}
private:
RecursiveMutexAutoLock m_lock;
};
RemoteClientMap& getClientList() { return m_clients; }
private:
/* update internal player list */
void UpdatePlayerList();
// Connection
std::shared_ptr<con::Connection> m_con;
std::recursive_mutex m_clients_mutex;
// Connected clients (behind the con mutex)
RemoteClientMap m_clients;
std::vector<std::string> m_clients_names; //for announcing masterserver
// Environment
ServerEnvironment *m_env;
float m_print_info_timer = 0;
float m_check_linger_timer = 0;
static const char *statenames[];
static constexpr int LINGER_TIMEOUT = 10;
};

833
src/server/rollback.cpp Normal file
View file

@ -0,0 +1,833 @@
/*
Minetest
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "rollback.h"
#include <fstream>
#include <list>
#include <sstream>
#include "log.h"
#include "mapnode.h"
#include "gamedef.h"
#include "nodedef.h"
#include "util/serialize.h"
#include "util/string.h"
#include "util/numeric.h"
#include "inventorymanager.h" // deserializing InventoryLocations
#include "sqlite3.h"
#include "filesys.h"
#define POINTS_PER_NODE (16.0)
#define SQLRES(f, good) \
if ((f) != (good)) {\
throw FileNotGoodException(std::string("RollbackManager: " \
"SQLite3 error (" __FILE__ ":" TOSTRING(__LINE__) \
"): ") + sqlite3_errmsg(db)); \
}
#define SQLOK(f) SQLRES(f, SQLITE_OK)
#define SQLOK_ERRSTREAM(s, m) \
if ((s) != SQLITE_OK) { \
errorstream << "RollbackManager: " << (m) << ": " \
<< sqlite3_errmsg(db) << std::endl; \
}
#define FINALIZE_STATEMENT(statement) \
SQLOK_ERRSTREAM(sqlite3_finalize(statement), "Failed to finalize " #statement)
class ItemStackRow : public ItemStack {
public:
ItemStackRow & operator = (const ItemStack & other)
{
*static_cast<ItemStack *>(this) = other;
return *this;
}
int id;
};
struct ActionRow {
int id;
int actor;
time_t timestamp;
int type;
std::string location, list;
int index, add;
ItemStackRow stack;
int nodeMeta;
int x, y, z;
int oldNode;
int oldParam1, oldParam2;
std::string oldMeta;
int newNode;
int newParam1, newParam2;
std::string newMeta;
int guessed;
};
struct Entity {
int id;
std::string name;
};
RollbackManager::RollbackManager(const std::string & world_path,
IGameDef * gamedef_) :
gamedef(gamedef_)
{
verbosestream << "RollbackManager::RollbackManager(" << world_path
<< ")" << std::endl;
database_path = world_path + DIR_DELIM "rollback.sqlite";
initDatabase();
}
RollbackManager::~RollbackManager()
{
flush();
FINALIZE_STATEMENT(stmt_insert);
FINALIZE_STATEMENT(stmt_replace);
FINALIZE_STATEMENT(stmt_select);
FINALIZE_STATEMENT(stmt_select_range);
FINALIZE_STATEMENT(stmt_select_withActor);
FINALIZE_STATEMENT(stmt_knownActor_select);
FINALIZE_STATEMENT(stmt_knownActor_insert);
FINALIZE_STATEMENT(stmt_knownNode_select);
FINALIZE_STATEMENT(stmt_knownNode_insert);
SQLOK_ERRSTREAM(sqlite3_close(db), "Could not close db");
}
void RollbackManager::registerNewActor(const int id, const std::string &name)
{
Entity actor = {id, name};
knownActors.push_back(actor);
}
void RollbackManager::registerNewNode(const int id, const std::string &name)
{
Entity node = {id, name};
knownNodes.push_back(node);
}
int RollbackManager::getActorId(const std::string &name)
{
for (std::vector<Entity>::const_iterator iter = knownActors.begin();
iter != knownActors.end(); ++iter) {
if (iter->name == name) {
return iter->id;
}
}
SQLOK(sqlite3_bind_text(stmt_knownActor_insert, 1, name.c_str(), name.size(), NULL));
SQLRES(sqlite3_step(stmt_knownActor_insert), SQLITE_DONE);
SQLOK(sqlite3_reset(stmt_knownActor_insert));
int id = sqlite3_last_insert_rowid(db);
registerNewActor(id, name);
return id;
}
int RollbackManager::getNodeId(const std::string &name)
{
for (std::vector<Entity>::const_iterator iter = knownNodes.begin();
iter != knownNodes.end(); ++iter) {
if (iter->name == name) {
return iter->id;
}
}
SQLOK(sqlite3_bind_text(stmt_knownNode_insert, 1, name.c_str(), name.size(), NULL));
SQLRES(sqlite3_step(stmt_knownNode_insert), SQLITE_DONE);
SQLOK(sqlite3_reset(stmt_knownNode_insert));
int id = sqlite3_last_insert_rowid(db);
registerNewNode(id, name);
return id;
}
const char * RollbackManager::getActorName(const int id)
{
for (std::vector<Entity>::const_iterator iter = knownActors.begin();
iter != knownActors.end(); ++iter) {
if (iter->id == id) {
return iter->name.c_str();
}
}
return "";
}
const char * RollbackManager::getNodeName(const int id)
{
for (std::vector<Entity>::const_iterator iter = knownNodes.begin();
iter != knownNodes.end(); ++iter) {
if (iter->id == id) {
return iter->name.c_str();
}
}
return "";
}
bool RollbackManager::createTables()
{
SQLOK(sqlite3_exec(db,
"CREATE TABLE IF NOT EXISTS `actor` (\n"
" `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,\n"
" `name` TEXT NOT NULL\n"
");\n"
"CREATE TABLE IF NOT EXISTS `node` (\n"
" `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,\n"
" `name` TEXT NOT NULL\n"
");\n"
"CREATE TABLE IF NOT EXISTS `action` (\n"
" `id` INTEGER PRIMARY KEY AUTOINCREMENT,\n"
" `actor` INTEGER NOT NULL,\n"
" `timestamp` TIMESTAMP NOT NULL,\n"
" `type` INTEGER NOT NULL,\n"
" `list` TEXT,\n"
" `index` INTEGER,\n"
" `add` INTEGER,\n"
" `stackNode` INTEGER,\n"
" `stackQuantity` INTEGER,\n"
" `nodeMeta` INTEGER,\n"
" `x` INT,\n"
" `y` INT,\n"
" `z` INT,\n"
" `oldNode` INTEGER,\n"
" `oldParam1` INTEGER,\n"
" `oldParam2` INTEGER,\n"
" `oldMeta` TEXT,\n"
" `newNode` INTEGER,\n"
" `newParam1` INTEGER,\n"
" `newParam2` INTEGER,\n"
" `newMeta` TEXT,\n"
" `guessedActor` INTEGER,\n"
" FOREIGN KEY (`actor`) REFERENCES `actor`(`id`),\n"
" FOREIGN KEY (`stackNode`) REFERENCES `node`(`id`),\n"
" FOREIGN KEY (`oldNode`) REFERENCES `node`(`id`),\n"
" FOREIGN KEY (`newNode`) REFERENCES `node`(`id`)\n"
");\n"
"CREATE INDEX IF NOT EXISTS `actionIndex` ON `action`(`x`,`y`,`z`,`timestamp`,`actor`);\n",
NULL, NULL, NULL));
verbosestream << "SQL Rollback: SQLite3 database structure was created" << std::endl;
return true;
}
bool RollbackManager::initDatabase()
{
verbosestream << "RollbackManager: Database connection setup" << std::endl;
bool needs_create = !fs::PathExists(database_path);
SQLOK(sqlite3_open_v2(database_path.c_str(), &db,
SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL));
if (needs_create) {
createTables();
}
SQLOK(sqlite3_prepare_v2(db,
"INSERT INTO `action` (\n"
" `actor`, `timestamp`, `type`,\n"
" `list`, `index`, `add`, `stackNode`, `stackQuantity`, `nodeMeta`,\n"
" `x`, `y`, `z`,\n"
" `oldNode`, `oldParam1`, `oldParam2`, `oldMeta`,\n"
" `newNode`, `newParam1`, `newParam2`, `newMeta`,\n"
" `guessedActor`\n"
") VALUES (\n"
" ?, ?, ?,\n"
" ?, ?, ?, ?, ?, ?,\n"
" ?, ?, ?,\n"
" ?, ?, ?, ?,\n"
" ?, ?, ?, ?,\n"
" ?"
");",
-1, &stmt_insert, NULL));
SQLOK(sqlite3_prepare_v2(db,
"REPLACE INTO `action` (\n"
" `actor`, `timestamp`, `type`,\n"
" `list`, `index`, `add`, `stackNode`, `stackQuantity`, `nodeMeta`,\n"
" `x`, `y`, `z`,\n"
" `oldNode`, `oldParam1`, `oldParam2`, `oldMeta`,\n"
" `newNode`, `newParam1`, `newParam2`, `newMeta`,\n"
" `guessedActor`, `id`\n"
") VALUES (\n"
" ?, ?, ?,\n"
" ?, ?, ?, ?, ?, ?,\n"
" ?, ?, ?,\n"
" ?, ?, ?, ?,\n"
" ?, ?, ?, ?,\n"
" ?, ?\n"
");",
-1, &stmt_replace, NULL));
SQLOK(sqlite3_prepare_v2(db,
"SELECT\n"
" `actor`, `timestamp`, `type`,\n"
" `list`, `index`, `add`, `stackNode`, `stackQuantity`, `nodemeta`,\n"
" `x`, `y`, `z`,\n"
" `oldNode`, `oldParam1`, `oldParam2`, `oldMeta`,\n"
" `newNode`, `newParam1`, `newParam2`, `newMeta`,\n"
" `guessedActor`\n"
" FROM `action`\n"
" WHERE `timestamp` >= ?\n"
" ORDER BY `timestamp` DESC, `id` DESC",
-1, &stmt_select, NULL));
SQLOK(sqlite3_prepare_v2(db,
"SELECT\n"
" `actor`, `timestamp`, `type`,\n"
" `list`, `index`, `add`, `stackNode`, `stackQuantity`, `nodemeta`,\n"
" `x`, `y`, `z`,\n"
" `oldNode`, `oldParam1`, `oldParam2`, `oldMeta`,\n"
" `newNode`, `newParam1`, `newParam2`, `newMeta`,\n"
" `guessedActor`\n"
"FROM `action`\n"
"WHERE `timestamp` >= ?\n"
" AND `x` IS NOT NULL\n"
" AND `y` IS NOT NULL\n"
" AND `z` IS NOT NULL\n"
" AND `x` BETWEEN ? AND ?\n"
" AND `y` BETWEEN ? AND ?\n"
" AND `z` BETWEEN ? AND ?\n"
"ORDER BY `timestamp` DESC, `id` DESC\n"
"LIMIT 0,?",
-1, &stmt_select_range, NULL));
SQLOK(sqlite3_prepare_v2(db,
"SELECT\n"
" `actor`, `timestamp`, `type`,\n"
" `list`, `index`, `add`, `stackNode`, `stackQuantity`, `nodemeta`,\n"
" `x`, `y`, `z`,\n"
" `oldNode`, `oldParam1`, `oldParam2`, `oldMeta`,\n"
" `newNode`, `newParam1`, `newParam2`, `newMeta`,\n"
" `guessedActor`\n"
"FROM `action`\n"
"WHERE `timestamp` >= ?\n"
" AND `actor` = ?\n"
"ORDER BY `timestamp` DESC, `id` DESC\n",
-1, &stmt_select_withActor, NULL));
SQLOK(sqlite3_prepare_v2(db, "SELECT `id`, `name` FROM `actor`",
-1, &stmt_knownActor_select, NULL));
SQLOK(sqlite3_prepare_v2(db, "INSERT INTO `actor` (`name`) VALUES (?)",
-1, &stmt_knownActor_insert, NULL));
SQLOK(sqlite3_prepare_v2(db, "SELECT `id`, `name` FROM `node`",
-1, &stmt_knownNode_select, NULL));
SQLOK(sqlite3_prepare_v2(db, "INSERT INTO `node` (`name`) VALUES (?)",
-1, &stmt_knownNode_insert, NULL));
verbosestream << "SQL prepared statements setup correctly" << std::endl;
while (sqlite3_step(stmt_knownActor_select) == SQLITE_ROW) {
registerNewActor(
sqlite3_column_int(stmt_knownActor_select, 0),
reinterpret_cast<const char *>(sqlite3_column_text(stmt_knownActor_select, 1))
);
}
SQLOK(sqlite3_reset(stmt_knownActor_select));
while (sqlite3_step(stmt_knownNode_select) == SQLITE_ROW) {
registerNewNode(
sqlite3_column_int(stmt_knownNode_select, 0),
reinterpret_cast<const char *>(sqlite3_column_text(stmt_knownNode_select, 1))
);
}
SQLOK(sqlite3_reset(stmt_knownNode_select));
return needs_create;
}
bool RollbackManager::registerRow(const ActionRow & row)
{
sqlite3_stmt * stmt_do = (row.id) ? stmt_replace : stmt_insert;
bool nodeMeta = false;
SQLOK(sqlite3_bind_int (stmt_do, 1, row.actor));
SQLOK(sqlite3_bind_int64(stmt_do, 2, row.timestamp));
SQLOK(sqlite3_bind_int (stmt_do, 3, row.type));
if (row.type == RollbackAction::TYPE_MODIFY_INVENTORY_STACK) {
const std::string & loc = row.location;
nodeMeta = (loc.substr(0, 9) == "nodemeta:");
SQLOK(sqlite3_bind_text(stmt_do, 4, row.list.c_str(), row.list.size(), NULL));
SQLOK(sqlite3_bind_int (stmt_do, 5, row.index));
SQLOK(sqlite3_bind_int (stmt_do, 6, row.add));
SQLOK(sqlite3_bind_int (stmt_do, 7, row.stack.id));
SQLOK(sqlite3_bind_int (stmt_do, 8, row.stack.count));
SQLOK(sqlite3_bind_int (stmt_do, 9, (int) nodeMeta));
if (nodeMeta) {
std::string::size_type p1, p2;
p1 = loc.find(':') + 1;
p2 = loc.find(',');
std::string x = loc.substr(p1, p2 - p1);
p1 = p2 + 1;
p2 = loc.find(',', p1);
std::string y = loc.substr(p1, p2 - p1);
std::string z = loc.substr(p2 + 1);
SQLOK(sqlite3_bind_int(stmt_do, 10, atoi(x.c_str())));
SQLOK(sqlite3_bind_int(stmt_do, 11, atoi(y.c_str())));
SQLOK(sqlite3_bind_int(stmt_do, 12, atoi(z.c_str())));
}
} else {
SQLOK(sqlite3_bind_null(stmt_do, 4));
SQLOK(sqlite3_bind_null(stmt_do, 5));
SQLOK(sqlite3_bind_null(stmt_do, 6));
SQLOK(sqlite3_bind_null(stmt_do, 7));
SQLOK(sqlite3_bind_null(stmt_do, 8));
SQLOK(sqlite3_bind_null(stmt_do, 9));
}
if (row.type == RollbackAction::TYPE_SET_NODE) {
SQLOK(sqlite3_bind_int (stmt_do, 10, row.x));
SQLOK(sqlite3_bind_int (stmt_do, 11, row.y));
SQLOK(sqlite3_bind_int (stmt_do, 12, row.z));
SQLOK(sqlite3_bind_int (stmt_do, 13, row.oldNode));
SQLOK(sqlite3_bind_int (stmt_do, 14, row.oldParam1));
SQLOK(sqlite3_bind_int (stmt_do, 15, row.oldParam2));
SQLOK(sqlite3_bind_text(stmt_do, 16, row.oldMeta.c_str(), row.oldMeta.size(), NULL));
SQLOK(sqlite3_bind_int (stmt_do, 17, row.newNode));
SQLOK(sqlite3_bind_int (stmt_do, 18, row.newParam1));
SQLOK(sqlite3_bind_int (stmt_do, 19, row.newParam2));
SQLOK(sqlite3_bind_text(stmt_do, 20, row.newMeta.c_str(), row.newMeta.size(), NULL));
SQLOK(sqlite3_bind_int (stmt_do, 21, row.guessed ? 1 : 0));
} else {
if (!nodeMeta) {
SQLOK(sqlite3_bind_null(stmt_do, 10));
SQLOK(sqlite3_bind_null(stmt_do, 11));
SQLOK(sqlite3_bind_null(stmt_do, 12));
}
SQLOK(sqlite3_bind_null(stmt_do, 13));
SQLOK(sqlite3_bind_null(stmt_do, 14));
SQLOK(sqlite3_bind_null(stmt_do, 15));
SQLOK(sqlite3_bind_null(stmt_do, 16));
SQLOK(sqlite3_bind_null(stmt_do, 17));
SQLOK(sqlite3_bind_null(stmt_do, 18));
SQLOK(sqlite3_bind_null(stmt_do, 19));
SQLOK(sqlite3_bind_null(stmt_do, 20));
SQLOK(sqlite3_bind_null(stmt_do, 21));
}
if (row.id) {
SQLOK(sqlite3_bind_int(stmt_do, 22, row.id));
}
int written = sqlite3_step(stmt_do);
SQLOK(sqlite3_reset(stmt_do));
return written == SQLITE_DONE;
}
const std::list<ActionRow> RollbackManager::actionRowsFromSelect(sqlite3_stmt* stmt)
{
std::list<ActionRow> rows;
const unsigned char * text;
size_t size;
while (sqlite3_step(stmt) == SQLITE_ROW) {
ActionRow row;
row.actor = sqlite3_column_int (stmt, 0);
row.timestamp = sqlite3_column_int64(stmt, 1);
row.type = sqlite3_column_int (stmt, 2);
row.nodeMeta = 0;
if (row.type == RollbackAction::TYPE_MODIFY_INVENTORY_STACK) {
text = sqlite3_column_text (stmt, 3);
size = sqlite3_column_bytes(stmt, 3);
row.list = std::string(reinterpret_cast<const char*>(text), size);
row.index = sqlite3_column_int(stmt, 4);
row.add = sqlite3_column_int(stmt, 5);
row.stack.id = sqlite3_column_int(stmt, 6);
row.stack.count = sqlite3_column_int(stmt, 7);
row.nodeMeta = sqlite3_column_int(stmt, 8);
}
if (row.type == RollbackAction::TYPE_SET_NODE || row.nodeMeta) {
row.x = sqlite3_column_int(stmt, 9);
row.y = sqlite3_column_int(stmt, 10);
row.z = sqlite3_column_int(stmt, 11);
}
if (row.type == RollbackAction::TYPE_SET_NODE) {
row.oldNode = sqlite3_column_int(stmt, 12);
row.oldParam1 = sqlite3_column_int(stmt, 13);
row.oldParam2 = sqlite3_column_int(stmt, 14);
text = sqlite3_column_text (stmt, 15);
size = sqlite3_column_bytes(stmt, 15);
row.oldMeta = std::string(reinterpret_cast<const char*>(text), size);
row.newNode = sqlite3_column_int(stmt, 16);
row.newParam1 = sqlite3_column_int(stmt, 17);
row.newParam2 = sqlite3_column_int(stmt, 18);
text = sqlite3_column_text(stmt, 19);
size = sqlite3_column_bytes(stmt, 19);
row.newMeta = std::string(reinterpret_cast<const char*>(text), size);
row.guessed = sqlite3_column_int(stmt, 20);
}
if (row.nodeMeta) {
row.location = "nodemeta:";
row.location += itos(row.x);
row.location += ',';
row.location += itos(row.y);
row.location += ',';
row.location += itos(row.z);
} else {
row.location = getActorName(row.actor);
}
rows.push_back(row);
}
SQLOK(sqlite3_reset(stmt));
return rows;
}
ActionRow RollbackManager::actionRowFromRollbackAction(const RollbackAction & action)
{
ActionRow row;
row.id = 0;
row.actor = getActorId(action.actor);
row.timestamp = action.unix_time;
row.type = action.type;
if (row.type == RollbackAction::TYPE_MODIFY_INVENTORY_STACK) {
row.location = action.inventory_location;
row.list = action.inventory_list;
row.index = action.inventory_index;
row.add = action.inventory_add;
row.stack = action.inventory_stack;
row.stack.id = getNodeId(row.stack.name);
} else {
row.x = action.p.X;
row.y = action.p.Y;
row.z = action.p.Z;
row.oldNode = getNodeId(action.n_old.name);
row.oldParam1 = action.n_old.param1;
row.oldParam2 = action.n_old.param2;
row.oldMeta = action.n_old.meta;
row.newNode = getNodeId(action.n_new.name);
row.newParam1 = action.n_new.param1;
row.newParam2 = action.n_new.param2;
row.newMeta = action.n_new.meta;
row.guessed = action.actor_is_guess;
}
return row;
}
const std::list<RollbackAction> RollbackManager::rollbackActionsFromActionRows(
const std::list<ActionRow> & rows)
{
std::list<RollbackAction> actions;
for (const ActionRow &row : rows) {
RollbackAction action;
action.actor = (row.actor) ? getActorName(row.actor) : "";
action.unix_time = row.timestamp;
action.type = static_cast<RollbackAction::Type>(row.type);
switch (action.type) {
case RollbackAction::TYPE_MODIFY_INVENTORY_STACK:
action.inventory_location = row.location;
action.inventory_list = row.list;
action.inventory_index = row.index;
action.inventory_add = row.add;
action.inventory_stack = row.stack;
if (action.inventory_stack.name.empty()) {
action.inventory_stack.name = getNodeName(row.stack.id);
}
break;
case RollbackAction::TYPE_SET_NODE:
action.p = v3s16(row.x, row.y, row.z);
action.n_old.name = getNodeName(row.oldNode);
action.n_old.param1 = row.oldParam1;
action.n_old.param2 = row.oldParam2;
action.n_old.meta = row.oldMeta;
action.n_new.name = getNodeName(row.newNode);
action.n_new.param1 = row.newParam1;
action.n_new.param2 = row.newParam2;
action.n_new.meta = row.newMeta;
break;
default:
throw ("W.T.F.");
break;
}
actions.push_back(action);
}
return actions;
}
const std::list<ActionRow> RollbackManager::getRowsSince(time_t firstTime, const std::string & actor)
{
sqlite3_stmt *stmt_stmt = actor.empty() ? stmt_select : stmt_select_withActor;
sqlite3_bind_int64(stmt_stmt, 1, firstTime);
if (!actor.empty()) {
sqlite3_bind_int(stmt_stmt, 2, getActorId(actor));
}
const std::list<ActionRow> & rows = actionRowsFromSelect(stmt_stmt);
sqlite3_reset(stmt_stmt);
return rows;
}
const std::list<ActionRow> RollbackManager::getRowsSince_range(
time_t start_time, v3s16 p, int range, int limit)
{
sqlite3_bind_int64(stmt_select_range, 1, start_time);
sqlite3_bind_int (stmt_select_range, 2, static_cast<int>(p.X - range));
sqlite3_bind_int (stmt_select_range, 3, static_cast<int>(p.X + range));
sqlite3_bind_int (stmt_select_range, 4, static_cast<int>(p.Y - range));
sqlite3_bind_int (stmt_select_range, 5, static_cast<int>(p.Y + range));
sqlite3_bind_int (stmt_select_range, 6, static_cast<int>(p.Z - range));
sqlite3_bind_int (stmt_select_range, 7, static_cast<int>(p.Z + range));
sqlite3_bind_int (stmt_select_range, 8, limit);
const std::list<ActionRow> & rows = actionRowsFromSelect(stmt_select_range);
sqlite3_reset(stmt_select_range);
return rows;
}
const std::list<RollbackAction> RollbackManager::getActionsSince_range(
time_t start_time, v3s16 p, int range, int limit)
{
return rollbackActionsFromActionRows(getRowsSince_range(start_time, p, range, limit));
}
const std::list<RollbackAction> RollbackManager::getActionsSince(
time_t start_time, const std::string & actor)
{
return rollbackActionsFromActionRows(getRowsSince(start_time, actor));
}
// Get nearness factor for subject's action for this action
// Return value: 0 = impossible, >0 = factor
float RollbackManager::getSuspectNearness(bool is_guess, v3s16 suspect_p,
time_t suspect_t, v3s16 action_p, time_t action_t)
{
// Suspect cannot cause things in the past
if (action_t < suspect_t) {
return 0; // 0 = cannot be
}
// Start from 100
int f = 100;
// Distance (1 node = -x points)
f -= POINTS_PER_NODE * intToFloat(suspect_p, 1).getDistanceFrom(intToFloat(action_p, 1));
// Time (1 second = -x points)
f -= 1 * (action_t - suspect_t);
// If is a guess, halve the points
if (is_guess) {
f *= 0.5;
}
// Limit to 0
if (f < 0) {
f = 0;
}
return f;
}
void RollbackManager::reportAction(const RollbackAction &action_)
{
// Ignore if not important
if (!action_.isImportant(gamedef)) {
return;
}
RollbackAction action = action_;
action.unix_time = time(0);
// Figure out actor
action.actor = current_actor;
action.actor_is_guess = current_actor_is_guess;
if (action.actor.empty()) { // If actor is not known, find out suspect or cancel
v3s16 p;
if (!action.getPosition(&p)) {
return;
}
action.actor = getSuspect(p, 83, 1);
if (action.actor.empty()) {
return;
}
action.actor_is_guess = true;
}
addAction(action);
}
std::string RollbackManager::getActor()
{
return current_actor;
}
bool RollbackManager::isActorGuess()
{
return current_actor_is_guess;
}
void RollbackManager::setActor(const std::string & actor, bool is_guess)
{
current_actor = actor;
current_actor_is_guess = is_guess;
}
std::string RollbackManager::getSuspect(v3s16 p, float nearness_shortcut,
float min_nearness)
{
if (!current_actor.empty()) {
return current_actor;
}
int cur_time = time(0);
time_t first_time = cur_time - (100 - min_nearness);
RollbackAction likely_suspect;
float likely_suspect_nearness = 0;
for (std::list<RollbackAction>::const_reverse_iterator
i = action_latest_buffer.rbegin();
i != action_latest_buffer.rend(); ++i) {
if (i->unix_time < first_time) {
break;
}
if (i->actor.empty()) {
continue;
}
// Find position of suspect or continue
v3s16 suspect_p;
if (!i->getPosition(&suspect_p)) {
continue;
}
float f = getSuspectNearness(i->actor_is_guess, suspect_p,
i->unix_time, p, cur_time);
if (f >= min_nearness && f > likely_suspect_nearness) {
likely_suspect_nearness = f;
likely_suspect = *i;
if (likely_suspect_nearness >= nearness_shortcut) {
break;
}
}
}
// No likely suspect was found
if (likely_suspect_nearness == 0) {
return "";
}
// Likely suspect was found
return likely_suspect.actor;
}
void RollbackManager::flush()
{
sqlite3_exec(db, "BEGIN", NULL, NULL, NULL);
std::list<RollbackAction>::const_iterator iter;
for (iter = action_todisk_buffer.begin();
iter != action_todisk_buffer.end();
++iter) {
if (iter->actor.empty()) {
continue;
}
registerRow(actionRowFromRollbackAction(*iter));
}
sqlite3_exec(db, "COMMIT", NULL, NULL, NULL);
action_todisk_buffer.clear();
}
void RollbackManager::addAction(const RollbackAction & action)
{
action_todisk_buffer.push_back(action);
action_latest_buffer.push_back(action);
// Flush to disk sometimes
if (action_todisk_buffer.size() >= 500) {
flush();
}
}
std::list<RollbackAction> RollbackManager::getNodeActors(v3s16 pos, int range,
time_t seconds, int limit)
{
flush();
time_t cur_time = time(0);
time_t first_time = cur_time - seconds;
return getActionsSince_range(first_time, pos, range, limit);
}
std::list<RollbackAction> RollbackManager::getRevertActions(
const std::string &actor_filter,
time_t seconds)
{
time_t cur_time = time(0);
time_t first_time = cur_time - seconds;
flush();
return getActionsSince(first_time, actor_filter);
}

102
src/server/rollback.h Normal file
View file

@ -0,0 +1,102 @@
/*
Minetest
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#pragma once
#include <string>
#include "irr_v3d.h"
#include "rollback_interface.h"
#include <list>
#include <vector>
#include "sqlite3.h"
class IGameDef;
struct ActionRow;
struct Entity;
class RollbackManager: public IRollbackManager
{
public:
RollbackManager(const std::string & world_path, IGameDef * gamedef);
~RollbackManager();
void reportAction(const RollbackAction & action_);
std::string getActor();
bool isActorGuess();
void setActor(const std::string & actor, bool is_guess);
std::string getSuspect(v3s16 p, float nearness_shortcut,
float min_nearness);
void flush();
void addAction(const RollbackAction & action);
std::list<RollbackAction> getNodeActors(v3s16 pos, int range,
time_t seconds, int limit);
std::list<RollbackAction> getRevertActions(
const std::string & actor_filter, time_t seconds);
private:
void registerNewActor(const int id, const std::string & name);
void registerNewNode(const int id, const std::string & name);
int getActorId(const std::string & name);
int getNodeId(const std::string & name);
const char * getActorName(const int id);
const char * getNodeName(const int id);
bool createTables();
bool initDatabase();
bool registerRow(const ActionRow & row);
const std::list<ActionRow> actionRowsFromSelect(sqlite3_stmt * stmt);
ActionRow actionRowFromRollbackAction(const RollbackAction & action);
const std::list<RollbackAction> rollbackActionsFromActionRows(
const std::list<ActionRow> & rows);
const std::list<ActionRow> getRowsSince(time_t firstTime,
const std::string & actor);
const std::list<ActionRow> getRowsSince_range(time_t firstTime, v3s16 p,
int range, int limit);
const std::list<RollbackAction> getActionsSince_range(time_t firstTime, v3s16 p,
int range, int limit);
const std::list<RollbackAction> getActionsSince(time_t firstTime,
const std::string & actor = "");
static float getSuspectNearness(bool is_guess, v3s16 suspect_p,
time_t suspect_t, v3s16 action_p, time_t action_t);
IGameDef *gamedef = nullptr;
std::string current_actor;
bool current_actor_is_guess = false;
std::list<RollbackAction> action_todisk_buffer;
std::list<RollbackAction> action_latest_buffer;
std::string database_path;
sqlite3 * db;
sqlite3_stmt * stmt_insert;
sqlite3_stmt * stmt_replace;
sqlite3_stmt * stmt_select;
sqlite3_stmt * stmt_select_range;
sqlite3_stmt * stmt_select_withActor;
sqlite3_stmt * stmt_knownActor_select;
sqlite3_stmt * stmt_knownActor_insert;
sqlite3_stmt * stmt_knownNode_select;
sqlite3_stmt * stmt_knownNode_insert;
std::vector<Entity> knownActors;
std::vector<Entity> knownNodes;
};

109
src/server/serverlist.cpp Normal file
View file

@ -0,0 +1,109 @@
/*
Minetest
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <iostream>
#include "version.h"
#include "settings.h"
#include "serverlist.h"
#include "filesys.h"
#include "log.h"
#include "network/networkprotocol.h"
#include <json/json.h>
#include "convert_json.h"
#include "httpfetch.h"
#include "server.h"
namespace ServerList
{
#if USE_CURL
void sendAnnounce(AnnounceAction action,
const u16 port,
const std::vector<std::string> &clients_names,
const double uptime,
const u32 game_time,
const float lag,
const std::string &gameid,
const std::string &mg_name,
const std::vector<ModSpec> &mods,
bool dedicated)
{
static const char *aa_names[] = {"start", "update", "delete"};
Json::Value server;
server["action"] = aa_names[action];
server["port"] = port;
if (g_settings->exists("server_address")) {
server["address"] = g_settings->get("server_address");
}
if (action != AA_DELETE) {
server["name"] = g_settings->get("server_name");
server["description"] = g_settings->get("server_description");
server["version"] = g_version_string;
server["proto_min"] = Server::getProtocolVersionMin();
server["proto_max"] = Server::getProtocolVersionMax();
server["url"] = g_settings->get("server_url");
server["creative"] = g_settings->getBool("creative_mode");
server["damage"] = g_settings->getBool("enable_damage");
server["password"] = g_settings->getBool("disallow_empty_password");
server["pvp"] = g_settings->getBool("enable_pvp");
server["uptime"] = (int) uptime;
server["game_time"] = game_time;
server["clients"] = (int) clients_names.size();
server["clients_max"] = g_settings->getU16("max_users");
server["clients_list"] = Json::Value(Json::arrayValue);
for (const std::string &clients_name : clients_names) {
server["clients_list"].append(clients_name);
}
if (!gameid.empty())
server["gameid"] = gameid;
}
if (action == AA_START) {
server["dedicated"] = dedicated;
server["rollback"] = g_settings->getBool("enable_rollback_recording");
server["mapgen"] = mg_name;
server["privs"] = g_settings->get("default_privs");
server["can_see_far_names"] = g_settings->getS16("player_transfer_distance") <= 0;
server["mods"] = Json::Value(Json::arrayValue);
for (const ModSpec &mod : mods) {
server["mods"].append(mod.name);
}
} else if (action == AA_UPDATE) {
if (lag)
server["lag"] = lag;
}
if (action == AA_START) {
actionstream << "Announcing " << aa_names[action] << " to " <<
g_settings->get("serverlist_url") << std::endl;
} else {
infostream << "Announcing " << aa_names[action] << " to " <<
g_settings->get("serverlist_url") << std::endl;
}
HTTPFetchRequest fetch_request;
fetch_request.caller = HTTPFETCH_PRINT_ERR;
fetch_request.url = g_settings->get("serverlist_url") + std::string("/announce");
fetch_request.method = HTTP_POST;
fetch_request.fields["json"] = fastWriteJson(server);
fetch_request.multipart = true;
httpfetch_async(fetch_request);
}
#endif
} // namespace ServerList

41
src/server/serverlist.h Normal file
View file

@ -0,0 +1,41 @@
/*
Minetest
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "config.h"
#include "content/mods.h"
#include "json-forwards.h"
#include <iostream>
#pragma once
// Note that client serverlist handling is all in Lua, this is only announcements now.
namespace ServerList
{
#if USE_CURL
enum AnnounceAction {AA_START, AA_UPDATE, AA_DELETE};
void sendAnnounce(AnnounceAction, u16 port,
const std::vector<std::string> &clients_names = std::vector<std::string>(),
double uptime = 0, u32 game_time = 0, float lag = 0,
const std::string &gameid = "", const std::string &mg_name = "",
const std::vector<ModSpec> &mods = std::vector<ModSpec>(),
bool dedicated = false);
#endif
}