Compare commits

..

1 Commits

52 changed files with 689 additions and 2540 deletions
+10 -11
View File
@@ -3,8 +3,8 @@ name: Linux
on: on:
push: push:
branches: branches:
- "develop" - 'develop'
- "minor" - 'minor'
pull_request: pull_request:
env: env:
@@ -20,9 +20,9 @@ jobs:
matrix: matrix:
include: include:
- distro: debian - distro: debian
version: 12 version: 11
- distro: debian - distro: debian
version: 13 version: 12
- distro: ubuntu - distro: ubuntu
version: 22.04 version: 22.04
- distro: ubuntu - distro: ubuntu
@@ -47,8 +47,7 @@ jobs:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
with: with:
submodules: "recursive" submodules: 'recursive'
fetch-depth: 0
- name: Git config safe directory - name: Git config safe directory
shell: bash shell: bash
@@ -85,16 +84,16 @@ jobs:
run: ./bin/BeamMP-Server-tests run: ./bin/BeamMP-Server-tests
arm64-matrix: arm64-matrix:
runs-on: ubuntu-22.04-arm runs-on: [Linux, ARM64]
env: env:
VCPKG_DEFAULT_TRIPLET: "arm64-linux" VCPKG_DEFAULT_TRIPLET: "arm64-linux"
strategy: strategy:
matrix: matrix:
include: include:
- distro: debian - distro: debian
version: 12 version: 11
- distro: debian - distro: debian
version: 13 version: 12
- distro: ubuntu - distro: ubuntu
version: 22.04 version: 22.04
- distro: ubuntu - distro: ubuntu
@@ -119,8 +118,7 @@ jobs:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
with: with:
submodules: "recursive" submodules: 'recursive'
fetch-depth: 0
- name: Git config safe directory - name: Git config safe directory
shell: bash shell: bash
@@ -155,3 +153,4 @@ jobs:
- name: Test - name: Test
run: ./bin/BeamMP-Server-tests run: ./bin/BeamMP-Server-tests
+9 -12
View File
@@ -3,7 +3,7 @@ on:
push: push:
# Sequence of patterns matched against refs/tags # Sequence of patterns matched against refs/tags
tags: tags:
- "v*" # Push events to matching v*, i.e. v1.0, v20.15.10 - 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10
env: env:
VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite" VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite"
@@ -39,9 +39,9 @@ jobs:
matrix: matrix:
include: include:
- distro: debian - distro: debian
version: 12 version: 11
- distro: debian - distro: debian
version: 13 version: 12
- distro: ubuntu - distro: ubuntu
version: 22.04 version: 22.04
- distro: ubuntu - distro: ubuntu
@@ -66,8 +66,7 @@ jobs:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
with: with:
submodules: "recursive" submodules: 'recursive'
fetch-depth: 0
- name: Git config safe directory - name: Git config safe directory
shell: bash shell: bash
@@ -105,15 +104,15 @@ jobs:
asset_content_type: application/x-elf asset_content_type: application/x-elf
arm64-matrix: arm64-matrix:
runs-on: ubuntu-22.04-arm runs-on: [Linux, ARM64]
needs: create-release needs: create-release
strategy: strategy:
matrix: matrix:
include: include:
- distro: debian - distro: debian
version: 12 version: 11
- distro: debian - distro: debian
version: 13 version: 12
- distro: ubuntu - distro: ubuntu
version: 22.04 version: 22.04
- distro: ubuntu - distro: ubuntu
@@ -141,8 +140,7 @@ jobs:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
with: with:
submodules: "recursive" submodules: 'recursive'
fetch-depth: 0
- name: Git config safe directory - name: Git config safe directory
shell: bash shell: bash
@@ -195,8 +193,7 @@ jobs:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
with: with:
submodules: "recursive" submodules: 'recursive'
fetch-depth: 0
- name: Create Build Environment - name: Create Build Environment
shell: bash shell: bash
-1
View File
@@ -26,7 +26,6 @@ jobs:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
with: with:
submodules: 'recursive' submodules: 'recursive'
fetch-depth: 0
- name: Setup vcpkg - name: Setup vcpkg
uses: lukka/run-vcpkg@v11 uses: lukka/run-vcpkg@v11
+2 -9
View File
@@ -8,7 +8,7 @@ include(cmake/Vcpkg.cmake) # needs to happen before project()
project( project(
"BeamMP-Server" # replace this "BeamMP-Server" # replace this
VERSION 3.9.3 VERSION 3.3.0
) )
include(cmake/StandardSettings.cmake) include(cmake/StandardSettings.cmake)
@@ -39,7 +39,6 @@ set(PRJ_HEADERS
include/TConfig.h include/TConfig.h
include/TConsole.h include/TConsole.h
include/THeartbeatThread.h include/THeartbeatThread.h
include/TIoPollThread.h
include/TLuaEngine.h include/TLuaEngine.h
include/TLuaPlugin.h include/TLuaPlugin.h
include/TNetwork.h include/TNetwork.h
@@ -53,8 +52,6 @@ set(PRJ_HEADERS
include/Settings.h include/Settings.h
include/Profiling.h include/Profiling.h
include/ChronoWrapper.h include/ChronoWrapper.h
include/TConnectionLimiter.h
include/TLuaResult.h
) )
# add all source files (.cpp) to this, except the one with main() # add all source files (.cpp) to this, except the one with main()
set(PRJ_SOURCES set(PRJ_SOURCES
@@ -68,7 +65,6 @@ set(PRJ_SOURCES
src/TConfig.cpp src/TConfig.cpp
src/TConsole.cpp src/TConsole.cpp
src/THeartbeatThread.cpp src/THeartbeatThread.cpp
src/TIoPollThread.cpp
src/TLuaEngine.cpp src/TLuaEngine.cpp
src/TLuaPlugin.cpp src/TLuaPlugin.cpp
src/TNetwork.cpp src/TNetwork.cpp
@@ -82,8 +78,6 @@ set(PRJ_SOURCES
src/Settings.cpp src/Settings.cpp
src/Profiling.cpp src/Profiling.cpp
src/ChronoWrapper.cpp src/ChronoWrapper.cpp
src/TConnectionLimiter.cpp
src/TLuaResult.cpp
) )
find_package(Lua REQUIRED) find_package(Lua REQUIRED)
@@ -154,8 +148,7 @@ if(UNIX)
endif(UNIX) endif(UNIX)
if (WIN32) if (WIN32)
add_compile_definitions(_WIN32_WINNT=0x0A00) add_compile_options("-D_WIN32_WINNT=0x0601")
add_compile_options("/bigobj") add_compile_options("/bigobj")
endif(WIN32) endif(WIN32)
-2
View File
@@ -15,5 +15,3 @@ if(NOT DEFINED CMAKE_TOOLCHAIN_FILE)
set(CMAKE_TOOLCHAIN_FILE ${CMAKE_SOURCE_DIR}/vcpkg/scripts/buildsystems/vcpkg.cmake) set(CMAKE_TOOLCHAIN_FILE ${CMAKE_SOURCE_DIR}/vcpkg/scripts/buildsystems/vcpkg.cmake)
endif() endif()
# ensure SOL2 safeties are all ON
add_compile_definitions(SOL_ALL_SAFETIES_ON=1)
+5 -21
View File
@@ -18,14 +18,12 @@
#pragma once #pragma once
#include <atomic>
#include <chrono> #include <chrono>
#include <memory> #include <memory>
#include <optional> #include <optional>
#include <queue> #include <queue>
#include <string> #include <string>
#include <unordered_set> #include <unordered_set>
#include <utility>
#include "BoostAliases.h" #include "BoostAliases.h"
#include "Common.h" #include "Common.h"
@@ -58,22 +56,19 @@ public:
~TClient(); ~TClient();
TClient& operator=(const TClient&) = delete; TClient& operator=(const TClient&) = delete;
void AddNewCar(int Ident, const nlohmann::json& Data); void AddNewCar(int Ident, const std::string& Data);
void SetCarData(int Ident, const nlohmann::json& Data); void SetCarData(int Ident, const std::string& Data);
void SetCarPosition(int Ident, const std::string& Data); void SetCarPosition(int Ident, const std::string& Data);
TVehicleDataLockPair GetAllCars(); TVehicleDataLockPair GetAllCars();
void SetName(const std::string& Name) { mName = Name; } void SetName(const std::string& Name) { mName = Name; }
void SetRoles(const std::string& Role) { mRole = Role; } void SetRoles(const std::string& Role) { mRole = Role; }
void SetIdentifier(const std::string& key, const std::string& value) { mIdentifiers[key] = value; } void SetIdentifier(const std::string& key, const std::string& value) { mIdentifiers[key] = value; }
nlohmann::json GetCarData(int Ident); std::string GetCarData(int Ident);
std::string GetCarPositionRaw(int Ident); std::string GetCarPositionRaw(int Ident);
void SetUDPAddr(const ip::udp::endpoint& Addr) { mUDPAddress = Addr; } void SetUDPAddr(const ip::udp::endpoint& Addr) { mUDPAddress = Addr; }
void SetTCPSock(ip::tcp::socket&& CSock) { mSocket = std::move(CSock); } void SetTCPSock(ip::tcp::socket&& CSock) { mSocket = std::move(CSock); }
// Returns true only for the thread that actually performs socket shutdown/close. void Disconnect(std::string_view Reason);
[[nodiscard]] bool Disconnect(std::string_view Reason); bool IsDisconnected() const { return !mSocket.is_open(); }
bool IsDisconnected() const {
return mDisconnectState.load(std::memory_order_acquire) != EDisconnectState::Connected;
}
// locks // locks
void DeleteCar(int Ident); void DeleteCar(int Ident);
[[nodiscard]] const std::unordered_map<std::string, std::string>& GetIdentifiers() const { return mIdentifiers; } [[nodiscard]] const std::unordered_map<std::string, std::string>& GetIdentifiers() const { return mIdentifiers; }
@@ -106,16 +101,8 @@ public:
[[nodiscard]] TServer& Server() const; [[nodiscard]] TServer& Server() const;
void UpdatePingTime(); void UpdatePingTime();
int SecondsSinceLastPing(); int SecondsSinceLastPing();
void SetMagic(std::vector<uint8_t> magic) { mMagic = std::move(magic); }
[[nodiscard]] const std::vector<uint8_t>& GetMagic() const { return mMagic; }
private: private:
enum class EDisconnectState {
Connected,
Disconnecting,
Disconnected
};
void InsertVehicle(int ID, const std::string& Data); void InsertVehicle(int ID, const std::string& Data);
TServer& mServer; TServer& mServer;
@@ -131,8 +118,6 @@ private:
TSetOfVehicleData mVehicleData; TSetOfVehicleData mVehicleData;
SparseArray<std::string> mVehiclePosition; SparseArray<std::string> mVehiclePosition;
std::string mName = "Unknown Client"; std::string mName = "Unknown Client";
// Once disconnect starts, this client is terminal and its socket must be treated as dead.
std::atomic<EDisconnectState> mDisconnectState { EDisconnectState::Connected };
ip::tcp::socket mSocket; ip::tcp::socket mSocket;
ip::udp::endpoint mUDPAddress {}; ip::udp::endpoint mUDPAddress {};
int mUnicycleID = -1; int mUnicycleID = -1;
@@ -140,7 +125,6 @@ private:
std::string mDID; std::string mDID;
int mID = -1; int mID = -1;
std::chrono::time_point<std::chrono::high_resolution_clock> mLastPingTime = std::chrono::high_resolution_clock::now(); std::chrono::time_point<std::chrono::high_resolution_clock> mLastPingTime = std::chrono::high_resolution_clock::now();
std::vector<uint8_t> mMagic;
}; };
std::optional<std::weak_ptr<TClient>> GetClient(class TServer& Server, int ID); std::optional<std::weak_ptr<TClient>> GetClient(class TServer& Server, int ID);
+2 -9
View File
@@ -74,7 +74,7 @@ public:
static TConsole& Console() { return mConsole; } static TConsole& Console() { return mConsole; }
static std::string ServerVersionString(); static std::string ServerVersionString();
static const Version& ServerVersion() { return mVersion; } static const Version& ServerVersion() { return mVersion; }
static Version ClientMinimumVersion() { return Version { 2, 7, 0 }; } static Version ClientMinimumVersion() { return Version { 2, 2, 0 }; }
static std::string PPS() { return mPPS; } static std::string PPS() { return mPPS; }
static void SetPPS(const std::string& NewPPS) { mPPS = NewPPS; } static void SetPPS(const std::string& NewPPS) { mPPS = NewPPS; }
@@ -86,8 +86,6 @@ public:
}; };
} }
static std::string GetServerCheckUrl() { return "https://check.beammp.com"; }
static std::string GetBackendUrlForAuth() { return "https://auth.beammp.com"; } static std::string GetBackendUrlForAuth() { return "https://auth.beammp.com"; }
static std::string GetBackendUrlForSocketIO() { return "https://backend.beammp.com"; } static std::string GetBackendUrlForSocketIO() { return "https://backend.beammp.com"; }
static void CheckForUpdates(); static void CheckForUpdates();
@@ -129,15 +127,10 @@ private:
static inline std::mutex mShutdownHandlersMutex {}; static inline std::mutex mShutdownHandlersMutex {};
static inline std::deque<TShutdownHandler> mShutdownHandlers {}; static inline std::deque<TShutdownHandler> mShutdownHandlers {};
static inline Version mVersion { 3, 9, 3 }; static inline Version mVersion { 3, 8, 1 };
}; };
/// Used to static_assert in std::visit
template<class>
inline constexpr bool AlwaysFalseV = false;
void SplitString(std::string const& str, const char delim, std::vector<std::string>& out); void SplitString(std::string const& str, const char delim, std::vector<std::string>& out);
std::string LowerString(std::string str);
std::string ThreadName(bool DebugModeOverride = false); std::string ThreadName(bool DebugModeOverride = false);
void RegisterThread(const std::string& str); void RegisterThread(const std::string& str);
-2
View File
@@ -26,9 +26,7 @@ enum class Key {
// provider settings // provider settings
PROVIDER_UPDATE_MESSAGE, PROVIDER_UPDATE_MESSAGE,
PROVIDER_DISABLE_CONFIG, PROVIDER_DISABLE_CONFIG,
PROVIDER_DISABLE_MP_SET,
PROVIDER_PORT_ENV, PROVIDER_PORT_ENV,
PROVIDER_IP_ENV
}; };
std::optional<std::string> Get(Key key); std::optional<std::string> Get(Key key);
+1 -2
View File
@@ -34,9 +34,8 @@ namespace MP {
std::pair<bool, std::string> TriggerClientEventJson(int PlayerID, const std::string& EventName, const sol::table& Data); std::pair<bool, std::string> TriggerClientEventJson(int PlayerID, const std::string& EventName, const sol::table& Data);
inline size_t GetPlayerCount() { return Engine->Server().ClientCount(); } inline size_t GetPlayerCount() { return Engine->Server().ClientCount(); }
std::pair<bool, std::string> DropPlayer(int ID, std::optional<std::string> MaybeReason); std::pair<bool, std::string> DropPlayer(int ID, std::optional<std::string> MaybeReason);
std::pair<bool, std::string> SendChatMessage(int ID, const std::string& Message, const bool& LogChat = true); std::pair<bool, std::string> SendChatMessage(int ID, const std::string& Message);
std::pair<bool, std::string> SendNotification(int ID, const std::string& Message, const std::string& Icon, const std::string& Category); std::pair<bool, std::string> SendNotification(int ID, const std::string& Message, const std::string& Icon, const std::string& Category);
std::pair<bool, std::string> ConfirmationDialog(int ID, const std::string& Title, const std::string& Body, const sol::table& buttons, const std::string& InteractionID, const bool& warning = false, const bool& reportToServer = true, const bool& reportToExtensions = true);
std::pair<bool, std::string> RemoveVehicle(int PlayerID, int VehicleID); std::pair<bool, std::string> RemoveVehicle(int PlayerID, int VehicleID);
void Set(int ConfigID, sol::object NewValue); void Set(int ConfigID, sol::object NewValue);
TLuaValue Get(int ConfigID); TLuaValue Get(int ConfigID);
-1
View File
@@ -79,7 +79,6 @@ struct Settings {
General_Map, General_Map,
General_AuthKey, General_AuthKey,
General_Private, General_Private,
General_IP,
General_Port, General_Port,
General_MaxCars, General_MaxCars,
General_LogChat, General_LogChat,
-90
View File
@@ -1,90 +0,0 @@
// BeamMP, the BeamNG.drive multiplayer mod.
// Copyright (C) 2026 BeamMP Ltd., BeamMP team and contributors.
//
// BeamMP Ltd. can be contacted by electronic mail via contact@beammp.com.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#pragma once
#include <mutex>
#include <optional>
#include <string>
#include <unordered_map>
#include <utility>
class TConnectionLimiter {
public:
struct TStats {
size_t CurrentGlobal = 0;
size_t MaxGlobal = 0;
size_t ActiveIpBuckets = 0;
size_t CurrentMaxPerIp = 0;
size_t MaxPerIp = 0;
size_t SaturatedIpBuckets = 0;
};
class TGuard {
public:
TGuard() = default;
TGuard(TConnectionLimiter* owner, std::string ip);
TGuard(const TGuard&) = delete;
TGuard& operator=(const TGuard&) = delete;
~TGuard() { Release(); }
// not threadsafe
TGuard(TGuard&& other) noexcept { *this = std::move(other); }
// not threadsafe
TGuard& operator=(TGuard&& other) noexcept;
private:
friend class TConnectionLimiter;
// not threadsafe
void Release();
TConnectionLimiter* mOwner { nullptr };
std::string mIp;
};
TConnectionLimiter(size_t maxPerIp, size_t maxGlobal);
[[nodiscard]] std::optional<TGuard> TryAcquire(const std::string& ip);
[[nodiscard]] TStats GetStats();
private:
void Release(const std::string& ip) {
std::unique_lock Lock { mMutex };
auto It = mPerIp.find(ip);
if (It != mPerIp.end()) {
// this guard exists to avoid underflow in case something goes wrong and this gets called too many times
if (It->second > 0)
--It->second;
if (It->second == 0)
mPerIp.erase(It);
}
// this guard exists to avoid underflow in case something goes wrong and this gets called too many times
if (mGlobal > 0)
--mGlobal;
}
const size_t mMaxPerIp;
const size_t mMaxGlobal;
std::mutex mMutex { };
std::unordered_map<std::string, size_t> mPerIp { };
size_t mGlobal = 0;
};
-6
View File
@@ -59,9 +59,6 @@ private:
void Command_Settings(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_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_Version(const std::string& cmd, const std::vector<std::string>& args);
void Command_ProtectMod(const std::string& cmd, const std::vector<std::string>& args);
void Command_ReloadMods(const std::string& cmd, const std::vector<std::string>& args);
void Command_NetTest(const std::string& cmd, const std::vector<std::string>& args);
void Command_Say(const std::string& FullCommand); void Command_Say(const std::string& FullCommand);
bool EnsureArgsCount(const std::vector<std::string>& args, size_t n); bool EnsureArgsCount(const std::vector<std::string>& args, size_t n);
@@ -80,9 +77,6 @@ private:
{ "clear", [this](const auto& a, const auto& b) { Command_Clear(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 { "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); } }, { "version", [this](const auto& a, const auto& b) { Command_Version(a, b); } },
{ "protectmod", [this](const auto& a, const auto& b) { Command_ProtectMod(a, b); } },
{ "reloadmods", [this](const auto& a, const auto& b) { Command_ReloadMods(a, b); } },
{ "nettest", [this](const auto& a, const auto& b) { Command_NetTest(a, b); } },
}; };
std::unique_ptr<Commandline> mCommandline { nullptr }; std::unique_ptr<Commandline> mCommandline { nullptr };
-36
View File
@@ -1,36 +0,0 @@
// BeamMP, the BeamNG.drive multiplayer mod.
// Copyright (C) 2024 BeamMP Ltd., BeamMP team and contributors.
//
// BeamMP Ltd. can be contacted by electronic mail via contact@beammp.com.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#pragma once
#include <boost/asio/io_context.hpp>
#include <boost/asio/executor_work_guard.hpp>
#include <thread>
class TIoPollThread {
public:
TIoPollThread();
~TIoPollThread();
boost::asio::io_context& IoCtx() noexcept { return mIoCtx; }
private:
boost::asio::io_context mIoCtx;
boost::asio::executor_work_guard<boost::asio::io_context::executor_type> mWorkGuard;
std::jthread mThread;
};
+38 -11
View File
@@ -19,12 +19,13 @@
#pragma once #pragma once
#include "Profiling.h" #include "Profiling.h"
#include "TLuaResult.h"
#include "TNetwork.h" #include "TNetwork.h"
#include "TServer.h" #include "TServer.h"
#include <any>
#include <chrono> #include <chrono>
#include <condition_variable> #include <condition_variable>
#include <filesystem> #include <filesystem>
#include <initializer_list>
#include <list> #include <list>
#include <lua.hpp> #include <lua.hpp>
#include <memory> #include <memory>
@@ -33,8 +34,6 @@
#include <queue> #include <queue>
#include <random> #include <random>
#include <set> #include <set>
#include <sol/forward.hpp>
#include <sol/protected_function_result.hpp>
#include <toml.hpp> #include <toml.hpp>
#include <unordered_map> #include <unordered_map>
#include <vector> #include <vector>
@@ -59,10 +58,36 @@ namespace fs = std::filesystem;
/** /**
* std::variant means, that TLuaArgTypes may be one of the Types listed as template args * std::variant means, that TLuaArgTypes may be one of the Types listed as template args
*/ */
using TLuaValue = std::variant<std::monostate, std::string, int, JsonString, bool, std::unordered_map<std::string, std::string>, float>; 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; class TLuaPlugin;
struct TLuaResult {
bool Ready;
bool Error;
std::string ErrorMessage;
sol::object Result { sol::lua_nil };
TLuaStateId StateId;
std::string Function;
std::shared_ptr<std::mutex> ReadyMutex {
std::make_shared<std::mutex>()
};
std::shared_ptr<std::condition_variable> ReadyCondition {
std::make_shared<std::condition_variable>()
};
void MarkAsReady();
void WaitUntilReady();
};
struct TLuaPluginConfig { struct TLuaPluginConfig {
static inline const std::string FileName = "PluginConfig.toml"; static inline const std::string FileName = "PluginConfig.toml";
TLuaStateId StateId; TLuaStateId StateId;
@@ -141,7 +166,7 @@ public:
const std::optional<std::chrono::high_resolution_clock::duration>& Max = std::nullopt); const std::optional<std::chrono::high_resolution_clock::duration>& Max = std::nullopt);
void ReportErrors(const std::vector<std::shared_ptr<TLuaResult>>& Results); void ReportErrors(const std::vector<std::shared_ptr<TLuaResult>>& Results);
bool HasState(TLuaStateId StateId); bool HasState(TLuaStateId StateId);
[[nodiscard]] std::shared_ptr<TLuaVoidResult> EnqueueScript(TLuaStateId StateID, const TLuaChunk& Script); [[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<TLuaValue>& Args, const std::string& EventName); [[nodiscard]] std::shared_ptr<TLuaResult> EnqueueFunctionCall(TLuaStateId StateID, const std::string& FunctionName, const std::vector<TLuaValue>& Args, const std::string& EventName);
void EnsureStateExists(TLuaStateId StateId, const std::string& Name, bool DontCallOnInit = false); void EnsureStateExists(TLuaStateId StateId, const std::string& Name, bool DontCallOnInit = false);
void RegisterEvent(const std::string& EventName, TLuaStateId StateId, const std::string& FunctionName); void RegisterEvent(const std::string& EventName, TLuaStateId StateId, const std::string& FunctionName);
@@ -167,7 +192,9 @@ public:
for (const auto& Event : mLuaEvents.at(EventName)) { for (const auto& Event : mLuaEvents.at(EventName)) {
for (const auto& Function : Event.second) { for (const auto& Function : Event.second) {
if (Event.first != IgnoreId) { if (Event.first != IgnoreId) {
Results.push_back(EnqueueFunctionCall(Event.first, Function, Arguments, EventName)); auto Result = EnqueueFunctionCall(Event.first, Function, Arguments, EventName);
Results.push_back(Result);
AddResultToCheck(Result);
} }
} }
} }
@@ -202,9 +229,9 @@ public:
// Debugging functions (slow) // Debugging functions (slow)
std::unordered_map<std::string /*event name */, std::vector<std::string> /* handlers */> Debug_GetEventsForState(TLuaStateId StateId); std::unordered_map<std::string /*event name */, std::vector<std::string> /* handlers */> Debug_GetEventsForState(TLuaStateId StateId);
std::queue<std::pair<TLuaChunk, std::shared_ptr<TLuaVoidResult>>> Debug_GetStateExecuteQueueForState(TLuaStateId StateId); std::queue<std::pair<TLuaChunk, std::shared_ptr<TLuaResult>>> Debug_GetStateExecuteQueueForState(TLuaStateId StateId);
std::vector<QueuedFunction> Debug_GetStateFunctionQueueForState(TLuaStateId StateId); std::vector<QueuedFunction> Debug_GetStateFunctionQueueForState(TLuaStateId StateId);
std::vector<TLuaResult::DetachedSnapshot> Debug_GetResultsToCheckForState(TLuaStateId StateId); std::vector<TLuaResult> Debug_GetResultsToCheckForState(TLuaStateId StateId);
private: private:
void CollectAndInitPlugins(); void CollectAndInitPlugins();
@@ -217,7 +244,7 @@ private:
StateThreadData(const std::string& Name, TLuaStateId StateId, TLuaEngine& Engine); StateThreadData(const std::string& Name, TLuaStateId StateId, TLuaEngine& Engine);
StateThreadData(const StateThreadData&) = delete; StateThreadData(const StateThreadData&) = delete;
virtual ~StateThreadData() noexcept { beammp_debug("\"" + mStateId + "\" destroyed"); } virtual ~StateThreadData() noexcept { beammp_debug("\"" + mStateId + "\" destroyed"); }
[[nodiscard]] std::shared_ptr<TLuaVoidResult> EnqueueScript(const TLuaChunk& Script); [[nodiscard]] std::shared_ptr<TLuaResult> EnqueueScript(const TLuaChunk& Script);
[[nodiscard]] std::shared_ptr<TLuaResult> EnqueueFunctionCall(const std::string& FunctionName, const std::vector<TLuaValue>& Args, const std::string& EventName); [[nodiscard]] std::shared_ptr<TLuaResult> EnqueueFunctionCall(const std::string& FunctionName, const std::vector<TLuaValue>& Args, const std::string& EventName);
[[nodiscard]] std::shared_ptr<TLuaResult> EnqueueFunctionCallFromCustomEvent(const std::string& FunctionName, const std::vector<TLuaValue>& Args, const std::string& EventName, CallStrategy Strategy); [[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 RegisterEvent(const std::string& EventName, const std::string& FunctionName);
@@ -229,7 +256,7 @@ private:
std::vector<std::string> GetStateTableKeys(const std::vector<std::string>& keys); std::vector<std::string> GetStateTableKeys(const std::vector<std::string>& keys);
// Debug functions, slow // Debug functions, slow
std::queue<std::pair<TLuaChunk, std::shared_ptr<TLuaVoidResult>>> Debug_GetStateExecuteQueue(); std::queue<std::pair<TLuaChunk, std::shared_ptr<TLuaResult>>> Debug_GetStateExecuteQueue();
std::vector<TLuaEngine::QueuedFunction> Debug_GetStateFunctionQueue(); std::vector<TLuaEngine::QueuedFunction> Debug_GetStateFunctionQueue();
private: private:
@@ -254,7 +281,7 @@ private:
TLuaStateId mStateId; TLuaStateId mStateId;
lua_State* mState; lua_State* mState;
std::thread mThread; std::thread mThread;
std::queue<std::pair<TLuaChunk, std::shared_ptr<TLuaVoidResult>>> mStateExecuteQueue; std::queue<std::pair<TLuaChunk, std::shared_ptr<TLuaResult>>> mStateExecuteQueue;
std::recursive_mutex mStateExecuteQueueMutex; std::recursive_mutex mStateExecuteQueueMutex;
std::vector<QueuedFunction> mStateFunctionQueue; std::vector<QueuedFunction> mStateFunctionQueue;
std::mutex mStateFunctionQueueMutex; std::mutex mStateFunctionQueueMutex;
-146
View File
@@ -1,146 +0,0 @@
// BeamMP, the BeamNG.drive multiplayer mod.
// Copyright (C) 2026 BeamMP Ltd., BeamMP team and contributors.
//
// BeamMP Ltd. can be contacted by electronic mail via contact@beammp.com.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#pragma once
#include "Common.h"
#include <condition_variable>
#include <string>
#include <memory>
#include <sol/sol.hpp>
using TLuaStateId = std::string;
struct TDetachedLuaValue {
using Ptr = std::shared_ptr<TDetachedLuaValue>;
using Array = std::vector<TDetachedLuaValue>;
// This weird Ptr indirection is needed because some implementations of libstc++,
// like the GCC 11 one shipped with ubuntu 22.04, need to know the size of the second
// member of the pairs that make up the elements of such a map. It's fine in vector.
using Object = std::unordered_map<std::string, TDetachedLuaValue::Ptr>;
std::variant<std::monostate, bool, double, int, std::string, Array, Object> V;
};
std::ostream& operator<<(std::ostream& os, const TDetachedLuaValue& value);
/// Result for operations that stay within one Lua state (e.g. script loads).
/// This type intentionally does not store any Lua references or serialized Lua values.
struct TLuaVoidResult {
struct Snapshot {
bool Error;
bool Ready;
std::string ErrorMessage;
TLuaStateId StateId;
};
explicit TLuaVoidResult(TLuaStateId StateId)
: mStateId(std::move(StateId)) {}
/// Marks this result as success & sets it as ready, waking all waiting threads.
void MarkReadySuccess();
/// Marks this result as erroneous & sets it as ready, waking all waiting threads.
void MarkReadyError(sol::protected_function_result Res);
/// Marks this result as erroneous & sets it as ready, waking all waiting threads.
void MarkReadyError(std::string Res);
/// Wait (suspend) until this result is ready.
void WaitUntilReady();
bool IsReady() const;
bool IsError() const;
Snapshot GetSnapshot() const;
private:
mutable std::mutex mMutex {};
bool mReady { false };
bool mError { false };
std::string mErrorMessage;
TLuaStateId mStateId;
std::condition_variable mReadyCondition {};
void SetErrorMessageFromResult(sol::protected_function_result& Res) {
if (Res.valid()) {
beammp_lua_errorf("Error was not an error");
}
if (Res.get_type() == sol::type::string) {
mErrorMessage = Res.get<sol::error>().what();
} else {
mErrorMessage = "(unknown error; error object is not inspectable)";
}
}
void MarkAsReady();
};
struct TLuaResult {
struct DetachedSnapshot {
bool Error;
bool Ready;
std::string ErrorMessage;
/// Serialized Lua result. Has no reference to the Lua state, and is safe to use
/// from any thread that acquires it.
TDetachedLuaValue Result;
TLuaStateId StateId;
std::string Function;
};
TLuaResult(TLuaStateId StateId, std::string FunctionName) : mStateId(StateId), mFunction(std::move(FunctionName)) {}
/// Marks this result as success & sets it as ready, waking all threads waiting on it.
void MarkReadySuccess(sol::object Res);
/// Marks this result as successful and ready without serializing a Lua value.
void MarkReadySuccessNoResult();
/// Marks this result as erroneous & sets it as ready, waking all threads waiting on it.
void MarkReadyError(sol::protected_function_result Res);
/// Marks this result as erroneous & sets it as ready, waking all threads waiting on it.
void MarkReadyError(std::string Res);
/// Wait (suspend) until this result is ready. Use `GetDetachedSnapshot` to get the result.
void WaitUntilReady();
bool IsReady() const;
bool IsError() const;
TLuaStateId OwnerState() const;
void SetOwnerState(TLuaStateId StateId);
/// Returns a snapshot with, if not an error, a serialized version of the result
/// object. This never stores or returns raw Lua references and is safe to use
/// across threads.
DetachedSnapshot GetDetachedSnapshot() const;
private:
mutable std::mutex mMutex {};
bool mReady { false };
bool mError { false };
std::string mErrorMessage;
TDetachedLuaValue mDetachedResult;
TLuaStateId mStateId;
std::string mFunction;
std::condition_variable mReadyCondition {};
TDetachedLuaValue Freeze(const sol::object& o, int depth = 0);
void SetErrorMessageFromResult(sol::protected_function_result& Res) {
if (Res.valid()) {
beammp_lua_errorf("Error was not an error");
}
if (Res.get_type() == sol::type::string) {
mErrorMessage = Res.get<sol::error>().what();
} else {
mErrorMessage = "(unknown error; error object is not inspectable)";
}
}
void MarkAsReady();
};
+9 -17
View File
@@ -20,9 +20,9 @@
#include "BoostAliases.h" #include "BoostAliases.h"
#include "Compat.h" #include "Compat.h"
#include "TConnectionLimiter.h"
#include "TResourceManager.h" #include "TResourceManager.h"
#include "TServer.h" #include "TServer.h"
#include <boost/asio/io_context.hpp>
#include <boost/asio/ip/udp.hpp> #include <boost/asio/ip/udp.hpp>
struct TConnection; struct TConnection;
@@ -34,23 +34,16 @@ public:
[[nodiscard]] bool TCPSend(TClient& c, const std::vector<uint8_t>& Data, bool IsSync = false); [[nodiscard]] bool TCPSend(TClient& c, const std::vector<uint8_t>& Data, bool IsSync = false);
[[nodiscard]] bool SendLarge(TClient& c, std::vector<uint8_t> Data, bool isSync = false); [[nodiscard]] bool SendLarge(TClient& c, std::vector<uint8_t> Data, bool isSync = false);
[[nodiscard]] bool Respond(TClient& c, const std::vector<uint8_t>& MSG, bool Rel, bool isSync = false); [[nodiscard]] bool Respond(TClient& c, const std::vector<uint8_t>& MSG, bool Rel, bool isSync = false);
std::shared_ptr<TClient> CreateClient(boost::asio::ip::tcp::socket&& TCPSock); std::shared_ptr<TClient> CreateClient(ip::tcp::socket&& TCPSock);
std::vector<uint8_t> TCPRcv(TClient& c, bool WithTimeout = false); std::vector<uint8_t> TCPRcv(TClient& c);
void ClientKick(TClient& c, const std::string& R); void ClientKick(TClient& c, const std::string& R);
void DisconnectClient(const std::weak_ptr<TClient>& c, const std::string& R);
void DisconnectClient(TClient& c, const std::string& R);
[[nodiscard]] bool SyncClient(const std::weak_ptr<TClient>& c); [[nodiscard]] bool SyncClient(const std::weak_ptr<TClient>& c);
void Identify(TConnection&& client, TConnectionLimiter::TGuard&&); void Identify(TConnection&& client);
std::shared_ptr<TClient> Authentication(TConnection&& ClientConnection); std::shared_ptr<TClient> Authentication(TConnection&& ClientConnection);
void SyncResources(TClient& c); void SyncResources(TClient& c);
[[nodiscard]] bool UDPSend(TClient& Client, std::vector<uint8_t> Data); [[nodiscard]] bool UDPSend(TClient& Client, std::vector<uint8_t> Data);
void SendToAll(TClient* c, const std::vector<uint8_t>& Data, bool Self, bool Rel); void SendToAll(TClient* c, const std::vector<uint8_t>& Data, bool Self, bool Rel);
void UpdatePlayer(TClient& Client); void UpdatePlayer(TClient& Client);
boost::system::error_code ReadWithTimeout(boost::asio::ip::tcp::socket& Socket, void* Buf, size_t Len, std::chrono::steady_clock::duration Timeout);
boost::system::error_code ReadWithTimeout(TConnection& Connection, void* Buf, size_t Len, std::chrono::steady_clock::duration Timeout);
[[nodiscard]] TConnectionLimiter::TStats GetConnectionLimiterStats() { return mConnectionLimiter.GetStats(); }
TResourceManager& ResourceManager() const { return mResourceManager; }
private: private:
void UDPServerMain(); void UDPServerMain();
@@ -58,14 +51,13 @@ private:
TServer& mServer; TServer& mServer;
TPPSMonitor& mPPSMonitor; TPPSMonitor& mPPSMonitor;
boost::asio::ip::udp::socket mUDPSock; ip::udp::socket mUDPSock;
TResourceManager& mResourceManager; TResourceManager& mResourceManager;
std::thread mUDPThread; std::thread mUDPThread;
std::thread mTCPThread; std::thread mTCPThread;
std::mutex mOpenIDMutex; std::mutex mOpenIDMutex;
TConnectionLimiter mConnectionLimiter;
std::vector<uint8_t> UDPRcvFromClient(boost::asio::ip::udp::endpoint& ClientEndpoint); std::vector<uint8_t> UDPRcvFromClient(ip::udp::endpoint& ClientEndpoint);
void OnConnect(const std::weak_ptr<TClient>& c); void OnConnect(const std::weak_ptr<TClient>& c);
void TCPClient(const std::weak_ptr<TClient>& c); void TCPClient(const std::weak_ptr<TClient>& c);
void Looper(const std::weak_ptr<TClient>& c); void Looper(const std::weak_ptr<TClient>& c);
@@ -73,9 +65,9 @@ private:
void OnDisconnect(const std::weak_ptr<TClient>& ClientPtr); void OnDisconnect(const std::weak_ptr<TClient>& ClientPtr);
void Parse(TClient& c, const std::vector<uint8_t>& Packet); void Parse(TClient& c, const std::vector<uint8_t>& Packet);
void SendFile(TClient& c, const std::string& Name); void SendFile(TClient& c, const std::string& Name);
static bool TCPSendRaw(TClient& C, boost::asio::ip::tcp::socket& socket, const uint8_t* Data, size_t Size); static bool TCPSendRaw(TClient& C, ip::tcp::socket& socket, const uint8_t* Data, size_t Size);
void SendFileToClient(TClient& c, size_t Size, const std::string& Name); static void SendFileToClient(TClient& c, size_t Size, const std::string& Name);
static const uint8_t* SendSplit(TClient& c, boost::asio::ip::tcp::socket& Socket, const uint8_t* DataPtr, size_t Size); static const uint8_t* SendSplit(TClient& c, ip::tcp::socket& Socket, const uint8_t* DataPtr, size_t Size);
}; };
std::string HashPassword(const std::string& str); std::string HashPassword(const std::string& str);
+2 -2
View File
@@ -30,10 +30,10 @@ public:
[[nodiscard]] std::string TrimmedList() const { return mTrimmedList; } [[nodiscard]] std::string TrimmedList() const { return mTrimmedList; }
[[nodiscard]] std::string FileSizes() const { return mFileSizes; } [[nodiscard]] std::string FileSizes() const { return mFileSizes; }
[[nodiscard]] int ModsLoaded() const { return mModsLoaded; } [[nodiscard]] int ModsLoaded() const { return mModsLoaded; }
[[nodiscard]] nlohmann::json GetMods() const { return mMods; }
[[nodiscard]] std::string NewFileList() const;
void RefreshFiles(); void RefreshFiles();
void SetProtected(const std::string& ModName, bool Protected);
private: private:
size_t mMaxModSize = 0; size_t mMaxModSize = 0;
+4 -4
View File
@@ -20,7 +20,6 @@
#include "IThreaded.h" #include "IThreaded.h"
#include "RWMutex.h" #include "RWMutex.h"
#include "TIoPollThread.h"
#include "TScopedTimer.h" #include "TScopedTimer.h"
#include <functional> #include <functional>
#include <memory> #include <memory>
@@ -45,16 +44,17 @@ public:
void ForEachClient(const std::function<bool(std::weak_ptr<TClient>)>& Fn); void ForEachClient(const std::function<bool(std::weak_ptr<TClient>)>& Fn);
size_t ClientCount() const; size_t ClientCount() const;
void GlobalParser(const std::weak_ptr<TClient>& Client, std::vector<uint8_t>&& Packet, TPPSMonitor& PPSMonitor, TNetwork& Network, bool udp); void GlobalParser(const std::weak_ptr<TClient>& Client, std::vector<uint8_t>&& Packet, TPPSMonitor& PPSMonitor, TNetwork& Network);
static void HandleEvent(TClient& c, const std::string& Data); static void HandleEvent(TClient& c, const std::string& Data);
RWMutex& GetClientMutex() const { return mClientsMutex; } RWMutex& GetClientMutex() const { return mClientsMutex; }
const TScopedTimer UptimeTimer; const TScopedTimer UptimeTimer;
io_context& IoCtx() { return mIoCtxPoller.IoCtx(); } // asio io context
io_context& IoCtx() { return mIoCtx; }
private: private:
TIoPollThread mIoCtxPoller; io_context mIoCtx {};
TClientSet mClients; TClientSet mClients;
mutable RWMutex mClientsMutex; mutable RWMutex mClientsMutex;
static void ParseVehicle(TClient& c, const std::string& Pckt, TNetwork& Network); static void ParseVehicle(TClient& c, const std::string& Pckt, TNetwork& Network);
+4 -7
View File
@@ -18,12 +18,11 @@
#pragma once #pragma once
#include <nlohmann/json.hpp>
#include <string> #include <string>
class TVehicleData final { class TVehicleData final {
public: public:
TVehicleData(int ID, nlohmann::json Data); TVehicleData(int ID, std::string Data);
~TVehicleData(); ~TVehicleData();
// We cannot delete this, since vector needs to be able to copy when it resizes. // We cannot delete this, since vector needs to be able to copy when it resizes.
// Deleting this causes some wacky template errors which are hard to decipher, // Deleting this causes some wacky template errors which are hard to decipher,
@@ -33,16 +32,14 @@ public:
[[nodiscard]] bool IsInvalid() const { return mID == -1; } [[nodiscard]] bool IsInvalid() const { return mID == -1; }
[[nodiscard]] int ID() const { return mID; } [[nodiscard]] int ID() const { return mID; }
[[nodiscard]] nlohmann::json Data() const { return mData; } [[nodiscard]] std::string Data() const { return mData; }
[[nodiscard]] std::string DataAsPacket(const std::string& Role, const std::string& Name, int ID) const; void SetData(const std::string& Data) { mData = Data; }
void SetData(const nlohmann::json& Data) { mData = Data; }
bool operator==(const TVehicleData& v) const { return mID == v.mID; } bool operator==(const TVehicleData& v) const { return mID == v.mID; }
private: private:
int mID { -1 }; int mID { -1 };
nlohmann::json mData; std::string mData;
}; };
// TODO: unused now, remove? // TODO: unused now, remove?
+1 -1
View File
@@ -4,4 +4,4 @@ set -ex
./vcpkg/bootstrap-vcpkg.sh ./vcpkg/bootstrap-vcpkg.sh
cmake . -B bin $1 -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_FLAGS="-O3 -g -Wl,-z,norelro -Wl,--hash-style=gnu -Wl,-z,noseparate-code -ffunction-sections -fdata-sections -Wl,--gc-sections -Wl,--export-dynamic" -DBeamMP-Server_ENABLE_LTO=ON cmake . -B bin $1 -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_FLAGS="-O3 -g -Wl,-z,norelro -Wl,--hash-style=gnu -Wl,-z,noseparate-code -ffunction-sections -fdata-sections -Wl,--gc-sections" -DBeamMP-Server_ENABLE_LTO=ON
+1 -1
View File
@@ -4,4 +4,4 @@ set -ex
./vcpkg/bootstrap-vcpkg.sh ./vcpkg/bootstrap-vcpkg.sh
cmake . -B bin $1 -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_FLAGS="-O3 -g -Wl,-z,norelro -Wl,--hash-style=gnu -Wl,-z,noseparate-code -ffunction-sections -fdata-sections -Wl,--gc-sections -Wl,--export-dynamic" -DBeamMP-Server_ENABLE_LTO=ON cmake . -B bin $1 -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_FLAGS="-O3 -g -Wl,-z,norelro -Wl,--hash-style=gnu -Wl,-z,noseparate-code -ffunction-sections -fdata-sections -Wl,--gc-sections" -DBeamMP-Server_ENABLE_LTO=ON
-7
View File
@@ -1,7 +0,0 @@
#!/bin/bash
set -ex
apt-get update -y
apt-get install -y liblua5.3-0 liblua5.3-dev curl zip unzip tar cmake make git g++ ninja-build
-7
View File
@@ -1,7 +0,0 @@
#!/bin/bash
set -ex
git config --global --add safe.directory $(pwd)
git config --global --add safe.directory $(pwd)/vcpkg
git config --global --add safe.directory $(pwd)/deps/commandline
-7
View File
@@ -1,7 +0,0 @@
#!/bin/bash
set -ex
./vcpkg/bootstrap-vcpkg.sh
cmake . -B bin $1 -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_FLAGS="-O3 -g -Wl,-z,norelro -Wl,--hash-style=gnu -Wl,-z,noseparate-code -ffunction-sections -fdata-sections -Wl,--gc-sections -Wl,--export-dynamic" -DBeamMP-Server_ENABLE_LTO=ON
-5
View File
@@ -1,5 +0,0 @@
#!/bin/bash
set -ex
cmake --build bin --parallel -t BeamMP-Server-tests
-10
View File
@@ -1,10 +0,0 @@
#!/bin/bash
set -ex
cmake --build bin --parallel -t BeamMP-Server
objcopy --only-keep-debug bin/BeamMP-Server bin/BeamMP-Server.debug
objcopy --add-gnu-debuglink bin/BeamMP-Server bin/BeamMP-Server.debug
strip -s bin/BeamMP-Server
@@ -1,8 +0,0 @@
#!/bin/bash
set -ex
apt-get update -y
apt-get install -y liblua5.3-0 curl
+1 -1
View File
@@ -4,4 +4,4 @@ set -ex
./vcpkg/bootstrap-vcpkg.sh ./vcpkg/bootstrap-vcpkg.sh
cmake . -B bin $1 -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_FLAGS="-O3 -g -Wl,-z,norelro -Wl,--hash-style=gnu -Wl,-z,noseparate-code -ffunction-sections -fdata-sections -Wl,--gc-sections -Wl,--export-dynamic" -DBeamMP-Server_ENABLE_LTO=ON cmake . -B bin $1 -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_FLAGS="-O3 -g -Wl,-z,norelro -Wl,--hash-style=gnu -Wl,-z,noseparate-code -ffunction-sections -fdata-sections -Wl,--gc-sections" -DBeamMP-Server_ENABLE_LTO=ON
+1 -1
View File
@@ -4,4 +4,4 @@ set -ex
./vcpkg/bootstrap-vcpkg.sh ./vcpkg/bootstrap-vcpkg.sh
cmake . -B bin $1 -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_FLAGS="-O3 -g -Wl,-z,norelro -Wl,--hash-style=gnu -Wl,-z,noseparate-code -ffunction-sections -fdata-sections -Wl,--gc-sections -Wl,--export-dynamic" -DBeamMP-Server_ENABLE_LTO=ON cmake . -B bin $1 -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_FLAGS="-O3 -g -Wl,-z,norelro -Wl,--hash-style=gnu -Wl,-z,noseparate-code -ffunction-sections -fdata-sections -Wl,--gc-sections" -DBeamMP-Server_ENABLE_LTO=ON
+8 -15
View File
@@ -57,7 +57,7 @@ int TClient::GetOpenCarID() const {
return OpenID; return OpenID;
} }
void TClient::AddNewCar(int Ident, const nlohmann::json& Data) { void TClient::AddNewCar(int Ident, const std::string& Data) {
std::unique_lock lock(mVehicleDataMutex); std::unique_lock lock(mVehicleDataMutex);
mVehicleData.emplace_back(Ident, Data); mVehicleData.emplace_back(Ident, Data);
} }
@@ -76,13 +76,7 @@ std::string TClient::GetCarPositionRaw(int Ident) {
} }
} }
bool TClient::Disconnect(std::string_view Reason) { void TClient::Disconnect(std::string_view Reason) {
// Do not remove this guard: concurrent close() on the same socket can crash in Asio internals.
EDisconnectState Expected = EDisconnectState::Connected;
if (!mDisconnectState.compare_exchange_strong(Expected, EDisconnectState::Disconnecting, std::memory_order_acq_rel, std::memory_order_acquire)) {
return false;
}
beammp_debugf("Disconnecting client {} for reason: {}", GetID(), Reason); beammp_debugf("Disconnecting client {} for reason: {}", GetID(), Reason);
boost::system::error_code ec; boost::system::error_code ec;
if (mSocket.is_open()) { if (mSocket.is_open()) {
@@ -90,6 +84,7 @@ bool TClient::Disconnect(std::string_view Reason) {
if (ec) { if (ec) {
beammp_debugf("Failed to shutdown client socket: {}", ec.message()); beammp_debugf("Failed to shutdown client socket: {}", ec.message());
} }
mSocket.cancel();
mSocket.close(ec); mSocket.close(ec);
if (ec) { if (ec) {
beammp_debugf("Failed to close client socket: {}", ec.message()); beammp_debugf("Failed to close client socket: {}", ec.message());
@@ -97,9 +92,6 @@ bool TClient::Disconnect(std::string_view Reason) {
} else { } else {
beammp_debug("Socket is already closed."); beammp_debug("Socket is already closed.");
} }
// Terminal state: this client must not perform TCP IO after this point.
mDisconnectState.store(EDisconnectState::Disconnected, std::memory_order_release);
return true;
} }
void TClient::SetCarPosition(int Ident, const std::string& Data) { void TClient::SetCarPosition(int Ident, const std::string& Data) {
@@ -107,7 +99,7 @@ void TClient::SetCarPosition(int Ident, const std::string& Data) {
mVehiclePosition[size_t(Ident)] = Data; mVehiclePosition[size_t(Ident)] = Data;
} }
nlohmann::json TClient::GetCarData(int Ident) { std::string TClient::GetCarData(int Ident) {
{ // lock { // lock
std::unique_lock lock(mVehicleDataMutex); std::unique_lock lock(mVehicleDataMutex);
for (auto& v : mVehicleData) { for (auto& v : mVehicleData) {
@@ -117,10 +109,10 @@ nlohmann::json TClient::GetCarData(int Ident) {
} }
} // unlock } // unlock
DeleteCar(Ident); DeleteCar(Ident);
return nlohmann::detail::value_t::null; return "";
} }
void TClient::SetCarData(int Ident, const nlohmann::json& Data) { void TClient::SetCarData(int Ident, const std::string& Data) {
{ // lock { // lock
std::unique_lock lock(mVehicleDataMutex); std::unique_lock lock(mVehicleDataMutex);
for (auto& v : mVehicleData) { for (auto& v : mVehicleData) {
@@ -178,7 +170,8 @@ std::optional<std::weak_ptr<TClient>> GetClient(TServer& Server, int ID) {
std::optional<std::weak_ptr<TClient>> MaybeClient { std::nullopt }; std::optional<std::weak_ptr<TClient>> MaybeClient { std::nullopt };
Server.ForEachClient([&](std::weak_ptr<TClient> CPtr) -> bool { Server.ForEachClient([&](std::weak_ptr<TClient> CPtr) -> bool {
ReadLock Lock(Server.GetClientMutex()); ReadLock Lock(Server.GetClientMutex());
if (auto C = CPtr.lock()) { if (!CPtr.expired()) {
auto C = CPtr.lock();
if (C->GetID() == ID) { if (C->GetID() == ID) {
MaybeClient = CPtr; MaybeClient = CPtr;
return false; return false;
-8
View File
@@ -31,7 +31,6 @@
#include <sstream> #include <sstream>
#include <thread> #include <thread>
#include "TLuaResult.h"
#include "Compat.h" #include "Compat.h"
#include "CustomAssert.h" #include "CustomAssert.h"
#include "Http.h" #include "Http.h"
@@ -385,13 +384,6 @@ void SplitString(const std::string& str, const char delim, std::vector<std::stri
out.push_back(str.substr(start, end - start)); out.push_back(str.substr(start, end - start));
} }
} }
std::string LowerString(std::string str) {
std::ranges::transform(str, str.begin(), ::tolower);
return str;
}
static constexpr size_t STARTING_MAX_DECOMPRESSION_BUFFER_SIZE = 15 * 1024 * 1024; static constexpr size_t STARTING_MAX_DECOMPRESSION_BUFFER_SIZE = 15 * 1024 * 1024;
static constexpr size_t MAX_DECOMPRESSION_BUFFER_SIZE = 30 * 1024 * 1024; static constexpr size_t MAX_DECOMPRESSION_BUFFER_SIZE = 30 * 1024 * 1024;
-6
View File
@@ -36,15 +36,9 @@ std::string_view Env::ToString(Env::Key key) {
case Key::PROVIDER_DISABLE_CONFIG: case Key::PROVIDER_DISABLE_CONFIG:
return "BEAMMP_PROVIDER_DISABLE_CONFIG"; return "BEAMMP_PROVIDER_DISABLE_CONFIG";
break; break;
case Key::PROVIDER_DISABLE_MP_SET:
return "BEAMMP_PROVIDER_DISABLE_MP_SET";
break;
case Key::PROVIDER_PORT_ENV: case Key::PROVIDER_PORT_ENV:
return "BEAMMP_PROVIDER_PORT_ENV"; return "BEAMMP_PROVIDER_PORT_ENV";
break; break;
case Key::PROVIDER_IP_ENV:
return "BEAMMP_PROVIDER_IP_ENV";
break;
} }
return ""; return "";
} }
+11 -107
View File
@@ -18,12 +18,15 @@
#include "Http.h" #include "Http.h"
#include "Client.h"
#include "Common.h" #include "Common.h"
#include "CustomAssert.h" #include "CustomAssert.h"
#include "LuaAPI.h"
#include <curl/easy.h>
#include <map> #include <map>
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
#include <random>
#include <stdexcept>
using json = nlohmann::json; using json = nlohmann::json;
@@ -34,107 +37,17 @@ static size_t CurlWriteCallback(void* contents, size_t size, size_t nmemb, void*
return size * nmemb; return size * nmemb;
} }
struct CurlDeleter {
void operator()(CURL* c) const noexcept {
if (c)
curl_easy_cleanup(c);
}
};
static std::mutex gCurlPoolMutex;
static std::map<CURL*, bool> gCurlPool; // false = free, true = in use
constexpr size_t MAX_CURL_POOL_SIZE = 128;
static CURL* AcquireCurl() {
std::unique_lock Lock(gCurlPoolMutex);
for (auto& [Curl, InUse] : gCurlPool) {
if (!InUse) {
InUse = true;
curl_easy_reset(Curl);
return Curl;
}
}
// none found, if we're under the limit add one!
if (gCurlPool.size() >= MAX_CURL_POOL_SIZE) {
beammp_debugf("Ran out of curl handles for network requests, skipping this request");
return nullptr;
}
CURL* Curl = curl_easy_init();
if (!Curl) {
// failed, ignore
return nullptr;
}
gCurlPool[Curl] = true;
return Curl;
}
static void ReleaseCurl(CURL* curl) {
if (!curl) {
return;
}
std::unique_lock Lock(gCurlPoolMutex);
auto It = gCurlPool.find(curl);
if (It != gCurlPool.end()) {
It->second = false;
}
}
/// RAII container for curl handles to ensure correct release
class CurlLease {
private:
CURL* mHandle = nullptr;
public:
CurlLease() : mHandle(AcquireCurl()) {}
~CurlLease() {
ReleaseCurl(mHandle);
}
CurlLease(const CurlLease&) = delete;
CurlLease& operator=(const CurlLease&) = delete;
CurlLease(CurlLease&& other) noexcept
: mHandle(other.mHandle) {
other.mHandle = nullptr;
}
CurlLease& operator=(CurlLease&& other) noexcept {
if (this != &other) {
ReleaseCurl(mHandle);
mHandle = other.mHandle;
other.mHandle = nullptr;
}
return *this;
}
CURL* GetHandle() const noexcept {
return mHandle;
}
};
std::string Http::GET(const std::string& url, unsigned int* status) { std::string Http::GET(const std::string& url, unsigned int* status) {
std::string Ret; std::string Ret;
CurlLease Lease{}; static thread_local CURL* curl = curl_easy_init();
CURL* curl = Lease.GetHandle();
if (curl) { if (curl) {
CURLcode res; CURLcode res;
char errbuf[CURL_ERROR_SIZE]; char errbuf[CURL_ERROR_SIZE];
curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
curl_easy_setopt(curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); curl_easy_setopt(curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlWriteCallback); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlWriteCallback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, reinterpret_cast<void*>(&Ret)); curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*)&Ret);
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 10); // seconds
// ensure we dont keep connections open for long
curl_easy_setopt(curl, CURLOPT_MAXCONNECTS, 8L);
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 10L);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 30L);
curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L);
curl_easy_setopt(curl, CURLOPT_MAXAGE_CONN, 60L);
curl_easy_setopt(curl, CURLOPT_MAXLIFETIME_CONN, 300L);
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, errbuf); curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, errbuf);
errbuf[0] = 0; errbuf[0] = 0;
@@ -158,16 +71,15 @@ std::string Http::GET(const std::string& url, unsigned int* status) {
std::string Http::POST(const std::string& url, const std::string& body, const std::string& ContentType, unsigned int* status, const std::map<std::string, std::string>& headers) { std::string Http::POST(const std::string& url, const std::string& body, const std::string& ContentType, unsigned int* status, const std::map<std::string, std::string>& headers) {
std::string Ret; std::string Ret;
CurlLease Lease{}; static thread_local CURL* curl = curl_easy_init();
CURL* curl = Lease.GetHandle();
if (curl) { if (curl) {
CURLcode res; CURLcode res;
char errbuf[CURL_ERROR_SIZE]; char errbuf[CURL_ERROR_SIZE];
curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
curl_easy_setopt(curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); curl_easy_setopt(curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlWriteCallback); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlWriteCallback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, reinterpret_cast<void*>(&Ret)); curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*)&Ret);
curl_easy_setopt(curl, CURLOPT_POST, 1L); curl_easy_setopt(curl, CURLOPT_POST, 1);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body.c_str()); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body.c_str());
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, body.size()); curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, body.size());
struct curl_slist* list = nullptr; struct curl_slist* list = nullptr;
@@ -178,15 +90,7 @@ std::string Http::POST(const std::string& url, const std::string& body, const st
} }
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list); curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list);
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 10); // seconds
// ensure we dont keep connections open for long
curl_easy_setopt(curl, CURLOPT_MAXCONNECTS, 8L);
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 10L);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 30L);
curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L);
curl_easy_setopt(curl, CURLOPT_MAXAGE_CONN, 60L);
curl_easy_setopt(curl, CURLOPT_MAXLIFETIME_CONN, 300L);
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, errbuf); curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, errbuf);
errbuf[0] = 0; errbuf[0] = 0;
+30 -115
View File
@@ -143,12 +143,11 @@ static inline std::pair<bool, std::string> InternalTriggerClientEvent(int Player
return { true, "" }; return { true, "" };
} else { } else {
auto MaybeClient = GetClient(LuaAPI::MP::Engine->Server(), PlayerID); auto MaybeClient = GetClient(LuaAPI::MP::Engine->Server(), PlayerID);
if (MaybeClient) { if (!MaybeClient || MaybeClient.value().expired()) {
if (auto c = MaybeClient.value().lock()) { beammp_lua_errorf("TriggerClientEvent invalid Player ID '{}'", PlayerID);
if (!c->IsSyncing() && !c->IsSynced()) { return { false, "Invalid Player ID" };
return { false, "Player hasn't joined yet" };
} }
auto c = MaybeClient.value().lock();
if (!LuaAPI::MP::Engine->Network().Respond(*c, StringToVector(Packet), true)) { if (!LuaAPI::MP::Engine->Network().Respond(*c, StringToVector(Packet), true)) {
beammp_lua_errorf("Respond failed, dropping client {}", PlayerID); beammp_lua_errorf("Respond failed, dropping client {}", PlayerID);
LuaAPI::MP::Engine->Network().ClientKick(*c, "Disconnected after failing to receive packets"); LuaAPI::MP::Engine->Network().ClientKick(*c, "Disconnected after failing to receive packets");
@@ -158,11 +157,6 @@ static inline std::pair<bool, std::string> InternalTriggerClientEvent(int Player
} }
} }
beammp_lua_errorf("TriggerClientEvent invalid Player ID '{}'", PlayerID);
return { false, "Invalid Player ID" };
}
}
std::pair<bool, std::string> LuaAPI::MP::TriggerClientEvent(int PlayerID, const std::string& EventName, const sol::object& DataObj) { std::pair<bool, std::string> LuaAPI::MP::TriggerClientEvent(int PlayerID, const std::string& EventName, const sol::object& DataObj) {
std::string Data = DataObj.as<std::string>(); std::string Data = DataObj.as<std::string>();
return InternalTriggerClientEvent(PlayerID, EventName, Data); return InternalTriggerClientEvent(PlayerID, EventName, Data);
@@ -170,38 +164,32 @@ std::pair<bool, std::string> LuaAPI::MP::TriggerClientEvent(int PlayerID, const
std::pair<bool, std::string> LuaAPI::MP::DropPlayer(int ID, std::optional<std::string> MaybeReason) { std::pair<bool, std::string> LuaAPI::MP::DropPlayer(int ID, std::optional<std::string> MaybeReason) {
auto MaybeClient = GetClient(Engine->Server(), ID); auto MaybeClient = GetClient(Engine->Server(), ID);
if (MaybeClient) { if (!MaybeClient || MaybeClient.value().expired()) {
if (auto c = MaybeClient.value().lock()) {
LuaAPI::MP::Engine->Network().ClientKick(*c, MaybeReason.value_or("No reason"));
return { true, "" };
}
}
beammp_lua_errorf("Tried to drop client with id {}, who doesn't exist", ID); beammp_lua_errorf("Tried to drop client with id {}, who doesn't exist", ID);
return { false, "Player does not exist" }; return { false, "Player does not exist" };
} }
auto c = MaybeClient.value().lock();
LuaAPI::MP::Engine->Network().ClientKick(*c, MaybeReason.value_or("No reason"));
return { true, "" };
}
std::pair<bool, std::string> LuaAPI::MP::SendChatMessage(int ID, const std::string& Message, const bool& LogChat) { std::pair<bool, std::string> LuaAPI::MP::SendChatMessage(int ID, const std::string& Message) {
std::pair<bool, std::string> Result; std::pair<bool, std::string> Result;
std::string Packet = "C:Server: " + Message; std::string Packet = "C:Server: " + Message;
if (ID == -1) { if (ID == -1) {
if (LogChat) {
LogChatMessage("<Server> (to everyone) ", -1, Message); LogChatMessage("<Server> (to everyone) ", -1, Message);
}
Engine->Network().SendToAll(nullptr, StringToVector(Packet), true, true); Engine->Network().SendToAll(nullptr, StringToVector(Packet), true, true);
Result.first = true; Result.first = true;
} else { } else {
auto MaybeClient = GetClient(Engine->Server(), ID); auto MaybeClient = GetClient(Engine->Server(), ID);
if (MaybeClient) { if (MaybeClient && !MaybeClient.value().expired()) {
if (auto c = MaybeClient.value().lock()) { auto c = MaybeClient.value().lock();
if (!c->IsSynced()) { if (!c->IsSynced()) {
Result.first = false; Result.first = false;
Result.second = "Player still syncing data"; Result.second = "Player still syncing data";
return Result; return Result;
} }
if (LogChat) {
LogChatMessage("<Server> (to \"" + c->GetName() + "\")", -1, Message); LogChatMessage("<Server> (to \"" + c->GetName() + "\")", -1, Message);
}
if (!Engine->Network().Respond(*c, StringToVector(Packet), true)) { if (!Engine->Network().Respond(*c, StringToVector(Packet), true)) {
beammp_errorf("Failed to send chat message back to sender (id {}) - did the sender disconnect?", ID); beammp_errorf("Failed to send chat message back to sender (id {}) - did the sender disconnect?", ID);
// TODO: should we return an error here? // TODO: should we return an error here?
@@ -212,11 +200,6 @@ std::pair<bool, std::string> LuaAPI::MP::SendChatMessage(int ID, const std::stri
Result.first = false; Result.first = false;
Result.second = "Invalid Player ID"; Result.second = "Invalid Player ID";
} }
} else {
beammp_lua_error("SendChatMessage invalid argument [1] invalid ID");
Result.first = false;
Result.second = "Invalid Player ID";
}
return Result; return Result;
} }
return Result; return Result;
@@ -231,7 +214,7 @@ std::pair<bool, std::string> LuaAPI::MP::SendNotification(int ID, const std::str
} else { } else {
auto MaybeClient = GetClient(Engine->Server(), ID); auto MaybeClient = GetClient(Engine->Server(), ID);
if (MaybeClient) { if (MaybeClient) {
if (auto c = MaybeClient.value().lock()) { auto c = MaybeClient.value().lock();
if (!c->IsSynced()) { if (!c->IsSynced()) {
Result.first = false; Result.first = false;
Result.second = "Player is not synced yet"; Result.second = "Player is not synced yet";
@@ -248,58 +231,6 @@ std::pair<bool, std::string> LuaAPI::MP::SendNotification(int ID, const std::str
Result.first = false; Result.first = false;
Result.second = "Invalid Player ID"; Result.second = "Invalid Player ID";
} }
} else {
beammp_lua_error("SendNotification invalid argument [1] invalid ID");
Result.first = false;
Result.second = "Invalid Player ID";
}
return Result;
}
return Result;
}
std::pair<bool, std::string> LuaAPI::MP::ConfirmationDialog(int ID, const std::string& Title, const std::string& Body, const sol::table& buttons, const std::string& InteractionID, const bool& warning, const bool& reportToServer, const bool& reportToExtensions) {
std::pair<bool, std::string> Result;
const nlohmann::json PacketBody = {
{ "title", Title },
{ "body", Body },
{ "buttons", nlohmann::json::parse(JsonEncode(buttons), nullptr, false) },
{ "interactionID", InteractionID },
{ "class", warning ? "experimental" : "" },
{ "reportToServer", reportToServer },
{ "reportToExtensions", reportToExtensions }
};
std::string Packet = "D" + PacketBody.dump();
if (ID == -1) {
Engine->Network().SendToAll(nullptr, StringToVector(Packet), true, true);
Result.first = true;
} else {
auto MaybeClient = GetClient(Engine->Server(), ID);
if (MaybeClient) {
if (auto c = MaybeClient.value().lock()) {
if (!c->IsSynced()) {
Result.first = false;
Result.second = "Player is not synced yet";
return Result;
}
if (!Engine->Network().Respond(*c, StringToVector(Packet), true)) {
beammp_errorf("Failed to send confirmation dialog to player (id {}) - did the player disconnect?", ID);
Result.first = false;
Result.second = "Failed to send packet";
}
Result.first = true;
} else {
beammp_lua_error("ConfirmationDialog invalid argument [1] invalid ID");
Result.first = false;
Result.second = "Invalid Player ID";
}
} else {
beammp_lua_error("ConfirmationDialog invalid argument [1] invalid ID");
Result.first = false;
Result.second = "Invalid Player ID";
}
return Result; return Result;
} }
return Result; return Result;
@@ -308,9 +239,14 @@ std::pair<bool, std::string> LuaAPI::MP::ConfirmationDialog(int ID, const std::s
std::pair<bool, std::string> LuaAPI::MP::RemoveVehicle(int PID, int VID) { std::pair<bool, std::string> LuaAPI::MP::RemoveVehicle(int PID, int VID) {
std::pair<bool, std::string> Result; std::pair<bool, std::string> Result;
auto MaybeClient = GetClient(Engine->Server(), PID); auto MaybeClient = GetClient(Engine->Server(), PID);
if (MaybeClient) { if (!MaybeClient || MaybeClient.value().expired()) {
if (auto c = MaybeClient.value().lock()) { beammp_lua_error("RemoveVehicle invalid Player ID");
if (c->GetCarData(VID) != nlohmann::detail::value_t::null) { Result.first = false;
Result.second = "Invalid Player ID";
return Result;
}
auto c = MaybeClient.value().lock();
if (!c->GetCarData(VID).empty()) {
std::string Destroy = "Od:" + std::to_string(PID) + "-" + std::to_string(VID); std::string Destroy = "Od:" + std::to_string(PID) + "-" + std::to_string(VID);
LuaAPI::MP::Engine->ReportErrors(LuaAPI::MP::Engine->TriggerEvent("onVehicleDeleted", "", PID, VID)); LuaAPI::MP::Engine->ReportErrors(LuaAPI::MP::Engine->TriggerEvent("onVehicleDeleted", "", PID, VID));
Engine->Network().SendToAll(nullptr, StringToVector(Destroy), true, true); Engine->Network().SendToAll(nullptr, StringToVector(Destroy), true, true);
@@ -320,16 +256,6 @@ std::pair<bool, std::string> LuaAPI::MP::RemoveVehicle(int PID, int VID) {
Result.first = false; Result.first = false;
Result.second = "Vehicle does not exist"; Result.second = "Vehicle does not exist";
} }
} else {
beammp_lua_error("RemoveVehicle invalid Player ID");
Result.first = false;
Result.second = "Invalid Player ID";
}
} else {
beammp_lua_error("RemoveVehicle invalid Player ID");
Result.first = false;
Result.second = "Invalid Player ID";
}
return Result; return Result;
} }
@@ -435,23 +361,21 @@ void LuaAPI::MP::Sleep(size_t Ms) {
bool LuaAPI::MP::IsPlayerConnected(int ID) { bool LuaAPI::MP::IsPlayerConnected(int ID) {
auto MaybeClient = GetClient(Engine->Server(), ID); auto MaybeClient = GetClient(Engine->Server(), ID);
if (MaybeClient) { if (MaybeClient && !MaybeClient.value().expired()) {
if (auto c = MaybeClient.value().lock()) { return MaybeClient.value().lock()->IsUDPConnected();
return c->IsUDPConnected(); } else {
}
}
return false; return false;
} }
}
bool LuaAPI::MP::IsPlayerGuest(int ID) { bool LuaAPI::MP::IsPlayerGuest(int ID) {
auto MaybeClient = GetClient(Engine->Server(), ID); auto MaybeClient = GetClient(Engine->Server(), ID);
if (MaybeClient) { if (MaybeClient && !MaybeClient.value().expired()) {
if (auto c = MaybeClient.value().lock()) { return MaybeClient.value().lock()->IsGuest();
return c->IsGuest(); } else {
}
}
return false; return false;
} }
}
void LuaAPI::MP::PrintRaw(sol::variadic_args Args) { void LuaAPI::MP::PrintRaw(sol::variadic_args Args) {
std::string ToPrint = ""; std::string ToPrint = "";
@@ -465,16 +389,7 @@ void LuaAPI::MP::PrintRaw(sol::variadic_args Args) {
} }
int LuaAPI::PanicHandler(lua_State* State) { int LuaAPI::PanicHandler(lua_State* State) {
// panic path: use raw lua c api only; sol2 conversions can raise lua_error beammp_lua_error("PANIC: " + sol::stack::get<std::string>(State, 1));
// and re-enter panic recursively.
const int ErrorType = lua_type(State, -1);
if (ErrorType == LUA_TSTRING) {
const char* Message = lua_tostring(State, -1);
beammp_lua_error(std::string("PANIC: ") + (Message ? Message : "(null)"));
} else {
const char* ErrorTypeName = lua_typename(State, ErrorType);
beammp_lua_error(std::string("PANIC: non-string error object (") + (ErrorTypeName ? ErrorTypeName : "unknown") + ")");
}
return 0; return 0;
} }
-2
View File
@@ -28,7 +28,6 @@ Settings::Settings() {
{ General_Map, std::string("/levels/gridmap_v2/info.json") }, { General_Map, std::string("/levels/gridmap_v2/info.json") },
{ General_AuthKey, std::string("") }, { General_AuthKey, std::string("") },
{ General_Private, true }, { General_Private, true },
{ General_IP, "::"},
{ General_Port, 30814 }, { General_Port, 30814 },
{ General_MaxCars, 1 }, { General_MaxCars, 1 },
{ General_LogChat, true }, { General_LogChat, true },
@@ -48,7 +47,6 @@ Settings::Settings() {
{ { "General", "Map" }, { General_Map, READ_WRITE } }, { { "General", "Map" }, { General_Map, READ_WRITE } },
{ { "General", "AuthKey" }, { General_AuthKey, NO_ACCESS } }, { { "General", "AuthKey" }, { General_AuthKey, NO_ACCESS } },
{ { "General", "Private" }, { General_Private, READ_ONLY } }, { { "General", "Private" }, { General_Private, READ_ONLY } },
{ { "General", "IP" }, { General_IP, READ_ONLY } },
{ { "General", "Port" }, { General_Port, READ_ONLY } }, { { "General", "Port" }, { General_Port, READ_ONLY } },
{ { "General", "MaxCars" }, { General_MaxCars, READ_WRITE } }, { { "General", "MaxCars" }, { General_MaxCars, READ_WRITE } },
{ { "General", "LogChat" }, { General_LogChat, READ_ONLY } }, { { "General", "LogChat" }, { General_LogChat, READ_ONLY } },
-10
View File
@@ -34,8 +34,6 @@ static constexpr std::string_view StrDebug = "Debug";
static constexpr std::string_view EnvStrDebug = "BEAMMP_DEBUG"; static constexpr std::string_view EnvStrDebug = "BEAMMP_DEBUG";
static constexpr std::string_view StrPrivate = "Private"; static constexpr std::string_view StrPrivate = "Private";
static constexpr std::string_view EnvStrPrivate = "BEAMMP_PRIVATE"; static constexpr std::string_view EnvStrPrivate = "BEAMMP_PRIVATE";
static constexpr std::string_view StrIP = "IP";
static constexpr std::string_view EnvStrIP = "BEAMMP_IP";
static constexpr std::string_view StrPort = "Port"; static constexpr std::string_view StrPort = "Port";
static constexpr std::string_view EnvStrPort = "BEAMMP_PORT"; static constexpr std::string_view EnvStrPort = "BEAMMP_PORT";
static constexpr std::string_view StrMaxCars = "MaxCars"; static constexpr std::string_view StrMaxCars = "MaxCars";
@@ -140,8 +138,6 @@ void TConfig::FlushToFile() {
data["General"][StrInformationPacket.data()] = Application::Settings.getAsBool(Settings::Key::General_InformationPacket); data["General"][StrInformationPacket.data()] = Application::Settings.getAsBool(Settings::Key::General_InformationPacket);
data["General"][StrAllowGuests.data()] = Application::Settings.getAsBool(Settings::Key::General_AllowGuests); data["General"][StrAllowGuests.data()] = Application::Settings.getAsBool(Settings::Key::General_AllowGuests);
SetComment(data["General"][StrAllowGuests.data()].comments(), " Whether to allow guests"); SetComment(data["General"][StrAllowGuests.data()].comments(), " Whether to allow guests");
data["General"][StrIP.data()] = Application::Settings.getAsString(Settings::Key::General_IP);
SetComment(data["General"][StrIP.data()].comments(), " The IP address to bind the server to, this is NOT related to your public IP. Can be used if your machine has multiple network interfaces");
data["General"][StrPort.data()] = Application::Settings.getAsInt(Settings::Key::General_Port); data["General"][StrPort.data()] = Application::Settings.getAsInt(Settings::Key::General_Port);
data["General"][StrName.data()] = Application::Settings.getAsString(Settings::Key::General_Name); data["General"][StrName.data()] = Application::Settings.getAsString(Settings::Key::General_Name);
SetComment(data["General"][StrTags.data()].comments(), " Add custom identifying tags to your server to make it easier to find. Format should be TagA,TagB,TagC. Note the comma seperation."); SetComment(data["General"][StrTags.data()].comments(), " Add custom identifying tags to your server to make it easier to find. Format should be TagA,TagB,TagC. Note the comma seperation.");
@@ -258,11 +254,6 @@ void TConfig::ParseFromFile(std::string_view name) {
} else { } else {
TryReadValue(data, "General", StrPort, EnvStrPort, Settings::Key::General_Port); TryReadValue(data, "General", StrPort, EnvStrPort, Settings::Key::General_Port);
} }
if (Env::Get(Env::Key::PROVIDER_IP_ENV).has_value()) {
TryReadValue(data, "General", StrIP, Env::Get(Env::Key::PROVIDER_IP_ENV).value(), Settings::Key::General_IP);
} else {
TryReadValue(data, "General", StrIP, EnvStrIP, Settings::Key::General_IP);
}
TryReadValue(data, "General", StrMaxCars, EnvStrMaxCars, Settings::Key::General_MaxCars); TryReadValue(data, "General", StrMaxCars, EnvStrMaxCars, Settings::Key::General_MaxCars);
TryReadValue(data, "General", StrMaxPlayers, EnvStrMaxPlayers, Settings::Key::General_MaxPlayers); TryReadValue(data, "General", StrMaxPlayers, EnvStrMaxPlayers, Settings::Key::General_MaxPlayers);
TryReadValue(data, "General", StrMap, EnvStrMap, Settings::Key::General_Map); TryReadValue(data, "General", StrMap, EnvStrMap, Settings::Key::General_Map);
@@ -313,7 +304,6 @@ void TConfig::PrintDebug() {
beammp_debug(std::string(StrPrivate) + ": " + std::string(Application::Settings.getAsBool(Settings::Key::General_Private) ? "true" : "false")); beammp_debug(std::string(StrPrivate) + ": " + std::string(Application::Settings.getAsBool(Settings::Key::General_Private) ? "true" : "false"));
beammp_debug(std::string(StrInformationPacket) + ": " + std::string(Application::Settings.getAsBool(Settings::Key::General_InformationPacket) ? "true" : "false")); beammp_debug(std::string(StrInformationPacket) + ": " + std::string(Application::Settings.getAsBool(Settings::Key::General_InformationPacket) ? "true" : "false"));
beammp_debug(std::string(StrPort) + ": " + std::to_string(Application::Settings.getAsInt(Settings::Key::General_Port))); beammp_debug(std::string(StrPort) + ": " + std::to_string(Application::Settings.getAsInt(Settings::Key::General_Port)));
beammp_debug(std::string(StrIP) + ": \"" + Application::Settings.getAsString(Settings::Key::General_IP) + "\"");
beammp_debug(std::string(StrMaxCars) + ": " + std::to_string(Application::Settings.getAsInt(Settings::Key::General_MaxCars))); beammp_debug(std::string(StrMaxCars) + ": " + std::to_string(Application::Settings.getAsInt(Settings::Key::General_MaxCars)));
beammp_debug(std::string(StrMaxPlayers) + ": " + std::to_string(Application::Settings.getAsInt(Settings::Key::General_MaxPlayers))); beammp_debug(std::string(StrMaxPlayers) + ": " + std::to_string(Application::Settings.getAsInt(Settings::Key::General_MaxPlayers)));
beammp_debug(std::string(StrMap) + ": \"" + Application::Settings.getAsString(Settings::Key::General_Map) + "\""); beammp_debug(std::string(StrMap) + ": \"" + Application::Settings.getAsString(Settings::Key::General_Map) + "\"");
-96
View File
@@ -1,96 +0,0 @@
// BeamMP, the BeamNG.drive multiplayer mod.
// Copyright (C) 2026 BeamMP Ltd., BeamMP team and contributors.
//
// BeamMP Ltd. can be contacted by electronic mail via contact@beammp.com.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#include "TConnectionLimiter.h"
#include <mutex>
#include <optional>
#include "Common.h"
TConnectionLimiter::TConnectionLimiter(size_t maxPerIp, size_t maxGlobal)
: mMaxPerIp(maxPerIp)
, mMaxGlobal(maxGlobal) {
if (maxPerIp == 0) {
beammp_errorf("Max connections count per IP is set to zero; the server would reject ALL connections");
throw std::runtime_error("Invalid maximum connections per IP setting");
}
if (maxGlobal == 0) {
beammp_errorf("Max connection count is set to zero; the server would reject ALL connections");
throw std::runtime_error("Invalid maximum connections setting");
}
}
std::optional<TConnectionLimiter::TGuard> TConnectionLimiter::TryAcquire(const std::string& ip) {
std::unique_lock Lock { mMutex };
if (mGlobal >= mMaxGlobal) return std::nullopt;
// `It` is the inserted element (so if insertion worked, its 0), or its the element that
// was already there, in which case we must check the ip connection limit
auto [It, _] = mPerIp.try_emplace(ip, 0);
if (It->second >= mMaxPerIp) {
return std::nullopt;
}
// now increment the counter finally
++It->second;
++mGlobal;
// RAII guard will drop the count once destructed
return TGuard(this, ip);
}
TConnectionLimiter::TStats TConnectionLimiter::GetStats() {
std::unique_lock Lock { mMutex };
TStats Stats;
Stats.CurrentGlobal = mGlobal;
Stats.MaxGlobal = mMaxGlobal;
Stats.ActiveIpBuckets = mPerIp.size();
Stats.MaxPerIp = mMaxPerIp;
for (const auto& [_, Count] : mPerIp) {
if (Count > Stats.CurrentMaxPerIp) {
Stats.CurrentMaxPerIp = Count;
}
if (Count >= mMaxPerIp) {
++Stats.SaturatedIpBuckets;
}
}
return Stats;
}
TConnectionLimiter::TGuard::TGuard(TConnectionLimiter* owner, std::string ip)
: mOwner(owner)
, mIp(std::move(ip)) {
beammp_debugf("Acquired connection guard for {}", mIp);
}
TConnectionLimiter::TGuard& TConnectionLimiter::TGuard::operator=(TGuard&& other) noexcept {
// identity check
if (this != &other) {
Release();
mOwner = other.mOwner;
mIp = std::move(other.mIp);
other.mOwner = nullptr;
// setting this so its obvious when this happens, instead of being UB or empty string
other.mIp = "<moved-from>";
}
return *this;
}
void TConnectionLimiter::TGuard::Release() {
if (mOwner) {
mOwner->Release(mIp);
mOwner = nullptr;
beammp_debugf("Released connection guard for {}", mIp);
}
}
+25 -87
View File
@@ -22,7 +22,6 @@
#include "Client.h" #include "Client.h"
#include "CustomAssert.h" #include "CustomAssert.h"
#include "Http.h"
#include "LuaAPI.h" #include "LuaAPI.h"
#include "TLuaEngine.h" #include "TLuaEngine.h"
@@ -160,7 +159,7 @@ void TConsole::ChangeToRegularConsole() {
} }
bool TConsole::EnsureArgsCount(const std::vector<std::string>& args, size_t n) { bool TConsole::EnsureArgsCount(const std::vector<std::string>& args, size_t n) {
if (n == 0 && !args.empty()) { if (n == 0 && args.size() != 0) {
Application::Console().WriteRaw("This command expects no arguments."); Application::Console().WriteRaw("This command expects no arguments.");
return false; return false;
} else if (args.size() != n) { } else if (args.size() != n) {
@@ -198,7 +197,7 @@ void TConsole::Command_Lua(const std::string&, const std::vector<std::string>& a
} else { } else {
Application::Console().WriteRaw("Lua state '" + NewStateId + "' is not a known state. Didn't switch to Lua."); Application::Console().WriteRaw("Lua state '" + NewStateId + "' is not a known state. Didn't switch to Lua.");
} }
} else if (args.empty()) { } else if (args.size() == 0) {
ChangeToLuaConsole(mDefaultStateId); ChangeToLuaConsole(mDefaultStateId);
} }
} }
@@ -218,9 +217,7 @@ void TConsole::Command_Help(const std::string&, const std::vector<std::string>&
settings [command] sets or gets settings for the server, run `settings help` for more info 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 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 version displays the server version)";
protectmod <name> <value> sets whether a mod is protected, value can be true or false
reloadmods reloads all mods from the Resources Client folder)";
Application::Console().WriteRaw("BeamMP-Server Console: " + std::string(sHelpString)); Application::Console().WriteRaw("BeamMP-Server Console: " + std::string(sHelpString));
} }
@@ -265,55 +262,6 @@ void TConsole::Command_Version(const std::string& cmd, const std::vector<std::st
std::string openssl_version = fmt::format("OpenSSL: v{}.{}.{}", OPENSSL_VERSION_MAJOR, OPENSSL_VERSION_MINOR, OPENSSL_VERSION_PATCH); std::string openssl_version = fmt::format("OpenSSL: v{}.{}.{}", OPENSSL_VERSION_MAJOR, OPENSSL_VERSION_MINOR, OPENSSL_VERSION_PATCH);
Application::Console().WriteRaw(openssl_version); Application::Console().WriteRaw(openssl_version);
} }
void TConsole::Command_ProtectMod(const std::string& cmd, const std::vector<std::string>& args) {
if (!EnsureArgsCount(args, 2)) {
return;
}
const auto& ModName = args.at(0);
const auto& Protect = args.at(1);
for (auto mod : mLuaEngine->Network().ResourceManager().GetMods()) {
if (mod["file_name"].get<std::string>() == ModName) {
mLuaEngine->Network().ResourceManager().SetProtected(ModName, Protect == "true");
Application::Console().WriteRaw("Mod " + ModName + " is now " + (Protect == "true" ? "protected" : "unprotected"));
return;
}
}
Application::Console().WriteRaw("Mod " + ModName + " not found.");
}
void TConsole::Command_ReloadMods(const std::string& cmd, const std::vector<std::string>& args) {
if (!EnsureArgsCount(args, 0)) {
return;
}
mLuaEngine->Network().ResourceManager().RefreshFiles();
Application::Console().WriteRaw("Mods reloaded.");
}
void TConsole::Command_NetTest(const std::string& cmd, const std::vector<std::string>& args) {
unsigned int status = 0;
std::string T = Http::GET(
Application::GetServerCheckUrl() + "/api/v2/beammp/" + std::to_string(Application::Settings.getAsInt(Settings::Key::General_Port)), &status);
beammp_debugf("Status and response from Server Check API: {0}, {1}", status, T);
auto Doc = nlohmann::json::parse(T, nullptr, false);
if (Doc.is_discarded() || !Doc.is_object()) {
beammp_warn("Failed to parse Server Check API response, however the server will most likely still work correctly.");
} else {
std::string status = Doc["status"];
std::string details = "Response from Server Check API: " + std::string(Doc["details"]);
if (status == "ok") {
beammp_info(details);
} else {
beammp_warn(details);
}
}
}
void TConsole::Command_Kick(const std::string&, const std::vector<std::string>& args) { void TConsole::Command_Kick(const std::string&, const std::vector<std::string>& args) {
if (!EnsureArgsCount(args, 1, size_t(-1))) { if (!EnsureArgsCount(args, 1, size_t(-1))) {
@@ -333,9 +281,10 @@ void TConsole::Command_Kick(const std::string&, const std::vector<std::string>&
return StringStartsWith(Name1, Name2) || StringStartsWith(Name2, Name1); return StringStartsWith(Name1, Name2) || StringStartsWith(Name2, Name1);
}; };
mLuaEngine->Server().ForEachClient([&](std::weak_ptr<TClient> Client) -> bool { mLuaEngine->Server().ForEachClient([&](std::weak_ptr<TClient> Client) -> bool {
if (auto Locked = Client.lock()) { if (!Client.expired()) {
if (NameCompare(Locked->GetName(), Name)) { auto locked = Client.lock();
mLuaEngine->Network().ClientKick(*Locked, Reason); if (NameCompare(locked->GetName(), Name)) {
mLuaEngine->Network().ClientKick(*locked, Reason);
Kicked = true; Kicked = true;
return false; return false;
} }
@@ -421,7 +370,7 @@ void TConsole::Command_Settings(const std::string&, const std::vector<std::strin
settings set <category> <setting> <value> sets specified setting to value settings set <category> <setting> <value> sets specified setting to value
)"; )";
if (args.empty()) { if (args.size() == 0) {
beammp_errorf("No arguments specified for command 'settings'!"); beammp_errorf("No arguments specified for command 'settings'!");
Application::Console().WriteRaw("BeamMP-Server Console: " + std::string(sHelpString)); Application::Console().WriteRaw("BeamMP-Server Console: " + std::string(sHelpString));
return; return;
@@ -564,10 +513,11 @@ void TConsole::Command_List(const std::string&, const std::vector<std::string>&
std::stringstream ss; std::stringstream ss;
ss << std::left << std::setw(25) << "Name" << std::setw(6) << "ID" << std::setw(6) << "Cars" << std::endl; ss << std::left << std::setw(25) << "Name" << std::setw(6) << "ID" << std::setw(6) << "Cars" << std::endl;
mLuaEngine->Server().ForEachClient([&](std::weak_ptr<TClient> Client) -> bool { mLuaEngine->Server().ForEachClient([&](std::weak_ptr<TClient> Client) -> bool {
if (auto Locked = Client.lock()) { if (!Client.expired()) {
ss << std::left << std::setw(25) << Locked->GetName() auto locked = Client.lock();
<< std::setw(6) << Locked->GetID() ss << std::left << std::setw(25) << locked->GetName()
<< std::setw(6) << Locked->GetCarCount() << "\n"; << std::setw(6) << locked->GetID()
<< std::setw(6) << locked->GetCarCount() << "\n";
} }
return true; return true;
}); });
@@ -590,7 +540,8 @@ void TConsole::Command_Status(const std::string&, const std::vector<std::string>
size_t MissedPacketQueueSum = 0; size_t MissedPacketQueueSum = 0;
int LargestSecondsSinceLastPing = 0; int LargestSecondsSinceLastPing = 0;
mLuaEngine->Server().ForEachClient([&](std::weak_ptr<TClient> Client) -> bool { mLuaEngine->Server().ForEachClient([&](std::weak_ptr<TClient> Client) -> bool {
if (auto Locked = Client.lock()) { if (!Client.expired()) {
auto Locked = Client.lock();
CarCount += Locked->GetCarCount(); CarCount += Locked->GetCarCount();
ConnectedCount += Locked->IsUDPConnected() ? 1 : 0; ConnectedCount += Locked->IsUDPConnected() ? 1 : 0;
GuestCount += Locked->IsGuest() ? 1 : 0; GuestCount += Locked->IsGuest() ? 1 : 0;
@@ -649,7 +600,6 @@ void TConsole::Command_Status(const std::string&, const std::vector<std::string>
SystemsShutdownList = SystemsShutdownList.substr(0, SystemsShutdownList.size() - 2); SystemsShutdownList = SystemsShutdownList.substr(0, SystemsShutdownList.size() - 2);
auto ElapsedTime = mLuaEngine->Server().UptimeTimer.GetElapsedTime(); auto ElapsedTime = mLuaEngine->Server().UptimeTimer.GetElapsedTime();
auto ConnectionLimiterStats = mLuaEngine->Network().GetConnectionLimiterStats();
Status << "BeamMP-Server Status:\n" Status << "BeamMP-Server Status:\n"
<< "\tTotal Players: " << mLuaEngine->Server().ClientCount() << "\n" << "\tTotal Players: " << mLuaEngine->Server().ClientCount() << "\n"
@@ -664,11 +614,6 @@ void TConsole::Command_Status(const std::string&, const std::vector<std::string>
<< "\t\tStates: " << mLuaEngine->GetLuaStateCount() << "\n" << "\t\tStates: " << mLuaEngine->GetLuaStateCount() << "\n"
<< "\t\tEvent timers: " << mLuaEngine->GetTimedEventsCount() << "\n" << "\t\tEvent timers: " << mLuaEngine->GetTimedEventsCount() << "\n"
<< "\t\tEvent handlers: " << mLuaEngine->GetRegisteredEventHandlerCount() << "\n" << "\t\tEvent handlers: " << mLuaEngine->GetRegisteredEventHandlerCount() << "\n"
<< "\tConnection limiter:\n"
<< "\t\tActive/Max global: " << ConnectionLimiterStats.CurrentGlobal << "/" << ConnectionLimiterStats.MaxGlobal << "\n"
<< "\t\tActive IP buckets: " << ConnectionLimiterStats.ActiveIpBuckets << "\n"
<< "\t\tHighest single IP load: " << ConnectionLimiterStats.CurrentMaxPerIp << "/" << ConnectionLimiterStats.MaxPerIp << "\n"
<< "\t\tSaturated IP buckets: " << ConnectionLimiterStats.SaturatedIpBuckets << "\n"
<< "\tSubsystems:\n" << "\tSubsystems:\n"
<< "\t\tGood/Starting/Bad: " << SystemsGood << "/" << SystemsStarting << "/" << SystemsBad << "\n" << "\t\tGood/Starting/Bad: " << SystemsGood << "/" << SystemsStarting << "/" << SystemsBad << "\n"
<< "\t\tShutting down/Shut down: " << SystemsShuttingDown << "/" << SystemsShutdown << "\n" << "\t\tShutting down/Shut down: " << SystemsShuttingDown << "/" << SystemsShutdown << "\n"
@@ -685,13 +630,9 @@ void TConsole::Command_Status(const std::string&, const std::vector<std::string>
void TConsole::RunAsCommand(const std::string& cmd, bool IgnoreNotACommand) { void TConsole::RunAsCommand(const std::string& cmd, bool IgnoreNotACommand) {
auto FutureIsNonNil = auto FutureIsNonNil =
[](const std::shared_ptr<TLuaResult>& Future) { [](const std::shared_ptr<TLuaResult>& Future) {
if (!Future->IsError()) { if (!Future->Error && Future->Result.valid()) {
auto Snapshot = Future->GetDetachedSnapshot(); auto Type = Future->Result.get_type();
if (Snapshot.Result.V.valueless_by_exception() || std::get_if<std::monostate>(&Snapshot.Result.V) != nullptr) { return Type != sol::type::lua_nil && Type != sol::type::none;
// no value contained
return false;
}
return true;
} }
return false; return false;
}; };
@@ -701,7 +642,7 @@ void TConsole::RunAsCommand(const std::string& cmd, bool IgnoreNotACommand) {
TLuaEngine::WaitForAll(Futures, std::chrono::seconds(5)); TLuaEngine::WaitForAll(Futures, std::chrono::seconds(5));
size_t Count = 0; size_t Count = 0;
for (auto& Future : Futures) { for (auto& Future : Futures) {
if (!Future->IsError()) { if (!Future->Error) {
++Count; ++Count;
} }
} }
@@ -711,7 +652,7 @@ void TConsole::RunAsCommand(const std::string& cmd, bool IgnoreNotACommand) {
} }
} }
} }
if (NonNilFutures.empty()) { if (NonNilFutures.size() == 0) {
if (!IgnoreNotACommand) { if (!IgnoreNotACommand) {
Application::Console().WriteRaw("Error: Unknown command: '" + cmd + "'. Type 'help' to see a list of valid commands."); Application::Console().WriteRaw("Error: Unknown command: '" + cmd + "'. Type 'help' to see a list of valid commands.");
} }
@@ -719,16 +660,14 @@ void TConsole::RunAsCommand(const std::string& cmd, bool IgnoreNotACommand) {
std::stringstream Reply; std::stringstream Reply;
if (NonNilFutures.size() > 1) { if (NonNilFutures.size() > 1) {
for (size_t i = 0; i < NonNilFutures.size(); ++i) { for (size_t i = 0; i < NonNilFutures.size(); ++i) {
auto Snapshot = NonNilFutures[i]->GetDetachedSnapshot(); Reply << NonNilFutures[i]->StateId << ": \n"
Reply << Snapshot.StateId << ": \n" << LuaAPI::LuaToString(NonNilFutures[i]->Result);
<< Snapshot.Result;
if (i < NonNilFutures.size() - 1) { if (i < NonNilFutures.size() - 1) {
Reply << "\n"; Reply << "\n";
} }
} }
} else { } else {
auto Snapshot = NonNilFutures[0]->GetDetachedSnapshot(); Reply << LuaAPI::LuaToString(NonNilFutures[0]->Result);
Reply << Snapshot.Result;
} }
Application::Console().WriteRaw(Reply.str()); Application::Console().WriteRaw(Reply.str());
} }
@@ -809,9 +748,8 @@ void TConsole::InitializeCommandline() {
} else { } else {
auto Future = mLuaEngine->EnqueueScript(mStateId, { std::make_shared<std::string>(TrimmedCmd), "", "" }); auto Future = mLuaEngine->EnqueueScript(mStateId, { std::make_shared<std::string>(TrimmedCmd), "", "" });
Future->WaitUntilReady(); Future->WaitUntilReady();
if (Future->IsError()) { if (Future->Error) {
auto Snapshot = Future->GetSnapshot(); beammp_lua_error("error in " + mStateId + ": " + Future->ErrorMessage);
beammp_lua_error("error in " + mStateId + ": " + Snapshot.ErrorMessage);
} }
} }
} else { } else {
+2 -2
View File
@@ -181,8 +181,8 @@ std::string THeartbeatThread::GetPlayers() {
std::string Return; std::string Return;
mServer.ForEachClient([&](const std::weak_ptr<TClient>& ClientPtr) -> bool { mServer.ForEachClient([&](const std::weak_ptr<TClient>& ClientPtr) -> bool {
ReadLock Lock(mServer.GetClientMutex()); ReadLock Lock(mServer.GetClientMutex());
if (auto Client = ClientPtr.lock()) { if (!ClientPtr.expired()) {
Return += Client->GetName() + ";"; Return += ClientPtr.lock()->GetName() + ";";
} }
return true; return true;
}); });
-37
View File
@@ -1,37 +0,0 @@
// BeamMP, the BeamNG.drive multiplayer mod.
// Copyright (C) 2024 BeamMP Ltd., BeamMP team and contributors.
//
// BeamMP Ltd. can be contacted by electronic mail via contact@beammp.com.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#include "TIoPollThread.h"
TIoPollThread::TIoPollThread()
: mWorkGuard(boost::asio::make_work_guard(mIoCtx))
, mThread([this](std::stop_token StopToken) {
while (!StopToken.stop_requested()) {
try {
mIoCtx.run();
break;
} catch (...) {
mIoCtx.restart();
}
}
}) { }
TIoPollThread::~TIoPollThread() {
mWorkGuard.reset();
mIoCtx.stop();
}
+189 -329
View File
@@ -20,29 +20,40 @@
#include "Client.h" #include "Client.h"
#include "Common.h" #include "Common.h"
#include "CustomAssert.h" #include "CustomAssert.h"
#include "Env.h"
#include "Http.h" #include "Http.h"
#include "LuaAPI.h" #include "LuaAPI.h"
#include "Profiling.h" #include "Profiling.h"
#include "TLuaPlugin.h" #include "TLuaPlugin.h"
#include "TLuaResult.h"
#include "sol/object.hpp" #include "sol/object.hpp"
#include <chrono> #include <chrono>
#include <condition_variable> #include <condition_variable>
#include <fmt/core.h> #include <fmt/core.h>
#include <mutex>
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
#include <optional>
#include <random> #include <random>
#include <sol/types.hpp> #include <sol/stack_core.hpp>
#include <thread> #include <thread>
#include <tuple> #include <tuple>
#include <type_traits>
#include <unordered_map>
#include <variant>
TLuaEngine* LuaAPI::MP::Engine; TLuaEngine* LuaAPI::MP::Engine;
static sol::protected_function AddTraceback(sol::state_view StateView, sol::protected_function RawFn);
static std::optional<sol::function> GetLuaHandler(sol::state_view StateView, const std::string Handler, const std::string EventName);
static std::optional<sol::function> GetLuaHandler(sol::state_view StateView, const std::string Handler, const std::string EventName) {
if (!StateView)
return std::nullopt;
auto Res = StateView.safe_script("return " + Handler, sol::script_pass_on_error);
if (!Res.valid()) {
beammp_errorf("invalid handler for event \"{}\". handler: \"{}\"", EventName, Handler);
} else if (Res.get_type() == sol::type::function) {
return Res.get<sol::function>();
}
return std::nullopt;
}
TLuaEngine::TLuaEngine() TLuaEngine::TLuaEngine()
: mResourceServerPath(fs::path(Application::Settings.getAsString(Settings::Key::General_ResourceFolder)) / "Server") { : mResourceServerPath(fs::path(Application::Settings.getAsString(Settings::Key::General_ResourceFolder)) / "Server") {
Application::SetSubsystemStatus("LuaEngine", Application::Status::Starting); Application::SetSubsystemStatus("LuaEngine", Application::Status::Starting);
@@ -63,20 +74,24 @@ TLuaEngine::TLuaEngine()
IThreaded::Start(); IThreaded::Start();
} }
TEST_CASE("TLuaEngine ctor & dtor") {
Application::Settings.set(Settings::Key::General_ResourceFolder, "beammp_server_test_resources");
TLuaEngine engine;
Application::GracefullyShutdown();
}
void TLuaEngine::operator()() { void TLuaEngine::operator()() {
RegisterThread("LuaEngine"); RegisterThread("LuaEngine");
Application::SetSubsystemStatus("LuaEngine", Application::Status::Good);
// lua engine main thread // lua engine main thread
beammp_infof("Lua v{}.{}.{}", LUA_VERSION_MAJOR, LUA_VERSION_MINOR, LUA_VERSION_RELEASE); beammp_infof("Lua v{}.{}.{}", LUA_VERSION_MAJOR, LUA_VERSION_MINOR, LUA_VERSION_RELEASE);
CollectAndInitPlugins(); CollectAndInitPlugins();
Application::SetSubsystemStatus("LuaEngine", Application::Status::Good);
// now call all onInit's // now call all onInit's
auto Futures = TriggerEvent("onInit", ""); auto Futures = TriggerEvent("onInit", "");
WaitForAll(Futures, std::chrono::seconds(5)); WaitForAll(Futures, std::chrono::seconds(5));
for (const auto& Future : Futures) { for (const auto& Future : Futures) {
auto Snapshot = Future->GetDetachedSnapshot(); if (Future->Error && Future->ErrorMessage != BeamMPFnNotFoundError) {
if (Snapshot.Error && Snapshot.ErrorMessage != BeamMPFnNotFoundError) { beammp_lua_error("Calling \"onInit\" on \"" + Future->StateId + "\" failed: " + Future->ErrorMessage);
beammp_lua_error("Calling \"onInit\" on \"" + Snapshot.StateId + "\" failed: " + Snapshot.ErrorMessage);
} }
} }
@@ -86,11 +101,10 @@ void TLuaEngine::operator()() {
std::unique_lock Lock(mResultsToCheckMutex); std::unique_lock Lock(mResultsToCheckMutex);
if (!mResultsToCheck.empty()) { if (!mResultsToCheck.empty()) {
mResultsToCheck.remove_if([](const std::shared_ptr<TLuaResult>& Ptr) -> bool { mResultsToCheck.remove_if([](const std::shared_ptr<TLuaResult>& Ptr) -> bool {
if (Ptr->IsReady()) { if (Ptr->Ready) {
auto Snapshot = Ptr->GetDetachedSnapshot(); if (Ptr->Error) {
if (Snapshot.Error) { if (Ptr->ErrorMessage != BeamMPFnNotFoundError) {
if (Snapshot.ErrorMessage != BeamMPFnNotFoundError) { beammp_lua_error(Ptr->Function + ": " + Ptr->ErrorMessage);
beammp_lua_error(Snapshot.Function + ": " + Snapshot.ErrorMessage);
} }
} }
return true; return true;
@@ -130,13 +144,7 @@ void TLuaEngine::operator()() {
} }
} }
} }
bool StatesEmpty = false; if (mLuaStates.size() == 0) {
{
std::unique_lock Lock(mLuaStatesMutex);
StatesEmpty = mLuaStates.empty();
}
if (StatesEmpty) {
beammp_trace("No Lua states, event loop running extremely sparsely"); beammp_trace("No Lua states, event loop running extremely sparsely");
Application::SleepSafeSeconds(10); Application::SleepSafeSeconds(10);
} else { } else {
@@ -209,8 +217,8 @@ std::unordered_map<std::string /* event name */, std::vector<std::string> /* han
return Result; return Result;
} }
std::queue<std::pair<TLuaChunk, std::shared_ptr<TLuaVoidResult>>> TLuaEngine::Debug_GetStateExecuteQueueForState(TLuaStateId StateId) { std::queue<std::pair<TLuaChunk, std::shared_ptr<TLuaResult>>> TLuaEngine::Debug_GetStateExecuteQueueForState(TLuaStateId StateId) {
std::queue<std::pair<TLuaChunk, std::shared_ptr<TLuaVoidResult>>> Result; std::queue<std::pair<TLuaChunk, std::shared_ptr<TLuaResult>>> Result;
std::unique_lock Lock(mLuaStatesMutex); std::unique_lock Lock(mLuaStatesMutex);
Result = mLuaStates.at(StateId)->Debug_GetStateExecuteQueue(); Result = mLuaStates.at(StateId)->Debug_GetStateExecuteQueue();
return Result; return Result;
@@ -223,16 +231,16 @@ std::vector<TLuaEngine::QueuedFunction> TLuaEngine::Debug_GetStateFunctionQueueF
return Result; return Result;
} }
std::vector<TLuaResult::DetachedSnapshot> TLuaEngine::Debug_GetResultsToCheckForState(TLuaStateId StateId) { std::vector<TLuaResult> TLuaEngine::Debug_GetResultsToCheckForState(TLuaStateId StateId) {
std::unique_lock Lock(mResultsToCheckMutex); std::unique_lock Lock(mResultsToCheckMutex);
auto ResultsToCheckCopy = mResultsToCheck; auto ResultsToCheckCopy = mResultsToCheck;
Lock.unlock(); Lock.unlock();
std::vector<TLuaResult::DetachedSnapshot> Result; std::vector<TLuaResult> Result;
while (!ResultsToCheckCopy.empty()) { while (!ResultsToCheckCopy.empty()) {
auto ResultToCheck = std::move(ResultsToCheckCopy.front()); auto ResultToCheck = std::move(ResultsToCheckCopy.front());
ResultsToCheckCopy.pop_front(); ResultsToCheckCopy.pop_front();
if (ResultToCheck->OwnerState() == StateId) { if (ResultToCheck->StateId == StateId) {
Result.push_back(ResultToCheck->GetDetachedSnapshot()); Result.push_back(*ResultToCheck);
} }
} }
return Result; return Result;
@@ -319,30 +327,27 @@ void TLuaEngine::WaitForAll(std::vector<std::shared_ptr<TLuaResult>>& Results, c
size_t ms = 0; size_t ms = 0;
std::set<std::string> WarnedResults; std::set<std::string> WarnedResults;
while (!Result->IsReady() && !Cancelled) { while (!Result->Ready && !Cancelled) {
std::this_thread::sleep_for(std::chrono::milliseconds(10)); std::this_thread::sleep_for(std::chrono::milliseconds(10));
ms += 10; ms += 10;
if (Max.has_value() && std::chrono::milliseconds(ms) > Max.value()) { if (Max.has_value() && std::chrono::milliseconds(ms) > Max.value()) {
auto Snapshot = Result->GetDetachedSnapshot(); beammp_trace("'" + Result->Function + "' in '" + Result->StateId + "' did not finish executing in time (took: " + std::to_string(ms) + "ms).");
beammp_trace("'" + Snapshot.Function + "' in '" + Snapshot.StateId + "' did not finish executing in time (took: " + std::to_string(ms) + "ms).");
Cancelled = true; Cancelled = true;
} else if (ms > 1000 * 60) { } else if (ms > 1000 * 60) {
auto Snapshot = Result->GetDetachedSnapshot(); auto ResultId = Result->StateId + "_" + Result->Function;
auto ResultId = Snapshot.StateId + "_" + Snapshot.Function;
if (WarnedResults.count(ResultId) == 0) { if (WarnedResults.count(ResultId) == 0) {
WarnedResults.insert(ResultId); WarnedResults.insert(ResultId);
beammp_lua_warn("'" + Snapshot.Function + "' in '" + Snapshot.StateId + "' is taking very long. The event it's handling is too important to discard the result of this handler, but may block this event and possibly the whole lua state."); beammp_lua_warn("'" + Result->Function + "' in '" + Result->StateId + "' is taking very long. The event it's handling is too important to discard the result of this handler, but may block this event and possibly the whole lua state.");
} }
} }
} }
auto Snapshot = Result->GetDetachedSnapshot();
if (Cancelled) { if (Cancelled) {
beammp_lua_warn("'" + Snapshot.Function + "' in '" + Snapshot.StateId + "' failed to execute in time and was not waited for. It may still finish executing at a later time."); beammp_lua_warn("'" + Result->Function + "' in '" + Result->StateId + "' failed to execute in time and was not waited for. It may still finish executing at a later time.");
LuaAPI::MP::Engine->ReportErrors({ Result }); LuaAPI::MP::Engine->ReportErrors({ Result });
} else if (Snapshot.Error) { } else if (Result->Error) {
if (Snapshot.ErrorMessage != BeamMPFnNotFoundError) { if (Result->ErrorMessage != BeamMPFnNotFoundError) {
beammp_lua_error(Snapshot.Function + ": " + Snapshot.ErrorMessage); beammp_lua_error(Result->Function + ": " + Result->ErrorMessage);
} }
} }
} }
@@ -362,7 +367,7 @@ bool TLuaEngine::HasState(TLuaStateId StateId) {
return mLuaStates.find(StateId) != mLuaStates.end(); return mLuaStates.find(StateId) != mLuaStates.end();
} }
std::shared_ptr<TLuaVoidResult> TLuaEngine::EnqueueScript(TLuaStateId StateID, const TLuaChunk& Script) { std::shared_ptr<TLuaResult> TLuaEngine::EnqueueScript(TLuaStateId StateID, const TLuaChunk& Script) {
std::unique_lock Lock(mLuaStatesMutex); std::unique_lock Lock(mLuaStatesMutex);
return mLuaStates.at(StateID)->EnqueueScript(Script); return mLuaStates.at(StateID)->EnqueueScript(Script);
} }
@@ -444,9 +449,8 @@ void TLuaEngine::EnsureStateExists(TLuaStateId StateId, const std::string& Name,
if (!DontCallOnInit) { if (!DontCallOnInit) {
auto Res = EnqueueFunctionCall(StateId, "onInit", {}, "onInit"); auto Res = EnqueueFunctionCall(StateId, "onInit", {}, "onInit");
Res->WaitUntilReady(); Res->WaitUntilReady();
auto Snapshot = Res->GetDetachedSnapshot(); if (Res->Error && Res->ErrorMessage != TLuaEngine::BeamMPFnNotFoundError) {
if (Snapshot.Error && Snapshot.ErrorMessage != TLuaEngine::BeamMPFnNotFoundError) { beammp_lua_error("Calling \"onInit\" on \"" + StateId + "\" failed: " + Res->ErrorMessage);
beammp_lua_error("Calling \"onInit\" on \"" + StateId + "\" failed: " + Snapshot.ErrorMessage);
} }
} }
} }
@@ -458,7 +462,6 @@ void TLuaEngine::RegisterEvent(const std::string& EventName, TLuaStateId StateId
} }
std::set<std::string> TLuaEngine::GetEventHandlersForState(const std::string& EventName, TLuaStateId StateId) { std::set<std::string> TLuaEngine::GetEventHandlersForState(const std::string& EventName, TLuaStateId StateId) {
std::unique_lock Lock(mLuaEventsMutex);
return mLuaEvents[EventName][StateId]; return mLuaEvents[EventName][StateId];
} }
@@ -505,19 +508,23 @@ sol::table TLuaEngine::StateThreadData::Lua_TriggerGlobalEvent(const std::string
sol::variadic_results LocalArgs = JsonStringToArray(Str); sol::variadic_results LocalArgs = JsonStringToArray(Str);
for (const auto& Handler : MyHandlers) { for (const auto& Handler : MyHandlers) {
auto Fn = mStateView[Handler]; auto Res = GetLuaHandler(mStateView, Handler, EventName);
if (Fn.valid()) { if (Res.has_value()) {
sol::function Fn = Res.value();
Fn = AddTraceback(mStateView, Fn);
auto LuaResult = Fn(LocalArgs); auto LuaResult = Fn(LocalArgs);
auto Result = std::make_shared<TLuaResult>(mStateId, Handler); auto Result = std::make_shared<TLuaResult>();
if (LuaResult.valid()) { if (LuaResult.valid()) {
try { Result->Error = false;
Result->MarkReadySuccess(LuaResult); Result->Result = LuaResult;
} catch (const std::exception& e) {
Result->MarkReadyError(fmt::format("Call was successful, but result could not be serialized"));
}
} else { } else {
Result->MarkReadyError("Function result in TriggerGlobalEvent was invalid"); Result->Error = true;
sol::error Err = LuaResult;
Result->ErrorMessage = Err.what();
beammp_errorf("An error occured while executing local event handler \"{}\" for event \"{}\": {}", Handler, EventName, Result->ErrorMessage);
} }
Result->MarkAsReady();
Return.push_back(Result); Return.push_back(Result);
} }
} }
@@ -525,60 +532,26 @@ sol::table TLuaEngine::StateThreadData::Lua_TriggerGlobalEvent(const std::string
sol::table AsyncEventReturn = StateView.create_table(); sol::table AsyncEventReturn = StateView.create_table();
AsyncEventReturn["ReturnValueImpl"] = Return; AsyncEventReturn["ReturnValueImpl"] = Return;
AsyncEventReturn.set_function("IsDone", AsyncEventReturn.set_function("IsDone",
[](const sol::table& Self) -> bool { [&](const sol::table& Self) -> bool {
auto Vector = Self.get<std::vector<std::shared_ptr<TLuaResult>>>("ReturnValueImpl"); auto Vector = Self.get<std::vector<std::shared_ptr<TLuaResult>>>("ReturnValueImpl");
for (const auto& Value : Vector) { for (const auto& Value : Vector) {
if (!Value->IsReady()) { if (!Value->Ready) {
return false; return false;
} }
} }
return true; return true;
}); });
AsyncEventReturn.set_function("GetResults", AsyncEventReturn.set_function("GetResults",
[](const sol::table& Self, sol::this_state State) -> sol::table { [&](const sol::table& Self) -> sol::table {
sol::state_view StateView(State); sol::state_view StateView(mState);
sol::table Result = StateView.create_table(); sol::table Result = StateView.create_table();
auto Vector = Self.get<std::vector<std::shared_ptr<TLuaResult>>>("ReturnValueImpl"); auto Vector = Self.get<std::vector<std::shared_ptr<TLuaResult>>>("ReturnValueImpl");
auto DetachedToLuaObject = [&StateView](const auto& SelfConvert, const TDetachedLuaValue& value) -> sol::object {
return std::visit([&StateView, &SelfConvert](auto&& arg) -> sol::object {
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, TDetachedLuaValue::Array>) {
sol::table Table = StateView.create_table(static_cast<int>(arg.size()), 0);
size_t i = 1;
for (const auto& Elem : arg) {
Table.set(i, SelfConvert(SelfConvert, Elem));
++i;
}
return sol::make_object(StateView, Table);
} else if constexpr (std::is_same_v<T, TDetachedLuaValue::Object>) {
sol::table Table = StateView.create_table();
for (const auto& [Key, Elem] : arg) {
Table.set(Key, SelfConvert(SelfConvert, *Elem));
}
return sol::make_object(StateView, Table);
}
else if constexpr (std::is_same_v<T, bool>)
return sol::make_object(StateView, arg);
else if constexpr (std::is_same_v<T, double>)
return sol::make_object(StateView, arg);
else if constexpr (std::is_same_v<T, int>)
return sol::make_object(StateView, arg);
else if constexpr (std::is_same_v<T, std::string>)
return sol::make_object(StateView, arg);
else if constexpr (std::is_same_v<T, std::monostate>)
return sol::make_object(StateView, sol::lua_nil_t());
else
static_assert(AlwaysFalseV<T>, "non-exhaustive visitor!");
}, value.V);
};
int i = 1; int i = 1;
for (const auto& Value : Vector) { for (const auto& Value : Vector) {
if (!Value->IsReady()) { if (!Value->Ready) {
return sol::lua_nil; return sol::lua_nil;
} }
auto Snapshot = Value->GetDetachedSnapshot(); Result.set(i, Value->Result);
Result.set(i, DetachedToLuaObject(DetachedToLuaObject, Snapshot.Result));
++i; ++i;
} }
return Result; return Result;
@@ -591,20 +564,16 @@ sol::table TLuaEngine::StateThreadData::Lua_TriggerLocalEvent(const std::string&
sol::table Result = mStateView.create_table(); sol::table Result = mStateView.create_table();
int i = 1; int i = 1;
for (const auto& Handler : mEngine->GetEventHandlersForState(EventName, mStateId)) { for (const auto& Handler : mEngine->GetEventHandlersForState(EventName, mStateId)) {
auto Fn = mStateView[Handler]; auto Res = GetLuaHandler(mStateView, Handler, EventName);
if (Fn.valid() && Fn.get_type() == sol::type::function) { if (Res.has_value()) {
sol::function Fn = Res.value();
auto FnRet = Fn(EventArgs); auto FnRet = Fn(EventArgs);
if (FnRet.valid()) { if (FnRet.valid()) {
Result.set(i, FnRet); Result.set(i, FnRet);
++i; ++i;
} else { } else {
std::string ErrStr; sol::error Err = FnRet;
if (FnRet.get_type() == sol::type::string) { beammp_lua_error(std::string("TriggerLocalEvent: ") + Err.what());
ErrStr = FnRet.get<sol::error>().what();
} else {
ErrStr = "(unknown error; error object is not inspectable)";
}
beammp_lua_errorf("TriggerLocalEvent: {}", ErrStr);
} }
} }
} }
@@ -613,37 +582,37 @@ sol::table TLuaEngine::StateThreadData::Lua_TriggerLocalEvent(const std::string&
sol::table TLuaEngine::StateThreadData::Lua_GetPlayerIdentifiers(int ID) { sol::table TLuaEngine::StateThreadData::Lua_GetPlayerIdentifiers(int ID) {
auto MaybeClient = GetClient(mEngine->Server(), ID); auto MaybeClient = GetClient(mEngine->Server(), ID);
if (MaybeClient) { if (MaybeClient && !MaybeClient.value().expired()) {
if (std::shared_ptr<TClient> Locked = MaybeClient.value().lock()) { auto IDs = MaybeClient.value().lock()->GetIdentifiers();
auto IDs = Locked->GetIdentifiers();
if (IDs.empty()) { if (IDs.empty()) {
return sol::lua_nil; return sol::lua_nil;
} }
sol::table Result = mStateView.create_table(); sol::table Result = mStateView.create_table();
for (const auto& Pair : IDs) { for (const auto& Pair : IDs) {
Result.set(Pair.first, Pair.second); Result[Pair.first] = Pair.second;
} }
return Result; return Result;
} } else {
}
return sol::lua_nil; return sol::lua_nil;
} }
}
std::variant<std::string, sol::nil_t> TLuaEngine::StateThreadData::Lua_GetPlayerRole(int ID) { std::variant<std::string, sol::nil_t> TLuaEngine::StateThreadData::Lua_GetPlayerRole(int ID) {
auto MaybeClient = GetClient(mEngine->Server(), ID); auto MaybeClient = GetClient(mEngine->Server(), ID);
if (MaybeClient) { if (MaybeClient) {
if (auto Locked = MaybeClient.value().lock()) { return MaybeClient.value().lock()->GetRoles();
return Locked->GetRoles(); } else {
}
}
return sol::nil; return sol::nil;
} }
}
sol::table TLuaEngine::StateThreadData::Lua_GetPlayers() { sol::table TLuaEngine::StateThreadData::Lua_GetPlayers() {
sol::table Result = mStateView.create_table(); sol::table Result = mStateView.create_table();
mEngine->Server().ForEachClient([&](std::weak_ptr<TClient> Client) -> bool { mEngine->Server().ForEachClient([&](std::weak_ptr<TClient> Client) -> bool {
if (auto Locked = Client.lock()) { if (!Client.expired()) {
Result[Locked->GetID()] = Locked->GetName(); auto locked = Client.lock();
Result[locked->GetID()] = locked->GetName();
} }
return true; return true;
}); });
@@ -653,9 +622,10 @@ sol::table TLuaEngine::StateThreadData::Lua_GetPlayers() {
int TLuaEngine::StateThreadData::Lua_GetPlayerIDByName(const std::string& Name) { int TLuaEngine::StateThreadData::Lua_GetPlayerIDByName(const std::string& Name) {
int Id = -1; int Id = -1;
mEngine->mServer->ForEachClient([&Id, &Name](std::weak_ptr<TClient> Client) -> bool { mEngine->mServer->ForEachClient([&Id, &Name](std::weak_ptr<TClient> Client) -> bool {
if (auto Locked = Client.lock()) { if (!Client.expired()) {
if (Locked->GetName() == Name) { auto locked = Client.lock();
Id = Locked->GetID(); if (locked->GetName() == Name) {
Id = locked->GetID();
return false; return false;
} }
} }
@@ -692,18 +662,17 @@ sol::table TLuaEngine::StateThreadData::Lua_FS_ListDirectories(const std::string
std::string TLuaEngine::StateThreadData::Lua_GetPlayerName(int ID) { std::string TLuaEngine::StateThreadData::Lua_GetPlayerName(int ID) {
auto MaybeClient = GetClient(mEngine->Server(), ID); auto MaybeClient = GetClient(mEngine->Server(), ID);
if (MaybeClient) { if (MaybeClient && !MaybeClient.value().expired()) {
if (auto Locked = MaybeClient.value().lock()) { return MaybeClient.value().lock()->GetName();
return Locked->GetName(); } else {
}
}
return ""; return "";
} }
}
sol::table TLuaEngine::StateThreadData::Lua_GetPlayerVehicles(int ID) { sol::table TLuaEngine::StateThreadData::Lua_GetPlayerVehicles(int ID) {
auto MaybeClient = GetClient(mEngine->Server(), ID); auto MaybeClient = GetClient(mEngine->Server(), ID);
if (MaybeClient) { if (MaybeClient && !MaybeClient.value().expired()) {
if (auto Client = MaybeClient.value().lock()) { auto Client = MaybeClient.value().lock();
TClient::TSetOfVehicleData VehicleData; TClient::TSetOfVehicleData VehicleData;
{ // Vehicle Data Lock Scope { // Vehicle Data Lock Scope
auto LockedData = Client->GetAllCars(); auto LockedData = Client->GetAllCars();
@@ -715,22 +684,22 @@ sol::table TLuaEngine::StateThreadData::Lua_GetPlayerVehicles(int ID) {
sol::state_view StateView(mState); sol::state_view StateView(mState);
sol::table Result = StateView.create_table(); sol::table Result = StateView.create_table();
for (const auto& v : VehicleData) { for (const auto& v : VehicleData) {
Result[v.ID()] = v.DataAsPacket(Client->GetRoles(), Client->GetName(), Client->GetID()).substr(3); Result[v.ID()] = v.Data().substr(3);
} }
return Result; return Result;
} } else
}
return sol::lua_nil; return sol::lua_nil;
} }
std::pair<sol::table, std::string> TLuaEngine::StateThreadData::Lua_GetPositionRaw(int PID, int VID) { std::pair<sol::table, std::string> TLuaEngine::StateThreadData::Lua_GetPositionRaw(int PID, int VID) {
std::pair<sol::table, std::string> Result; std::pair<sol::table, std::string> Result;
auto MaybeClient = GetClient(mEngine->Server(), PID); auto MaybeClient = GetClient(mEngine->Server(), PID);
if (MaybeClient) { if (MaybeClient && !MaybeClient.value().expired()) {
if (auto Client = MaybeClient.value().lock()) { auto Client = MaybeClient.value().lock();
std::string VehiclePos = Client->GetCarPositionRaw(VID); std::string VehiclePos = Client->GetCarPositionRaw(VID);
if (VehiclePos.empty()) { if (VehiclePos.empty()) {
// return std::make_tuple(sol::lua_nil, sol::make_object(StateView, "Vehicle not found"));
Result.second = "Vehicle not found"; Result.second = "Vehicle not found";
return Result; return Result;
} }
@@ -739,13 +708,15 @@ std::pair<sol::table, std::string> TLuaEngine::StateThreadData::Lua_GetPositionR
if (t == sol::lua_nil) { if (t == sol::lua_nil) {
Result.second = "Packet decode failed"; Result.second = "Packet decode failed";
} }
// return std::make_tuple(Result, sol::make_object(StateView, sol::lua_nil));
Result.first = t; Result.first = t;
return Result; return Result;
} } else {
} // return std::make_tuple(sol::lua_nil, sol::make_object(StateView, "Client expired"));
Result.second = "No such player"; Result.second = "No such player";
return Result; return Result;
} }
}
sol::table TLuaEngine::StateThreadData::Lua_HttpCreateConnection(const std::string& host, uint16_t port) { sol::table TLuaEngine::StateThreadData::Lua_HttpCreateConnection(const std::string& host, uint16_t port) {
auto table = mStateView.create_table(); auto table = mStateView.create_table();
@@ -778,34 +749,6 @@ static void AddToTable(sol::table& table, const std::string& left, const T& valu
} }
} }
static bool mDisableMPSet = [] {
auto DisableMPSet = Env::Get(Env::Key::PROVIDER_DISABLE_MP_SET).value_or("false");
return DisableMPSet == "true" || DisableMPSet == "1";
}();
static auto GetSettingName = [](int id) -> const char* {
switch (id) {
case 0:
return "Debug";
case 1:
return "Private";
case 2:
return "MaxCars";
case 3:
return "MaxPlayers";
case 4:
return "Map";
case 5:
return "Name";
case 6:
return "Description";
case 7:
return "InformationPacket";
default:
return "Unknown";
}
};
static void JsonDecodeRecursive(sol::state_view& StateView, sol::table& table, const std::string& left, const nlohmann::json& right) { static void JsonDecodeRecursive(sol::state_view& StateView, sol::table& table, const std::string& left, const nlohmann::json& right) {
switch (right.type()) { switch (right.type()) {
case nlohmann::detail::value_t::null: case nlohmann::detail::value_t::null:
@@ -937,15 +880,7 @@ TLuaEngine::StateThreadData::StateThreadData(const std::string& Name, TLuaStateI
MPTable.set_function("GetPositionRaw", [&](int PID, int VID) -> std::pair<sol::table, std::string> { MPTable.set_function("GetPositionRaw", [&](int PID, int VID) -> std::pair<sol::table, std::string> {
return Lua_GetPositionRaw(PID, VID); return Lua_GetPositionRaw(PID, VID);
}); });
MPTable.set_function("SendChatMessage", [&](sol::variadic_args Args) { MPTable.set_function("SendChatMessage", &LuaAPI::MP::SendChatMessage);
if (Args.size() == 2) {
LuaAPI::MP::SendChatMessage(Args.get<int>(0), Args.get<std::string>(1));
} else if (Args.size() == 3) {
LuaAPI::MP::SendChatMessage(Args.get<int>(0), Args.get<std::string>(1), Args.get<bool>(2));
} else {
beammp_lua_error("SendChatMessage expects 2 or 3 arguments.");
}
});
MPTable.set_function("SendNotification", [&](sol::variadic_args Args) { MPTable.set_function("SendNotification", [&](sol::variadic_args Args) {
if (Args.size() == 2) { if (Args.size() == 2) {
LuaAPI::MP::SendNotification(Args.get<int>(0), Args.get<std::string>(1), "", Args.get<std::string>(1)); LuaAPI::MP::SendNotification(Args.get<int>(0), Args.get<std::string>(1), "", Args.get<std::string>(1));
@@ -957,9 +892,6 @@ TLuaEngine::StateThreadData::StateThreadData(const std::string& Name, TLuaStateI
beammp_lua_error("SendNotification expects 2, 3 or 4 arguments."); beammp_lua_error("SendNotification expects 2, 3 or 4 arguments.");
} }
}); });
MPTable.set_function("ConfirmationDialog", sol::overload(&LuaAPI::MP::ConfirmationDialog, [&](const int& ID, const std::string& Title, const std::string& Body, const sol::table& Buttons, const std::string& InteractionID) {
LuaAPI::MP::ConfirmationDialog(ID, Title, Body, Buttons, InteractionID);
}));
MPTable.set_function("GetPlayers", [&]() -> sol::table { MPTable.set_function("GetPlayers", [&]() -> sol::table {
return Lua_GetPlayers(); return Lua_GetPlayers();
}); });
@@ -1003,13 +935,7 @@ TLuaEngine::StateThreadData::StateThreadData(const std::string& Name, TLuaStateI
MPTable.set_function("CancelEventTimer", [&](const std::string& EventName) { MPTable.set_function("CancelEventTimer", [&](const std::string& EventName) {
mEngine->CancelEventTimers(EventName, mStateId); mEngine->CancelEventTimers(EventName, mStateId);
}); });
if (mDisableMPSet) {
MPTable.set_function("Set", [this](int ConfigID, sol::object NewValue) {
beammp_lua_errorf("A script ({}) tried to call MP.Set to change setting '{}' but this was blocked by your server provider.", mStateId, GetSettingName(ConfigID));
});
} else {
MPTable.set_function("Set", &LuaAPI::MP::Set); MPTable.set_function("Set", &LuaAPI::MP::Set);
}
MPTable.set_function("Get", &LuaAPI::MP::Get); MPTable.set_function("Get", &LuaAPI::MP::Get);
auto UtilTable = StateView.create_named_table("Util"); auto UtilTable = StateView.create_named_table("Util");
@@ -1130,16 +1056,15 @@ TLuaEngine::StateThreadData::StateThreadData(const std::string& Name, TLuaStateI
Start(); Start();
} }
std::shared_ptr<TLuaVoidResult> TLuaEngine::StateThreadData::EnqueueScript(const TLuaChunk& Script) { std::shared_ptr<TLuaResult> TLuaEngine::StateThreadData::EnqueueScript(const TLuaChunk& Script) {
std::unique_lock Lock(mStateExecuteQueueMutex); std::unique_lock Lock(mStateExecuteQueueMutex);
auto Result = std::make_shared<TLuaVoidResult>(mStateId); auto Result = std::make_shared<TLuaResult>();
mStateExecuteQueue.push({ Script, Result }); mStateExecuteQueue.push({ Script, Result });
return Result; return Result;
} }
std::shared_ptr<TLuaResult> TLuaEngine::StateThreadData::EnqueueFunctionCallFromCustomEvent(const std::string& FunctionName, const std::vector<TLuaValue>& 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 // TODO: Document all this
std::unique_lock Lock(mStateFunctionQueueMutex);
decltype(mStateFunctionQueue)::iterator Iter = mStateFunctionQueue.end(); decltype(mStateFunctionQueue)::iterator Iter = mStateFunctionQueue.end();
if (Strategy == CallStrategy::BestEffort) { if (Strategy == CallStrategy::BestEffort) {
Iter = std::find_if(mStateFunctionQueue.begin(), mStateFunctionQueue.end(), Iter = std::find_if(mStateFunctionQueue.begin(), mStateFunctionQueue.end(),
@@ -1148,7 +1073,10 @@ std::shared_ptr<TLuaResult> TLuaEngine::StateThreadData::EnqueueFunctionCallFrom
}); });
} }
if (Iter == mStateFunctionQueue.end()) { if (Iter == mStateFunctionQueue.end()) {
auto Result = std::make_shared<TLuaResult>(mStateId, FunctionName); auto Result = std::make_shared<TLuaResult>();
Result->StateId = mStateId;
Result->Function = FunctionName;
std::unique_lock Lock(mStateFunctionQueueMutex);
mStateFunctionQueue.push_back({ FunctionName, Result, Args, EventName }); mStateFunctionQueue.push_back({ FunctionName, Result, Args, EventName });
mStateFunctionQueueCond.notify_all(); mStateFunctionQueueCond.notify_all();
return Result; return Result;
@@ -1158,7 +1086,9 @@ std::shared_ptr<TLuaResult> TLuaEngine::StateThreadData::EnqueueFunctionCallFrom
} }
std::shared_ptr<TLuaResult> TLuaEngine::StateThreadData::EnqueueFunctionCall(const std::string& FunctionName, const std::vector<TLuaValue>& Args, const std::string& EventName) { std::shared_ptr<TLuaResult> TLuaEngine::StateThreadData::EnqueueFunctionCall(const std::string& FunctionName, const std::vector<TLuaValue>& Args, const std::string& EventName) {
auto Result = std::make_shared<TLuaResult>(mStateId, FunctionName); auto Result = std::make_shared<TLuaResult>();
Result->StateId = mStateId;
Result->Function = FunctionName;
std::unique_lock Lock(mStateFunctionQueueMutex); std::unique_lock Lock(mStateFunctionQueueMutex);
mStateFunctionQueue.push_back({ FunctionName, Result, Args, EventName }); mStateFunctionQueue.push_back({ FunctionName, Result, Args, EventName });
mStateFunctionQueueCond.notify_all(); mStateFunctionQueueCond.notify_all();
@@ -1169,6 +1099,21 @@ void TLuaEngine::StateThreadData::RegisterEvent(const std::string& EventName, co
mEngine->RegisterEvent(EventName, mStateId, FunctionName); mEngine->RegisterEvent(EventName, mStateId, FunctionName);
} }
static sol::protected_function AddTraceback(sol::state_view StateView, sol::protected_function RawFn) {
StateView["INTERNAL_ERROR_HANDLER"] = [](lua_State *L) {
auto Error = sol::stack::get<std::optional<std::string>>(L);
std::string ErrorString = "<Unknown error>";
if (Error.has_value()) {
ErrorString = Error.value();
}
auto DebugTracebackFn = sol::state_view(L).globals().get<sol::table>("debug").get<sol::protected_function>("traceback");
// 2 = start collecting the trace one above the current function (1=current function)
std::string Traceback = DebugTracebackFn(ErrorString, 2);
return sol::stack::push(L, Traceback);
};
return sol::protected_function(RawFn, StateView["INTERNAL_ERROR_HANDLER"]);
}
void TLuaEngine::StateThreadData::operator()() { void TLuaEngine::StateThreadData::operator()() {
RegisterThread("Lua:" + mStateId); RegisterThread("Lua:" + mStateId);
while (!Application::IsShuttingDown()) { while (!Application::IsShuttingDown()) {
@@ -1207,14 +1152,14 @@ void TLuaEngine::StateThreadData::operator()() {
sol::state_view StateView(mState); sol::state_view StateView(mState);
auto Res = StateView.safe_script(*S.first.Content, sol::script_pass_on_error, S.first.FileName); auto Res = StateView.safe_script(*S.first.Content, sol::script_pass_on_error, S.first.FileName);
if (Res.valid()) { if (Res.valid()) {
// Script-load completion should not serialize the script's return value. S.second->Error = false;
// A loaded chunk may legally return non-serializable Lua values such as S.second->Result = std::move(Res);
// functions or function tables. For this reason, we don't pass anything
// to the result here.
S.second->MarkReadySuccess();
} else { } else {
S.second->MarkReadyError(std::move(Res)); S.second->Error = true;
sol::error Err = Res;
S.second->ErrorMessage = Err.what();
} }
S.second->MarkAsReady();
} }
} }
{ // StateFunctionQueue Scope { // StateFunctionQueue Scope
@@ -1231,54 +1176,61 @@ void TLuaEngine::StateThreadData::operator()() {
auto& Result = TheQueuedFunction.Result; auto& Result = TheQueuedFunction.Result;
auto Args = TheQueuedFunction.Args; auto Args = TheQueuedFunction.Args;
// TODO: Use TheQueuedFunction.EventName for errors, warnings, etc // TODO: Use TheQueuedFunction.EventName for errors, warnings, etc
Result->SetOwnerState(mStateId); Result->StateId = mStateId;
sol::state_view StateView(mState); sol::state_view StateView(mState);
auto Fn = StateView[FnName];
if (Fn.valid() && Fn.get_type() == sol::type::function) { auto Res = GetLuaHandler(StateView, FnName, TheQueuedFunction.EventName);
if (Res.has_value()) {
sol::function Fn = Res.value();
std::vector<sol::object> LuaArgs; std::vector<sol::object> LuaArgs;
for (const auto& Arg : Args) { for (const auto& Arg : Args) {
if (Arg.valueless_by_exception()) { if (Arg.valueless_by_exception()) {
continue; continue;
} }
std::visit([&LuaArgs, &StateView, this](const auto& arg) { switch (Arg.index()) {
using T = std::decay_t<decltype(arg)>; case TLuaType::String:
if constexpr (std::is_same_v<T, std::string>) { LuaArgs.push_back(sol::make_object(StateView, std::get<std::string>(Arg)));
LuaArgs.push_back(sol::make_object(StateView, arg)); break;
} else if constexpr (std::is_same_v<T, int>) { case TLuaType::Int:
LuaArgs.push_back(sol::make_object(StateView, arg)); LuaArgs.push_back(sol::make_object(StateView, std::get<int>(Arg)));
} else if constexpr (std::is_same_v<T, bool>) { break;
LuaArgs.push_back(sol::make_object(StateView, arg)); case TLuaType::Json: {
} else if constexpr (std::is_same_v<T, JsonString>) { auto LocalArgs = JsonStringToArray(std::get<JsonString>(Arg));
auto LocalArgs = JsonStringToArray(arg);
LuaArgs.insert(LuaArgs.end(), LocalArgs.begin(), LocalArgs.end()); LuaArgs.insert(LuaArgs.end(), LocalArgs.begin(), LocalArgs.end());
} else if constexpr (std::is_same_v<T, std::monostate>) { break;
beammp_lua_error("Unknown argument type, passed as nil"); }
LuaArgs.push_back(sol::lua_nil_t()); case TLuaType::Bool:
} else if constexpr (std::is_same_v<T, std::unordered_map<std::string, std::string>>) { LuaArgs.push_back(sol::make_object(StateView, std::get<bool>(Arg)));
break;
case TLuaType::StringStringMap: {
auto Map = std::get<std::unordered_map<std::string, std::string>>(Arg);
auto Table = StateView.create_table(); auto Table = StateView.create_table();
for (const auto& [k, v] : arg) { for (const auto& [k, v] : Map) {
Table[k] = v; Table[k] = v;
} }
LuaArgs.push_back(Table); LuaArgs.push_back(sol::make_object(StateView, Table));
} else if constexpr (std::is_same_v<T, float>) { break;
LuaArgs.push_back(sol::make_object(StateView, arg));
} else {
static_assert(AlwaysFalseV<T>, "unhandled variant");
} }
}, Arg); default:
beammp_error("Unknown argument type, passed as nil");
break;
} }
}
Fn = AddTraceback(StateView, Fn);
auto Res = Fn(sol::as_args(LuaArgs)); auto Res = Fn(sol::as_args(LuaArgs));
if (Res.valid()) { if (Res.valid()) {
try { Result->Error = false;
Result->MarkReadySuccess(std::move(Res)); Result->Result = std::move(Res);
} catch (const std::exception& e) {
Result->MarkReadyError(fmt::format("Call was successful, but result could not be serialized"));
}
} else { } else {
Result->MarkReadyError(std::move(Res)); Result->Error = true;
sol::error Err = Res;
Result->ErrorMessage = Err.what();
} }
Result->MarkAsReady();
} else { } else {
Result->MarkReadyError(BeamMPFnNotFoundError); Result->Error = true;
Result->ErrorMessage = BeamMPFnNotFoundError; // special error kind that we can ignore later
Result->MarkAsReady();
} }
auto ProfEnd = prof::now(); auto ProfEnd = prof::now();
auto ProfDuration = prof::duration(ProfStart, ProfEnd); auto ProfDuration = prof::duration(ProfStart, ProfEnd);
@@ -1288,7 +1240,7 @@ void TLuaEngine::StateThreadData::operator()() {
} }
} }
std::queue<std::pair<TLuaChunk, std::shared_ptr<TLuaVoidResult>>> TLuaEngine::StateThreadData::Debug_GetStateExecuteQueue() { std::queue<std::pair<TLuaChunk, std::shared_ptr<TLuaResult>>> TLuaEngine::StateThreadData::Debug_GetStateExecuteQueue() {
std::unique_lock Lock(mStateExecuteQueueMutex); std::unique_lock Lock(mStateExecuteQueueMutex);
return mStateExecuteQueue; return mStateExecuteQueue;
} }
@@ -1331,6 +1283,21 @@ void TLuaEngine::StateThreadData::AddPath(const fs::path& Path) {
mPaths.push(Path); mPaths.push(Path);
} }
void TLuaResult::MarkAsReady() {
{
std::lock_guard<std::mutex> readyLock(*this->ReadyMutex);
this->Ready = true;
}
this->ReadyCondition->notify_all();
}
void TLuaResult::WaitUntilReady() {
std::unique_lock readyLock(*this->ReadyMutex);
// wait if not ready yet
if (!this->Ready)
this->ReadyCondition->wait(readyLock);
}
TLuaChunk::TLuaChunk(std::shared_ptr<std::string> Content, std::string FileName, std::string PluginPath) TLuaChunk::TLuaChunk(std::shared_ptr<std::string> Content, std::string FileName, std::string PluginPath)
: Content(Content) : Content(Content)
, FileName(FileName) , FileName(FileName)
@@ -1345,110 +1312,3 @@ bool TLuaEngine::TimedEvent::Expired() {
void TLuaEngine::TimedEvent::Reset() { void TLuaEngine::TimedEvent::Reset() {
LastCompletion = std::chrono::high_resolution_clock::now(); LastCompletion = std::chrono::high_resolution_clock::now();
} }
TEST_CASE("TLuaEngine ctor & dtor") {
Application::Settings.set(Settings::Key::General_ResourceFolder, "beammp_server_test_resources");
TLuaEngine engine;
const TLuaStateId StateId = "lua_event_contract_test";
engine.EnsureStateExists(StateId, "LuaEventContractTest", true);
// LLM generated test code
auto Script = std::make_shared<std::string>(R"(
function onPlayerAuth(playerName, playerRole, isGuest, identifiers)
if type(playerName) ~= "string" then return "on:bad-playerName-type:" .. type(playerName) end
if type(playerRole) ~= "string" then return "on:bad-playerRole-type:" .. type(playerRole) end
if type(isGuest) ~= "boolean" then return "on:bad-isGuest-type:" .. type(isGuest) end
if type(identifiers) ~= "table" then return "on:bad-identifiers-type:" .. type(identifiers) end
return "on:" .. playerName .. ":" .. playerRole .. ":" .. tostring(isGuest) .. ":" .. tostring(identifiers.ip) .. ":" .. tostring(identifiers.beammp)
end
function postPlayerAuth(isDenied, reason, playerName, playerRole, isGuest, identifiers)
if type(isDenied) ~= "boolean" then return "post:bad-isDenied-type:" .. type(isDenied) end
if type(reason) ~= "string" then return "post:bad-reason-type:" .. type(reason) end
if type(playerName) ~= "string" then return "post:bad-playerName-type:" .. type(playerName) end
if type(playerRole) ~= "string" then return "post:bad-playerRole-type:" .. type(playerRole) end
if type(isGuest) ~= "boolean" then return "post:bad-isGuest-type:" .. type(isGuest) end
if type(identifiers) ~= "table" then return "post:bad-identifiers-type:" .. type(identifiers) end
return "post:" .. tostring(isDenied) .. ":" .. reason .. ":" .. playerName .. ":" .. playerRole .. ":" .. tostring(isGuest) .. ":" .. tostring(identifiers.ip) .. ":" .. tostring(identifiers.beammp)
end
function arrayBoundaryHandler()
return { "first", "second", [4] = true }
end
function verifyArrayBoundaryRoundtrip()
local pending = MP.TriggerGlobalEvent("arrayBoundaryEvent")
if not pending:IsDone() then
return "not_done"
end
local results = pending:GetResults()
if type(results) ~= "table" then
return "bad_results_type:" .. type(results)
end
if type(results[1]) ~= "table" then
return "bad_item_type:" .. type(results[1])
end
local arr = results[1]
return tostring(arr[1]) .. "|" .. tostring(arr[2]) .. "|" .. tostring(arr[4]) .. "|" .. tostring(arr[3] == nil)
end
MP.RegisterEvent("onPlayerAuth", "onPlayerAuth")
MP.RegisterEvent("postPlayerAuth", "postPlayerAuth")
MP.RegisterEvent("arrayBoundaryEvent", "arrayBoundaryHandler")
)");
auto LoadResult = engine.EnqueueScript(StateId, TLuaChunk(Script, "event_contract.lua", "beammp_server_test_resources/Server/LuaEventContractTest"));
LoadResult->WaitUntilReady();
auto LoadSnapshot = LoadResult->GetSnapshot();
CHECK(!LoadSnapshot.Error);
const std::unordered_map<std::string, std::string> Identifiers {
{"ip", "410.0.24.1"},
{"beammp", "123456"},
};
auto OnPlayerAuthResults = engine.TriggerEvent(
"onPlayerAuth", "",
std::string("guest8133569"),
std::string("USER"),
true,
Identifiers);
REQUIRE(OnPlayerAuthResults.size() == 1);
TLuaEngine::WaitForAll(OnPlayerAuthResults);
auto OnPlayerAuthSnapshot = OnPlayerAuthResults.front()->GetDetachedSnapshot();
CHECK(!OnPlayerAuthSnapshot.Error);
const auto* OnPlayerAuthValue = std::get_if<std::string>(&OnPlayerAuthSnapshot.Result.V);
REQUIRE(OnPlayerAuthValue != nullptr);
CHECK(*OnPlayerAuthValue == "on:guest8133569:USER:true:410.0.24.1:123456");
auto PostPlayerAuthResults = engine.TriggerEvent(
"postPlayerAuth", "",
false,
std::string(""),
std::string("guest8133569"),
std::string("USER"),
true,
Identifiers);
REQUIRE(PostPlayerAuthResults.size() == 1);
TLuaEngine::WaitForAll(PostPlayerAuthResults);
auto PostPlayerAuthSnapshot = PostPlayerAuthResults.front()->GetDetachedSnapshot();
CHECK(!PostPlayerAuthSnapshot.Error);
const auto* PostPlayerAuthValue = std::get_if<std::string>(&PostPlayerAuthSnapshot.Result.V);
REQUIRE(PostPlayerAuthValue != nullptr);
CHECK(*PostPlayerAuthValue == "post:false::guest8133569:USER:true:410.0.24.1:123456");
auto ArrayRoundtrip = engine.EnqueueFunctionCall(StateId, "verifyArrayBoundaryRoundtrip", {}, "verifyArrayBoundaryRoundtrip");
ArrayRoundtrip->WaitUntilReady();
auto ArrayRoundtripSnapshot = ArrayRoundtrip->GetDetachedSnapshot();
CHECK(!ArrayRoundtripSnapshot.Error);
const auto* ArrayRoundtripValue = std::get_if<std::string>(&ArrayRoundtripSnapshot.Result.V);
REQUIRE(ArrayRoundtripValue != nullptr);
CHECK(*ArrayRoundtripValue == "first|second|true|true");
Application::GracefullyShutdown();
}
+3 -4
View File
@@ -44,7 +44,7 @@ TLuaPlugin::TLuaPlugin(TLuaEngine& Engine, const TLuaPluginConfig& Config, const
std::transform(secondStr.begin(), secondStr.end(), secondStr.begin(), ::tolower); std::transform(secondStr.begin(), secondStr.end(), secondStr.begin(), ::tolower);
return firstStr < secondStr; return firstStr < secondStr;
}); });
std::vector<std::pair<fs::path, std::shared_ptr<TLuaVoidResult>>> ResultsToCheck; std::vector<std::pair<fs::path, std::shared_ptr<TLuaResult>>> ResultsToCheck;
for (const auto& Entry : Entries) { for (const auto& Entry : Entries) {
// read in entire file // read in entire file
try { try {
@@ -63,9 +63,8 @@ TLuaPlugin::TLuaPlugin(TLuaEngine& Engine, const TLuaPluginConfig& Config, const
} }
for (auto& Result : ResultsToCheck) { for (auto& Result : ResultsToCheck) {
Result.second->WaitUntilReady(); Result.second->WaitUntilReady();
auto Snapshot = Result.second->GetSnapshot(); if (Result.second->Error) {
if (Snapshot.Error) { beammp_lua_error("Failed: \"" + Result.first.string() + "\": " + Result.second->ErrorMessage);
beammp_lua_error("Failed: \"" + Result.first.string() + "\": " + Snapshot.ErrorMessage);
} }
} }
} }
-425
View File
@@ -1,425 +0,0 @@
// BeamMP, the BeamNG.drive multiplayer mod.
// Copyright (C) 2026 BeamMP Ltd., BeamMP team and contributors.
//
// BeamMP Ltd. can be contacted by electronic mail via contact@beammp.com.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#include "TLuaResult.h"
#include <algorithm>
#include <atomic>
#include <chrono>
#include <cmath>
#include <limits>
#include <sol/unsafe_function_result.hpp>
#include <stdexcept>
#include <thread>
std::ostream& operator<<(std::ostream& os, const TDetachedLuaValue& value) {
std::visit([&os](auto&& arg) {
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, TDetachedLuaValue::Array>) {
size_t i = 0;
for (const auto& val : arg) {
if (i > 0) {
os << ", ";
}
os << val;
++i;
}
} else if constexpr (std::is_same_v<T, TDetachedLuaValue::Object>) {
size_t i = 0;
for (const auto& [key, val] : arg) {
if (i > 0) {
os << ", ";
}
os << key << "=" << val;
++i;
}
} else if constexpr (std::is_same_v<T, bool>)
os << (arg ? "true" : "false");
else if constexpr (std::is_same_v<T, double>)
os << arg;
else if constexpr (std::is_same_v<T, int>)
os << arg;
else if constexpr (std::is_same_v<T, std::string>)
os << arg;
else if constexpr (std::is_same_v<T, std::monostate>)
// monostate means no result value
os << "";
else
static_assert(AlwaysFalseV<T>, "non-exhaustive visitor!");
},
value.V);
return os;
}
void TLuaVoidResult::MarkReadySuccess() {
std::unique_lock Lock(mMutex);
mError = false;
mErrorMessage.clear();
MarkAsReady();
}
void TLuaVoidResult::MarkReadyError(sol::protected_function_result Res) {
std::unique_lock Lock(mMutex);
mError = true;
SetErrorMessageFromResult(Res);
MarkAsReady();
}
void TLuaVoidResult::MarkReadyError(std::string Res) {
std::unique_lock Lock(mMutex);
mError = true;
mErrorMessage = std::move(Res);
MarkAsReady();
}
bool TLuaVoidResult::IsReady() const {
std::unique_lock Lock(mMutex);
return mReady;
}
bool TLuaVoidResult::IsError() const {
std::unique_lock Lock(mMutex);
return mError;
}
TLuaVoidResult::Snapshot TLuaVoidResult::GetSnapshot() const {
std::unique_lock Lock(mMutex);
Snapshot snapshot {
.Error = mError,
.Ready = mReady,
.ErrorMessage = mErrorMessage,
.StateId = mStateId,
};
return snapshot;
}
void TLuaVoidResult::MarkAsReady() {
mReady = true;
mReadyCondition.notify_all();
}
void TLuaVoidResult::WaitUntilReady() {
std::unique_lock Lock(mMutex);
while (!mReady)
mReadyCondition.wait_for(Lock, std::chrono::milliseconds(50),
[this] {
return mReady;
});
}
void TLuaResult::MarkReadySuccess(sol::object Res) {
std::unique_lock Lock(mMutex);
mError = false;
mDetachedResult = Freeze(Res);
MarkAsReady();
}
void TLuaResult::MarkReadySuccessNoResult() {
std::unique_lock Lock(mMutex);
mError = false;
mDetachedResult = { { std::monostate { } } };
MarkAsReady();
}
void TLuaResult::MarkReadyError(sol::protected_function_result Res) {
std::unique_lock Lock(mMutex);
mError = true;
SetErrorMessageFromResult(Res);
MarkAsReady();
}
void TLuaResult::MarkReadyError(std::string Res) {
std::unique_lock Lock(mMutex);
mError = true;
mErrorMessage = std::move(Res);
MarkAsReady();
}
bool TLuaResult::IsReady() const {
std::unique_lock Lock(mMutex);
return mReady;
}
bool TLuaResult::IsError() const {
std::unique_lock Lock(mMutex);
return mError;
}
TLuaStateId TLuaResult::OwnerState() const {
std::unique_lock Lock(mMutex);
// copy
return mStateId;
}
void TLuaResult::SetOwnerState(TLuaStateId StateId) {
std::unique_lock Lock(mMutex);
mStateId = std::move(StateId);
}
TLuaResult::DetachedSnapshot TLuaResult::GetDetachedSnapshot() const {
std::unique_lock Lock(mMutex);
DetachedSnapshot snapshot {
.Error = mError,
.Ready = mReady,
.ErrorMessage = mErrorMessage,
.Result = mDetachedResult,
.StateId = mStateId,
.Function = mFunction,
};
return snapshot;
}
TDetachedLuaValue TLuaResult::Freeze(const sol::object& o, int depth) {
if (depth > 64)
throw std::runtime_error("max depth (64) reached");
switch (o.get_type()) {
case sol::type::lua_nil:
return { { std::monostate { } } };
case sol::type::boolean:
return { { o.as<bool>() } };
case sol::type::number: {
if (o.is<int>()) {
return { { o.as<int>() } };
} else {
return { { o.as<double>() } };
}
}
case sol::type::string:
return { { o.as<std::string>() } };
case sol::type::table: {
TDetachedLuaValue::Object ObjectOut;
// numeric stuff is for arrays
std::vector<std::pair<size_t, TDetachedLuaValue>> NumericEntries;
bool HasNonStringOrNumericKey = false;
size_t MaxNumericIndex = 0;
for (auto&& [k, v] : o.as<sol::table>()) {
if (k.is<std::string>()) {
ObjectOut.emplace(k.as<std::string>(), std::make_shared<TDetachedLuaValue>(std::move(Freeze(v, depth + 1))));
continue;
}
// Thanks to lua handling arrays in a weird way, and because they can also be sparse, this weird code is needed.
if (k.get_type() == sol::type::number) {
const double NumericKey = k.as<double>();
const size_t CandidateIndex = static_cast<size_t>(NumericKey);
// unsure if we need to do this, or if we can do the same int check we do in other places, but this works well
const bool IsPositiveInteger = std::isfinite(NumericKey)
&& NumericKey >= 1.0
&& std::fabs(NumericKey - static_cast<double>(CandidateIndex)) <= std::numeric_limits<double>::epsilon();
if (IsPositiveInteger) {
const auto Index = CandidateIndex;
MaxNumericIndex = std::max(MaxNumericIndex, Index);
NumericEntries.emplace_back(Index, Freeze(v, depth + 1));
continue;
}
}
HasNonStringOrNumericKey = true;
}
// Pure numeric-keyed tables are reconstructed as Lua arrays (1-based indexes).
// This preserves array semantics across this serialization boundary :^)
if (!NumericEntries.empty() && ObjectOut.empty() && !HasNonStringOrNumericKey) {
TDetachedLuaValue::Array ArrayOut(MaxNumericIndex);
for (const auto& [Index, Value] : NumericEntries) {
if (Index > 0 && Index <= ArrayOut.size()) {
ArrayOut[Index - 1] = Value;
}
}
return { { std::move(ArrayOut) } };
}
return { { std::move(ObjectOut) } };
}
default:
throw std::runtime_error("unsupported Lua type for cross-thread snapshot");
}
}
void TLuaResult::MarkAsReady() {
mReady = true;
mReadyCondition.notify_all();
}
void TLuaResult::WaitUntilReady() {
std::unique_lock Lock(mMutex);
while (!mReady)
mReadyCondition.wait_for(Lock, std::chrono::milliseconds(50),
[this] {
return mReady;
});
}
TEST_CASE("TLuaInStateResult MarkReadyError(string) marks ready and wakes waiters") {
TLuaVoidResult result("state_local");
std::atomic<bool> waiterDone { false };
auto waiter = std::thread([&] {
result.WaitUntilReady();
waiterDone.store(true, std::memory_order_release);
});
std::this_thread::sleep_for(std::chrono::milliseconds(20));
CHECK_FALSE(waiterDone.load(std::memory_order_acquire));
result.MarkReadyError(std::string("boom"));
waiter.join();
CHECK(result.IsReady());
const auto snapshot = result.GetSnapshot();
CHECK(snapshot.Ready);
CHECK(snapshot.Error);
CHECK(snapshot.ErrorMessage == "boom");
CHECK(snapshot.StateId == "state_local");
}
TEST_CASE("TLuaInStateResult MarkReadySuccess clears error state") {
TLuaVoidResult result("state_local_success");
result.MarkReadySuccess();
CHECK(result.IsReady());
CHECK_FALSE(result.IsError());
const auto snapshot = result.GetSnapshot();
CHECK(snapshot.Ready);
CHECK_FALSE(snapshot.Error);
CHECK(snapshot.ErrorMessage.empty());
}
TEST_CASE("TLuaResult MarkReadyError(string) marks ready and wakes waiters") {
TLuaResult result("state_a", "fn_a");
std::atomic<bool> waiterDone { false };
auto waiter = std::thread([&] {
result.WaitUntilReady();
waiterDone.store(true, std::memory_order_release);
});
std::this_thread::sleep_for(std::chrono::milliseconds(20));
CHECK_FALSE(waiterDone.load(std::memory_order_acquire));
result.MarkReadyError(std::string("boom"));
waiter.join();
CHECK(result.IsReady());
const auto snapshot = result.GetDetachedSnapshot();
CHECK(snapshot.Ready);
CHECK(snapshot.Error);
CHECK(snapshot.ErrorMessage == "boom");
CHECK(snapshot.StateId == "state_a");
CHECK(snapshot.Function == "fn_a");
}
TEST_CASE("TLuaResult detached snapshot freezes nested string-keyed tables") {
sol::state lua;
TLuaResult result("state_table", "fn_table");
lua.open_libraries(sol::lib::base);
auto outer = lua.create_table();
auto inner = lua.create_table();
inner["k"] = std::string("v");
outer["flag"] = true;
outer["msg"] = std::string("hello");
outer["inner"] = inner;
outer[1] = std::string("ignored_numeric_key");
result.MarkReadySuccess(sol::make_object(lua.lua_state(), outer));
const auto detached = result.GetDetachedSnapshot();
CHECK(detached.Ready);
CHECK_FALSE(detached.Error);
const auto* object = std::get_if<TDetachedLuaValue::Object>(&detached.Result.V);
REQUIRE(object != nullptr);
CHECK(object->contains("flag"));
CHECK(object->contains("msg"));
CHECK(object->contains("inner"));
CHECK_FALSE(object->contains("1"));
const auto* flag = std::get_if<bool>(&object->at("flag")->V);
REQUIRE(flag != nullptr);
CHECK(*flag);
const auto* msg = std::get_if<std::string>(&object->at("msg")->V);
REQUIRE(msg != nullptr);
CHECK(*msg == "hello");
const auto* innerObj = std::get_if<TDetachedLuaValue::Object>(&object->at("inner")->V);
REQUIRE(innerObj != nullptr);
REQUIRE(innerObj->contains("k"));
const auto* innerValue = std::get_if<std::string>(&innerObj->at("k")->V);
REQUIRE(innerValue != nullptr);
CHECK(*innerValue == "v");
}
TEST_CASE("TLuaResult detached snapshot preserves numeric array tables") {
sol::state lua;
TLuaResult result("state_array", "fn_array");
lua.open_libraries(sol::lib::base);
auto arr = lua.create_table();
arr[1] = std::string("a");
arr[2] = 42;
arr[4] = true; // keep sparse indexes
result.MarkReadySuccess(sol::make_object(lua.lua_state(), arr));
const auto detached = result.GetDetachedSnapshot();
CHECK(detached.Ready);
CHECK_FALSE(detached.Error);
const auto* array = std::get_if<TDetachedLuaValue::Array>(&detached.Result.V);
REQUIRE(array != nullptr);
REQUIRE(array->size() == 4);
const auto* v1 = std::get_if<std::string>(&(*array)[0].V);
REQUIRE(v1 != nullptr);
CHECK(*v1 == "a");
const auto* v2 = std::get_if<int>(&(*array)[1].V);
REQUIRE(v2 != nullptr);
CHECK(*v2 == 42);
CHECK(std::holds_alternative<std::monostate>((*array)[2].V));
const auto* v4 = std::get_if<bool>(&(*array)[3].V);
REQUIRE(v4 != nullptr);
CHECK(*v4);
}
TEST_CASE("TLuaResult MarkReadySuccess throws on unsupported Lua function value") {
sol::state lua;
TLuaResult result("state_fn", "fn_fn");
lua.open_libraries(sol::lib::base);
lua["f"] = [] { return 1; };
const sol::table globals = lua.globals();
const sol::protected_function fn = globals.get<sol::protected_function>("f");
const sol::object fnObj = sol::make_object(lua.lua_state(), fn);
CHECK_THROWS_AS(result.MarkReadySuccess(fnObj), std::runtime_error);
CHECK_FALSE(result.IsReady());
}
TEST_CASE("TLuaResult MarkReadySuccessNoResult stores monostate") {
TLuaResult result("state_empty", "fn_empty");
result.MarkReadySuccessNoResult();
CHECK(result.IsReady());
const auto snapshot = result.GetDetachedSnapshot();
CHECK_FALSE(snapshot.Error);
CHECK(std::holds_alternative<std::monostate>(snapshot.Result.V));
}
+121 -431
View File
@@ -20,7 +20,6 @@
#include "Client.h" #include "Client.h"
#include "Common.h" #include "Common.h"
#include "LuaAPI.h" #include "LuaAPI.h"
#include "TConnectionLimiter.h"
#include "THeartbeatThread.h" #include "THeartbeatThread.h"
#include "TLuaEngine.h" #include "TLuaEngine.h"
#include "TScopedTimer.h" #include "TScopedTimer.h"
@@ -28,27 +27,15 @@
#include <CustomAssert.h> #include <CustomAssert.h>
#include <Http.h> #include <Http.h>
#include <array> #include <array>
#include <boost/asio/bind_executor.hpp>
#include <boost/asio/ip/address.hpp> #include <boost/asio/ip/address.hpp>
#include <boost/asio/ip/address_v4.hpp> #include <boost/asio/ip/address_v4.hpp>
#include <boost/asio/ip/address_v6.hpp> #include <boost/asio/ip/address_v6.hpp>
#include <boost/asio/ip/v6_only.hpp> #include <boost/asio/ip/v6_only.hpp>
#include <boost/asio/socket_base.hpp>
#include <boost/asio/steady_timer.hpp>
#include <condition_variable>
#include <cstring> #include <cstring>
#include <memory>
#include <openssl/err.h>
#include <openssl/rand.h>
#include <variant>
#include <zlib.h> #include <zlib.h>
typedef boost::asio::detail::socket_option::integer<SOL_SOCKET, SO_RCVTIMEO> rcv_timeout_option; typedef boost::asio::detail::socket_option::integer<SOL_SOCKET, SO_RCVTIMEO> rcv_timeout_option;
static constexpr uint8_t MAX_CONCURRENT_CONNECTIONS = 10;
static constexpr uint8_t MAX_GLOBAL_CONNECTIONS = 128;
static constexpr uint8_t READ_TIMEOUT_S = 10; // seconds
std::vector<uint8_t> StringToVector(const std::string& Str) { std::vector<uint8_t> StringToVector(const std::string& Str) {
return std::vector<uint8_t>(Str.data(), Str.data() + Str.size()); return std::vector<uint8_t>(Str.data(), Str.data() + Str.size());
} }
@@ -62,42 +49,22 @@ static void CompressProperly(std::vector<uint8_t>& Data) {
Data = CombinedData; Data = CombinedData;
} }
// for unit-tests, otherwise unused
static bool OpenLoopbackSocketPair(io_context& IoCtx, ip::tcp::socket& ClientSocket, ip::tcp::socket& ServerSocket, boost::system::error_code& Ec) {
ip::tcp::acceptor Acceptor(IoCtx);
Acceptor.open(ip::tcp::v4(), Ec);
if (Ec) {
return true;
}
Acceptor.bind(ip::tcp::endpoint(ip::address_v4::loopback(), 0), Ec);
if (Ec) {
return true;
}
Acceptor.listen(socket_base::max_listen_connections, Ec);
if (Ec) {
return true;
}
const auto Port = Acceptor.local_endpoint(Ec).port();
if (Ec) {
return true;
}
ClientSocket.connect(ip::tcp::endpoint(ip::address_v4::loopback(), Port), Ec);
if (Ec) {
return true;
}
Acceptor.accept(ServerSocket, Ec);
return true;
}
TNetwork::TNetwork(TServer& Server, TPPSMonitor& PPSMonitor, TResourceManager& ResourceManager) TNetwork::TNetwork(TServer& Server, TPPSMonitor& PPSMonitor, TResourceManager& ResourceManager)
: mServer(Server) : mServer(Server)
, mPPSMonitor(PPSMonitor) , mPPSMonitor(PPSMonitor)
, mUDPSock(Server.IoCtx()) , mUDPSock(Server.IoCtx())
, mResourceManager(ResourceManager) , mResourceManager(ResourceManager) {
, mConnectionLimiter(MAX_CONCURRENT_CONNECTIONS, MAX_GLOBAL_CONNECTIONS) {
Application::SetSubsystemStatus("TCPNetwork", Application::Status::Starting); Application::SetSubsystemStatus("TCPNetwork", Application::Status::Starting);
Application::SetSubsystemStatus("UDPNetwork", Application::Status::Starting); Application::SetSubsystemStatus("UDPNetwork", Application::Status::Starting);
Application::RegisterShutdownHandler([&] {
beammp_debug("Kicking all players due to shutdown");
Server.ForEachClient([&](std::weak_ptr<TClient> client) -> bool {
if (!client.expired()) {
ClientKick(*client.lock(), "Server shutdown");
}
return true;
});
});
Application::RegisterShutdownHandler([&] { Application::RegisterShutdownHandler([&] {
Application::SetSubsystemStatus("UDPNetwork", Application::Status::ShuttingDown); Application::SetSubsystemStatus("UDPNetwork", Application::Status::ShuttingDown);
if (mUDPThread.joinable()) { if (mUDPThread.joinable()) {
@@ -118,17 +85,9 @@ TNetwork::TNetwork(TServer& Server, TPPSMonitor& PPSMonitor, TResourceManager& R
void TNetwork::UDPServerMain() { void TNetwork::UDPServerMain() {
RegisterThread("UDPServer"); RegisterThread("UDPServer");
// listen on all ipv6 addresses
ip::udp::endpoint UdpListenEndpoint(ip::make_address("::"), Application::Settings.getAsInt(Settings::Key::General_Port));
boost::system::error_code ec; boost::system::error_code ec;
auto address = boost::asio::ip::make_address(Application::Settings.getAsString(Settings::Key::General_IP), ec);
if (ec) {
beammp_errorf("Failed to parse IP: {}", ec.message());
Application::GracefullyShutdown();
}
boost::asio::ip::udp::endpoint UdpListenEndpoint(address, Application::Settings.getAsInt(Settings::Key::General_Port));
mUDPSock.open(UdpListenEndpoint.protocol(), ec); mUDPSock.open(UdpListenEndpoint.protocol(), ec);
if (ec) { if (ec) {
beammp_error("open() failed: " + ec.message()); beammp_error("open() failed: " + ec.message());
@@ -148,17 +107,17 @@ void TNetwork::UDPServerMain() {
Application::GracefullyShutdown(); Application::GracefullyShutdown();
} }
Application::SetSubsystemStatus("UDPNetwork", Application::Status::Good); Application::SetSubsystemStatus("UDPNetwork", Application::Status::Good);
beammp_info(("Vehicle data network online on port ") + std::to_string(UdpListenEndpoint.port()) + (" with a Max of ") beammp_info(("Vehicle data network online on port ") + std::to_string(Application::Settings.getAsInt(Settings::Key::General_Port)) + (" with a Max of ")
+ std::to_string(Application::Settings.getAsInt(Settings::Key::General_MaxPlayers)) + (" Clients")); + std::to_string(Application::Settings.getAsInt(Settings::Key::General_MaxPlayers)) + (" Clients"));
while (!Application::IsShuttingDown()) { while (!Application::IsShuttingDown()) {
try { try {
boost::asio::ip::udp::endpoint remote_client_ep { }; ip::udp::endpoint remote_client_ep {};
std::vector<uint8_t> Data = UDPRcvFromClient(remote_client_ep); std::vector<uint8_t> Data = UDPRcvFromClient(remote_client_ep);
if (Data.empty()) { if (Data.empty()) {
continue; continue;
} }
if (Data.size() == 1 && Data.at(0) == 'P') { if (Data.size() == 1 && Data.at(0) == 'P') {
mUDPSock.send_to(boost::asio::const_buffer("P", 1), remote_client_ep, { }, ec); mUDPSock.send_to(const_buffer("P", 1), remote_client_ep, {}, ec);
// ignore errors // ignore errors
(void)ec; (void)ec;
continue; continue;
@@ -172,37 +131,30 @@ void TNetwork::UDPServerMain() {
std::shared_ptr<TClient> Client; std::shared_ptr<TClient> Client;
{ {
ReadLock Lock(mServer.GetClientMutex()); ReadLock Lock(mServer.GetClientMutex());
if (auto Locked = ClientPtr.lock()) { if (!ClientPtr.expired()) {
Client = std::move(Locked); Client = ClientPtr.lock();
} else } else
return true; return true;
} }
if (Client->GetID() == ID) { if (Client->GetID() == ID) {
if (Client->GetUDPAddr() == boost::asio::ip::udp::endpoint { } && !Client->IsUDPConnected() && !Client->GetMagic().empty()) { // not initialized yet
if (Data.size() != 66) { if (Client->GetUDPAddr() == ip::udp::endpoint {} || !Client->IsUDPConnected()) {
beammp_debugf("Invalid size for UDP value. IP: {} ID: {}", remote_client_ep.address().to_string(), ID); // same IP (just a sanity check)
return false; if (remote_client_ep.address() == Client->GetTCPSock().remote_endpoint().address()) {
}
const std::vector Magic(Data.begin() + 2, Data.end());
if (Magic != Client->GetMagic()) {
beammp_debugf("Invalid value for UDP IP: {} ID: {}", remote_client_ep.address().to_string(), ID);
return false;
}
Client->SetMagic({ });
Client->SetUDPAddr(remote_client_ep); Client->SetUDPAddr(remote_client_ep);
Client->SetIsUDPConnected(true); Client->SetIsUDPConnected(true);
beammp_debugf("UDP connected for client {}", ID);
} else {
beammp_debugf("Denied initial UDP packet due to IP mismatch");
return false; return false;
} }
}
if (Client->GetUDPAddr() == remote_client_ep) { if (Client->GetUDPAddr() == remote_client_ep) {
Data.erase(Data.begin(), Data.begin() + 2); Data.erase(Data.begin(), Data.begin() + 2);
mServer.GlobalParser(ClientPtr, std::move(Data), mPPSMonitor, *this, true); mServer.GlobalParser(ClientPtr, std::move(Data), mPPSMonitor, *this);
} else { } else {
beammp_debugf("Ignored UDP packet for Client {} due to remote address mismatch. Source: {}, Client: {}", ID, remote_client_ep.address().to_string(), Client->GetUDPAddr().address().to_string()); beammp_debugf("Ignored UDP packet due to remote address mismatch");
return false; return false;
} }
} }
@@ -218,17 +170,12 @@ void TNetwork::UDPServerMain() {
void TNetwork::TCPServerMain() { void TNetwork::TCPServerMain() {
RegisterThread("TCPServer"); RegisterThread("TCPServer");
// listen on all ipv6 addresses
auto port = uint16_t(Application::Settings.getAsInt(Settings::Key::General_Port));
ip::tcp::endpoint ListenEp(ip::make_address("::"), port);
beammp_infof("Listening on 0.0.0.0:{0} and [::]:{0}", port);
ip::tcp::socket Listener(mServer.IoCtx());
boost::system::error_code ec; boost::system::error_code ec;
auto address = boost::asio::ip::make_address(Application::Settings.getAsString(Settings::Key::General_IP), ec);
if (ec) {
beammp_errorf("Failed to parse IP: {}", ec.message());
return;
}
boost::asio::ip::tcp::endpoint ListenEp(address,
uint16_t(Application::Settings.getAsInt(Settings::Key::General_Port)));
boost::asio::ip::tcp::socket Listener(mServer.IoCtx());
Listener.open(ListenEp.protocol(), ec); Listener.open(ListenEp.protocol(), ec);
if (ec) { if (ec) {
beammp_errorf("Failed to open socket: {}", ec.message()); beammp_errorf("Failed to open socket: {}", ec.message());
@@ -253,7 +200,7 @@ void TNetwork::TCPServerMain() {
ec.message()); ec.message());
} }
boost::asio::ip::tcp::acceptor Acceptor(mServer.IoCtx(), ListenEp); ip::tcp::acceptor Acceptor(mServer.IoCtx(), ListenEp);
Acceptor.listen(socket_base::max_listen_connections, ec); Acceptor.listen(socket_base::max_listen_connections, ec);
if (ec) { if (ec) {
beammp_errorf("listen() failed, which is needed for the server to operate. " beammp_errorf("listen() failed, which is needed for the server to operate. "
@@ -262,7 +209,6 @@ void TNetwork::TCPServerMain() {
Application::GracefullyShutdown(); Application::GracefullyShutdown();
} }
Application::SetSubsystemStatus("TCPNetwork", Application::Status::Good); Application::SetSubsystemStatus("TCPNetwork", Application::Status::Good);
beammp_infof("Listening on {0} port {1}", ListenEp.address().to_string(), static_cast<uint16_t>(ListenEp.port()));
beammp_info("Vehicle event network online"); beammp_info("Vehicle event network online");
do { do {
try { try {
@@ -270,26 +216,13 @@ void TNetwork::TCPServerMain() {
beammp_debug("shutdown during TCP wait for accept loop"); beammp_debug("shutdown during TCP wait for accept loop");
break; break;
} }
boost::asio::ip::tcp::socket ClientSocket = Acceptor.accept(ec); ip::tcp::endpoint ClientEp;
ip::tcp::socket ClientSocket = Acceptor.accept(ClientEp, ec);
if (ec) { if (ec) {
beammp_errorf("Failed to accept() new client: {}", ec.message()); beammp_errorf("Failed to accept() new client: {}", ec.message());
continue;
} }
boost::asio::ip::tcp::endpoint ClientEp = ClientSocket.remote_endpoint(ec);
if (ec) {
beammp_errorf("Accepted socket but failed to query remote endpoint for IP address: {}", ec.message());
continue;
}
std::string ClientIP = ClientEp.address().to_string();
auto MaybeGuard = mConnectionLimiter.TryAcquire(ClientIP);
if (!MaybeGuard.has_value()) {
beammp_debugf("Connection rejected for {} due to the global or concurrent connection limit", ClientIP);
continue;
}
// move-swap to avoid copy ctor (deleted)
auto Guard = std::move(MaybeGuard.value());
TConnection Conn { std::move(ClientSocket), ClientEp }; TConnection Conn { std::move(ClientSocket), ClientEp };
std::thread ID(&TNetwork::Identify, this, std::move(Conn), std::move(Guard)); std::thread ID(&TNetwork::Identify, this, std::move(Conn));
ID.detach(); // TODO: Add to a queue and attempt to join periodically ID.detach(); // TODO: Add to a queue and attempt to join periodically
} catch (const std::exception& e) { } catch (const std::exception& e) {
beammp_errorf("Exception in accept routine: {}", e.what()); beammp_errorf("Exception in accept routine: {}", e.what());
@@ -302,17 +235,15 @@ void TNetwork::TCPServerMain() {
#include "Json.h" #include "Json.h"
namespace json = rapidjson; namespace json = rapidjson;
void TNetwork::Identify(TConnection&& RawConnection, TConnectionLimiter::TGuard&& Guard) { void TNetwork::Identify(TConnection&& RawConnection) {
RegisterThreadAuto(); RegisterThreadAuto();
char Code; char Code;
boost::system::error_code ec = ReadWithTimeout(RawConnection, &Code, 1, std::chrono::seconds(READ_TIMEOUT_S)); boost::system::error_code ec;
read(RawConnection.Socket, buffer(&Code, 1), ec);
if (ec) { if (ec) {
// TODO: is this right?! // TODO: is this right?!
beammp_debug("Error occured reading code");
RawConnection.Socket.shutdown(socket_base::shutdown_both, ec); RawConnection.Socket.shutdown(socket_base::shutdown_both, ec);
// TODO: is this right too?
RawConnection.Socket.close(ec);
return; return;
} }
std::shared_ptr<TClient> Client { nullptr }; std::shared_ptr<TClient> Client { nullptr };
@@ -323,7 +254,8 @@ void TNetwork::Identify(TConnection&& RawConnection, TConnectionLimiter::TGuard&
beammp_errorf("Old download packet detected - the client is wildly out of date, this will be ignored"); beammp_errorf("Old download packet detected - the client is wildly out of date, this will be ignored");
return; return;
} else if (Code == 'P') { } else if (Code == 'P') {
boost::asio::write(RawConnection.Socket, boost::asio::buffer("P"), ec); boost::system::error_code ec;
write(RawConnection.Socket, buffer("P"), ec);
return; return;
} else if (Code == 'I') { } else if (Code == 'I') {
const std::string Data = Application::Settings.getAsBool(Settings::Key::General_InformationPacket) ? THeartbeatThread::lastCall : ""; const std::string Data = Application::Settings.getAsBool(Settings::Key::General_InformationPacket) ? THeartbeatThread::lastCall : "";
@@ -335,14 +267,14 @@ void TNetwork::Identify(TConnection&& RawConnection, TConnectionLimiter::TGuard&
std::memcpy(ToSend.data() + sizeof(Size), Data.data(), Data.size()); std::memcpy(ToSend.data() + sizeof(Size), Data.data(), Data.size());
boost::system::error_code ec; boost::system::error_code ec;
boost::asio::write(RawConnection.Socket, boost::asio::buffer(ToSend), ec); write(RawConnection.Socket, buffer(ToSend), ec);
} else { } else {
beammp_errorf("Invalid code got in Identify: '{}'", Code); 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, e.what()); beammp_errorf("Error during handling of code {} - client left in invalid state, closing socket: {}", Code, e.what());
boost::system::error_code ec; boost::system::error_code ec;
RawConnection.Socket.shutdown(boost::asio::socket_base::shutdown_both, ec); RawConnection.Socket.shutdown(socket_base::shutdown_both, ec);
if (ec) { if (ec) {
beammp_debugf("Failed to shutdown client socket: {}", ec.message()); beammp_debugf("Failed to shutdown client socket: {}", ec.message());
} }
@@ -353,6 +285,8 @@ void TNetwork::Identify(TConnection&& RawConnection, TConnectionLimiter::TGuard&
} }
} }
std::string HashPassword(const std::string& str) { std::string HashPassword(const std::string& str) {
std::stringstream ret; std::stringstream ret;
unsigned char* hash = SHA256(reinterpret_cast<const unsigned char*>(str.c_str()), str.length(), nullptr); unsigned char* hash = SHA256(reinterpret_cast<const unsigned char*>(str.c_str()), str.length(), nullptr);
@@ -373,19 +307,9 @@ std::shared_ptr<TClient> TNetwork::Authentication(TConnection&& RawConnection) {
Client->SetIdentifier("ip", ip); Client->SetIdentifier("ip", ip);
beammp_tracef("This thread is ip {} ({})", ip, RawConnection.SockAddr.address().to_v6().is_v4_mapped() ? "IPv4 mapped IPv6" : "IPv6"); beammp_tracef("This thread is ip {} ({})", ip, RawConnection.SockAddr.address().to_v6().is_v4_mapped() ? "IPv4 mapped IPv6" : "IPv6");
if (Application::GetSubsystemStatuses().at("Main") == Application::Status::Starting) {
ClientKick(*Client, "The server is still starting, please try joining again later.");
return nullptr;
}
beammp_info("Identifying new ClientConnection..."); beammp_info("Identifying new ClientConnection...");
auto Data = TCPRcv(*Client, true); auto Data = TCPRcv(*Client);
if (Data.empty()) {
beammp_debug("Authentication failed: did not receive version packet");
ClientKick(*Client, "Connection closed during version handshake");
return nullptr;
}
constexpr std::string_view VC = "VC"; constexpr std::string_view VC = "VC";
if (Data.size() > 3 && std::equal(Data.begin(), Data.begin() + VC.size(), VC.begin(), VC.end())) { if (Data.size() > 3 && std::equal(Data.begin(), Data.begin() + VC.size(), VC.begin(), VC.end())) {
@@ -407,12 +331,7 @@ std::shared_ptr<TClient> TNetwork::Authentication(TConnection&& RawConnection) {
// TODO: handle // TODO: handle
} }
Data = TCPRcv(*Client, true); Data = TCPRcv(*Client);
if (Data.empty()) {
beammp_debug("Authentication failed: did not receive auth key packet");
ClientKick(*Client, "Connection closed during authentication");
return nullptr;
}
if (Data.size() > 50) { if (Data.size() > 50) {
ClientKick(*Client, "Invalid Key (too long)!"); ClientKick(*Client, "Invalid Key (too long)!");
@@ -420,10 +339,6 @@ std::shared_ptr<TClient> TNetwork::Authentication(TConnection&& RawConnection) {
} }
std::string Key(reinterpret_cast<const char*>(Data.data()), Data.size()); std::string Key(reinterpret_cast<const char*>(Data.data()), Data.size());
if (Key.empty()) {
ClientKick(*Client, "Invalid Key (empty)!");
return nullptr;
}
std::string AuthKey = Application::Settings.getAsString(Settings::Key::General_AuthKey); std::string AuthKey = Application::Settings.getAsString(Settings::Key::General_AuthKey);
std::string ClientIp = Client->GetIdentifiers().at("ip"); std::string ClientIp = Client->GetIdentifiers().at("ip");
@@ -452,7 +367,7 @@ std::shared_ptr<TClient> TNetwork::Authentication(TConnection&& RawConnection) {
try { try {
nlohmann::json AuthRes = nlohmann::json::parse(AuthResStr); nlohmann::json AuthRes = nlohmann::json::parse(AuthResStr);
if (AuthRes["username"].is_string() && AuthRes["username"].size() > 0 && AuthRes["roles"].is_string() if (AuthRes["username"].is_string() && AuthRes["roles"].is_string()
&& AuthRes["guest"].is_boolean() && AuthRes["identifiers"].is_array()) { && AuthRes["guest"].is_boolean() && AuthRes["identifiers"].is_array()) {
Client->SetName(AuthRes["username"]); Client->SetName(AuthRes["username"]);
@@ -480,13 +395,13 @@ std::shared_ptr<TClient> TNetwork::Authentication(TConnection&& RawConnection) {
std::shared_ptr<TClient> Cl; std::shared_ptr<TClient> Cl;
{ {
ReadLock Lock(mServer.GetClientMutex()); ReadLock Lock(mServer.GetClientMutex());
if (auto Locked = ClientPtr.lock()) { if (!ClientPtr.expired()) {
Cl = std::move(Locked); Cl = ClientPtr.lock();
} else } else
return true; return true;
} }
if (Cl->GetName() == Client->GetName() && Cl->IsGuest() == Client->IsGuest()) { if (Cl->GetName() == Client->GetName() && Cl->IsGuest() == Client->IsGuest()) {
DisconnectClient(Cl, "Stale Client (not a real player)"); Cl->Disconnect("Stale Client (not a real player)");
return false; return false;
} }
@@ -499,11 +414,8 @@ std::shared_ptr<TClient> TNetwork::Authentication(TConnection&& RawConnection) {
bool BypassLimit = false; bool BypassLimit = false;
for (const auto& Result : Futures) { for (const auto& Result : Futures) {
auto Snapshot = Result->GetDetachedSnapshot(); if (!Result->Error && Result->Result.is<int>()) {
if (!Snapshot.Error) { auto Res = Result->Result.as<int>();
const int* MaybeInt = std::get_if<int>(&Snapshot.Result.V);
if (MaybeInt != nullptr) {
auto Res = *MaybeInt;
if (Res == 1) { if (Res == 1) {
NotAllowed = true; NotAllowed = true;
@@ -513,18 +425,13 @@ std::shared_ptr<TClient> TNetwork::Authentication(TConnection&& RawConnection) {
} }
} }
} }
}
std::string Reason; std::string Reason;
bool NotAllowedWithReason = std::any_of(Futures.begin(), Futures.end(), bool NotAllowedWithReason = std::any_of(Futures.begin(), Futures.end(),
[&Reason](const std::shared_ptr<TLuaResult>& Result) -> bool { [&Reason](const std::shared_ptr<TLuaResult>& Result) -> bool {
auto Snapshot = Result->GetDetachedSnapshot(); if (!Result->Error && Result->Result.is<std::string>()) {
if (!Snapshot.Error) { Reason = Result->Result.as<std::string>();
const std::string* MaybeStr = std::get_if<std::string>(&Snapshot.Result.V);
if (MaybeStr != nullptr) {
Reason = *MaybeStr;
return true; return true;
} }
}
return false; return false;
}); });
@@ -557,7 +464,7 @@ std::shared_ptr<TClient> TNetwork::Authentication(TConnection&& RawConnection) {
return Client; return Client;
} }
std::shared_ptr<TClient> TNetwork::CreateClient(boost::asio::ip::tcp::socket&& TCPSock) { std::shared_ptr<TClient> TNetwork::CreateClient(ip::tcp::socket&& TCPSock) {
auto c = std::make_shared<TClient>(mServer, std::move(TCPSock)); auto c = std::make_shared<TClient>(mServer, std::move(TCPSock));
return c; return c;
} }
@@ -590,17 +497,17 @@ bool TNetwork::TCPSend(TClient& c, const std::vector<uint8_t>& Data, bool IsSync
std::memcpy(ToSend.data(), &Size, sizeof(Size)); std::memcpy(ToSend.data(), &Size, sizeof(Size));
std::memcpy(ToSend.data() + sizeof(Size), Data.data(), Data.size()); std::memcpy(ToSend.data() + sizeof(Size), Data.data(), Data.size());
boost::system::error_code ec; boost::system::error_code ec;
boost::asio::write(Sock, boost::asio::buffer(ToSend), ec); write(Sock, buffer(ToSend), ec);
if (ec) { if (ec) {
beammp_debugf("write(): {}", ec.message()); beammp_debugf("write(): {}", ec.message());
DisconnectClient(c, "write() failed"); c.Disconnect("write() failed");
return false; return false;
} }
c.UpdatePingTime(); c.UpdatePingTime();
return true; return true;
} }
std::vector<uint8_t> TNetwork::TCPRcv(TClient& c, bool WithTimeout) { std::vector<uint8_t> TNetwork::TCPRcv(TClient& c) {
if (c.IsDisconnected()) { if (c.IsDisconnected()) {
beammp_error("Client disconnected, cancelling TCPRcv"); beammp_error("Client disconnected, cancelling TCPRcv");
return {}; return {};
@@ -611,11 +518,7 @@ std::vector<uint8_t> TNetwork::TCPRcv(TClient& c, bool WithTimeout) {
boost::system::error_code ec; boost::system::error_code ec;
std::array<uint8_t, sizeof(Header)> HeaderData; std::array<uint8_t, sizeof(Header)> HeaderData;
if (WithTimeout) { read(Sock, buffer(HeaderData), ec);
ec = ReadWithTimeout(Sock, HeaderData.data(), HeaderData.size(), std::chrono::seconds(READ_TIMEOUT_S));
} else {
boost::asio::read(Sock, boost::asio::buffer(HeaderData), ec);
}
if (ec) { if (ec) {
// TODO: handle this case (read failed) // TODO: handle this case (read failed)
beammp_debugf("TCPRcv: Reading header failed: {}", ec.message()); beammp_debugf("TCPRcv: Reading header failed: {}", ec.message());
@@ -631,26 +534,14 @@ std::vector<uint8_t> TNetwork::TCPRcv(TClient& c, bool WithTimeout) {
std::vector<uint8_t> Data; std::vector<uint8_t> Data;
// TODO: This is arbitrary, this needs to be handled another way // TODO: This is arbitrary, this needs to be handled another way
bool isUnauthenticated = c.GetName().empty(); if (Header < int32_t(100 * MB)) {
int32_t maxHeaderSize = isUnauthenticated ? 4096 : int32_t(100 * MB);
if (Header < maxHeaderSize) {
Data.resize(Header); Data.resize(Header);
} else { } else {
ClientKick(c, "Header size limit exceeded"); ClientKick(c, "Header size limit exceeded");
beammp_warn("Client " + c.GetName() + " (" + std::to_string(c.GetID()) + ") sent header larger than expected - assuming malicious intent and disconnecting the client."); beammp_warn("Client " + c.GetName() + " (" + std::to_string(c.GetID()) + ") sent header of >100MB - assuming malicious intent and disconnecting the client.");
return {}; return {};
} }
std::size_t N = 0; auto N = read(Sock, buffer(Data), ec);
if (WithTimeout) {
if (!Data.empty()) {
ec = ReadWithTimeout(Sock, Data.data(), Data.size(), std::chrono::seconds(READ_TIMEOUT_S));
if (!ec) {
N = Data.size();
}
}
} else {
N = boost::asio::read(Sock, boost::asio::buffer(Data), ec);
}
if (ec) { if (ec) {
// TODO: handle this case properly // TODO: handle this case properly
beammp_debugf("TCPRcv: Reading data failed: {}", ec.message()); beammp_debugf("TCPRcv: Reading data failed: {}", ec.message());
@@ -685,29 +576,13 @@ void TNetwork::ClientKick(TClient& c, const std::string& R) {
if (!TCPSend(c, StringToVector("K" + R))) { if (!TCPSend(c, StringToVector("K" + R))) {
beammp_debugf("tried to kick player '{}' (id {}), but was already disconnected", c.GetName(), c.GetID()); beammp_debugf("tried to kick player '{}' (id {}), but was already disconnected", c.GetName(), c.GetID());
} }
DisconnectClient(c, "Kicked"); c.Disconnect("Kicked");
}
void TNetwork::DisconnectClient(const std::weak_ptr<TClient>& c, const std::string& R) {
if (auto locked = c.lock()) {
DisconnectClient(*locked, R);
} else {
beammp_debugf("Tried to disconnect a non existant client with reason: {}", R);
}
}
void TNetwork::DisconnectClient(TClient& c, const std::string& R) {
// Keep this unconditional; TClient::Disconnect() is the single-winner guard.
(void)c.Disconnect(R);
} }
void TNetwork::Looper(const std::weak_ptr<TClient>& c) { void TNetwork::Looper(const std::weak_ptr<TClient>& c) {
RegisterThreadAuto(); RegisterThreadAuto();
while (true) { while (!c.expired()) {
auto Client = c.lock(); auto Client = c.lock();
if (!Client) {
break;
}
if (Client->IsDisconnected()) { if (Client->IsDisconnected()) {
beammp_debug("client is disconnected, breaking client loop"); beammp_debug("client is disconnected, breaking client loop");
break; break;
@@ -726,7 +601,7 @@ void TNetwork::Looper(const std::weak_ptr<TClient>& c) {
} // end locked context } // end locked context
// beammp_debug("sending a missed packet: " + QData); // beammp_debug("sending a missed packet: " + QData);
if (!TCPSend(*Client, QData, true)) { if (!TCPSend(*Client, QData, true)) {
DisconnectClient(Client, "Failed to TCPSend while clearing the missed packet queue"); Client->Disconnect("Failed to TCPSend while clearing the missed packet queue");
std::unique_lock lock(Client->MissedPacketQueueMutex()); std::unique_lock lock(Client->MissedPacketQueueMutex());
while (!Client->MissedPacketQueue().empty()) { while (!Client->MissedPacketQueue().empty()) {
Client->MissedPacketQueue().pop(); Client->MissedPacketQueue().pop();
@@ -741,29 +616,20 @@ void TNetwork::Looper(const std::weak_ptr<TClient>& c) {
} }
void TNetwork::TCPClient(const std::weak_ptr<TClient>& c) { void TNetwork::TCPClient(const std::weak_ptr<TClient>& c) {
if (auto Client = c.lock()) { // TODO: the c.expired() might cause issues here, remove if you end up here with your debugger
if (Client->IsDisconnected()) { if (c.expired() || !c.lock()->GetTCPSock().is_open()) {
mServer.RemoveClient(c);
return;
}
} else {
mServer.RemoveClient(c); mServer.RemoveClient(c);
return; return;
} }
OnConnect(c); OnConnect(c);
if (auto Client = c.lock()) { RegisterThread("(" + std::to_string(c.lock()->GetID()) + ") \"" + c.lock()->GetName() + "\"");
RegisterThread("(" + std::to_string(Client->GetID()) + ") \"" + Client->GetName() + "\"");
} else {
return;
}
std::jthread QueueSync(&TNetwork::Looper, this, c); std::thread QueueSync(&TNetwork::Looper, this, c);
while (true) { while (true) {
auto Client = c.lock(); if (c.expired())
if (!Client) {
break; break;
} auto Client = c.lock();
if (Client->IsDisconnected()) { if (Client->IsDisconnected()) {
beammp_debug("client status < 0, breaking client loop"); beammp_debug("client status < 0, breaking client loop");
break; break;
@@ -772,19 +638,17 @@ void TNetwork::TCPClient(const std::weak_ptr<TClient>& c) {
auto res = TCPRcv(*Client); auto res = TCPRcv(*Client);
if (res.empty()) { if (res.empty()) {
beammp_debug("TCPRcv empty"); beammp_debug("TCPRcv empty");
DisconnectClient(Client, "TCPRcv failed"); Client->Disconnect("TCPRcv failed");
break;
}
try {
mServer.GlobalParser(c, std::move(res), mPPSMonitor, *this, false);
} catch (const std::exception& e) {
beammp_warnf("Failed to receive/parse packet via TCP from client {}: {}", Client->GetID(), e.what());
DisconnectClient(Client, "Failed to parse packet");
break; break;
} }
mServer.GlobalParser(c, std::move(res), mPPSMonitor, *this);
} }
if (auto Client = c.lock()) { if (QueueSync.joinable())
QueueSync.join();
if (!c.expired()) {
auto Client = c.lock();
OnDisconnect(c); OnDisconnect(c);
} else { } else {
beammp_warn("client expired in TCPClient, should never happen"); beammp_warn("client expired in TCPClient, should never happen");
@@ -795,7 +659,8 @@ void TNetwork::UpdatePlayer(TClient& Client) {
std::string Packet = ("Ss") + std::to_string(mServer.ClientCount()) + "/" + std::to_string(Application::Settings.getAsInt(Settings::Key::General_MaxPlayers)) + ":"; std::string Packet = ("Ss") + std::to_string(mServer.ClientCount()) + "/" + std::to_string(Application::Settings.getAsInt(Settings::Key::General_MaxPlayers)) + ":";
mServer.ForEachClient([&](const std::weak_ptr<TClient>& ClientPtr) -> bool { mServer.ForEachClient([&](const std::weak_ptr<TClient>& ClientPtr) -> bool {
ReadLock Lock(mServer.GetClientMutex()); ReadLock Lock(mServer.GetClientMutex());
if (auto c = ClientPtr.lock()) { if (!ClientPtr.expired()) {
auto c = ClientPtr.lock();
Packet += c->GetName() + ","; Packet += c->GetName() + ",";
} }
return true; return true;
@@ -805,144 +670,15 @@ void TNetwork::UpdatePlayer(TClient& Client) {
//(void)Respond(Client, Packet, true); //(void)Respond(Client, Packet, true);
} }
static boost::system::error_code ReadSocketWithTimeout(
boost::asio::ip::tcp::socket& socket,
void* buffer,
std::size_t length,
std::chrono::steady_clock::duration timeout);
boost::system::error_code TNetwork::ReadWithTimeout(TConnection& Connection, void* Buf, size_t Len, std::chrono::steady_clock::duration Timeout) {
return ReadWithTimeout(Connection.Socket, Buf, Len, Timeout);
}
boost::system::error_code TNetwork::ReadWithTimeout(boost::asio::ip::tcp::socket& Socket, void* Buf, size_t Len, std::chrono::steady_clock::duration Timeout) {
return ReadSocketWithTimeout(Socket, Buf, Len, Timeout);
}
static boost::system::error_code ReadSocketWithTimeout(
boost::asio::ip::tcp::socket& Socket,
void* Buffer,
std::size_t Length,
std::chrono::steady_clock::duration Timeout) {
namespace asio = boost::asio;
using boost::system::error_code;
struct TTimeoutState {
explicit TTimeoutState(const boost::asio::any_io_executor& Executor)
: Timer(Executor) { }
asio::steady_timer Timer;
std::promise<std::pair<error_code, std::size_t>> Promise;
std::atomic_bool Completed { false };
};
auto State = std::make_shared<TTimeoutState>(Socket.get_executor());
auto Future = State->Promise.get_future();
asio::async_read(
Socket,
asio::buffer(Buffer, Length),
[State](error_code ec, std::size_t n) {
if (!State->Completed.exchange(true)) {
State->Timer.cancel();
State->Promise.set_value({ ec, n });
}
});
State->Timer.expires_after(Timeout);
State->Timer.async_wait(
[State, &Socket](error_code ec) {
if (ec == asio::error::operation_aborted)
return;
if (!State->Completed.exchange(true)) {
error_code IgnoredEc;
Socket.cancel(IgnoredEc);
State->Promise.set_value({ asio::error::timed_out, 0 });
}
});
auto [ec, NRead] = Future.get();
return ec;
}
TEST_CASE("ReadSocketWithTimeout returns timed_out when peer sends no data") {
TIoPollThread TimerThread;
boost::system::error_code Ec;
ip::tcp::socket ClientSocket(TimerThread.IoCtx());
ip::tcp::socket ServerSocket(TimerThread.IoCtx());
OpenLoopbackSocketPair(TimerThread.IoCtx(), ClientSocket, ServerSocket, Ec);
REQUIRE(!Ec);
uint8_t ReadByte = 0;
const auto ReadEc = ReadSocketWithTimeout(ServerSocket, &ReadByte, 1, std::chrono::milliseconds(50));
CHECK(ReadEc == error::timed_out);
}
TEST_CASE("ReadSocketWithTimeout reads small payload") {
TIoPollThread TimerThread;
boost::system::error_code Ec;
ip::tcp::socket ClientSocket(TimerThread.IoCtx());
ip::tcp::socket ServerSocket(TimerThread.IoCtx());
OpenLoopbackSocketPair(TimerThread.IoCtx(), ClientSocket, ServerSocket, Ec);
REQUIRE(!Ec);
const std::array<uint8_t, 2> Sent { 'O', 'K' };
boost::asio::write(ClientSocket, boost::asio::buffer(Sent), Ec);
REQUIRE(!Ec);
std::array<uint8_t, 2> Received { };
const auto ReadEc = ReadSocketWithTimeout(ServerSocket, Received.data(), Received.size(), std::chrono::milliseconds(200));
CHECK(!ReadEc);
CHECK(Received == Sent);
}
TEST_CASE("ReadSocketWithTimeout reads large payload") {
TIoPollThread TimerThread;
boost::system::error_code Ec;
ip::tcp::socket ClientSocket(TimerThread.IoCtx());
ip::tcp::socket ServerSocket(TimerThread.IoCtx());
OpenLoopbackSocketPair(TimerThread.IoCtx(), ClientSocket, ServerSocket, Ec);
REQUIRE(!Ec);
constexpr size_t PacketSize = 2 * 1024 * 1024;
std::vector<uint8_t> Sent(PacketSize, uint8_t(0x7A));
boost::asio::write(ClientSocket, boost::asio::buffer(Sent), Ec);
REQUIRE(!Ec);
std::vector<uint8_t> Received(PacketSize);
const auto ReadEc = ReadSocketWithTimeout(ServerSocket, Received.data(), Received.size(), std::chrono::seconds(2));
CHECK(!ReadEc);
CHECK(Received == Sent);
}
TEST_CASE("ReadSocketWithTimeout can timeout then retry successfully") {
TIoPollThread TimerThread;
boost::system::error_code Ec;
ip::tcp::socket ClientSocket(TimerThread.IoCtx());
ip::tcp::socket ServerSocket(TimerThread.IoCtx());
OpenLoopbackSocketPair(TimerThread.IoCtx(), ClientSocket, ServerSocket, Ec);
REQUIRE(!Ec);
uint8_t Received = 0;
CHECK(ReadSocketWithTimeout(ServerSocket, &Received, 1, std::chrono::milliseconds(20)) == error::timed_out);
const uint8_t Sent = 0x42;
boost::asio::write(ClientSocket, boost::asio::buffer(&Sent, 1), Ec);
REQUIRE(!Ec);
const auto ReadEc = ReadSocketWithTimeout(ServerSocket, &Received, 1, std::chrono::milliseconds(200));
CHECK(!ReadEc);
CHECK(Received == Sent);
}
void TNetwork::OnDisconnect(const std::weak_ptr<TClient>& ClientPtr) { void TNetwork::OnDisconnect(const std::weak_ptr<TClient>& ClientPtr) {
auto LockedClientPtr = ClientPtr.lock(); std::shared_ptr<TClient> LockedClientPtr { nullptr };
if (!LockedClientPtr) { try {
LockedClientPtr = ClientPtr.lock();
} catch (const std::exception&) {
beammp_warn("Client expired in OnDisconnect, this is unexpected"); beammp_warn("Client expired in OnDisconnect, this is unexpected");
return; return;
} }
beammp_assert(LockedClientPtr != nullptr);
TClient& c = *LockedClientPtr; TClient& c = *LockedClientPtr;
beammp_info(c.GetName() + (" Connection Terminated")); beammp_info(c.GetName() + (" Connection Terminated"));
std::string Packet; std::string Packet;
@@ -961,7 +697,7 @@ void TNetwork::OnDisconnect(const std::weak_ptr<TClient>& ClientPtr) {
Packet.clear(); Packet.clear();
auto Futures = LuaAPI::MP::Engine->TriggerEvent("onPlayerDisconnect", "", c.GetID()); auto Futures = LuaAPI::MP::Engine->TriggerEvent("onPlayerDisconnect", "", c.GetID());
LuaAPI::MP::Engine->WaitForAll(Futures); LuaAPI::MP::Engine->WaitForAll(Futures);
DisconnectClient(c, "Already Disconnected (OnDisconnect)"); c.Disconnect("Already Disconnected (OnDisconnect)");
mServer.RemoveClient(ClientPtr); mServer.RemoveClient(ClientPtr);
} }
@@ -973,7 +709,8 @@ int TNetwork::OpenID() {
found = true; found = true;
mServer.ForEachClient([&](const std::weak_ptr<TClient>& ClientPtr) -> bool { mServer.ForEachClient([&](const std::weak_ptr<TClient>& ClientPtr) -> bool {
ReadLock Lock(mServer.GetClientMutex()); ReadLock Lock(mServer.GetClientMutex());
if (auto c = ClientPtr.lock()) { if (!ClientPtr.expired()) {
auto c = ClientPtr.lock();
if (c->GetID() == ID) { if (c->GetID() == ID) {
found = false; found = false;
ID++; ID++;
@@ -986,29 +723,15 @@ int TNetwork::OpenID() {
} }
void TNetwork::OnConnect(const std::weak_ptr<TClient>& c) { void TNetwork::OnConnect(const std::weak_ptr<TClient>& c) {
auto LockedClient = c.lock(); beammp_assert(!c.expired());
if (!LockedClient) {
return;
}
beammp_info("Client connected"); beammp_info("Client connected");
auto LockedClient = c.lock();
LockedClient->SetID(OpenID()); LockedClient->SetID(OpenID());
beammp_info("Assigned ID " + std::to_string(LockedClient->GetID()) + " to " + LockedClient->GetName()); beammp_info("Assigned ID " + std::to_string(LockedClient->GetID()) + " to " + LockedClient->GetName());
LuaAPI::MP::Engine->ReportErrors(LuaAPI::MP::Engine->TriggerEvent("onPlayerConnecting", "", LockedClient->GetID())); LuaAPI::MP::Engine->ReportErrors(LuaAPI::MP::Engine->TriggerEvent("onPlayerConnecting", "", LockedClient->GetID()));
SyncResources(*LockedClient); SyncResources(*LockedClient);
if (LockedClient->IsDisconnected()) if (LockedClient->IsDisconnected())
return; return;
std::vector<unsigned char> buf(64);
int ret = RAND_bytes(buf.data(), buf.size());
if (ret != 1) {
unsigned long error = ERR_get_error();
beammp_errorf("RAND_bytes failed with error code {}", error);
beammp_assert(ret != 1);
return;
}
LockedClient->SetMagic(buf);
buf.insert(buf.begin(), 'U');
(void)Respond(*LockedClient, buf, true);
(void)Respond(*LockedClient, StringToVector("M" + Application::Settings.getAsString(Settings::Key::General_Map)), true); // Send the Map on connect (void)Respond(*LockedClient, StringToVector("M" + Application::Settings.getAsString(Settings::Key::General_Map)), true); // Send the Map on connect
beammp_info(LockedClient->GetName() + " : Connected"); beammp_info(LockedClient->GetName() + " : Connected");
LuaAPI::MP::Engine->ReportErrors(LuaAPI::MP::Engine->TriggerEvent("onPlayerJoining", "", LockedClient->GetID())); LuaAPI::MP::Engine->ReportErrors(LuaAPI::MP::Engine->TriggerEvent("onPlayerJoining", "", LockedClient->GetID()));
@@ -1022,7 +745,6 @@ void TNetwork::SyncResources(TClient& c) {
while (!c.IsDisconnected()) { while (!c.IsDisconnected()) {
Data = TCPRcv(c); Data = TCPRcv(c);
if (Data.empty()) { if (Data.empty()) {
DisconnectClient(c, "TCPRcv failed during resource sync");
break; break;
} }
constexpr std::string_view Done = "Done"; constexpr std::string_view Done = "Done";
@@ -1045,7 +767,7 @@ void TNetwork::Parse(TClient& c, const std::vector<uint8_t>& Packet) {
case 'S': case 'S':
if (SubCode == 'R') { if (SubCode == 'R') {
beammp_debug("Sending Mod Info"); beammp_debug("Sending Mod Info");
std::string ToSend = mResourceManager.GetMods().dump(); std::string ToSend = mResourceManager.NewFileList();
beammp_debugf("Mod Info: {}", ToSend); beammp_debugf("Mod Info: {}", ToSend);
if (!TCPSend(c, StringToVector(ToSend))) { if (!TCPSend(c, StringToVector(ToSend))) {
ClientKick(c, "TCP Send 'SY' failed"); ClientKick(c, "TCP Send 'SY' failed");
@@ -1067,15 +789,6 @@ void TNetwork::SendFile(TClient& c, const std::string& UnsafeName) {
return; return;
} }
auto FileName = fs::path(UnsafeName).filename().string(); auto FileName = fs::path(UnsafeName).filename().string();
for (auto mod : mResourceManager.GetMods()) {
if (mod["file_name"].get<std::string>() == FileName && mod["protected"] == true) {
beammp_warn("Client tried to access protected file " + UnsafeName);
DisconnectClient(c, "Mod is protected thus cannot be downloaded");
return;
}
}
FileName = Application::Settings.getAsString(Settings::Key::General_ResourceFolder) + "/Client/" + FileName; FileName = Application::Settings.getAsString(Settings::Key::General_ResourceFolder) + "/Client/" + FileName;
if (!std::filesystem::exists(FileName)) { if (!std::filesystem::exists(FileName)) {
@@ -1098,9 +811,9 @@ void TNetwork::SendFile(TClient& c, const std::string& UnsafeName) {
#if defined(BEAMMP_LINUX) #if defined(BEAMMP_LINUX)
#include <cerrno> #include <cerrno>
#include <cstring> #include <cstring>
#include <signal.h>
#include <sys/sendfile.h> #include <sys/sendfile.h>
#include <unistd.h> #include <unistd.h>
#include <signal.h>
#endif #endif
void TNetwork::SendFileToClient(TClient& c, size_t Size, const std::string& Name) { void TNetwork::SendFileToClient(TClient& c, size_t Size, const std::string& Name) {
TScopedTimer timer(fmt::format("Download of '{}' for client {}", Name, c.GetID())); TScopedTimer timer(fmt::format("Download of '{}' for client {}", Name, c.GetID()));
@@ -1115,40 +828,17 @@ void TNetwork::SendFileToClient(TClient& c, size_t Size, const std::string& Name
// native handle, needed in order to make native syscalls with it // native handle, needed in order to make native syscalls with it
int socket = c.GetTCPSock().native_handle(); int socket = c.GetTCPSock().native_handle();
const auto ToSendTotal = Size; ssize_t ret = 0;
size_t TotalSent = 0; auto ToSendTotal = Size;
while (TotalSent < ToSendTotal) { auto Start = 0;
off_t SysOffset = off_t(TotalSent); while (ret < ssize_t(ToSendTotal)) {
const ssize_t SentNow = sendfile(socket, fd, &SysOffset, ToSendTotal - TotalSent); auto SysOffset = off_t(Start + size_t(ret));
if (SentNow > 0) { ret = sendfile(socket, fd, &SysOffset, ToSendTotal - size_t(ret));
TotalSent += size_t(SentNow); if (ret < 0) {
continue;
}
if (SentNow == 0) {
beammp_errorf("Failed to send mod '{}' to client {}: sendfile returned 0 before all bytes were sent", Name, c.GetID());
::close(fd);
DisconnectClient(c, "sendfile returned 0 during mod download");
return;
}
if (errno == EINTR) {
continue;
}
if (errno == EAGAIN
#if EWOULDBLOCK != EAGAIN
|| errno == EWOULDBLOCK
#endif
) {
std::this_thread::sleep_for(std::chrono::milliseconds(1));
continue;
}
beammp_errorf("Failed to send mod '{}' to client {}: {}", Name, c.GetID(), std::strerror(errno)); beammp_errorf("Failed to send mod '{}' to client {}: {}", Name, c.GetID(), std::strerror(errno));
::close(fd);
DisconnectClient(c, "sendfile failed during mod download");
return; return;
} }
::close(fd); }
#else #else
std::ifstream f(Name.c_str(), std::ios::binary); std::ifstream f(Name.c_str(), std::ios::binary);
@@ -1158,7 +848,7 @@ void TNetwork::SendFileToClient(TClient& c, size_t Size, const std::string& Name
Data.resize(Split); Data.resize(Split);
else else
Data.resize(Size); Data.resize(Size);
boost::asio::ip::tcp::socket* TCPSock = &c.GetTCPSock(); ip::tcp::socket* TCPSock = &c.GetTCPSock();
std::streamsize Sent = 0; std::streamsize Sent = 0;
while (!c.IsDisconnected() && Sent < Size) { while (!c.IsDisconnected() && Sent < Size) {
size_t Diff = Size - Sent; size_t Diff = Size - Sent;
@@ -1167,7 +857,7 @@ void TNetwork::SendFileToClient(TClient& c, size_t Size, const std::string& Name
f.read(reinterpret_cast<char*>(Data.data()), Split); f.read(reinterpret_cast<char*>(Data.data()), Split);
if (!TCPSendRaw(c, *TCPSock, Data.data(), Split)) { if (!TCPSendRaw(c, *TCPSock, Data.data(), Split)) {
if (!c.IsDisconnected()) if (!c.IsDisconnected())
DisconnectClient(c, "TCPSendRaw failed in mod download (1)"); c.Disconnect("TCPSendRaw failed in mod download (1)");
break; break;
} }
Sent += Split; Sent += Split;
@@ -1176,7 +866,7 @@ void TNetwork::SendFileToClient(TClient& c, size_t Size, const std::string& Name
f.read(reinterpret_cast<char*>(Data.data()), Diff); f.read(reinterpret_cast<char*>(Data.data()), Diff);
if (!TCPSendRaw(c, *TCPSock, Data.data(), int32_t(Diff))) { if (!TCPSendRaw(c, *TCPSock, Data.data(), int32_t(Diff))) {
if (!c.IsDisconnected()) if (!c.IsDisconnected())
DisconnectClient(c, "TCPSendRaw failed in mod download (2)"); c.Disconnect("TCPSendRaw failed in mod download (2)");
break; break;
} }
Sent += Diff; Sent += Diff;
@@ -1185,9 +875,9 @@ void TNetwork::SendFileToClient(TClient& c, size_t Size, const std::string& Name
#endif #endif
} }
bool TNetwork::TCPSendRaw(TClient& C, boost::asio::ip::tcp::socket& socket, const uint8_t* Data, size_t Size) { bool TNetwork::TCPSendRaw(TClient& C, ip::tcp::socket& socket, const uint8_t* Data, size_t Size) {
boost::system::error_code ec; boost::system::error_code ec;
boost::asio::write(socket, boost::asio::buffer(Data, Size), ec); write(socket, buffer(Data, Size), ec);
if (ec) { if (ec) {
beammp_errorf("Failed to send raw data to client: {}", ec.message()); beammp_errorf("Failed to send raw data to client: {}", ec.message());
return false; return false;
@@ -1217,10 +907,10 @@ bool TNetwork::Respond(TClient& c, const std::vector<uint8_t>& MSG, bool Rel, bo
} }
bool TNetwork::SyncClient(const std::weak_ptr<TClient>& c) { bool TNetwork::SyncClient(const std::weak_ptr<TClient>& c) {
auto LockedClient = c.lock(); if (c.expired()) {
if (!LockedClient) {
return false; return false;
} }
auto LockedClient = c.lock();
if (LockedClient->IsSynced()) if (LockedClient->IsSynced())
return true; return true;
// Syncing, later set isSynced // Syncing, later set isSynced
@@ -1239,8 +929,8 @@ bool TNetwork::SyncClient(const std::weak_ptr<TClient>& c) {
std::shared_ptr<TClient> client; std::shared_ptr<TClient> client;
{ {
ReadLock Lock(mServer.GetClientMutex()); ReadLock Lock(mServer.GetClientMutex());
if (auto Locked = ClientPtr.lock()) { if (!ClientPtr.expired()) {
client = std::move(Locked); client = ClientPtr.lock();
} else } else
return true; return true;
} }
@@ -1256,7 +946,7 @@ bool TNetwork::SyncClient(const std::weak_ptr<TClient>& c) {
res = false; res = false;
return false; return false;
} }
res = Respond(*LockedClient, StringToVector(v.DataAsPacket(client->GetRoles(), client->GetName(), client->GetID())), true, true); res = Respond(*LockedClient, StringToVector(v.Data()), true, true);
} }
} }
@@ -1277,15 +967,15 @@ void TNetwork::SendToAll(TClient* c, const std::vector<uint8_t>& Data, bool Self
char C = Data.at(0); char C = Data.at(0);
bool ret = true; bool ret = true;
mServer.ForEachClient([&](std::weak_ptr<TClient> ClientPtr) -> bool { mServer.ForEachClient([&](std::weak_ptr<TClient> ClientPtr) -> bool {
std::shared_ptr<TClient> Client { nullptr }; std::shared_ptr<TClient> Client;
{ try {
ReadLock Lock(mServer.GetClientMutex()); ReadLock Lock(mServer.GetClientMutex());
if (auto Locked = ClientPtr.lock()) { Client = ClientPtr.lock();
Client = std::move(Locked); } catch (const std::exception&) {
} else { // continue
beammp_warn("Client expired, shouldn't happen - if a client disconnected recently, you can ignore this");
return true; return true;
} }
}
if (Self || Client.get() != c) { if (Self || Client.get() != c) {
if (Client->IsSynced() || Client->IsSyncing()) { if (Client->IsSynced() || Client->IsSyncing()) {
if (Rel || C == 'W' || C == 'Y' || C == 'V' || C == 'E' || compressBound(Data.size()) > 1024) { if (Rel || C == 'W' || C == 'Y' || C == 'V' || C == 'E' || compressBound(Data.size()) > 1024) {
@@ -1328,20 +1018,20 @@ bool TNetwork::UDPSend(TClient& Client, std::vector<uint8_t> Data) {
CompressProperly(Data); CompressProperly(Data);
} }
boost::system::error_code ec; boost::system::error_code ec;
mUDPSock.send_to(boost::asio::buffer(Data), Addr, 0, ec); mUDPSock.send_to(buffer(Data), Addr, 0, ec);
if (ec) { if (ec) {
beammp_debugf("UDP sendto() failed: {}", ec.message()); beammp_debugf("UDP sendto() failed: {}", ec.message());
if (!Client.IsDisconnected()) if (!Client.IsDisconnected())
DisconnectClient(Client, "UDP send failed"); Client.Disconnect("UDP send failed");
return false; return false;
} }
return true; return true;
} }
std::vector<uint8_t> TNetwork::UDPRcvFromClient(boost::asio::ip::udp::endpoint& ClientEndpoint) { std::vector<uint8_t> TNetwork::UDPRcvFromClient(ip::udp::endpoint& ClientEndpoint) {
std::array<char, 1024> Ret {}; std::array<char, 1024> Ret {};
boost::system::error_code ec; boost::system::error_code ec;
const auto Rcv = mUDPSock.receive_from(boost::asio::mutable_buffer(Ret.data(), Ret.size()), ClientEndpoint, 0, ec); const auto Rcv = mUDPSock.receive_from(mutable_buffer(Ret.data(), Ret.size()), ClientEndpoint, 0, ec);
if (ec) { if (ec) {
beammp_errorf("UDP recvfrom() failed: {}", ec.message()); beammp_errorf("UDP recvfrom() failed: {}", ec.message());
return {}; return {};
+3 -3
View File
@@ -55,8 +55,8 @@ void TPPSMonitor::operator()() {
std::shared_ptr<TClient> c; std::shared_ptr<TClient> c;
{ {
ReadLock Lock(mServer.GetClientMutex()); ReadLock Lock(mServer.GetClientMutex());
if (auto Locked = ClientPtr.lock()) { if (!ClientPtr.expired()) {
c = std::move(Locked); c = ClientPtr.lock();
} else } else
return true; return true;
} }
@@ -76,7 +76,7 @@ void TPPSMonitor::operator()() {
return true; return true;
}); });
for (auto& ClientToKick : TimedOutClients) { for (auto& ClientToKick : TimedOutClients) {
Network().DisconnectClient(*ClientToKick, "Timeout"); ClientToKick->Disconnect("Timeout");
} }
TimedOutClients.clear(); TimedOutClients.clear();
if (C == 0 || mInternalPPS == 0) { if (C == 0 || mInternalPPS == 0) {
+2 -8
View File
@@ -57,7 +57,6 @@ void TPluginMonitor::operator()() {
mFileTimes[Pair.first] = CurrentTime; mFileTimes[Pair.first] = CurrentTime;
// grandparent of the path should be Resources/Server // grandparent of the path should be Resources/Server
if (fs::equivalent(fs::path(Pair.first).parent_path().parent_path(), mPath)) { if (fs::equivalent(fs::path(Pair.first).parent_path().parent_path(), mPath)) {
if (LowerString(fs::path(Pair.first).extension().string()) == ".lua") {
beammp_infof("File \"{}\" changed, reloading", Pair.first); beammp_infof("File \"{}\" changed, reloading", Pair.first);
// is in root folder, so reload // is in root folder, so reload
std::ifstream FileStream(Pair.first, std::ios::in | std::ios::binary); std::ifstream FileStream(Pair.first, std::ios::in | std::ios::binary);
@@ -69,17 +68,12 @@ void TPluginMonitor::operator()() {
auto StateID = mEngine->GetStateIDForPlugin(fs::path(Pair.first).parent_path()); auto StateID = mEngine->GetStateIDForPlugin(fs::path(Pair.first).parent_path());
auto Res = mEngine->EnqueueScript(StateID, Chunk); auto Res = mEngine->EnqueueScript(StateID, Chunk);
Res->WaitUntilReady(); Res->WaitUntilReady();
if (Res->IsError()) { if (Res->Error) {
auto Snapshot = Res->GetSnapshot(); beammp_lua_errorf("Error while hot-reloading \"{}\": {}", Pair.first, Res->ErrorMessage);
beammp_lua_errorf("Error while hot-reloading \"{}\": {}", Pair.first, Snapshot.ErrorMessage);
} else { } else {
mEngine->ReportErrors(mEngine->TriggerLocalEvent(StateID, "onInit")); mEngine->ReportErrors(mEngine->TriggerLocalEvent(StateID, "onInit"));
mEngine->ReportErrors(mEngine->TriggerEvent("onFileChanged", "", Pair.first)); mEngine->ReportErrors(mEngine->TriggerEvent("onFileChanged", "", Pair.first));
} }
} else {
beammp_debugf("File \"{}\" changed, not reloading because it's not a lua file. Triggering 'onFileChanged' event instead", Pair.first);
mEngine->ReportErrors(mEngine->TriggerEvent("onFileChanged", "", Pair.first));
}
} else { } else {
// is in subfolder, dont reload, just trigger an event // is in subfolder, dont reload, just trigger an event
beammp_debugf("File \"{}\" changed, not reloading because it's in a subdirectory. Triggering 'onFileChanged' event instead", Pair.first); beammp_debugf("File \"{}\" changed, not reloading because it's in a subdirectory. Triggering 'onFileChanged' event instead", Pair.first);
+4 -104
View File
@@ -58,58 +58,22 @@ TResourceManager::TResourceManager() {
Application::SetSubsystemStatus("ResourceManager", Application::Status::Good); Application::SetSubsystemStatus("ResourceManager", Application::Status::Good);
} }
std::string TResourceManager::NewFileList() const {
return mMods.dump();
}
void TResourceManager::RefreshFiles() { void TResourceManager::RefreshFiles() {
mMods.clear(); mMods.clear();
std::unique_lock Lock(mModsMutex); std::unique_lock Lock(mModsMutex);
std::string Path = Application::Settings.getAsString(Settings::Key::General_ResourceFolder) + "/Client"; std::string Path = Application::Settings.getAsString(Settings::Key::General_ResourceFolder) + "/Client";
nlohmann::json modsDB;
if (std::filesystem::exists(Path + "/mods.json")) {
try {
std::ifstream stream(Path + "/mods.json");
stream >> modsDB;
stream.close();
} catch (const std::exception& e) {
beammp_errorf("Failed to load mods.json: {}", e.what());
}
}
for (const auto& entry : fs::directory_iterator(Path)) { for (const auto& entry : fs::directory_iterator(Path)) {
std::string File(entry.path().string()); std::string File(entry.path().string());
if (entry.path().filename().string() == "mods.json") {
continue;
}
if (entry.path().extension() != ".zip" || std::filesystem::is_directory(entry.path())) { if (entry.path().extension() != ".zip" || std::filesystem::is_directory(entry.path())) {
beammp_warnf("'{}' is not a ZIP file and will be ignored", File); beammp_warnf("'{}' is not a ZIP file and will be ignored", File);
continue; continue;
} }
if (modsDB.contains(entry.path().filename().string())) {
auto& dbEntry = modsDB[entry.path().filename().string()];
if (entry.last_write_time().time_since_epoch().count() > dbEntry["lastwrite"] || std::filesystem::file_size(File) != dbEntry["filesize"].get<size_t>()) {
beammp_infof("File '{}' has been modified, rehashing", File);
} else {
dbEntry["exists"] = true;
mMods.push_back(nlohmann::json {
{ "file_name", std::filesystem::path(File).filename() },
{ "file_size", std::filesystem::file_size(File) },
{ "hash_algorithm", "sha256" },
{ "hash", dbEntry["hash"] },
{ "protected", dbEntry["protected"] } });
beammp_debugf("Mod '{}' loaded from cache", File);
continue;
}
}
try { try {
EVP_MD_CTX* mdctx; EVP_MD_CTX* mdctx;
const EVP_MD* md; const EVP_MD* md;
@@ -169,73 +133,9 @@ void TResourceManager::RefreshFiles() {
{ "file_size", std::filesystem::file_size(File) }, { "file_size", std::filesystem::file_size(File) },
{ "hash_algorithm", "sha256" }, { "hash_algorithm", "sha256" },
{ "hash", result }, { "hash", result },
{ "protected", false } }); });
modsDB[std::filesystem::path(File).filename().string()] = {
{ "lastwrite", entry.last_write_time().time_since_epoch().count() },
{ "hash", result },
{ "filesize", std::filesystem::file_size(File) },
{ "protected", false },
{ "exists", true }
};
} catch (const std::exception& e) { } catch (const std::exception& e) {
beammp_errorf("Sha256 hashing of '{}' failed: {}", File, e.what()); beammp_errorf("Sha256 hashing of '{}' failed: {}", File, e.what());
} }
} }
for (auto it = modsDB.begin(); it != modsDB.end();) {
if (!it.value().contains("exists")) {
it = modsDB.erase(it);
} else {
it.value().erase("exists");
++it;
}
}
try {
std::ofstream stream(Path + "/mods.json");
stream << modsDB.dump(4);
stream.close();
} catch (std::exception& e) {
beammp_error("Failed to update mod DB: " + std::string(e.what()));
}
}
void TResourceManager::SetProtected(const std::string& ModName, bool Protected) {
std::unique_lock Lock(mModsMutex);
for (auto& mod : mMods) {
if (mod["file_name"].get<std::string>() == ModName) {
mod["protected"] = Protected;
break;
}
}
auto modsDBPath = Application::Settings.getAsString(Settings::Key::General_ResourceFolder) + "/Client/mods.json";
if (std::filesystem::exists(modsDBPath)) {
try {
nlohmann::json modsDB;
std::fstream stream(modsDBPath);
stream >> modsDB;
if (modsDB.contains(ModName)) {
modsDB[ModName]["protected"] = Protected;
}
stream.clear();
stream.seekp(0, std::ios::beg);
stream << modsDB.dump(4);
stream.close();
} catch (const std::exception& e) {
beammp_errorf("Failed to update mods.json: {}", e.what());
}
}
} }
+52 -133
View File
@@ -28,7 +28,6 @@
#include <any> #include <any>
#include <optional> #include <optional>
#include <sstream> #include <sstream>
#include <utility>
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
@@ -54,6 +53,7 @@ static std::optional<std::pair<int, int>> GetPidVid(const std::string& str) {
} }
return std::nullopt; return std::nullopt;
} }
TEST_CASE("GetPidVid") { TEST_CASE("GetPidVid") {
SUBCASE("Valid singledigit") { SUBCASE("Valid singledigit") {
const auto MaybePidVid = GetPidVid("0-1"); const auto MaybePidVid = GetPidVid("0-1");
@@ -120,6 +120,7 @@ TEST_CASE("GetPidVid") {
CHECK(!MaybePidVid); CHECK(!MaybePidVid);
} }
} }
TServer::TServer(const std::vector<std::string_view>& Arguments) { TServer::TServer(const std::vector<std::string_view>& Arguments) {
beammp_info("BeamMP Server v" + Application::ServerVersionString()); beammp_info("BeamMP Server v" + Application::ServerVersionString());
Application::SetSubsystemStatus("Server", Application::Status::Starting); Application::SetSubsystemStatus("Server", Application::Status::Starting);
@@ -127,15 +128,19 @@ TServer::TServer(const std::vector<std::string_view>& Arguments) {
} }
void TServer::RemoveClient(const std::weak_ptr<TClient>& WeakClientPtr) { void TServer::RemoveClient(const std::weak_ptr<TClient>& WeakClientPtr) {
auto LockedClientPtr = WeakClientPtr.lock(); std::shared_ptr<TClient> LockedClientPtr { nullptr };
if (!LockedClientPtr) { try {
LockedClientPtr = WeakClientPtr.lock();
} catch (const std::exception&) {
// silently fail, as there's nothing to do
return; return;
} }
beammp_assert(LockedClientPtr != nullptr);
TClient& Client = *LockedClientPtr; TClient& Client = *LockedClientPtr;
beammp_debug("removing client " + Client.GetName() + " (" + std::to_string(ClientCount()) + ")"); beammp_debug("removing client " + Client.GetName() + " (" + std::to_string(ClientCount()) + ")");
Client.ClearCars(); Client.ClearCars();
WriteLock Lock(mClientsMutex); WriteLock Lock(mClientsMutex);
mClients.erase(LockedClientPtr); mClients.erase(WeakClientPtr.lock());
} }
void TServer::ForEachClient(const std::function<bool(std::weak_ptr<TClient>)>& Fn) { void TServer::ForEachClient(const std::function<bool(std::weak_ptr<TClient>)>& Fn) {
@@ -156,23 +161,21 @@ size_t TServer::ClientCount() const {
return mClients.size(); return mClients.size();
} }
void TServer::GlobalParser(const std::weak_ptr<TClient>& Client, std::vector<uint8_t>&& Packet, TPPSMonitor& PPSMonitor, TNetwork& Network, bool udp) { void TServer::GlobalParser(const std::weak_ptr<TClient>& Client, std::vector<uint8_t>&& Packet, TPPSMonitor& PPSMonitor, TNetwork& Network) {
constexpr std::string_view ABG = "ABG:"; constexpr std::string_view ABG = "ABG:";
if (Packet.size() >= ABG.size() && std::equal(Packet.begin(), Packet.begin() + ABG.size(), ABG.begin(), ABG.end())) { if (Packet.size() >= ABG.size() && std::equal(Packet.begin(), Packet.begin() + ABG.size(), ABG.begin(), ABG.end())) {
Packet.erase(Packet.begin(), Packet.begin() + ABG.size()); Packet.erase(Packet.begin(), Packet.begin() + ABG.size());
try { try {
Packet = DeComp(Packet); Packet = DeComp(Packet);
} catch (const InvalidDataError& ) { } catch (const InvalidDataError& ) {
if (auto LockedClient = Client.lock()) { auto LockedClient = Client.lock();
beammp_errorf("Failed to decompress packet from client {}. The client sent invalid data and will now be disconnected.", LockedClient->GetID()); beammp_errorf("Failed to decompress packet from client {}. The client sent invalid data and will now be disconnected.", LockedClient->GetID());
Network.ClientKick(*LockedClient, "Sent invalid compressed packet (this is likely a bug on your end)"); Network.ClientKick(*LockedClient, "Sent invalid compressed packet (this is likely a bug on your end)");
}
return; return;
} catch (const std::runtime_error& e) { } catch (const std::runtime_error& e) {
if (auto LockedClient = Client.lock()) { auto LockedClient = Client.lock();
beammp_errorf("Failed to decompress packet from client {}: {}. The server might be out of RAM! The client will now be disconnected.", LockedClient->GetID(), e.what()); beammp_errorf("Failed to decompress packet from client {}: {}. The server might be out of RAM! The client will now be disconnected.", LockedClient->GetID(), e.what());
Network.ClientKick(*LockedClient, "Decompression failed (likely a server-side problem)"); Network.ClientKick(*LockedClient, "Decompression failed (likely a server-side problem)");
}
return; return;
} }
} }
@@ -180,10 +183,10 @@ void TServer::GlobalParser(const std::weak_ptr<TClient>& Client, std::vector<uin
return; return;
} }
auto LockedClient = Client.lock(); if (Client.expired()) {
if (!LockedClient) {
return; return;
} }
auto LockedClient = Client.lock();
std::any Res; std::any Res;
char Code = Packet.at(0); char Code = Packet.at(0);
@@ -192,29 +195,12 @@ void TServer::GlobalParser(const std::weak_ptr<TClient>& Client, std::vector<uin
// V to Y // V to Y
if (Code <= 89 && Code >= 86) { if (Code <= 89 && Code >= 86) {
int PID = -1;
int VID = -1;
auto pidVidPart = StringPacket.substr(3);
auto MaybePidVid = GetPidVid(pidVidPart.substr(0, pidVidPart.find(':')));
if (MaybePidVid) {
std::tie(PID, VID) = MaybePidVid.value();
}
if (PID == -1 || VID == -1 || PID != LockedClient->GetID()) {
return;
}
PPSMonitor.IncrementInternalPPS(); PPSMonitor.IncrementInternalPPS();
Network.SendToAll(LockedClient.get(), Packet, false, false); Network.SendToAll(LockedClient.get(), Packet, false, false);
return; return;
} }
switch (Code) { switch (Code) {
case 'H': // initial connection case 'H': // initial connection
if (udp) {
beammp_debugf("Received 'H' packet over UDP from client '{}' ({}), ignoring it", LockedClient->GetName(), LockedClient->GetID());
return;
}
if (!Network.SyncClient(Client)) { if (!Network.SyncClient(Client)) {
// TODO handle // TODO handle
} }
@@ -222,26 +208,18 @@ void TServer::GlobalParser(const std::weak_ptr<TClient>& Client, std::vector<uin
case 'p': case 'p':
if (!Network.Respond(*LockedClient, StringToVector("p"), false)) { if (!Network.Respond(*LockedClient, StringToVector("p"), false)) {
// failed to send // failed to send
Network.DisconnectClient(*LockedClient, "Failed to send ping"); LockedClient->Disconnect("Failed to send ping");
} else { } else {
Network.UpdatePlayer(*LockedClient); Network.UpdatePlayer(*LockedClient);
} }
return; return;
case 'O': case 'O':
if (udp) {
beammp_debugf("Received 'O' packet over UDP from client '{}' ({}), ignoring it", LockedClient->GetName(), LockedClient->GetID());
return;
}
if (Packet.size() > 1000) { if (Packet.size() > 1000) {
beammp_debug(("Received data from: ") + LockedClient->GetName() + (" Size: ") + std::to_string(Packet.size())); beammp_debug(("Received data from: ") + LockedClient->GetName() + (" Size: ") + std::to_string(Packet.size()));
} }
ParseVehicle(*LockedClient, StringPacket, Network); ParseVehicle(*LockedClient, StringPacket, Network);
return; return;
case 'C': { case 'C': {
if (udp) {
beammp_debugf("Received 'C' packet over UDP from client '{}' ({}), ignoring it", LockedClient->GetName(), LockedClient->GetID());
return;
}
if (Packet.size() < 4 || std::find(Packet.begin() + 3, Packet.end(), ':') == Packet.end()) if (Packet.size() < 4 || std::find(Packet.begin() + 3, Packet.end(), ':') == Packet.end())
break; break;
const auto PacketAsString = std::string(reinterpret_cast<const char*>(Packet.data()), Packet.size()); const auto PacketAsString = std::string(reinterpret_cast<const char*>(Packet.data()), Packet.size());
@@ -254,24 +232,14 @@ void TServer::GlobalParser(const std::weak_ptr<TClient>& Client, std::vector<uin
beammp_debugf("Empty chat message received from '{}' ({}), ignoring it", LockedClient->GetName(), LockedClient->GetID()); beammp_debugf("Empty chat message received from '{}' ({}), ignoring it", LockedClient->GetName(), LockedClient->GetID());
return; return;
} }
if (Message.size() > 500) {
beammp_debugf("Chat message too long from '{}' ({}), ignoring it", LockedClient->GetName(), LockedClient->GetID());
return;
}
auto Futures = LuaAPI::MP::Engine->TriggerEvent("onChatMessage", "", LockedClient->GetID(), LockedClient->GetName(), Message); auto Futures = LuaAPI::MP::Engine->TriggerEvent("onChatMessage", "", LockedClient->GetID(), LockedClient->GetName(), Message);
TLuaEngine::WaitForAll(Futures); TLuaEngine::WaitForAll(Futures);
LogChatMessage(LockedClient->GetName(), LockedClient->GetID(), PacketAsString.substr(PacketAsString.find(':', 3) + 1)); LogChatMessage(LockedClient->GetName(), LockedClient->GetID(), PacketAsString.substr(PacketAsString.find(':', 3) + 1));
bool Rejected = std::any_of(Futures.begin(), Futures.end(), bool Rejected = std::any_of(Futures.begin(), Futures.end(),
[](const std::shared_ptr<TLuaResult>& Elem) { [](const std::shared_ptr<TLuaResult>& Elem) {
auto Snapshot = Elem->GetDetachedSnapshot(); return !Elem->Error
if (Snapshot.Error) { && Elem->Result.is<int>()
return false; && bool(Elem->Result.as<int>());
}
const int* MaybeInt = std::get_if<int>(&Snapshot.Result.V);
if (MaybeInt == nullptr) {
return false;
}
return bool(*MaybeInt);
}); });
if (!Rejected) { if (!Rejected) {
std::string SanitizedPacket = fmt::format("C:{}: {}", LockedClient->GetName(), Message); std::string SanitizedPacket = fmt::format("C:{}: {}", LockedClient->GetName(), Message);
@@ -282,35 +250,16 @@ void TServer::GlobalParser(const std::weak_ptr<TClient>& Client, std::vector<uin
return; return;
} }
case 'E': case 'E':
if (udp) {
beammp_debugf("Received 'E' packet over UDP from client '{}' ({}), ignoring it", LockedClient->GetName(), LockedClient->GetID());
return;
}
HandleEvent(*LockedClient, StringPacket); HandleEvent(*LockedClient, StringPacket);
return; return;
case 'N': case 'N':
Network.SendToAll(LockedClient.get(), Packet, false, true); Network.SendToAll(LockedClient.get(), Packet, false, true);
return; return;
case 'Z': { // position packet case 'Z': // position packet
PPSMonitor.IncrementInternalPPS(); PPSMonitor.IncrementInternalPPS();
int PID = -1;
int VID = -1;
auto pidVidPart = StringPacket.substr(3);
auto MaybePidVid = GetPidVid(pidVidPart.substr(0, pidVidPart.find(':')));
if (MaybePidVid) {
std::tie(PID, VID) = MaybePidVid.value();
}
if (PID == -1 || VID == -1 || PID != LockedClient->GetID()) {
return;
}
Network.SendToAll(LockedClient.get(), Packet, false, false); Network.SendToAll(LockedClient.get(), Packet, false, false);
HandlePosition(*LockedClient, StringPacket); HandlePosition(*LockedClient, StringPacket);
return; return;
}
default: default:
return; return;
} }
@@ -329,15 +278,6 @@ void TServer::HandleEvent(TClient& c, const std::string& RawData) {
} }
std::string Name = RawData.substr(2, NameDataSep - 2); std::string Name = RawData.substr(2, NameDataSep - 2);
std::string Data = RawData.substr(NameDataSep + 1); std::string Data = RawData.substr(NameDataSep + 1);
std::vector<std::string> exclude = {"onInit", "onFileChanged","onVehicleDeleted","onConsoleInput","onPlayerAuth","postPlayerAuth", "onPlayerDisconnect",
"onPlayerConnecting","onPlayerJoining","onPlayerJoin","onChatMessage","postChatMessage","onVehicleSpawn","postVehicleSpawn","onVehicleEdited", "postVehicleEdited",
"onVehicleReset","onVehiclePaintChanged","onShutdown"};
if (std::ranges::find(exclude, Name) != exclude.end()) {
beammp_debugf("Excluded event triggered by client '{}' ({}): '{}', ignoring.", c.GetName(), c.GetID(), Name);
return;
}
LuaAPI::MP::Engine->ReportErrors(LuaAPI::MP::Engine->TriggerEvent(Name, "", c.GetID(), Data)); LuaAPI::MP::Engine->ReportErrors(LuaAPI::MP::Engine->TriggerEvent(Name, "", c.GetID(), Data));
} }
@@ -384,21 +324,12 @@ void TServer::ParseVehicle(TClient& c, const std::string& Pckt, TNetwork& Networ
TLuaEngine::WaitForAll(Futures); TLuaEngine::WaitForAll(Futures);
bool ShouldntSpawn = std::any_of(Futures.begin(), Futures.end(), bool ShouldntSpawn = std::any_of(Futures.begin(), Futures.end(),
[](const std::shared_ptr<TLuaResult>& Result) { [](const std::shared_ptr<TLuaResult>& Result) {
auto Snapshot = Result->GetDetachedSnapshot(); return !Result->Error && Result->Result.is<int>() && Result->Result.as<int>() != 0;
if (Snapshot.Error) {
return false;
}
const int* MaybeInt = std::get_if<int>(&Snapshot.Result.V);
if (MaybeInt == nullptr) {
return false;
}
return *MaybeInt != 0;
}); });
bool SpawnConfirmed = false; bool SpawnConfirmed = false;
auto CarJsonDoc = nlohmann::json::parse(CarJson, nullptr, false); if (ShouldSpawn(c, CarJson, CarID) && !ShouldntSpawn) {
if (ShouldSpawn(c, CarJson, CarID) && !ShouldntSpawn && !CarJsonDoc.is_discarded()) { c.AddNewCar(CarID, Packet);
c.AddNewCar(CarID, CarJsonDoc);
Network.SendToAll(nullptr, StringToVector(Packet), true, true); Network.SendToAll(nullptr, StringToVector(Packet), true, true);
SpawnConfirmed = true; SpawnConfirmed = true;
} else { } else {
@@ -429,15 +360,7 @@ void TServer::ParseVehicle(TClient& c, const std::string& Pckt, TNetwork& Networ
TLuaEngine::WaitForAll(Futures); TLuaEngine::WaitForAll(Futures);
bool ShouldntAllow = std::any_of(Futures.begin(), Futures.end(), bool ShouldntAllow = std::any_of(Futures.begin(), Futures.end(),
[](const std::shared_ptr<TLuaResult>& Result) { [](const std::shared_ptr<TLuaResult>& Result) {
auto Snapshot = Result->GetDetachedSnapshot(); return !Result->Error && Result->Result.is<int>() && Result->Result.as<int>() != 0;
if (Snapshot.Error) {
return false;
}
const int* MaybeInt = std::get_if<int>(&Snapshot.Result.V);
if (MaybeInt == nullptr) {
return false;
}
return *MaybeInt != 0;
}); });
auto FoundPos = Packet.find('{'); auto FoundPos = Packet.find('{');
@@ -491,13 +414,7 @@ void TServer::ParseVehicle(TClient& c, const std::string& Pckt, TNetwork& Networ
} }
if (PID != -1 && VID != -1 && PID == c.GetID()) { if (PID != -1 && VID != -1 && PID == c.GetID()) {
auto BracketPos = Data.find('{'); Data = Data.substr(Data.find('{'));
if (BracketPos == std::string::npos) {
beammp_debugf("Invalid 'Or' packet body from client {}", c.GetID());
return;
}
Data = Data.substr(BracketPos);
LuaAPI::MP::Engine->ReportErrors(LuaAPI::MP::Engine->TriggerEvent("onVehicleReset", "", c.GetID(), VID, Data)); LuaAPI::MP::Engine->ReportErrors(LuaAPI::MP::Engine->TriggerEvent("onVehicleReset", "", c.GetID(), VID, Data));
Network.SendToAll(&c, StringToVector(Packet), false, true); Network.SendToAll(&c, StringToVector(Packet), false, true);
} }
@@ -526,27 +443,9 @@ void TServer::ParseVehicle(TClient& c, const std::string& Pckt, TNetwork& Networ
} }
if (PID != -1 && VID != -1 && PID == c.GetID()) { if (PID != -1 && VID != -1 && PID == c.GetID()) {
auto BracketPos = Data.find('['); Data = Data.substr(Data.find('['));
if (BracketPos == std::string::npos) {
beammp_debugf("Invalid 'Op' packet body from client {}", c.GetID());
return;
}
Data = Data.substr(BracketPos);
LuaAPI::MP::Engine->ReportErrors(LuaAPI::MP::Engine->TriggerEvent("onVehiclePaintChanged", "", c.GetID(), VID, Data)); LuaAPI::MP::Engine->ReportErrors(LuaAPI::MP::Engine->TriggerEvent("onVehiclePaintChanged", "", c.GetID(), VID, Data));
Network.SendToAll(&c, StringToVector(Packet), false, true); Network.SendToAll(&c, StringToVector(Packet), false, true);
auto CarData = c.GetCarData(VID);
if (CarData == nlohmann::detail::value_t::null)
return;
if (CarData.contains("vcf") && CarData.at("vcf").is_object())
if (CarData.at("vcf").contains("paints") && CarData.at("vcf").at("paints").is_array()) {
CarData.at("vcf")["paints"] = nlohmann::json::parse(Data);
c.SetCarData(VID, CarData);
}
} }
return; return;
} }
@@ -562,22 +461,42 @@ void TServer::Apply(TClient& c, int VID, const std::string& pckt) {
beammp_error("Malformed packet received, no '{' found"); beammp_error("Malformed packet received, no '{' found");
return; return;
} }
std::string Packet = pckt.substr(FoundPos); std::string Packet = pckt.substr(FoundPos);
nlohmann::json VD = c.GetCarData(VID); std::string VD = c.GetCarData(VID);
if (VD == nlohmann::detail::value_t::null) { if (VD.empty()) {
beammp_error("Tried to apply change to vehicle that does not exist"); beammp_error("Tried to apply change to vehicle that does not exist");
return; return;
} }
std::string Header = VD.substr(0, VD.find('{'));
nlohmann::json Pack = nlohmann::json::parse(Packet, nullptr, false); FoundPos = VD.find('{');
if (FoundPos == std::string::npos) {
if (Pack.is_discarded()) { return;
}
VD = VD.substr(FoundPos);
rapidjson::Document Veh, Pack;
Veh.Parse(VD.c_str());
if (Veh.HasParseError()) {
beammp_error("Could not get vehicle config!");
return;
}
Pack.Parse(Packet.c_str());
if (Pack.HasParseError() || Pack.IsNull()) {
beammp_error("Could not get active vehicle config!"); beammp_error("Could not get active vehicle config!");
return; return;
} }
c.SetCarData(VID, Pack); for (auto& M : Pack.GetObject()) {
if (Veh[M.name].IsNull()) {
Veh.AddMember(M.name, M.value, Veh.GetAllocator());
} else {
Veh[M.name] = Pack[M.name];
}
}
rapidjson::StringBuffer Buffer;
rapidjson::Writer<rapidjson::StringBuffer> writer(Buffer);
Veh.Accept(writer);
c.SetCarData(VID, Header + Buffer.GetString());
} }
void TServer::InsertClient(const std::shared_ptr<TClient>& NewClient) { void TServer::InsertClient(const std::shared_ptr<TClient>& NewClient) {
+1 -5
View File
@@ -21,7 +21,7 @@
#include "Common.h" #include "Common.h"
#include <utility> #include <utility>
TVehicleData::TVehicleData(int ID, nlohmann::json Data) TVehicleData::TVehicleData(int ID, std::string Data)
: mID(ID) : mID(ID)
, mData(std::move(Data)) { , mData(std::move(Data)) {
beammp_trace("vehicle " + std::to_string(mID) + " constructed"); beammp_trace("vehicle " + std::to_string(mID) + " constructed");
@@ -30,7 +30,3 @@ TVehicleData::TVehicleData(int ID, nlohmann::json Data)
TVehicleData::~TVehicleData() { TVehicleData::~TVehicleData() {
beammp_trace("vehicle " + std::to_string(mID) + " destroyed"); beammp_trace("vehicle " + std::to_string(mID) + " destroyed");
} }
std::string TVehicleData::DataAsPacket(const std::string& Role, const std::string& Name, const int ID) const {
return "Os:" + Role + ":" + Name + ":" + std::to_string(ID) + "-" + std::to_string(this->mID) + ":" + this->mData.dump();
}
+9 -25
View File
@@ -17,7 +17,6 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
#include "ArgsParser.h" #include "ArgsParser.h"
#include "Client.h"
#include "Common.h" #include "Common.h"
#include "Http.h" #include "Http.h"
#include "LuaAPI.h" #include "LuaAPI.h"
@@ -176,9 +175,17 @@ int BeamMPServerMain(MainArguments Arguments) {
Application::SetSubsystemStatus("Main", Application::Status::ShuttingDown); Application::SetSubsystemStatus("Main", Application::Status::ShuttingDown);
Shutdown = true; Shutdown = true;
}); });
Application::RegisterShutdownHandler([] {
auto Futures = LuaAPI::MP::Engine->TriggerEvent("onShutdown", "");
TLuaEngine::WaitForAll(Futures, std::chrono::seconds(5));
});
TServer Server(Arguments.List); TServer Server(Arguments.List);
auto LuaEngine = std::make_shared<TLuaEngine>();
LuaEngine->SetServer(&Server);
Application::Console().InitializeLuaConsole(*LuaEngine);
RegisterThread("Main"); RegisterThread("Main");
beammp_trace("Running in debug mode on a debug build"); beammp_trace("Running in debug mode on a debug build");
@@ -187,30 +194,13 @@ int BeamMPServerMain(MainArguments Arguments) {
TPPSMonitor PPSMonitor(Server); TPPSMonitor PPSMonitor(Server);
THeartbeatThread Heartbeat(ResourceManager, Server); THeartbeatThread Heartbeat(ResourceManager, Server);
TNetwork Network(Server, PPSMonitor, ResourceManager); TNetwork Network(Server, PPSMonitor, ResourceManager);
auto LuaEngine = std::make_shared<TLuaEngine>();
LuaEngine->SetServer(&Server);
Application::Console().InitializeLuaConsole(*LuaEngine);
LuaEngine->SetNetwork(&Network); LuaEngine->SetNetwork(&Network);
PPSMonitor.SetNetwork(Network); PPSMonitor.SetNetwork(Network);
Application::CheckForUpdates(); Application::CheckForUpdates();
TPluginMonitor PluginMonitor(fs::path(Application::Settings.getAsString(Settings::Key::General_ResourceFolder)) / "Server", LuaEngine); TPluginMonitor PluginMonitor(fs::path(Application::Settings.getAsString(Settings::Key::General_ResourceFolder)) / "Server", LuaEngine);
Application::RegisterShutdownHandler([] { Application::SetSubsystemStatus("Main", Application::Status::Good);
auto Futures = LuaAPI::MP::Engine->TriggerEvent("onShutdown", "");
TLuaEngine::WaitForAll(Futures, std::chrono::seconds(5));
});
Application::RegisterShutdownHandler([&Server, &Network] {
beammp_debug("Kicking all players due to shutdown");
Server.ForEachClient([&Network](std::weak_ptr<TClient> client) -> bool {
if (!client.expired()) {
Network.ClientKick(*client.lock(), "Server shutdown");
}
return true;
});
});
RegisterThread("Main(Waiting)"); RegisterThread("Main(Waiting)");
std::set<std::string> IgnoreSubsystems { std::set<std::string> IgnoreSubsystems {
@@ -225,10 +215,6 @@ int BeamMPServerMain(MainArguments Arguments) {
std::string SystemsBadList {}; std::string SystemsBadList {};
auto Statuses = Application::GetSubsystemStatuses(); auto Statuses = Application::GetSubsystemStatuses();
for (const auto& NameStatusPair : Statuses) { for (const auto& NameStatusPair : Statuses) {
if (NameStatusPair.first == "Main") {
continue;
}
if (IgnoreSubsystems.count(NameStatusPair.first) > 0) { if (IgnoreSubsystems.count(NameStatusPair.first) > 0) {
continue; // ignore continue; // ignore
} }
@@ -242,8 +228,6 @@ int BeamMPServerMain(MainArguments Arguments) {
// remove ", " // remove ", "
SystemsBadList = SystemsBadList.substr(0, SystemsBadList.size() - 2); SystemsBadList = SystemsBadList.substr(0, SystemsBadList.size() - 2);
if (FullyStarted) { if (FullyStarted) {
Application::SetSubsystemStatus("Main", Application::Status::Good);
if (!WithErrors) { if (!WithErrors) {
beammp_info("ALL SYSTEMS STARTED SUCCESSFULLY, EVERYTHING IS OKAY"); beammp_info("ALL SYSTEMS STARTED SUCCESSFULLY, EVERYTHING IS OKAY");
} else { } else {
+1 -1
Submodule vcpkg updated: 5bf0c55239...6978381401
+2 -14
View File
@@ -14,18 +14,6 @@
"openssl", "openssl",
"rapidjson", "rapidjson",
"sol2", "sol2",
"curl", "curl"
"lua" ]
],
"overrides": [
{
"name": "sol2",
"version": "3.3.1"
},
{
"name": "lua",
"version": "5.3.5#6"
}
],
"builtin-baseline": "5bf0c55239da398b8c6f450818c9e28d36bf9966"
} }