Compare commits

...

106 Commits

Author SHA1 Message Date
sla-ppy
70967a81a3 add RateLimiter class 2024-06-20 16:47:20 +02:00
sla-ppy
93a477e9c3 refactor dos protection 2024-06-20 16:47:17 +02:00
Igor
2c05a442ee add a simple DOS protection system 2024-06-20 16:47:04 +02:00
Lion
a9385c47e1 Adjust allow guests feature in heartbeat to follow Backend#33 (#341)
https://github.com/BeamMP/Backend/issues/33
2024-06-20 09:00:13 +02:00
Lion
1e9c4e357c adjust allow guests feature in heartbeat to follow Backend#33
https://github.com/BeamMP/Backend/issues/33
2024-06-20 08:58:58 +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
b068a9b48f)
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
Lion
4d7967d5d9 bump version (#314) 2024-05-06 14:37:50 +02:00
Lion Kortlepel
5dd4c97ed1 bump version 2024-05-06 14:37:34 +02:00
Lion
13390c5388 Remove backup1, backup2 backend endpoints (#313) 2024-05-06 12:05:30 +02:00
Lion Kortlepel
2f995a71ae remove backup1, backup2 endpoints 2024-05-06 12:04:56 +02:00
Lion
3b5c6491a3 Fix timeout for synced players (#289)
Fix: #275.

While I was at it, I also refactored the debug output so its more
appealing to read.

I dont see further ways to improve on this currently.
2024-05-06 12:02:31 +02:00
sla-ppy
db8719b5cd fix timeout for synced players 2024-05-06 12:02:16 +02:00
Lion
a3c4b82a5d Allow provider to define server port ENV via BEAMMP_PROVIDER_PORT_ENV (#244) 2024-05-06 11:59:28 +02:00
Lion Kortlepel
3b0e49fb06 add /bigobj to fix compiling on windows 2024-05-06 11:56:26 +02:00
Lion Kortlepel
cf8f10b949 add --port=value argument 2024-05-06 11:56:26 +02:00
Lion Kortlepel
6f50cad76b add BEAMMP_PROVIDER_PORT_ENV to allow provider to change which ENV
variable the port is fetched from
2024-05-06 11:56:26 +02:00
Lion
03d91b1f4d Disable server config generation via ENV variable BEAMMP_PROVIDER_DISABLE_CONFIG (#240) 2024-05-06 11:53:57 +02:00
Lion Kortlepel
e407d246e2 update vcpkg 2024-05-06 11:47:00 +02:00
Lion Kortlepel
234bdf5877 add ENV variable to disable config generation and parsing 2024-05-06 11:46:21 +02:00
Lion
c3b4528c89 Add lua Util.LogInfo(), Util.LogError(), etc. functions (#309)
```lua
Util.LogInfo("Hello, World!")
Util.LogWarn("Cool warning")
Util.LogError("Oh no!")
Util.LogDebug("hi")
```
now produces

```
[19/04/24 11:06:50.142] [Test] [INFO] Hello, World!    
[19/04/24 11:06:50.142] [Test] [WARN] Cool warning    
[19/04/24 11:06:50.142] [Test] [ERROR] Oh no!
[19/04/24 11:06:50.142] [Test] [DEBUG] hi
```

where `Test` is the lua state id.
2024-05-06 11:39:12 +02:00
Lion Kortlepel
5cf6d1d228 add Util.LogDebug 2024-04-20 20:16:57 +02:00
Lion Kortlepel
65017d0834 add lua log to test common 2024-04-19 11:35:39 +02:00
Lion Kortlepel
1237e7e533 add lua logging facilities as Util.Log*() functions 2024-04-19 11:10:50 +02:00
Neptnium
d81087b5af fix TriggerGlobalEvent not passing event arguments correctly (closes #106) 2024-04-18 18:43:04 +02: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
e2d7721438 Update lionkor/commandline to v2.4.2 (#265)
This fixes stdin/stdout redirect on windows.
2024-02-24 20:41:17 +01:00
Lion
0146a1cbe8 Housekeeping (#293) 2024-02-24 20:36:12 +01:00
Lion Kortlepel
352a3aa536 update contributing 2024-02-15 17:38:21 +01:00
Lion Kortlepel
ac8c386c61 run gh workflows on PR and push differently 2024-02-15 17:38:21 +01:00
Lion Kortlepel
d1fb15fc9a run ci/cd on PR open, reopen and ready for review 2024-02-15 17:38:21 +01:00
Lion Kortlepel
f376d9f7be update vcpkg 2024-02-15 17:38:21 +01:00
Lion Kortlepel
2f3dcfeb5a update commandline 2024-02-15 17:38:21 +01:00
Lion
913ba1c793 Link to the docs instead of the wiki in ServerConfig.toml (#291)
Changed the update message as well.
Closes #290
2024-02-15 12:38:48 +01:00
Lion
9a0c9d3ce4 Fix unicycle counting as car (#288)
Fixes #246.
2024-02-15 10:13:49 +01:00
sla-ppy
a0d9be18b7 documentate unicycle dirty fix 2024-02-14 13:51:37 +01:00
alex.ita
87d2e3355a Update Common.cpp 2024-02-14 11:56:02 +01:00
alex.ita
afb3763eab Update TConfig.cpp 2024-02-14 11:51:24 +01:00
sla-ppy
9de5a08b0c dirty fix unicycle counting as car 2024-02-12 01:24:17 +01:00
Lion
bef623a281 Fix assigment of ID's so two unique users dont get the same ID (#277)
Closes #154
2024-02-08 23:58:18 +01:00
sla-ppy
e852245bae add mutex to openid 2024-02-08 23:29:10 +01:00
Lion
5f5af9b0a7 Add version command (#274)
closes #148
2024-02-07 21:51:41 +01:00
sla-ppy
64f2e08ed2 add the letter v 2024-02-07 21:46:18 +01:00
sla-ppy
707682f4a8 refactor version 2024-02-07 20:47:01 +01:00
sla-ppy
913674740d add version cmd 2024-02-07 16:07:22 +01:00
sla-ppy
1c575ff1bc add /bigobj 2024-02-07 16:06:19 +01:00
Lion
e72c217e63 Remove all unused platforms from FUNDING.yml 2024-01-25 12:14:25 +01:00
Lion
75bae8ee5a Create FUNDING.yml 2024-01-25 12:14:25 +01:00
Lion Kortlepel
acc2dd29e9 update lionkor/commandline to v2.4.2
this fixes stdin/stdout redirect on windows
2024-01-23 19:19:29 +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
33 changed files with 794 additions and 110 deletions

1
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1 @@
patreon: BeamMP

View File

@@ -1,7 +1,11 @@
name: Linux
on: [push]
on:
push:
branches:
- 'develop'
- 'minor'
pull_request:
env:
VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite"

View File

@@ -1,6 +1,11 @@
name: Windows
on: [push]
on:
push:
branches:
- 'develop'
- 'minor'
pull_request:
env:
VCPKG_DEFAULT_TRIPLET: x64-windows-static

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)
@@ -141,6 +147,7 @@ endif(UNIX)
if (WIN32)
add_compile_options("-D_WIN32_WINNT=0x0601")
add_compile_options("/bigobj")
endif(WIN32)
@@ -170,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 ()
@@ -194,6 +206,7 @@ target_compile_definitions(${PROJECT_NAME} PRIVATE ${PRJ_DEFINITIONS} ${PRJ_WARN
)
if(MSVC)
target_compile_options(${PROJECT_NAME} PUBLIC "/bigobj")
target_link_options(${PROJECT_NAME} PRIVATE "/SUBSYSTEM:CONSOLE")
endif(MSVC)
@@ -212,4 +225,3 @@ if(${PROJECT_NAME}_ENABLE_UNIT_TESTING)
target_link_options(${PROJECT_NAME}-tests PRIVATE "/SUBSYSTEM:CONSOLE")
endif(MSVC)
endif()

View File

@@ -53,11 +53,28 @@ Do **NOT** pull with merge. This is the default git behavior for `git pull`, but
The only acceptable merge commits are those which actually merge functionally different branches into each other, for example for merging one feature branch into another.
## Workflow
### Making an issue and fixing it
1. Create an issue detailing the feature or bug.
2. Assign a milestone to the issue, or wait for a maintainer to add a milestone to your issue.
3. Fork the repository and base your work on the branch mentioned in the milestone attached to your issue (e.g. `v3.0.0 (develop)` -> `develop`).
4. Program your feature or bug fix.
5. Open a PR that references the issue by number in the format: `#12345`.
6. Someone will review your PR and merge it, or ask for changes.
### Fixing an existing issue
1. Fork the repository and base your work on the branch mentioned in the milestone attached to your issue (e.g. `v3.0.0 (develop)` -> `develop`).
2. Program your feature or bug fix.
3. Open a PR that references the issue by number in the format: `#12345`.
4. Someone will review your PR and merge it, or ask for changes.
## Branches
### Which branch should I base my work on?
Each *feature* or *bug-fix* is implemented on a new Git branch, branched off of the branch it should be based on. The `master` branch is usually stable, so we don't do development on it. It is always a safe bet to branch off of `master`, but it may be more work to merge later. Branches to base your work on are usually branches like `rc-v3.3.0`, when the latest public version is `3.2.0`, for example. These can often be found in Pull-Requests on GitHub which are tagged `Release Candidate`.
- `minor`: Minor releases, like `v1.2.3` -> `v1.3.0` or `v1.2.3` -> `v1.2.4`.
- `develop`: Major releases, like `v1.2.3` -> `v2.0.0`, and larger feature/minor releases.
## Unit tests & CI/CD

8
include/ChronoWrapper.h Normal file
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);
}

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);

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(); }
};
@@ -106,8 +109,6 @@ public:
static std::vector<std::string> GetBackendUrlsInOrder() {
return {
"backend.beammp.com",
"backup1.beammp.com",
"backup2.beammp.com"
};
}
@@ -152,7 +153,7 @@ private:
static inline std::mutex mShutdownHandlersMutex {};
static inline std::deque<TShutdownHandler> mShutdownHandlers {};
static inline Version mVersion { 3, 2, 2 };
static inline Version mVersion { 3, 5, 0 };
};
void SplitString(std::string const& str, const char delim, std::vector<std::string>& out);
@@ -214,6 +215,10 @@ void RegisterThread(const std::string& str);
do { \
Application::Console().Write(_this_location + std::string("[LUA ERROR] ") + (x)); \
} while (false)
#define beammp_lua_log(level, plugin, x) \
do { \
Application::Console().Write(_this_location + fmt::format("[{}] [{}] ", plugin, level) + (x)); \
} while (false)
#define beammp_lua_warn(x) \
do { \
Application::Console().Write(_this_location + std::string("[LUA WARN] ") + (x)); \
@@ -269,6 +274,7 @@ void RegisterThread(const std::string& str);
#define beammp_tracef(...) beammp_trace(fmt::format(__VA_ARGS__))
#define beammp_lua_errorf(...) beammp_lua_error(fmt::format(__VA_ARGS__))
#define beammp_lua_warnf(...) beammp_lua_warn(fmt::format(__VA_ARGS__))
#define beammp_lua_log(level, plugin, x) /* x */
#endif // DOCTEST_CONFIG_DISABLE

View File

@@ -25,6 +25,8 @@ namespace Env {
enum class Key {
// provider settings
PROVIDER_UPDATE_MESSAGE,
PROVIDER_DISABLE_CONFIG,
PROVIDER_PORT_ENV,
};
std::optional<std::string> Get(Key key);

72
include/Profiling.h Normal file
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
include/RateLimiter.h Normal file
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);
};

View File

@@ -35,11 +35,11 @@ public:
[[nodiscard]] bool Failed() const { return mFailed; }
void FlushToFile();
void PrintDebug();
private:
void CreateConfigFile();
void ParseFromFile(std::string_view name);
void PrintDebug();
void TryReadValue(toml::value& Table, const std::string& Category, const std::string_view& Key, const std::string_view& Env, std::string& OutValue);
void TryReadValue(toml::value& Table, const std::string& Category, const std::string_view& Key, const std::string_view& Env, bool& OutValue);
void TryReadValue(toml::value& Table, const std::string& Category, const std::string_view& Key, const std::string_view& Env, int& OutValue);
@@ -48,5 +48,6 @@ private:
std::string TagsAsPrettyArray() const;
bool IsDefault();
bool mFailed { false };
bool mDisableConfig { false };
std::string mConfigFileName;
};

View File

@@ -58,6 +58,7 @@ private:
void Command_Status(const std::string& cmd, const std::vector<std::string>& args);
void Command_Settings(const std::string& cmd, const std::vector<std::string>& args);
void Command_Clear(const std::string&, const std::vector<std::string>& args);
void Command_Version(const std::string& cmd, const std::vector<std::string>& args);
void Command_Say(const std::string& FullCommand);
bool EnsureArgsCount(const std::vector<std::string>& args, size_t n);
@@ -75,6 +76,7 @@ private:
{ "settings", [this](const auto& a, const auto& b) { Command_Settings(a, b); } },
{ "clear", [this](const auto& a, const auto& b) { Command_Clear(a, b); } },
{ "say", [this](const auto&, const auto&) { Command_Say(""); } }, // shouldn't actually be called
{ "version", [this](const auto& a, const auto& b) { Command_Version(a, b); } },
};
std::unique_ptr<Commandline> mCommandline { nullptr };

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 {

View File

@@ -48,13 +48,13 @@ public:
private:
void UDPServerMain();
void TCPServerMain();
TServer& mServer;
TPPSMonitor& mPPSMonitor;
ip::udp::socket mUDPSock;
TResourceManager& mResourceManager;
std::thread mUDPThread;
std::thread mTCPThread;
std::mutex mOpenIDMutex;
std::vector<uint8_t> UDPRcvFromClient(ip::udp::endpoint& ClientEndpoint);
void HandleDownload(TConnection&& TCPSock);

27
src/ChronoWrapper.cpp Normal file
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);
}

View File

@@ -121,6 +121,14 @@ void TClient::SetCarData(int Ident, const std::string& Data) {
}
int TClient::GetCarCount() const {
// mVechileData holds both unicycle and cars which both count towards the maximum car count
// spawning a unicycle meant reaching the max, hence being unable to spawn car. this dirty fixes the problem for now.
std::unique_lock lock(mVehicleDataMutex);
for (auto& v : mVehicleData) {
if (v.ID() == mUnicycleID) {
return int(mVehicleData.size() - 1);
}
}
return int(mVehicleData.size());
}

View File

@@ -28,6 +28,7 @@
#include <regex>
#include <sstream>
#include <thread>
#include <chrono>
#include "Compat.h"
#include "CustomAssert.h"
@@ -222,7 +223,7 @@ void Application::CheckForUpdates() {
auto RemoteVersion = Version(VersionStrToInts(Response));
if (IsOutdated(MyVersion, RemoteVersion)) {
std::string RealVersionString = std::string("v") + RemoteVersion.AsString();
const std::string DefaultUpdateMsg = "NEW VERSION IS OUT! Please update to the new version ({}) of the BeamMP-Server! Download it here: https://beammp.com/! For a guide on how to update, visit: https://wiki.beammp.com/en/home/server-maintenance#updating-the-server";
const std::string DefaultUpdateMsg = "NEW VERSION IS OUT! Please update to the new version ({}) of the BeamMP-Server! Download it here: https://beammp.com/! For a guide on how to update, visit: https://docs.beammp.com/server/server-maintenance/#updating-the-server";
auto UpdateMsg = Env::Get(Env::Key::PROVIDER_UPDATE_MESSAGE).value_or(DefaultUpdateMsg);
UpdateMsg = fmt::vformat(std::string_view(UpdateMsg), fmt::make_format_args(RealVersionString));
beammp_warnf("{}{}{}", ANSI_YELLOW_BOLD, UpdateMsg, ANSI_RESET);
@@ -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));
}
}

View File

@@ -33,6 +33,12 @@ std::string_view Env::ToString(Env::Key key) {
case Key::PROVIDER_UPDATE_MESSAGE:
return "BEAMMP_PROVIDER_UPDATE_MESSAGE";
break;
case Key::PROVIDER_DISABLE_CONFIG:
return "BEAMMP_PROVIDER_DISABLE_CONFIG";
break;
case Key::PROVIDER_PORT_ENV:
return "BEAMMP_PROVIDER_PORT_ENV";
break;
}
return "";
}

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;

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
src/Profiling.cpp Normal file
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
src/RateLimiter.cpp Normal file
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);
};

View File

@@ -18,6 +18,7 @@
#include "Common.h"
#include "Env.h"
#include "TConfig.h"
#include <cstdlib>
#include <fstream>
@@ -50,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";
@@ -87,7 +91,9 @@ TEST_CASE("TConfig::TConfig") {
TConfig::TConfig(const std::string& ConfigFileName)
: mConfigFileName(ConfigFileName) {
Application::SetSubsystemStatus("Config", Application::Status::Starting);
if (!fs::exists(mConfigFileName) || !fs::is_regular_file(mConfigFileName)) {
auto DisableConfig = Env::Get(Env::Key::PROVIDER_DISABLE_CONFIG).value_or("false");
mDisableConfig = DisableConfig == "true" || DisableConfig == "1";
if (!mDisableConfig && (!fs::exists(mConfigFileName) || !fs::is_regular_file(mConfigFileName))) {
beammp_info("No config file found! Generating one...");
CreateConfigFile();
}
@@ -121,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;
@@ -137,13 +145,15 @@ 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;
SetComment(data["Misc"][StrSendErrorsMessageEnabled.data()].comments(), " You can turn on/off the SendErrors message you get on startup here");
std::stringstream Ss;
Ss << "# This is the BeamMP-Server config file.\n"
"# Help & Documentation: `https://wiki.beammp.com/en/home/server-maintenance`\n"
"# Help & Documentation: `https://docs.beammp.com/server/server-maintenance/`\n"
"# IMPORTANT: Fill in the AuthKey with the key you got from `https://keymaster.beammp.com/` on the left under \"Keys\"\n"
<< data;
auto File = std::fopen(mConfigFileName.c_str(), "w+");
@@ -161,7 +171,9 @@ void TConfig::FlushToFile() {
void TConfig::CreateConfigFile() {
// build from old config Server.cfg
if (mDisableConfig) {
return;
}
try {
if (fs::exists("Server.cfg")) {
// parse it (this is weird and bad and should be removed in some future version)
@@ -181,6 +193,9 @@ void TConfig::TryReadValue(toml::value& Table, const std::string& Category, cons
return;
}
}
if (mDisableConfig) {
return;
}
if (Table[Category.c_str()][Key.data()].is_string()) {
OutValue = Table[Category.c_str()][Key.data()].as_string();
}
@@ -194,6 +209,9 @@ void TConfig::TryReadValue(toml::value& Table, const std::string& Category, cons
return;
}
}
if (mDisableConfig) {
return;
}
if (Table[Category.c_str()][Key.data()].is_boolean()) {
OutValue = Table[Category.c_str()][Key.data()].as_boolean();
}
@@ -206,6 +224,9 @@ void TConfig::TryReadValue(toml::value& Table, const std::string& Category, cons
return;
}
}
if (mDisableConfig) {
return;
}
if (Table[Category.c_str()][Key.data()].is_integer()) {
OutValue = int(Table[Category.c_str()][Key.data()].as_integer());
}
@@ -213,11 +234,18 @@ void TConfig::TryReadValue(toml::value& Table, const std::string& Category, cons
void TConfig::ParseFromFile(std::string_view name) {
try {
toml::value data = toml::parse<toml::preserve_comments>(name.data());
toml::value data {};
if (!mDisableConfig) {
data = toml::parse<toml::preserve_comments>(name.data());
}
// GENERAL
TryReadValue(data, "General", StrDebug, EnvStrDebug, Application::Settings.DebugModeEnabled);
TryReadValue(data, "General", StrPrivate, EnvStrPrivate, Application::Settings.Private);
TryReadValue(data, "General", StrPort, EnvStrPort, Application::Settings.Port);
if (Env::Get(Env::Key::PROVIDER_PORT_ENV).has_value()) {
TryReadValue(data, "General", StrPort, Env::Get(Env::Key::PROVIDER_PORT_ENV).value(), Application::Settings.Port);
} else {
TryReadValue(data, "General", StrPort, EnvStrPort, Application::Settings.Port);
}
TryReadValue(data, "General", StrMaxCars, EnvStrMaxCars, Application::Settings.MaxCars);
TryReadValue(data, "General", StrMaxPlayers, EnvStrMaxPlayers, Application::Settings.MaxPlayers);
TryReadValue(data, "General", StrMap, EnvStrMap, Application::Settings.MapName);
@@ -227,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()));
@@ -238,13 +268,18 @@ void TConfig::ParseFromFile(std::string_view name) {
Application::SetSubsystemStatus("Config", Application::Status::Bad);
return;
}
PrintDebug();
// Update in any case
FlushToFile();
if (!mDisableConfig) {
FlushToFile();
}
// all good so far, let's check if there's a key
if (Application::Settings.Key.empty()) {
beammp_error("No AuthKey specified in the \"" + std::string(mConfigFileName) + "\" file. Please get an AuthKey, enter it into the config file, and restart this server.");
if (mDisableConfig) {
beammp_error("No AuthKey specified in the environment.");
} else {
beammp_error("No AuthKey specified in the \"" + std::string(mConfigFileName) + "\" file. Please get an AuthKey, enter it into the config file, and restart this server.");
}
Application::SetSubsystemStatus("Config", Application::Status::Bad);
mFailed = true;
return;
@@ -256,6 +291,9 @@ void TConfig::ParseFromFile(std::string_view name) {
}
void TConfig::PrintDebug() {
if (mDisableConfig) {
beammp_debug("Provider turned off the generation and parsing of the ServerConfig.toml");
}
beammp_debug(std::string(StrDebug) + ": " + std::string(Application::Settings.DebugModeEnabled ? "true" : "false"));
beammp_debug(std::string(StrPrivate) + ": " + std::string(Application::Settings.Private ? "true" : "false"));
beammp_debug(std::string(StrPort) + ": " + std::to_string(Application::Settings.Port));
@@ -266,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()) + "");

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) {
@@ -247,7 +249,8 @@ void TConsole::Command_Help(const std::string&, const std::vector<std::string>&
lua [state id] switches to lua, optionally into a specific state id's lua
settings [command] sets or gets settings for the server, run `settings help` for more info
status how the server is doing and what it's up to
clear clears the console window)";
clear clears the console window
version displays the server version)";
Application::Console().WriteRaw("BeamMP-Server Console: " + std::string(sHelpString));
}
@@ -267,6 +270,32 @@ void TConsole::Command_Clear(const std::string&, const std::vector<std::string>&
mCommandline->write("\x1b[;H\x1b[2J");
}
void TConsole::Command_Version(const std::string& cmd, const std::vector<std::string>& args) {
if (!EnsureArgsCount(args, 0)) {
return;
}
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) {
if (!EnsureArgsCount(args, 1, size_t(-1))) {
return;

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
<< "&guests=" << (Application::Settings.AllowGuests ? "true" : "false")
<< "&modlist=" << mResourceManager.TrimmedList()
<< "&modstotalsize=" << mResourceManager.MaxModSize()
<< "&modstotal=" << mResourceManager.ModsLoaded()

View File

@@ -18,14 +18,17 @@
#include "TLuaEngine.h"
#include "Client.h"
#include "Common.h"
#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>
@@ -63,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", "");
@@ -265,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) {
@@ -350,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);
}
@@ -359,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) {
@@ -430,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;
@@ -467,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;
});
@@ -481,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());
@@ -658,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();
@@ -829,6 +890,40 @@ TLuaEngine::StateThreadData::StateThreadData(const std::string& Name, TLuaStateI
MPTable.set_function("Set", &LuaAPI::MP::Set);
auto UtilTable = StateView.create_named_table("Util");
UtilTable.set_function("LogDebug", [this](sol::variadic_args args) {
std::string ToPrint = "";
for (const auto& arg : args) {
ToPrint += LuaAPI::LuaToString(static_cast<const sol::object>(arg));
ToPrint += "\t";
}
if (Application::Settings.DebugModeEnabled) {
beammp_lua_log("DEBUG", mStateId, ToPrint);
}
});
UtilTable.set_function("LogInfo", [this](sol::variadic_args args) {
std::string ToPrint = "";
for (const auto& arg : args) {
ToPrint += LuaAPI::LuaToString(static_cast<const sol::object>(arg));
ToPrint += "\t";
}
beammp_lua_log("INFO", mStateId, ToPrint);
});
UtilTable.set_function("LogWarn", [this](sol::variadic_args args) {
std::string ToPrint = "";
for (const auto& arg : args) {
ToPrint += LuaAPI::LuaToString(static_cast<const sol::object>(arg));
ToPrint += "\t";
}
beammp_lua_log("WARN", mStateId, ToPrint);
});
UtilTable.set_function("LogError", [this](sol::variadic_args args) {
std::string ToPrint = "";
for (const auto& arg : args) {
ToPrint += LuaAPI::LuaToString(static_cast<const sol::object>(arg));
ToPrint += "\t";
}
beammp_lua_log("ERROR", mStateId, ToPrint);
});
UtilTable.set_function("JsonEncode", &LuaAPI::MP::JsonEncode);
UtilTable.set_function("JsonDecode", [this](const std::string& str) {
return Lua_JsonDecode(str);
@@ -847,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) {
@@ -894,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) {
@@ -916,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;
@@ -984,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();
@@ -1001,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) {
@@ -1042,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);
}
}
}
@@ -1101,7 +1226,7 @@ void TLuaResult::MarkAsReady() {
void TLuaResult::WaitUntilReady() {
std::unique_lock readyLock(*this->ReadyMutex);
// wait if not ready yet
if(!this->Ready)
if (!this->Ready)
this->ReadyCondition->wait(readyLock);
}

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 {};
@@ -630,6 +649,7 @@ void TNetwork::OnDisconnect(const std::weak_ptr<TClient>& ClientPtr) {
}
int TNetwork::OpenID() {
std::unique_lock OpenIDLock(mOpenIDMutex);
int ID = 0;
bool found;
do {

View File

@@ -65,15 +65,18 @@ void TPPSMonitor::operator()() {
V += c->GetCarCount();
}
// kick on "no ping"
if (c->SecondsSinceLastPing() > (20 * 60)) {
beammp_debug("client " + std::string("(") + std::to_string(c->GetID()) + ")" + c->GetName() + " timing out: " + std::to_string(c->SecondsSinceLastPing()) + ", pps: " + Application::PPS());
if (c->SecondsSinceLastPing() > (20 * 60) ){
beammp_debugf("client {} ({}) timing out: {}", c->GetID(), c->GetName(), c->SecondsSinceLastPing());
TimedOutClients.push_back(c);
}
} else if (c->IsSynced() && c->SecondsSinceLastPing() > (1 * 60)) {
beammp_debugf("client {} ({}) timing out: {}", c->GetName(), c->GetID(), c->SecondsSinceLastPing());
TimedOutClients.push_back(c);
}
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) {

View File

@@ -30,6 +30,7 @@
#include "TResourceManager.h"
#include "TServer.h"
#include <cstdint>
#include <iostream>
#include <thread>
@@ -40,6 +41,9 @@ USAGE:
ARGUMENTS:
--help
Displays this help and exits.
--port=1234
Sets the server's listening TCP and
UDP port. Overrides ENV and ServerConfig.
--config=/path/to/ServerConfig.toml
Absolute or relative path to the
Server Config file, including the
@@ -91,6 +95,7 @@ int BeamMPServerMain(MainArguments Arguments) {
Parser.RegisterArgument({ "help" }, ArgsParser::NONE);
Parser.RegisterArgument({ "version" }, ArgsParser::NONE);
Parser.RegisterArgument({ "config" }, ArgsParser::HAS_VALUE);
Parser.RegisterArgument({ "port" }, ArgsParser::HAS_VALUE);
Parser.RegisterArgument({ "working-directory" }, ArgsParser::HAS_VALUE);
Parser.Parse(Arguments.List);
if (!Parser.Verify()) {
@@ -124,7 +129,7 @@ int BeamMPServerMain(MainArguments Arguments) {
}
}
}
TConfig Config(ConfigPath);
if (Config.Failed()) {
@@ -135,6 +140,24 @@ int BeamMPServerMain(MainArguments Arguments) {
}
return 1;
}
// override port if provided via arguments
if (Parser.FoundArgument({ "port" })) {
auto Port = Parser.GetValueOfArgument({ "port" });
if (Port.has_value()) {
auto P = int(std::strtoul(Port.value().c_str(), nullptr, 10));
if (P == 0 || P < 0 || P > UINT16_MAX) {
beammp_errorf("Custom port requested via --port is invalid: '{}'", Port.value());
return 1;
} else {
Application::Settings.Port = P;
beammp_info("Custom port requested via commandline arguments: " + Port.value());
}
}
}
Config.PrintDebug();
Application::InitializeConsole();
Application::Console().StartLoggingToFile();

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")

2
vcpkg

Submodule vcpkg updated: 8397227251...6978381401