diff --git a/.github/workflows/cmake-linux.yml b/.github/workflows/cmake-linux.yml index 3ddf653..b831148 100644 --- a/.github/workflows/cmake-linux.yml +++ b/.github/workflows/cmake-linux.yml @@ -7,38 +7,72 @@ env: jobs: linux-build: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v2 - with: - submodules: 'recursive' + - uses: actions/checkout@v2 + with: + submodules: "recursive" - - name: Install Dependencies - env: - beammp_sentry_url: ${{ secrets.BEAMMP_SECRET_SENTRY_URL }} - run: | + - name: Install Dependencies + env: + beammp_sentry_url: ${{ secrets.BEAMMP_SECRET_SENTRY_URL }} + run: | echo ${#beammp_sentry_url} sudo apt-get update - sudo apt-get install -y libz-dev rapidjson-dev liblua5.3 libssl-dev libwebsocketpp-dev libcurl4-openssl-dev + sudo apt-get install -y libz-dev rapidjson-dev liblua5.3 libssl-dev libwebsocketpp-dev libcurl4-openssl-dev cmake g++-10 libboost1.74-all-dev - - name: Create Build Environment - run: cmake -E make_directory ${{github.workspace}}/build-linux + - name: Create Build Environment + run: cmake -E make_directory ${{github.workspace}}/build-linux - - name: Configure CMake - shell: bash - working-directory: ${{github.workspace}}/build-linux - env: - beammp_sentry_url: ${{ secrets.BEAMMP_SECRET_SENTRY_URL }} - run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_CXX_COMPILER=g++-10 -DBEAMMP_SECRET_SENTRY_URL="$beammp_sentry_url" + - name: Configure CMake + shell: bash + working-directory: ${{github.workspace}}/build-linux + env: + beammp_sentry_url: ${{ secrets.BEAMMP_SECRET_SENTRY_URL }} + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_CXX_COMPILER=g++-10 -DBEAMMP_SECRET_SENTRY_URL="$beammp_sentry_url" - - name: Build - working-directory: ${{github.workspace}}/build-linux - shell: bash - run: cmake --build . --config $BUILD_TYPE + - name: Build Server + working-directory: ${{github.workspace}}/build-linux + shell: bash + run: cmake --build . --config $BUILD_TYPE -t BeamMP-Server --parallel - - name: Archive artifacts - uses: actions/upload-artifact@v2 - with: - name: BeamMP-Server-linux - path: ${{github.workspace}}/build-linux/BeamMP-Server + - name: Build Tests + working-directory: ${{github.workspace}}/build-linux + shell: bash + run: cmake --build . --config $BUILD_TYPE -t BeamMP-Server-tests --parallel + + - name: Archive server artifact + uses: actions/upload-artifact@v2 + with: + name: BeamMP-Server-linux + path: ${{github.workspace}}/build-linux/BeamMP-Server + + - name: Archive test artifact + uses: actions/upload-artifact@v2 + with: + name: BeamMP-Server-linux-tests + path: ${{github.workspace}}/build-linux/BeamMP-Server-tests + + run-tests: + needs: linux-build + runs-on: ubuntu-22.04 + + steps: + - uses: actions/download-artifact@master + with: + name: BeamMP-Server-linux-tests + path: ${{github.workspace}} + + - name: Install Runtime Dependencies + shell: bash + run: | + sudo apt-get update + sudo apt-get install -y liblua5.3-0 libssl3 curl + + - name: Test + working-directory: ${{github.workspace}} + shell: bash + run: | + chmod +x ./BeamMP-Server-tests + ./BeamMP-Server-tests diff --git a/.github/workflows/cmake-windows.yml b/.github/workflows/cmake-windows.yml index b882766..4c2ba3b 100644 --- a/.github/workflows/cmake-windows.yml +++ b/.github/workflows/cmake-windows.yml @@ -20,7 +20,7 @@ jobs: with: vcpkgArguments: 'lua zlib rapidjson openssl websocketpp curl' vcpkgDirectory: '${{ runner.workspace }}/b/vcpkg' - vcpkgGitCommitId: 'a106de33bbee694e3be6243718aa2a549a692832' + vcpkgGitCommitId: "06b5f4a769d848d1a20fa0acd556019728b56273" vcpkgTriplet: 'x64-windows-static' - name: Create Build Environment diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml index 2f8e3f3..9b9733d 100644 --- a/.github/workflows/release-build.yml +++ b/.github/workflows/release-build.yml @@ -32,7 +32,7 @@ jobs: upload-release-files-linux: name: Upload Linux Release Files - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 needs: create-release steps: - uses: actions/checkout@v2 @@ -42,7 +42,7 @@ jobs: - name: Install Dependencies run: | sudo apt-get update - sudo apt-get install -y libz-dev rapidjson-dev liblua5.3 libssl-dev libwebsocketpp-dev libcurl4-openssl-dev + sudo apt-get install -y libz-dev rapidjson-dev liblua5.3 libssl-dev libwebsocketpp-dev libcurl4-openssl-dev libboost-dev libboost1.74-all-dev libboost1.74-dev - name: Create Build Environment run: cmake -E make_directory ${{github.workspace}}/build-linux @@ -85,7 +85,7 @@ jobs: with: vcpkgArguments: 'lua zlib rapidjson openssl websocketpp curl' vcpkgDirectory: '${{ runner.workspace }}/b/vcpkg' - vcpkgGitCommitId: 'a106de33bbee694e3be6243718aa2a549a692832' + vcpkgGitCommitId: '06b5f4a769d848d1a20fa0acd556019728b56273' vcpkgTriplet: 'x64-windows-static' - name: Create Build Environment diff --git a/.gitmodules b/.gitmodules index b54a97b..e513ccf 100644 --- a/.gitmodules +++ b/.gitmodules @@ -25,3 +25,9 @@ [submodule "deps/json"] path = deps/json url = https://github.com/nlohmann/json +[submodule "deps/fmt"] + path = deps/fmt + url = https://github.com/fmtlib/fmt +[submodule "deps/doctest"] + path = deps/doctest + url = https://github.com/doctest/doctest diff --git a/CMakeLists.txt b/CMakeLists.txt index 72b1cef..31c196e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,5 @@ -cmake_minimum_required(VERSION 3.0) +# 3.4 is required for imported targets. +cmake_minimum_required(VERSION 3.4 FATAL_ERROR) message(STATUS "You can find build instructions and a list of dependencies in the README at \ https://github.com/BeamMP/BeamMP-Server") @@ -8,19 +9,26 @@ project(BeamMP-Server HOMEPAGE_URL https://beammp.com LANGUAGES CXX C) +find_package(Git REQUIRED) +# Update submodules as needed +option(GIT_SUBMODULE "Check submodules during build" ON) +if(GIT_SUBMODULE) + message(STATUS "Submodule update") + execute_process(COMMAND ${GIT_EXECUTABLE} submodule update --init --recursive + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + RESULT_VARIABLE GIT_SUBMOD_RESULT) + if(NOT GIT_SUBMOD_RESULT EQUAL "0") + message(FATAL_ERROR "git submodule update --init --recursive failed with ${GIT_SUBMOD_RESULT}, please checkout submodules") + endif() +endif() + + set(HTTPLIB_REQUIRE_OPENSSL ON) +set(SENTRY_BUILD_SHARED_LIBS OFF) -include_directories("${PROJECT_SOURCE_DIR}/deps/asio/asio/include") -include_directories("${PROJECT_SOURCE_DIR}/deps/rapidjson/include") -include_directories("${PROJECT_SOURCE_DIR}/deps/websocketpp") -include_directories("${PROJECT_SOURCE_DIR}/deps/commandline") -include_directories("${PROJECT_SOURCE_DIR}/deps/sol2/include") -include_directories("${PROJECT_SOURCE_DIR}/deps/cpp-httplib") -include_directories("${PROJECT_SOURCE_DIR}/deps/json/single_include") -include_directories("${PROJECT_SOURCE_DIR}/deps") - -add_compile_definitions(CPPHTTPLIB_OPENSSL_SUPPORT) +add_compile_definitions(CPPHTTPLIB_OPENSSL_SUPPORT=1) +# ------------------------ APPLE --------------------------------- if(APPLE) if(IS_DIRECTORY /opt/homebrew/Cellar/lua@5.3/5.3.6) set(LUA_INCLUDE_DIR /opt/homebrew/Cellar/lua@5.3/5.3.6/include/lua5.3) @@ -37,20 +45,28 @@ if(APPLE) include_directories(/usr/local/opt/openssl@1.1/include) link_directories(/usr/local/opt/openssl@1.1/lib) endif() -endif() - -if (WIN32) +# ------------------------ WINDOWS --------------------------------- +option(WIN32_STATIC_RUNTIME "Build statically-linked runtime on windows (don't touch unless you know what you're doing)" ON) +elseif (WIN32) # this has to happen before sentry, so that crashpad on windows links with these settings. - message(STATUS "MSVC -> forcing use of statically-linked runtime.") - STRING(REPLACE "/MD" "/MT" CMAKE_CXX_FLAGS_RELEASE ${CMAKE_CXX_FLAGS_RELEASE}) - STRING(REPLACE "/MDd" "/MTd" CMAKE_CXX_FLAGS_DEBUG ${CMAKE_CXX_FLAGS_DEBUG}) -endif() + if (WIN32_STATIC_RUNTIME) + message(STATUS "MSVC -> forcing use of statically-linked runtime.") + STRING(REPLACE "/MD" "/MT" CMAKE_CXX_FLAGS_RELEASE ${CMAKE_CXX_FLAGS_RELEASE}) + STRING(REPLACE "/MDd" "/MTd" CMAKE_CXX_FLAGS_DEBUG ${CMAKE_CXX_FLAGS_DEBUG}) + endif() +# ------------------------ LINUX --------------------------------- +elseif (UNIX) + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -O0 -g") + set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O2 -fno-builtin") + option(SANITIZE "Turns on thread and UB sanitizers" OFF) + if (SANITIZE) + message(STATUS "sanitize is ON") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize={address,thread,undefined}") + endif (SANITIZE) +endif () -include_directories("include/sentry-native/include") set(BUILD_SHARED_LIBS OFF) -if (MSVC) - set(SENTRY_BUILD_RUNTIMESTATIC ON) -endif() +# ------------------------ SENTRY --------------------------------- message(STATUS "Checking for Sentry URL") # this is set by the build system. # IMPORTANT: if you're building from source, just leave this empty @@ -60,120 +76,186 @@ if (NOT DEFINED BEAMMP_SECRET_SENTRY_URL) set(BEAMMP_SECRET_SENTRY_URL "") set(SENTRY_BACKEND none) else() - string(LENGTH ${BEAMMP_SECRET_SENTRY_URL} URL_LEN) - message(STATUS "Sentry URL is length ${URL_LEN}") set(SENTRY_BACKEND breakpad) endif() add_subdirectory("deps/sentry-native") +# ------------------------ C++ SETUP --------------------------------- +set(CMAKE_CXX_STANDARD 17) if (MSVC) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /bigobj") endif () +# ------------------------ DEPENDENCIES ------------------------------ message(STATUS "Adding local source dependencies") # this has to happen before -DDEBUG since it wont compile properly with -DDEBUG add_subdirectory(deps) -message(STATUS "Setting compiler flags") -if (WIN32) +# ------------------------ VARIABLES --------------------------------- - #-DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake -DVCPKG_TARGET_TRIPLET=x64-windows-static - set(VcpkgRoot ${_VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}) - include_directories(${VcpkgRoot}/include) - link_directories(${VcpkgRoot}/lib) -elseif (UNIX) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wpedantic -static-libstdc++") - set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -O0 -g") - set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O2 -fno-builtin") - if (SANITIZE) - message(STATUS "sanitize is ON") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=undefined,thread") - endif (SANITIZE) +include(FindLua) +include(FindOpenSSL) +include(FindThreads) +include(FindZLIB) + +find_package(Boost 1.70 REQUIRED COMPONENTS system) + +set(BeamMP_Sources + include/TConsole.h src/TConsole.cpp + include/TServer.h src/TServer.cpp + include/Compat.h src/Compat.cpp + include/Common.h src/Common.cpp + include/Client.h src/Client.cpp + include/VehicleData.h src/VehicleData.cpp + include/TConfig.h src/TConfig.cpp + include/TLuaEngine.h src/TLuaEngine.cpp + include/TLuaPlugin.h src/TLuaPlugin.cpp + include/TResourceManager.h src/TResourceManager.cpp + include/THeartbeatThread.h src/THeartbeatThread.cpp + include/Http.h src/Http.cpp + include/TSentry.h src/TSentry.cpp + include/TPPSMonitor.h src/TPPSMonitor.cpp + include/TNetwork.h src/TNetwork.cpp + include/LuaAPI.h src/LuaAPI.cpp + include/TScopedTimer.h src/TScopedTimer.cpp + include/SignalHandling.h src/SignalHandling.cpp + include/ArgsParser.h src/ArgsParser.cpp + include/TPluginMonitor.h src/TPluginMonitor.cpp + include/Environment.h + include/BoostAliases.h +) + +set(BeamMP_Includes + ${LUA_INCLUDE_DIR} + ${CURL_INCLUDE_DIRS} + "${CMAKE_CURRENT_SOURCE_DIR}/deps/cpp-httplib" + "${CMAKE_CURRENT_SOURCE_DIR}/deps/commandline" + "${CMAKE_CURRENT_SOURCE_DIR}/deps/json/single_include" + "${CMAKE_CURRENT_SOURCE_DIR}/deps/sol2/include" + "${CMAKE_CURRENT_SOURCE_DIR}/deps/rapidjson/include" + "${CMAKE_CURRENT_SOURCE_DIR}/deps/asio/asio/include" + "${CMAKE_CURRENT_SOURCE_DIR}/deps" +) + +set(BeamMP_Definitions + SECRET_SENTRY_URL="${BEAMMP_SECRET_SENTRY_URL}" +) + +if (WIN32) + list(APPEND BeamMP_Definitions _WIN32_WINNT=0x0601) + list(APPEND BeamMP_Definitions _CRT_SECURE_NO_WARNINGS) +endif() +if (UNIX) + set(BeamMP_CompileOptions + -Wall + -Wextra + -Wpedantic + + -Werror=uninitialized + -Werror=float-equal + -Werror=pointer-arith + -Werror=double-promotion + -Werror=write-strings + -Werror=cast-qual + -Werror=init-self + -Werror=cast-align + -Werror=unreachable-code + -Werror=strict-aliasing -fstrict-aliasing + -Werror=redundant-decls + -Werror=missing-declarations + -Werror=missing-field-initializers + -Werror=write-strings + -Werror=ctor-dtor-privacy + -Werror=switch-enum + -Werror=switch-default + -Werror=old-style-cast + -Werror=overloaded-virtual + -Werror=overloaded-virtual + -Werror=missing-include-dirs + -Werror=unused-result + + -fstack-protector + -Wzero-as-null-pointer-constant + ) +endif() + +set(BeamMP_Libraries + Boost::boost + Boost::system + doctest::doctest + OpenSSL::SSL + OpenSSL::Crypto + sol2::sol2 + fmt::fmt + Threads::Threads + ZLIB::ZLIB + ${LUA_LIBRARIES} + commandline + sentry +) + +if (WIN32) + set(BeamMP_PlatformLibs wsock32 ws2_32) endif () - - -set(CMAKE_CXX_STANDARD 17) - -set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DDEBUG") +# ------------------------ BEAMMP SERVER ----------------------------- add_executable(BeamMP-Server src/main.cpp - include/TConsole.h src/TConsole.cpp - include/TServer.h src/TServer.cpp - include/Compat.h src/Compat.cpp - include/Common.h src/Common.cpp - include/Client.h src/Client.cpp - include/VehicleData.h src/VehicleData.cpp - include/TConfig.h src/TConfig.cpp - include/TLuaEngine.h src/TLuaEngine.cpp - include/TLuaPlugin.h src/TLuaPlugin.cpp - include/TResourceManager.h src/TResourceManager.cpp - include/THeartbeatThread.h src/THeartbeatThread.cpp - include/Http.h src/Http.cpp - include/TSentry.h src/TSentry.cpp - include/TPPSMonitor.h src/TPPSMonitor.cpp - include/TNetwork.h src/TNetwork.cpp - include/LuaAPI.h src/LuaAPI.cpp - include/TScopedTimer.h src/TScopedTimer.cpp - include/SignalHandling.h src/SignalHandling.cpp - include/ArgsParser.h src/ArgsParser.cpp - include/Environment.h) + ${BeamMP_Sources} +) -target_compile_definitions(BeamMP-Server PRIVATE SECRET_SENTRY_URL="${BEAMMP_SECRET_SENTRY_URL}") -include_directories(BeamMP-Server PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) +target_compile_definitions(BeamMP-Server PRIVATE + ${BeamMP_Definitions} + DOCTEST_CONFIG_DISABLE +) -target_include_directories(BeamMP-Server PUBLIC - "${CMAKE_CURRENT_SOURCE_DIR}/include" - "${CMAKE_CURRENT_SOURCE_DIR}/commandline") +target_compile_options(BeamMP-Server PRIVATE + ${BeamMP_CompileOptions} +) -if (APPLE) - message(STATUS "NOT looking for Lua on APPLE") -else() - message(STATUS "Looking for Lua") - find_package(Lua REQUIRED VERSION 5.3) +target_include_directories(BeamMP-Server PRIVATE + "${CMAKE_CURRENT_SOURCE_DIR}/include" +) + +target_include_directories(BeamMP-Server SYSTEM PRIVATE + ${BeamMP_Includes} +) + +target_link_libraries(BeamMP-Server + ${BeamMP_Libraries} + ${BeamMP_PlatformLibs} +) + +# ------------------------ BEAMMP SERVER TESTS ----------------------- + +option(BUILD_TESTS "Build BeamMP-Server tests" ON) + +if(BUILD_TESTS) + add_executable(BeamMP-Server-tests + test/test_main.cpp + ${BeamMP_Sources} + ) + + target_compile_definitions(BeamMP-Server-tests PRIVATE + ${BeamMP_Definitions} + ) + + target_compile_options(BeamMP-Server-tests PRIVATE + ${BeamMP_CompileOptions} + ) + + target_include_directories(BeamMP-Server-tests PRIVATE + "${CMAKE_CURRENT_SOURCE_DIR}/include" + ) + + target_include_directories(BeamMP-Server-tests SYSTEM PRIVATE + ${BeamMP_Includes} + ) + + target_link_libraries(BeamMP-Server-tests + ${BeamMP_Libraries} + ${BeamMP_PlatformLibs} + ) endif() -target_include_directories(BeamMP-Server PUBLIC - ${LUA_INCLUDE_DIR} - ${CURL_INCLUDE_DIRS} - "include/tomlplusplus" - "include/sentry-native/include" - "include/curl/include") - -message(STATUS "Looking for SSL") - -if (APPLE) - set(OPENSSL_LIBRARIES ssl crypto) -else() - find_package(OpenSSL REQUIRED) -endif() - -target_link_libraries(BeamMP-Server sol2::sol2 ${LUA_LIBRARIES}) -message(STATUS "CURL IS ${CURL_LIBRARIES}") - -if (UNIX) - target_link_libraries(BeamMP-Server - z - pthread - ${LUA_LIBRARIES} - crypto - ${OPENSSL_LIBRARIES} - commandline - sentry - ssl) -elseif (WIN32) - include(FindLua) - message(STATUS "Looking for libz") - find_package(ZLIB REQUIRED) - message(STATUS "Looking for RapidJSON") - find_package(RapidJSON CONFIG REQUIRED) - target_include_directories(BeamMP-Server PRIVATE ${RAPIDJSON_INCLUDE_DIRS}) - target_link_libraries(BeamMP-Server - ws2_32 - ZLIB::ZLIB - ${LUA_LIBRARIES} - ${OPENSSL_LIBRARIES} - commandline - sentry) -endif () diff --git a/Changelog.md b/Changelog.md index 0486505..9ed32e4 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,3 +1,29 @@ + +# v3.1.0 + +- ADDED Tab autocomplete in console, smart tab autocomplete (understands lua tables and types) in the lua console +- ADDED lua debug facilities (type `:help` when attached to lua via `lua`) +- ADDED Util.JsonEncode() and Util.JsonDecode(), which turn lua tables into json and vice-versa +- ADDED FS.ListFiles and FS.ListDirectories +- ADDED onFileChanged event, triggered when a server plugin file changes +- ADDED MP.GetPositionRaw(), which can be used to retrieve the latest position packet per player, per vehicle +- ADDED error messages to some lua functions +- ADDED HOME and END button working in console +- ADDED `MP.TriggerClientEventJson()` which takes a table as the data argument and sends it as JSON +- ADDED identifiers (beammp id, ip) to onPlayerAuth (4th argument) +- ADDED more network debug logging +- CHANGED all networking to be more stable, performant, and safe +- FIXED `ip` in MP.GetPlayerIdentifiers +- FIXED issue with client->server events which contain `:` +- FIXED a fatal exception on LuaEngine startup if Resources/Server is a symlink +- FIXED onInit not being called on hot-reload +- FIXED incorrect timing calculation of Lua EventTimer loop +- FIXED bug which caused hot-reload not to report syntax errors +- FIXED missing error messages on some event handler calls +- FIXED vehicles not deleting for all players if an edit was cancelled by Lua +- FIXED server not handling binary UDP packets properly +- REMOVED "Backend response failed to parse as valid json" message + # v3.0.2 - ADDED Periodic update message if a new server is released diff --git a/README.md b/README.md index 64006d8..724ca30 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ These values are guesstimated and are subject to change with each release. ## Contributing -TLDR; [Issues](https://github.com/BeamMP/BeamMP-Server/issues) with the "help wanted" label or with nobody assigned. +TLDR; [Issues](https://github.com/BeamMP/BeamMP-Server/issues) with the "help wanted" or "good first issue" label or with nobody assigned. To contribute, look at the active [issues](https://github.com/BeamMP/BeamMP-Server/issues). Any issues that have the "help wanted" label or don't have anyone assigned are good tasks to take on. You can either contribute by programming or by testing and adding more info and ideas. @@ -39,7 +39,7 @@ If you need support with understanding the codebase, please write us in the Disc ## About Building from Source -We only allow building unmodified (original) source code for public use. `master` is considered **unstable** and we will not provide technical support if such a build doesn't work, so always build from a tag. You can checkout a tag with `git checkout tags/TAGNAME`, where `TAGNAME` is the tag, for example `v1.20`. +We only allow building unmodified (original) source code for public use. `master` is considered **unstable** and we will not provide technical support if such a build doesn't work, so always build from a tag. You can checkout a tag with `git checkout tags/TAGNAME`, where `TAGNAME` is the tag, for example `v3.1.0`. ## Supported Operating Systems @@ -51,7 +51,7 @@ You can find precompiled binaries under [Releases](https://github.com/BeamMP/Bea ## Build Instructions -**__Do not compile from `master`. Always build from a release tag, i.e. `tags/v2.3.3`!__** +**__Do not compile from `master`. Always build from a release tag, i.e. `tags/v3.1.0`!__** Currently only Linux and Windows are supported (generally). See [Releases](https://github.com/BeamMP/BeamMP-Server/releases/) for official binary releases. On systems to which we do not provide binaries (so anything but windows), you are allowed to compile the program and use it. Other restrictions, such as not being allowed to distribute those binaries, still apply (see [copyright notice](#copyright)). @@ -59,36 +59,100 @@ Currently only Linux and Windows are supported (generally). See [Releases](https #### Windows +There are **no runtime libraries** needed for Windows. + Please use the prepackaged binaries in [Releases](https://github.com/BeamMP/BeamMP-Server/releases/). Dependencies for **Windows** can be installed with `vcpkg`. These are: ``` -lua -zlib -rapidjson -openssl -websocketpp -curl +lua zlib rapidjson openssl websocketpp curl ``` +The triplet we use for releases is `x64-windows-static`. #### Linux -Runtime dependencies - you want to find packages for: -- libz -- rapidjson -- lua5.3 -- ssl / openssl -- websocketpp -- curl (with ssl support) +We recommend Ubuntu 22.04 or Arch Linux. Any Linux distribution will work, but you have to figure out the package names yourself (please feel free to PR in a change to this README with that info). -Build-time dependencies are: +##### Runtime Dependencies + +These are needed to *run* the server. + + + +Ubuntu 22.04 + + +`apt-get install` the following libraries: +``` +liblua5.3-0 +libssl3 +curl +``` + + + + +Arch Linux + + +`pacman -Syu` the following libraries: +``` +lua53 +openssl +curl +``` + + +##### Build Dependencies +These are needed for you to *build* the server, in addition to the [runtime dependencies](#runtime-dependencies). + +**Ubuntu 22.04** +``` + +``` + + + +Ubuntu 22.04 + + +`apt-get install` the following libraries and programs: ``` git -make +libz-dev +rapidjson-dev +liblua5.3 +libssl-dev +libwebsocketpp-dev +libcurl4-openssl-dev +cmake +g++-10 +libboost1.74-all-dev +libssl3 +curl +``` + + + + +Arch Linux + + +`pacman -Syu` the following libraries and programs: +``` +lua53 +openssl +curl +git cmake g++ +cmake +zlib +boost +websocketpp ``` + #### macOS @@ -106,13 +170,12 @@ brew install curl zlib git make On Windows, use git-bash for these commands. On Linux, these should work in your shell. 1. Make sure you have all [prerequisites](#prerequisites) installed -2. Clone the repository in a location of your choice with `git clone --recurse-submodules https://github.com/BeamMP/BeamMP-Server`. +2. Clone the repository in a location of your choice with `git clone https://github.com/BeamMP/BeamMP-Server` . 3. Change into the BeamMP-Server directory by running `cd BeamMP-Server`. -4. Checkout the branch of the release you want to compile, for example `git checkout tags/v3.0.2` for version 3.0.2. You can find the latest version [here](https://github.com/BeamMP/BeamMP-Server/tags). -5. Ensure that all submodules are initialized by running `git submodule update --init --recursive` -6. Run `cmake . -DCMAKE_BUILD_TYPE=Release` (with `.`) -7. Run `make` -8. You will now have a `BeamMP-Server` file in your directory, which is executable with `./BeamMP-Server` (`.\BeamMP-Server.exe` for windows). Follow the (Windows or Linux, doesnt matter) instructions on the [wiki](https://wiki.beammp.com/en/home/server-installation) for further setup after installation (which we just did), such as port-forwarding and getting a key to actually run the server. +4. Checkout the branch or tag of the release you want to compile, for example `git checkout tags/v3.0.2` for version 3.0.2. You can find the latest version [here](https://github.com/BeamMP/BeamMP-Server/tags). +6. Run `cmake . -DCMAKE_BUILD_TYPE=Release` (with `.`). This may take some time, and will update all submodules and prepare the build. +7. Run `make -j` . This step will take some time and will use a lot of CPU and RAM. Remove the `-j` if you run out of memory. *If you change something in the source code, you only have to re-run this step.* +8. You now have a `BeamMP-Server` file in your directory, which is executable with `./BeamMP-Server` (`.\BeamMP-Server.exe` for windows). Follow the (Windows or Linux, doesnt matter) instructions on the [wiki](https://wiki.beammp.com/en/home/server-installation) for further setup after installation (which we just did), such as port-forwarding and getting a key to actually run the server. *tip: to run the server in the background, simply (in bash, zsh, etc) run:* `nohup ./BeamMP-Server &`*.* diff --git a/deps/CMakeLists.txt b/deps/CMakeLists.txt index 4e556b0..f439b19 100644 --- a/deps/CMakeLists.txt +++ b/deps/CMakeLists.txt @@ -1,9 +1,6 @@ -include_directories("${PROJECT_SOURCE_DIR}/deps/asio/asio/include") -include_directories("${PROJECT_SOURCE_DIR}/deps/rapidjson/include") -include_directories("${PROJECT_SOURCE_DIR}/deps/websocketpp") -include_directories("${PROJECT_SOURCE_DIR}/deps/commandline") -include_directories("${PROJECT_SOURCE_DIR}/deps/sol2/include") include_directories("${PROJECT_SOURCE_DIR}/deps") add_subdirectory("${PROJECT_SOURCE_DIR}/deps/commandline") +add_subdirectory("${PROJECT_SOURCE_DIR}/deps/fmt") add_subdirectory("${PROJECT_SOURCE_DIR}/deps/sol2") +add_subdirectory("${PROJECT_SOURCE_DIR}/deps/doctest") diff --git a/deps/asio b/deps/asio index d038fb3..4915cfd 160000 --- a/deps/asio +++ b/deps/asio @@ -1 +1 @@ -Subproject commit d038fb3c2fb56fb91ff1d17b0715cff7887aa09e +Subproject commit 4915cfd8a1653c157a1480162ae5601318553eb8 diff --git a/deps/commandline b/deps/commandline index d6b1c32..470cf2d 160000 --- a/deps/commandline +++ b/deps/commandline @@ -1 +1 @@ -Subproject commit d6b1c32c8af6ad5306f9f001305b3be9928ae4bb +Subproject commit 470cf2df4a6c94847b3a22868139095ae51902e6 diff --git a/deps/cpp-httplib b/deps/cpp-httplib index b324921..d92c314 160000 --- a/deps/cpp-httplib +++ b/deps/cpp-httplib @@ -1 +1 @@ -Subproject commit b324921c1aeff2976544128e4bb2a0979a4aa595 +Subproject commit d92c31446687cfa336a6332b1015b4fe289fbdec diff --git a/deps/doctest b/deps/doctest new file mode 160000 index 0000000..b7c21ec --- /dev/null +++ b/deps/doctest @@ -0,0 +1 @@ +Subproject commit b7c21ec5ceeadb4951b00396fc1e4642dd347e5f diff --git a/deps/fmt b/deps/fmt new file mode 160000 index 0000000..c4ee726 --- /dev/null +++ b/deps/fmt @@ -0,0 +1 @@ +Subproject commit c4ee726532178e556d923372f29163bd206d7732 diff --git a/deps/json b/deps/json index eb21824..69d7448 160000 --- a/deps/json +++ b/deps/json @@ -1 +1 @@ -Subproject commit eb2182414749825be086c825edb5229e5c28503d +Subproject commit 69d744867f8847c91a126fa25e9a6a3d67b3be41 diff --git a/deps/libzip b/deps/libzip index 76df02f..5532f9b 160000 --- a/deps/libzip +++ b/deps/libzip @@ -1 +1 @@ -Subproject commit 76df02f86b9746e139fd9fc934a70e3a21bbc557 +Subproject commit 5532f9baa0c44cc5435ad135686a4ea009075b9a diff --git a/deps/sentry-native b/deps/sentry-native index 90966cc..28be51f 160000 --- a/deps/sentry-native +++ b/deps/sentry-native @@ -1 +1 @@ -Subproject commit 90966cc1022b8155681b6899539b35466baccf2c +Subproject commit 28be51f5e3acb01327b1164206d3145464577670 diff --git a/deps/sol2 b/deps/sol2 index c068aef..eba8662 160000 --- a/deps/sol2 +++ b/deps/sol2 @@ -1 +1 @@ -Subproject commit c068aefbeddb3dd1f1fd38d42843ecb49a3b4cdb +Subproject commit eba86625b707e3c8c99bbfc4624e51f42dc9e561 diff --git a/deps/toml11 b/deps/toml11 index 1400dd2..c7627ff 160000 --- a/deps/toml11 +++ b/deps/toml11 @@ -1 +1 @@ -Subproject commit 1400dd223fb4297337266fcb5d04b700338aea71 +Subproject commit c7627ff6a1eb6f34fbd98369990a9442e2836c25 diff --git a/include/BoostAliases.h b/include/BoostAliases.h new file mode 100644 index 0000000..0348350 --- /dev/null +++ b/include/BoostAliases.h @@ -0,0 +1,6 @@ +#pragma once + +#include +#include + +using namespace boost::asio; diff --git a/include/Client.h b/include/Client.h index d513659..b8858d1 100644 --- a/include/Client.h +++ b/include/Client.h @@ -7,6 +7,7 @@ #include #include +#include "BoostAliases.h" #include "Common.h" #include "Compat.h" #include "VehicleData.h" @@ -19,9 +20,8 @@ class TServer; #endif // WINDOWS struct TConnection final { - SOCKET Socket; - struct sockaddr SockAddr; - socklen_t SockAddrLen; + ip::tcp::socket Socket; + ip::tcp::endpoint SockAddr; }; class TClient final { @@ -33,27 +33,34 @@ public: std::unique_lock Lock; }; - explicit TClient(TServer& Server); + TClient(TServer& Server, ip::tcp::socket&& Socket); TClient(const TClient&) = delete; + ~TClient(); TClient& operator=(const TClient&) = delete; void AddNewCar(int Ident, const std::string& Data); void SetCarData(int Ident, const std::string& Data); + void SetCarPosition(int Ident, const std::string& Data); TVehicleDataLockPair GetAllCars(); void SetName(const std::string& Name) { mName = Name; } void SetRoles(const std::string& Role) { mRole = Role; } void SetIdentifier(const std::string& key, const std::string& value) { mIdentifiers[key] = value; } std::string GetCarData(int Ident); - void SetUDPAddr(sockaddr_in Addr) { mUDPAddress = Addr; } - void SetDownSock(SOCKET CSock) { mSocket[1] = CSock; } - void SetTCPSock(SOCKET CSock) { mSocket[0] = CSock; } - void SetStatus(int Status) { mStatus = Status; } + std::string GetCarPositionRaw(int Ident); + void SetUDPAddr(const ip::udp::endpoint& Addr) { mUDPAddress = Addr; } + void SetDownSock(ip::tcp::socket&& CSock) { mDownSocket = std::move(CSock); } + void SetTCPSock(ip::tcp::socket&& CSock) { mSocket = std::move(CSock); } + void Disconnect(std::string_view Reason); + bool IsDisconnected() const { return !mSocket.is_open(); } // locks void DeleteCar(int Ident); [[nodiscard]] const std::unordered_map& GetIdentifiers() const { return mIdentifiers; } - [[nodiscard]] sockaddr_in GetUDPAddr() const { return mUDPAddress; } - [[nodiscard]] SOCKET GetDownSock() const { return mSocket[1]; } - [[nodiscard]] SOCKET GetTCPSock() const { return mSocket[0]; } + [[nodiscard]] const ip::udp::endpoint& GetUDPAddr() const { return mUDPAddress; } + [[nodiscard]] ip::udp::endpoint& GetUDPAddr() { return mUDPAddress; } + [[nodiscard]] ip::tcp::socket& GetDownSock() { return mDownSocket; } + [[nodiscard]] const ip::tcp::socket& GetDownSock() const { return mDownSocket; } + [[nodiscard]] ip::tcp::socket& GetTCPSock() { return mSocket; } + [[nodiscard]] const ip::tcp::socket& GetTCPSock() const { return mSocket; } [[nodiscard]] std::string GetRoles() const { return mRole; } [[nodiscard]] std::string GetName() const { return mName; } void SetUnicycleID(int ID) { mUnicycleID = ID; } @@ -61,7 +68,6 @@ public: [[nodiscard]] int GetOpenCarID() const; [[nodiscard]] int GetCarCount() const; void ClearCars(); - [[nodiscard]] int GetStatus() const { return mStatus; } [[nodiscard]] int GetID() const { return mID; } [[nodiscard]] int GetUnicycleID() const { return mUnicycleID; } [[nodiscard]] bool IsConnected() const { return mIsConnected; } @@ -71,9 +77,9 @@ public: void SetIsGuest(bool NewIsGuest) { mIsGuest = NewIsGuest; } void SetIsSynced(bool NewIsSynced) { mIsSynced = NewIsSynced; } void SetIsSyncing(bool NewIsSyncing) { mIsSyncing = NewIsSyncing; } - void EnqueuePacket(const std::string& Packet); - [[nodiscard]] std::queue& MissedPacketQueue() { return mPacketsSync; } - [[nodiscard]] const std::queue& MissedPacketQueue() const { return mPacketsSync; } + void EnqueuePacket(const std::vector& Packet); + [[nodiscard]] std::queue>& MissedPacketQueue() { return mPacketsSync; } + [[nodiscard]] const std::queue>& MissedPacketQueue() const { return mPacketsSync; } [[nodiscard]] size_t MissedPacketQueueSize() const { return mPacketsSync.size(); } [[nodiscard]] std::mutex& MissedPacketQueueMutex() const { return mMissedPacketsMutex; } void SetIsConnected(bool NewIsConnected) { mIsConnected = NewIsConnected; } @@ -89,18 +95,20 @@ private: bool mIsSynced = false; bool mIsSyncing = false; mutable std::mutex mMissedPacketsMutex; - std::queue mPacketsSync; + std::queue> mPacketsSync; std::unordered_map mIdentifiers; bool mIsGuest = false; mutable std::mutex mVehicleDataMutex; + mutable std::mutex mVehiclePositionMutex; TSetOfVehicleData mVehicleData; + SparseArray mVehiclePosition; std::string mName = "Unknown Client"; - SOCKET mSocket[2] { SOCKET(0), SOCKET(0) }; - sockaddr_in mUDPAddress {}; // is this initialization OK? yes it is + ip::tcp::socket mSocket; + ip::tcp::socket mDownSocket; + ip::udp::endpoint mUDPAddress {}; int mUnicycleID = -1; std::string mRole; std::string mDID; - int mStatus = 0; int mID = -1; std::chrono::time_point mLastPingTime; }; diff --git a/include/Common.h b/include/Common.h index 39714d0..06b24c8 100644 --- a/include/Common.h +++ b/include/Common.h @@ -8,13 +8,18 @@ extern TSentry Sentry; #include #include #include +#include #include #include #include +#include #include +#include #include -#include "Compat.h" +#include +#include +namespace fs = std::filesystem; #include "TConsole.h" @@ -27,6 +32,9 @@ struct Version { std::string AsString(); }; +template +using SparseArray = std::unordered_map; + // static class handling application start, shutdown, etc. // yes, static classes, singletons, globals are all pretty // bad idioms. In this case we need a central way to access @@ -50,6 +58,7 @@ public: bool DebugModeEnabled { false }; int Port { 30814 }; std::string CustomIP {}; + bool LogChat { true }; bool SendErrors { true }; bool SendErrorsMessageEnabled { true }; int HTTPServerPort { 8080 }; @@ -71,7 +80,7 @@ public: static TConsole& Console() { return *mConsole; } static std::string ServerVersionString(); static const Version& ServerVersion() { return mVersion; } - static std::string ClientVersionString() { return "2.0"; } + static uint8_t ClientMajorVersion() { return 2; } static std::string PPS() { return mPPS; } static void SetPPS(const std::string& NewPPS) { mPPS = NewPPS; } @@ -90,6 +99,8 @@ public: static void CheckForUpdates(); static std::array VersionStrToInts(const std::string& str); static bool IsOutdated(const Version& Current, const Version& Newest); + static bool IsShuttingDown(); + static void SleepSafeSeconds(size_t Seconds); static void InitializeConsole() { if (!mConsole) { @@ -115,99 +126,144 @@ public: static void SetSubsystemStatus(const std::string& Subsystem, Status status); private: + static void SetShutdown(bool Val); + static inline SystemStatusMap mSystemStatusMap {}; static inline std::mutex mSystemStatusMapMutex {}; static inline std::string mPPS; static inline std::unique_ptr mConsole; + static inline std::shared_mutex mShutdownMtx {}; + static inline bool mShutdown { false }; static inline std::mutex mShutdownHandlersMutex {}; static inline std::deque mShutdownHandlers {}; - static inline Version mVersion { 3, 0, 2 }; + static inline Version mVersion { 3, 1, 0 }; }; std::string ThreadName(bool DebugModeOverride = false); void RegisterThread(const std::string& str); #define RegisterThreadAuto() RegisterThread(__func__) -#define KB 1024 -#define MB (KB * 1024) +#define KB 1024llu +#define MB (KB * 1024llu) +#define GB (MB * 1024llu) #define SSU_UNRAW SECRET_SENTRY_URL #define _file_basename std::filesystem::path(__FILE__).filename().string() #define _line std::to_string(__LINE__) #define _in_lambda (std::string(__func__) == "operator()") -// we would like the full function signature 'void a::foo() const' -// on windows this is __FUNCSIG__, on GCC it's __PRETTY_FUNCTION__, -// feel free to add more -#if defined(WIN32) -#define _function_name std::string(__FUNCSIG__) -#elif defined(__unix) || defined(__unix__) -#define _function_name std::string(__PRETTY_FUNCTION__) -#else -#define _function_name std::string(__func__) -#endif - -#if defined(DEBUG) - -// if this is defined, we will show the full function signature infront of -// each info/debug/warn... call instead of the 'filename:line' format. -#if defined(BMP_FULL_FUNCTION_NAMES) -#define _this_location (ThreadName() + _function_name + " ") -#else -#define _this_location (ThreadName() + _file_basename + ":" + _line + " ") -#endif -#define SU_RAW SSU_UNRAW - -#else // !defined(DEBUG) - -#define SU_RAW RAWIFY(SSU_UNRAW) -#define _this_location (ThreadName()) - -#endif // defined(DEBUG) - -#define beammp_warn(x) Application::Console().Write(_this_location + std::string("[WARN] ") + (x)) -#define beammp_info(x) Application::Console().Write(_this_location + std::string("[INFO] ") + (x)) -#define beammp_error(x) \ - do { \ - Application::Console().Write(_this_location + std::string("[ERROR] ") + (x)); \ - Sentry.AddErrorBreadcrumb((x), _file_basename, _line); \ - } while (false) -#define beammp_lua_error(x) \ - do { \ - Application::Console().Write(_this_location + std::string("[LUA ERROR] ") + (x)); \ - } while (false) -#define beammp_lua_warn(x) \ - do { \ - Application::Console().Write(_this_location + std::string("[LUA WARN] ") + (x)); \ - } while (false) -#define luaprint(x) Application::Console().Write(_this_location + std::string("[LUA] ") + (x)) -#define beammp_debug(x) \ - do { \ - if (Application::Settings.DebugModeEnabled) { \ - Application::Console().Write(_this_location + std::string("[DEBUG] ") + (x)); \ - } \ - } while (false) -#define beammp_event(x) \ - do { \ - if (Application::Settings.DebugModeEnabled) { \ - Application::Console().Write(_this_location + std::string("[EVENT] ") + (x)); \ - } \ - } while (false) // for those times when you just need to ignore something :^) // explicity disables a [[nodiscard]] warning #define beammp_ignore(x) (void)x -// trace() is a debug-build debug() + +// clang-format off +#ifdef DOCTEST_CONFIG_DISABLE + + // we would like the full function signature 'void a::foo() const' + // on windows this is __FUNCSIG__, on GCC it's __PRETTY_FUNCTION__, + // feel free to add more + #if defined(WIN32) + #define _function_name std::string(__FUNCSIG__) + #elif defined(__unix) || defined(__unix__) + #define _function_name std::string(__PRETTY_FUNCTION__) + #else + #define _function_name std::string(__func__) + #endif + + #ifndef NDEBUG + #define DEBUG + #endif + + #if defined(DEBUG) + + // if this is defined, we will show the full function signature infront of + // each info/debug/warn... call instead of the 'filename:line' format. + #if defined(BMP_FULL_FUNCTION_NAMES) + #define _this_location (ThreadName() + _function_name + " ") + #else + #define _this_location (ThreadName() + _file_basename + ":" + _line + " ") + #endif + + #endif // defined(DEBUG) + + #define beammp_warn(x) Application::Console().Write(_this_location + std::string("[WARN] ") + (x)) + #define beammp_info(x) Application::Console().Write(_this_location + std::string("[INFO] ") + (x)) + #define beammp_error(x) \ + do { \ + Application::Console().Write(_this_location + std::string("[ERROR] ") + (x)); \ + Sentry.AddErrorBreadcrumb((x), _file_basename, _line); \ + } while (false) + #define beammp_lua_error(x) \ + do { \ + Application::Console().Write(_this_location + std::string("[LUA ERROR] ") + (x)); \ + } while (false) + #define beammp_lua_warn(x) \ + do { \ + Application::Console().Write(_this_location + std::string("[LUA WARN] ") + (x)); \ + } while (false) + #define luaprint(x) Application::Console().Write(_this_location + std::string("[LUA] ") + (x)) + #define beammp_debug(x) \ + do { \ + if (Application::Settings.DebugModeEnabled) { \ + Application::Console().Write(_this_location + std::string("[DEBUG] ") + (x)); \ + } \ + } while (false) + #define beammp_event(x) \ + do { \ + if (Application::Settings.DebugModeEnabled) { \ + Application::Console().Write(_this_location + std::string("[EVENT] ") + (x)); \ + } \ + } while (false) + // trace() is a debug-build debug() + #if defined(DEBUG) + #define beammp_trace(x) \ + do { \ + if (Application::Settings.DebugModeEnabled) { \ + Application::Console().Write(_this_location + std::string("[TRACE] ") + (x)); \ + } \ + } while (false) + #else + #define beammp_trace(x) + #endif // defined(DEBUG) + + #define beammp_errorf(...) beammp_error(fmt::format(__VA_ARGS__)) + #define beammp_infof(...) beammp_info(fmt::format(__VA_ARGS__)) + #define beammp_debugf(...) beammp_debug(fmt::format(__VA_ARGS__)) + #define beammp_warnf(...) beammp_warn(fmt::format(__VA_ARGS__)) + #define beammp_tracef(...) beammp_trace(fmt::format(__VA_ARGS__)) + #define beammp_lua_errorf(...) beammp_lua_error(fmt::format(__VA_ARGS__)) + #define beammp_lua_warnf(...) beammp_lua_warn(fmt::format(__VA_ARGS__)) + +#else // DOCTEST_CONFIG_DISABLE + + #define beammp_error(x) /* x */ + #define beammp_lua_error(x) /* x */ + #define beammp_warn(x) /* x */ + #define beammp_lua_warn(x) /* x */ + #define beammp_info(x) /* x */ + #define beammp_event(x) /* x */ + #define beammp_debug(x) /* x */ + #define beammp_trace(x) /* x */ + #define luaprint(x) /* x */ + #define beammp_errorf(...) beammp_error(fmt::format(__VA_ARGS__)) + #define beammp_infof(...) beammp_info(fmt::format(__VA_ARGS__)) + #define beammp_warnf(...) beammp_warn(fmt::format(__VA_ARGS__)) + #define beammp_debugf(...) beammp_debug(fmt::format(__VA_ARGS__)) + #define beammp_tracef(...) beammp_trace(fmt::format(__VA_ARGS__)) + #define beammp_lua_errorf(...) beammp_lua_error(fmt::format(__VA_ARGS__)) + #define beammp_lua_warnf(...) beammp_lua_warn(fmt::format(__VA_ARGS__)) + +#endif // DOCTEST_CONFIG_DISABLE + #if defined(DEBUG) -#define beammp_trace(x) \ - do { \ - if (Application::Settings.DebugModeEnabled) { \ - Application::Console().Write(_this_location + std::string("[TRACE] ") + (x)); \ - } \ - } while (false) + #define SU_RAW SSU_UNRAW #else -#define beammp_trace(x) -#endif // defined(DEBUG) + #define SU_RAW RAWIFY(SSU_UNRAW) + #define _this_location (ThreadName()) +#endif + +// clang-format on void LogChatMessage(const std::string& name, int id, const std::string& msg); @@ -219,11 +275,11 @@ inline T Comp(const T& Data) { // obsolete C.fill(0); z_stream defstream; - defstream.zalloc = Z_NULL; - defstream.zfree = Z_NULL; - defstream.opaque = Z_NULL; - defstream.avail_in = (uInt)Data.size(); - defstream.next_in = (Bytef*)&Data[0]; + defstream.zalloc = nullptr; + defstream.zfree = nullptr; + defstream.opaque = nullptr; + defstream.avail_in = uInt(Data.size()); + defstream.next_in = const_cast(reinterpret_cast(&Data[0])); defstream.avail_out = Biggest; defstream.next_out = reinterpret_cast(C.data()); deflateInit(&defstream, Z_BEST_COMPRESSION); @@ -244,13 +300,13 @@ inline T DeComp(const T& Compressed) { // not needed C.fill(0); z_stream infstream; - infstream.zalloc = Z_NULL; - infstream.zfree = Z_NULL; - infstream.opaque = Z_NULL; + infstream.zalloc = nullptr; + infstream.zfree = nullptr; + infstream.opaque = nullptr; infstream.avail_in = Biggest; - infstream.next_in = (Bytef*)(&Compressed[0]); + infstream.next_in = const_cast(reinterpret_cast(&Compressed[0])); infstream.avail_out = Biggest; - infstream.next_out = (Bytef*)(C.data()); + infstream.next_out = const_cast(reinterpret_cast(C.data())); inflateInit(&infstream); inflate(&infstream, Z_SYNC_FLUSH); inflate(&infstream, Z_FINISH); @@ -265,5 +321,3 @@ inline T DeComp(const T& Compressed) { std::string GetPlatformAgnosticErrorString(); #define S_DSN SU_RAW - -void LogChatMessage(const std::string& name, int id, const std::string& msg); diff --git a/include/Compat.h b/include/Compat.h index e1e906d..fd4de59 100644 --- a/include/Compat.h +++ b/include/Compat.h @@ -5,49 +5,23 @@ // ======================= UNIX ======================== #ifdef BEAMMP_LINUX -#include -#include +#include #include #include -#include -using SOCKET = int; -using DWORD = unsigned long; -using PDWORD = unsigned long*; -using LPDWORD = unsigned long*; char _getch(); -inline void CloseSocketProper(int TheSocket) { - shutdown(TheSocket, SHUT_RDWR); - close(TheSocket); -} #endif // unix // ======================= APPLE ======================== #ifdef BEAMMP_APPLE -#include -#include +#include #include #include -#include -using SOCKET = int; -using DWORD = unsigned long; -using PDWORD = unsigned long*; -using LPDWORD = unsigned long*; char _getch(); -inline void CloseSocketProper(int TheSocket) { - shutdown(TheSocket, SHUT_RDWR); - close(TheSocket); -} #endif // unix // ======================= WINDOWS ======================= #ifdef BEAMMP_WINDOWS #include -#include -inline void CloseSocketProper(SOCKET TheSocket) { - shutdown(TheSocket, 2); // 2 == SD_BOTH - closesocket(TheSocket); - -} #endif // WIN32 diff --git a/include/CustomAssert.h b/include/CustomAssert.h index 73179f0..df3c4c6 100644 --- a/include/CustomAssert.h +++ b/include/CustomAssert.h @@ -58,16 +58,17 @@ inline void _assert([[maybe_unused]] const char* file, [[maybe_unused]] const ch #define beammp_assert(cond) _assert(__FILE__, __func__, __LINE__, #cond, (cond)) #define beammp_assert_not_reachable() _assert(__FILE__, __func__, __LINE__, "reached unreachable code", false) #else -// In release build, these macros turn into NOPs. The compiler will optimize these out. -#define beammp_assert(cond) \ - do { \ - bool result = (cond); \ - if (!result) { \ - Sentry.LogAssert(#cond, _file_basename, _line, __func__); \ - } \ +#define beammp_assert(cond) \ + do { \ + bool result = (cond); \ + if (!result) { \ + beammp_errorf("Assertion failed in '{}:{}': {}.", __func__, _line, #cond); \ + Sentry.LogAssert(#cond, _file_basename, _line, __func__); \ + } \ } while (false) -#define beammp_assert_not_reachable() \ - do { \ - Sentry.LogAssert("code is unreachable", _file_basename, _line, __func__); \ +#define beammp_assert_not_reachable() \ + do { \ + beammp_errorf("Assertion failed in '{}:{}': Unreachable code reached. This may result in a crash or undefined state of the program.", __func__, _line); \ + Sentry.LogAssert("code is unreachable", _file_basename, _line, __func__); \ } while (false) #endif // DEBUG diff --git a/include/Http.h b/include/Http.h index 3ff2cd5..0898f2e 100644 --- a/include/Http.h +++ b/include/Http.h @@ -3,14 +3,14 @@ #include #include #include -#include -#include #include #include #if defined(BEAMMP_LINUX) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wmaybe-uninitialized" +#pragma GCC diagnostic ignored "-Wcast-qual" +#pragma GCC diagnostic ignored "-Wold-style-cast" #endif #include #if defined(BEAMMP_LINUX) @@ -19,10 +19,6 @@ namespace fs = std::filesystem; -namespace Crypto { -constexpr size_t RSA_DEFAULT_KEYLENGTH { 2048 }; -} - namespace Http { std::string GET(const std::string& host, int port, const std::string& target, unsigned int* status = nullptr); std::string POST(const std::string& host, int port, const std::string& target, const std::string& body, const std::string& ContentType, unsigned int* status = nullptr, const httplib::Headers& headers = {}); @@ -32,13 +28,9 @@ namespace Status { const std::string ErrorString = "-1"; namespace Server { - void SetupEnvironment(); - // todo: Add non TLS Server Instance, this one is TLS only class THttpServerInstance { public: THttpServerInstance(); - static fs::path KeyFilePath; - static fs::path CertFilePath; protected: void operator()(); @@ -46,15 +38,5 @@ namespace Server { private: std::thread mThread; }; - // todo: all of these functions are likely unsafe, - // todo: replace with something that's managed by a domain specific crypto library - class Tx509KeypairGenerator { - public: - static long GenerateRandomId(); - static bool EnsureTLSConfigExists(); - static X509* GenerateCertificate(EVP_PKEY& pkey); - static EVP_PKEY* GenerateKey(); - static void GenerateAndWriteToDisk(const fs::path& KeyFilePath, const fs::path& CertFilePath); - }; } } diff --git a/include/LuaAPI.h b/include/LuaAPI.h index 0cd0e1f..6d2af31 100644 --- a/include/LuaAPI.h +++ b/include/LuaAPI.h @@ -12,17 +12,26 @@ namespace MP { std::string GetOSName(); std::tuple GetServerVersion(); - bool TriggerClientEvent(int PlayerID, const std::string& EventName, const std::string& Data); + std::pair TriggerClientEvent(int PlayerID, const std::string& EventName, const sol::object& Data); + std::pair TriggerClientEventJson(int PlayerID, const std::string& EventName, const sol::table& Data); inline size_t GetPlayerCount() { return Engine->Server().ClientCount(); } - void DropPlayer(int ID, std::optional MaybeReason); - void SendChatMessage(int ID, const std::string& Message); - void RemoveVehicle(int PlayerID, int VehicleID); + std::pair DropPlayer(int ID, std::optional MaybeReason); + std::pair SendChatMessage(int ID, const std::string& Message); + std::pair RemoveVehicle(int PlayerID, int VehicleID); void Set(int ConfigID, sol::object NewValue); bool IsPlayerGuest(int ID); bool IsPlayerConnected(int ID); void Sleep(size_t Ms); void PrintRaw(sol::variadic_args); + std::string JsonEncode(const sol::table& object); + std::string JsonDiff(const std::string& a, const std::string& b); + std::string JsonDiffApply(const std::string& data, const std::string& patch); + std::string JsonPrettify(const std::string& json); + std::string JsonMinify(const std::string& json); + std::string JsonFlatten(const std::string& json); + std::string JsonUnflatten(const std::string& json); } + namespace FS { std::pair CreateDirectory(const std::string& Path); std::pair Remove(const std::string& Path); diff --git a/include/TConfig.h b/include/TConfig.h index 3fcc597..d9d6045 100644 --- a/include/TConfig.h +++ b/include/TConfig.h @@ -3,6 +3,7 @@ #include "Common.h" #include +#include #define TOML11_PRESERVE_COMMENTS_BY_DEFAULT #include // header-only version of TOML++ @@ -18,7 +19,7 @@ public: void FlushToFile(); private: - void CreateConfigFile(std::string_view name); + void CreateConfigFile(); void ParseFromFile(std::string_view name); void PrintDebug(); void TryReadValue(toml::value& Table, const std::string& Category, const std::string_view& Key, std::string& OutValue); diff --git a/include/TConsole.h b/include/TConsole.h index 839f862..22a3b01 100644 --- a/include/TConsole.h +++ b/include/TConsole.h @@ -4,6 +4,11 @@ #include "commandline.h" #include #include +#include +#include +#include +#include +#include class TLuaEngine; @@ -22,13 +27,33 @@ private: void RunAsCommand(const std::string& cmd, bool IgnoreNotACommand = false); void ChangeToLuaConsole(const std::string& LuaStateId); void ChangeToRegularConsole(); + void HandleLuaInternalCommand(const std::string& cmd); - void Command_Lua(const std::string& cmd); - void Command_Help(const std::string& cmd); - void Command_Kick(const std::string& cmd); - void Command_Say(const std::string& cmd); - void Command_List(const std::string& cmd); - void Command_Status(const std::string& cmd); + void Command_Lua(const std::string& cmd, const std::vector& args); + void Command_Help(const std::string& cmd, const std::vector& args); + void Command_Kick(const std::string& cmd, const std::vector& args); + void Command_List(const std::string& cmd, const std::vector& args); + void Command_Status(const std::string& cmd, const std::vector& args); + void Command_Settings(const std::string& cmd, const std::vector& args); + void Command_Clear(const std::string&, const std::vector& args); + + void Command_Say(const std::string& FullCommand); + bool EnsureArgsCount(const std::vector& args, size_t n); + bool EnsureArgsCount(const std::vector& args, size_t min, size_t max); + + static std::tuple> ParseCommand(const std::string& cmd); + static std::string ConcatArgs(const std::vector& args, char space = ' '); + + std::unordered_map&)>> mCommandMap = { + { "lua", [this](const auto& a, const auto& b) { Command_Lua(a, b); } }, + { "help", [this](const auto& a, const auto& b) { Command_Help(a, b); } }, + { "kick", [this](const auto& a, const auto& b) { Command_Kick(a, b); } }, + { "list", [this](const auto& a, const auto& b) { Command_List(a, b); } }, + { "status", [this](const auto& a, const auto& b) { Command_Status(a, b); } }, + { "settings", [this](const auto& a, const auto& b) { Command_Settings(a, b); } }, + { "clear", [this](const auto& a, const auto& b) { Command_Clear(a, b); } }, + { "say", [this](const auto&, const auto&) { Command_Say(""); } }, // shouldn't actually be called + }; Commandline mCommandline; std::vector mCachedLuaHistory; diff --git a/include/THeartbeatThread.h b/include/THeartbeatThread.h index 1f0ab14..1063be6 100644 --- a/include/THeartbeatThread.h +++ b/include/THeartbeatThread.h @@ -15,7 +15,6 @@ private: std::string GenerateCall(); std::string GetPlayers(); - bool mShutdown = false; TResourceManager& mResourceManager; TServer& mServer; }; diff --git a/include/TLuaEngine.h b/include/TLuaEngine.h index 5ca2222..de19f13 100644 --- a/include/TLuaEngine.h +++ b/include/TLuaEngine.h @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -24,17 +25,18 @@ namespace fs = std::filesystem; /** * std::variant means, that TLuaArgTypes may be one of the Types listed as template args */ -using TLuaArgTypes = std::variant; +using TLuaArgTypes = std::variant>; static constexpr size_t TLuaArgTypes_String = 0; static constexpr size_t TLuaArgTypes_Int = 1; static constexpr size_t TLuaArgTypes_VariadicArgs = 2; static constexpr size_t TLuaArgTypes_Bool = 3; +static constexpr size_t TLuaArgTypes_StringStringMap = 4; class TLuaPlugin; struct TLuaResult { - std::atomic_bool Ready; - std::atomic_bool Error; + bool Ready; + bool Error; std::string ErrorMessage; sol::object Result { sol::lua_nil }; TLuaStateId StateId; @@ -47,6 +49,7 @@ struct TLuaPluginConfig { static inline const std::string FileName = "PluginConfig.toml"; TLuaStateId StateId; // TODO: Add execute list + // TODO: Build a better toml serializer, or some way to do this in an easier way }; struct TLuaChunk { @@ -58,20 +61,7 @@ struct TLuaChunk { std::string PluginPath; }; -class TPluginMonitor : IThreaded { -public: - TPluginMonitor(const fs::path& Path, TLuaEngine& Engine, std::atomic_bool& Shutdown); - - void operator()(); - -private: - TLuaEngine& mEngine; - fs::path mPath; - std::atomic_bool& mShutdown; - std::unordered_map mFileTimes; -}; - -class TLuaEngine : IThreaded { +class TLuaEngine : public std::enable_shared_from_this, IThreaded { public: enum CallStrategy : int { BestEffort, @@ -102,10 +92,18 @@ public: std::unique_lock Lock(mResultsToCheckMutex); return mResultsToCheck.size(); } + size_t GetLuaStateCount() { std::unique_lock Lock(mLuaStatesMutex); return mLuaStates.size(); } + std::vector GetLuaStateNames() { + std::vector names{}; + for(auto const& [stateId, _ ] : mLuaStates) { + names.push_back(stateId); + } + return names; + } size_t GetTimedEventsCount() { std::unique_lock Lock(mTimedEventsMutex); return mTimedEvents.size(); @@ -129,7 +127,6 @@ public: [[nodiscard]] std::shared_ptr EnqueueFunctionCall(TLuaStateId StateID, const std::string& FunctionName, const std::vector& Args); void EnsureStateExists(TLuaStateId StateId, const std::string& Name, bool DontCallOnInit = false); void RegisterEvent(const std::string& EventName, TLuaStateId StateId, const std::string& FunctionName); - template /** * * @tparam ArgsT Template Arguments for the event (Metadata) todo: figure out what this means @@ -138,6 +135,7 @@ public: * @param Args * @return */ + template [[nodiscard]] std::vector> TriggerEvent(const std::string& EventName, TLuaStateId IgnoreId, ArgsT&&... Args) { std::unique_lock Lock(mLuaEventsMutex); beammp_event(EventName); @@ -157,6 +155,21 @@ public: } return Results; // } + template + [[nodiscard]] std::vector> TriggerLocalEvent(const TLuaStateId& StateId, const std::string& EventName, ArgsT&&... Args) { + std::unique_lock Lock(mLuaEventsMutex); + beammp_event(EventName + " in '" + StateId + "'"); + if (mLuaEvents.find(EventName) == mLuaEvents.end()) { // if no event handler is defined for 'EventName', return immediately + return {}; + } + std::vector> Results; + std::vector Arguments { TLuaArgTypes { std::forward(Args) }... }; + const auto Handlers = GetEventHandlersForState(EventName, StateId); + for (const auto& Handler : Handlers) { + Results.push_back(EnqueueFunctionCall(StateId, Handler, Arguments)); + } + return Results; + } std::set GetEventHandlersForState(const std::string& EventName, TLuaStateId StateId); void CreateEventTimer(const std::string& EventName, TLuaStateId StateId, size_t IntervalMS, CallStrategy Strategy); void CancelEventTimers(const std::string& EventName, TLuaStateId StateId); @@ -166,6 +179,15 @@ public: static constexpr const char* BeamMPFnNotFoundError = "BEAMMP_FN_NOT_FOUND"; + std::vector GetStateGlobalKeysForState(TLuaStateId StateId); + std::vector GetStateTableKeysForState(TLuaStateId StateId, std::vector keys); + + // Debugging functions (slow) + std::unordered_map /* handlers */> Debug_GetEventsForState(TLuaStateId StateId); + std::queue>> Debug_GetStateExecuteQueueForState(TLuaStateId StateId); + std::vector Debug_GetStateFunctionQueueForState(TLuaStateId StateId); + std::vector Debug_GetResultsToCheckForState(TLuaStateId StateId); + private: void CollectAndInitPlugins(); void InitializePlugin(const fs::path& Folder, const TLuaPluginConfig& Config); @@ -174,7 +196,7 @@ private: class StateThreadData : IThreaded { public: - StateThreadData(const std::string& Name, std::atomic_bool& Shutdown, TLuaStateId StateId, TLuaEngine& Engine); + StateThreadData(const std::string& Name, TLuaStateId StateId, TLuaEngine& Engine); StateThreadData(const StateThreadData&) = delete; ~StateThreadData() noexcept { beammp_debug("\"" + mStateId + "\" destroyed"); } [[nodiscard]] std::shared_ptr EnqueueScript(const TLuaChunk& Script); @@ -185,6 +207,13 @@ private: void operator()() override; sol::state_view State() { return sol::state_view(mState); } + std::vector GetStateGlobalKeys(); + std::vector GetStateTableKeys(const std::vector& keys); + + // Debug functions, slow + std::queue>> Debug_GetStateExecuteQueue(); + std::vector Debug_GetStateFunctionQueue(); + private: sol::table Lua_TriggerGlobalEvent(const std::string& EventName, sol::variadic_args EventArgs); sol::table Lua_TriggerLocalEvent(const std::string& EventName, sol::variadic_args EventArgs); @@ -192,11 +221,14 @@ private: sol::table Lua_GetPlayers(); std::string Lua_GetPlayerName(int ID); sol::table Lua_GetPlayerVehicles(int ID); + std::pair Lua_GetPositionRaw(int PID, int VID); sol::table Lua_HttpCreateConnection(const std::string& host, uint16_t port); + sol::table Lua_JsonDecode(const std::string& str); int Lua_GetPlayerIDByName(const std::string& Name); + sol::table Lua_FS_ListFiles(const std::string& Path); + sol::table Lua_FS_ListDirectories(const std::string& Path); std::string mName; - std::atomic_bool& mShutdown; TLuaStateId mStateId; lua_State* mState; std::thread mThread; @@ -209,6 +241,8 @@ private: sol::state_view mStateView { mState }; std::queue mPaths; std::recursive_mutex mPathsMutex; + std::mt19937 mMersenneTwister; + std::uniform_real_distribution mUniformRealDistribution01; }; struct TimedEvent { @@ -223,9 +257,7 @@ private: TNetwork* mNetwork; TServer* mServer; - TPluginMonitor mPluginMonitor; - std::atomic_bool mShutdown { false }; - fs::path mResourceServerPath; + const fs::path mResourceServerPath; std::vector> mLuaPlugins; std::unordered_map> mLuaStates; std::recursive_mutex mLuaStatesMutex; diff --git a/include/TNetwork.h b/include/TNetwork.h index 528aef4..3b4980e 100644 --- a/include/TNetwork.h +++ b/include/TNetwork.h @@ -1,8 +1,11 @@ #pragma once +#include "BoostAliases.h" #include "Compat.h" #include "TResourceManager.h" #include "TServer.h" +#include +#include struct TConnection; @@ -10,19 +13,18 @@ class TNetwork { public: TNetwork(TServer& Server, TPPSMonitor& PPSMonitor, TResourceManager& ResourceManager); - [[nodiscard]] bool TCPSend(TClient& c, const std::string& Data, bool IsSync = false); - [[nodiscard]] bool SendLarge(TClient& c, std::string Data, bool isSync = false); - [[nodiscard]] bool Respond(TClient& c, const std::string& MSG, bool Rel, bool isSync = false); - std::shared_ptr CreateClient(SOCKET TCPSock); - std::string TCPRcv(TClient& c); + [[nodiscard]] bool TCPSend(TClient& c, const std::vector& Data, bool IsSync = false); + [[nodiscard]] bool SendLarge(TClient& c, std::vector Data, bool isSync = false); + [[nodiscard]] bool Respond(TClient& c, const std::vector& MSG, bool Rel, bool isSync = false); + std::shared_ptr CreateClient(ip::tcp::socket&& TCPSock); + std::vector TCPRcv(TClient& c); void ClientKick(TClient& c, const std::string& R); [[nodiscard]] bool SyncClient(const std::weak_ptr& c); - void Identify(const TConnection& client); - void Authentication(const TConnection& ClientConnection); - [[nodiscard]] bool CheckBytes(TClient& c, int32_t BytesRcv); + void Identify(TConnection&& client); + std::shared_ptr Authentication(TConnection&& ClientConnection); void SyncResources(TClient& c); - [[nodiscard]] bool UDPSend(TClient& Client, std::string Data) const; - void SendToAll(TClient* c, const std::string& Data, bool Self, bool Rel); + [[nodiscard]] bool UDPSend(TClient& Client, std::vector Data); + void SendToAll(TClient* c, const std::vector& Data, bool Self, bool Rel); void UpdatePlayer(TClient& Client); private: @@ -31,21 +33,23 @@ private: TServer& mServer; TPPSMonitor& mPPSMonitor; - SOCKET mUDPSock {}; - bool mShutdown { false }; + ip::udp::socket mUDPSock; TResourceManager& mResourceManager; std::thread mUDPThread; std::thread mTCPThread; - std::string UDPRcvFromClient(sockaddr_in& client) const; - void HandleDownload(SOCKET TCPSock); + std::vector UDPRcvFromClient(ip::udp::endpoint& ClientEndpoint); + void HandleDownload(TConnection&& TCPSock); void OnConnect(const std::weak_ptr& c); void TCPClient(const std::weak_ptr& c); void Looper(const std::weak_ptr& c); int OpenID(); - void OnDisconnect(const std::weak_ptr& ClientPtr, bool kicked); - void Parse(TClient& c, const std::string& Packet); + void OnDisconnect(const std::weak_ptr& ClientPtr); + void Parse(TClient& c, const std::vector& Packet); void SendFile(TClient& c, const std::string& Name); - static bool TCPSendRaw(TClient& C, SOCKET socket, char* Data, int32_t Size); + static bool TCPSendRaw(TClient& C, ip::tcp::socket& socket, const uint8_t* Data, size_t Size); static void SplitLoad(TClient& c, size_t Sent, size_t Size, bool D, const std::string& Name); + static const uint8_t* SendSplit(TClient& c, ip::tcp::socket& Socket, const uint8_t* DataPtr, size_t Size); }; + +std::vector StringToVector(const std::string& Str); diff --git a/include/TPPSMonitor.h b/include/TPPSMonitor.h index 508dfc5..0718f10 100644 --- a/include/TPPSMonitor.h +++ b/include/TPPSMonitor.h @@ -22,6 +22,5 @@ private: TServer& mServer; std::optional> mNetwork { std::nullopt }; - bool mShutdown { false }; int mInternalPPS { 0 }; -}; \ No newline at end of file +}; diff --git a/include/TPluginMonitor.h b/include/TPluginMonitor.h new file mode 100644 index 0000000..2ed77bc --- /dev/null +++ b/include/TPluginMonitor.h @@ -0,0 +1,22 @@ +#pragma once + +#include "Common.h" +#include "IThreaded.h" + +#include +#include +#include + +class TLuaEngine; + +class TPluginMonitor : IThreaded, public std::enable_shared_from_this { +public: + TPluginMonitor(const fs::path& Path, std::shared_ptr Engine); + + void operator()(); + +private: + std::shared_ptr mEngine; + fs::path mPath; + std::unordered_map mFileTimes; +}; diff --git a/include/TServer.h b/include/TServer.h index 2e51e12..0967381 100644 --- a/include/TServer.h +++ b/include/TServer.h @@ -8,6 +8,8 @@ #include #include +#include "BoostAliases.h" + class TClient; class TNetwork; class TPPSMonitor; @@ -19,23 +21,35 @@ public: TServer(const std::vector& Arguments); void InsertClient(const std::shared_ptr& Ptr); - std::weak_ptr InsertNewClient(); void RemoveClient(const std::weak_ptr&); // in Fn, return true to continue, return false to break void ForEachClient(const std::function)>& Fn); size_t ClientCount() const; - static void GlobalParser(const std::weak_ptr& Client, std::string Packet, TPPSMonitor& PPSMonitor, TNetwork& Network); + static void GlobalParser(const std::weak_ptr& Client, std::vector&& Packet, TPPSMonitor& PPSMonitor, TNetwork& Network); static void HandleEvent(TClient& c, const std::string& Data); RWMutex& GetClientMutex() const { return mClientsMutex; } - const TScopedTimer UptimeTimer; + + // asio io context + io_context& IoCtx() { return mIoCtx; } + private: + io_context mIoCtx {}; TClientSet mClients; mutable RWMutex mClientsMutex; static void ParseVehicle(TClient& c, const std::string& Pckt, TNetwork& Network); static bool ShouldSpawn(TClient& c, const std::string& CarJson, int ID); static bool IsUnicycle(TClient& c, const std::string& CarJson); static void Apply(TClient& c, int VID, const std::string& pckt); + static void HandlePosition(TClient& c, const std::string& Packet); +}; + +struct BufferView { + uint8_t* Data { nullptr }; + size_t Size { 0 }; + const uint8_t* data() const { return Data; } + uint8_t* data() { return Data; } + size_t size() const { return Size; } }; diff --git a/src/ArgsParser.cpp b/src/ArgsParser.cpp index 8440661..f0d6257 100644 --- a/src/ArgsParser.cpp +++ b/src/ArgsParser.cpp @@ -1,6 +1,7 @@ #include "ArgsParser.h" #include "Common.h" #include +#include void ArgsParser::Parse(const std::vector& ArgList) { for (const auto& Arg : ArgList) { @@ -12,7 +13,7 @@ void ArgsParser::Parse(const std::vector& ArgList) { ConsumeLongFlag(std::string(Arg)); } } else { - beammp_error("Error parsing commandline arguments: Supplied argument '" + std::string(Arg) + "' is not a valid argument and was ignored."); + beammp_errorf("Error parsing commandline arguments: Supplied argument '{}' is not a valid argument and was ignored.", Arg); } } } @@ -21,7 +22,7 @@ bool ArgsParser::Verify() { bool Ok = true; for (const auto& RegisteredArg : mRegisteredArguments) { if (RegisteredArg.Flags & Flags::REQUIRED && !FoundArgument(RegisteredArg.Names)) { - beammp_error("Error in commandline arguments: Argument '" + std::string(RegisteredArg.Names.at(0)) + "' is required but wasn't found."); + beammp_errorf("Error in commandline arguments: Argument '{}' is required but wasn't found.", RegisteredArg.Names.at(0)); Ok = false; continue; } else if (FoundArgument(RegisteredArg.Names)) { @@ -92,3 +93,78 @@ void ArgsParser::ConsumeLongFlag(const std::string& Arg) { beammp_warn("Argument '" + Name + "' was supplied but isn't a known argument, so it is likely being ignored."); } } + +TEST_CASE("ArgsParser") { + ArgsParser parser; + + SUBCASE("Simple args") { + parser.RegisterArgument({ "a" }, ArgsParser::Flags::NONE); + parser.RegisterArgument({ "hello" }, ArgsParser::Flags::NONE); + parser.Parse({ "--a", "--hello" }); + CHECK(parser.Verify()); + CHECK(parser.FoundArgument({ "a" })); + CHECK(parser.FoundArgument({ "hello" })); + CHECK(parser.FoundArgument({ "a", "hello" })); + CHECK(!parser.FoundArgument({ "b" })); + CHECK(!parser.FoundArgument({ "goodbye" })); + } + + SUBCASE("No args") { + parser.RegisterArgument({ "a" }, ArgsParser::Flags::NONE); + parser.RegisterArgument({ "hello" }, ArgsParser::Flags::NONE); + parser.Parse({}); + CHECK(parser.Verify()); + CHECK(!parser.FoundArgument({ "a" })); + CHECK(!parser.FoundArgument({ "hello" })); + CHECK(!parser.FoundArgument({ "a", "hello" })); + CHECK(!parser.FoundArgument({ "b" })); + CHECK(!parser.FoundArgument({ "goodbye" })); + CHECK(!parser.FoundArgument({ "" })); + } + + SUBCASE("Value args") { + parser.RegisterArgument({ "a" }, ArgsParser::Flags::HAS_VALUE); + parser.RegisterArgument({ "hello" }, ArgsParser::Flags::HAS_VALUE); + parser.Parse({ "--a=5", "--hello=world" }); + CHECK(parser.Verify()); + REQUIRE(parser.FoundArgument({ "a" })); + REQUIRE(parser.FoundArgument({ "hello" })); + CHECK(parser.GetValueOfArgument({ "a" }).has_value()); + CHECK(parser.GetValueOfArgument({ "a" }).value() == "5"); + CHECK(parser.GetValueOfArgument({ "hello" }).has_value()); + CHECK(parser.GetValueOfArgument({ "hello" }).value() == "world"); + } + + SUBCASE("Mixed value & no-value args") { + parser.RegisterArgument({ "a" }, ArgsParser::Flags::HAS_VALUE); + parser.RegisterArgument({ "hello" }, ArgsParser::Flags::NONE); + parser.Parse({ "--a=5", "--hello" }); + CHECK(parser.Verify()); + REQUIRE(parser.FoundArgument({ "a" })); + REQUIRE(parser.FoundArgument({ "hello" })); + CHECK(parser.GetValueOfArgument({ "a" }).has_value()); + CHECK(parser.GetValueOfArgument({ "a" }).value() == "5"); + CHECK(!parser.GetValueOfArgument({ "hello" }).has_value()); + } + + SUBCASE("Required args") { + SUBCASE("Two required, two present") { + parser.RegisterArgument({ "a" }, ArgsParser::Flags::REQUIRED); + parser.RegisterArgument({ "hello" }, ArgsParser::Flags::REQUIRED); + parser.Parse({ "--a", "--hello" }); + CHECK(parser.Verify()); + } + SUBCASE("Two required, one present") { + parser.RegisterArgument({ "a" }, ArgsParser::Flags::REQUIRED); + parser.RegisterArgument({ "hello" }, ArgsParser::Flags::REQUIRED); + parser.Parse({ "--a" }); + CHECK(!parser.Verify()); + } + SUBCASE("Two required, none present") { + parser.RegisterArgument({ "a" }, ArgsParser::Flags::REQUIRED); + parser.RegisterArgument({ "hello" }, ArgsParser::Flags::REQUIRED); + parser.Parse({ "--b" }); + CHECK(!parser.Verify()); + } + } +} diff --git a/src/Client.cpp b/src/Client.cpp index 4edbbc7..b03c99a 100644 --- a/src/Client.cpp +++ b/src/Client.cpp @@ -5,8 +5,6 @@ #include #include -// FIXME: add debug prints - void TClient::DeleteCar(int Ident) { std::unique_lock lock(mVehicleDataMutex); auto iter = std::find_if(mVehicleData.begin(), mVehicleData.end(), [&](auto& elem) { @@ -49,6 +47,34 @@ TClient::TVehicleDataLockPair TClient::GetAllCars() { return { &mVehicleData, std::unique_lock(mVehicleDataMutex) }; } +std::string TClient::GetCarPositionRaw(int Ident) { + std::unique_lock lock(mVehiclePositionMutex); + try { + return mVehiclePosition.at(Ident); + } catch (const std::out_of_range& oor) { + return ""; + } + return ""; +} + +void TClient::Disconnect(std::string_view Reason) { + beammp_debugf("Disconnecting client {} for reason: {}", GetID(), Reason); + boost::system::error_code ec; + mSocket.shutdown(socket_base::shutdown_both, ec); + if (ec) { + beammp_debugf("Failed to shutdown client socket: {}", ec.message()); + } + mSocket.close(ec); + if (ec) { + beammp_debugf("Failed to close client socket: {}", ec.message()); + } +} + +void TClient::SetCarPosition(int Ident, const std::string& Data) { + std::unique_lock lock(mVehiclePositionMutex); + mVehiclePosition[Ident] = Data; +} + std::string TClient::GetCarData(int Ident) { { // lock std::unique_lock lock(mVehicleDataMutex); @@ -83,16 +109,22 @@ TServer& TClient::Server() const { return mServer; } -void TClient::EnqueuePacket(const std::string& Packet) { +void TClient::EnqueuePacket(const std::vector& Packet) { std::unique_lock Lock(mMissedPacketsMutex); mPacketsSync.push(Packet); } -TClient::TClient(TServer& Server) +TClient::TClient(TServer& Server, ip::tcp::socket&& Socket) : mServer(Server) + , mSocket(std::move(Socket)) + , mDownSocket(ip::tcp::socket(Server.IoCtx())) , mLastPingTime(std::chrono::high_resolution_clock::now()) { } +TClient::~TClient() { + beammp_debugf("client destroyed: {} ('{}')", this->GetID(), this->GetName()); +} + void TClient::UpdatePingTime() { mLastPingTime = std::chrono::high_resolution_clock::now(); } diff --git a/src/Common.cpp b/src/Common.cpp index 34b28ea..5b153ca 100644 --- a/src/Common.cpp +++ b/src/Common.cpp @@ -9,9 +9,13 @@ #include #include +#include "Compat.h" #include "CustomAssert.h" #include "Http.h" +// global, yes, this is ugly, no, it cant be done another way +TSentry Sentry {}; + Application::TSettings Application::Settings = {}; void Application::RegisterShutdownHandler(const TShutdownHandler& Handler) { @@ -22,6 +26,7 @@ void Application::RegisterShutdownHandler(const TShutdownHandler& Handler) { } void Application::GracefullyShutdown() { + SetShutdown(true); static bool AlreadyShuttingDown = false; static uint8_t ShutdownAttempts = 0; if (AlreadyShuttingDown) { @@ -43,6 +48,7 @@ void Application::GracefullyShutdown() { beammp_info("Subsystem " + std::to_string(i + 1) + "/" + std::to_string(mShutdownHandlers.size()) + " shutting down"); mShutdownHandlers[i](); } + // std::exit(-1); } std::string Application::ServerVersionString() { @@ -60,7 +66,23 @@ std::array Application::VersionStrToInts(const std::string& str) { return Version; } -// FIXME: This should be used by operator< on Version +TEST_CASE("Application::VersionStrToInts") { + auto v = Application::VersionStrToInts("1.2.3"); + CHECK(v[0] == 1); + CHECK(v[1] == 2); + CHECK(v[2] == 3); + + v = Application::VersionStrToInts("10.20.30"); + CHECK(v[0] == 10); + CHECK(v[1] == 20); + CHECK(v[2] == 30); + + v = Application::VersionStrToInts("100.200.255"); + CHECK(v[0] == 100); + CHECK(v[1] == 200); + CHECK(v[2] == 255); +} + bool Application::IsOutdated(const Version& Current, const Version& Newest) { if (Newest.major > Current.major) { return true; @@ -73,6 +95,65 @@ bool Application::IsOutdated(const Version& Current, const Version& Newest) { } } +bool Application::IsShuttingDown() { + std::shared_lock Lock(mShutdownMtx); + return mShutdown; +} + +void Application::SleepSafeSeconds(size_t Seconds) { + // Sleeps for 500 ms, checks if a shutdown occurred, and so forth + for (size_t i = 0; i < Seconds * 2; ++i) { + if (Application::IsShuttingDown()) { + return; + } else { + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + } + } +} + +TEST_CASE("Application::IsOutdated (version check)") { + SUBCASE("Same version") { + CHECK(!Application::IsOutdated({ 1, 2, 3 }, { 1, 2, 3 })); + } + // we need to use over 1-2 digits to test against lexical comparisons + SUBCASE("Patch outdated") { + for (uint8_t Patch = 0; Patch < 10; ++Patch) { + for (uint8_t Minor = 0; Minor < 10; ++Minor) { + for (uint8_t Major = 0; Major < 10; ++Major) { + CHECK(Application::IsOutdated({ uint8_t(Major), uint8_t(Minor), uint8_t(Patch) }, { uint8_t(Major), uint8_t(Minor), uint8_t(Patch + 1) })); + } + } + } + } + SUBCASE("Minor outdated") { + for (uint8_t Patch = 0; Patch < 10; ++Patch) { + for (uint8_t Minor = 0; Minor < 10; ++Minor) { + for (uint8_t Major = 0; Major < 10; ++Major) { + CHECK(Application::IsOutdated({ uint8_t(Major), uint8_t(Minor), uint8_t(Patch) }, { uint8_t(Major), uint8_t(Minor + 1), uint8_t(Patch) })); + } + } + } + } + SUBCASE("Major outdated") { + for (uint8_t Patch = 0; Patch < 10; ++Patch) { + for (uint8_t Minor = 0; Minor < 10; ++Minor) { + for (uint8_t Major = 0; Major < 10; ++Major) { + CHECK(Application::IsOutdated({ uint8_t(Major), uint8_t(Minor), uint8_t(Patch) }, { uint8_t(Major + 1), uint8_t(Minor), uint8_t(Patch) })); + } + } + } + } + SUBCASE("All outdated") { + for (uint8_t Patch = 0; Patch < 10; ++Patch) { + for (uint8_t Minor = 0; Minor < 10; ++Minor) { + for (uint8_t Major = 0; Major < 10; ++Major) { + CHECK(Application::IsOutdated({ uint8_t(Major), uint8_t(Minor), uint8_t(Patch) }, { uint8_t(Major + 1), uint8_t(Minor + 1), uint8_t(Patch + 1) })); + } + } + } + } +} + void Application::SetSubsystemStatus(const std::string& Subsystem, Status status) { switch (status) { case Status::Good: @@ -90,18 +171,34 @@ void Application::SetSubsystemStatus(const std::string& Subsystem, Status status case Status::Shutdown: beammp_trace("Subsystem '" + Subsystem + "': Shutdown"); break; + default: + beammp_assert_not_reachable(); } std::unique_lock Lock(mSystemStatusMapMutex); mSystemStatusMap[Subsystem] = status; } +void Application::SetShutdown(bool Val) { + std::unique_lock Lock(mShutdownMtx); + mShutdown = Val; +} + +TEST_CASE("Application::SetSubsystemStatus") { + Application::SetSubsystemStatus("Test", Application::Status::Good); + auto Map = Application::GetSubsystemStatuses(); + CHECK(Map.at("Test") == Application::Status::Good); + Application::SetSubsystemStatus("Test", Application::Status::Bad); + Map = Application::GetSubsystemStatuses(); + CHECK(Map.at("Test") == Application::Status::Bad); +} + void Application::CheckForUpdates() { Application::SetSubsystemStatus("UpdateCheck", Application::Status::Starting); static bool FirstTime = true; // checks current version against latest version std::regex VersionRegex { R"(\d+\.\d+\.\d+\n*)" }; for (const auto& url : GetBackendUrlsInOrder()) { - auto Response = Http::GET(GetBackendUrlsInOrder().at(0), 443, "/v/s"); + auto Response = Http::GET(url, 443, "/v/s"); bool Matches = std::regex_match(Response, VersionRegex); if (Matches) { auto MyVersion = ServerVersion(); @@ -152,6 +249,25 @@ std::string ThreadName(bool DebugModeOverride) { return ""; } +TEST_CASE("ThreadName") { + RegisterThread("MyThread"); + auto OrigDebug = Application::Settings.DebugModeEnabled; + + // ThreadName adds a space at the end, legacy but we need it still + SUBCASE("Debug mode enabled") { + Application::Settings.DebugModeEnabled = true; + CHECK(ThreadName(true) == "MyThread "); + CHECK(ThreadName(false) == "MyThread "); + } + SUBCASE("Debug mode disabled") { + Application::Settings.DebugModeEnabled = false; + CHECK(ThreadName(true) == "MyThread "); + CHECK(ThreadName(false) == ""); + } + // cleanup + Application::Settings.DebugModeEnabled = OrigDebug; +} + void RegisterThread(const std::string& str) { std::string ThreadId; #ifdef BEAMMP_WINDOWS @@ -162,13 +278,18 @@ void RegisterThread(const std::string& str) { ThreadId = std::to_string(gettid()); #endif if (Application::Settings.DebugModeEnabled) { - std::ofstream ThreadFile("Threads.log", std::ios::app); + std::ofstream ThreadFile(".Threads.log", std::ios::app); ThreadFile << ("Thread \"" + str + "\" is TID " + ThreadId) << std::endl; } auto Lock = std::unique_lock(ThreadNameMapMutex); threadNameMap[std::this_thread::get_id()] = str; } +TEST_CASE("RegisterThread") { + RegisterThread("MyThread"); + CHECK(threadNameMap.at(std::this_thread::get_id()) == "MyThread"); +} + Version::Version(uint8_t major, uint8_t minor, uint8_t patch) : major(major) , minor(minor) @@ -179,22 +300,30 @@ Version::Version(const std::array& v) } std::string Version::AsString() { - std::stringstream ss {}; - ss << int(major) << "." << int(minor) << "." << int(patch); - return ss.str(); + return fmt::format("{:d}.{:d}.{:d}", major, minor, patch); +} + +TEST_CASE("Version::AsString") { + CHECK(Version { 0, 0, 0 }.AsString() == "0.0.0"); + CHECK(Version { 1, 2, 3 }.AsString() == "1.2.3"); + CHECK(Version { 255, 255, 255 }.AsString() == "255.255.255"); } void LogChatMessage(const std::string& name, int id, const std::string& msg) { - std::stringstream ss; - ss << ThreadName(); - ss << "[CHAT] "; - if (id != -1) { - ss << "(" << id << ") <" << name << "> "; - } else { - ss << name << ""; + if (Application::Settings.LogChat) { + std::stringstream ss; + ss << ThreadName(); + ss << "[CHAT] "; + if (id != -1) { + ss << "(" << id << ") <" << name << "> "; + } else { + ss << name << ""; + } + ss << msg; +#ifdef DOCTEST_CONFIG_DISABLE + Application::Console().Write(ss.str()); +#endif } - ss << msg; - Application::Console().Write(ss.str()); } std::string GetPlatformAgnosticErrorString() { @@ -221,5 +350,7 @@ std::string GetPlatformAgnosticErrorString() { } #elif defined(BEAMMP_LINUX) || defined(BEAMMP_APPLE) return std::strerror(errno); +#else + return "(no human-readable errors on this platform)"; #endif } diff --git a/src/Compat.cpp b/src/Compat.cpp index a1765b7..46efeda 100644 --- a/src/Compat.cpp +++ b/src/Compat.cpp @@ -1,10 +1,13 @@ #include "Compat.h" +#include +#include + #ifndef WIN32 static struct termios old, current; -void initTermios(int echo) { +static void initTermios(int echo) { tcgetattr(0, &old); /* grab old terminal i/o settings */ current = old; /* make new settings same as old settings */ current.c_lflag &= ~ICANON; /* disable buffered i/o */ @@ -16,14 +19,40 @@ void initTermios(int echo) { tcsetattr(0, TCSANOW, ¤t); /* use these new terminal i/o settings now */ } -void resetTermios(void) { +static void resetTermios(void) { tcsetattr(0, TCSANOW, &old); } -char getch_(int echo) { +TEST_CASE("init and reset termios") { + if (isatty(STDIN_FILENO)) { + struct termios original; + tcgetattr(0, &original); + SUBCASE("no echo") { + initTermios(false); + } + SUBCASE("yes echo") { + initTermios(true); + } + resetTermios(); + struct termios current; + tcgetattr(0, ¤t); + CHECK_EQ(std::memcmp(¤t.c_cc, &original.c_cc, sizeof(current.c_cc)), 0); + CHECK_EQ(current.c_cflag, original.c_cflag); + CHECK_EQ(current.c_iflag, original.c_iflag); + CHECK_EQ(current.c_ispeed, original.c_ispeed); + CHECK_EQ(current.c_lflag, original.c_lflag); + CHECK_EQ(current.c_line, original.c_line); + CHECK_EQ(current.c_oflag, original.c_oflag); + CHECK_EQ(current.c_ospeed, original.c_ospeed); + } +} + +static char getch_(int echo) { char ch; initTermios(echo); - read(STDIN_FILENO, &ch, 1); + if (read(STDIN_FILENO, &ch, 1) < 0) { + // ignore, not much we can do + } resetTermios(); return ch; } diff --git a/src/Http.cpp b/src/Http.cpp index aeb66d5..9f87ea4 100644 --- a/src/Http.cpp +++ b/src/Http.cpp @@ -4,20 +4,15 @@ #include "Common.h" #include "CustomAssert.h" #include "LuaAPI.h" -#include "httplib.h" #include +#include #include -#include -#include -#include -#include #include -fs::path Http::Server::THttpServerInstance::KeyFilePath; -fs::path Http::Server::THttpServerInstance::CertFilePath; + // TODO: Add sentry error handling back -namespace json = rapidjson; +using json = nlohmann::json; std::string Http::GET(const std::string& host, int port, const std::string& target, unsigned int* status) { httplib::SSLClient client(host, port); @@ -146,145 +141,10 @@ std::string Http::Status::ToString(int Code) { } } -long Http::Server::Tx509KeypairGenerator::GenerateRandomId() { - std::random_device R; - std::default_random_engine E1(R()); - std::uniform_int_distribution UniformDist(0, ULONG_MAX); - return UniformDist(E1); -} - -// Http::Server::THttpServerInstance::THttpServerInstance() { } -EVP_PKEY* Http::Server::Tx509KeypairGenerator::GenerateKey() { - /** - * Allocate memory for the pkey - */ - EVP_PKEY* PKey = EVP_PKEY_new(); - if (PKey == nullptr) { - beammp_error("Could not allocate memory for X.509 private key (PKEY) generation."); - throw std::runtime_error { std::string { "X.509 PKEY allocation error" } }; - } - BIGNUM* E = BN_new(); - beammp_assert(E); // TODO: replace all these asserts with beammp_errors - unsigned char three = 3; - BIGNUM* EErr = BN_bin2bn(&three, sizeof(three), E); - beammp_assert(EErr); - RSA* Rsa = RSA_new(); - beammp_assert(Rsa); - int Ret = RSA_generate_key_ex(Rsa, Crypto::RSA_DEFAULT_KEYLENGTH, E, nullptr); - beammp_assert(Ret == 1); - BN_free(E); - if (!EVP_PKEY_assign_RSA(PKey, Rsa)) { - EVP_PKEY_free(PKey); - beammp_error(std::string("Could not generate " + std::to_string(Crypto::RSA_DEFAULT_KEYLENGTH) + "-bit RSA key.")); - throw std::runtime_error { std::string("X.509 RSA key generation error") }; - } - // todo: figure out if returning by reference instead of passing pointers is a security breach - return PKey; -} - -X509* Http::Server::Tx509KeypairGenerator::GenerateCertificate(EVP_PKEY& PKey) { - X509* X509 = X509_new(); - if (X509 == nullptr) { - X509_free(X509); - beammp_error("Could not allocate memory for X.509 certificate generation."); - throw std::runtime_error { std::string("X.509 certificate generation error") }; - } - - /**Set the metadata of the certificate*/ - ASN1_INTEGER_set(X509_get_serialNumber(X509), GenerateRandomId()); - - /**Set the cert validity to a year*/ - X509_gmtime_adj(X509_get_notBefore(X509), 0); - X509_gmtime_adj(X509_get_notAfter(X509), 31536000L); - - /**Set the public key of the cert*/ - X509_set_pubkey(X509, &PKey); - - X509_NAME* Name = X509_get_subject_name(X509); - - /**Set cert metadata*/ - X509_NAME_add_entry_by_txt(Name, "C", MBSTRING_ASC, (unsigned char*)"GB", -1, -1, 0); - X509_NAME_add_entry_by_txt(Name, "O", MBSTRING_ASC, (unsigned char*)"BeamMP Ltd.", -1, -1, 0); - X509_NAME_add_entry_by_txt(Name, "CN", MBSTRING_ASC, (unsigned char*)"localhost", -1, -1, 0); - - X509_set_issuer_name(X509, Name); - - // TODO: Hashing with sha256 might cause problems, check later - if (!X509_sign(X509, &PKey, EVP_sha1())) { - X509_free(X509); - beammp_error("Could not sign X.509 certificate."); - throw std::runtime_error { std::string("X.509 certificate signing error") }; - } - return X509; -} - -void Http::Server::Tx509KeypairGenerator::GenerateAndWriteToDisk(const fs::path& KeyFilePath, const fs::path& CertFilePath) { - // todo: generate directories for ssl keys - FILE* KeyFile = std::fopen(reinterpret_cast(KeyFilePath.c_str()), "wb"); - if (!KeyFile) { - beammp_error("Could not create file 'key.pem', check your permissions"); - throw std::runtime_error("Could not create file 'key.pem'"); - } - - EVP_PKEY* PKey = Http::Server::Tx509KeypairGenerator::GenerateKey(); - - bool WriteOpResult = PEM_write_PrivateKey(KeyFile, PKey, nullptr, nullptr, 0, nullptr, nullptr); - fclose(KeyFile); - - if (!WriteOpResult) { - beammp_error("Could not write to file 'key.pem', check your permissions"); - throw std::runtime_error("Could not write to file 'key.pem'"); - } - - FILE* CertFile = std::fopen(reinterpret_cast(CertFilePath.c_str()), "wb"); // x509 file - if (!CertFile) { - beammp_error("Could not create file 'cert.pem', check your permissions"); - throw std::runtime_error("Could not create file 'cert.pem'"); - } - - X509* x509 = Http::Server::Tx509KeypairGenerator::GenerateCertificate(*PKey); - WriteOpResult = PEM_write_X509(CertFile, x509); - fclose(CertFile); - - if (!WriteOpResult) { - beammp_error("Could not write to file 'cert.pem', check your permissions"); - throw std::runtime_error("Could not write to file 'cert.pem'"); - } - EVP_PKEY_free(PKey); - X509_free(x509); - return; -} - -bool Http::Server::Tx509KeypairGenerator::EnsureTLSConfigExists() { - if (fs::is_regular_file(Application::Settings.SSLKeyPath) - && fs::is_regular_file(Application::Settings.SSLCertPath)) { - return true; - } else { - return false; - } -} - -void Http::Server::SetupEnvironment() { - if (!Application::Settings.HTTPServerUseSSL) { - return; - } - auto parent = fs::path(Application::Settings.SSLKeyPath).parent_path(); - if (!fs::exists(parent)) - fs::create_directories(parent); - - Application::TSettings defaultSettings {}; - if (!Tx509KeypairGenerator::EnsureTLSConfigExists()) { - beammp_warn(std::string("No default TLS Key / Cert found. " - "IF YOU HAVE NOT MODIFIED THE SSLKeyPath OR SSLCertPath VALUES " - "THIS IS NORMAL ON FIRST STARTUP! BeamMP will generate it's own certs in the default directory " - "(Check for permissions or corrupted key-/certfile)")); - Tx509KeypairGenerator::GenerateAndWriteToDisk(defaultSettings.SSLKeyPath, defaultSettings.SSLCertPath); - Http::Server::THttpServerInstance::KeyFilePath = defaultSettings.SSLKeyPath; - Http::Server::THttpServerInstance::CertFilePath = defaultSettings.SSLCertPath; - } else { - Http::Server::THttpServerInstance::KeyFilePath = Application::Settings.SSLKeyPath; - Http::Server::THttpServerInstance::CertFilePath = Application::Settings.SSLCertPath; - } +TEST_CASE("Http::Status::ToString") { + CHECK(Http::Status::ToString(200) == "OK"); + CHECK(Http::Status::ToString(696969) == "696969"); + CHECK(Http::Status::ToString(-1) == "Invalid Response Code"); } Http::Server::THttpServerInstance::THttpServerInstance() { @@ -296,13 +156,7 @@ Http::Server::THttpServerInstance::THttpServerInstance() { void Http::Server::THttpServerInstance::operator()() try { beammp_info("HTTP(S) Server started on port " + std::to_string(Application::Settings.HTTPServerPort)); std::unique_ptr HttpLibServerInstance; - if (Application::Settings.HTTPServerUseSSL) { - HttpLibServerInstance = std::make_unique( - reinterpret_cast(Http::Server::THttpServerInstance::CertFilePath.c_str()), - reinterpret_cast(Http::Server::THttpServerInstance::KeyFilePath.c_str())); - } else { - HttpLibServerInstance = std::make_unique(); - } + HttpLibServerInstance = std::make_unique(); // todo: make this IP agnostic so people can set their own IP HttpLibServerInstance->Get("/", [](const httplib::Request&, httplib::Response& res) { res.set_content("Hello World!BeamMP Server can now serve HTTP requests!
BeamMP Server can now serve HTTP requests!