Compare commits

...

67 Commits

Author SHA1 Message Date
sla-ppy 5afc07b0b3 add RateLimiter class 2024-06-20 16:39:03 +02:00
sla-ppy bc5e407d60 refactor dos protection 2024-06-20 07:28:55 +02:00
sla-ppy df308c1dc5 Merge branch 'simple-dos-protected' of github.com:Igor20264/BeamMP-Server into simple-dos-protected 2024-06-18 16:49:04 +02:00
Lion a998a7c091 Reuse HTTP connections (#339)
fix #223
2024-06-16 03:10:29 +02:00
SaltySnail 277036fc52 fix not following naming convention 2024-06-16 03:00:18 +02:00
SaltySnail e776848a76 Update src/Http.cpp
Co-authored-by: Lion <development@kortlepel.com>
2024-06-16 02:53:01 +02:00
SaltySnail 63fa65e9a7 Update src/Http.cpp
Co-authored-by: Lion <development@kortlepel.com>
2024-06-16 02:52:55 +02:00
SaltySnail c07baeed1a add reusing Http connections 2024-06-16 02:45:53 +02:00
Lion 33b5384398 Add config setting to allow/deny guests (#335)
fix #247
2024-06-11 09:01:49 +02:00
SaltySnail e94cfd641d remove debug print 2024-06-10 23:16:45 +02:00
SaltySnail 6e590ff18a fix naming of the ENV 2024-06-10 23:16:16 +02:00
SaltySnail 91bc7dea79 Update src/TNetwork.cpp
Co-authored-by: Lion <development@kortlepel.com>
2024-06-10 23:12:23 +02:00
Lion 8b94b1f0ef Add an entry in serverconfig.toml for the time between update reminders (#329)
fix #321
2024-06-10 22:59:09 +02:00
SaltySnail 5dab48af92 fix #247, add allow guests config setting. 2024-06-10 22:06:09 +02:00
SaltySnail f3060f5247 change default from 3h to 30s 2024-06-08 20:24:46 +02:00
SaltySnail c61816dfeb fix 0....2h being allowed as time format 2024-06-01 23:42:44 +02:00
Lion 2fcb53530a Add more info to return to backend's /pkToUser endpoint (#332)
Features added were tested through a custom client (since @sla-ppy is
too cheap to own the game) with @lionkor while pair programming.

Fixes: #303
2024-06-01 12:25:01 +02:00
sla-ppy cee039d922 Merge branch 'BeamMP:minor' into fix_303 2024-05-30 19:15:06 +02:00
sla-ppy 566f0b55f7 remove debug info 2024-05-28 14:53:19 +02:00
sla-ppy 58a7e39419 add more info to return to pkToUser endpoint 2024-05-28 14:15:41 +02:00
SaltySnail bf7f1ef1a5 change update reminder frequency description to include an example of using a float 2024-05-25 21:59:34 +02:00
SaltySnail e35bf4fe15 change TimeFromStringWithLiteral to work with floats 2024-05-25 21:43:54 +02:00
SaltySnail 419a951c29 change wrong version number
lol

Co-authored-by: Lion <development@kortlepel.com>
2024-05-25 20:45:53 +02:00
SaltySnail 135008a73c Change time_str to be a reference
Co-authored-by: Lion <development@kortlepel.com>
2024-05-25 20:44:51 +02:00
SaltySnail 29c3fed374 fix #321 2024-05-25 20:34:33 +02:00
Lion 93192fd9b5 Fix #326 Arch Compile Issue "copy_n is not a member of 'std';" (#327)
added ```#include <algorithm>``` to Common.h, needed for "copy_n"
2024-05-24 23:01:48 +02:00
redracer eea041e8eb Fix #326 Arch Compile Issue "copy_n is not a member of 'std';"
added #include <algorithm> to Common.h, needed for "copy_n"
2024-05-24 16:10:37 -04:00
Lion a3670bff4a Add platform, lua, openssl version to version command, show lua version on startup (#325)
Closes #300
2024-05-24 12:58:30 +02:00
sla-ppy 1b60e89f26 add lua info on LuaEngine startup 2024-05-24 12:31:01 +02:00
sla-ppy 06e5805428 change version cmd behaviour 2024-05-24 12:12:20 +02:00
Lion fd2f713485 bump version 2024-05-21 08:52:05 +02:00
Lion c880460a55 Fix macos compile Issue (#324)
Related [Issue 206](https://github.com/BeamMP/BeamMP-Server/issues/206)
2024-05-19 12:57:49 +02:00
Maximilian Rehms 7f54bcfaec successful implemented suggested change to "lua_nil"
Co-authored-by: Lion <development@kortlepel.com>
2024-05-18 22:36:57 +02:00
Maximilian Rehms 785c5343cd removed warnig suppression 2024-05-18 15:11:42 +02:00
Maximilian Rehms 40e5496819 compiles now on macos 2024-05-18 15:06:26 +02:00
Lion 5f9726f10f Use hard disconnect instead of ClientKick in timeout (#320) 2024-05-11 13:00:25 +02:00
Lion Kortlepel fcd408970b always initialize ping timer 2024-05-11 12:44:41 +02:00
Lion cf5ebcbd1a Fix lua number (int vs double) handling, add lua unit tests for json encode + decode, fix empty array or table serializing to null (#319) 2024-05-11 12:19:13 +02:00
Lion c9d926f9e3 Fix Lua assert error when adding values to tables (e.g. in event arguments) (#318)
When adding elements to a table, the .add() function does not behave as
expected in various cases, making it really shit and difficult to use.
Instead, we keep our own index and just add one by one. It works, and
it's easy to understand.

This may lead to indices which are nil, i.e. non-fully-sequential
tables, but I can't be asked to worry about it because that shouldn't be
an issue when we use .set() everywhere.
2024-05-11 12:18:56 +02:00
Lion Kortlepel 9f47978f0f use hard disconnect insteadof clientkick 2024-05-11 12:17:02 +02:00
Lion Kortlepel a0f649288e fix lua number handling, add lua unit tests for json encode + decode 2024-05-10 15:54:52 +02:00
Lion Kortlepel b995a222ff bump version 2024-05-10 14:57:22 +02:00
Lion Kortlepel c5dff8b913 fix lua assertion on event argument passing
When adding elements to a table, the .add() function does not behave as
expected in various cases, making it really shit and difficult to use.
Instead, we keep our own index and just add one by one. It works, and
it's easy to understand.

This may lead to indices which are nil, i.e. non-fully-sequential
tables, but I can't be asked to worry about it because that shouldn't be
an issue when we use .set() everywhere.
2024-05-10 14:45:06 +02:00
Lion 0c740ccedf bump version 2024-05-08 12:01:33 +02:00
Lion b0cf5c7838 Fix lua assertions crashing the server (#198)
The server would crash on a Lua plugin ASSERT() - this now instead
prints an error. May still lead to weird behavior, but less weird than a
crash.
2024-05-08 11:48:56 +02:00
Lion 586510041d fix TriggerGlobalEvent not passing event arguments correctly (#307)
This is supposed to close #106 (The code is by @lionkor from
https://github.com/BeamMP/BeamMP-Server/commit/b068a9b48f21cc80c6aa2ae3580fc9a5e3d411e0)
2024-05-08 11:47:23 +02:00
Lion 1794c3fe45 Add Lua execution profiler Util.DebugExecutionTime() (#267)
Adds `Util.DebugExecutionTime()`, which returns a table of
`function_name: milliseconds`, in which each function's execution time
is averaged over all time.

You can call this function like so:
```lua
-- event to print the debug times
MP.RegisterEvent("printStuff", "printStuff")
-- prints the execution time of all event handlers
function printStuff()
	print(Util.DebugExecutionTime())
end
-- run every 5 seconds (or 10, or 60, whatever makes sense for you
MP.CreateEventTimer("printStuff", 5000)
```

Pretty print function:
```lua
function printDebugExecutionTime()
    local stats = Util.DebugExecutionTime()
    local pretty = "DebugExecutionTime:\n"
    local longest = 0
    for name, t in pairs(stats) do
        if #name > longest then
            longest = #name
        end
    end
    for name, t in pairs(stats) do
        pretty = pretty .. string.format("%" .. longest + 1 .. "s: %12f +/- %12f (min: %12f, max: %12f) (called %d time(s))\n", name, t.mean, t.stdev, t.min, t.max, t.n)
    end
    print(pretty)
end
```

`Util.DebugExecutionTime()` returns a table, where each key is an event
handler function name, and each value is a table consisting of `mean`
(simple average), `stddev` (standard deviation aka mean of the
variance), `min` and `max`, all in milliseconds, as well as `n` as the
number of samples taken.
2024-05-08 11:46:02 +02:00
Lion ee599e970d Make Plugin load order deterministic (alphabetic) (#315)
Add Feature / Fix: #306
2024-05-08 11:31:34 +02:00
Lion Kortlepel 40158dc252 fix compile error 2024-05-08 11:31:12 +02:00
Neptnium 5ece60574a Make Plugin load order deterministic
fix typo "mResourceServerPath" named "mFolder"

Fix typo number 2
2024-05-08 11:31:05 +02:00
Neptnium c605498a2d Merge branch 'BeamMP:minor' into minor 2024-05-07 08:26:44 +02:00
Igor f26b091042 Merge branch 'minor' into simple-dos-protected 2024-05-06 22:21:10 +03:00
Lion 4d7967d5d9 bump version (#314) 2024-05-06 14:37:50 +02:00
Neptnium d81087b5af fix TriggerGlobalEvent not passing event arguments correctly (closes #106) 2024-04-18 18:43:04 +02:00
Igor a008015d78 add a simple DOS protection system 2024-03-29 10:56:05 +03:00
Lion Kortlepel 7e1f86478d make mutex mutable 2024-03-07 01:03:16 +01:00
Lion Kortlepel c85e026c2d cleanup 2024-03-07 00:45:00 +01:00
Lion Kortlepel e4826e8bf1 remove test code 2024-03-07 00:42:36 +01:00
Lion Kortlepel 590b159f14 switch to a runnning calculation for stdev and mean 2024-03-07 00:25:47 +01:00
Lion Kortlepel 40533c04bc add total calls to stats 2024-03-06 10:27:22 +01:00
Lion Kortlepel 946c1362e1 add custom profiling via Debug(Start|Stop)Profile 2024-03-06 10:27:22 +01:00
Lion Kortlepel 4347cb4af2 add min, max, stddev to stats 2024-03-06 10:27:22 +01:00
Lion Kortlepel 88721d4f7f add boost circular buffer to dependencies 2024-03-06 10:27:22 +01:00
Lion Kortlepel 7deea900fb add Util.DebugExecutionTime 2024-03-06 10:27:22 +01:00
Lion Kortlepel cbc1483537 add profiling code 2024-03-06 10:27:22 +01:00
Lion 39badf432d remove whitespace 2024-01-17 13:15:30 +01:00
Lion Kortlepel 03307e71fb use a beammp_lua_errorf instead of a std::terminate on sol2 assertion failure 2023-12-05 18:25:27 +01:00
22 changed files with 618 additions and 86 deletions
+11 -1
View File
@@ -35,6 +35,7 @@ set(PRJ_HEADERS
include/Json.h
include/LuaAPI.h
include/RWMutex.h
include/RateLimiter.h
include/SignalHandling.h
include/TConfig.h
include/TConsole.h
@@ -49,6 +50,8 @@ set(PRJ_HEADERS
include/TServer.h
include/VehicleData.h
include/Env.h
include/Profiling.h
include/ChronoWrapper.h
)
# add all source files (.cpp) to this, except the one with main()
set(PRJ_SOURCES
@@ -70,8 +73,11 @@ set(PRJ_SOURCES
src/TResourceManager.cpp
src/TScopedTimer.cpp
src/TServer.cpp
src/RateLimiter.cpp
src/VehicleData.cpp
src/Env.cpp
src/Profiling.cpp
src/ChronoWrapper.cpp
)
find_package(Lua REQUIRED)
@@ -171,6 +177,11 @@ add_library(commandline_static
deps/commandline/src/backends/BufferedBackend.cpp
deps/commandline/src/backends/BufferedBackend.h
)
# Ensure the commandline library uses C++11
set_target_properties(commandline_static PROPERTIES CXX_STANDARD 11 CXX_STANDARD_REQUIRED YES)
if (WIN32)
target_compile_definitions(commandline_static PRIVATE -DPLATFORM_WINDOWS=1)
else ()
@@ -214,4 +225,3 @@ if(${PROJECT_NAME}_ENABLE_UNIT_TESTING)
target_link_options(${PROJECT_NAME}-tests PRIVATE "/SUBSYSTEM:CONSOLE")
endif(MSVC)
endif()
+8
View File
@@ -0,0 +1,8 @@
#pragma once
#include <chrono>
#include <string>
namespace ChronoWrapper {
std::chrono::high_resolution_clock::duration TimeFromStringWithLiteral(const std::string& time_str);
}
+1 -1
View File
@@ -128,7 +128,7 @@ private:
std::string mRole;
std::string mDID;
int mID = -1;
std::chrono::time_point<std::chrono::high_resolution_clock> mLastPingTime;
std::chrono::time_point<std::chrono::high_resolution_clock> mLastPingTime = std::chrono::high_resolution_clock::now();
};
std::optional<std::weak_ptr<TClient>> GetClient(class TServer& Server, int ID);
+5 -2
View File
@@ -18,6 +18,7 @@
#pragma once
#include <algorithm>
#include <array>
#include <atomic>
#include <cstring>
@@ -65,7 +66,7 @@ public:
std::string Resource { "Resources" };
std::string MapName { "/levels/gridmap_v2/info.json" };
std::string Key {};
std::string Password{};
std::string Password {};
std::string SSLKeyPath { "./.ssl/HttpServer/key.pem" };
std::string SSLCertPath { "./.ssl/HttpServer/cert.pem" };
bool HTTPServerEnabled { false };
@@ -76,12 +77,14 @@ public:
int Port { 30814 };
std::string CustomIP {};
bool LogChat { true };
bool AllowGuests { true };
bool SendErrors { true };
bool SendErrorsMessageEnabled { true };
int HTTPServerPort { 8080 };
std::string HTTPServerIP { "127.0.0.1" };
bool HTTPServerUseSSL { false };
bool HideUpdateMessages { false };
std::string UpdateReminderTime { "30s" };
[[nodiscard]] bool HasCustomIP() const { return !CustomIP.empty(); }
};
@@ -150,7 +153,7 @@ private:
static inline std::mutex mShutdownHandlersMutex {};
static inline std::deque<TShutdownHandler> mShutdownHandlers {};
static inline Version mVersion { 3, 3, 0 };
static inline Version mVersion { 3, 5, 0 };
};
void SplitString(std::string const& str, const char delim, std::vector<std::string>& out);
+72
View File
@@ -0,0 +1,72 @@
#pragma once
#include <boost/thread/synchronized_value.hpp>
#include <chrono>
#include <cstddef>
#include <limits>
#include <unordered_map>
namespace prof {
using Duration = std::chrono::duration<double, std::milli>;
using TimePoint = std::chrono::high_resolution_clock::time_point;
/// Returns the current time.
TimePoint now();
/// Returns a sub-millisecond resolution duration between start and end.
Duration duration(const TimePoint& start, const TimePoint& end);
struct Stats {
double mean;
double stdev;
double min;
double max;
size_t n;
};
/// Calculates and stores the moving average over K samples of execution time data
/// for some single unit of code. Threadsafe.
struct UnitExecutionTime {
UnitExecutionTime();
/// Adds a sample to the collection, overriding the oldest sample if needed.
void add_sample(const Duration& dur);
/// Calculates the mean duration over the `measurement_count()` measurements,
/// as well as the standard deviation.
Stats stats() const;
/// Returns the number of elements the moving average is calculated over.
size_t measurement_count() const;
private:
mutable std::mutex m_mtx {};
size_t m_total_calls {};
double m_sum {};
// sum of measurements squared (for running stdev)
double m_measurement_sqr_sum {};
double m_min { std::numeric_limits<double>::max() };
double m_max { std::numeric_limits<double>::min() };
};
/// Holds profiles for multiple units by name. Threadsafe.
struct UnitProfileCollection {
/// Adds a sample to the collection, overriding the oldest sample if needed.
void add_sample(const std::string& unit, const Duration& duration);
/// Calculates the mean duration over the `measurement_count()` measurements,
/// as well as the standard deviation.
Stats stats(const std::string& unit);
/// Returns the number of elements the moving average is calculated over.
size_t measurement_count(const std::string& unit);
/// Returns the stats for all stored units.
std::unordered_map<std::string, Stats> all_stats();
private:
boost::synchronized_value<std::unordered_map<std::string, UnitExecutionTime>> m_map;
};
}
+20
View File
@@ -0,0 +1,20 @@
#include "Common.h"
#include <chrono>
#include <fstream>
#include <iostream>
#include <mutex>
#include <unordered_map>
#include <unordered_set>
class RateLimiter {
public:
RateLimiter();
bool isConnectionAllowed(const std::string& client_address);
private:
std::unordered_map<std::string, std::vector<std::chrono::time_point<std::chrono::high_resolution_clock>>> m_connection;
std::mutex m_connection_mutex;
void blockIP(const std::string& client_address);
bool isIPBlocked(const std::string& client_address);
};
+34 -12
View File
@@ -18,9 +18,11 @@
#pragma once
#include "Profiling.h"
#include "TNetwork.h"
#include "TServer.h"
#include <any>
#include <chrono>
#include <condition_variable>
#include <filesystem>
#include <initializer_list>
@@ -28,6 +30,7 @@
#include <lua.hpp>
#include <memory>
#include <mutex>
#include <nlohmann/json.hpp>
#include <queue>
#include <random>
#include <set>
@@ -36,19 +39,34 @@
#include <vector>
#define SOL_ALL_SAFETIES_ON 1
#define SOL_USER_C_ASSERT SOL_ON
#define SOL_C_ASSERT(...) \
beammp_lua_errorf("SOL2 assertion failure: Assertion `{}` failed in {}:{}. This *should* be a fatal error, but BeamMP Server overrides it to not be fatal. This may cause the Lua Engine to crash, or cause other issues.", #__VA_ARGS__, __FILE__, __LINE__)
#include <sol/sol.hpp>
struct JsonString {
std::string value;
};
// value used to keep nils in a table or array, across serialization boundaries like
// JsonEncode, so that the nil stays at the same index and isn't treated like a special
// value (e.g. one that can be ignored or discarded).
const inline std::string BEAMMP_INTERNAL_NIL = "BEAMMP_SERVER_INTERNAL_NIL_VALUE";
using TLuaStateId = std::string;
namespace fs = std::filesystem;
/**
* std::variant means, that TLuaArgTypes may be one of the Types listed as template args
*/
using TLuaArgTypes = std::variant<std::string, int, sol::variadic_args, bool, std::unordered_map<std::string, std::string>>;
static constexpr size_t TLuaArgTypes_String = 0;
static constexpr size_t TLuaArgTypes_Int = 1;
static constexpr size_t TLuaArgTypes_VariadicArgs = 2;
static constexpr size_t TLuaArgTypes_Bool = 3;
static constexpr size_t TLuaArgTypes_StringStringMap = 4;
using TLuaValue = std::variant<std::string, int, JsonString, bool, std::unordered_map<std::string, std::string>, float>;
enum TLuaType {
String = 0,
Int = 1,
Json = 2,
Bool = 3,
StringStringMap = 4,
Float = 5,
};
class TLuaPlugin;
@@ -96,7 +114,7 @@ public:
struct QueuedFunction {
std::string FunctionName;
std::shared_ptr<TLuaResult> Result;
std::vector<TLuaArgTypes> Args;
std::vector<TLuaValue> Args;
std::string EventName; // optional, may be empty
};
@@ -149,7 +167,7 @@ public:
void ReportErrors(const std::vector<std::shared_ptr<TLuaResult>>& Results);
bool HasState(TLuaStateId StateId);
[[nodiscard]] std::shared_ptr<TLuaResult> EnqueueScript(TLuaStateId StateID, const TLuaChunk& Script);
[[nodiscard]] std::shared_ptr<TLuaResult> EnqueueFunctionCall(TLuaStateId StateID, const std::string& FunctionName, const std::vector<TLuaArgTypes>& Args);
[[nodiscard]] std::shared_ptr<TLuaResult> EnqueueFunctionCall(TLuaStateId StateID, const std::string& FunctionName, const std::vector<TLuaValue>& Args);
void EnsureStateExists(TLuaStateId StateId, const std::string& Name, bool DontCallOnInit = false);
void RegisterEvent(const std::string& EventName, TLuaStateId StateId, const std::string& FunctionName);
/**
@@ -169,7 +187,7 @@ public:
}
std::vector<std::shared_ptr<TLuaResult>> Results;
std::vector<TLuaArgTypes> Arguments { TLuaArgTypes { std::forward<ArgsT>(Args) }... };
std::vector<TLuaValue> Arguments { TLuaValue { std::forward<ArgsT>(Args) }... };
for (const auto& Event : mLuaEvents.at(EventName)) {
for (const auto& Function : Event.second) {
@@ -188,7 +206,7 @@ public:
return {};
}
std::vector<std::shared_ptr<TLuaResult>> Results;
std::vector<TLuaArgTypes> Arguments { TLuaArgTypes { std::forward<ArgsT>(Args) }... };
std::vector<TLuaValue> Arguments { TLuaValue { std::forward<ArgsT>(Args) }... };
const auto Handlers = GetEventHandlersForState(EventName, StateId);
for (const auto& Handler : Handlers) {
Results.push_back(EnqueueFunctionCall(StateId, Handler, Arguments));
@@ -225,8 +243,8 @@ private:
StateThreadData(const StateThreadData&) = delete;
virtual ~StateThreadData() noexcept { beammp_debug("\"" + mStateId + "\" destroyed"); }
[[nodiscard]] std::shared_ptr<TLuaResult> EnqueueScript(const TLuaChunk& Script);
[[nodiscard]] std::shared_ptr<TLuaResult> EnqueueFunctionCall(const std::string& FunctionName, const std::vector<TLuaArgTypes>& Args);
[[nodiscard]] std::shared_ptr<TLuaResult> EnqueueFunctionCallFromCustomEvent(const std::string& FunctionName, const std::vector<TLuaArgTypes>& Args, const std::string& EventName, CallStrategy Strategy);
[[nodiscard]] std::shared_ptr<TLuaResult> EnqueueFunctionCall(const std::string& FunctionName, const std::vector<TLuaValue>& Args);
[[nodiscard]] std::shared_ptr<TLuaResult> EnqueueFunctionCallFromCustomEvent(const std::string& FunctionName, const std::vector<TLuaValue>& Args, const std::string& EventName, CallStrategy Strategy);
void RegisterEvent(const std::string& EventName, const std::string& FunctionName);
void AddPath(const fs::path& Path); // to be added to path and cpath
void operator()() override;
@@ -253,6 +271,9 @@ private:
sol::table Lua_FS_ListFiles(const std::string& Path);
sol::table Lua_FS_ListDirectories(const std::string& Path);
prof::UnitProfileCollection mProfile {};
std::unordered_map<std::string, prof::TimePoint> mProfileStarts;
std::string mName;
TLuaStateId mStateId;
lua_State* mState;
@@ -268,6 +289,7 @@ private:
std::recursive_mutex mPathsMutex;
std::mt19937 mMersenneTwister;
std::uniform_real_distribution<double> mUniformRealDistribution01;
std::vector<sol::object> JsonStringToArray(JsonString Str);
};
struct TimedEvent {
-1
View File
@@ -48,7 +48,6 @@ public:
private:
void UDPServerMain();
void TCPServerMain();
TServer& mServer;
TPPSMonitor& mPPSMonitor;
ip::udp::socket mUDPSock;
+27
View File
@@ -0,0 +1,27 @@
#include "ChronoWrapper.h"
#include "Common.h"
#include <regex>
std::chrono::high_resolution_clock::duration ChronoWrapper::TimeFromStringWithLiteral(const std::string& time_str)
{
// const std::regex time_regex(R"((\d+\.{0,1}\d*)(min|ms|us|ns|[dhs]))"); //i.e one of: "25ns, 6us, 256ms, 2s, 13min, 69h, 356d" will get matched (only available in newer C++ versions)
const std::regex time_regex(R"((\d+\.{0,1}\d*)(min|[dhs]))"); //i.e one of: "2.01s, 13min, 69h, 356.69d" will get matched
std::smatch match;
float time_value;
if (!std::regex_search(time_str, match, time_regex)) return std::chrono::nanoseconds(0);
time_value = stof(match.str(1));
beammp_debugf("Parsed time was: {}{}", time_value, match.str(2));
if (match.str(2) == "d") {
return std::chrono::seconds((uint64_t)(time_value * 86400)); //86400 seconds in a day
}
else if (match.str(2) == "h") {
return std::chrono::seconds((uint64_t)(time_value * 3600)); //3600 seconds in an hour
}
else if (match.str(2) == "min") {
return std::chrono::seconds((uint64_t)(time_value * 60));
}
else if (match.str(2) == "s") {
return std::chrono::seconds((uint64_t)time_value);
}
return std::chrono::nanoseconds(0);
}
+2
View File
@@ -28,6 +28,7 @@
#include <regex>
#include <sstream>
#include <thread>
#include <chrono>
#include "Compat.h"
#include "CustomAssert.h"
@@ -384,3 +385,4 @@ void SplitString(const std::string& str, const char delim, std::vector<std::stri
out.push_back(str.substr(start, end - start));
}
}
+39 -12
View File
@@ -28,15 +28,42 @@
#include <random>
#include <stdexcept>
// TODO: Add sentry error handling back
using json = nlohmann::json;
struct Connection {
std::string host{};
int port{};
Connection() = default;
Connection(std::string host, int port)
: host(host)
, port(port) {};
};
constexpr uint8_t CONNECTION_AMOUNT = 10;
static thread_local uint8_t write_index = 0;
static thread_local std::array<Connection, CONNECTION_AMOUNT> connections;
static thread_local std::array<std::shared_ptr<httplib::SSLClient>, CONNECTION_AMOUNT> clients;
[[nodiscard]] static std::shared_ptr<httplib::SSLClient> getClient(Connection connectionInfo) {
for (uint8_t i = 0; i < CONNECTION_AMOUNT; i++) {
if (connectionInfo.host == connections[i].host
&& connectionInfo.port == connections[i].port) {
beammp_tracef("Old client reconnected, with ip {} and port {}", connectionInfo.host, connectionInfo.port);
return clients[i];
}
}
uint8_t i = write_index;
write_index++;
write_index %= CONNECTION_AMOUNT;
clients[i] = std::make_shared<httplib::SSLClient>(connectionInfo.host, connectionInfo.port);
connections[i] = {connectionInfo.host, connectionInfo.port};
beammp_tracef("New client connected, with ip {} and port {}", connectionInfo.host, connectionInfo.port);
return clients[i];
}
std::string Http::GET(const std::string& host, int port, const std::string& target, unsigned int* status) {
httplib::SSLClient client(host, port);
client.enable_server_certificate_verification(false);
client.set_address_family(AF_INET);
auto res = client.Get(target.c_str());
std::shared_ptr<httplib::SSLClient> client = getClient({host, port});
client->enable_server_certificate_verification(false);
client->set_address_family(AF_INET);
auto res = client->Get(target.c_str());
if (res) {
if (status) {
*status = res->status;
@@ -48,12 +75,12 @@ std::string Http::GET(const std::string& host, int port, const std::string& targ
}
std::string Http::POST(const std::string& host, int port, const std::string& target, const std::string& body, const std::string& ContentType, unsigned int* status, const httplib::Headers& headers) {
httplib::SSLClient client(host, port);
client.set_read_timeout(std::chrono::seconds(10));
beammp_assert(client.is_valid());
client.enable_server_certificate_verification(false);
client.set_address_family(AF_INET);
auto res = client.Post(target.c_str(), headers, body.c_str(), body.size(), ContentType.c_str());
std::shared_ptr<httplib::SSLClient> client = getClient({host, port});
client->set_read_timeout(std::chrono::seconds(10));
beammp_assert(client->is_valid());
client->enable_server_certificate_verification(false);
client->set_address_family(AF_INET);
auto res = client->Post(target.c_str(), headers, body.c_str(), body.size(), ContentType.c_str());
if (res) {
if (status) {
*status = res->status;
+39 -18
View File
@@ -60,7 +60,11 @@ std::string LuaAPI::LuaToString(const sol::object Value, size_t Indent, bool Quo
}
case sol::type::number: {
std::stringstream ss;
ss << Value.as<float>();
if (Value.is<int>()) {
ss << Value.as<int>();
} else {
ss << Value.as<float>();
}
return ss.str();
}
case sol::type::lua_nil:
@@ -561,7 +565,11 @@ static void JsonEncodeRecursive(nlohmann::json& json, const sol::object& left, c
key = left.as<std::string>();
break;
case sol::type::number:
key = std::to_string(left.as<double>());
if (left.is<int>()) {
key = std::to_string(left.as<int>());
} else {
key = std::to_string(left.as<double>());
}
break;
default:
beammp_assert_not_reachable();
@@ -589,21 +597,30 @@ static void JsonEncodeRecursive(nlohmann::json& json, const sol::object& left, c
case sol::type::string:
value = right.as<std::string>();
break;
case sol::type::number:
value = right.as<double>();
case sol::type::number: {
if (right.is<int>()) {
value = right.as<int>();
} else {
value = right.as<double>();
}
break;
}
case sol::type::function:
beammp_lua_warn("unsure what to do with function in JsonEncode, ignoring");
return;
case sol::type::table: {
bool local_is_array = true;
for (const auto& pair : right.as<sol::table>()) {
if (pair.first.get_type() != sol::type::number) {
local_is_array = false;
if (right.as<sol::table>().empty()) {
value = nlohmann::json::object();
} else {
bool local_is_array = true;
for (const auto& pair : right.as<sol::table>()) {
if (pair.first.get_type() != sol::type::number) {
local_is_array = false;
}
}
for (const auto& pair : right.as<sol::table>()) {
JsonEncodeRecursive(value, pair.first, pair.second, local_is_array, depth + 1);
}
}
for (const auto& pair : right.as<sol::table>()) {
JsonEncodeRecursive(value, pair.first, pair.second, local_is_array, depth + 1);
}
break;
}
@@ -620,14 +637,18 @@ static void JsonEncodeRecursive(nlohmann::json& json, const sol::object& left, c
std::string LuaAPI::MP::JsonEncode(const sol::table& object) {
nlohmann::json json;
// table
bool is_array = true;
for (const auto& pair : object.as<sol::table>()) {
if (pair.first.get_type() != sol::type::number) {
is_array = false;
if (object.as<sol::table>().empty()) {
json = nlohmann::json::object();
} else {
bool is_array = true;
for (const auto& pair : object.as<sol::table>()) {
if (pair.first.get_type() != sol::type::number) {
is_array = false;
}
}
for (const auto& entry : object) {
JsonEncodeRecursive(json, entry.first, entry.second, is_array);
}
}
for (const auto& entry : object) {
JsonEncodeRecursive(json, entry.first, entry.second, is_array);
}
return json.dump();
}
+60
View File
@@ -0,0 +1,60 @@
#include "Profiling.h"
#include <limits>
prof::Duration prof::duration(const TimePoint& start, const TimePoint& end) {
return end - start;
}
prof::TimePoint prof::now() {
return std::chrono::high_resolution_clock::now();
}
prof::Stats prof::UnitProfileCollection::stats(const std::string& unit) {
return m_map->operator[](unit).stats();
}
size_t prof::UnitProfileCollection::measurement_count(const std::string& unit) {
return m_map->operator[](unit).measurement_count();
}
void prof::UnitProfileCollection::add_sample(const std::string& unit, const Duration& duration) {
m_map->operator[](unit).add_sample(duration);
}
prof::Stats prof::UnitExecutionTime::stats() const {
std::unique_lock lock(m_mtx);
Stats result {};
// calculate sum
result.n = m_total_calls;
result.max = m_min;
result.min = m_max;
// calculate mean: mean = sum_x / n
result.mean = m_sum / double(m_total_calls);
// calculate stdev: stdev = sqrt((sum_x2 / n) - (mean * mean))
result.stdev = std::sqrt((m_measurement_sqr_sum / double(result.n)) - (result.mean * result.mean));
return result;
}
void prof::UnitExecutionTime::add_sample(const Duration& dur) {
std::unique_lock lock(m_mtx);
m_sum += dur.count();
m_measurement_sqr_sum += dur.count() * dur.count();
m_min = std::min(dur.count(), m_min);
m_max = std::max(dur.count(), m_max);
++m_total_calls;
}
prof::UnitExecutionTime::UnitExecutionTime() {
}
std::unordered_map<std::string, prof::Stats> prof::UnitProfileCollection::all_stats() {
auto map = m_map.synchronize();
std::unordered_map<std::string, Stats> result {};
for (const auto& [name, time] : *map) {
result[name] = time.stats();
}
return result;
}
size_t prof::UnitExecutionTime::measurement_count() const {
std::unique_lock lock(m_mtx);
return m_total_calls;
}
+50
View File
@@ -0,0 +1,50 @@
#include "RateLimiter.h"
RateLimiter::RateLimiter() {};
bool RateLimiter::isConnectionAllowed(const std::string& client_address) {
if (RateLimiter::isIPBlocked(client_address)) {
return false;
}
std::lock_guard<std::mutex> lock(m_connection_mutex);
auto current_time = std::chrono::high_resolution_clock::now();
auto& violations = m_connection[client_address];
// Deleting old violations (older than 5 seconds)
violations.erase(std::remove_if(violations.begin(), violations.end(),
[&](const auto& timestamp) {
return std::chrono::duration_cast<std::chrono::seconds>(current_time - timestamp).count() > 5;
}),
violations.end());
violations.push_back(current_time);
if (violations.size() >= 4) {
RateLimiter::blockIP(client_address);
beammp_errorf("[DoS Protection] Client with the IP: {} surpassed the violation treshhold and is now on the blocked list", client_address);
return false;
}
return true; // We allow the connection
}
void RateLimiter::blockIP(const std::string& client_address) {
std::ofstream block_file("blocked_ips.txt", std::ios::app);
if (block_file.is_open()) {
block_file << client_address << std::endl;
}
}
bool RateLimiter::isIPBlocked(const std::string& client_address) {
std::ifstream block_file("blocked_ips.txt");
std::unordered_set<std::string> blockedIPs;
if (block_file.is_open()) {
std::string line;
while (std::getline(block_file, line)) {
blockedIPs.insert(line);
}
}
return blockedIPs.contains(client_address);
};
+10
View File
@@ -51,12 +51,15 @@ static constexpr std::string_view StrAuthKey = "AuthKey";
static constexpr std::string_view EnvStrAuthKey = "BEAMMP_AUTH_KEY";
static constexpr std::string_view StrLogChat = "LogChat";
static constexpr std::string_view EnvStrLogChat = "BEAMMP_LOG_CHAT";
static constexpr std::string_view StrAllowGuests = "AllowGuests";
static constexpr std::string_view EnvStrAllowGuests = "BEAMMP_ALLOW_GUESTS";
static constexpr std::string_view StrPassword = "Password";
// Misc
static constexpr std::string_view StrSendErrors = "SendErrors";
static constexpr std::string_view StrSendErrorsMessageEnabled = "SendErrorsShowMessage";
static constexpr std::string_view StrHideUpdateMessages = "ImScaredOfUpdates";
static constexpr std::string_view StrUpdateReminderTime = "UpdateReminderTime";
TEST_CASE("TConfig::TConfig") {
const std::string CfgFile = "beammp_server_testconfig.toml";
@@ -124,6 +127,8 @@ void TConfig::FlushToFile() {
SetComment(data["General"][StrAuthKey.data()].comments(), " AuthKey has to be filled out in order to run the server");
data["General"][StrLogChat.data()] = Application::Settings.LogChat;
SetComment(data["General"][StrLogChat.data()].comments(), " Whether to log chat messages in the console / log");
data["General"][StrAllowGuests.data()] = Application::Settings.AllowGuests;
SetComment(data["General"][StrAllowGuests.data()].comments(), " Whether to allow guests");
data["General"][StrDebug.data()] = Application::Settings.DebugModeEnabled;
data["General"][StrPrivate.data()] = Application::Settings.Private;
data["General"][StrPort.data()] = Application::Settings.Port;
@@ -140,6 +145,8 @@ void TConfig::FlushToFile() {
// Misc
data["Misc"][StrHideUpdateMessages.data()] = Application::Settings.HideUpdateMessages;
SetComment(data["Misc"][StrHideUpdateMessages.data()].comments(), " Hides the periodic update message which notifies you of a new server version. You should really keep this on and always update as soon as possible. For more information visit https://wiki.beammp.com/en/home/server-maintenance#updating-the-server. An update message will always appear at startup regardless.");
data["Misc"][StrUpdateReminderTime.data()] = Application::Settings.UpdateReminderTime;
SetComment(data["Misc"][StrUpdateReminderTime.data()].comments(), " Specifies the time between update reminders. You can use any of \"s, min, h, d\" at the end to specify the units seconds, minutes, hours or days. So 30d or 0.5min will print the update message every 30 days or half a minute.");
data["Misc"][StrSendErrors.data()] = Application::Settings.SendErrors;
SetComment(data["Misc"][StrSendErrors.data()].comments(), " If SendErrors is `true`, the server will send helpful info about crashes and other issues back to the BeamMP developers. This info may include your config, who is on your server at the time of the error, and similar general information. This kind of data is vital in helping us diagnose and fix issues faster. This has no impact on server performance. You can opt-out of this system by setting this to `false`");
data["Misc"][StrSendErrorsMessageEnabled.data()] = Application::Settings.SendErrorsMessageEnabled;
@@ -248,10 +255,12 @@ void TConfig::ParseFromFile(std::string_view name) {
TryReadValue(data, "General", StrResourceFolder, EnvStrResourceFolder, Application::Settings.Resource);
TryReadValue(data, "General", StrAuthKey, EnvStrAuthKey, Application::Settings.Key);
TryReadValue(data, "General", StrLogChat, EnvStrLogChat, Application::Settings.LogChat);
TryReadValue(data, "General", StrAllowGuests, EnvStrAllowGuests, Application::Settings.AllowGuests);
TryReadValue(data, "General", StrPassword, "", Application::Settings.Password);
// Misc
TryReadValue(data, "Misc", StrSendErrors, "", Application::Settings.SendErrors);
TryReadValue(data, "Misc", StrHideUpdateMessages, "", Application::Settings.HideUpdateMessages);
TryReadValue(data, "Misc", StrUpdateReminderTime, "", Application::Settings.UpdateReminderTime);
TryReadValue(data, "Misc", StrSendErrorsMessageEnabled, "", Application::Settings.SendErrorsMessageEnabled);
} catch (const std::exception& err) {
beammp_error("Error parsing config file value: " + std::string(err.what()));
@@ -295,6 +304,7 @@ void TConfig::PrintDebug() {
beammp_debug(std::string(StrDescription) + ": \"" + Application::Settings.ServerDesc + "\"");
beammp_debug(std::string(StrTags) + ": " + TagsAsPrettyArray());
beammp_debug(std::string(StrLogChat) + ": \"" + (Application::Settings.LogChat ? "true" : "false") + "\"");
beammp_debug(std::string(StrAllowGuests) + ": \"" + (Application::Settings.AllowGuests ? "true" : "false") + "\"");
beammp_debug(std::string(StrResourceFolder) + ": \"" + Application::Settings.Resource + "\"");
// special!
beammp_debug("Key Length: " + std::to_string(Application::Settings.Key.length()) + "");
+21 -1
View File
@@ -26,7 +26,9 @@
#include "TLuaEngine.h"
#include <ctime>
#include <lua.hpp>
#include <mutex>
#include <openssl/opensslv.h>
#include <sstream>
static inline bool StringStartsWith(const std::string& What, const std::string& StartsWith) {
@@ -273,7 +275,25 @@ void TConsole::Command_Version(const std::string& cmd, const std::vector<std::st
return;
}
Application::Console().WriteRaw("Current version: v" + Application::ServerVersionString());
std::string platform;
#if defined(BEAMMP_WINDOWS)
platform = "Windows";
#elif defined(BEAMMP_LINUX)
platform = "Linux";
#elif defined(BEAMMP_FREEBSD)
platform = "FreeBSD";
#elif defined(BEAMMP_APPLE)
platform = "Apple";
#else
platform = "Unknown";
#endif
Application::Console().WriteRaw("Platform: " + platform);
Application::Console().WriteRaw("Server: v" + Application::ServerVersionString());
std::string lua_version = fmt::format("Lua: v{}.{}.{}", LUA_VERSION_MAJOR, LUA_VERSION_MINOR, LUA_VERSION_RELEASE);
Application::Console().WriteRaw(lua_version);
std::string openssl_version = fmt::format("OpenSSL: v{}.{}.{}", OPENSSL_VERSION_MAJOR, OPENSSL_VERSION_MINOR, OPENSSL_VERSION_PATCH);
Application::Console().WriteRaw(openssl_version);
}
void TConsole::Command_Kick(const std::string&, const std::vector<std::string>& args) {
+9 -3
View File
@@ -20,6 +20,7 @@
#include "Client.h"
#include "Http.h"
#include "ChronoWrapper.h"
//#include "SocketIO.h"
#include <rapidjson/document.h>
#include <rapidjson/rapidjson.h>
@@ -36,15 +37,17 @@ void THeartbeatThread::operator()() {
static std::string Last;
static std::chrono::high_resolution_clock::time_point LastNormalUpdateTime = std::chrono::high_resolution_clock::now();
static std::chrono::high_resolution_clock::time_point LastUpdateReminderTime = std::chrono::high_resolution_clock::now();
bool isAuth = false;
size_t UpdateReminderCounter = 0;
std::chrono::high_resolution_clock::duration UpdateReminderTimePassed;
auto UpdateReminderTimeout = ChronoWrapper::TimeFromStringWithLiteral(Application::Settings.UpdateReminderTime);
while (!Application::IsShuttingDown()) {
++UpdateReminderCounter;
Body = GenerateCall();
// a hot-change occurs when a setting has changed, to update the backend of that change.
auto Now = std::chrono::high_resolution_clock::now();
bool Unchanged = Last == Body;
auto TimePassed = (Now - LastNormalUpdateTime);
UpdateReminderTimePassed = (Now - LastUpdateReminderTime);
auto Threshold = Unchanged ? 30 : 5;
if (TimePassed < std::chrono::seconds(Threshold)) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
@@ -129,7 +132,9 @@ void THeartbeatThread::operator()() {
if (isAuth || Application::Settings.Private) {
Application::SetSubsystemStatus("Heartbeat", Application::Status::Good);
}
if (!Application::Settings.HideUpdateMessages && UpdateReminderCounter % 5) {
// beammp_debugf("Update reminder time passed: {}, Update reminder time: {}", UpdateReminderTimePassed.count(), UpdateReminderTimeout.count());
if (!Application::Settings.HideUpdateMessages && UpdateReminderTimePassed.count() > UpdateReminderTimeout.count()) {
LastUpdateReminderTime = std::chrono::high_resolution_clock::now();
Application::CheckForUpdates();
}
}
@@ -148,6 +153,7 @@ std::string THeartbeatThread::GenerateCall() {
<< "&clientversion=" << std::to_string(Application::ClientMajorVersion()) + ".0" // FIXME: Wtf.
<< "&name=" << Application::Settings.ServerName
<< "&tags=" << Application::Settings.ServerTags
<< "&allowguests=" << (Application::Settings.AllowGuests ? "true" : "false")
<< "&modlist=" << mResourceManager.TrimmedList()
<< "&modstotalsize=" << mResourceManager.MaxModSize()
<< "&modstotal=" << mResourceManager.ModsLoaded()
+112 -22
View File
@@ -22,11 +22,13 @@
#include "CustomAssert.h"
#include "Http.h"
#include "LuaAPI.h"
#include "Profiling.h"
#include "TLuaPlugin.h"
#include "sol/object.hpp"
#include <chrono>
#include <condition_variable>
#include <fmt/core.h>
#include <nlohmann/json.hpp>
#include <random>
#include <thread>
@@ -64,6 +66,7 @@ void TLuaEngine::operator()() {
RegisterThread("LuaEngine");
Application::SetSubsystemStatus("LuaEngine", Application::Status::Good);
// lua engine main thread
beammp_infof("Lua v{}.{}.{}", LUA_VERSION_MAJOR, LUA_VERSION_MINOR, LUA_VERSION_RELEASE);
CollectAndInitPlugins();
// now call all onInit's
auto Futures = TriggerEvent("onInit", "");
@@ -266,7 +269,7 @@ std::vector<std::string> TLuaEngine::StateThreadData::GetStateTableKeys(const st
for (size_t i = 0; i < keys.size(); ++i) {
auto obj = current.get<sol::object>(keys.at(i));
if (obj.get_type() == sol::type::nil) {
if (obj.get_type() == sol::type::lua_nil) {
// error
break;
} else if (i == keys.size() - 1) {
@@ -351,7 +354,7 @@ std::shared_ptr<TLuaResult> TLuaEngine::EnqueueScript(TLuaStateId StateID, const
return mLuaStates.at(StateID)->EnqueueScript(Script);
}
std::shared_ptr<TLuaResult> TLuaEngine::EnqueueFunctionCall(TLuaStateId StateID, const std::string& FunctionName, const std::vector<TLuaArgTypes>& Args) {
std::shared_ptr<TLuaResult> TLuaEngine::EnqueueFunctionCall(TLuaStateId StateID, const std::string& FunctionName, const std::vector<TLuaValue>& Args) {
std::unique_lock Lock(mLuaStatesMutex);
return mLuaStates.at(StateID)->EnqueueFunctionCall(FunctionName, Args);
}
@@ -360,17 +363,30 @@ void TLuaEngine::CollectAndInitPlugins() {
if (!fs::exists(mResourceServerPath)) {
fs::create_directories(mResourceServerPath);
}
for (const auto& Dir : fs::directory_iterator(mResourceServerPath)) {
auto Path = Dir.path();
Path = fs::relative(Path);
if (!Dir.is_directory()) {
beammp_error("\"" + Dir.path().string() + "\" is not a directory, skipping");
std::vector<fs::path> PluginsEntries;
for (const auto& Entry : fs::directory_iterator(mResourceServerPath)) {
if (Entry.is_directory()) {
PluginsEntries.push_back(Entry);
} else {
TLuaPluginConfig Config { Path.stem().string() };
FindAndParseConfig(Path, Config);
InitializePlugin(Path, Config);
beammp_error("\"" + Entry.path().string() + "\" is not a directory, skipping");
}
}
std::sort(PluginsEntries.begin(), PluginsEntries.end(), [](const fs::path& first, const fs::path& second) {
auto firstStr = first.string();
auto secondStr = second.string();
std::transform(firstStr.begin(), firstStr.end(), firstStr.begin(), ::tolower);
std::transform(secondStr.begin(), secondStr.end(), secondStr.begin(), ::tolower);
return firstStr < secondStr;
});
for (const auto& Dir : PluginsEntries) {
auto Path = fs::relative(Dir);
TLuaPluginConfig Config { Path.stem().string() };
FindAndParseConfig(Path, Config);
InitializePlugin(Path, Config);
}
}
void TLuaEngine::InitializePlugin(const fs::path& Folder, const TLuaPluginConfig& Config) {
@@ -431,13 +447,52 @@ std::set<std::string> TLuaEngine::GetEventHandlersForState(const std::string& Ev
return mLuaEvents[EventName][StateId];
}
std::vector<sol::object> TLuaEngine::StateThreadData::JsonStringToArray(JsonString Str) {
auto LocalTable = Lua_JsonDecode(Str.value).as<std::vector<sol::object>>();
for (auto& value : LocalTable) {
if (value.is<std::string>() && value.as<std::string>() == BEAMMP_INTERNAL_NIL) {
value = sol::object {};
}
}
return LocalTable;
}
sol::table TLuaEngine::StateThreadData::Lua_TriggerGlobalEvent(const std::string& EventName, sol::variadic_args EventArgs) {
auto Return = mEngine->TriggerEvent(EventName, mStateId, EventArgs);
auto Table = mStateView.create_table();
int i = 1;
for (auto Arg : EventArgs) {
switch (Arg.get_type()) {
case sol::type::none:
case sol::type::userdata:
case sol::type::lightuserdata:
case sol::type::thread:
case sol::type::function:
case sol::type::poly:
Table.set(i, BEAMMP_INTERNAL_NIL);
beammp_warnf("Passed a value of type '{}' to TriggerGlobalEvent(\"{}\", ...). This type can not be serialized, and cannot be passed between states. It will arrive as <nil> in handlers.", sol::type_name(EventArgs.lua_state(), Arg.get_type()), EventName);
break;
case sol::type::lua_nil:
Table.set(i, BEAMMP_INTERNAL_NIL);
break;
case sol::type::string:
case sol::type::number:
case sol::type::boolean:
case sol::type::table:
Table.set(i, Arg);
break;
}
++i;
}
JsonString Str { LuaAPI::MP::JsonEncode(Table) };
beammp_debugf("json: {}", Str.value);
auto Return = mEngine->TriggerEvent(EventName, mStateId, Str);
auto MyHandlers = mEngine->GetEventHandlersForState(EventName, mStateId);
sol::variadic_results LocalArgs = JsonStringToArray(Str);
for (const auto& Handler : MyHandlers) {
auto Fn = mStateView[Handler];
if (Fn.valid()) {
auto LuaResult = Fn(EventArgs);
auto LuaResult = Fn(LocalArgs);
auto Result = std::make_shared<TLuaResult>();
if (LuaResult.valid()) {
Result->Error = false;
@@ -468,11 +523,13 @@ sol::table TLuaEngine::StateThreadData::Lua_TriggerGlobalEvent(const std::string
sol::state_view StateView(mState);
sol::table Result = StateView.create_table();
auto Vector = Self.get<std::vector<std::shared_ptr<TLuaResult>>>("ReturnValueImpl");
int i = 1;
for (const auto& Value : Vector) {
if (!Value->Ready) {
return sol::lua_nil;
}
Result.add(Value->Result);
Result.set(i, Value->Result);
++i;
}
return Result;
});
@@ -482,12 +539,14 @@ sol::table TLuaEngine::StateThreadData::Lua_TriggerGlobalEvent(const std::string
sol::table TLuaEngine::StateThreadData::Lua_TriggerLocalEvent(const std::string& EventName, sol::variadic_args EventArgs) {
// TODO: make asynchronous?
sol::table Result = mStateView.create_table();
int i = 1;
for (const auto& Handler : mEngine->GetEventHandlersForState(EventName, mStateId)) {
auto Fn = mStateView[Handler];
if (Fn.valid() && Fn.get_type() == sol::type::function) {
auto FnRet = Fn(EventArgs);
if (FnRet.valid()) {
Result.add(FnRet);
Result.set(i, FnRet);
++i;
} else {
sol::error Err = FnRet;
beammp_lua_error(std::string("TriggerLocalEvent: ") + Err.what());
@@ -659,6 +718,7 @@ static void AddToTable(sol::table& table, const std::string& left, const T& valu
static void JsonDecodeRecursive(sol::state_view& StateView, sol::table& table, const std::string& left, const nlohmann::json& right) {
switch (right.type()) {
case nlohmann::detail::value_t::null:
AddToTable(table, left, sol::lua_nil_t {});
return;
case nlohmann::detail::value_t::object: {
auto value = table.create();
@@ -882,6 +942,30 @@ TLuaEngine::StateThreadData::StateThreadData(const std::string& Name, TLuaStateI
UtilTable.set_function("RandomIntRange", [this](int64_t min, int64_t max) -> int64_t {
return std::uniform_int_distribution(min, max)(mMersenneTwister);
});
UtilTable.set_function("DebugExecutionTime", [this]() -> sol::table {
sol::state_view StateView(mState);
sol::table Result = StateView.create_table();
auto stats = mProfile.all_stats();
for (const auto& [name, stat] : stats) {
Result[name] = StateView.create_table();
Result[name]["mean"] = stat.mean;
Result[name]["stdev"] = stat.stdev;
Result[name]["min"] = stat.min;
Result[name]["max"] = stat.max;
Result[name]["n"] = stat.n;
}
return Result;
});
UtilTable.set_function("DebugStartProfile", [this](const std::string& name) {
mProfileStarts[name] = prof::now();
});
UtilTable.set_function("DebugStopProfile", [this](const std::string& name) {
if (!mProfileStarts.contains(name)) {
beammp_lua_errorf("DebugStopProfile('{}') failed, because a profile for '{}' wasn't started", name, name);
return;
}
mProfile.add_sample(name, prof::duration(mProfileStarts.at(name), prof::now()));
});
auto HttpTable = StateView.create_named_table("Http");
HttpTable.set_function("CreateConnection", [this](const std::string& host, uint16_t port) {
@@ -929,7 +1013,7 @@ std::shared_ptr<TLuaResult> TLuaEngine::StateThreadData::EnqueueScript(const TLu
return Result;
}
std::shared_ptr<TLuaResult> TLuaEngine::StateThreadData::EnqueueFunctionCallFromCustomEvent(const std::string& FunctionName, const std::vector<TLuaArgTypes>& Args, const std::string& EventName, CallStrategy Strategy) {
std::shared_ptr<TLuaResult> TLuaEngine::StateThreadData::EnqueueFunctionCallFromCustomEvent(const std::string& FunctionName, const std::vector<TLuaValue>& Args, const std::string& EventName, CallStrategy Strategy) {
// TODO: Document all this
decltype(mStateFunctionQueue)::iterator Iter = mStateFunctionQueue.end();
if (Strategy == CallStrategy::BestEffort) {
@@ -951,7 +1035,7 @@ std::shared_ptr<TLuaResult> TLuaEngine::StateThreadData::EnqueueFunctionCallFrom
}
}
std::shared_ptr<TLuaResult> TLuaEngine::StateThreadData::EnqueueFunctionCall(const std::string& FunctionName, const std::vector<TLuaArgTypes>& Args) {
std::shared_ptr<TLuaResult> TLuaEngine::StateThreadData::EnqueueFunctionCall(const std::string& FunctionName, const std::vector<TLuaValue>& Args) {
auto Result = std::make_shared<TLuaResult>();
Result->StateId = mStateId;
Result->Function = FunctionName;
@@ -1019,6 +1103,7 @@ void TLuaEngine::StateThreadData::operator()() {
std::chrono::milliseconds(500),
[&]() -> bool { return !mStateFunctionQueue.empty(); });
if (NotExpired) {
auto ProfStart = prof::now();
auto TheQueuedFunction = std::move(mStateFunctionQueue.front());
mStateFunctionQueue.erase(mStateFunctionQueue.begin());
Lock.unlock();
@@ -1036,19 +1121,21 @@ void TLuaEngine::StateThreadData::operator()() {
continue;
}
switch (Arg.index()) {
case TLuaArgTypes_String:
case TLuaType::String:
LuaArgs.push_back(sol::make_object(StateView, std::get<std::string>(Arg)));
break;
case TLuaArgTypes_Int:
case TLuaType::Int:
LuaArgs.push_back(sol::make_object(StateView, std::get<int>(Arg)));
break;
case TLuaArgTypes_VariadicArgs:
LuaArgs.push_back(sol::make_object(StateView, std::get<sol::variadic_args>(Arg)));
case TLuaType::Json: {
auto LocalArgs = JsonStringToArray(std::get<JsonString>(Arg));
LuaArgs.insert(LuaArgs.end(), LocalArgs.begin(), LocalArgs.end());
break;
case TLuaArgTypes_Bool:
}
case TLuaType::Bool:
LuaArgs.push_back(sol::make_object(StateView, std::get<bool>(Arg)));
break;
case TLuaArgTypes_StringStringMap: {
case TLuaType::StringStringMap: {
auto Map = std::get<std::unordered_map<std::string, std::string>>(Arg);
auto Table = StateView.create_table();
for (const auto& [k, v] : Map) {
@@ -1077,6 +1164,9 @@ void TLuaEngine::StateThreadData::operator()() {
Result->ErrorMessage = BeamMPFnNotFoundError; // special error kind that we can ignore later
Result->MarkAsReady();
}
auto ProfEnd = prof::now();
auto ProfDuration = prof::duration(ProfStart, ProfEnd);
mProfile.add_sample(FnName, ProfDuration);
}
}
}
+30 -11
View File
@@ -20,6 +20,7 @@
#include "Client.h"
#include "Common.h"
#include "LuaAPI.h"
#include "RateLimiter.h"
#include "TLuaEngine.h"
#include "nlohmann/json.hpp"
#include <CustomAssert.h>
@@ -196,10 +197,18 @@ void TNetwork::Identify(TConnection&& RawConnection) {
RawConnection.Socket.shutdown(socket_base::shutdown_both, ec);
return;
}
std::shared_ptr<TClient> Client { nullptr };
std::string client_address = RawConnection.SockAddr.address().to_string();
std::shared_ptr<TClient> client { nullptr };
RateLimiter ddos_protection;
try {
if (Code == 'C') {
Client = Authentication(std::move(RawConnection));
if (ddos_protection.isConnectionAllowed(client_address)) {
beammp_infof("[DoS Protection] Client: [{}] is authorized to connect to the server", client_address);
client = Authentication(std::move(RawConnection));
} else {
beammp_infof("[DoS Protection] Client: [{}] has been denied access to the server", client_address);
RawConnection.Socket.shutdown(socket_base::shutdown_both, ec);
}
} else if (Code == 'D') {
HandleDownload(std::move(RawConnection));
} else if (Code == 'P') {
@@ -209,7 +218,7 @@ void TNetwork::Identify(TConnection&& RawConnection) {
} else {
beammp_errorf("Invalid code got in Identify: '{}'", Code);
}
} catch(const std::exception& e) {
} catch (const std::exception& e) {
beammp_errorf("Error during handling of code {} - client left in invalid state, closing socket", Code);
boost::system::error_code ec;
RawConnection.Socket.shutdown(socket_base::shutdown_both, ec);
@@ -278,7 +287,7 @@ std::shared_ptr<TClient> TNetwork::Authentication(TConnection&& RawConnection) {
return nullptr;
}
if (!TCPSend(*Client, StringToVector("A"))) { //changed to A for Accepted version
if (!TCPSend(*Client, StringToVector("A"))) { // changed to A for Accepted version
// TODO: handle
}
@@ -289,16 +298,21 @@ std::shared_ptr<TClient> TNetwork::Authentication(TConnection&& RawConnection) {
return nullptr;
}
std::string key(reinterpret_cast<const char*>(Data.data()), Data.size());
std::string Key(reinterpret_cast<const char*>(Data.data()), Data.size());
std::string AuthKey = Application::Settings.Key;
std::string ClientIp = Client->GetIdentifiers().at("ip");
nlohmann::json AuthReq{};
std::string AuthResStr{};
nlohmann::json AuthReq {};
std::string AuthResStr {};
try {
AuthReq = nlohmann::json {
{ "key", key }
{ "key", Key },
{ "auth_key", AuthKey },
{ "client_ip", ClientIp }
};
auto Target = "/pkToUser";
unsigned int ResponseCode = 0;
AuthResStr = Http::POST(Application::GetBackendUrlForAuth(), 443, Target, AuthReq.dump(), "application/json", &ResponseCode);
@@ -334,14 +348,14 @@ std::shared_ptr<TClient> TNetwork::Authentication(TConnection&& RawConnection) {
return nullptr;
}
if(!Application::Settings.Password.empty()) { // ask password
if(!TCPSend(*Client, StringToVector("S"))) {
if (!Application::Settings.Password.empty()) { // ask password
if (!TCPSend(*Client, StringToVector("S"))) {
// TODO: handle
}
beammp_info("Waiting for password");
Data = TCPRcv(*Client);
std::string Pass = std::string(reinterpret_cast<const char*>(Data.data()), Data.size());
if(Pass != HashPassword(Application::Settings.Password)) {
if (Pass != HashPassword(Application::Settings.Password)) {
beammp_debug(Client->GetName() + " attempted to connect with a wrong password");
ClientKick(*Client, "Wrong password!");
return {};
@@ -384,6 +398,11 @@ std::shared_ptr<TClient> TNetwork::Authentication(TConnection&& RawConnection) {
return false;
});
if (!NotAllowedWithReason && !Application::Settings.AllowGuests && Client->IsGuest()) { //! NotAllowedWithReason because this message has the lowest priority
NotAllowedWithReason = true;
Reason = "No guests are allowed on this server! To join, sign up at: forum.beammp.com.";
}
if (NotAllowed) {
ClientKick(*Client, "you are not allowed on the server!");
return {};
+1 -1
View File
@@ -76,7 +76,7 @@ void TPPSMonitor::operator()() {
return true;
});
for (auto& ClientToKick : TimedOutClients) {
Network().ClientKick(*ClientToKick, "Timeout (no ping for way too long)");
ClientToKick->Disconnect("Timeout");
}
TimedOutClients.clear();
if (C == 0 || mInternalPPS == 0) {
+66
View File
@@ -0,0 +1,66 @@
local function assert_eq(x, y, explain)
if x ~= y then
print("assertion '"..explain.."' failed:\n\tgot:\t", x, "\n\texpected:", y)
end
end
---@param o1 any|table First object to compare
---@param o2 any|table Second object to compare
---@param ignore_mt boolean True to ignore metatables (a recursive function to tests tables inside tables)
function equals(o1, o2, ignore_mt)
if o1 == o2 then return true end
local o1Type = type(o1)
local o2Type = type(o2)
if o1Type ~= o2Type then return false end
if o1Type ~= 'table' then return false end
if not ignore_mt then
local mt1 = getmetatable(o1)
if mt1 and mt1.__eq then
--compare using built in method
return o1 == o2
end
end
local keySet = {}
for key1, value1 in pairs(o1) do
local value2 = o2[key1]
if value2 == nil or equals(value1, value2, ignore_mt) == false then
return false
end
keySet[key1] = true
end
for key2, _ in pairs(o2) do
if not keySet[key2] then return false end
end
return true
end
local function assert_table_eq(x, y, explain)
if not equals(x, y, true) then
print("assertion '"..explain.."' failed:\n\tgot:\t", x, "\n\texpected:", y)
end
end
assert_eq(Util.JsonEncode({1, 2, 3, 4, 5}), "[1,2,3,4,5]", "table to array")
assert_eq(Util.JsonEncode({"a", 1, 2, 3, 4, 5}), '["a",1,2,3,4,5]', "table to array")
assert_eq(Util.JsonEncode({"a", 1, 2.0, 3, 4, 5}), '["a",1,2.0,3,4,5]', "table to array")
assert_eq(Util.JsonEncode({hello="world", john={doe = 1, jane = 2.5, mike = {2, 3, 4}}, dave={}}), '{"dave":{},"hello":"world","john":{"doe":1,"jane":2.5,"mike":[2,3,4]}}', "table to obj")
assert_eq(Util.JsonEncode({a = nil}), "{}", "null obj member")
assert_eq(Util.JsonEncode({1, nil, 3}), "[1,3]", "null array member")
assert_eq(Util.JsonEncode({}), "{}", "empty array/table")
assert_eq(Util.JsonEncode({1234}), "[1234]", "int")
assert_eq(Util.JsonEncode({1234.0}), "[1234.0]", "double")
assert_table_eq(Util.JsonDecode("[1,2,3,4,5]"), {1, 2, 3, 4, 5}, "decode table to array")
assert_table_eq(Util.JsonDecode('["a",1,2,3,4,5]'), {"a", 1, 2, 3, 4, 5}, "decode table to array")
assert_table_eq(Util.JsonDecode('["a",1,2.0,3,4,5]'), {"a", 1, 2.0, 3, 4, 5}, "decode table to array")
assert_table_eq(Util.JsonDecode('{"dave":{},"hello":"world","john":{"doe":1,"jane":2.5,"mike":[2,3,4]}}'), {hello="world", john={doe = 1, jane = 2.5, mike = {2, 3, 4}}, dave={}}, "decode table to obj")
assert_table_eq(Util.JsonDecode("{}"), {a = nil}, "decode null obj member")
assert_table_eq(Util.JsonDecode("[1,3]"), {1, 3}, "decode null array member")
assert_table_eq(Util.JsonDecode("{}"), {}, "decode empty array/table")
assert_table_eq(Util.JsonDecode("[1234]"), {1234}, "decode int")
assert_table_eq(Util.JsonDecode("[1234.0]"), {1234.0}, "decode double")
+1 -1
Submodule vcpkg updated: 326d8b43e3...6978381401