wesnothd: use std::chrono instead of the C time API

This commit is contained in:
Charles Dang 2024-11-15 00:22:09 -05:00
parent c2b2664eb2
commit 79120f1e31
20 changed files with 288 additions and 234 deletions

View file

@ -23,6 +23,8 @@
#include "log.hpp"
#include "filesystem.hpp"
#include "mt_rng.hpp"
#include "serialization/chrono.hpp"
#include "serialization/string_utils.hpp"
#include "utils/general.hpp"
#include <boost/algorithm/string.hpp>
@ -399,41 +401,28 @@ bool broke_strict() {
return strict_threw_;
}
std::string get_timestamp(const std::time_t& t, const std::string& format) {
std::ostringstream ss;
ss << std::put_time(std::localtime(&t), format.c_str());
return ss.str();
}
std::string get_timespan(const std::time_t& t) {
std::ostringstream sout;
// There doesn't seem to be any library function for this
const std::time_t minutes = t / 60;
const std::time_t days = minutes / 60 / 24;
if(t <= 0) {
sout << "expired";
} else if(minutes == 0) {
sout << t << " seconds";
} else if(days == 0) {
sout << minutes / 60 << " hours, " << minutes % 60 << " minutes";
} else {
sout << days << " days, " << (minutes / 60) % 24 << " hours, " << minutes % 60 << " minutes";
}
return sout.str();
}
static void print_precise_timestamp(std::ostream& out) noexcept
std::string format_timespan(const std::chrono::seconds& span)
{
try {
auto now = std::chrono::system_clock::now();
auto seconds = std::chrono::time_point_cast<std::chrono::seconds>(now);
auto fractional = std::chrono::duration_cast<std::chrono::microseconds>(now - seconds);
std::time_t tm = std::chrono::system_clock::to_time_t(seconds);
char c = out.fill('0');
out << std::put_time(std::localtime(&tm), "%Y%m%d %H:%M:%S") << "." << std::setw(6) << fractional.count() << ' ';
out.fill(c);
} catch(...) {}
if(span <= std::chrono::seconds{0}) {
return "expired";
}
auto [days, hours, minutes, seconds] = chrono::deconstruct_duration(span);
std::vector<std::string> formatted_values;
// TODO C++20: see if we can use the duration stream operators
const auto format_time = [&formatted_values](const auto& val, const std::string& suffix) {
if(val > std::decay_t<decltype(val)>{0}) {
formatted_values.push_back(formatter{} << val.count() << " " << suffix);
}
};
format_time(days, "days");
format_time(hours, "hours");
format_time(minutes, "minutes");
format_time(seconds, "seconds");
return utils::join(formatted_values, ", ");
}
void set_log_sanitize(bool sanitize) {
@ -502,10 +491,12 @@ void log_in_progress::operator|(formatter&& message)
for(int i = 0; i < indent; ++i)
stream_ << " ";
if(timestamp_) {
auto now = std::chrono::system_clock::now();
stream_ << chrono::format_local_timestamp(now); // Truncates precision to seconds
if(precise_timestamp) {
print_precise_timestamp(stream_);
} else {
stream_ << get_timestamp(std::time(nullptr));
auto as_seconds = std::chrono::time_point_cast<std::chrono::seconds>(now);
auto fractional = std::chrono::duration_cast<std::chrono::microseconds>(now - as_seconds);
stream_ << "." << std::setw(6) << fractional.count();
}
}
stream_ << prefix_ << sanitize_log(message.str());

View file

@ -230,8 +230,8 @@ public:
void timestamps(bool);
void precise_timestamps(bool);
std::string get_timestamp(const std::time_t& t, const std::string& format="%Y%m%d %H:%M:%S ");
std::string get_timespan(const std::time_t& t);
/** TODO: we also have utils::format_timespan, which does something very similar... */
std::string format_timespan(const std::chrono::seconds& span);
std::string sanitize_log(const std::string& logstr);
std::string get_log_file_path();

View file

@ -1755,10 +1755,11 @@ compression::format prefs::save_compression_format()
std::string prefs::get_chat_timestamp(const std::time_t& t)
{
if(chat_timestamp()) {
auto temp = std::chrono::system_clock::from_time_t(t); // FIXME: remove
if(use_twelve_hour_clock_format() == false) {
return lg::get_timestamp(t, _("[%H:%M]")) + " ";
return chrono::format_local_timestamp(temp, _("[%H:%M]")) + " ";
} else {
return lg::get_timestamp(t, _("[%I:%M %p]")) + " ";
return chrono::format_local_timestamp(temp, _("[%I:%M %p]")) + " ";
}
}

View file

@ -17,9 +17,37 @@
#include "config_attribute_value.hpp"
#include <chrono>
#include <iomanip>
#include <sstream>
#include <string_view>
#if __cpp_lib_chrono >= 201907L
#define CPP20_CHRONO_SUPPORT
#endif
namespace chrono
{
#ifdef CPP20_CHRONO_SUPPORT
using std::chrono::days;
using std::chrono::weeks;
using std::chrono::months;
using std::chrono::years;
#else
using days = std::chrono::duration<int, std::ratio<86400>>;
using weeks = std::chrono::duration<int, std::ratio<604800>>;
using months = std::chrono::duration<int, std::ratio<2629746>>;
using years = std::chrono::duration<int, std::ratio<31556952>>;
#endif
inline auto parse_timestamp(long long val)
{
return std::chrono::system_clock::from_time_t(val);
}
inline auto parse_timestamp(const config_attribute_value& val)
{
return std::chrono::system_clock::from_time_t(val.to_long_long());
@ -30,10 +58,29 @@ inline auto serialize_timestamp(const std::chrono::system_clock::time_point& tim
return std::chrono::system_clock::to_time_t(time);
}
inline auto format_local_timestamp(const std::chrono::system_clock::time_point& time, std::string_view format = "%F %T")
{
std::ostringstream ss;
auto as_time_t = std::chrono::system_clock::to_time_t(time);
ss << std::put_time(std::localtime(&as_time_t), format.data());
return ss.str();
}
template<typename Duration>
inline auto parse_duration(const config_attribute_value& val, const Duration& def = Duration{0})
{
return Duration{val.to_long_long(def.count())};
}
template<typename Rep, typename Period>
constexpr auto deconstruct_duration(const std::chrono::duration<Rep, Period>& span)
{
auto days = std::chrono::duration_cast<chrono::days>(span);
auto hours = std::chrono::duration_cast<std::chrono::hours>(span - days);
auto minutes = std::chrono::duration_cast<std::chrono::minutes>(span - days - hours);
auto seconds = std::chrono::duration_cast<std::chrono::seconds>(span - days - hours - minutes);
return std::tuple{ days, hours, minutes, seconds };
}
} // namespace chrono

View file

@ -313,7 +313,7 @@ ban_check dbconn::get_ban_info(const std::string& name, const std::string& ip)
// selected ban_type value must be part of user_handler::BAN_TYPE
ban_check b;
get_complex_results(connection_, b, "select ban_userid, ban_email, case when ban_ip != '' then 1 when ban_userid != 0 then 2 when ban_email != '' then 3 end as ban_type, ban_end from `"+db_banlist_table_+"` where (ban_ip = ? or ban_userid = (select user_id from `"+db_users_table_+"` where UPPER(username) = UPPER(?)) or UPPER(ban_email) = (select UPPER(user_email) from `"+db_users_table_+"` where UPPER(username) = UPPER(?))) AND ban_exclude = 0 AND (ban_end = 0 OR ban_end >= ?)",
{ ip, name, name, std::time(nullptr) });
{ ip, name, name, std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()) });
return b;
}
catch(const mariadb::exception::base& e)

View file

@ -17,6 +17,7 @@
#include "server/common/forum_user_handler.hpp"
#include "server/wesnothd/server.hpp"
#include "serialization/chrono.hpp"
#include "hash.hpp"
#include "log.hpp"
#include "config.hpp"
@ -99,7 +100,8 @@ std::string fuh::extract_salt(const std::string& name) {
}
void fuh::user_logged_in(const std::string& name) {
conn_.write_user_int("user_lastvisit", name, static_cast<int>(std::time(nullptr)));
auto now = chrono::serialize_timestamp(std::chrono::system_clock::now());
conn_.write_user_int("user_lastvisit", name, static_cast<int>(now));
}
bool fuh::user_exists(const std::string& name) {
@ -156,14 +158,15 @@ std::string fuh::user_info(const std::string& name) {
throw error("No user with the name '" + name + "' exists.");
}
std::time_t reg_date = get_registrationdate(name);
std::time_t ll_date = get_lastlogin(name);
auto reg_date = get_registrationdate(name);
auto ll_date = get_lastlogin(name);
std::string reg_string = ctime(&reg_date);
static constexpr std::string_view format = "%a %b %d %T %Y"; // equivalent to std::ctime
std::string reg_string = chrono::format_local_timestamp(reg_date, format);
std::string ll_string;
if(ll_date) {
ll_string = ctime(&ll_date);
if(ll_date > decltype(ll_date){}) {
ll_string = chrono::format_local_timestamp(ll_date, format);
} else {
ll_string = "Never\n";
}
@ -191,12 +194,12 @@ void fuh::db_update_addon_download_count(const std::string& instance_version, co
return conn_.update_addon_download_count(instance_version, id, version);
}
std::time_t fuh::get_lastlogin(const std::string& user) {
return std::time_t(conn_.get_user_int(db_extra_table_, "user_lastvisit", user));
std::chrono::system_clock::time_point fuh::get_lastlogin(const std::string& user) {
return chrono::parse_timestamp(conn_.get_user_int(db_extra_table_, "user_lastvisit", user));
}
std::time_t fuh::get_registrationdate(const std::string& user) {
return std::time_t(conn_.get_user_int(db_users_table_, "user_regdate", user));
std::chrono::system_clock::time_point fuh::get_registrationdate(const std::string& user) {
return chrono::parse_timestamp(conn_.get_user_int(db_users_table_, "user_regdate", user));
}
std::string fuh::get_uuid(){

View file

@ -18,9 +18,9 @@
#include "server/common/user_handler.hpp"
#include "server/common/dbconn.hpp"
#include <chrono>
#include <vector>
#include <memory>
#include <ctime>
/**
* A class to handle the non-SQL logic for connecting to the phpbb forum database.
@ -344,11 +344,11 @@ private:
* @param user The player's username.
* @return The player's last login time.
*/
std::time_t get_lastlogin(const std::string& user);
std::chrono::system_clock::time_point get_lastlogin(const std::string& user);
/**
* @param user The player's username.
* @return The player's forum registration date.
*/
std::time_t get_registrationdate(const std::string& user);
std::chrono::system_clock::time_point get_registrationdate(const std::string& user);
};

View file

@ -14,28 +14,34 @@
#ifdef HAVE_MYSQLPP
#include <ctime>
#include "server/common/resultsets/ban_check.hpp"
#include "server/common/user_handler.hpp"
#include "serialization/chrono.hpp"
ban_check::ban_check()
: ban_type(user_handler::BAN_TYPE::BAN_NONE)
, ban_duration(0)
, user_id(0)
, email()
{
ban_type = user_handler::BAN_TYPE::BAN_NONE;
ban_duration = 0;
user_id = 0;
email = "";
}
ban_check::ban_check(const mariadb::result_set& rslt)
: ban_type(rslt.get_signed32("ban_type"))
, ban_duration(0)
, user_id(rslt.get_signed32("ban_userid"))
, email(rslt.get_string("ban_email"))
{
auto ban_end = rslt.get_signed32("ban_end");
if(ban_end == 0) return;
auto time_remaining = chrono::parse_timestamp(ban_end) - std::chrono::system_clock::now();
ban_duration = std::chrono::duration_cast<std::chrono::seconds>(time_remaining);
}
void ban_check::read(mariadb::result_set_ref rslt)
{
if(rslt->next())
{
ban_type = rslt->get_signed32("ban_type");
ban_duration = rslt->get_signed32("ban_end") != 0 ? rslt->get_signed32("ban_end") - std::time(nullptr) : 0;
user_id = rslt->get_signed32("ban_userid");
email = rslt->get_string("ban_email");
}
if(rslt->next()) { *this = ban_check{*rslt}; }
}
long ban_check::get_ban_type()
@ -43,7 +49,7 @@ long ban_check::get_ban_type()
return ban_type;
}
int ban_check::get_ban_duration()
std::chrono::seconds ban_check::get_ban_duration()
{
return ban_duration;
}

View file

@ -18,19 +18,22 @@
#include "server/common/resultsets/rs_base.hpp"
#include <chrono>
class ban_check : public rs_base
{
public:
ban_check();
void read(mariadb::result_set_ref rslt);
explicit ban_check(const mariadb::result_set& rslt);
void read(mariadb::result_set_ref rslt) override;
long get_ban_type();
int get_ban_duration();
std::chrono::seconds get_ban_duration();
int get_user_id();
std::string get_email();
private:
long ban_type;
int ban_duration;
std::chrono::seconds ban_duration;
int user_id;
std::string email;
};

View file

@ -19,7 +19,6 @@ class config;
#include "exceptions.hpp"
#include <ctime>
#include <string>
#include <boost/asio/io_service.hpp>
@ -103,20 +102,8 @@ public:
/** Ban status description */
struct ban_info
{
BAN_TYPE type; /**< Ban type */
std::time_t duration; /**< Ban duration (0 if permanent) */
ban_info()
: type(BAN_NONE)
, duration(0)
{
}
ban_info(BAN_TYPE ptype, std::time_t pduration)
: type(ptype)
, duration(pduration)
{
}
BAN_TYPE type = BAN_NONE; /**< Ban type */
std::chrono::seconds duration{0}; /**< Ban duration (0 if permanent) */
};
/**

View file

@ -18,6 +18,7 @@
#include "lexical_cast.hpp"
#include "log.hpp"
#include "serialization/binary_or_text.hpp"
#include "serialization/chrono.hpp"
#include "serialization/parser.hpp"
#include "serialization/string_utils.hpp"
#include "serialization/unicode.hpp"
@ -67,8 +68,8 @@ banned::banned(const std::string& ip)
: ip_(0)
, mask_(0)
, ip_text_()
, end_time_(0)
, start_time_(0)
, end_time_()
, start_time_()
, reason_()
, who_banned_(who_banned_default_)
, group_()
@ -80,7 +81,7 @@ banned::banned(const std::string& ip)
}
banned::banned(const std::string& ip,
const std::time_t end_time,
const utils::optional<std::chrono::system_clock::time_point>& end_time,
const std::string& reason,
const std::string& who_banned,
const std::string& group,
@ -89,7 +90,7 @@ banned::banned(const std::string& ip,
, mask_(0)
, ip_text_(ip)
, end_time_(end_time)
, start_time_(std::time(0))
, start_time_(std::chrono::system_clock::now())
, reason_(reason)
, who_banned_(who_banned)
, group_(group)
@ -104,8 +105,8 @@ banned::banned(const config& cfg)
: ip_(0)
, mask_(0)
, ip_text_()
, end_time_(0)
, start_time_(0)
, end_time_()
, start_time_()
, reason_()
, who_banned_(who_banned_default_)
, group_()
@ -172,11 +173,11 @@ void banned::read(const config& cfg)
nick_ = cfg["nick"].str();
if(cfg.has_attribute("end_time")) {
end_time_ = cfg["end_time"].to_time_t(0);
end_time_ = chrono::parse_timestamp(cfg["end_time"]);
}
if(cfg.has_attribute("start_time")) {
start_time_ = cfg["start_time"].to_time_t(0);
start_time_ = chrono::parse_timestamp(cfg["start_time"]);
}
reason_ = cfg["reason"].str();
@ -196,16 +197,12 @@ void banned::write(config& cfg) const
cfg["ip"] = get_ip();
cfg["nick"] = get_nick();
if(end_time_ > 0) {
std::stringstream ss;
ss << end_time_;
cfg["end_time"] = ss.str();
if(end_time_) {
cfg["end_time"] = chrono::serialize_timestamp(*end_time_);
}
if(start_time_ > 0) {
std::stringstream ss;
ss << start_time_;
cfg["start_time"] = ss.str();
if(start_time_) {
cfg["start_time"] = chrono::serialize_timestamp(*start_time_);
}
cfg["reason"] = reason_;
@ -221,34 +218,36 @@ void banned::write(config& cfg) const
std::string banned::get_human_start_time() const
{
if(start_time_ == 0) {
if(start_time_) {
return chrono::format_local_timestamp(*start_time_);
} else {
return "unknown";
}
return lg::get_timestamp(start_time_);
}
std::string banned::get_human_end_time() const
{
if(end_time_ == 0) {
if(end_time_) {
return chrono::format_local_timestamp(*end_time_);
} else {
return "permanent";
}
return lg::get_timestamp(end_time_);
}
std::string banned::get_human_time_span() const
{
if(end_time_ == 0) {
if(!end_time_) {
return "permanent";
}
return lg::get_timespan(end_time_ - std::time(nullptr));
auto remaining = *end_time_ - std::chrono::system_clock::now();
return lg::format_timespan(std::chrono::duration_cast<std::chrono::seconds>(remaining));
}
bool banned::operator>(const banned& b) const
{
return end_time_ > b.get_end_time();
static constexpr std::chrono::system_clock::time_point epoch;
return end_time_.value_or(epoch) > b.get_end_time().value_or(epoch);
}
unsigned int banned::get_mask_ip(unsigned int mask) const
@ -284,7 +283,7 @@ void ban_manager::read()
auto new_ban = std::make_shared<banned>(b);
assert(bans_.insert(new_ban).second);
if (new_ban->get_end_time() != 0)
if (new_ban->get_end_time())
time_queue_.push(new_ban);
} catch(const banned::error& e) {
ERR_SERVER << e.message << " while reading bans";
@ -330,13 +329,12 @@ void ban_manager::write()
writer.write(cfg);
}
bool ban_manager::parse_time(const std::string& duration, std::time_t* time) const
std::pair<bool, utils::optional<std::chrono::system_clock::time_point>> ban_manager::parse_time(
const std::string& duration, std::chrono::system_clock::time_point start_time) const
{
if (!time) return false;
if(duration.substr(0, 4) == "TIME") {
std::tm* loc;
loc = std::localtime(time);
auto as_time_t = std::chrono::system_clock::to_time_t(start_time);
std::tm* loc = std::localtime(&as_time_t);
std::size_t number = 0;
for(auto i = duration.begin() + 4; i != duration.end(); ++i) {
@ -369,24 +367,21 @@ bool ban_manager::parse_time(const std::string& duration, std::time_t* time) con
number = 0;
}
}
*time = mktime(loc);
return true;
return { true, std::chrono::system_clock::from_time_t(std::mktime(loc)) };
}
const auto time_itor = ban_times_.find(duration);
std::string dur_lower;
try {
dur_lower = utf8::lowercase(duration);
} catch(const utf8::invalid_utf8_exception& e) {
ERR_SERVER << "While parsing ban command duration string, caught an invalid utf8 exception: " << e.what();
return false;
return { false, utils::nullopt };
}
if(dur_lower == "permanent" || duration == "0") {
*time = 0;
} else if(ban_times_.find(duration) != ban_times_.end()) {
*time += time_itor->second;
return { true, utils::nullopt };
} else if(const auto time_itor = ban_times_.find(duration); time_itor != ban_times_.end()) {
return { true, start_time + time_itor->second };
} else {
std::string::const_iterator i = duration.begin();
int number = -1;
@ -406,7 +401,7 @@ bool ban_manager::parse_time(const std::string& duration, std::time_t* time) con
&& ++i != d_end && tolower(*i) == 'r'
&& ++i != d_end && tolower(*i) == 's') {
} else --i;
*time += number * 365*24*60*60; // a year;
start_time += chrono::years{number};
break;
case 'M':
if (++i != d_end && tolower(*i) == 'i') {
@ -416,7 +411,7 @@ bool ban_manager::parse_time(const std::string& duration, std::time_t* time) con
&& ++i != d_end && tolower(*i) == 'e'
&& ++i != d_end && tolower(*i) == 's') {
} else --i;
*time += number * 60;
start_time += std::chrono::minutes{number};
break;
}
--i;
@ -426,7 +421,7 @@ bool ban_manager::parse_time(const std::string& duration, std::time_t* time) con
&& ++i != d_end && tolower(*i) == 'h'
&& ++i != d_end && tolower(*i) == 's') {
} else --i;
*time += number * 30*24*60*60; // 30 days
start_time += chrono::months{number};
break;
case 'D':
case 'd':
@ -434,7 +429,7 @@ bool ban_manager::parse_time(const std::string& duration, std::time_t* time) con
&& ++i != d_end && tolower(*i) == 'y'
&& ++i != d_end && tolower(*i) == 's') {
} else --i;
*time += number * 24*60*60;
start_time += chrono::days{number};
break;
case 'H':
case 'h':
@ -443,7 +438,7 @@ bool ban_manager::parse_time(const std::string& duration, std::time_t* time) con
&& ++i != d_end && tolower(*i) == 'r'
&& ++i != d_end && tolower(*i) == 's') {
} else --i;
*time += number * 60*60;
start_time += std::chrono::hours{number};
break;
case 'm':
if (++i != d_end && tolower(*i) == 'o') {
@ -452,7 +447,7 @@ bool ban_manager::parse_time(const std::string& duration, std::time_t* time) con
&& ++i != d_end && tolower(*i) == 'h'
&& ++i != d_end && tolower(*i) == 's') {
} else --i;
*time += number * 30*24*60*60; // 30 days
start_time += chrono::months{number};
break;
}
--i;
@ -463,7 +458,7 @@ bool ban_manager::parse_time(const std::string& duration, std::time_t* time) con
&& ++i != d_end && tolower(*i) == 'e'
&& ++i != d_end && tolower(*i) == 's') {
} else --i;
*time += number * 60;
start_time += std::chrono::minutes{number};
break;
case 'S':
case 's':
@ -474,10 +469,10 @@ bool ban_manager::parse_time(const std::string& duration, std::time_t* time) con
&& ++i != d_end && tolower(*i) == 'd'
&& ++i != d_end && tolower(*i) == 's') {
} else --i;
*time += number;
start_time += std::chrono::seconds{number};
break;
default:
return false;
return { false, utils::nullopt };
break;
}
number = -1;
@ -485,15 +480,15 @@ bool ban_manager::parse_time(const std::string& duration, std::time_t* time) con
}
if(is_digit(*--i)) {
*time += number * 60; // default to minutes
start_time += std::chrono::minutes{number}; // default to minutes
}
}
return true;
return { true, start_time };
}
}
std::string ban_manager::ban(const std::string& ip,
const std::time_t& end_time,
const utils::optional<std::chrono::system_clock::time_point>& end_time,
const std::string& reason,
const std::string& who_banned,
const std::string& group,
@ -515,7 +510,7 @@ std::string ban_manager::ban(const std::string& ip,
try {
auto new_ban = std::make_shared<banned>(ip, end_time, reason, who_banned, group, nick);
bans_.insert(new_ban);
if(end_time != 0) {
if(end_time) {
time_queue_.push(new_ban);
}
ret << *new_ban;
@ -568,20 +563,26 @@ void ban_manager::unban_group(std::ostringstream& os, const std::string& group)
write();
}
void ban_manager::check_ban_times(std::time_t time_now)
void ban_manager::check_ban_times(const std::chrono::system_clock::time_point& time_now)
{
while(!time_queue_.empty()) {
banned_ptr ban = time_queue_.top();
const auto& end_time = ban->get_end_time();
if(ban->get_end_time() > time_now) {
if(!end_time || *end_time > time_now) {
// No bans going to expire
DBG_SERVER << "ban " << ban->get_ip() << " not removed. time: " << time_now << " end_time "
<< ban->get_end_time();
DBG_SERVER
<< "Ban on " << ban->get_ip() << " not removed."
<< " time: " << chrono::format_local_timestamp(time_now)
<< " end_time: " << (end_time ? chrono::format_local_timestamp(*end_time) : "none");
break;
}
// This ban is going to expire so delete it.
LOG_SERVER << "Remove a ban " << ban->get_ip() << ". time: " << time_now << " end_time " << ban->get_end_time();
LOG_SERVER
<< "Removing ban on " << ban->get_ip() << "."
<< " time: " << chrono::format_local_timestamp(time_now)
<< " end_time: " << chrono::format_local_timestamp(*end_time);
std::ostringstream os;
unban(os, ban->get_ip(), false);
time_queue_.pop();
@ -698,9 +699,12 @@ void ban_manager::load_config(const config& cfg)
{
ban_times_.clear();
for(const config& bt : cfg.child_range("ban_time")) {
std::time_t duration = 0;
if(parse_time(bt["time"], &duration)) {
ban_times_.emplace(bt["name"], duration);
// Use the zero time point so we can easily convert the end time point to a duration
auto [success, end_time] = parse_time(bt["time"], {});
if(success) {
auto duration = end_time.value_or(decltype(end_time)::value_type{}).time_since_epoch();
ban_times_.emplace(bt["name"], std::chrono::duration_cast<std::chrono::seconds>(duration));
}
}

View file

@ -16,8 +16,8 @@
#pragma once
#include "exceptions.hpp"
#include "utils/optional_fwd.hpp"
#include <ctime>
#include <list>
#include <map>
#include <queue>
@ -52,7 +52,7 @@ private:
typedef std::set<banned_ptr, banned_compare_subnet> ban_set;
typedef std::list<banned_ptr> deleted_ban_list;
typedef std::priority_queue<banned_ptr, std::vector<banned_ptr>, banned_compare> ban_time_queue;
typedef std::map<std::string, std::size_t> default_ban_times;
typedef std::map<std::string, std::chrono::seconds> default_ban_times;
typedef std::pair<unsigned int, unsigned int> ip_mask;
ip_mask parse_ip(const std::string&);
@ -61,8 +61,8 @@ class banned {
unsigned int ip_;
unsigned int mask_;
std::string ip_text_;
std::time_t end_time_;
std::time_t start_time_;
utils::optional<std::chrono::system_clock::time_point> end_time_;
utils::optional<std::chrono::system_clock::time_point> start_time_;
std::string reason_;
std::string who_banned_;
std::string group_;
@ -70,7 +70,13 @@ class banned {
static const std::string who_banned_default_;
public:
banned(const std::string& ip, const std::time_t end_time, const std::string& reason, const std::string& who_banned=who_banned_default_, const std::string& group="", const std::string& nick="");
banned(const std::string& ip,
const utils::optional<std::chrono::system_clock::time_point>& end_time,
const std::string& reason,
const std::string& who_banned = who_banned_default_,
const std::string& group = "",
const std::string& nick = "");
banned(const config&);
banned(const std::string& ip);
@ -78,13 +84,12 @@ public:
void read(const config&);
void write(config&) const;
std::time_t get_end_time() const
const auto& get_end_time() const
{ return end_time_; }
std::string get_human_end_time() const;
std::string get_human_start_time() const;
std::string get_human_time_span() const;
static std::string get_human_time(const std::time_t&);
std::string get_reason() const
{ return reason_; }
@ -141,10 +146,10 @@ class ban_manager
}
void init_ban_help();
void check_ban_times(std::time_t time_now);
void check_ban_times(const std::chrono::system_clock::time_point& time_now);
inline void expire_bans()
{
check_ban_times(std::time(nullptr));
check_ban_times(std::chrono::system_clock::now());
}
public:
@ -160,9 +165,16 @@ public:
* @returns false if an invalid time modifier is encountered.
* *time is undefined in that case.
*/
bool parse_time(const std::string& duration, std::time_t* time) const;
std::pair<bool, utils::optional<std::chrono::system_clock::time_point>> parse_time(
const std::string& duration, std::chrono::system_clock::time_point start_time) const;
std::string ban(const std::string& ip,
const utils::optional<std::chrono::system_clock::time_point>& end_time,
const std::string& reason,
const std::string& who_banned,
const std::string& group,
const std::string& nick = "");
std::string ban(const std::string&, const std::time_t&, const std::string&, const std::string&, const std::string&, const std::string& = "");
void unban(std::ostringstream& os, const std::string& ip, bool immediate_write=true);
void unban_group(std::ostringstream& os, const std::string& group);

View file

@ -18,6 +18,7 @@
#include "filesystem.hpp"
#include "lexical_cast.hpp"
#include "log.hpp"
#include "serialization/chrono.hpp"
#include "server/wesnothd/player_network.hpp"
#include "server/wesnothd/server.hpp"
@ -1975,7 +1976,7 @@ void game::send_server_message(const char* message, utils::optional<player_itera
msg.set_attr("id", "server");
msg.set_attr_dup("message", message);
std::stringstream ss;
ss << ::std::time(nullptr);
ss << chrono::serialize_timestamp(std::chrono::system_clock::now());
msg.set_attr_dup("time", ss.str().c_str());
} else {
simple_wml::node& msg = doc.root().add_child("message");

View file

@ -20,6 +20,8 @@
#include "server/wesnothd/metrics.hpp"
#include "serialization/chrono.hpp"
#include <algorithm>
#include <ostream>
@ -42,7 +44,7 @@ metrics::metrics()
, current_requests_(0)
, nrequests_(0)
, nrequests_waited_(0)
, started_at_(std::time(nullptr))
, started_at_(std::chrono::steady_clock::now())
, terminations_()
{
}
@ -74,7 +76,9 @@ void metrics::no_requests()
current_requests_ = 0;
}
void metrics::record_sample(const simple_wml::string_span& name, clock_t parsing_time, clock_t processing_time)
void metrics::record_sample(const simple_wml::string_span& name,
const std::chrono::steady_clock::duration& parsing_time,
const std::chrono::steady_clock::duration& processing_time)
{
auto isample = std::lower_bound(samples_.begin(), samples_.end(), name,compare_samples_to_stringspan());
if(isample == samples_.end() || isample->name != name) {
@ -128,34 +132,32 @@ std::ostream& metrics::requests(std::ostream& out) const
out << "\nSampled request types:\n";
std::size_t n = 0;
std::size_t pa = 0;
std::size_t pr = 0;
std::chrono::steady_clock::duration pa{0};
std::chrono::steady_clock::duration pr{0};
for(const auto& s : ordered_samples) {
out << "'" << s.name << "' called " << s.nsamples << " times "
<< s.parsing_time << "("<< s.max_parsing_time <<") parsing time, "
<< s.processing_time << "("<<s.max_processing_time<<") processing time\n";
<< s.parsing_time.count() << "(" << s.max_parsing_time.count() << ") parsing time, "
<< s.processing_time.count() << "(" << s.max_processing_time.count() << ") processing time\n";
n += s.nsamples;
pa += s.parsing_time;
pr += s.processing_time;
}
out << "Total number of request samples = " << n << "\n"
<< "Total parsing time = " << pa << "\n"
<< "Total processing time = " << pr;
<< "Total parsing time = " << pa.count() << "\n"
<< "Total processing time = " << pr.count();
return out;
}
std::ostream& operator<<(std::ostream& out, metrics& met)
{
const std::time_t time_up = std::time(nullptr) - met.started_at_;
const int seconds = time_up%60;
const int minutes = (time_up/60)%60;
const int hours = (time_up/(60*60))%24;
const int days = time_up/(60*60*24);
const auto time_up = std::chrono::steady_clock::now() - met.started_at_;
auto [days, hours, minutes, seconds] = chrono::deconstruct_duration(time_up);
const int requests_immediate = met.nrequests_ - met.nrequests_waited_;
const int percent_immediate = (requests_immediate*100)/(met.nrequests_ > 0 ? met.nrequests_ : 1);
out << "METRICS\nUp " << days << " days, " << hours << " hours, "
<< minutes << " minutes, " << seconds << " seconds\n"
out << "METRICS\nUp " << days.count() << " days, " << hours.count() << " hours, "
<< minutes.count() << " minutes, " << seconds.count() << " seconds\n"
<< met.nrequests_ << " requests serviced. " << requests_immediate
<< " (" << percent_immediate << "%) "
<< "requests were serviced immediately.\n"

View file

@ -17,7 +17,7 @@
#include "server/common/simple_wml.hpp"
#include <ctime>
#include <chrono>
#include <iosfwd>
#include <map>
#include <string>
@ -31,7 +31,10 @@ public:
void service_request();
void no_requests();
void record_sample(const simple_wml::string_span& name, clock_t parsing_time, clock_t processing_time);
/** @todo: Currently unused. Use for something? */
void record_sample(const simple_wml::string_span& name,
const std::chrono::steady_clock::duration& parsing_time,
const std::chrono::steady_clock::duration& processing_time);
void game_terminated(const std::string& reason);
@ -41,20 +44,12 @@ public:
struct sample
{
sample()
: name()
, nsamples(0)
, parsing_time(0)
, processing_time(0)
, max_parsing_time(0)
, max_processing_time(0)
{
}
simple_wml::string_span name;
int nsamples;
clock_t parsing_time, processing_time;
clock_t max_parsing_time, max_processing_time;
simple_wml::string_span name{};
int nsamples = 0;
std::chrono::steady_clock::duration parsing_time{0};
std::chrono::steady_clock::duration processing_time{0};
std::chrono::steady_clock::duration max_parsing_time{0};
std::chrono::steady_clock::duration max_processing_time{0};
operator const simple_wml::string_span&()
{
@ -69,7 +64,7 @@ private:
int current_requests_;
int nrequests_;
int nrequests_waited_;
const std::time_t started_at_;
const std::chrono::steady_clock::time_point started_at_;
std::map<std::string, int> terminations_;
};

View file

@ -17,14 +17,14 @@
wesnothd::player::player(const std::string& n, simple_wml::node& cfg, long id,
bool registered, const std::string& version, const std::string& source, unsigned long long login_id, const std::size_t max_messages,
const std::size_t time_period,
const std::chrono::seconds& time_period,
const bool moderator)
: name_(n)
, version_(version)
, source_(source)
, cfg_(cfg)
, registered_(registered)
, flood_start_(0)
, flood_start_()
, messages_since_flood_start_(0)
, MaxMessages(max_messages)
, TimePeriod(time_period)
@ -80,8 +80,8 @@ void wesnothd::player::mark_registered(bool registered)
bool wesnothd::player::is_message_flooding()
{
const std::time_t now = std::time(nullptr);
if (flood_start_ == 0) {
const auto now = std::chrono::system_clock::now();
if (flood_start_ == std::chrono::system_clock::time_point{}) {
flood_start_ = now;
return false;
}

View file

@ -17,7 +17,7 @@
#include "server/common/simple_wml.hpp"
#include <ctime>
#include <chrono>
#include <set>
namespace wesnothd {
@ -32,7 +32,7 @@ public:
};
player(const std::string& n, simple_wml::node& cfg, long id, bool registered, const std::string& version, const std::string& source,
unsigned long long login_id, const std::size_t max_messages=4, const std::size_t time_period=10,
unsigned long long login_id, const std::size_t max_messages=4, const std::chrono::seconds& time_period=std::chrono::seconds{10},
const bool moderator=false);
void set_status(STATUS status);
@ -65,10 +65,10 @@ private:
bool registered_;
std::time_t flood_start_;
std::chrono::system_clock::time_point flood_start_;
unsigned int messages_since_flood_start_;
const std::size_t MaxMessages;
const std::time_t TimePeriod;
const std::chrono::seconds TimePeriod;
STATUS status_;
bool moderator_;
unsigned long long login_id_;

View file

@ -70,7 +70,7 @@ public:
void enter_lobby();
const std::chrono::time_point<std::chrono::steady_clock> login_time;
const std::chrono::steady_clock::time_point login_time;
private:
const any_socket_ptr socket_;

View file

@ -24,6 +24,7 @@
#include "filesystem.hpp"
#include "log.hpp"
#include "multiplayer_error_codes.hpp"
#include "serialization/chrono.hpp"
#include "serialization/parser.hpp"
#include "serialization/preprocessor.hpp"
#include "serialization/string_utils.hpp"
@ -75,6 +76,8 @@ static lg::log_domain log_config("config");
#define ERR_CONFIG LOG_STREAM(err, log_config)
#define WRN_CONFIG LOG_STREAM(warn, log_config)
using namespace std::chrono_literals;
namespace wesnothd
{
// we take profiling info on every n requests
@ -187,7 +190,7 @@ static std::string player_status(const wesnothd::player_record& player)
{
std::ostringstream out;
out << "'" << player.name() << "' @ " << player.client_ip()
<< " logged on for " << lg::get_timespan(std::chrono::duration_cast<std::chrono::seconds>(std::chrono::steady_clock::now() - player.login_time).count());
<< " logged on for " << lg::format_timespan(std::chrono::duration_cast<std::chrono::seconds>(std::chrono::steady_clock::now() - player.login_time));
return out.str();
}
@ -232,8 +235,7 @@ server::server(int port,
, default_time_period_(0)
, concurrent_connections_(0)
, graceful_restart(false)
, lan_server_(std::time(nullptr))
, last_user_seen_time_(std::time(nullptr))
, lan_server_(0)
, restart_command()
, max_ip_log_size_(0)
, deny_unregistered_login_(false)
@ -289,14 +291,14 @@ void server::handle_graceful_timeout(const boost::system::error_code& error)
process_command("msg All games ended. Shutting down now. Reconnect to the new server instance.", "system");
BOOST_THROW_EXCEPTION(server_shutdown("graceful shutdown timeout"));
} else {
timer_.expires_from_now(std::chrono::seconds(1));
timer_.expires_from_now(1s);
timer_.async_wait(std::bind(&server::handle_graceful_timeout, this, std::placeholders::_1));
}
}
void server::start_lan_server_timer()
{
lan_server_timer_.expires_from_now(std::chrono::seconds(lan_server_));
lan_server_timer_.expires_from_now(lan_server_);
lan_server_timer_.async_wait([this](const boost::system::error_code& ec) { handle_lan_server_shutdown(ec); });
}
@ -452,7 +454,7 @@ void server::load_config()
information_ = cfg_["information"].str();
announcements_ = cfg_["announcements"].str();
server_id_ = cfg_["id"].str();
lan_server_ = cfg_["lan_server"].to_time_t(0);
lan_server_ = chrono::parse_duration(cfg_["lan_server"], 0s);
deny_unregistered_login_ = cfg_["deny_unregistered_login"].to_bool();
@ -479,12 +481,12 @@ void server::load_config()
}
default_max_messages_ = cfg_["max_messages"].to_int(4);
default_time_period_ = cfg_["messages_time_period"].to_int(10);
default_time_period_ = chrono::parse_duration(cfg_["messages_time_period"], 10s);
concurrent_connections_ = cfg_["connections_allowed"].to_int(5);
max_ip_log_size_ = cfg_["max_ip_log_size"].to_int(500);
failed_login_limit_ = cfg_["failed_logins_limit"].to_int(10);
failed_login_ban_ = cfg_["failed_logins_ban"].to_int(3600);
failed_login_ban_ = chrono::parse_duration(cfg_["failed_logins_ban"], 3600s);
failed_login_buffer_size_ = cfg_["failed_logins_buffer_size"].to_int(500);
// Example config line:
@ -587,7 +589,7 @@ std::string server::is_ip_banned(const std::string& ip)
void server::start_dump_stats()
{
dump_stats_timer_.expires_after(std::chrono::minutes(5));
dump_stats_timer_.expires_after(5min);
dump_stats_timer_.async_wait([this](const boost::system::error_code& ec) { dump_stats(ec); });
}
@ -648,7 +650,7 @@ void server::dummy_player_updates(const boost::system::error_code& ec)
void server::start_tournaments_timer()
{
tournaments_timer_.expires_after(std::chrono::minutes(60));
tournaments_timer_.expires_after(60min);
tournaments_timer_.async_wait([this](const boost::system::error_code& ec) { refresh_tournaments(ec); });
}
@ -805,7 +807,7 @@ void server::login_client(boost::asio::yield_context yield, SocketPtr socket)
// Log the IP
if(!user_handler_) {
connection_log ip_name { username, client_address(socket), 0 };
connection_log ip_name { username, client_address(socket), {} };
if(std::find(ip_log_.begin(), ip_log_.end(), ip_name) == ip_log_.end()) {
ip_log_.push_back(ip_name);
@ -878,7 +880,7 @@ template<class SocketPtr> bool server::is_login_allowed(boost::asio::yield_conte
std::string ban_type_desc;
std::string ban_reason;
const char* msg_numeric;
std::string ban_duration = std::to_string(auth_ban.duration);
std::string ban_duration = std::to_string(auth_ban.duration.count());
switch(auth_ban.type) {
case user_handler::BAN_USER:
@ -907,7 +909,7 @@ template<class SocketPtr> bool server::is_login_allowed(boost::asio::yield_conte
if(!is_moderator) {
LOG_SERVER << log_address(socket) << "\t" << username << "\tis banned by user_handler (" << ban_type_desc
<< ")";
if(auth_ban.duration) {
if(auth_ban.duration > 0s) {
// Temporary ban
async_send_error(socket, "You are banned from this server: " + ban_reason, msg_numeric, {{"duration", ban_duration}});
} else {
@ -998,7 +1000,7 @@ template<class SocketPtr> bool server::authenticate(
}
// This name is registered and an incorrect password provided
else if(!(user_handler_->login(username, hashed_password))) {
const std::time_t now = std::time(nullptr);
const auto now = std::chrono::system_clock::now();
login_log login_ip { client_address(socket), 0, now };
auto i = std::find(failed_logins_.begin(), failed_logins_.end(), login_ip);
@ -1068,7 +1070,7 @@ template<class SocketPtr> void server::send_password_request(SocketPtr socket,
template<class SocketPtr> void server::handle_player(boost::asio::yield_context yield, SocketPtr socket, player_iterator player)
{
if(lan_server_)
if(lan_server_ > 0s)
abort_lan_server_timer();
BOOST_SCOPE_EXIT_ALL(this, &player) {
@ -1984,17 +1986,17 @@ void server::remove_player(player_iterator iter)
if(user_handler_) {
user_handler_->db_update_logout(iter->info().get_login_id());
} else {
connection_log ip_name { iter->info().name(), ip, 0 };
connection_log ip_name { iter->info().name(), ip, {} };
auto i = std::find(ip_log_.begin(), ip_log_.end(), ip_name);
if(i != ip_log_.end()) {
i->log_off = std::time(nullptr);
i->log_off = std::chrono::system_clock::now();
}
}
player_connections_.erase(iter);
if(lan_server_ && player_connections_.size() == 0)
if(lan_server_ > 0s && player_connections_.size() == 0)
start_lan_server_timer();
if(game_ended) delete_game(g->id());
@ -2118,7 +2120,7 @@ void server::shut_down_handler(
acceptor_v6_.close();
acceptor_v4_.close();
timer_.expires_from_now(std::chrono::seconds(10));
timer_.expires_from_now(10s);
timer_.async_wait(std::bind(&server::handle_graceful_timeout, this, std::placeholders::_1));
process_command(
@ -2149,7 +2151,7 @@ void server::restart_handler(const std::string& issuer_name,
graceful_restart = true;
acceptor_v6_.close();
acceptor_v4_.close();
timer_.expires_from_now(std::chrono::seconds(10));
timer_.expires_from_now(10s);
timer_.async_wait(std::bind(&server::handle_graceful_timeout, this, std::placeholders::_1));
start_new_server();
@ -2552,9 +2554,9 @@ void server::ban_handler(
auto second_space = std::find(first_space + 1, parameters.end(), ' ');
const std::string target(parameters.begin(), first_space);
const std::string duration(first_space + 1, second_space);
std::time_t parsed_time = std::time(nullptr);
auto [success, parsed_time] = ban_manager_.parse_time(duration, std::chrono::system_clock::now());
if(ban_manager_.parse_time(duration, &parsed_time) == false) {
if(!success) {
*out << "Failed to parse the ban duration: '" << duration << "'\n" << ban_manager_.get_ban_help();
return;
}
@ -2614,9 +2616,9 @@ void server::kickban_handler(
auto second_space = std::find(first_space + 1, parameters.end(), ' ');
const std::string target(parameters.begin(), first_space);
const std::string duration(first_space + 1, second_space);
std::time_t parsed_time = std::time(nullptr);
auto [success, parsed_time] = ban_manager_.parse_time(duration, std::chrono::system_clock::now());
if(ban_manager_.parse_time(duration, &parsed_time) == false) {
if(!success) {
*out << "Failed to parse the ban duration: '" << duration << "'\n" << ban_manager_.get_ban_help();
return;
}
@ -2695,9 +2697,9 @@ void server::gban_handler(
second_space = std::find(first_space + 1, parameters.end(), ' ');
const std::string duration(first_space + 1, second_space);
std::time_t parsed_time = std::time(nullptr);
auto [success, parsed_time] = ban_manager_.parse_time(duration, std::chrono::system_clock::now());
if(ban_manager_.parse_time(duration, &parsed_time) == false) {
if(!success) {
*out << "Failed to parse the ban duration: '" << duration << "'\n" << ban_manager_.get_ban_help();
return;
}
@ -2877,7 +2879,7 @@ void server::searchlog_handler(const std::string& /*issuer_name*/,
*out << std::endl << player_status(*player);
} else {
*out << "\n'" << username << "' @ " << ip
<< " last seen: " << lg::get_timestamp(i.log_off, "%H:%M:%S %d.%m.%Y");
<< " last seen: " << chrono::format_local_timestamp(i.log_off, "%H:%M:%S %d.%m.%Y");
}
}
}

View file

@ -27,6 +27,7 @@
#include <boost/asio/steady_timer.hpp>
#include <chrono>
#include <random>
namespace wesnothd
@ -95,7 +96,7 @@ private:
struct connection_log
{
std::string nick, ip;
std::time_t log_off;
std::chrono::system_clock::time_point log_off;
bool operator==(const connection_log& c) const
{
@ -110,7 +111,7 @@ private:
{
std::string ip;
int attempts;
std::time_t first_attempt;
std::chrono::system_clock::time_point first_attempt;
bool operator==(const login_log& l) const
{
@ -151,11 +152,10 @@ private:
std::string tournaments_;
std::string information_;
std::size_t default_max_messages_;
std::size_t default_time_period_;
std::chrono::seconds default_time_period_;
std::size_t concurrent_connections_;
bool graceful_restart;
std::time_t lan_server_;
std::time_t last_user_seen_time_;
std::chrono::seconds lan_server_;
std::string restart_command;
std::size_t max_ip_log_size_;
bool deny_unregistered_login_;
@ -165,7 +165,7 @@ private:
std::set<std::string> client_sources_;
std::vector<std::string> tor_ip_list_;
int failed_login_limit_;
std::time_t failed_login_ban_;
std::chrono::seconds failed_login_ban_;
std::deque<login_log>::size_type failed_login_buffer_size_;
/** Parse the server config into local variables. */