From ff36addda53c53e57988c478f7a67bb7ac54ab4c Mon Sep 17 00:00:00 2001 From: WolverinDEV Date: Wed, 26 Jun 2019 22:11:22 +0200 Subject: [PATCH] Initial commit --- .gitignore | 2 + CMakeLists.txt | 229 ++++++ main.cpp | 73 ++ src/BasicChannel.cpp | 291 ++++++++ src/BasicChannel.h | 116 +++ src/Definitions.h | 155 ++++ src/Error.cpp | 176 +++++ src/Error.h | 333 +++++++++ src/EventLoop.cpp | 212 ++++++ src/EventLoop.h | 88 +++ src/License.cpp | 376 ++++++++++ src/License.h | 197 +++++ src/PermissionManager.cpp | 1010 ++++++++++++++++++++++++++ src/PermissionManager.h | 725 ++++++++++++++++++ src/Properties.cpp | 621 ++++++++++++++++ src/Properties.h | 842 +++++++++++++++++++++ src/Variable.cpp | 17 + src/Variable.h | 143 ++++ src/bbcode/bbcodes.cpp | 94 +++ src/bbcode/bbcodes.h | 57 ++ src/channel/TreeView.cpp | 325 +++++++++ src/channel/TreeView.h | 76 ++ src/converters/converter.h | 84 +++ src/linked_helper.cpp | 133 ++++ src/linked_helper.h | 30 + src/log/LogSinks.cpp | 118 +++ src/log/LogSinks.h | 50 ++ src/log/LogUtils.cpp | 206 ++++++ src/log/LogUtils.h | 136 ++++ src/log/translation.cpp | 5 + src/log/translation.h | 13 + src/misc/TraceUtils.cpp | 119 +++ src/misc/TraceUtils.h | 56 ++ src/misc/advanced_mutex.h | 299 ++++++++ src/misc/base64.h | 71 ++ src/misc/cast.h | 24 + src/misc/digest.h | 58 ++ src/misc/endianness.h | 110 +++ src/misc/hex.h | 20 + src/misc/lambda.h | 59 ++ src/misc/memtracker.cpp | 227 ++++++ src/misc/memtracker.h | 30 + src/misc/net.h | 200 +++++ src/misc/queue.h | 527 ++++++++++++++ src/misc/rnd.cpp | 18 + src/misc/rnd.h | 8 + src/misc/sassert.h | 15 + src/misc/spin_lock.h | 25 + src/misc/std_unique_ptr.h | 38 + src/misc/time.cpp | 59 ++ src/misc/time.h | 9 + src/misc/timer.h | 36 + src/protocol/AcknowledgeManager.cpp | 157 ++++ src/protocol/AcknowledgeManager.h | 51 ++ src/protocol/CompressionHandler.cpp | 81 +++ src/protocol/CompressionHandler.h | 21 + src/protocol/CryptionHandler.cpp | 378 ++++++++++ src/protocol/CryptionHandler.h | 74 ++ src/protocol/Packet.cpp | 233 ++++++ src/protocol/Packet.h | 290 ++++++++ src/protocol/buffers.cpp | 18 + src/protocol/buffers.h | 269 +++++++ src/protocol/buffers_allocator_a.cpp | 392 ++++++++++ src/protocol/buffers_allocator_b.cpp | 179 +++++ src/protocol/buffers_allocator_c.cpp | 16 + src/protocol/ringbuffer.cpp | 5 + src/protocol/ringbuffer.h | 225 ++++++ src/qlz/QuickLZ.cpp | 26 + src/qlz/QuickLZ.h | 162 +++++ src/qlz/QuickLZ_L1.cpp | 843 +++++++++++++++++++++ src/qlz/QuickLZ_L2.cpp | 848 +++++++++++++++++++++ src/qlz/QuickLZ_L3.cpp | 843 +++++++++++++++++++++ src/query/Command.cpp | 243 +++++++ src/query/Command.h | 218 ++++++ src/query/command2.cpp | 251 +++++++ src/query/command2.h | 713 ++++++++++++++++++ src/query/command_constants.h | 28 + src/query/command_exception.h | 26 + src/query/command_internal.h | 32 + src/query/escape.cpp | 143 ++++ src/query/escape.h | 10 + src/sql/SqlQuery.cpp | 53 ++ src/sql/SqlQuery.h | 436 +++++++++++ src/sql/mysql/MySQL.cpp | 384 ++++++++++ src/sql/mysql/MySQL.h | 91 +++ src/sql/sqlite/SqliteSQL.cpp | 169 +++++ src/sql/sqlite/SqliteSQL.h | 32 + src/ssl/SSLManager.cpp | 371 ++++++++++ src/ssl/SSLManager.h | 62 ++ test/BBTest.cpp | 29 + test/ChannelTest.cpp | 121 +++ test/CommandTest.cpp | 156 ++++ test/CrashTest.cpp | 67 ++ test/EndianessTest.cpp | 59 ++ test/LinkedTest.cpp | 29 + test/PropertyTest.cpp | 69 ++ test/RingTest.cpp | 98 +++ test/SQLTest.cpp | 150 ++++ test/WSSTest.cpp | 303 ++++++++ 99 files changed, 18395 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 main.cpp create mode 100644 src/BasicChannel.cpp create mode 100644 src/BasicChannel.h create mode 100644 src/Definitions.h create mode 100644 src/Error.cpp create mode 100644 src/Error.h create mode 100644 src/EventLoop.cpp create mode 100644 src/EventLoop.h create mode 100644 src/License.cpp create mode 100644 src/License.h create mode 100644 src/PermissionManager.cpp create mode 100644 src/PermissionManager.h create mode 100644 src/Properties.cpp create mode 100644 src/Properties.h create mode 100644 src/Variable.cpp create mode 100644 src/Variable.h create mode 100644 src/bbcode/bbcodes.cpp create mode 100644 src/bbcode/bbcodes.h create mode 100644 src/channel/TreeView.cpp create mode 100644 src/channel/TreeView.h create mode 100644 src/converters/converter.h create mode 100644 src/linked_helper.cpp create mode 100644 src/linked_helper.h create mode 100644 src/log/LogSinks.cpp create mode 100644 src/log/LogSinks.h create mode 100644 src/log/LogUtils.cpp create mode 100644 src/log/LogUtils.h create mode 100644 src/log/translation.cpp create mode 100644 src/log/translation.h create mode 100644 src/misc/TraceUtils.cpp create mode 100644 src/misc/TraceUtils.h create mode 100644 src/misc/advanced_mutex.h create mode 100644 src/misc/base64.h create mode 100644 src/misc/cast.h create mode 100644 src/misc/digest.h create mode 100644 src/misc/endianness.h create mode 100644 src/misc/hex.h create mode 100644 src/misc/lambda.h create mode 100644 src/misc/memtracker.cpp create mode 100644 src/misc/memtracker.h create mode 100644 src/misc/net.h create mode 100644 src/misc/queue.h create mode 100644 src/misc/rnd.cpp create mode 100644 src/misc/rnd.h create mode 100644 src/misc/sassert.h create mode 100644 src/misc/spin_lock.h create mode 100644 src/misc/std_unique_ptr.h create mode 100644 src/misc/time.cpp create mode 100644 src/misc/time.h create mode 100644 src/misc/timer.h create mode 100644 src/protocol/AcknowledgeManager.cpp create mode 100644 src/protocol/AcknowledgeManager.h create mode 100644 src/protocol/CompressionHandler.cpp create mode 100644 src/protocol/CompressionHandler.h create mode 100644 src/protocol/CryptionHandler.cpp create mode 100644 src/protocol/CryptionHandler.h create mode 100644 src/protocol/Packet.cpp create mode 100644 src/protocol/Packet.h create mode 100644 src/protocol/buffers.cpp create mode 100644 src/protocol/buffers.h create mode 100644 src/protocol/buffers_allocator_a.cpp create mode 100644 src/protocol/buffers_allocator_b.cpp create mode 100644 src/protocol/buffers_allocator_c.cpp create mode 100644 src/protocol/ringbuffer.cpp create mode 100644 src/protocol/ringbuffer.h create mode 100644 src/qlz/QuickLZ.cpp create mode 100644 src/qlz/QuickLZ.h create mode 100644 src/qlz/QuickLZ_L1.cpp create mode 100644 src/qlz/QuickLZ_L2.cpp create mode 100644 src/qlz/QuickLZ_L3.cpp create mode 100644 src/query/Command.cpp create mode 100644 src/query/Command.h create mode 100644 src/query/command2.cpp create mode 100644 src/query/command2.h create mode 100644 src/query/command_constants.h create mode 100644 src/query/command_exception.h create mode 100644 src/query/command_internal.h create mode 100644 src/query/escape.cpp create mode 100644 src/query/escape.h create mode 100644 src/sql/SqlQuery.cpp create mode 100644 src/sql/SqlQuery.h create mode 100644 src/sql/mysql/MySQL.cpp create mode 100644 src/sql/mysql/MySQL.h create mode 100644 src/sql/sqlite/SqliteSQL.cpp create mode 100644 src/sql/sqlite/SqliteSQL.h create mode 100644 src/ssl/SSLManager.cpp create mode 100644 src/ssl/SSLManager.h create mode 100644 test/BBTest.cpp create mode 100644 test/ChannelTest.cpp create mode 100644 test/CommandTest.cpp create mode 100644 test/CrashTest.cpp create mode 100644 test/EndianessTest.cpp create mode 100644 test/LinkedTest.cpp create mode 100644 test/PropertyTest.cpp create mode 100644 test/RingTest.cpp create mode 100644 test/SQLTest.cpp create mode 100644 test/WSSTest.cpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..822dd55 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +cmake-build-*/ +.idea/ diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..51d1e79 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,229 @@ +cmake_minimum_required(VERSION 3.6) +project(TeaSpeak-Shared) +set(CMAKE_CXX_STANDARD 17) + +if(NOT WIN32) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC -Wall -Wno-reorder -Wno-sign-compare -fpermissive -ftemplate-depth=1000 ${MEMORY_DEBUG_FLAGS}") + set(CMAKE_INCLUDE_CURRENT_DIR ON) +endif() + +if(NOT TEASPEAK_SERVER) + set(CMAKE_MODULE_PATH "C:/Users/WolverinDEV/TeaSpeak/cmake") + include(${CMAKE_MODULE_PATH}/libraries_wolverin_lap.cmake) + + find_package(TomMath REQUIRED) + include_directories(${TomMath_INCLUDE_DIR}) + + find_package(TomCrypt REQUIRED) + include_directories(${TomCrypt_INCLUDE_DIR}) + + find_package(DataPipes REQUIRED) + include_directories(${DataPipes_INCLUDE_DIR}) + message("Include: ${DataPipes_INCLUDE_DIR}") + + find_package(LibEvent REQUIRED) + include_directories(${LIBEVENT_INCLUDE_DIRS}) + + find_package(StringVariable REQUIRED) + include_directories(${StringVariable_INCLUDE_DIR}) + + find_package(ed25519 REQUIRED) + include_directories(${ed25519_INCLUDE_DIR}) + + find_package(ThreadPool REQUIRED) + include_directories(${ThreadPool_INCLUDE_DIR}) + add_definitions(-DWINDOWS) #Required for ThreadPool + + find_package(SpdLog CONFIG REQUIRED) + link_libraries(spdlog::spdlog) #Its a header only lib so we should be fine :) + + add_definitions(-DNO_OPEN_SSL) + add_definitions(-D_HAS_STD_BYTE) + #FML +else() + add_definitions(-DHAVE_CXX_TERMINAL) + add_definitions(-DHAVE_JSON) + set(HAVE_SQLITE3 ON) + set(HAVE_OPEN_SSL ON) + message("HAVE JSON!") +endif() + +if (MSVC) + set(CompilerFlags + CMAKE_CXX_FLAGS + CMAKE_CXX_FLAGS_DEBUG + CMAKE_CXX_FLAGS_RELEASE + CMAKE_C_FLAGS + CMAKE_C_FLAGS_DEBUG + CMAKE_C_FLAGS_RELEASE + ) + foreach(CompilerFlag ${CompilerFlags}) + string(REPLACE "/MD" "/MT" ${CompilerFlag} "${${CompilerFlag}}") + endforeach() +else() + set(CMAKE_CXX_FLAGS_RELEASE "-O3") #-DNDEBUG We want assert! +endif() +add_definitions(-DUSE_BORINGSSL) +include_directories(${LIBRARY_PATH}/boringssl/include/) +set(SOURCE_FILES + src/misc/rnd.cpp + src/misc/time.cpp + src/misc/memtracker.cpp + + #Logger + src/log/LogUtils.cpp + src/log/LogSinks.cpp + + + src/qlz/QuickLZ.cpp + src/qlz/QuickLZ_L3.cpp + src/qlz/QuickLZ_L1.cpp + + src/query/command2.cpp + src/query/Command.cpp + src/query/escape.cpp + + src/protocol/Packet.cpp + src/protocol/buffers.cpp + src/protocol/buffers_allocator_c.cpp + src/PermissionManager.cpp + src/Properties.cpp + src/BasicChannel.cpp + src/Error.cpp + src/protocol/CryptionHandler.cpp + src/protocol/CompressionHandler.cpp + src/Variable.cpp + src/linked_helper.cpp + src/EventLoop.cpp + + src/License.cpp + + src/bbcode/bbcodes.cpp + + src/channel/TreeView.cpp + src/protocol/ringbuffer.cpp + src/protocol/AcknowledgeManager.cpp +) + +set(HEADER_FILES + src/misc/base64.h + src/misc/endianness.h + src/misc/cast.h + src/misc/rnd.h + src/misc/time.h + src/misc/std_unique_ptr.h + src/misc/net.h + src/misc/lambda.h + src/misc/hex.h + src/misc/advanced_mutex.h + src/misc/memtracker.h + + src/log/translation.h + src/log/LogUtils.h + + src/PermissionManager.h + src/protocol/buffers.h + src/protocol/Packet.h + src/Properties.h + src/BasicChannel.h + src/Definitions.h + src/Error.h + src/protocol/CryptionHandler.h + src/Variable.h + src/misc/queue.h + + src/misc/digest.h + + src/bbcode/bbcodes.h + + src/channel/TreeView.h +) + +if(HAVE_SQLITE3) + set(SOURCE_FILES ${SOURCE_FILES} + src/sql/SqlQuery.cpp + src/sql/sqlite/SqliteSQL.cpp + src/sql/mysql/MySQL.cpp + ) + set(HEADER_FILES ${HEADER_FILES} + src/sql/SqlQuery.h + src/sql/sqlite/SqliteSQL.h + src/sql/mysql/MySQL.h + ) +endif() + +if(HAVE_OPEN_SSL) + set(SOURCE_FILES ${SOURCE_FILES} + src/ssl/SSLManager.cpp + ) + set(HEADER_FILES ${HEADER_FILES} + src/ssl/SSLManager.h + ) +endif() + +if (NOT WIN32) + set(SOURCE_FILES ${SOURCE_FILES} + src/misc/TraceUtils.cpp + ) + set(HEADER_FILES ${HEADER_FILES} + + ) +endif () + +add_library(TeaSpeak STATIC ${SOURCE_FILES} ${HEADER_FILES}) +set(TEST_LIBRARIES + sqlite3 + ${LIBRARY_PATH_THREAD_POOL} #Static + ${LIBRARY_PATH_TERMINAL} #Static + ${LIBRARY_PATH_VARIBALES} + ${LIBRARY_PATH_YAML} + pthread + stdc++fs + ${TOM_LIBRARIES} + ${LIBEVENT_PATH}/libevent.a + ${LIBEVENT_PATH}/libevent_pthreads.a + + ${LIBRARY_PATH_ED255} + ${LIBRARY_PATH_JSON} + ${LIBRARY_TOM_CRYPT} + ${LIBRARY_TOM_MATH} + ${LIBRARY_PATH_JDBC} + + ${LIBRARY_PATH_DATA_PIPES} + ${LIBRARY_PATH_BORINGSSL_SSL} + ${LIBRARY_PATH_BORINGSSL_CRYPTO} #Crypto must be linked after +) + +include_directories(src/) + +add_executable(RingTest test/RingTest.cpp ${SOURCE_FILES}) +target_link_libraries(RingTest ${TEST_LIBRARIES}) +if(NOT WIN32) + add_executable(CommandTest ${SOURCE_FILES} ${HEADER_FILES} test/CommandTest.cpp src/log/LogSinks.cpp src/log/LogSinks.h) + target_link_libraries(CommandTest ${TEST_LIBRARIES}) + + add_executable(WebsocketTest ${SOURCE_FILES} ${HEADER_FILES} test/WSSTest.cpp src/log/LogSinks.cpp src/log/LogSinks.h) + target_link_libraries(WebsocketTest ${TEST_LIBRARIES}) + + add_executable(SQLTest ${SOURCE_FILES} ${HEADER_FILES} test/SQLTest.cpp src/log/LogSinks.cpp src/log/LogSinks.h) + target_link_libraries(SQLTest ${TEST_LIBRARIES}) + + add_executable(ChannelTest ${SOURCE_FILES} ${HEADER_FILES} test/ChannelTest.cpp src/log/LogSinks.cpp src/log/LogSinks.h) + target_link_libraries(ChannelTest ${TEST_LIBRARIES}) + + add_executable(EndianessTest ${SOURCE_FILES} ${HEADER_FILES} test/EndianessTest.cpp src/log/LogSinks.cpp src/log/LogSinks.h) + target_link_libraries(EndianessTest ${TEST_LIBRARIES}) + + include_directories(/usr/local/include/breakpad) + add_executable(CrashTest test/CrashTest.cpp ${SOURCE_FILES}) + target_link_libraries(CrashTest ${TEST_LIBRARIES}) + + add_executable(PorpertyTest test/PropertyTest.cpp ${SOURCE_FILES}) + target_link_libraries(PorpertyTest ${TEST_LIBRARIES}) + + add_executable(BBTest test/BBTest.cpp ${SOURCE_FILES}) + target_link_libraries(BBTest ${TEST_LIBRARIES}) + + add_executable(LinkedTest test/LinkedTest.cpp ${SOURCE_FILES}) + target_link_libraries(LinkedTest ${TEST_LIBRARIES}) +endif() \ No newline at end of file diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..e79de26 --- /dev/null +++ b/main.cpp @@ -0,0 +1,73 @@ +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + + +using namespace std; +using namespace sql; + +void testSql(){ + sql::sqlite::SqliteManager handle; + auto res = handle.connect("test.sqlite"); + if(!res) { + cerr << "Failed to open file. error: " << res << endl; + return 0; + } + + res = sql::command(&handle, "CREATE TABLE IF NOT EXISTS `test` (`key` TEXT, `value` TEXT);").execute(); + if(!res) { + cerr << "Failed to execute command. error: " << res << endl; + return 0; + } + + res = sql::command(&handle, "SELECT * FROM `sqlite_master` WHERE `name` = :name", variable{":name", "test"}).query([](void*, int length, string* values, string* names) { + cout << " | "; + for(int i = 0; i < length; i++) + cout << values[i] << " | "; + cout << endl; + return 0; + }, (void*) nullptr); + + res = sql::command(&handle, "SELECT * FROM `sqlite_master` WHERE `name` = :name", variable{":name", "test"}).query([](void*, int length, string* values, string* names) { + cout << " | "; + for(int i = 0; i < length; i++) + cout << values[i] << " | "; + cout << endl; + return 0; + }, nullptr); + + res = sql::command(&handle, "SELECT * FROM `sqlite_master` WHERE `name` = :name", variable{":name", "test"}).query([](void*, int length, string* values, string* names) { + cout << " | "; + for(int i = 0; i < length; i++) + cout << values[i] << " | "; + cout << endl; + return 0; + }, (int*) nullptr); + + cout << "Res: " << res << endl; +} + +int main(int, char**) { + sql::Driver* driver = get_driver_instance(); + driver->connect(SQLString("tcp://127.0.0.1:3306"), SQLString("root"), SQLString("markus")); + std::shared_ptr con(driver->connect("", "root", "markus")); + if(!con->isValid()) { + cerr << "Invalid connection!" << endl; + } + con->setSchema("test"); + //std::shared_ptr stmt(con->createStatement()); + /* + sql::mysql::MySQLManager manager; + manager.connect("hellop"); +*/ + + return 0; +} \ No newline at end of file diff --git a/src/BasicChannel.cpp b/src/BasicChannel.cpp new file mode 100644 index 0000000..7125351 --- /dev/null +++ b/src/BasicChannel.cpp @@ -0,0 +1,291 @@ +#include "log/LogUtils.h" +#include +#include +#include +#include +#include +#include "query/Command.h" +#include "misc/digest.h" +#include "BasicChannel.h" +#include "query/escape.h" + +using namespace std; +using namespace std::chrono; +using namespace ts; + +BasicChannel::BasicChannel(ChannelId parentId, ChannelId channelId) { + this->setProperties(make_shared()); + + this->_properties->register_property_type(); + this->properties()[property::CHANNEL_ID] = channelId; + this->properties()[property::CHANNEL_PID] = parentId; +} + +void BasicChannel::setPermissionManager(const std::shared_ptr& manager) { + auto handler = [&](std::shared_ptr perm){ + if(perm->type->type == permission::i_icon_id) + this->properties()[property::CHANNEL_ICON_ID] = (IconId) (perm->hasValue() ? perm->value : 0); + else if(perm->type->type == permission::i_client_needed_talk_power) { + this->properties()[property::CHANNEL_NEEDED_TALK_POWER] = (perm->hasValue() ? perm->value : 0); + } + }; + manager->registerUpdateHandler(handler); + //Update channel properties + for(const auto& perm : manager->getPermission(permission::i_icon_id, nullptr)) handler(perm); + for(const auto& perm : manager->getPermission(permission::i_client_needed_talk_power, nullptr)) handler(perm); + + this->_permissions = manager; + /* + this->_permissions->registerPermission(permission::i_channel_needed_permission_modify_power, 0, nullptr, PERM_FLAG_PUBLIC); + this->_permissions->registerPermission(permission::i_channel_needed_join_power, 0, nullptr, PERM_FLAG_PUBLIC); + this->_permissions->registerPermission(permission::i_channel_needed_subscribe_power, 0, nullptr, PERM_FLAG_PUBLIC); + this->_permissions->registerPermission(permission::i_channel_needed_description_view_power, 0, nullptr, PERM_FLAG_PUBLIC); + this->_permissions->registerPermission(permission::i_channel_needed_modify_power, 0, nullptr, PERM_FLAG_PUBLIC); + this->_permissions->registerPermission(permission::i_channel_needed_delete_power, 0, nullptr, PERM_FLAG_PUBLIC); + this->_permissions->registerPermission(permission::i_ft_needed_file_browse_power, 0, nullptr, PERM_FLAG_PUBLIC); + this->_permissions->registerPermission(permission::i_ft_needed_file_upload_power, 0, nullptr, PERM_FLAG_PUBLIC); + this->_permissions->registerPermission(permission::i_ft_needed_file_download_power, 0, nullptr, PERM_FLAG_PUBLIC); + this->_permissions->registerPermission(permission::i_ft_needed_file_rename_power, 0, nullptr, PERM_FLAG_PUBLIC); + this->_permissions->registerPermission(permission::i_ft_needed_directory_create_power, 0, nullptr, PERM_FLAG_PUBLIC); + this->_permissions->registerPermission(permission::i_icon_id, 0, nullptr, PERM_FLAG_PUBLIC); + */ +} + +void BasicChannel::setProperties(const std::shared_ptr& props) { + if(this->_properties) { + (*props)[property::CHANNEL_ID] = this->channelId(); + (*props)[property::CHANNEL_PID] = this->properties()[property::CHANNEL_PID].value(); + } + this->_properties = props; + + this->properties().registerNotifyHandler([&](Property& prop){ + if(prop.type() == property::CHANNEL_FLAG_DEFAULT) + this->properties()[property::CHANNEL_FLAG_PASSWORD] = false; + else if(prop.type() == property::CHANNEL_ID) + this->_channel_id = prop; + else if(prop.type() == property::CHANNEL_ORDER) + this->_channel_order = prop; + }); + + //Update cached variables + if(props->has(property::CHANNEL_ORDER)) this->_channel_order = this->properties()[property::CHANNEL_ORDER]; + else this->_channel_order = 0; + this->_channel_id = this->channelId(); +} + +std::shared_ptr BasicChannel::parent() { + auto link_lock = this->_link.lock(); + if(!link_lock) + return nullptr; + + auto parent_lock = link_lock->parent.lock(); + if(!parent_lock) + return nullptr; + + return dynamic_pointer_cast(parent_lock->entry); +} + +BasicChannel::BasicChannel(std::shared_ptr parent, ChannelId channelId) : BasicChannel(parent ? parent->channelId() : 0, channelId) { } + +BasicChannel::~BasicChannel() { } + +ChannelType::ChannelType BasicChannel::channelType() { + if (this->properties()[property::CHANNEL_FLAG_PERMANENT].as()) return ChannelType::ChannelType::permanent; + else if (this->properties()[property::CHANNEL_FLAG_SEMI_PERMANENT].as()) return ChannelType::ChannelType::semipermanent; + else return ChannelType::ChannelType::temporary; +} + +void BasicChannel::setChannelType(ChannelType::ChannelType type) { + properties()[property::CHANNEL_FLAG_PERMANENT] = type == ChannelType::permanent; + properties()[property::CHANNEL_FLAG_SEMI_PERMANENT] = type == ChannelType::semipermanent; +} + +bool BasicChannel::passwordMatch(std::string password, bool hashed) { + if (!this->properties()[property::CHANNEL_FLAG_PASSWORD].as() || this->properties()[property::CHANNEL_PASSWORD].value().empty()) return true; + if (password.empty()) return false; + + if(this->properties()[property::CHANNEL_PASSWORD].as() == password) + return true; + + password = base64::encode(digest::sha1(password)); + return this->properties()[property::CHANNEL_PASSWORD].as() == password; +} + +int BasicChannel::emptySince() { + if (!properties().hasProperty(property::CHANNEL_LAST_LEFT)) + return 0; + + time_point lastLeft = time_point() + milliseconds(properties()[property::CHANNEL_LAST_LEFT].as()); + return duration_cast(system_clock::now() - lastLeft).count(); +} + +void BasicChannel::setLinkedHandle(const std::weak_ptr &ptr) { + TreeEntry::setLinkedHandle(ptr); + this->_link = ptr; +} + +ChannelId BasicChannel::channelId() const { + return this->_channel_id; +} + +ChannelId BasicChannel::previousChannelId() const { + return this->_channel_order; +} + +void BasicChannel::setPreviousChannelId(ChannelId id) { + this->properties()[property::CHANNEL_ORDER] = id; +} + +void BasicChannel::setParentChannelId(ChannelId id) { + this->properties()[property::CHANNEL_PID] = id; +} + +BasicChannelTree::BasicChannelTree() { } + +BasicChannelTree::~BasicChannelTree() { } + +std::deque> BasicChannelTree::channels(const shared_ptr &root, int deep) { + auto result = root ? this->entries_sub(root, deep) : this->entries(root, deep); + + std::deque> res; + for(const auto& entry : result) { + auto e = dynamic_pointer_cast(entry); + if(e) res.push_back(e); + } + return res; +} + +shared_ptr BasicChannelTree::findLinkedChannel(ts::ChannelId channelId) { + return this->find_linked_entry(channelId); +} + +std::shared_ptr BasicChannelTree::findChannel(ChannelId channelId) { + if(channelId == 0) return nullptr; /* we start counting at 1! */ + return this->findChannel(channelId, this->channels()); +} + +std::shared_ptr BasicChannelTree::findChannel(ChannelId channelId, std::deque> avariable) { + for (auto elm : avariable) + if (elm->channelId() == channelId) + return elm; + return nullptr; +} + + +std::shared_ptr BasicChannelTree::findChannel(const std::string &name, const shared_ptr &layer) { + for (auto elm : this->channels()) + if (elm->name() == name && elm->parent() == layer) + return elm; + return nullptr; +} + +std::shared_ptr BasicChannelTree::findChannelByPath(const std::string &path) { + int maxChannelDeep = 255; //FIXME + + std::deque entries; + size_t index = 0; + do { + auto found = path.find('/', index); + if (found == index) { + index++; + continue; + } else if (found < path.length() && path[found - 1] == '\\') { + index++; + continue; + } + entries.push_back(query::unescape(path.substr(index, found - index), false)); + index = found + 1; + } while (index != 0 && entries.size() <= maxChannelDeep); + + debugMessage("Parsed channel path '" + path + "'. Entries:"); + std::shared_ptr current = nullptr; + for (const auto &name : entries) { + current = this->findChannel(name, current); + debugMessage(" - '" + name + "' (" + (current ? "found" : "unknown") + ")"); + if (!current) break; + } + return current; +} + +std::shared_ptr BasicChannelTree::createChannel(ChannelId parentId, ChannelId orderId, const string &name) { + auto parent = this->findChannel(parentId); + if (!parent && parentId != 0) return nullptr; + + auto order = this->findChannel(orderId); + if (!order && orderId != 0) return nullptr; + + + if (this->findChannel(name, parent)) return nullptr; + + auto channelId = generateChannelId(); + if (channelId < 1) { + logger::logger(0)->error("Cant generate channel id."); + return nullptr; + } + + auto channel = this->allocateChannel(parent, channelId); + channel->properties()[property::CHANNEL_NAME] = name; + + if(!this->insert_entry(channel, parent, order)) return nullptr; + return channel; +} + +deque> BasicChannelTree::delete_channel_root(const std::shared_ptr &root) { + deque> result; + /* + for(const auto& channels : this->channels(root)) { + std::lock_guard lock(channels->deleteLock); + channels->deleted = true; + } + */ + auto channels = this->delete_entry(root); + for(const auto& channel : channels) { + if(!channel) continue; + auto c = dynamic_pointer_cast(channel); + assert(c); + this->on_channel_entry_deleted(c); + result.push_back(c); + } + + return result; +} + +bool BasicChannelTree::setDefaultChannel(const shared_ptr &ch) { + if (!ch) return false; + for (const auto &elm : this->channels()) + elm->properties()[property::CHANNEL_FLAG_DEFAULT] = false; + ch->properties()[property::CHANNEL_FLAG_DEFAULT] = true; + return true; +} + +std::shared_ptr BasicChannelTree::getDefaultChannel() { + for (auto elm : this->channels()) + if (elm->properties()[property::CHANNEL_FLAG_DEFAULT].as()) return elm; + return nullptr; +} + +void BasicChannelTree::on_channel_entry_deleted(const shared_ptr &ptr) { + +} + +std::shared_ptr BasicChannelTree::allocateChannel(const shared_ptr &parent, ChannelId channelId) { + return std::make_shared(parent, channelId); +} + +ChannelId BasicChannelTree::generateChannelId() { + ChannelId currentChannelId = 1; + while (this->findChannel(currentChannelId)) currentChannelId++; + return currentChannelId; +} + +void BasicChannelTree::printChannelTree(std::function fn) { + this->print_tree([&](const std::shared_ptr& t_entry, int deep) { + auto entry = dynamic_pointer_cast(t_entry); + assert(entry); + + string prefix; + while(deep-- > 0) prefix += " "; + + fn(prefix + " - " + to_string(entry->channelId()) + " | " + to_string(entry->previousChannelId()) + " (" + entry->name() + ")"); + }); +} \ No newline at end of file diff --git a/src/BasicChannel.h b/src/BasicChannel.h new file mode 100644 index 0000000..7da4ef9 --- /dev/null +++ b/src/BasicChannel.h @@ -0,0 +1,116 @@ +#pragma once + +#include +#include +#include "misc/advanced_mutex.h" +#include "channel/TreeView.h" +#include "Definitions.h" +#include "Properties.h" +#include "PermissionManager.h" + +namespace ts { + + class BasicChannel; + class BasicChannelTree; + + namespace ChannelType { + enum ChannelType { + permanent, + semipermanent, + temporary + }; + } + + struct BasicChannelEntry { + BasicChannelEntry* previus{}; + std::shared_ptr current = nullptr; + BasicChannelEntry* next{}; + + BasicChannelEntry* children{}; + + ~BasicChannelEntry() { + this->current = nullptr; + } + }; + + class BasicChannel : public TreeEntry { + public: + BasicChannel(ChannelId parentId, ChannelId channelId); + BasicChannel(std::shared_ptr parent, ChannelId channelId); + + virtual ~BasicChannel(); + + bool hasParent(){ return !!this->parent(); } + std::shared_ptr parent(); + + inline std::string name(){ return properties()[property::CHANNEL_NAME]; } + inline ChannelId channelOrder(){ return this->previousChannelId(); } + inline Properties& properties() const { return *this->_properties; } + + ChannelType::ChannelType channelType(); + void setChannelType(ChannelType::ChannelType); + + bool passwordMatch(std::string password, bool hashed = false); + bool defaultChannel() { return (*this->_properties)[property::CHANNEL_FLAG_DEFAULT]; } + int emptySince(); + + inline std::chrono::system_clock::time_point createdTimestamp() { + return std::chrono::system_clock::time_point() + std::chrono::milliseconds(this->properties()[property::CHANNEL_CREATED_AT].as()); + } + + inline std::shared_ptr permissions(){ return this->_permissions; } + virtual void setPermissionManager(const std::shared_ptr&); + virtual void setProperties(const std::shared_ptr&); + protected: + public: + ChannelId channelId() const override; + ChannelId previousChannelId() const override; + void setPreviousChannelId(ChannelId id) override; + void setParentChannelId(ChannelId id) override; + void setLinkedHandle(const std::weak_ptr &) override; + protected: + std::weak_ptr _link; + std::shared_ptr _properties; + std::shared_ptr _permissions; + + ChannelId _channel_order = 0; + ChannelId _channel_id = 0; + }; + + class BasicChannelTree : public TreeView { + public: + BasicChannelTree(); + virtual ~BasicChannelTree(); + + size_t channel_count() { return this->entry_count(); } + + std::shared_ptr findLinkedChannel(ChannelId channelId); + std::shared_ptr findChannel(ChannelId channelId); + std::shared_ptr findChannel(ChannelId channelId, std::deque> avariable); + std::shared_ptr findChannel(const std::string &name, const std::shared_ptr &layer); + std::shared_ptr findChannelByPath(const std::string& path); + //std::deque> topChannels(); + std::deque> channels(const std::shared_ptr &root = nullptr, int deep = -1); + + virtual std::shared_ptr createChannel(ChannelId parentId, ChannelId orderId, const std::string &name); + virtual std::deque> delete_channel_root(const std::shared_ptr &); + + inline bool change_order(const std::shared_ptr& channel, ChannelId channelId) { + return this->move_entry(channel, channel->parent(), this->find_entry(channelId)); + } + inline bool move_channel(const std::shared_ptr& channel, const std::shared_ptr& parent, const std::shared_ptr& order) { + return this->move_entry(channel, parent, order); + } + + bool setDefaultChannel(const std::shared_ptr &); + std::shared_ptr getDefaultChannel(); + + void printChannelTree(std::function = [](std::string msg) { std::cout << msg << std::endl; }); + protected: + virtual ChannelId generateChannelId(); + virtual void on_channel_entry_deleted(const std::shared_ptr &); + virtual std::shared_ptr allocateChannel(const std::shared_ptr &parent, ChannelId channelId); + }; +} + +DEFINE_TRANSFORMS(ts::ChannelType::ChannelType, uint8_t); \ No newline at end of file diff --git a/src/Definitions.h b/src/Definitions.h new file mode 100644 index 0000000..8a0729a --- /dev/null +++ b/src/Definitions.h @@ -0,0 +1,155 @@ +#pragma once + +#include "Variable.h" +#include "converters/converter.h" + +#define tsclient std::shared_ptr +#define tsclientmusic std::shared_ptr + +namespace ts { + typedef uint16_t ServerId; + typedef int32_t OptionalServerId; + constexpr auto EmptyServerId = (OptionalServerId) -1; + + typedef uint64_t ClientDbId; + typedef uint16_t ClientId; + typedef std::string ClientUid; + typedef uint64_t ChannelId; + typedef uint32_t GroupId; + typedef uint16_t FloodPoints; + typedef uint64_t BanId; + typedef uint32_t LetterId; + typedef uint32_t SongId; + typedef uint32_t IconId; + typedef uint32_t PlaylistId; + + //0 = No License | 1 = Hosting | 2 = Offline license | 3 = Non-profit license | 4 = Unknown | 5 = Unknown + //Everything over 5 is no lt (Client >= 1.3.6) + enum LicenseType: uint8_t { + _LicenseType_MIN, + LICENSE_NONE = _LicenseType_MIN, + LICENSE_HOSTING, + LICENSE_OFFLINE, + LICENSE_NPL, //3 + LICENSE_UNKNOWN, + LICENSE_PLACEHOLDER, + LICENSE_AUTOMATIC_SERVER, + LICENSE_AUTOMATIC_INSTANCE, + _LicenseType_MAX = LICENSE_AUTOMATIC_INSTANCE, + }; + + enum PluginTargetMode : uint8_t { + PLUGINCMD_CURRENT_CHANNEL = 0, //send plugincmd to all clients in current channel + PLUGINCMD_SERVER, //send plugincmd to all clients on server + PLUGINCMD_CLIENT, //send plugincmd to all given manager ids + PLUGINCMD_SUBSCRIBED_CLIENTS, //send plugincmd to all subscribed clients in current channel + + PLUGINCMD_SEND_COMMAND = 0xF0 + }; + + enum ViewReasonId: uint8_t { + VREASON_USER_ACTION = 0, + VREASON_MOVED = 1, + VREASON_SYSTEM = 2, + VREASON_TIMEOUT = 3, + VREASON_CHANNEL_KICK = 4, + VREASON_SERVER_KICK = 5, + VREASON_BAN = 6, + VREASON_SERVER_STOPPED = 7, + VREASON_SERVER_LEFT = 8, + VREASON_CHANNEL_UPDATED = 9, + VREASON_EDITED = 10, + VREASON_SERVER_SHUTDOWN = 11 + }; + + struct ViewReasonSystemT { }; + static constexpr auto ViewReasonSystem = ViewReasonSystemT{}; + + struct ViewReasonServerLeftT { }; + static constexpr auto ViewReasonServerLeft = ViewReasonServerLeftT{}; + + enum ChatMessageMode : uint8_t { + TEXTMODE_PRIVATE = 1, + TEXTMODE_CHANNEL = 2, + TEXTMODE_SERVER = 3 + }; + + namespace server { + enum ClientType { + CLIENT_TEAMSPEAK, + CLIENT_QUERY, + CLIENT_INTERNAL, + CLIENT_WEB, + CLIENT_MUSIC, + CLIENT_TEASPEAK + }; + + enum ConnectionState { + UNKNWON, + INIT_LOW, //Web -> WS Handschake + INIT_HIGH, //Web -> Auth + CONNECTED, + DISCONNECTING, + DISCONNECTED + }; + } + + enum QueryEventGroup : int { + QEVENTGROUP_MIN = 0, + QEVENTGROUP_SERVER = 0, + QEVENTGROUP_CLIENT_MISC = 1, + QEVENTGROUP_CLIENT_GROUPS = 2, + QEVENTGROUP_CLIENT_VIEW = 3, + QEVENTGROUP_CHAT = 4, + QEVENTGROUP_CHANNEL = 5, + QEVENTGROUP_MUSIC = 6, + QEVENTGROUP_MAX = 7 + }; + + enum QueryEventSpecifier { + QEVENTSPECIFIER_MIN = 0, + QEVENTSPECIFIER_SERVER_EDIT = 0, + + QEVENTSPECIFIER_CLIENT_MISC_POKE = 0, + QEVENTSPECIFIER_CLIENT_MISC_UPDATE = 1, + QEVENTSPECIFIER_CLIENT_MISC_COMMAND = 2, + + QEVENTSPECIFIER_CLIENT_GROUPS_ADD = 0, + QEVENTSPECIFIER_CLIENT_GROUPS_REMOVE = 1, + QEVENTSPECIFIER_CLIENT_GROUPS_CHANNEL_CHANGED = 2, + + QEVENTSPECIFIER_CLIENT_VIEW_JOIN = 0, + QEVENTSPECIFIER_CLIENT_VIEW_SWITCH = 1, + QEVENTSPECIFIER_CLIENT_VIEW_LEAVE = 2, + + QEVENTSPECIFIER_CHAT_COMPOSING = 0, + QEVENTSPECIFIER_CHAT_MESSAGE_SERVER = 1, + QEVENTSPECIFIER_CHAT_MESSAGE_CHANNEL = 2, + QEVENTSPECIFIER_CHAT_MESSAGE_PRIVATE = 3, + QEVENTSPECIFIER_CHAT_CLOSED = 4, + + QEVENTSPECIFIER_CHANNEL_CREATE = 0, + QEVENTSPECIFIER_CHANNEL_MOVE = 1, + QEVENTSPECIFIER_CHANNEL_EDIT = 2, + QEVENTSPECIFIER_CHANNEL_DESC_EDIT = 3, + QEVENTSPECIFIER_CHANNEL_PASSWORD_EDIT = 4, + QEVENTSPECIFIER_CHANNEL_DELETED = 5, + + QEVENTSPECIFIER_MUSIC_QUEUE = 0, + QEVENTSPECIFIER_MUSIC_PLAYER = 1, + + QEVENTSPECIFIER_MAX = 6, + }; +} + + +#define DEFINE_TRANSFORMS(a, b) \ + DEFINE_CONVERTER_ENUM(a, b); \ + DEFINE_VARIABLE_TRANSFORM_ENUM(a, b); + +DEFINE_TRANSFORMS(ts::server::ConnectionState, uint8_t); +DEFINE_TRANSFORMS(ts::server::ClientType, uint8_t); +DEFINE_TRANSFORMS(ts::LicenseType, uint8_t); +DEFINE_TRANSFORMS(ts::PluginTargetMode, uint8_t); +DEFINE_TRANSFORMS(ts::ViewReasonId, uint8_t); +DEFINE_TRANSFORMS(ts::ChatMessageMode, uint8_t); \ No newline at end of file diff --git a/src/Error.cpp b/src/Error.cpp new file mode 100644 index 0000000..43ac351 --- /dev/null +++ b/src/Error.cpp @@ -0,0 +1,176 @@ +// +// Created by wolverindev on 17.10.17. +// + +#include "Error.h" + +using namespace ts; + +const std::vector ts::avariableErrors = { + {0x0000, "ok" , "ok" }, + {0x0001, "undefined" , "undefined error" }, + {0x0002, "not_implemented" , "not implemented" }, + {0x0005, "lib_time_limit_reached" , "library time limit reached" }, + {0x0100, "command_not_found" , "command not found" }, + {0x0101, "unable_to_bind_network_port" , "unable to bind network port" }, + {0x0102, "no_network_port_available" , "no network port available" }, + {0x0200, "client_invalid_id" , "invalid clientID" }, + {0x0201, "client_nickname_inuse" , "nickname is already in use" }, + {0x0202, "" , "invalid error code" }, + {0x0203, "client_protocol_limit_reached" , "max clients protocol limit reached" }, + {0x0204, "client_invalid_type" , "invalid client type" }, + {0x0205, "client_already_subscribed" , "already subscribed" }, + {0x0206, "client_not_logged_in" , "not logged in" }, + {0x0207, "client_could_not_validate_identity" , "could not validate client identity" }, + {0x0208, "client_invalid_password" , "invalid loginname or password" }, + {0x0209, "client_too_many_clones_connected" , "too many clones already connected" }, + {0x020A, "client_version_outdated" , "client version outdated, please update" }, + {0x020B, "client_is_online" , "client is online" }, + {0x020C, "client_is_flooding" , "client is flooding" }, + {0x020D, "client_hacked" , "client is modified" }, + {0x020E, "client_cannot_verify_now" , "can not verify client at this moment" }, + {0x020F, "client_login_not_permitted" , "client is not permitted to log in" }, + {0x0210, "client_not_subscribed" , "client is not subscribed to the channel" }, + {0x0211, "client_unknown" , "client is not known" }, + {0x0300, "channel_invalid_id" , "invalid channelID" }, + {0x0301, "channel_protocol_limit_reached" , "max channels protocol limit reached" }, + {0x0302, "channel_already_in" , "already member of channel" }, + {0x0303, "channel_name_inuse" , "channel name is already in use" }, + {0x0304, "channel_not_empty" , "channel not empty" }, + {0x0305, "channel_can_not_delete_default" , "can not delete default channel" }, + {0x0306, "channel_default_require_permanent" , "default channel requires permanent" }, + {0x0307, "channel_invalid_flags" , "invalid channel flags" }, + {0x0308, "channel_parent_not_permanent" , "permanent channel can not be child of non permanent channel"}, + {0x0309, "channel_maxclients_reached" , "channel maxclient reached" }, + {0x030A, "channel_maxfamily_reached" , "channel maxfamily reached" }, + {0x030B, "channel_invalid_order" , "invalid channel order" }, + {0x030C, "channel_no_filetransfer_supported" , "channel does not support filetransfers" }, + {0x030D, "channel_invalid_password" , "invalid channel password" }, + {0x030E, "channel_is_private_channel" , "channel is private channel" }, + {0x030F, "channel_invalid_security_hash" , "invalid security hash supplied by client" }, + {0x0310, "channel_is_deleted" , "target channel is deleted" }, + {0x0400, "server_invalid_id" , "invalid serverID" }, + {0x0401, "server_running" , "server is running" }, + {0x0402, "server_is_shutting_down" , "server is shutting down" }, + {0x0403, "server_maxclients_reached" , "server maxclient reached" }, + {0x0404, "server_invalid_password" , "invalid server password" }, + {0x0405, "server_deployment_active" , "deployment active" }, + {0x0406, "server_unable_to_stop_own_server" , "unable to stop own server in your connection class" }, + {0x0407, "server_is_virtual" , "server is virtual" }, + {0x0408, "server_wrong_machineid" , "server wrong machineID" }, + {0x0409, "server_is_not_running" , "server is not running" }, + {0x040A, "server_is_booting" , "server is booting up" }, + {0x040B, "server_status_invalid" , "server got an invalid status for this operation" }, + {0x040C, "server_modal_quit" , "server modal quit" }, + {0x040D, "server_version_outdated" , "server version is too old for command" }, + {0x040D, "server_already_joined" , "query client already joined to the server" }, + {0x040E, "server_is_not_shutting_down" , "server isn't shutting down" }, + {0x040F, "server_max_vs_reached" , "You reached the maximal virtual server limit" }, + {0x0500, "sql" , "sql error" }, + {0x0501, "database_empty_result" , "sql empty result set" }, + {0x0502, "database_duplicate_entry" , "sql duplicate entry" }, + {0x0503, "database_no_modifications" , "sql no modifications" }, + {0x0504, "database_constraint" , "sql invalid constraint" }, + {0x0505, "database_reinvoke" , "sql reinvoke command" }, + {0x0600, "parameter_quote" , "invalid quote" }, + {0x0601, "parameter_invalid_count" , "invalid parameter count" }, + {0x0602, "parameter_invalid" , "invalid parameter" }, + {0x0603, "parameter_not_found" , "parameter not found" }, + {0x0604, "parameter_convert" , "convert error" }, + {0x0605, "parameter_invalid_size" , "invalid parameter size" }, + {0x0606, "parameter_missing" , "missing required parameter" }, + {0x0607, "parameter_checksum" , "invalid checksum" }, + {0x0700, "vs_critical" , "virtual server got a critical error" }, + {0x0701, "connection_lost" , "Connection lost" }, + {0x0702, "not_connected" , "not connected" }, + {0x0703, "no_cached_connection_info" , "no cached connection info" }, + {0x0704, "currently_not_possible" , "currently not possible" }, + {0x0705, "failed_connection_initialisation" , "failed connection initialization" }, + {0x0706, "could_not_resolve_hostname" , "could not resolve hostname" }, + {0x0707, "invalid_server_connection_handler_id" , "invalid server connection handler ID" }, + {0x0708, "could_not_initialise_input_client" , "could not initialize Input client" }, + {0x0709, "clientlibrary_not_initialised" , "client library not initialized" }, + {0x070A, "serverlibrary_not_initialised" , "server library not initialized" }, + {0x070B, "whisper_too_many_targets" , "too many whisper targets" }, + {0x070C, "whisper_no_targets" , "no whisper targets found" }, + {0x0800, "file_invalid_name" , "invalid file name" }, + {0x0801, "file_invalid_permissions" , "invalid file permissions" }, + {0x0802, "file_already_exists" , "file already exists" }, + {0x0803, "file_not_found" , "file not found" }, + {0x0804, "file_io_error" , "file input/output error" }, + {0x0805, "file_invalid_transfer_id" , "invalid file transfer ID" }, + {0x0806, "file_invalid_path" , "invalid file path" }, + {0x0807, "file_no_files_available" , "no files available" }, + {0x0808, "file_overwrite_excludes_resume" , "overwrite excludes resume" }, + {0x0809, "file_invalid_size" , "invalid file size" }, + {0x080A, "file_already_in_use" , "file already in use" }, + {0x080B, "file_could_not_open_connection" , "could not open file transfer connection" }, + {0x080C, "file_no_space_left_on_device" , "no space left on device (disk full?)" }, + {0x080D, "file_exceeds_file_system_maximum_size", "file exceeds file system's maximum file size" }, + {0x080E, "file_transfer_connection_timeout" , "file transfer connection timeout" }, + {0x080F, "file_connection_lost" , "lost file transfer connection" }, + {0x0810, "file_exceeds_supplied_size" , "file exceeds supplied file size" }, + {0x0811, "file_transfer_complete" , "file transfer complete" }, + {0x0812, "file_transfer_canceled" , "file transfer canceled" }, + {0x0813, "file_transfer_interrupted" , "file transfer interrupted" }, + {0x0814, "file_transfer_server_quota_exceeded" , "file transfer server quota exceeded" }, + {0x0815, "file_transfer_client_quota_exceeded" , "file transfer client quota exceeded" }, + {0x0816, "file_transfer_reset" , "file transfer reset" }, + {0x0817, "file_transfer_limit_reached" , "file transfer limit reached" }, + + + {0x0A08, "server_insufficeient_permissions" , "insufficient client permissions" }, + {0x0B01, "accounting_slot_limit_reached" , "max slot limit reached" }, + {0x0D01, "server_connect_banned" , "connection failed, you are banned" }, + {0x0D03, "ban_flooding" , "flood ban" }, + + {0x0F00, "token_invalid_id" , "invalid privilege key" }, + + {0x1001, "web_handshake_invalid" , "Invalid handshake" }, + {0x1001, "web_handshake_unsupported" , "Handshake intention unsupported" }, + {0x1002, "web_handshake_identity_unsupported" , "Handshake identity unsupported" }, + {0x1002, "web_handshake_identity_proof_failed" , "Identity proof failed" }, + + {0x1100, "music_invalid_id" , "invalid botID" }, + {0x1101, "music_limit_reached" , "Server music bot limit is reached" }, + {0x1102, "music_client_limit_reached" , "Client music bot limit is reached" }, + {0x1103, "music_invalid_player_state" , "Invalid player state" }, + {0x1104, "music_invalid_action" , "Invalid action" }, + {0x1105, "music_no_player" , "Missing player instance" }, + {0x1105, "music_disabled" , "Music bots have been disabled" }, + + {0x2100, "playlist_invalid_id" , "invalid playlist id" }, + {0x2101, "playlist_invalid_song_id" , "invalid playlist song id" }, + {0x2102, "playlist_already_in_use" , "playlist is already used by another bot" }, + {0x2103, "playlist_is_in_use" , "playlist is used by another bot" }, + + {0x1200, "query_not_exists" , "query account does not exists" }, + {0x1201, "query_already_exists" , "query account already exists" }, + + + {0xFFFF, "custom_error" , "costume" }, +}; +ErrorType ErrorType::Success = avariableErrors[0]; +ErrorType ErrorType::Costume = findError("custom_error"); +ErrorType ErrorType::VSError = findError("vs_critical"); +ErrorType ErrorType::DBEmpty = findError("database_empty_result"); + +ErrorType ts::findError(uint16_t errorId){ + for(auto elm : avariableErrors) + if(elm.errorId == errorId) return elm; + return ErrorType{errorId, "undefined", "undefined"}; +} + +ErrorType ts::findError(std::string key){ + for(auto elm : avariableErrors) + if(elm.name == key) return elm; + return ErrorType{1, key, "undefined"}; +} + +CommandResult CommandResult::Success = {avariableErrors[0], ""}; +CommandResult CommandResult::NotImplemented = {avariableErrors[2], ""}; + +CommandResultPermissionError::CommandResultPermissionError(permission::PermissionType error, const std::string &extraMsg) : CommandResult(findError(0x0A08), "") { + this->extraProperties["failed_permid"] = std::to_string((int16_t) error); + this->_type = PERM_ERROR; +} \ No newline at end of file diff --git a/src/Error.h b/src/Error.h new file mode 100644 index 0000000..3cccc55 --- /dev/null +++ b/src/Error.h @@ -0,0 +1,333 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace ts { + namespace permission { + enum PermissionType : uint16_t; + } + + struct error { + enum type : uint16_t { + ok = 0x0, + undefined = 0x1, + not_implemented = 0x2, + lib_time_limit_reached = 0x5, + command_not_found = 0x100, + unable_to_bind_network_port = 0x101, + no_network_port_available = 0x102, + client_invalid_id = 0x200, + client_nickname_inuse = 0x201, + invalid_error_code = 0x202, + client_protocol_limit_reached = 0x203, + client_invalid_type = 0x204, + client_already_subscribed = 0x205, + client_not_logged_in = 0x206, + client_could_not_validate_identity = 0x207, + client_invalid_password = 0x208, + client_too_many_clones_connected = 0x209, + client_version_outdated = 0x20a, + client_is_online = 0x20b, + client_is_flooding = 0x20c, + client_hacked = 0x20d, + client_cannot_verify_now = 0x20e, + client_login_not_permitted = 0x20f, + client_not_subscribed = 0x210, + channel_invalid_id = 0x300, + channel_protocol_limit_reached = 0x301, + channel_already_in = 0x302, + channel_name_inuse = 0x303, + channel_not_empty = 0x304, + channel_can_not_delete_default = 0x305, + channel_default_require_permanent = 0x306, + channel_invalid_flags = 0x307, + channel_parent_not_permanent = 0x308, + channel_maxclients_reached = 0x309, + channel_maxfamily_reached = 0x30a, + channel_invalid_order = 0x30b, + channel_no_filetransfer_supported = 0x30c, + channel_invalid_password = 0x30d, + channel_is_private_channel = 0x30e, + channel_invalid_security_hash = 0x30f, + channel_is_deleted = 0x310, + server_invalid_id = 0x400, + server_running = 0x401, + server_is_shutting_down = 0x402, + server_maxclients_reached = 0x403, + server_invalid_password = 0x404, + server_deployment_active = 0x405, + server_unable_to_stop_own_server = 0x406, + server_is_virtual = 0x407, + server_wrong_machineid = 0x408, + server_is_not_running = 0x409, + server_is_booting = 0x40a, + server_status_invalid = 0x40b, + server_modal_quit = 0x40c, + server_version_outdated = 0x40d, + server_already_joined = 0x40d, + server_is_not_shutting_down = 0x40e, + sql = 0x500, + database_empty_result = 0x501, + database_duplicate_entry = 0x502, + database_no_modifications = 0x503, + database_constraint = 0x504, + database_reinvoke = 0x505, + parameter_quote = 0x600, + parameter_invalid_count = 0x601, + parameter_invalid = 0x602, + parameter_not_found = 0x603, + parameter_convert = 0x604, + parameter_invalid_size = 0x605, + parameter_missing = 0x606, + parameter_checksum = 0x607, + vs_critical = 0x700, + connection_lost = 0x701, + not_connected = 0x702, + no_cached_connection_info = 0x703, + currently_not_possible = 0x704, + failed_connection_initialisation = 0x705, + could_not_resolve_hostname = 0x706, + invalid_server_connection_handler_id = 0x707, + could_not_initialise_input_client = 0x708, + clientlibrary_not_initialised = 0x709, + serverlibrary_not_initialised = 0x70a, + whisper_too_many_targets = 0x70b, + whisper_no_targets = 0x70c, + file_invalid_name = 0x800, + file_invalid_permissions = 0x801, + file_already_exists = 0x802, + file_not_found = 0x803, + file_io_error = 0x804, + file_invalid_transfer_id = 0x805, + file_invalid_path = 0x806, + file_no_files_available = 0x807, + file_overwrite_excludes_resume = 0x808, + file_invalid_size = 0x809, + file_already_in_use = 0x80a, + file_could_not_open_connection = 0x80b, + file_no_space_left_on_device = 0x80c, + file_exceeds_file_system_maximum_size = 0x80d, + file_transfer_connection_timeout = 0x80e, + file_connection_lost = 0x80f, + file_exceeds_supplied_size = 0x810, + file_transfer_complete = 0x811, + file_transfer_canceled = 0x812, + file_transfer_interrupted = 0x813, + file_transfer_server_quota_exceeded = 0x814, + file_transfer_client_quota_exceeded = 0x815, + file_transfer_reset = 0x816, + file_transfer_limit_reached = 0x817, + server_insufficeient_permissions = 0xa08, + accounting_slot_limit_reached = 0xb01, + server_connect_banned = 0xd01, + ban_flooding = 0xd03, + token_invalid_id = 0xf00, + web_handshake_invalid = 0x1001, + web_handshake_unsupported = 0x1001, + web_handshake_identity_unsupported = 0x1002, + web_handshake_identity_proof_failed = 0x1002, + music_invalid_id = 0x1100, + music_limit_reached = 0x1101, + music_client_limit_reached = 0x1102, + music_invalid_player_state = 0x1103, + music_invalid_action = 0x1104, + music_no_player = 0x1105, + music_disabled = 0x1105, + playlist_invalid_id = 0x2100, + playlist_invalid_song_id = 0x2101, + playlist_already_in_use = 0x2102, + playlist_is_in_use = 0x2103, + query_not_exists = 0x1200, + query_already_exists = 0x1201, + custom_error = 0xffff + }; + }; + + struct detailed_command_result { + error::type error_id; + std::map extra_properties; + }; + + /* + * return command_result{permission::b_virtualserver_select_godmode}; => movabs rax,0xa08001700000001; ret; (Only if there is no deconstructor!) + * return command_result{permission::b_virtualserver_select_godmode}; => movabs rax,0xa08001700000001; ret; (Only if there is no deconstructor!) + * return command_result{error::vs_critical, "unknown error"}; => To a lot of code + */ + struct command_result { /* fixed size of 8 (64 bits) */ + static constexpr uint64_t MASK_ERROR = ~((uint64_t) 1 << (sizeof(error::type) * 8)); + static constexpr uint64_t MASK_PERMISSION = ~((uint64_t) 1 << (sizeof(permission::PermissionType) * 8)); + + static constexpr uint8_t OFFSET_ERROR = (8 - sizeof(error::type)) * 8; + static constexpr uint8_t OFFSET_PERMISSION = (8 - sizeof(permission::PermissionType) - sizeof(error::type)) * 8; + /* + * First bit is a flag bit which switches between detailed and code mode. + * 0 means detailed (Because then we could interpret data as a ptr (all ptr are 8 bit aligned => 3 zero bits)) + * bits [0;64] => data ptr (needs to be zero at destruction to avoid memory leaks) + * + * 1 means code + * bits [64 - sizeof(error::type);64] => error type | Usually evaluates to [48;64] + * bits [64 - sizeof(error::type) - sizeof(permission::PermissionType);64 - sizeof(error::type)] => permission id | Usually evaluates to [32;48] + */ + uint64_t data; + + /* Test for mode 1 as described before */ + static_assert(sizeof(permission::PermissionType) * 8 + sizeof(error::type) * 8 <= 63); + + inline error::type error_code() { + return (error::type) ((this->data >> OFFSET_ERROR) & MASK_ERROR); + } + + inline error::type permission_id() { + return (error::type) ((this->data >> OFFSET_PERMISSION) & MASK_PERMISSION); + } + + inline detailed_command_result* details() { + return (detailed_command_result*) this->data; + } + + inline bool is_detailed() { + return (this->data & 0x1) == 0; + } + + inline detailed_command_result* release_details() { + auto result = this->details(); + this->data = 0; + return result; + } + +#ifndef NDEBUG /* We dont need to secure that because gcc deduct us to an uint64_t and the only advantage is the mem leak test which is deactivated anyways */ + command_result(command_result&) = delete; + command_result(const command_result&) = delete; + command_result(command_result&& other) : data(other.data) { + other.data = 0; + } +#endif + + command_result() { + this->data = 0; + } + + explicit command_result(permission::PermissionType permission) { + this->data = 0x01; /* the the type to 1 */ + + this->data |= (uint64_t) error::server_insufficeient_permissions << OFFSET_ERROR; + this->data |= (uint64_t) permission << OFFSET_PERMISSION; + } + + + explicit command_result(error::type error) { + this->data = 0x01; /* the the type to 1 */ + + this->data |= (uint64_t) error << (8 - sizeof(error::type)) * 8; + } + + command_result(error::type error, const std::string& message) { + auto details_ptr = new detailed_command_result{}; + assert(((uintptr_t) details_ptr & 0x01) == 0); // must be aligned! + + this->data = (uintptr_t) details_ptr; + + details_ptr->error_id = error; + details_ptr->extra_properties["extra_msg"] = message; + } + +#ifndef NDEBUG + /* if we're not using any debug we dont have to use a deconstructor. A deconstructor prevent a direct uint64_t return as described above */ + ~command_result() { + if((this->data & 0x01) == 0x00) { + // this->details needs to be removed 'till this gets destructed + assert(this->data == 0); + } + } +#endif + + }; + + struct ErrorType { + public: + static ErrorType Success; + static ErrorType Costume; + static ErrorType VSError; + static ErrorType DBEmpty; + + ErrorType(const uint16_t errorId, const std::string &name, const std::string &message) : errorId(errorId), name(name), message(message) {} + + ErrorType(const ErrorType& ref) : errorId(ref.errorId), name(ref.name), message(ref.message) {} + ErrorType(ErrorType& ref) : errorId(ref.errorId), name(ref.name), message(ref.message) {} + ErrorType(ErrorType&& ref) : errorId(ref.errorId), name(std::move(ref.name)), message(std::move(ref.message)) {} + + uint16_t errorId; + std::string name; + std::string message; + + bool operator==(const ErrorType& ref) const { + return errorId == ref.errorId; + } + + bool operator!=(const ErrorType& ref) const { return !operator==(ref); } + + ErrorType& operator=(const ErrorType& ref) { + errorId = ref.errorId; + name = ref.name; + message = ref.message; + return *this; + } + + /** + * @return true if fail + */ + bool operator!() const { + return errorId != 0; + } + }; + + extern const std::vector avariableErrors; + extern ErrorType findError(uint16_t errorId); + extern ErrorType findError(std::string key); + + enum CommandResultType { + GENERAL, + PERM_ERROR + }; + + struct CommandResult { + public: + static CommandResult Success; + static CommandResult NotImplemented; + + CommandResult(const CommandResult& ref) : _type(ref._type), error(ref.error), extraProperties(ref.extraProperties) {} + CommandResult(CommandResult&& ref) : _type(ref._type), error(ref.error), extraProperties(ref.extraProperties) {} + CommandResult(ErrorType error, const std::string &extraMsg = "") : error(std::move(error)) { if(extraMsg.empty()) return; /*extraProperties["extramsg"] = extraMsg; */extraProperties["extra_msg"] = extraMsg; } + CommandResult(std::string error, const std::string &extraMsg = "") : error(findError(std::move(error))) { if(extraMsg.empty()) return; /*extraProperties["extramsg"] = extraMsg; */extraProperties["extra_msg"] = extraMsg; } + CommandResult() : CommandResult(ErrorType::Success, ""){} + + bool operator==(const CommandResult& ref){ + return this->error == ref.error && ref.extraProperties == this->extraProperties; + } + + CommandResult& operator=(const CommandResult& ref)= default; + + /** + * @return true if fail + */ + bool operator!() const { + return this->error != ErrorType::Success; + } + virtual CommandResultType type(){ return _type; } + + ErrorType error; + std::map extraProperties; + CommandResultType _type = CommandResultType::GENERAL; + }; + + struct CommandResultPermissionError : public CommandResult { + public: + CommandResultPermissionError(permission::PermissionType error, const std::string &extraMsg = ""); + }; +} \ No newline at end of file diff --git a/src/EventLoop.cpp b/src/EventLoop.cpp new file mode 100644 index 0000000..6eb72b8 --- /dev/null +++ b/src/EventLoop.cpp @@ -0,0 +1,212 @@ +#include +#include +#include +#include +#include +#include "EventLoop.h" + +using namespace std; +using namespace ts::event; + +EventExecutor::EventExecutor(const std::string& thread_prefix) : _thread_prefix(thread_prefix) {} +EventExecutor::~EventExecutor() { + unique_lock lock(this->lock); + this->_shutdown(lock); + this->_reset_events(lock); +} + +void EventExecutor::threads(int threads) { + unique_lock lock(this->lock); + + if(this->target_threads == threads) + return; + + this->target_threads = threads; + while(this->_threads.size() < threads) + this->_spawn_executor(lock); + + if(this->_threads.size() > threads) { + this->should_adjust = true; + this->condition.notify_all(); + } + +#ifndef WIN32 + this->_reassign_thread_names(lock); +#endif +} + +bool EventExecutor::initialize(int threads) { + unique_lock lock(this->lock); + this->_shutdown(lock); + if(!lock.owns_lock()) + lock.lock(); + + this->should_shutdown = false; + while(threads-- > 0) + _spawn_executor(lock); + +#ifndef WIN32 + this->_reassign_thread_names(lock); +#endif + return true; +} + +void EventExecutor::_spawn_executor(std::unique_lock& lock) { + if(!lock.owns_lock()) + lock.lock(); + + this->_threads.emplace_back(&EventExecutor::_executor, this); +} + +void EventExecutor::shutdown() { + unique_lock lock(this->lock); + this->_shutdown(lock); +} + +bool EventExecutor::schedule(const std::shared_ptr &entry) { + unique_lock lock(this->lock); + if(!entry || entry->_event_ptr) + return true; /* already scheduled */ + + auto linked_entry = new LinkedEntry{}; + linked_entry->entry = entry; + linked_entry->next = nullptr; + linked_entry->previous = this->tail; + linked_entry->scheduled = std::chrono::system_clock::now(); + + if(this->tail) { + this->tail->next = linked_entry; + this->tail = linked_entry; + } else { + this->head = linked_entry; + this->tail = linked_entry; + } + this->condition.notify_one(); + + entry->_event_ptr = linked_entry; + return true; +} + +bool EventExecutor::cancel(const std::shared_ptr &entry) { + unique_lock lock(this->lock); + if(!entry || !entry->_event_ptr) + return false; + + auto linked_entry = (LinkedEntry*) entry->_event_ptr; + this->head = linked_entry->next; + if(this->head) { + assert(linked_entry == this->head->previous); + this->head->previous = nullptr; + } else { + assert(linked_entry == this->tail); + this->tail = nullptr; + } + + delete linked_entry; + entry->_event_ptr = nullptr; + return true; +} + +void EventExecutor::_shutdown(std::unique_lock &lock) { + if(!lock.owns_lock()) + lock.lock(); + + this->should_shutdown = true; + this->condition.notify_all(); + lock.unlock(); + for(auto& thread : this->_threads) + if(thread.joinable()) { + try { + thread.join(); /* TODO: Timeout? */ + } catch(std::system_error& ex) { + if(ex.code() != errc::invalid_argument) /* thread is not joinable anymore :) */ + throw; + } + } + lock.lock(); + this->should_shutdown = false; +} + +void EventExecutor::_reset_events(std::unique_lock &lock) { + if(!lock.owns_lock()) + lock.lock(); + + auto entry = this->head; + while(entry) { + auto next = entry->next; + delete entry; + entry = next; + } + this->head = nullptr; + this->tail = nullptr; +} + +#ifndef WIN32 +void EventExecutor::_reassign_thread_names(std::unique_lock &lock) { + if(!lock.owns_lock()) + lock.lock(); + + size_t index = 1; + for(auto& thread : this->_threads) { + auto handle = thread.native_handle(); + auto name = this->_thread_prefix + to_string(index++); + pthread_setname_np(handle, name.c_str()); + } +} +#endif + +void EventExecutor::_executor(event::EventExecutor *loop) { + while(true) { + unique_lock lock(loop->lock); + loop->condition.wait(lock, [&] { + return loop->should_shutdown || loop->should_adjust || loop->head != nullptr; + }); + if(loop->should_shutdown) + break; + + if(loop->should_adjust) { + const auto current_threads = loop->_threads.size(); + if(current_threads > loop->target_threads) { + /* terminate this loop */ + + auto thread_id = std::this_thread::get_id(); + auto index = std::find_if(loop->_threads.begin(), loop->_threads.end(), [&](const std::thread& thread) { return thread_id == thread.get_id(); }); + if(index == loop->_threads.end()) { + /* TODO: Log error */ + } else { + loop->_threads.erase(index); + } + loop->should_adjust = (current_threads - 1) != loop->target_threads; + } else { + loop->should_adjust = false; + } + +#ifndef WIN32 + if(!loop->should_adjust) + loop->_reassign_thread_names(lock); +#endif + } + + if(!loop->head) + continue; + + auto linked_entry = loop->head; + loop->head = linked_entry->next; + if(loop->head) { + assert(linked_entry == loop->head->previous); + loop->head->previous = nullptr; + } else { + assert(linked_entry == loop->tail); + loop->tail = nullptr; + } + + auto event_handler = linked_entry->entry.lock(); + assert(event_handler->_event_ptr == linked_entry); + event_handler->_event_ptr = nullptr; + lock.unlock(); + + if(event_handler) + event_handler->event_execute(linked_entry->scheduled); + delete linked_entry; + } +} \ No newline at end of file diff --git a/src/EventLoop.h b/src/EventLoop.h new file mode 100644 index 0000000..3a424da --- /dev/null +++ b/src/EventLoop.h @@ -0,0 +1,88 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace ts { + namespace event { + class EventExecutor; + + class EventEntry { + friend class EventExecutor; + public: + virtual void event_execute(const std::chrono::system_clock::time_point& /* scheduled timestamp */) = 0; + + private: + void* _event_ptr = nullptr; + }; + + template + class ProxiedEventEntry : public event::EventEntry { + public: + using callback_t = void(class_t::*)(const std::chrono::system_clock::time_point &); + + ProxiedEventEntry(const std::shared_ptr& _instance, callback_t callback) : instance(_instance), callback(callback) { } + + std::weak_ptr instance; + callback_t callback; + + void event_execute(const std::chrono::system_clock::time_point &point) override { + auto _instance = this->instance.lock(); + if(!_instance) + return; + + ((void(*)(class_t*, const std::chrono::system_clock::time_point &)) this->callback)(&*_instance, point); + } + }; + + class EventExecutor { + public: + explicit EventExecutor(const std::string& /* thread prefix */); + virtual ~EventExecutor(); + + bool initialize(int /* num threads */); + bool schedule(const std::shared_ptr& /* entry */); + bool cancel(const std::shared_ptr& /* entry */); /* Note: Will not cancel already running executes */ + void shutdown(); + + inline const std::string& thread_prefix() const { return this->_thread_prefix; } + + void threads(int /* num threads */); + inline int threads() const { return this->target_threads; } + private: + struct LinkedEntry { + LinkedEntry* previous; + LinkedEntry* next; + + std::chrono::system_clock::time_point scheduled; + std::weak_ptr entry; + }; + + static void _executor(EventExecutor*); + void _spawn_executor(std::unique_lock&); + void _shutdown(std::unique_lock&); + void _reset_events(std::unique_lock&); + +#ifndef WIN32 + void _reassign_thread_names(std::unique_lock&); +#endif + + bool should_shutdown = true; + bool should_adjust = false; /* thread adjustments */ + int target_threads = 0; + + std::vector _threads; + std::mutex lock; + std::condition_variable condition; + + LinkedEntry* head = nullptr; + LinkedEntry* tail = nullptr; + + std::string _thread_prefix; + }; + } +} \ No newline at end of file diff --git a/src/License.cpp b/src/License.cpp new file mode 100644 index 0000000..1d99c41 --- /dev/null +++ b/src/License.cpp @@ -0,0 +1,376 @@ +#include +#include +#include +#include "misc/endianness.h" +#include +#define FIXEDINT_H_INCLUDED +#include +#include +#include +#include +#include "License.h" + +using namespace ts; +using namespace license::teamspeak; +using namespace std; +using namespace std::chrono; + +LicensePublicKey license::teamspeak::public_root = + {0xcd, 0x0d, 0xe2, 0xae, 0xd4, 0x63, 0x45, 0x50, + 0x9a, 0x7e, 0x3c, 0xfd, 0x8f, 0x68, 0xb3, 0xdc, + 0x75, 0x55, 0xb2, 0x9d, 0xcc, 0xec, 0x73, 0xcd, + 0x18, 0x75, 0x0f, 0x99, 0x38, 0x12, 0x40, 0x8a}; +//0DCB3688FFD3C7E9A504563D4AEE20DCD +//8A401238990F7518CD73ECCC9DB25575 +/* + * +Public key: 0x41, 0x47, 0xeb, 0x8b, 0xab, 0xaa, 0x89, 0xb9, 0x34, 0x86, 0x76, 0x25, 0x5f, 0x9b, 0xaf, 0x10, 0xfb, 0x2b, 0x03, 0x62, 0x10, 0xd0, 0x18, 0x59, 0x04, 0x42, 0x39, 0x5b, 0x2c, 0x22, 0xc3, 0x6a, +Private key: 0xa8, 0xcf, 0x96, 0xe8, 0xa8, 0xce, 0x33, 0x3b, 0x80, 0xb5, 0xd4, 0x27, 0x25, 0x62, 0xa6, 0x3a, 0x4a, 0x9e, 0x81, 0xf3, 0x05, 0xda, 0xb5, 0xf7, 0xe9, 0x35, 0x3d, 0x02, 0x81, 0x39, 0x8d, 0x64, + */ +LicensePublicKey license::teamspeak::public_tea_root = { + 0x41, 0x47, 0xeb, 0x8b, 0xab, 0xaa, 0x89, 0xb9, + 0x34, 0x86, 0x76, 0x25, 0x5f, 0x9b, 0xaf, 0x10, + 0xfb, 0x2b, 0x03, 0x62, 0x10, 0xd0, 0x18, 0x59, + 0x04, 0x42, 0x39, 0x5b, 0x2c, 0x22, 0xc3, 0x6a, +}; +LicensePublicKey license::teamspeak::private_tea_root = { + 0xa8, 0xcf, 0x96, 0xe8, 0xa8, 0xce, 0x33, 0x3b, 0x80, 0xb5, 0xd4, 0x27, 0x25, 0x62, 0xa6, 0x3a, 0x4a, 0x9e, 0x81, 0xf3, 0x05, 0xda, 0xb5, 0xf7, 0xe9, 0x35, 0x3d, 0x02, 0x81, 0x39, 0x8d, 0x64, +}; + +/* +//Could be found at linux x64 3.1.3 (1621edab12fd2cfc74aee73258fec2f1435e70f2): 0x9BDFE0 +LicensePublicKey license::teamspeak::Anonymous::root_key = { + 0xA0, 0x23, 0xE6, 0x30, 0x0B, 0xDF, 0x91, 0x2E, 0xB3, 0xFD, 0x45, 0xA0, 0x86, 0x97, 0x1F, 0x97, + 0xE6, 0xBB, 0x10, 0xED, 0x75, 0x14, 0xC1, 0xA1, 0xFC, 0x31, 0x8A, 0x49, 0x58, 0x09, 0xDF, 0x78 +}; +size_t license::teamspeak::Anonymous::root_index = 1; + +//01 00 35 85 41 49 8A 24 AC D3 +//Could be found at linux x64 3.1.3 (1621edab12fd2cfc74aee73258fec2f1435e70f2): 0x9BDFA0 +uint8_t default_chain[] = { + 0x01, 0x00, 0x35, 0x85, 0x41, 0x49, 0x8A, 0x24, 0xAC, 0xD3, 0x01, 0x57, 0x91, 0x8B, 0x8F, 0x50, + 0x95, 0x5C, 0x0D, 0xAE, 0x97, 0x0A, 0xB6, 0x53, 0x72, 0xCB, 0xE4, 0x07, 0x41, 0x5F, 0xCF, 0x3E, + 0x02, 0x9B, 0x02, 0x08, 0x4D, 0x15, 0xE0, 0x0A, 0xA7, 0x93, 0x60, 0x07, 0x00, 0x00, 0x00, 0x20, + 0x41, 0x6E, 0x6F, 0x6E, 0x79, 0x6D, 0x6F, 0x75, 0x73 +}; + */ + +//9ED960 linux x64 +uint8_t default_chain[] = { + 0x01, 0x00, 0x95, 0x5D, 0x39, 0x4A, 0x17, 0xE5, 0x10, 0x73, 0x4C, 0xA0, 0x6B, 0xDF, 0x5D, 0x39, + 0x0F, 0x45, 0x24, 0x2C, 0x0B, 0x68, 0x9A, 0xAD, 0x0D, 0xBD, 0x46, 0xEF, 0x16, 0x0F, 0x32, 0xF1, + 0xC5, 0x33, 0x02, 0x0A, 0x55, 0xF2, 0x80, 0x0C, 0x5E, 0xB3, 0x00, 0x07, 0x00, 0x00, 0x00, 0x20, + 0x41, 0x6E, 0x6F, 0x6E, 0x79, 0x6D, 0x6F, 0x75, 0x73 +}; + +//9ED960 + sizeof(default_chain) + pad(16) +LicensePublicKey license::teamspeak::Anonymous::root_key = { + 0x40, 0x25, 0x08, 0x56, 0xD8, 0xDA, 0x90, 0x85, 0x9E, 0xDC, 0x1A, 0x0D, 0x58, 0x7B, 0x7D, 0x73, + 0xA0, 0x57, 0xF2, 0x55, 0x32, 0x47, 0x84, 0x0E, 0x3E, 0x2A, 0xF2, 0xC0, 0x1B, 0x8F, 0x23, 0x4B +}; +size_t license::teamspeak::Anonymous::root_index = 1; + +shared_ptr license::teamspeak::Anonymous::chain = []() -> shared_ptr { + string error; + auto str = istringstream(string((const char*) default_chain, sizeof(default_chain))); + auto chain = LicenseChain::parse(str, error); + if(!chain) { + cerr << "Failed to load default chain!" << endl; + return nullptr; + } + + return chain; +}(); + +#define IOERROR(message) \ +do {\ + error = message;\ + return 0; \ +} while(0) + +std::shared_ptr LicenseEntry::read(std::istream& stream, std::string& error) { + int baseLength = 42; + streamsize read; + char buffer[42]; + if((read = stream.readsome(buffer, baseLength)) != baseLength) { + if(read == 0) + return nullptr; + else + IOERROR("Could not read new license block"); + } + + if(buffer[0] != 0x00) IOERROR("Invalid entry type (" + to_string(buffer[0]) + ")"); + + shared_ptr result; + switch ((int) buffer[33]) { + case 0x00: + result = make_shared(); + break; + case 0x02: + result = make_shared(); + break; + case 0x03: + result = make_shared(); + break; + case 0x05: + result = make_shared(); + break; + case 0x20: + result = make_shared(); + break; + default: + error = "Invalid license type! (" + to_string((int) buffer[33]) + ")"; + return nullptr; + } + + memcpy(result->key.publicKeyData, &buffer[1], 32); + result->_begin = system_clock::time_point() + seconds(be2le32(buffer, 34) + LicenseEntry::TIMESTAMP_OFFSET); + result->_end = system_clock::time_point() + seconds(be2le32(buffer, 38) + LicenseEntry::TIMESTAMP_OFFSET); + + if(!result->readContent(stream, error)) return nullptr; + return result; +} +bool LicenseEntry::write(std::ostream& stream, std::string& error) const { + stream << (uint8_t) 0x00; + stream.write((char*) this->key.publicKeyData, 32); + stream.write((char*) &this->_type, 1); + + char timeBuffer[8]; + le2be32((uint32_t) (duration_cast(this->_begin.time_since_epoch()).count() - LicenseEntry::TIMESTAMP_OFFSET), timeBuffer, 0); + le2be32((uint32_t) (duration_cast(this->_end.time_since_epoch()).count() - LicenseEntry::TIMESTAMP_OFFSET), timeBuffer, 4); + stream.write(timeBuffer, 8); + + return this->writeContent(stream, error); +} + +std::string LicenseEntry::hash() const { + ostringstream buffer; + string error; + this->write(buffer, error); + + return digest::sha512(buffer.str().substr(1)); +} + +IntermediateLicenseEntry::IntermediateLicenseEntry() : LicenseEntry(LicenseType::INTERMEDIATE) {} + +bool IntermediateLicenseEntry::readContent(std::istream &stream, std::string &error) { + if(stream.readsome(this->dummy, 4) != 4) IOERROR("Could not read data! (Invalid length!)"); + getline(stream, this->issuer, '\0'); + return true; +} + +bool IntermediateLicenseEntry::writeContent(std::ostream &ostream, std::string &string1) const { + ostream.write(this->dummy, 4); + ostream << this->issuer; + ostream << (uint8_t) 0; + return true; +} + +ServerLicenseEntry::ServerLicenseEntry() : LicenseEntry(LicenseType::SERVER) {} +bool ServerLicenseEntry::readContent(std::istream &stream, std::string &error) { + if(stream.readsome((char*) &this->licenseType, 1) != 1) IOERROR("Could not read server license type!"); + char buffer[4]; + if(stream.readsome(buffer, 4) != 4) IOERROR("Could not read data! (Invalid length!)"); + this->slots = be2le32(buffer); + getline(stream, this->issuer, '\0'); + return true; +} +bool ServerLicenseEntry::writeContent(std::ostream &ostream, std::string &string1) const { + ostream.write((char*) &this->licenseType, 1); + char buffer[4]; + le2be32(this->slots, buffer); + ostream.write(buffer, 4); + ostream.write(this->issuer.data(), this->issuer.length()); + ostream << (uint8_t) 0; + return false; +} + +CodeLicenseEntry::CodeLicenseEntry() : LicenseEntry(LicenseType::CODE) {} +bool CodeLicenseEntry::readContent(std::istream &stream, std::string &error) { + getline(stream, this->issuer, '\0'); + return true; +} +bool CodeLicenseEntry::writeContent(std::ostream &ostream, std::string &error) const { + ostream.write(this->issuer.data(), this->issuer.length()); + ostream << (uint8_t) 0; + return true; +} + +LicenseSignLicenseEntry::LicenseSignLicenseEntry() : LicenseEntry(LicenseType::LICENSE_SIGN) {} +bool LicenseSignLicenseEntry::writeContent(std::ostream &stream, std::string &error) const { return true; } +bool LicenseSignLicenseEntry::readContent(std::istream &stream, std::string &error) { + cout << "License read: " << stream.gcount() << " -> " << stream.tellg() << endl; + return true; +} + +EphemeralLicenseEntry::EphemeralLicenseEntry() : LicenseEntry(LicenseType::EPHEMERAL) {} +bool EphemeralLicenseEntry::readContent(std::istream &stream, std::string &error) { return true; } +bool EphemeralLicenseEntry::writeContent(std::ostream &ostream, std::string &string1) const { return true; } + +std::shared_ptr LicenseChain::parse(std::istream& stream, std::string& error, bool return_on_error) { + error = ""; + + uint8_t chainType; + if(stream.readsome((char*) &chainType, 1) != 1) IOERROR("Invalid stream length!"); + if(chainType != 1) IOERROR("Invalid chain type! (" + to_string(chainType) + ")"); + + deque> entries; + while (stream) { + auto entry = LicenseEntry::read(stream, error); + if(!entry) { + if(error.length() == 0) break; + if(return_on_error) break; + return nullptr; + } + entries.push_back(entry); + } + + auto result = make_shared(); + result->entries = entries; + return result; +} + +inline std::string to_string(const std::chrono::system_clock::time_point& point) { + auto tp = system_clock::to_time_t(point); + string str = ctime(&tp); + return str.empty() ? str : str.substr(0, str.length() - 1); +} + +void LicenseChain::print() { + int index = 1; + for(const auto& entry : this->entries) { + cout << "Entry " << index ++ << " (Type: " << entry->type() << " | " << type_name(entry->type()) << ")" << endl; + + cout << hex; + cout << " Begin : " << to_string(entry->begin()) << " (" << (duration_cast(entry->begin().time_since_epoch()).count() - LicenseEntry::TIMESTAMP_OFFSET) << ")" << endl; + cout << " End : " << to_string(entry->end()) << " (" << (duration_cast(entry->end().time_since_epoch()).count() - LicenseEntry::TIMESTAMP_OFFSET) << ")" << endl; + cout << " Public key:"; + for(const auto& e : entry->key.publicKeyData) + cout << " " << hex << setfill('0') << setw(2) << (int) e << dec; + cout << endl; + + if(dynamic_pointer_cast(entry)) { + cout << " Issuer : " << dynamic_pointer_cast(entry)->issuer << endl; + cout << " Slots : " << dynamic_pointer_cast(entry)->unknown << endl; + } else if(dynamic_pointer_cast(entry)) { + cout << " Issuer : " << dynamic_pointer_cast(entry)->issuer << endl; + cout << " Server license type : " << (int) dynamic_pointer_cast(entry)->licenseType << endl; + cout << " Slots : " << dynamic_pointer_cast(entry)->slots << endl; + } else if(dynamic_pointer_cast(entry)){ + cout << " Issuer : " << dynamic_pointer_cast(entry)->issuer << endl; + } + } + + auto key = this->generatePublicKey(); + cout << "Public key: " << endl; + hexDump((char*) key.data(), key.length(), key.length(), key.length(), [](string message) { cout << message << endl; }); +} + +std::string LicenseChain::exportChain() { + string error; + ostringstream stream; + stream << (uint8_t) 0x01; + for(const auto& entry : this->entries) + entry->write(stream, error); + return stream.str(); +} + +void LicenseChain::addIntermediateEntry() { + auto entry = make_shared(); + uint8_t seed[32 * 2]; //FIXME more secure + ed25519_create_keypair(entry->key.publicKeyData, entry->key.privateKeyData, seed); + entry->key.privateKey = true; + entry->_begin = system_clock::now() - hours(16); + entry->_end = system_clock::now() + hours(16); + entry->issuer = "XXXX"; + entry->unknown = 10; //Max 0x7F + this->entries.push_back(entry); +} + +std::shared_ptr LicenseChain::addServerEntry(ServerLicenseType type, const std::string &issuer, uint32_t slots) { + auto entry = make_shared(); + uint8_t seed[32 * 2]; //FIXME more secure + ed25519_create_keypair(entry->key.publicKeyData, entry->key.privateKeyData, seed); + entry->key.privateKey = true; + entry->issuer = issuer; + entry->licenseType = type; + entry->slots = slots; + entry->_begin = system_clock::now() - hours(8); + entry->_end = system_clock::now() + hours(8); + this->entries.push_back(entry); + return entry; +} + +void LicenseChain::addEphemeralEntry() { + auto entry = make_shared(); + uint8_t seed[32 * 2]; //FIXME more secure + ed25519_create_keypair(entry->key.publicKeyData, entry->key.privateKeyData, seed); + entry->key.privateKey = true; + entry->_begin = system_clock::now() - hours(6); + entry->_end = system_clock::now() + hours(6); + this->entries.push_back(entry); +} + +inline ge_p3 import(const char* buffer) { + ge_p3 result{}; + ge_frombytes_negate_vartime(&result, (uint8_t*) buffer); + return result; +} + +inline string importHash(const std::string& hash) { + uint8_t buffer[64]; //We need to allocate 64 bytes (s[0]+256*s[1]+...+256^63*s[63] as input for sc_reduce (result could max be 2^256 | 32 bytes)) + memset(buffer, 0, 64); + + memcpy(buffer, (void*) hash.data(), 32); + buffer[0] &= 0xF8; + buffer[31] &= 0x3F; + buffer[31] |= 0x40; + sc_reduce(buffer); + return string((char*) buffer, 32); +} + +std::string LicenseChain::generatePublicKey(LicensePublicKey root, int length) const { + auto parent = import((char*) root); + fe_neg(parent.X, parent.X); // undo negate + fe_neg(parent.T, parent.T); // undo negate + + for(const auto& entry : this->entries) { + if(length-- == 0) continue; + ge_p3 pKey{}; + ge_frombytes_negate_vartime(&pKey, entry->key.publicKeyData); + + + ge_cached parentCached{}; + ge_p3_to_cached(&parentCached, &parent); + + auto clamp = importHash(entry->hash()); + + ge_p3 p3_clamp_mul_pKey{}; + ge_p2 p2_clamp_mul_pKey{}; + ge_scalarmult_vartime(&p2_clamp_mul_pKey, (unsigned char *) clamp.data(), &pKey); + ge_p2_to_p3(&p3_clamp_mul_pKey, &p2_clamp_mul_pKey); + + //----- | ----- + ge_p1p1 a{}; + ge_add(&a, &p3_clamp_mul_pKey, &parentCached); + + ge_p3 r2{}; + ge_p1p1_to_p3(&r2, &a); + + parent = r2; + } + + char buf[32]; + ge_p3_tobytes((uint8_t*) buf, &parent); + return string(buf, 32); +} + +std::string LicenseChain::generatePrivateKey(uint8_t *root, int begin) const { + uint8_t buffer[32]; + memcpy(buffer, root, 32); + for(int index = begin; index < this->entries.size(); index++) { + assert(this->entries[index]->key.privateKey); + sc_muladd(buffer, this->entries[index]->key.privateKeyData, (uint8_t*) importHash(this->entries[index]->hash()).data(), buffer); + } + return string((char*) buffer, 32); +} \ No newline at end of file diff --git a/src/License.h b/src/License.h new file mode 100644 index 0000000..19c273a --- /dev/null +++ b/src/License.h @@ -0,0 +1,197 @@ +#pragma once + +#include +#include +#include +#include + +namespace license { + namespace teamspeak { + class LicenseChain; + enum LicenseType { + INTERMEDIATE = 0x00, + WEBSIDE = 0x01, + SERVER = 0x02, + CODE = 0x03, + TOKEN = 0x04, + LICENSE_SIGN = 0x05, + MY_TS_ID_SIGN = 0x06, + EPHEMERAL = 0x20 + }; + inline std::string type_name(LicenseType type) { + switch (type) { + case LicenseType::INTERMEDIATE: + return "Intermediate"; + case LicenseType::WEBSIDE: + return "Website"; + case LicenseType::SERVER: + return "Server"; + case LicenseType::CODE: + return "Code"; + case LicenseType::LICENSE_SIGN: + return "LicenseSign"; + case LicenseType::MY_TS_ID_SIGN: + return "MyTsIdSign"; + case LicenseType::EPHEMERAL: + return "Ephemeral"; + default: + return "Unknown"; + } + } + + enum ServerLicenseType : uint8_t { + SERVER_LICENSE_NONE, + SERVER_LICENSE_OFFLINE, + SERVER_LICENSE_SDK, + SERVER_LICENSE_SDKOFFLINE, + SERVER_LICENSE_NPL, + SERVER_LICENSE_ATHP, + SERVER_LICENSE_AAL, + SERVER_LICENSE_DEFAULT, + }; + + struct LicenseKey { + bool privateKey = false; + uint8_t privateKeyData[32] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + uint8_t publicKeyData[32] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + }; + + typedef uint8_t LicensePublicKey[32]; + class LicenseEntry { + static constexpr int64_t TIMESTAMP_OFFSET = 0x50e22700; + friend class LicenseChain; + public: + LicenseEntry(LicenseType type) : _type(type) {} + ~LicenseEntry() {} + + virtual LicenseType type() const { return this->_type; } + + static std::shared_ptr read(std::istream&, std::string&); + virtual bool write(std::ostream&, std::string&) const ; + + std::string hash() const; + + std::chrono::system_clock::time_point begin() const { return this->_begin; } + std::chrono::system_clock::time_point end() const { return this->_end; } + + void begin(const std::chrono::system_clock::time_point& begin) { this->_begin = begin; } + void end(const std::chrono::system_clock::time_point& end) { this->_end = end; } + + template + Unit lifetime() { return std::chrono::duration_cast(this->_end - this->_begin); } + + template + void lifetime(const Unit& lifetime, const std::chrono::system_clock::time_point& begin = std::chrono::system_clock::now()) { + this->_begin = begin; + this->_end = begin + lifetime; + } + protected: + virtual bool readContent(std::istream&, std::string&) = 0; + virtual bool writeContent(std::ostream&, std::string&) const = 0; + + LicenseKey key; + LicenseType _type; + std::chrono::system_clock::time_point _begin; + std::chrono::system_clock::time_point _end; + }; + + class IntermediateLicenseEntry : public LicenseEntry { + public: + IntermediateLicenseEntry(); + + std::string issuer; + union { + uint32_t unknown; + char dummy[4]; + }; + protected: + bool readContent(std::istream &istream1, std::string &string1) override; + bool writeContent(std::ostream &ostream1, std::string &string1) const override; + }; + + class ServerLicenseEntry : public LicenseEntry { + public: + ServerLicenseEntry(); + + ServerLicenseType licenseType; + std::string issuer; + uint32_t slots; + protected: + bool readContent(std::istream &stream, std::string &error) override; + bool writeContent(std::ostream &stream, std::string &error) const override; + }; + + class CodeLicenseEntry : public LicenseEntry { + public: + CodeLicenseEntry(); + + std::string issuer; + protected: + bool readContent(std::istream &stream, std::string &error) override; + bool writeContent(std::ostream &stream, std::string &error) const override; + }; + + class LicenseSignLicenseEntry : public LicenseEntry { + public: + LicenseSignLicenseEntry(); + + protected: + bool readContent(std::istream &stream, std::string &error) override; + bool writeContent(std::ostream &stream, std::string &error) const override; + }; + + class EphemeralLicenseEntry : public LicenseEntry { + public: + EphemeralLicenseEntry(); + protected: + bool readContent(std::istream &stream, std::string &error) override; + bool writeContent(std::ostream &stream, std::string &error) const override; + }; + + extern LicensePublicKey public_root; + extern LicensePublicKey public_tea_root; + extern LicensePublicKey private_tea_root; + + class LicenseChain { + public: + static std::shared_ptr parse(std::istream&, std::string&, bool return_on_error = false); + inline static std::shared_ptr parse(const std::string& license, std::string& error, bool return_on_error = false) { + std::istringstream s(license); + return LicenseChain::parse(s, error, return_on_error); + } + + LicenseChain() {} + ~LicenseChain() {} + + std::shared_ptr copy() { + auto result = std::make_shared(); + result->entries = this->entries; + return result; + } + + void print(); + std::string exportChain(); + + void addEphemeralEntry(); + void addIntermediateEntry(); + std::shared_ptr addServerEntry(ServerLicenseType, const std::string& issuer, uint32_t slots); + void addEntry(const std::shared_ptr& entry) { this->entries.push_back(entry); } + + /* + * Attention! Root must be compressd + */ + std::string generatePublicKey(LicensePublicKey = public_root, int length = -1) const; + std::string generatePrivateKey(LicensePublicKey = private_tea_root, int begin = 0) const; + + std::deque> entries; + private: + }; + + namespace Anonymous { + extern std::shared_ptr chain; //Thu Jun 1 00:00:00 2017 - Sat Sep 1 00:00:00 2018 + extern LicensePublicKey root_key; + extern size_t root_index; + } + + } +} \ No newline at end of file diff --git a/src/PermissionManager.cpp b/src/PermissionManager.cpp new file mode 100644 index 0000000..b0bd14b --- /dev/null +++ b/src/PermissionManager.cpp @@ -0,0 +1,1010 @@ +#include +#include +#include "misc/memtracker.h" +#include "BasicChannel.h" +#include "log/LogUtils.h" + +using namespace std; +using namespace ts; +using namespace ts::permission; + +deque> ts::permission::availablePermissions = deque>{ + make_shared(PermissionType::b_serverinstance_help_view, PermissionGroup::global_info, "b_serverinstance_help_view", "Retrieve information about ServerQuery commands"), + //make_shared(PermissionType::b_serverinstance_help_view_teespeack, PermissionGroup::group_7, "b_serverinstance_help_view_teespeack", "Costume teespeck help command idk ist just a test!"), + make_shared(PermissionType::b_serverinstance_version_view, PermissionGroup::global_info, "b_serverinstance_version_view", "Retrieve global server version (including platform and build number)"), + make_shared(PermissionType::b_serverinstance_info_view, PermissionGroup::global_info, "b_serverinstance_info_view", "Retrieve global server information"), + make_shared(PermissionType::b_serverinstance_virtualserver_list, PermissionGroup::global_info, "b_serverinstance_virtualserver_list", "List virtual servers stored in the sql"), + make_shared(PermissionType::b_serverinstance_binding_list, PermissionGroup::global_info, "b_serverinstance_binding_list", "List active IP bindings on multi-homed machines"), + //Removed due its useless + make_shared(PermissionType::b_serverinstance_permission_list, PermissionGroup::global_info, "b_serverinstance_permission_list", "List permissions available available on the server instance"), + make_shared(PermissionType::b_serverinstance_permission_find, PermissionGroup::global_info, "b_serverinstance_permission_find", "Search permission assignments by name or ID"), + //make_shared(PermissionType::b_serverinstance_allow_teaspeak_plugin, PermissionGroup::group_7, "b_serverinstance_allow_teespeak_plugin", "Allow a user to use the TeaSpeak plugin extension"), + make_shared(PermissionType::b_virtualserver_create, PermissionGroup::global_vsmanage, "b_virtualserver_create", "Create virtual servers"), + make_shared(PermissionType::b_virtualserver_delete, PermissionGroup::global_vsmanage, "b_virtualserver_delete", "Delete virtual servers"), + make_shared(PermissionType::b_virtualserver_start_any, PermissionGroup::global_vsmanage, "b_virtualserver_start_any", "Start any virtual server in the server instance"), + make_shared(PermissionType::b_virtualserver_stop_any, PermissionGroup::global_vsmanage, "b_virtualserver_stop_any", "Stop any virtual server in the server instance"), + make_shared(PermissionType::b_virtualserver_change_machine_id, PermissionGroup::global_vsmanage, "b_virtualserver_change_machine_id", "Change a virtual servers machine ID"), + make_shared(PermissionType::b_virtualserver_change_template, PermissionGroup::global_vsmanage, "b_virtualserver_change_template", "Edit virtual server default template values"), + make_shared(PermissionType::b_serverquery_login, PermissionGroup::global_admin, "b_serverquery_login", "Login to ServerQuery"), + make_shared(PermissionType::b_serverinstance_textmessage_send, PermissionGroup::global_admin, "b_serverinstance_textmessage_send", "Send text messages to all virtual servers at once"), + make_shared(PermissionType::b_serverinstance_log_view, PermissionGroup::global_admin, "b_serverinstance_log_view", "Retrieve global server log"), + make_shared(PermissionType::b_serverinstance_log_add, PermissionGroup::global_admin, "b_serverinstance_log_add", "Write to global server log"), + make_shared(PermissionType::b_serverinstance_stop, PermissionGroup::global_admin, "b_serverinstance_stop", "Shutdown the server process"), + make_shared(PermissionType::b_serverinstance_modify_settings, PermissionGroup::global_settings, "b_serverinstance_modify_settings", "Edit global settings"), + make_shared(PermissionType::b_serverinstance_modify_querygroup, PermissionGroup::global_settings, "b_serverinstance_modify_querygroup", "Edit global ServerQuery groups"), + make_shared(PermissionType::b_serverinstance_modify_templates, PermissionGroup::global_settings, "b_serverinstance_modify_templates", "Edit global template groups"), + make_shared(PermissionType::b_virtualserver_select, PermissionGroup::vs_info, "b_virtualserver_select", "Select a virtual server"), + make_shared(PermissionType::b_virtualserver_select_godmode, PermissionGroup::vs_info, "b_virtualserver_select_godmode", "Select a virtual server but be invisible"), + make_shared(PermissionType::b_virtualserver_info_view, PermissionGroup::vs_info, "b_virtualserver_info_view", "Retrieve virtual server information"), + make_shared(PermissionType::b_virtualserver_connectioninfo_view, PermissionGroup::vs_info, "b_virtualserver_connectioninfo_view", "Retrieve virtual server connection information"), + make_shared(PermissionType::b_virtualserver_channel_list, PermissionGroup::vs_info, "b_virtualserver_channel_list", "List channels on a virtual server"), + make_shared(PermissionType::b_virtualserver_channel_search, PermissionGroup::vs_info, "b_virtualserver_channel_search", "Search for channels on a virtual server"), + make_shared(PermissionType::b_virtualserver_client_list, PermissionGroup::vs_info, "b_virtualserver_client_list", "List clients online on a virtual server"), + make_shared(PermissionType::b_virtualserver_client_search, PermissionGroup::vs_info, "b_virtualserver_client_search", "Search for clients online on a virtual server"), + make_shared(PermissionType::b_virtualserver_client_dblist, PermissionGroup::vs_info, "b_virtualserver_client_dblist", "List client identities known by the virtual server"), + make_shared(PermissionType::b_virtualserver_client_dbsearch, PermissionGroup::vs_info, "b_virtualserver_client_dbsearch", "Search for client identities known by the virtual server"), + make_shared(PermissionType::b_virtualserver_client_dbinfo, PermissionGroup::vs_info, "b_virtualserver_client_dbinfo", "Retrieve client information"), + make_shared(PermissionType::b_virtualserver_permission_find, PermissionGroup::vs_info, "b_virtualserver_permission_find", "Find permissions"), + make_shared(PermissionType::b_virtualserver_custom_search, PermissionGroup::vs_info, "b_virtualserver_custom_search", "Find custom fields"), + make_shared(PermissionType::b_virtualserver_start, PermissionGroup::vs_admin, "b_virtualserver_start", "Start own virtual server"), + make_shared(PermissionType::b_virtualserver_stop, PermissionGroup::vs_admin, "b_virtualserver_stop", "Stop own virtual server"), + make_shared(PermissionType::b_virtualserver_token_list, PermissionGroup::vs_admin, "b_virtualserver_token_list", "List privilege keys available"), + make_shared(PermissionType::b_virtualserver_token_add, PermissionGroup::vs_admin, "b_virtualserver_token_add", "Create new privilege keys"), + make_shared(PermissionType::b_virtualserver_token_use, PermissionGroup::vs_admin, "b_virtualserver_token_use", "Use a privilege keys to gain access to groups"), + make_shared(PermissionType::b_virtualserver_token_delete, PermissionGroup::vs_admin, "b_virtualserver_token_delete", "Delete a privilege key"), + make_shared(PermissionType::b_virtualserver_log_view, PermissionGroup::vs_admin, "b_virtualserver_log_view", "Retrieve virtual server log"), + make_shared(PermissionType::b_virtualserver_log_add, PermissionGroup::vs_admin, "b_virtualserver_log_add", "Write to virtual server log"), + make_shared(PermissionType::b_virtualserver_join_ignore_password, PermissionGroup::vs_admin, "b_virtualserver_join_ignore_password", "Join virtual server ignoring its password"), + make_shared(PermissionType::b_virtualserver_notify_register, PermissionGroup::vs_admin, "b_virtualserver_notify_register", "Register for server notifications"), + make_shared(PermissionType::b_virtualserver_notify_unregister, PermissionGroup::vs_admin, "b_virtualserver_notify_unregister", "Unregister from server notifications"), + make_shared(PermissionType::b_virtualserver_snapshot_create, PermissionGroup::vs_admin, "b_virtualserver_snapshot_create", "Create server snapshots"), + make_shared(PermissionType::b_virtualserver_snapshot_deploy, PermissionGroup::vs_admin, "b_virtualserver_snapshot_deploy", "Deploy server snapshots"), + make_shared(PermissionType::b_virtualserver_permission_reset, PermissionGroup::vs_admin, "b_virtualserver_permission_reset", "Reset the server permission settings to default values"), + make_shared(PermissionType::b_virtualserver_modify_name, PermissionGroup::vs_settings, "b_virtualserver_modify_name", "Modify server name"), + make_shared(PermissionType::b_virtualserver_modify_welcomemessage, PermissionGroup::vs_settings, "b_virtualserver_modify_welcomemessage", "Modify welcome message"), + make_shared(PermissionType::b_virtualserver_modify_maxclients, PermissionGroup::vs_settings, "b_virtualserver_modify_maxclients", "Modify servers max clients"), + make_shared(PermissionType::b_virtualserver_modify_reserved_slots, PermissionGroup::vs_settings, "b_virtualserver_modify_reserved_slots", "Modify reserved slots"), + make_shared(PermissionType::b_virtualserver_modify_password, PermissionGroup::vs_settings, "b_virtualserver_modify_password", "Modify server password"), + make_shared(PermissionType::b_virtualserver_modify_default_servergroup, PermissionGroup::vs_settings, "b_virtualserver_modify_default_servergroup", "Modify default Server Group"), + make_shared(PermissionType::b_virtualserver_modify_default_musicgroup, PermissionGroup::vs_settings, "b_virtualserver_modify_default_musicgroup", "Modify default music Group"), + make_shared(PermissionType::b_virtualserver_modify_default_channelgroup, PermissionGroup::vs_settings, "b_virtualserver_modify_default_channelgroup", "Modify default Channel Group"), + make_shared(PermissionType::b_virtualserver_modify_default_channeladmingroup, PermissionGroup::vs_settings, "b_virtualserver_modify_default_channeladmingroup", "Modify default Channel Admin Group"), + make_shared(PermissionType::b_virtualserver_modify_channel_forced_silence, PermissionGroup::vs_settings, "b_virtualserver_modify_channel_forced_silence", "Modify channel force silence value"), + make_shared(PermissionType::b_virtualserver_modify_complain, PermissionGroup::vs_settings, "b_virtualserver_modify_complain", "Modify individual complain settings"), + make_shared(PermissionType::b_virtualserver_modify_antiflood, PermissionGroup::vs_settings, "b_virtualserver_modify_antiflood", "Modify individual antiflood settings"), + make_shared(PermissionType::b_virtualserver_modify_ft_settings, PermissionGroup::vs_settings, "b_virtualserver_modify_ft_settings", "Modify file transfer settings"), + make_shared(PermissionType::b_virtualserver_modify_ft_quotas, PermissionGroup::vs_settings, "b_virtualserver_modify_ft_quotas", "Modify file transfer quotas"), + make_shared(PermissionType::b_virtualserver_modify_hostmessage, PermissionGroup::vs_settings, "b_virtualserver_modify_hostmessage", "Modify individual hostmessage settings"), + make_shared(PermissionType::b_virtualserver_modify_hostbanner, PermissionGroup::vs_settings, "b_virtualserver_modify_hostbanner", "Modify individual hostbanner settings"), + make_shared(PermissionType::b_virtualserver_modify_hostbutton, PermissionGroup::vs_settings, "b_virtualserver_modify_hostbutton", "Modify individual hostbutton settings"), + make_shared(PermissionType::b_virtualserver_modify_port, PermissionGroup::vs_settings, "b_virtualserver_modify_port", "Modify server port"), +#ifndef LAGENCY + make_shared(PermissionType::b_virtualserver_modify_host, PermissionGroup::vs_settings, "b_virtualserver_modify_host", "Modify server host"), +#endif + make_shared(PermissionType::b_virtualserver_modify_autostart, PermissionGroup::vs_settings, "b_virtualserver_modify_autostart", "Modify server autostart"), + make_shared(PermissionType::b_virtualserver_modify_needed_identity_security_level, PermissionGroup::vs_settings, "b_virtualserver_modify_needed_identity_security_level", "Modify required identity security level"), + make_shared(PermissionType::b_virtualserver_modify_priority_speaker_dimm_modificator, PermissionGroup::vs_settings, "b_virtualserver_modify_priority_speaker_dimm_modificator", "Modify priority speaker dimm modificator"), + make_shared(PermissionType::b_virtualserver_modify_log_settings, PermissionGroup::vs_settings, "b_virtualserver_modify_log_settings", "Modify log settings"), + make_shared(PermissionType::b_virtualserver_modify_min_client_version, PermissionGroup::vs_settings, "b_virtualserver_modify_min_client_version", "Modify min client version"), + make_shared(PermissionType::b_virtualserver_modify_icon_id, PermissionGroup::vs_settings, "b_virtualserver_modify_icon_id", "Modify server icon"), + make_shared(PermissionType::b_virtualserver_modify_weblist, PermissionGroup::vs_settings, "b_virtualserver_modify_weblist", "Modify web server list reporting settings"), + make_shared(PermissionType::b_virtualserver_modify_codec_encryption_mode, PermissionGroup::vs_settings, "b_virtualserver_modify_codec_encryption_mode", "Modify codec encryption mode"), + make_shared(PermissionType::b_virtualserver_modify_temporary_passwords, PermissionGroup::vs_settings, "b_virtualserver_modify_temporary_passwords", "Modify temporary serverpasswords"), + make_shared(PermissionType::b_virtualserver_modify_temporary_passwords_own, PermissionGroup::vs_settings, "b_virtualserver_modify_temporary_passwords_own", "Modify own temporary serverpasswords"), + make_shared(PermissionType::b_virtualserver_modify_channel_temp_delete_delay_default, PermissionGroup::vs_settings, "b_virtualserver_modify_channel_temp_delete_delay_default", "Modify default temporary channel delete delay"), + make_shared(PermissionType::b_virtualserver_modify_music_bot_limit, PermissionGroup::vs_settings, "b_virtualserver_modify_music_bot_limit", "Allow client to edit the server music bot limit"), + make_shared(PermissionType::b_virtualserver_modify_default_messages, PermissionGroup::vs_settings, "b_virtualserver_modify_default_messages", "Allows the client to edit the default messages"), + make_shared(PermissionType::i_channel_min_depth, PermissionGroup::channel, "i_channel_min_depth", "Min channel creation depth in hierarchy"), + make_shared(PermissionType::i_channel_max_depth, PermissionGroup::channel, "i_channel_max_depth", "Max channel creation depth in hierarchy"), + make_shared(PermissionType::b_channel_group_inheritance_end, PermissionGroup::channel, "b_channel_group_inheritance_end", "Stop inheritance of channel group permissions"), + make_shared(PermissionType::i_channel_permission_modify_power, PermissionGroup::channel, "i_channel_permission_modify_power", "Modify channel permission power"), + make_shared(PermissionType::i_channel_needed_permission_modify_power, PermissionGroup::channel, "i_channel_needed_permission_modify_power", "Needed modify channel permission power"), + make_shared(PermissionType::b_channel_info_view, PermissionGroup::channel_info, "b_channel_info_view", "Retrieve channel information"), + make_shared(PermissionType::b_channel_create_child, PermissionGroup::channel_create, "b_channel_create_child", "Create sub-channels"), + make_shared(PermissionType::b_channel_create_permanent, PermissionGroup::channel_create, "b_channel_create_permanent", "Create permanent channels"), + make_shared(PermissionType::b_channel_create_semi_permanent, PermissionGroup::channel_create, "b_channel_create_semi_permanent", "Create semi-permanent channels"), + make_shared(PermissionType::b_channel_create_temporary, PermissionGroup::channel_create, "b_channel_create_temporary", "Create temporary channels"), + make_shared(PermissionType::b_channel_create_private, PermissionGroup::channel_create, "b_channel_create_private", "Create private channel"), + make_shared(PermissionType::b_channel_create_with_topic, PermissionGroup::channel_create, "b_channel_create_with_topic", "Create channels with a topic"), + make_shared(PermissionType::b_channel_create_with_description, PermissionGroup::channel_create, "b_channel_create_with_description", "Create channels with a description"), + make_shared(PermissionType::b_channel_create_with_password, PermissionGroup::channel_create, "b_channel_create_with_password", "Create password protected channels"), + make_shared(PermissionType::b_channel_create_modify_with_codec_speex8, PermissionGroup::channel_create, "b_channel_create_modify_with_codec_speex8", "Create channels using Speex Narrowband (8 kHz) codecs"), + make_shared(PermissionType::b_channel_create_modify_with_codec_speex16, PermissionGroup::channel_create, "b_channel_create_modify_with_codec_speex16", "Create channels using Speex Wideband (16 kHz) codecs"), + make_shared(PermissionType::b_channel_create_modify_with_codec_speex32, PermissionGroup::channel_create, "b_channel_create_modify_with_codec_speex32", "Create channels using Speex Ultra-Wideband (32 kHz) codecs"), + make_shared(PermissionType::b_channel_create_modify_with_codec_celtmono48, PermissionGroup::channel_create, "b_channel_create_modify_with_codec_celtmono48", "Create channels using the CELT Mono (48 kHz) codec"), + make_shared(PermissionType::b_channel_create_modify_with_codec_opusvoice, PermissionGroup::channel_create, "b_channel_create_modify_with_codec_opusvoice", "Create channels using OPUS (voice) codec"), + make_shared(PermissionType::b_channel_create_modify_with_codec_opusmusic, PermissionGroup::channel_create, "b_channel_create_modify_with_codec_opusmusic", "Create channels using OPUS (music) codec"), + make_shared(PermissionType::i_channel_create_modify_with_codec_maxquality, PermissionGroup::channel_create, "i_channel_create_modify_with_codec_maxquality", "Create channels with custom codec quality"), + make_shared(PermissionType::i_channel_create_modify_with_codec_latency_factor_min, PermissionGroup::channel_create, "i_channel_create_modify_with_codec_latency_factor_min", "Create channels with minimal custom codec latency factor"), + make_shared(PermissionType::b_channel_create_with_maxclients, PermissionGroup::channel_create, "b_channel_create_with_maxclients", "Create channels with custom max clients"), + make_shared(PermissionType::b_channel_create_with_maxfamilyclients, PermissionGroup::channel_create, "b_channel_create_with_maxfamilyclients", "Create channels with custom max family clients"), + make_shared(PermissionType::b_channel_create_with_sortorder, PermissionGroup::channel_create, "b_channel_create_with_sortorder", "Create channels with custom sort order"), + make_shared(PermissionType::b_channel_create_with_default, PermissionGroup::channel_create, "b_channel_create_with_default", "Create default channels"), + make_shared(PermissionType::b_channel_create_with_needed_talk_power, PermissionGroup::channel_create, "b_channel_create_with_needed_talk_power", "Create channels with needed talk power"), + make_shared(PermissionType::b_channel_create_modify_with_force_password, PermissionGroup::channel_create, "b_channel_create_modify_with_force_password", "Create new channels only with password"), + make_shared(PermissionType::i_channel_create_modify_with_temp_delete_delay, PermissionGroup::channel_create, "i_channel_create_modify_with_temp_delete_delay", "Max delete delay for temporary channels"), + make_shared(PermissionType::b_channel_modify_parent, PermissionGroup::channel_modify, "b_channel_modify_parent", "Move channels"), + make_shared(PermissionType::b_channel_modify_make_default, PermissionGroup::channel_modify, "b_channel_modify_make_default", "Make channel default"), + make_shared(PermissionType::b_channel_modify_make_permanent, PermissionGroup::channel_modify, "b_channel_modify_make_permanent", "Make channel permanent"), + make_shared(PermissionType::b_channel_modify_make_semi_permanent, PermissionGroup::channel_modify, "b_channel_modify_make_semi_permanent", "Make channel semi-permanent"), + make_shared(PermissionType::b_channel_modify_make_temporary, PermissionGroup::channel_modify, "b_channel_modify_make_temporary", "Make channel temporary"), + make_shared(PermissionType::b_channel_modify_name, PermissionGroup::channel_modify, "b_channel_modify_name", "Modify channel name"), + make_shared(PermissionType::b_channel_modify_topic, PermissionGroup::channel_modify, "b_channel_modify_topic", "Modify channel topic"), + make_shared(PermissionType::b_channel_modify_description, PermissionGroup::channel_modify, "b_channel_modify_description", "Modify channel description"), + make_shared(PermissionType::b_channel_modify_password, PermissionGroup::channel_modify, "b_channel_modify_password", "Modify channel password"), + make_shared(PermissionType::b_channel_modify_codec, PermissionGroup::channel_modify, "b_channel_modify_codec", "Modify channel codec"), + make_shared(PermissionType::b_channel_modify_codec_quality, PermissionGroup::channel_modify, "b_channel_modify_codec_quality", "Modify channel codec quality"), + make_shared(PermissionType::b_channel_modify_codec_latency_factor, PermissionGroup::channel_modify, "b_channel_modify_codec_latency_factor", "Modify channel codec latency factor"), + make_shared(PermissionType::b_channel_modify_maxclients, PermissionGroup::channel_modify, "b_channel_modify_maxclients", "Modify channels max clients"), + make_shared(PermissionType::b_channel_modify_maxfamilyclients, PermissionGroup::channel_modify, "b_channel_modify_maxfamilyclients", "Modify channels max family clients"), + make_shared(PermissionType::b_channel_modify_sortorder, PermissionGroup::channel_modify, "b_channel_modify_sortorder", "Modify channel sort order"), + make_shared(PermissionType::b_channel_modify_needed_talk_power, PermissionGroup::channel_modify, "b_channel_modify_needed_talk_power", "Change needed channel talk power"), + make_shared(PermissionType::i_channel_modify_power, PermissionGroup::channel_modify, "i_channel_modify_power", "Channel modify power"), + make_shared(PermissionType::i_channel_needed_modify_power, PermissionGroup::channel_modify, "i_channel_needed_modify_power", "Needed channel modify power"), + make_shared(PermissionType::b_channel_modify_make_codec_encrypted, PermissionGroup::channel_modify, "b_channel_modify_make_codec_encrypted", "Make channel codec encrypted"), + make_shared(PermissionType::b_channel_modify_temp_delete_delay, PermissionGroup::channel_modify, "b_channel_modify_temp_delete_delay", "Modify temporary channel delete delay"), + make_shared(PermissionType::b_channel_delete_permanent, PermissionGroup::channel_delete, "b_channel_delete_permanent", "Delete permanent channels"), + make_shared(PermissionType::b_channel_delete_semi_permanent, PermissionGroup::channel_delete, "b_channel_delete_semi_permanent", "Delete semi-permanent channels"), + make_shared(PermissionType::b_channel_delete_temporary, PermissionGroup::channel_delete, "b_channel_delete_temporary", "Delete temporary channels"), + make_shared(PermissionType::b_channel_delete_flag_force, PermissionGroup::channel_delete, "b_channel_delete_flag_force", "Force channel delete"), + make_shared(PermissionType::i_channel_delete_power, PermissionGroup::channel_delete, "i_channel_delete_power", "Delete channel power"), + make_shared(PermissionType::i_channel_needed_delete_power, PermissionGroup::channel_delete, "i_channel_needed_delete_power", "Needed delete channel power"), + make_shared(PermissionType::b_channel_join_permanent, PermissionGroup::channel_access, "b_channel_join_permanent", "Join permanent channels"), + make_shared(PermissionType::b_channel_join_semi_permanent, PermissionGroup::channel_access, "b_channel_join_semi_permanent", "Join semi-permanent channels"), + make_shared(PermissionType::b_channel_join_temporary, PermissionGroup::channel_access, "b_channel_join_temporary", "Join temporary channels"), + make_shared(PermissionType::b_channel_join_ignore_password, PermissionGroup::channel_access, "b_channel_join_ignore_password", "Join channel ignoring its password"), + make_shared(PermissionType::b_channel_join_ignore_maxclients, PermissionGroup::channel_access, "b_channel_join_ignore_maxclients", "Ignore channels max clients limit"), + make_shared(PermissionType::b_channel_ignore_view_power, PermissionGroup::channel_access, "b_channel_ignore_view_power", "If set the client see's every channel"), + make_shared(PermissionType::i_channel_join_power, PermissionGroup::channel_access, "i_channel_join_power", "Channel join power"), + make_shared(PermissionType::i_channel_needed_join_power, PermissionGroup::channel_access, "i_channel_needed_join_power", "Needed channel join power"), + make_shared(PermissionType::b_channel_ignore_join_power, PermissionGroup::channel_access, "b_channel_ignore_join_power", "Allows the client to bypass the channel join power"), + make_shared(PermissionType::i_channel_view_power, PermissionGroup::channel_access, "i_channel_view_power", "Channel view power"), + make_shared(PermissionType::i_channel_needed_view_power, PermissionGroup::channel_access, "i_channel_needed_view_power", "Needed channel view power"), + make_shared(PermissionType::i_channel_subscribe_power, PermissionGroup::channel_access, "i_channel_subscribe_power", "Channel subscribe power"), + make_shared(PermissionType::i_channel_needed_subscribe_power, PermissionGroup::channel_access, "i_channel_needed_subscribe_power", "Needed channel subscribe power"), + make_shared(PermissionType::b_channel_ignore_subscribe_power, PermissionGroup::channel_access, "b_channel_ignore_subscribe_power", "Allows the client to bypass the subscribe power"), + make_shared(PermissionType::i_channel_description_view_power, PermissionGroup::channel_access, "i_channel_description_view_power", "Channel description view power"), + make_shared(PermissionType::i_channel_needed_description_view_power, PermissionGroup::channel_access, "i_channel_needed_description_view_power", "Needed channel needed description view power"), + make_shared(PermissionType::b_channel_ignore_description_view_power, PermissionGroup::channel_access, "b_channel_ignore_description_view_power", "Allows the client to bypass the channel description view power"), + make_shared(PermissionType::i_icon_id, PermissionGroup::group, "i_icon_id", "Group icon identifier"), + make_shared(PermissionType::i_max_icon_filesize, PermissionGroup::group, "i_max_icon_filesize", "Max icon filesize in bytes"), + make_shared(PermissionType::i_max_playlist_size, PermissionGroup::group, "i_max_playlist_size", "Max songs within one playlist"), + make_shared(PermissionType::i_max_playlists, PermissionGroup::group, "i_max_playlists", "Max amount of playlists a client could own"), + make_shared(PermissionType::b_icon_manage, PermissionGroup::group, "b_icon_manage", "Enables icon management"), + make_shared(PermissionType::b_group_is_permanent, PermissionGroup::group, "b_group_is_permanent", "Group is permanent"), + make_shared(PermissionType::i_group_auto_update_type, PermissionGroup::group, "i_group_auto_update_type", "Group auto-update type"), + make_shared(PermissionType::i_group_auto_update_max_value, PermissionGroup::group, "i_group_auto_update_max_value", "Group auto-update max value"), + make_shared(PermissionType::i_group_sort_id, PermissionGroup::group, "i_group_sort_id", "Group sort id"), + make_shared(PermissionType::i_group_show_name_in_tree, PermissionGroup::group, "i_group_show_name_in_tree", "Show group name in tree depending on selected mode"), + make_shared(PermissionType::b_virtualserver_servergroup_list, PermissionGroup::group_info, "b_virtualserver_servergroup_list", "List server groups"), + make_shared(PermissionType::b_virtualserver_servergroup_permission_list, PermissionGroup::group_info, "b_virtualserver_servergroup_permission_list", "Allows the client to view all server group permissions"), + make_shared(PermissionType::b_virtualserver_servergroup_client_list, PermissionGroup::group_info, "b_virtualserver_servergroup_client_list", "List clients from a server group"), + make_shared(PermissionType::b_virtualserver_channelgroup_list, PermissionGroup::group_info, "b_virtualserver_channelgroup_list", "List channel groups"), + make_shared(PermissionType::b_virtualserver_channelgroup_permission_list, PermissionGroup::group_info, "b_virtualserver_channelgroup_permission_list", "Allows the client to view all channel group permissions"), + make_shared(PermissionType::b_virtualserver_channelgroup_client_list, PermissionGroup::group_info, "b_virtualserver_channelgroup_client_list", "List clients from a channel group"), + make_shared(PermissionType::b_virtualserver_client_permission_list, PermissionGroup::group_info, "b_virtualserver_client_permission_list", "Allows the client to view all client permissions"), + make_shared(PermissionType::b_virtualserver_channel_permission_list, PermissionGroup::group_info, "b_virtualserver_channel_permission_list", "Allows the client to view all channel permissions"), + make_shared(PermissionType::b_virtualserver_channelclient_permission_list, PermissionGroup::group_info, "b_virtualserver_channelclient_permission_list", "Allows the client to view all client channel permissions"), + make_shared(PermissionType::b_virtualserver_playlist_permission_list, PermissionGroup::group_info, "b_virtualserver_playlist_permission_list", "Allows the client to view all playlist permissions"), + make_shared(PermissionType::b_virtualserver_servergroup_create, PermissionGroup::group_create, "b_virtualserver_servergroup_create", "Create server groups"), + make_shared(PermissionType::b_virtualserver_channelgroup_create, PermissionGroup::group_create, "b_virtualserver_channelgroup_create", "Create channel groups"), + +#ifdef LAGENCY + make_shared(PermissionType::i_group_modify_power, PermissionGroup::group_168, "i_group_modify_power", "Group modify power"), + make_shared(PermissionType::i_group_needed_modify_power, PermissionGroup::group_168, "i_group_needed_modify_power", "Needed group modify power"), + make_shared(PermissionType::i_group_member_add_power, PermissionGroup::group_168, "i_group_member_add_power", "Group member add power"), + make_shared(PermissionType::i_group_needed_member_add_power, PermissionGroup::group_168, "i_group_needed_member_add_power", "Needed group member add power"), + make_shared(PermissionType::i_group_member_remove_power, PermissionGroup::group_168, "i_group_member_remove_power", "Group member delete power"), + make_shared(PermissionType::i_group_needed_member_remove_power, PermissionGroup::group_168, "i_group_needed_member_remove_power", "Needed group member delete power"), +#else + make_shared(PermissionType::i_server_group_modify_power, PermissionGroup::group_modify, "i_server_group_modify_power", "Server group modify power"), + make_shared(PermissionType::i_server_group_needed_modify_power, PermissionGroup::group_modify, "i_server_group_needed_modify_power", "Needed server group modify power"), + make_shared(PermissionType::i_server_group_member_add_power, PermissionGroup::group_modify, "i_server_group_member_add_power", "Server group member add power"), + make_shared(PermissionType::i_server_group_self_add_power, PermissionGroup::group_modify, "i_server_group_self_add_power", "Server group self add power"), + make_shared(PermissionType::i_server_group_needed_member_add_power, PermissionGroup::group_modify, "i_server_group_needed_member_add_power", "Needed server group member add power"), + make_shared(PermissionType::i_server_group_member_remove_power, PermissionGroup::group_modify, "i_server_group_member_remove_power", "Server group member delete power"), + make_shared(PermissionType::i_server_group_self_remove_power, PermissionGroup::group_modify, "i_server_group_self_remove_power", "Server group self delete power"), + make_shared(PermissionType::i_server_group_needed_member_remove_power, PermissionGroup::group_modify, "i_server_group_needed_member_remove_power", "Needed server group member delete power"), + + make_shared(PermissionType::i_channel_group_modify_power, PermissionGroup::group_modify, "i_channel_group_modify_power", "Channel group modify power"), + make_shared(PermissionType::i_channel_group_needed_modify_power, PermissionGroup::group_modify, "i_channel_group_needed_modify_power", "Needed channel group modify power"), + make_shared(PermissionType::i_channel_group_member_add_power, PermissionGroup::group_modify, "i_channel_group_member_add_power", "Channel group member add power"), + make_shared(PermissionType::i_channel_group_self_add_power, PermissionGroup::group_modify, "i_channel_group_self_add_power", "Channel group self add power"), + make_shared(PermissionType::i_channel_group_needed_member_add_power, PermissionGroup::group_modify, "i_channel_group_needed_member_add_power", "Needed channel group member add power"), + make_shared(PermissionType::i_channel_group_member_remove_power, PermissionGroup::group_modify, "i_channel_group_member_remove_power", "Channel group member delete power"), + make_shared(PermissionType::i_channel_group_self_remove_power, PermissionGroup::group_modify, "i_channel_group_self_remove_power", "Channel group self delete power"), + make_shared(PermissionType::i_channel_group_needed_member_remove_power, PermissionGroup::group_modify, "i_channel_group_needed_member_remove_power", "Needed channel group member delete power"), + + //old enum mapping + make_shared(PermissionType::i_displayed_group_member_add_power, PermissionGroup::group_modify, "i_group_member_add_power", "The displayed member add power (Enables/Disabled the group in the select menu)"), + make_shared(PermissionType::i_displayed_group_needed_member_add_power, PermissionGroup::group_modify, "i_group_needed_member_add_power", "The needed displayed member add power (Enables/Disabled the group in the select menu)"), + make_shared(PermissionType::i_displayed_group_member_remove_power, PermissionGroup::group_modify, "i_group_member_remove_power", "The displayed member add power (Enables/Disabled the group in the select menu)"), + make_shared(PermissionType::i_displayed_group_needed_member_remove_power, PermissionGroup::group_modify, "i_group_needed_member_remove_power", "The needed displayed member add power (Enables/Disabled the group in the select menu)"), + make_shared(PermissionType::i_displayed_group_modify_power, PermissionGroup::group_modify, "i_group_modify_power", "The displayed member add power (Enables/Disabled the group in the select menu)"), + make_shared(PermissionType::i_displayed_group_needed_modify_power, PermissionGroup::group_modify, "i_group_needed_modify_power", "The needed displayed member add power (Enables/Disabled the group in the select menu)"), + + //new enum mapping (must come AFTER the supported permissions) + make_shared(PermissionType::i_displayed_group_member_add_power, PermissionGroup::group_modify, "i_displayed_group_member_add_power", "The displayed member add power (Enables/Disabled the group in the select menu)", false), + make_shared(PermissionType::i_displayed_group_needed_member_add_power, PermissionGroup::group_modify, "i_displayed_group_needed_member_add_power", "The needed displayed member add power (Enables/Disabled the group in the select menu)", false), + make_shared(PermissionType::i_displayed_group_member_remove_power, PermissionGroup::group_modify, "i_displayed_group_member_remove_power", "The displayed member add power (Enables/Disabled the group in the select menu)", false), + make_shared(PermissionType::i_displayed_group_needed_member_remove_power, PermissionGroup::group_modify, "i_displayed_group_needed_member_remove_power", "The needed displayed member add power (Enables/Disabled the group in the select menu)", false), + make_shared(PermissionType::i_displayed_group_modify_power, PermissionGroup::group_modify, "i_displayed_group_modify_power", "The displayed member add power (Enables/Disabled the group in the select menu)", false), + make_shared(PermissionType::i_displayed_group_needed_modify_power, PermissionGroup::group_modify, "i_displayed_group_needed_modify_power", "The needed displayed member add power (Enables/Disabled the group in the select menu)", false), +#endif + + make_shared(PermissionType::i_permission_modify_power, PermissionGroup::group_modify, "i_permission_modify_power", "Permission modify power"), + make_shared(PermissionType::b_permission_modify_power_ignore, PermissionGroup::group_modify, "b_permission_modify_power_ignore", "Ignore needed permission modify power"), + make_shared(PermissionType::b_virtualserver_servergroup_delete, PermissionGroup::group_delete, "b_virtualserver_servergroup_delete", "Delete server groups"), + make_shared(PermissionType::b_virtualserver_channelgroup_delete, PermissionGroup::group_delete, "b_virtualserver_channelgroup_delete", "Delete channel groups"), + make_shared(PermissionType::i_client_permission_modify_power, PermissionGroup::client, "i_client_permission_modify_power", "Client permission modify power"), + make_shared(PermissionType::i_client_needed_permission_modify_power, PermissionGroup::client, "i_client_needed_permission_modify_power", "Needed client permission modify power"), + make_shared(PermissionType::i_client_max_clones_uid, PermissionGroup::client, "i_client_max_clones_uid", "Max additional connections per client identity"), + make_shared(PermissionType::i_client_max_clones_ip, PermissionGroup::client, "i_client_max_clones_ip", "Max additional connections per client address"), + make_shared(PermissionType::i_client_max_clones_hwid, PermissionGroup::client, "i_client_max_clones_hwid", "Max additional connections per client hardware id"), + make_shared(PermissionType::i_client_max_idletime, PermissionGroup::client, "i_client_max_idletime", "Max idle time in seconds"), + make_shared(PermissionType::i_client_max_avatar_filesize, PermissionGroup::client, "i_client_max_avatar_filesize", "Max avatar filesize in bytes"), + make_shared(PermissionType::i_client_max_channel_subscriptions, PermissionGroup::client, "i_client_max_channel_subscriptions", "Max channel subscriptions"), + make_shared(PermissionType::i_client_max_channels, PermissionGroup::client, "i_client_max_channels", "Limit of created channels"), + make_shared(PermissionType::i_client_max_temporary_channels, PermissionGroup::client, "i_client_max_temporary_channels", "Limit of created temporary channels"), + make_shared(PermissionType::i_client_max_semi_channels, PermissionGroup::client, "i_client_max_semi_channels", "Limit of created semi-permanent channels"), + make_shared(PermissionType::i_client_max_permanent_channels, PermissionGroup::client, "i_client_max_permanent_channels", "Limit of created permanent channels"), + make_shared(PermissionType::b_client_use_priority_speaker, PermissionGroup::client, "b_client_use_priority_speaker", "Allows the client to use priority speaker"), + make_shared(PermissionType::b_client_is_priority_speaker, PermissionGroup::client, "b_client_is_priority_speaker", "Toogels the client priority speaker mode"), + make_shared(PermissionType::b_client_skip_channelgroup_permissions, PermissionGroup::client, "b_client_skip_channelgroup_permissions", "Ignore channel group permissions"), + make_shared(PermissionType::b_client_force_push_to_talk, PermissionGroup::client, "b_client_force_push_to_talk", "Force Push-To-Talk capture mode"), + make_shared(PermissionType::b_client_ignore_bans, PermissionGroup::client, "b_client_ignore_bans", "Ignore bans"), + make_shared(PermissionType::b_client_ignore_vpn, PermissionGroup::client, "b_client_ignore_vpn", "Ignore the vpn check"), + make_shared(PermissionType::b_client_ignore_antiflood, PermissionGroup::client, "b_client_ignore_antiflood", "Ignore antiflood measurements"), + make_shared(PermissionType::b_client_enforce_valid_hwid, PermissionGroup::client, "b_client_enforce_valid_hwid", "Enforces the client to have a valid hardware id"), + make_shared(PermissionType::b_client_allow_invalid_packet, PermissionGroup::client, "b_client_allow_invalid_packet", "Allow client to send invalid packets"), + make_shared(PermissionType::b_client_allow_invalid_badges, PermissionGroup::client, "b_client_allow_invalid_badges", "Allow client to have invalid badges"), + make_shared(PermissionType::b_client_issue_client_query_command, PermissionGroup::client, "b_client_issue_client_query_command", "Issue query commands from client"), + make_shared(PermissionType::b_client_use_reserved_slot, PermissionGroup::client, "b_client_use_reserved_slot", "Use an reserved slot"), + make_shared(PermissionType::b_client_use_channel_commander, PermissionGroup::client, "b_client_use_channel_commander", "Use channel commander"), + make_shared(PermissionType::b_client_request_talker, PermissionGroup::client, "b_client_request_talker", "Allow to request talk power"), + make_shared(PermissionType::b_client_avatar_delete_other, PermissionGroup::client, "b_client_avatar_delete_other", "Allow deletion of avatars from other clients"), + make_shared(PermissionType::b_client_is_sticky, PermissionGroup::client, "b_client_is_sticky", "Client will be sticked to current channel"), + make_shared(PermissionType::b_client_ignore_sticky, PermissionGroup::client, "b_client_ignore_sticky", "Client ignores sticky flag"), +#ifndef LAGACY + make_shared(PermissionType::b_client_music_channel_list, PermissionGroup::client_info, "b_client_music_channel_list", "List all music bots in the current channel"), + make_shared(PermissionType::b_client_music_server_list, PermissionGroup::client_info, "b_client_music_server_list", "List all music bots on the sderver"), + make_shared(PermissionType::i_client_music_info, PermissionGroup::client_info, "i_client_music_info", "Permission to view music bot info"), + make_shared(PermissionType::i_client_music_needed_info, PermissionGroup::client_info, "i_client_music_needed_info", "Required permission to view music bot info"), +#endif + make_shared(PermissionType::b_client_info_view, PermissionGroup::client_info, "b_client_info_view", "Retrieve client information"), + make_shared(PermissionType::b_client_permissionoverview_view, PermissionGroup::client_info, "b_client_permissionoverview_view", "Retrieve client permissions overview"), + make_shared(PermissionType::b_client_permissionoverview_own, PermissionGroup::client_info, "b_client_permissionoverview_own", "Retrieve clients own permissions overview"), + make_shared(PermissionType::b_client_remoteaddress_view, PermissionGroup::client_info, "b_client_remoteaddress_view", "View client IP address and port"), + make_shared(PermissionType::i_client_serverquery_view_power, PermissionGroup::client_info, "i_client_serverquery_view_power", "ServerQuery view power"), + make_shared(PermissionType::i_client_needed_serverquery_view_power, PermissionGroup::client_info, "i_client_needed_serverquery_view_power", "Needed ServerQuery view power"), + make_shared(PermissionType::b_client_custom_info_view, PermissionGroup::client_info, "b_client_custom_info_view", "View custom fields"), + make_shared(PermissionType::i_client_kick_from_server_power, PermissionGroup::client_admin, "i_client_kick_from_server_power", "Client kick power from server"), + make_shared(PermissionType::i_client_needed_kick_from_server_power, PermissionGroup::client_admin, "i_client_needed_kick_from_server_power", "Needed client kick power from server"), + make_shared(PermissionType::i_client_kick_from_channel_power, PermissionGroup::client_admin, "i_client_kick_from_channel_power", "Client kick power from channel"), + make_shared(PermissionType::i_client_needed_kick_from_channel_power, PermissionGroup::client_admin, "i_client_needed_kick_from_channel_power", "Needed client kick power from channel"), + make_shared(PermissionType::i_client_ban_power, PermissionGroup::client_admin, "i_client_ban_power", "Client ban power"), + make_shared(PermissionType::i_client_needed_ban_power, PermissionGroup::client_admin, "i_client_needed_ban_power", "Needed client ban power"), + make_shared(PermissionType::i_client_move_power, PermissionGroup::client_admin, "i_client_move_power", "Client move power"), + make_shared(PermissionType::i_client_needed_move_power, PermissionGroup::client_admin, "i_client_needed_move_power", "Needed client move power"), + make_shared(PermissionType::i_client_complain_power, PermissionGroup::client_admin, "i_client_complain_power", "Complain power"), + make_shared(PermissionType::i_client_needed_complain_power, PermissionGroup::client_admin, "i_client_needed_complain_power", "Needed complain power"), + make_shared(PermissionType::b_client_complain_list, PermissionGroup::client_admin, "b_client_complain_list", "Show complain list"), + make_shared(PermissionType::b_client_complain_delete_own, PermissionGroup::client_admin, "b_client_complain_delete_own", "Delete own complains"), + make_shared(PermissionType::b_client_complain_delete, PermissionGroup::client_admin, "b_client_complain_delete", "Delete complains"), + make_shared(PermissionType::b_client_ban_list, PermissionGroup::client_admin, "b_client_ban_list", "Show banlist"), + make_shared(PermissionType::b_client_ban_trigger_list, PermissionGroup::client_admin, "b_client_ban_trigger_list", "Show trigger banlist"), + make_shared(PermissionType::b_client_ban_list_global, PermissionGroup::client_admin, "b_client_ban_list_global", "Show banlist globaly"), + make_shared(PermissionType::b_client_ban_create, PermissionGroup::client_admin, "b_client_ban_create", "Add a ban"), + make_shared(PermissionType::b_client_ban_create_global, PermissionGroup::client_admin, "b_client_ban_create_global", "Allow to create global bans"), + make_shared(PermissionType::b_client_ban_name, PermissionGroup::client_admin, "b_client_ban_name", "Allows the client to ban a client by name"), + make_shared(PermissionType::b_client_ban_ip, PermissionGroup::client_admin, "b_client_ban_ip", "Allows the client to ban a client by ip"), + make_shared(PermissionType::b_client_ban_hwid, PermissionGroup::client_admin, "b_client_ban_hwid", "Allows the client to ban a client hardware id"), + make_shared(PermissionType::b_client_ban_edit, PermissionGroup::client_admin, "b_client_ban_edit", "Allow to edit bans"), + make_shared(PermissionType::b_client_ban_edit_global, PermissionGroup::client_admin, "b_client_ban_edit_global", "Allow to edit global bans"), + make_shared(PermissionType::b_client_ban_delete_own, PermissionGroup::client_admin, "b_client_ban_delete_own", "Delete own bans"), + make_shared(PermissionType::b_client_ban_delete, PermissionGroup::client_admin, "b_client_ban_delete", "Delete bans"), + make_shared(PermissionType::b_client_ban_delete_own_global, PermissionGroup::client_admin, "b_client_ban_delete_own_global", "Delete own global bans"), + make_shared(PermissionType::b_client_ban_delete_global, PermissionGroup::client_admin, "b_client_ban_delete_global", "Delete global bans"), + make_shared(PermissionType::i_client_ban_max_bantime, PermissionGroup::client_admin, "i_client_ban_max_bantime", "Max bantime"), + make_shared(PermissionType::b_client_even_textmessage_send, PermissionGroup::client_basic, "b_client_even_textmessage_send", "Allows the client to send text messages to himself"), + make_shared(PermissionType::i_client_private_textmessage_power, PermissionGroup::client_basic, "i_client_private_textmessage_power", "Client private message power"), + make_shared(PermissionType::i_client_needed_private_textmessage_power, PermissionGroup::client_basic, "i_client_needed_private_textmessage_power", "Needed client private message power"), + make_shared(PermissionType::b_client_server_textmessage_send, PermissionGroup::client_basic, "b_client_server_textmessage_send", "Send text messages to virtual server"), + make_shared(PermissionType::b_client_channel_textmessage_send, PermissionGroup::client_basic, "b_client_channel_textmessage_send", "Send text messages to channel"), + make_shared(PermissionType::b_client_offline_textmessage_send, PermissionGroup::client_basic, "b_client_offline_textmessage_send", "Send offline messages to clients"), + make_shared(PermissionType::i_client_talk_power, PermissionGroup::client_basic, "i_client_talk_power", "Client talk power"), + make_shared(PermissionType::i_client_needed_talk_power, PermissionGroup::client_basic, "i_client_needed_talk_power", "Needed client talk power"), + make_shared(PermissionType::i_client_poke_power, PermissionGroup::client_basic, "i_client_poke_power", "Client poke power"), + make_shared(PermissionType::i_client_needed_poke_power, PermissionGroup::client_basic, "i_client_needed_poke_power", "Needed client poke power"), + make_shared(PermissionType::b_client_set_flag_talker, PermissionGroup::client_basic, "b_client_set_flag_talker", "Set the talker flag for clients and allow them to speak"), + make_shared(PermissionType::i_client_whisper_power, PermissionGroup::client_basic, "i_client_whisper_power", "Client whisper power"), + make_shared(PermissionType::i_client_needed_whisper_power, PermissionGroup::client_basic, "i_client_needed_whisper_power", "Client needed whisper power"), + make_shared(PermissionType::b_client_modify_description, PermissionGroup::client_modify, "b_client_modify_description", "Edit a clients description"), + make_shared(PermissionType::b_client_modify_own_description, PermissionGroup::client_modify, "b_client_modify_own_description", "Allow client to edit own description"), + make_shared(PermissionType::b_client_modify_dbproperties, PermissionGroup::client_modify, "b_client_modify_dbproperties", "Edit a clients properties in the sql"), + make_shared(PermissionType::b_client_delete_dbproperties, PermissionGroup::client_modify, "b_client_delete_dbproperties", "Delete a clients properties in the sql"), + make_shared(PermissionType::b_client_create_modify_serverquery_login, PermissionGroup::client_modify, "b_client_create_modify_serverquery_login", "Create or modify own ServerQuery account"), + + make_shared(PermissionType::b_client_query_create, PermissionGroup::client_modify, "b_client_query_create", "Create your own ServerQuery account"), + make_shared(PermissionType::b_client_query_list, PermissionGroup::client_modify, "b_client_query_list", "List all ServerQuery accounts"), + make_shared(PermissionType::b_client_query_list_own, PermissionGroup::client_modify, "b_client_query_list_own", "List all own ServerQuery accounts"), + make_shared(PermissionType::b_client_query_rename, PermissionGroup::client_modify, "b_client_query_rename", "Rename a ServerQuery account"), + make_shared(PermissionType::b_client_query_rename_own, PermissionGroup::client_modify, "b_client_query_rename_own", "Rename the own ServerQuery account"), + make_shared(PermissionType::b_client_query_change_password, PermissionGroup::client_modify, "b_client_query_change_password", "Change a server query accounts password"), + make_shared(PermissionType::b_client_query_change_own_password, PermissionGroup::client_modify, "b_client_query_change_own_password", "Change a query accounts own password"), + make_shared(PermissionType::b_client_query_change_password_global, PermissionGroup::client_modify, "b_client_query_change_password_global", "Change a global query accounts own password"), + make_shared(PermissionType::b_client_query_delete, PermissionGroup::client_modify, "b_client_query_delete", "Delete a query accounts password"), + make_shared(PermissionType::b_client_query_delete_own, PermissionGroup::client_modify, "b_client_query_delete_own", "Delete own query accounts password"), + +#ifndef LAGENCY + make_shared(PermissionType::b_client_music_create_temporary, PermissionGroup::client, "b_client_music_create_temporary", "Permission to create a music bot"), + make_shared(PermissionType::b_client_music_create_semi_permanent, PermissionGroup::client, "b_client_music_create_semi_permanent", "Allows the client to create semi permanent music bots"), + make_shared(PermissionType::b_client_music_create_permanent, PermissionGroup::client, "b_client_music_create_permanent", "Allows the client to create permanent music bots"), + make_shared(PermissionType::b_client_music_modify_temporary, PermissionGroup::client, "b_client_music_modify_temporary", "Permission to make a music bot temporary"), + make_shared(PermissionType::b_client_music_modify_semi_permanent, PermissionGroup::client, "b_client_music_modify_semi_permanent", "Allows the client to make a bot semi permanent"), + make_shared(PermissionType::b_client_music_modify_permanent, PermissionGroup::client, "b_client_music_modify_permanent", "Allows the client to make a bot permanent"), + make_shared(PermissionType::i_client_music_create_modify_max_volume, PermissionGroup::client, "i_client_music_create_modify_max_volume", "Sets the max allowed music bot volume"), + make_shared(PermissionType::i_client_music_limit, PermissionGroup::client, "i_client_music_limit", "The limit of music bots bound to this client"), + make_shared(PermissionType::i_client_music_delete_power, PermissionGroup::client, "i_client_music_delete_power", "Power to delete the music bot"), + make_shared(PermissionType::i_client_music_needed_delete_power, PermissionGroup::client, "i_client_music_needed_delete_power", "Required power to delete the music bot"), + make_shared(PermissionType::i_client_music_play_power, PermissionGroup::client, "i_client_music_play_power", "Power to play music"), + make_shared(PermissionType::i_client_music_needed_modify_power, PermissionGroup::client, "i_client_music_needed_modify_power", "Required power to modify the bot settings"), + make_shared(PermissionType::i_client_music_modify_power, PermissionGroup::client, "i_client_music_modify_power", "Power to modify the music bot settings"), + make_shared(PermissionType::i_client_music_needed_play_power, PermissionGroup::client, "i_client_music_needed_play_power", "Required power to play music"), + make_shared(PermissionType::i_client_music_rename_power, PermissionGroup::client, "i_client_music_rename_power", "Power to rename the bot"), + make_shared(PermissionType::i_client_music_needed_rename_power, PermissionGroup::client, "i_client_music_needed_rename_power", "The required rename power for a music bot"), + + make_shared(PermissionType::b_playlist_create, PermissionGroup::client, "b_playlist_create", "Allows the client to create playlists"), + make_shared(PermissionType::i_playlist_view_power, PermissionGroup::client, "i_playlist_view_power", "Power to see a playlist, and their songs"), + make_shared(PermissionType::i_playlist_needed_view_power, PermissionGroup::client, "i_playlist_needed_view_power", "Needed power to see a playlist, and their songs"), + make_shared(PermissionType::i_playlist_modify_power, PermissionGroup::client, "i_playlist_modify_power", "Power to modify the playlist properties"), + make_shared(PermissionType::i_playlist_needed_modify_power, PermissionGroup::client, "i_playlist_needed_modify_power", "Needed power to modify the playlist properties"), + make_shared(PermissionType::i_playlist_permission_modify_power, PermissionGroup::client, "i_playlist_permission_modify_power", "Power to modify the playlist permissions"), + make_shared(PermissionType::i_playlist_needed_permission_modify_power, PermissionGroup::client, "i_playlist_needed_permission_modify_power", "Needed power to modify the playlist permissions"), + make_shared(PermissionType::i_playlist_delete_power, PermissionGroup::client, "i_playlist_delete_power", "Power to delete the playlist"), + make_shared(PermissionType::i_playlist_needed_delete_power, PermissionGroup::client, "i_playlist_needed_delete_power", "Needed power to delete the playlist"), + + make_shared(PermissionType::i_playlist_song_add_power, PermissionGroup::client, "i_playlist_song_add_power", "Power to add songs to a playlist"), + make_shared(PermissionType::i_playlist_song_needed_add_power, PermissionGroup::client, "i_playlist_song_needed_add_power", "Needed power to add songs to a playlist"), + make_shared(PermissionType::i_playlist_song_remove_power, PermissionGroup::client, "i_playlist_song_remove_power", "Power to remove songs from a playlist"), + make_shared(PermissionType::i_playlist_song_needed_remove_power, PermissionGroup::client, "i_playlist_song_needed_remove_power", "Needed power to remove songs from a playlist"), + make_shared(PermissionType::i_playlist_song_move_power, PermissionGroup::client, "i_playlist_song_remove_power", "Power to move songs witin a playlist"), + make_shared(PermissionType::i_playlist_song_needed_move_power, PermissionGroup::client, "i_playlist_song_needed_remove_power", "Needed power to move songs witin a playlist"), + + make_shared(PermissionType::b_client_use_bbcode_any, PermissionGroup::client, "b_client_use_bbcode_any", "Allows the client to use any bbcodes"), + make_shared(PermissionType::b_client_use_bbcode_image, PermissionGroup::client, "b_client_use_bbcode_image", "Allows the client to use img bbcode"), + make_shared(PermissionType::b_client_use_bbcode_url, PermissionGroup::client, "b_client_use_bbcode_url", "Allows the client to use url bbcode"), +#endif + make_shared(PermissionType::b_ft_ignore_password, PermissionGroup::ft, "b_ft_ignore_password", "Browse files without channel password"), + make_shared(PermissionType::b_ft_transfer_list, PermissionGroup::ft, "b_ft_transfer_list", "Retrieve list of running filetransfers"), + make_shared(PermissionType::i_ft_file_upload_power, PermissionGroup::ft, "i_ft_file_upload_power", "File upload power"), + make_shared(PermissionType::i_ft_needed_file_upload_power, PermissionGroup::ft, "i_ft_needed_file_upload_power", "Needed file upload power"), + make_shared(PermissionType::i_ft_file_download_power, PermissionGroup::ft, "i_ft_file_download_power", "File download power"), + make_shared(PermissionType::i_ft_needed_file_download_power, PermissionGroup::ft, "i_ft_needed_file_download_power", "Needed file download power"), + make_shared(PermissionType::i_ft_file_delete_power, PermissionGroup::ft, "i_ft_file_delete_power", "File delete power"), + make_shared(PermissionType::i_ft_needed_file_delete_power, PermissionGroup::ft, "i_ft_needed_file_delete_power", "Needed file delete power"), + make_shared(PermissionType::i_ft_file_rename_power, PermissionGroup::ft, "i_ft_file_rename_power", "File rename power"), + make_shared(PermissionType::i_ft_needed_file_rename_power, PermissionGroup::ft, "i_ft_needed_file_rename_power", "Needed file rename power"), + make_shared(PermissionType::i_ft_file_browse_power, PermissionGroup::ft, "i_ft_file_browse_power", "File browse power"), + make_shared(PermissionType::i_ft_needed_file_browse_power, PermissionGroup::ft, "i_ft_needed_file_browse_power", "Needed file browse power"), + make_shared(PermissionType::i_ft_directory_create_power, PermissionGroup::ft, "i_ft_directory_create_power", "Create directory power"), + make_shared(PermissionType::i_ft_needed_directory_create_power, PermissionGroup::ft, "i_ft_needed_directory_create_power", "Needed create directory power"), + make_shared(PermissionType::i_ft_quota_mb_download_per_client, PermissionGroup::ft, "i_ft_quota_mb_download_per_client", "Download quota per client in MByte"), + make_shared(PermissionType::i_ft_quota_mb_upload_per_client, PermissionGroup::ft, "i_ft_quota_mb_upload_per_client", "Upload quota per client in MByte") +}; + +deque ts::permission::neededPermissions = { + b_client_force_push_to_talk, + b_channel_join_ignore_password, + b_ft_ignore_password, + i_client_max_avatar_filesize, + i_client_max_channel_subscriptions, + i_permission_modify_power, + b_virtualserver_servergroup_permission_list, + b_virtualserver_client_permission_list, + b_virtualserver_channelgroup_permission_list, + b_virtualserver_channelclient_permission_list, + b_virtualserver_playlist_permission_list, + b_virtualserver_channelgroup_client_list, + b_client_permissionoverview_view, + b_client_ban_list, + b_client_ban_trigger_list, + b_client_complain_list, + b_client_complain_delete, + b_client_complain_delete_own, + b_virtualserver_log_view, + b_client_create_modify_serverquery_login, + b_virtualserver_connectioninfo_view, + b_client_modify_description, + b_client_server_textmessage_send, + b_client_channel_textmessage_send, +#ifdef LAGENCY +i_group_modify_power, + i_group_member_add_power, + i_group_member_remove_power, +#else + i_displayed_group_modify_power, + i_displayed_group_member_add_power, + i_displayed_group_member_remove_power, + + i_server_group_modify_power, + i_server_group_member_add_power, + i_server_group_self_add_power, + i_server_group_member_remove_power, + i_server_group_self_remove_power, + + i_channel_group_modify_power, + i_channel_group_member_add_power, + i_channel_group_self_add_power, + i_channel_group_member_remove_power, + i_channel_group_self_remove_power, +#endif + i_ft_file_browse_power, + b_permission_modify_power_ignore, + b_virtualserver_modify_hostmessage, + b_virtualserver_modify_ft_settings, + b_virtualserver_modify_default_musicgroup, + b_virtualserver_modify_default_servergroup, + b_virtualserver_modify_default_channelgroup, + b_virtualserver_modify_default_channeladmingroup, + b_virtualserver_modify_hostbanner, + b_virtualserver_modify_channel_forced_silence, + b_virtualserver_modify_needed_identity_security_level, + b_virtualserver_modify_name, + b_virtualserver_modify_welcomemessage, + b_virtualserver_modify_maxclients, + b_virtualserver_modify_password, + b_virtualserver_modify_complain, + b_virtualserver_modify_antiflood, + b_virtualserver_modify_ft_quotas, + b_virtualserver_modify_hostbutton, + b_virtualserver_modify_autostart, + b_virtualserver_modify_port, + b_virtualserver_modify_host, + b_virtualserver_modify_log_settings, + b_virtualserver_modify_priority_speaker_dimm_modificator, + b_virtualserver_modify_music_bot_limit, + b_virtualserver_modify_default_messages, + b_channel_modify_name, + b_channel_modify_password, + b_channel_modify_topic, + b_channel_modify_description, + b_channel_modify_codec, + b_channel_modify_codec_quality, + b_channel_modify_make_permanent, + b_channel_modify_make_semi_permanent, + b_channel_modify_make_temporary, + b_channel_modify_make_default, + b_channel_modify_maxclients, + b_channel_modify_maxfamilyclients, + b_channel_modify_sortorder, + b_channel_modify_needed_talk_power, + b_channel_create_child, + b_channel_create_permanent, + b_channel_create_temporary, + b_channel_create_with_topic, + b_channel_create_with_description, + b_channel_create_with_password, + b_channel_create_with_maxclients, + b_channel_create_with_maxfamilyclients, + b_channel_create_with_sortorder, + b_channel_create_with_default, + b_channel_create_modify_with_codec_speex8, + b_channel_create_modify_with_codec_speex16, + b_channel_create_modify_with_codec_speex32, + b_channel_create_modify_with_codec_celtmono48, + b_channel_create_modify_with_codec_opusvoice, + b_channel_create_modify_with_codec_opusmusic, + i_channel_create_modify_with_codec_maxquality, + i_client_serverquery_view_power, + b_channel_create_semi_permanent, + b_serverinstance_modify_querygroup, + b_serverinstance_modify_templates, + b_virtualserver_channel_permission_list, + b_channel_delete_permanent, + b_channel_delete_semi_permanent, + b_channel_delete_temporary, + b_channel_delete_flag_force, + b_client_set_flag_talker, + b_channel_create_with_needed_talk_power, + b_virtualserver_token_list, + b_virtualserver_token_add, + b_virtualserver_token_use, + b_virtualserver_token_delete, + b_client_ban_create, + b_client_ban_create_global, + b_client_ban_name, + b_client_ban_ip, + b_client_ban_hwid, + b_virtualserver_servergroup_list, + b_virtualserver_servergroup_client_list, + b_virtualserver_channelgroup_list, + i_client_ban_max_bantime, + b_icon_manage, + i_max_icon_filesize, + i_max_playlist_size, + i_max_playlists, + + i_client_needed_whisper_power, + i_client_whisper_power, + + b_virtualserver_modify_icon_id, + b_client_modify_own_description, + b_client_offline_textmessage_send, + b_virtualserver_client_dblist, + b_virtualserver_modify_reserved_slots, + i_channel_create_modify_with_codec_latency_factor_min, + b_channel_modify_codec_latency_factor, + b_channel_modify_make_codec_encrypted, + b_virtualserver_modify_codec_encryption_mode, + b_client_use_channel_commander, + b_virtualserver_servergroup_create, + b_virtualserver_channelgroup_create, + b_client_permissionoverview_own, + i_ft_quota_mb_upload_per_client, + i_ft_quota_mb_download_per_client, + b_virtualserver_modify_weblist, + b_virtualserver_channelgroup_delete, + b_virtualserver_servergroup_delete, + b_client_ban_delete, + b_client_ban_delete_own, + b_client_ban_delete_global, + b_client_ban_delete_own_global, + b_virtualserver_modify_temporary_passwords, + b_virtualserver_modify_temporary_passwords_own, + b_client_request_talker, + b_client_avatar_delete_other, + b_channel_create_modify_with_force_password, + b_channel_create_private, + b_channel_join_ignore_maxclients, + b_virtualserver_modify_channel_temp_delete_delay_default, + i_channel_create_modify_with_temp_delete_delay, + b_channel_modify_temp_delete_delay, + i_client_poke_power, + + b_client_ban_list_global, + b_client_ban_list, + + b_client_remoteaddress_view, + + i_client_music_play_power, + i_client_music_modify_power, + i_client_music_rename_power, + b_client_music_create_temporary, + b_client_music_create_semi_permanent, + b_client_music_create_permanent, + b_client_music_modify_temporary, + b_client_music_modify_semi_permanent, + b_client_music_modify_permanent, + i_client_music_create_modify_max_volume, + + b_playlist_create, + i_playlist_view_power, + i_playlist_modify_power, + i_playlist_permission_modify_power, + i_playlist_delete_power, + + i_playlist_song_add_power, + i_playlist_song_remove_power, + i_playlist_song_move_power, +}; + +std::deque permission::availableGroups = { + global, + global_info, + global_vsmanage, + global_admin, + global_settings, + vs, + vs_info, + vs_admin, + vs_settings, + channel, + channel_info, + channel_create, + channel_modify, + channel_delete, + channel_access, + group, + group_info, + group_create, + group_modify, + group_delete, + client, + client_info, + client_admin, + client_basic, + client_modify, + ft +}; + +std::shared_ptr PermissionTypeEntry::unknown = make_shared(PermissionType::unknown, PermissionGroup::global, "unknown", "unknown"); + +std::shared_ptr permission::resolvePermissionData(PermissionType type){ + if((type & PERM_ID_GRANT) > 0) type &= ~PERM_ID_GRANT; + for(auto& elm : availablePermissions) + if(elm->type == type) return elm; + return PermissionTypeEntry::unknown; +} + +std::shared_ptr permission::resolvePermissionData(const std::string& name) { + for(auto& elm : availablePermissions) { + if(elm->name == name || elm->grant_name == name) { + return elm; + } + } + debugMessage(LOG_GENERAL, "Could not find permission {}.", name); + return PermissionTypeEntry::unknown; +} + +ChannelId Permission::channelId() { + return this->channel ? this->channel->channelId() : 0; +} + +PermissionManager::PermissionManager() { + memtrack::allocated(this); +} +PermissionManager::~PermissionManager() { + memtrack::freed(this); +} + +std::shared_ptr PermissionManager::registerPermission(ts::permission::PermissionType type, + ts::permission::PermissionValue val, + const std::shared_ptr &channel, + uint16_t flag) { + return this->registerPermission(resolvePermissionData(type), val, channel, flag); +} + +std::shared_ptr PermissionManager::registerPermission(const std::shared_ptr& type, PermissionValue value, const std::shared_ptr& channel, uint16_t flagMask) { + { + auto found = getPermission(type->type, channel, false); + if(!found.empty()) return found.front(); + } + auto permission = std::make_shared(type, value, permNotGranted, flagMask, channel); + this->permissions.push_back(permission); + for(auto& elm : this->updateHandler) + elm(permission); + return permission; +} + +bool PermissionManager::hasPermission(PermissionType type, const std::shared_ptr& channel, bool testGlobal) { + return !this->getPermission(type, channel, testGlobal).empty(); +} + +bool PermissionManager::setPermission(PermissionType type, PermissionValue value, const std::shared_ptr& channel) { + auto list = this->getPermission(type, channel, false); + if(list.empty()) + list.push_back(registerPermission(type, value, channel, PERM_FLAG_PUBLIC)); + + list.front()->value = value; + for(const auto& elm : this->updateHandler) + elm(list.front()); + + return true; +} + +bool PermissionManager::setPermission(ts::permission::PermissionType type, ts::permission::PermissionValue value, const std::shared_ptr &channel, bool negated, bool skiped) { + auto list = this->getPermission(type, channel, false); + if(list.empty()) + list.push_back(registerPermission(type, value, channel, PERM_FLAG_PUBLIC)); + + list.front()->value = value; + list.front()->flag_negate = negated; + list.front()->flag_skip = skiped; + + for(const auto& elm : this->updateHandler) + elm(list.front()); + + return true; +} + +bool PermissionManager::setPermissionGranted(PermissionType type, PermissionValue value, const std::shared_ptr& channel) { + auto list = this->getPermission(type, channel, false); + if(list.empty()) + list.push_back(registerPermission(type, permNotGranted, channel, PERM_FLAG_PUBLIC)); + list.front()->granted = value; + + for(auto& elm : this->updateHandler) + elm(list.front()); + + return true; +} + +void PermissionManager::deletePermission(PermissionType type, const std::shared_ptr& channel) { + auto list = getPermission(type, channel, false); + if(list.empty()) return; + list.front()->value = permNotGranted; + //list.front()->deleted = true; + //this->permissions.erase(std::find(this->permissions.begin(), this->permissions.end(), list.front())); + + for(auto& e : this->updateHandler) + e(list.front()); +} + +std::deque> PermissionManager::getPermission(PermissionType type, const std::shared_ptr& channel, bool testGlobal) { + std::deque> res; + + std::shared_ptr channel_permission; + std::shared_ptr global_permission; + for(const auto &perm : this->permissions) + if(perm->type->type == type) { + if(perm->channel == channel) + channel_permission = perm; + else if(!perm->channel && testGlobal) + global_permission = perm; + } + if(channel_permission) res.push_back(channel_permission); + if(global_permission) res.push_back(global_permission); + return res; +} + +std::vector> PermissionManager::listPermissions(uint16_t flags) { + vector> result; + for(const auto &perm : this->permissions) + if((perm->flagMask & flags) > 0 || true) //FIXME? + result.push_back(perm); + return result; +} + +std::vector> PermissionManager::allPermissions() { + return std::vector>(this->permissions.begin(), this->permissions.end()); +} + +std::deque> PermissionManager::all_channel_specific_permissions() { + std::deque> result; + + for(const auto& permission : this->permissions) + if(permission->channel) + result.push_back(permission); + + return result; +} + +std::deque> PermissionManager::all_channel_unspecific_permissions() { + std::deque> result; + + for(const auto& permission : this->permissions) + if(!permission->channel) + result.push_back(permission); + + return result; +} + +teamspeak::MapType teamspeak::unmapping; +teamspeak::MapType teamspeak::mapping; + +teamspeak::MapType build_mapping(){ + return { + {teamspeak::GENERAL, { + {"b_virtualserver_modify_port", {"b_virtualserver_modify_port", "b_virtualserver_modify_host"}}, + {"i_client_max_clones_uid", {"i_client_max_clones_uid", "i_client_max_clones_ip", "i_client_max_clones_hwid"}}, + {"b_client_ignore_bans", {"b_client_ignore_bans", "b_client_ignore_vpn"}}, + {"b_client_ban_list", {"b_client_ban_list", "b_client_ban_list_global"}}, + + //Permissions which TeaSpeak has but TeamSpeak not + {"", {"b_virtualserver_modify_music_bot_limit"}}, + {"", {"b_client_music_channel_list"}}, + {"", {"b_client_music_server_list"}}, + {"", {"i_client_music_info"}}, + {"", {"i_client_music_needed_info"}}, + + {"", {"b_client_ban_edit"}}, + {"", {"b_client_ban_edit_global"}}, + {"", {"b_client_ban_create_global"}}, + {"", {"b_client_ban_delete_own_global"}}, + {"", {"b_client_ban_delete_global"}}, + + {"", {"b_client_even_textmessage_send"}}, + {"", {"b_client_enforce_valid_hwid"}}, + {"", {"b_client_allow_invalid_packet"}}, + {"", {"b_client_allow_invalid_badges"}}, + + {"", {"b_client_music_create"}}, + {"", {"b_client_music_delete_own"}}, + + {"", {"i_client_music_limit"}}, + {"", {"i_client_music_needed_delete_power"}}, + {"", {"i_client_music_delete_power"}}, + {"", {"i_client_music_play_power"}}, + {"", {"i_client_music_needed_play_power"}}, + {"", {"i_client_music_rename_power"}}, + {"", {"i_client_music_needed_rename_power"}}, + + {"", {"b_client_use_bbcode_any"}}, + {"", {"b_client_use_bbcode_url"}}, + {"", {"b_client_use_bbcode_image"}}, + + {"", {"b_channel_ignore_view_power"}}, + {"", {"i_channel_view_power"}}, + {"", {"i_channel_needed_view_power"}}, + + {"", {"i_client_max_channels"}}, + {"", {"i_client_max_temporary_channels"}}, + {"", {"i_client_max_semi_channels"}}, + {"", {"i_client_max_permanent_channels"}}, + + {"", {"b_virtualserver_modify_default_messages"}}, + + {"b_client_ban_list", {"b_client_ban_list", "b_client_ban_trigger_list"}}, + {"b_virtualserver_select", {"b_virtualserver_select", "b_virtualserver_select_godmode"}}, + {"b_virtualserver_modify_default_servergroup", {"b_virtualserver_modify_default_servergroup", "b_virtualserver_modify_default_musicgroup"}}, + + {"b_client_ban_create", {"b_client_ban_create", "b_client_ban_name", "b_client_ban_ip", "b_client_ban_hwid"}} + }, + }, + {teamspeak::SERVER, { + {"i_group_modify_power", {"i_server_group_modify_power", "i_channel_group_modify_power", "i_displayed_group_modify_power"}}, + {"i_group_member_add_power", {"i_server_group_member_add_power", "i_channel_group_member_add_power", "i_displayed_group_member_add_power"}}, + {"i_group_member_remove_power", {"i_server_group_member_remove_power", "i_channel_group_member_remove_power", "i_displayed_group_member_remove_power"}}, + {"i_group_needed_modify_power", {"i_server_group_needed_modify_power", "i_channel_group_needed_modify_power", "i_displayed_group_needed_modify_power"}}, + {"i_group_needed_member_add_power", {"i_server_group_needed_member_add_power", "i_channel_group_needed_member_add_power", "i_displayed_group_needed_member_add_power"}}, + {"i_group_needed_member_remove_power", {"i_server_group_needed_member_remove_power", "i_channel_group_needed_member_remove_power", "i_displayed_group_needed_member_remove_power"}}, + }, + }, + {teamspeak::CLIENT, { + {"i_group_modify_power", {"i_server_group_modify_power", "i_channel_group_modify_power", "i_displayed_group_modify_power"}}, + {"i_group_member_add_power", {"i_server_group_member_add_power", "i_channel_group_member_add_power", "i_displayed_group_member_add_power"}}, + {"i_group_member_remove_power", {"i_server_group_member_remove_power", "i_channel_group_member_remove_power", "i_displayed_group_member_remove_power"}}, + {"i_group_needed_modify_power", {"i_server_group_needed_modify_power", "i_channel_group_needed_modify_power", "i_displayed_group_needed_modify_power"}}, + {"i_group_needed_member_add_power", {"i_server_group_needed_member_add_power", "i_channel_group_needed_member_add_power", "i_displayed_group_needed_member_add_power"}}, + {"i_group_needed_member_remove_power", {"i_server_group_needed_member_remove_power", "i_channel_group_needed_member_remove_power", "i_displayed_group_needed_member_remove_power"}}, + }, + }, + {teamspeak::CHANNEL, { + {"i_group_modify_power", {"i_channel_group_modify_power", "i_displayed_group_modify_power"}}, + {"i_group_member_add_power", {"i_channel_group_member_add_power", "i_displayed_group_member_add_power"}}, + {"i_group_member_remove_power", {"i_channel_group_member_remove_power", "i_displayed_group_member_remove_power"}}, + {"i_group_needed_modify_power", {"i_channel_group_needed_modify_power", "i_displayed_group_needed_modify_power"}}, + {"i_group_needed_member_add_power", {"i_channel_group_needed_member_add_power", "i_displayed_group_needed_member_add_power"}}, + {"i_group_needed_member_remove_power", {"i_channel_group_needed_member_remove_power", "i_displayed_group_needed_member_remove_power"}}, + }, + }, + }; +}; + +inline teamspeak::MapType build_unmapping() { + teamspeak::MapType result; + + for(const auto& map : teamspeak::mapping) { + for(const auto& entry : map.second) { + for(const auto& key : entry.second) { + auto& m = result[map.first]; + auto it = m.find(key); + if(it == m.end()) + m.insert({key, {}}); + it = m.find(key); + if(!entry.first.empty()) it->second.push_back(entry.first); + } + } + } + + return result; +}; + +inline void init_mapping() { + if(teamspeak::mapping.empty()) teamspeak::mapping = build_mapping(); + if(teamspeak::unmapping.empty()) teamspeak::unmapping = build_unmapping(); +} + +template +inline deque operator+(const deque& a, const deque& b) { + deque result; + result.insert(result.end(), a.begin(), a.end()); + result.insert(result.end(), b.begin(), b.end()); + return result; +} + +inline deque mmget(teamspeak::MapType& map, teamspeak::GroupType type, const std::string& key) { + return map[type].count(key) > 0 ? map[type].find(key)->second : deque{}; +} + +inline std::deque map_entry(std::string key, teamspeak::GroupType type, teamspeak::MapType& map_table) { + init_mapping(); + + if(key.find("_needed_modify_power_") == 1) { + key = key.substr(strlen("x_needed_modify_power_")); + + deque result; + if(map_table[type].count("i_" + key) > 0 || map_table[teamspeak::GroupType::GENERAL].count("i_" + key) > 0) result = mmget(map_table, type, "i_" + key) + mmget(map_table, teamspeak::GroupType::GENERAL, "i_" + key); + else if(map_table[type].count("b_" + key) > 0 || map_table[teamspeak::GroupType::GENERAL].count("b_" + key) > 0) result = mmget(map_table, type, "b_" + key) + mmget(map_table, teamspeak::GroupType::GENERAL, "b_" + key); + else result = {"x_" + key}; + + for(auto& entry : result) + entry = "i_needed_modify_power_" + entry.substr(2); + return result; + } + if(map_table[type].count(key) > 0 || map_table[teamspeak::GroupType::GENERAL].count(key) > 0) return mmget(map_table, type, key) + mmget(map_table, teamspeak::GroupType::GENERAL, key); + return {key}; +} + +std::deque teamspeak::map_key(std::string key, GroupType type) { + return map_entry(key, type, teamspeak::mapping); +} + +std::deque teamspeak::unmap_key(std::string key, GroupType type) { + return map_entry(key, type, teamspeak::unmapping); +} + + +#define AQB(name) \ +{update::QUERY_ADMIN, {name, 1, 100, false, false}}, \ +{update::SERVER_ADMIN, {name, 1, 75, false, false}}, \ + +#define AQBG(name) \ +{update::QUERY_ADMIN, {name, permNotGranted, 100, false, false}}, \ +{update::SERVER_ADMIN, {name, permNotGranted, 75, false, false}}, \ + +#define AQI(name) \ +{update::QUERY_ADMIN, {name, 100, 100, false, false}}, \ +{update::SERVER_ADMIN, {name, 75, 75, false, false}}, \ + +#define AQIG(name) \ +{update::QUERY_ADMIN, {name, permNotGranted, 100, false, false}}, \ +{update::SERVER_ADMIN, {name, permNotGranted, 75, false, false}}, \ + +deque update::migrate = { + AQB("b_virtualserver_modify_music_bot_limit") + {update::QUERY_ADMIN, {"b_client_music_channel_list", 1, 100, false, false}}, + {update::SERVER_ADMIN, {"b_client_music_channel_list", 1, 75, false, false}}, + {update::CHANNEL_ADMIN, {"b_client_music_channel_list", 1, 75, false, false}}, + + {update::QUERY_ADMIN, {"b_client_music_server_list", 1, 100, false, false}}, + {update::SERVER_ADMIN, {"b_client_music_server_list", 1, 75, false, false}}, + + {update::QUERY_ADMIN, {"i_client_music_info", 100, 100, false, false}}, + {update::SERVER_ADMIN, {"i_client_music_info", 75, 75, false, false}}, + + {update::QUERY_ADMIN, {"i_client_music_needed_info", permNotGranted, 100, false, false}}, + {update::SERVER_ADMIN, {"i_client_music_needed_info", permNotGranted, 75, false, false}}, + + {update::QUERY_ADMIN, {"b_client_ban_list_global", 1, 100, false, false}}, + {update::SERVER_ADMIN, {"b_client_ban_list_global", 1, 75, false, false}}, + + {update::QUERY_ADMIN, {"b_client_ban_edit", 1, 100, false, false}}, + {update::SERVER_ADMIN, {"b_client_ban_edit", 1, 75, false, false}}, + + {update::QUERY_ADMIN, {"b_client_ban_create_global", 1, 100, false, false}}, + {update::QUERY_ADMIN, {"b_client_ban_edit_global", 1, 100, false, false}}, + {update::QUERY_ADMIN, {"b_client_ban_delete_own_global", 1, 100, false, false}}, + {update::QUERY_ADMIN, {"b_client_ban_delete_global", 1, 100, false, false}}, + + AQB("b_client_even_textmessage_send") + AQBG("b_client_enforce_valid_hwid") + AQB("b_client_allow_invalid_packet") + AQB("b_client_allow_invalid_badges") + + AQB("b_client_music_create") + AQB("b_client_music_delete_own") + + AQI("i_client_music_limit") + AQIG("i_client_music_needed_delete_power") + AQI("i_client_music_delete_power") + AQI("i_client_music_play_power") + AQIG("i_client_music_needed_play_power") + AQI("i_client_music_rename_power") + AQIG("i_client_music_needed_rename_power") + + + AQB("b_client_use_bbcode_any") + AQB("b_client_use_bbcode_url") + AQB("b_client_use_bbcode_image") + + {update::QUERY_ADMIN, {"b_channel_ignore_view_power", 1, 100, false, false}}, + AQI("i_channel_view_power") + AQIG("i_channel_needed_view_power") + + AQB("b_client_ignore_vpn") + + AQIG("i_client_max_channels") + AQIG("i_client_max_temporary_channels") + AQIG("i_client_max_semi_channels") + AQIG("i_client_max_permanent_channels") + {update::SERVER_NORMAL, {"i_client_max_channels", 2, permNotGranted, false, false}}, + {update::SERVER_GUEST, {"i_client_max_channels", 1, permNotGranted, false, false}}, + + AQB("b_virtualserver_modify_default_messages") + AQB("b_virtualserver_modify_default_musicgroup") + AQB("b_channel_ignore_join_power") + AQB("b_virtualserver_select_godmode") + AQB("b_client_ban_trigger_list") +}; \ No newline at end of file diff --git a/src/PermissionManager.h b/src/PermissionManager.h new file mode 100644 index 0000000..1664572 --- /dev/null +++ b/src/PermissionManager.h @@ -0,0 +1,725 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include "Definitions.h" +#include "Variable.h" + +#define permNotGranted (-2) +#define PERM_ID_GRANT ((ts::permission::PermissionType) (1 << 15)) + +namespace ts { + class BasicChannel; + namespace permission { + typedef int32_t PermissionValue; + + enum PermissionSqlType { + SQL_PERM_GROUP, + SQL_PERM_CHANNEL, + SQL_PERM_USER, + SQL_PERM_PLAYLIST + }; + enum PermissionType : uint16_t { + undefined = (uint16_t) -1, + unknown = (uint16_t) 0, + type_begin = 1, + + /* global */ + + /* global::information */ + b_serverinstance_help_view = type_begin, + b_serverinstance_version_view, + b_serverinstance_info_view, + b_serverinstance_virtualserver_list, + b_serverinstance_binding_list, + b_serverinstance_permission_list, + b_serverinstance_permission_find, + //b_serverinstance_allow_teaspeak_plugin, + + /* global::vs_management */ + b_virtualserver_create, + b_virtualserver_delete, + b_virtualserver_start_any, + b_virtualserver_stop_any, + b_virtualserver_change_machine_id, + b_virtualserver_change_template, + + /* global::administration */ + b_serverquery_login, + b_serverinstance_textmessage_send, + b_serverinstance_log_view, + b_serverinstance_log_add, + b_serverinstance_stop, + + /* global::settings */ + b_serverinstance_modify_settings, + b_serverinstance_modify_querygroup, + b_serverinstance_modify_templates, + + /* virtual_server */ + + /* virtual_server::information */ + b_virtualserver_select, + b_virtualserver_select_godmode, + b_virtualserver_info_view, + b_virtualserver_connectioninfo_view, + b_virtualserver_channel_list, + b_virtualserver_channel_search, + b_virtualserver_client_list, + b_virtualserver_client_search, + b_virtualserver_client_dblist, + b_virtualserver_client_dbsearch, + b_virtualserver_client_dbinfo, + b_virtualserver_permission_find, + b_virtualserver_custom_search, + + /* virtual_server::administration */ + b_virtualserver_start, + b_virtualserver_stop, + b_virtualserver_token_list, + b_virtualserver_token_add, + b_virtualserver_token_use, + b_virtualserver_token_delete, + b_virtualserver_log_view, + b_virtualserver_log_add, + b_virtualserver_join_ignore_password, + b_virtualserver_notify_register, + b_virtualserver_notify_unregister, + b_virtualserver_snapshot_create, + b_virtualserver_snapshot_deploy, + b_virtualserver_permission_reset, + + /* virtual_server::settings */ + b_virtualserver_modify_name, + b_virtualserver_modify_welcomemessage, + b_virtualserver_modify_maxclients, + b_virtualserver_modify_reserved_slots, + b_virtualserver_modify_password, + b_virtualserver_modify_default_servergroup, + b_virtualserver_modify_default_musicgroup, + b_virtualserver_modify_default_channelgroup, + b_virtualserver_modify_default_channeladmingroup, + b_virtualserver_modify_channel_forced_silence, + b_virtualserver_modify_complain, + b_virtualserver_modify_antiflood, + b_virtualserver_modify_ft_settings, + b_virtualserver_modify_ft_quotas, + b_virtualserver_modify_hostmessage, + b_virtualserver_modify_hostbanner, + b_virtualserver_modify_hostbutton, + b_virtualserver_modify_port, +#ifndef LAGENCY + b_virtualserver_modify_host, + b_virtualserver_modify_default_messages, +#endif + b_virtualserver_modify_autostart, + b_virtualserver_modify_needed_identity_security_level, + b_virtualserver_modify_priority_speaker_dimm_modificator, + b_virtualserver_modify_log_settings, + b_virtualserver_modify_min_client_version, + b_virtualserver_modify_icon_id, + b_virtualserver_modify_weblist, + b_virtualserver_modify_codec_encryption_mode, + b_virtualserver_modify_temporary_passwords, + b_virtualserver_modify_temporary_passwords_own, + b_virtualserver_modify_channel_temp_delete_delay_default, + b_virtualserver_modify_music_bot_limit, + + /* channel */ + i_channel_min_depth, + i_channel_max_depth, + b_channel_group_inheritance_end, + i_channel_permission_modify_power, + i_channel_needed_permission_modify_power, + + /* channel::info */ + b_channel_info_view, + b_virtualserver_channel_permission_list, + + /* channel::create */ + b_channel_create_child, + b_channel_create_permanent, + b_channel_create_semi_permanent, + b_channel_create_temporary, + b_channel_create_private, + b_channel_create_with_topic, + b_channel_create_with_description, + b_channel_create_with_password, + b_channel_create_modify_with_codec_speex8, + b_channel_create_modify_with_codec_speex16, + b_channel_create_modify_with_codec_speex32, + b_channel_create_modify_with_codec_celtmono48, + b_channel_create_modify_with_codec_opusvoice, + b_channel_create_modify_with_codec_opusmusic, + i_channel_create_modify_with_codec_maxquality, + i_channel_create_modify_with_codec_latency_factor_min, + b_channel_create_with_maxclients, + b_channel_create_with_maxfamilyclients, + b_channel_create_with_sortorder, + b_channel_create_with_default, + b_channel_create_with_needed_talk_power, + b_channel_create_modify_with_force_password, + i_channel_create_modify_with_temp_delete_delay, + + /* channel::modify */ + b_channel_modify_parent, + b_channel_modify_make_default, + b_channel_modify_make_permanent, + b_channel_modify_make_semi_permanent, + b_channel_modify_make_temporary, + b_channel_modify_name, + b_channel_modify_topic, + b_channel_modify_description, + b_channel_modify_password, + b_channel_modify_codec, + b_channel_modify_codec_quality, + b_channel_modify_codec_latency_factor, + b_channel_modify_maxclients, + b_channel_modify_maxfamilyclients, + b_channel_modify_sortorder, + b_channel_modify_needed_talk_power, + i_channel_modify_power, + i_channel_needed_modify_power, + b_channel_modify_make_codec_encrypted, + b_channel_modify_temp_delete_delay, + + /* channel::delete */ + b_channel_delete_permanent, + b_channel_delete_semi_permanent, + b_channel_delete_temporary, + b_channel_delete_flag_force, + i_channel_delete_power, + i_channel_needed_delete_power, + + /* channel::access */ + b_channel_join_permanent, + b_channel_join_semi_permanent, + b_channel_join_temporary, + b_channel_join_ignore_password, + b_channel_join_ignore_maxclients, + i_channel_join_power, + i_channel_needed_join_power, + b_channel_ignore_join_power, + + i_channel_view_power, + i_channel_needed_view_power, + b_channel_ignore_view_power, + + i_channel_subscribe_power, + i_channel_needed_subscribe_power, + b_channel_ignore_subscribe_power, + + i_channel_description_view_power, + i_channel_needed_description_view_power, + b_channel_ignore_description_view_power, + + /* group */ + i_icon_id, + i_max_icon_filesize, + i_max_playlist_size, + i_max_playlists, + b_icon_manage, + b_group_is_permanent, + i_group_auto_update_type, + i_group_auto_update_max_value, + i_group_sort_id, + i_group_show_name_in_tree, + + /* group::info */ + b_virtualserver_servergroup_list, //Unused + b_virtualserver_servergroup_permission_list, + b_virtualserver_servergroup_client_list, + + b_virtualserver_channelgroup_list, //Unused + b_virtualserver_channelgroup_permission_list, + b_virtualserver_channelgroup_client_list, + + /* group::create */ + b_virtualserver_servergroup_create, + b_virtualserver_channelgroup_create, + + /* group::modify */ +#ifdef LAGENCY + i_group_modify_power, + i_group_needed_modify_power, + i_group_member_add_power, + i_group_needed_member_add_power, + i_group_member_remove_power, + i_group_needed_member_remove_power, +#else + //permission patch start + i_server_group_modify_power, + i_server_group_needed_modify_power, + i_server_group_member_add_power, + i_server_group_self_add_power, + i_server_group_needed_member_add_power, + i_server_group_member_remove_power, + i_server_group_self_remove_power, + i_server_group_needed_member_remove_power, + i_channel_group_modify_power, + i_channel_group_needed_modify_power, + i_channel_group_member_add_power, + i_channel_group_self_add_power, + i_channel_group_needed_member_add_power, + i_channel_group_member_remove_power, + i_channel_group_self_remove_power, + i_channel_group_needed_member_remove_power, + + i_displayed_group_member_add_power, + i_displayed_group_needed_member_add_power, + i_displayed_group_member_remove_power, + i_displayed_group_needed_member_remove_power, + i_displayed_group_modify_power, + i_displayed_group_needed_modify_power, + //permission patch end +#endif + i_permission_modify_power, + b_permission_modify_power_ignore, + + /* group::delete */ + b_virtualserver_servergroup_delete, + b_virtualserver_channelgroup_delete, + + /* client */ + i_client_permission_modify_power, + i_client_needed_permission_modify_power, + i_client_max_clones_uid, + i_client_max_clones_ip, + i_client_max_clones_hwid, + i_client_max_idletime, + i_client_max_avatar_filesize, + i_client_max_channel_subscriptions, + i_client_max_channels, + i_client_max_temporary_channels, + i_client_max_semi_channels, + i_client_max_permanent_channels, + b_client_use_priority_speaker, + b_client_is_priority_speaker, + b_client_skip_channelgroup_permissions, + b_client_force_push_to_talk, + b_client_ignore_bans, + b_client_ignore_vpn, + b_client_ignore_antiflood, + b_client_enforce_valid_hwid, + b_client_allow_invalid_packet, + b_client_allow_invalid_badges, + b_client_issue_client_query_command, + b_client_use_reserved_slot, + b_client_use_channel_commander, + b_client_request_talker, + b_client_avatar_delete_other, + b_client_is_sticky, + b_client_ignore_sticky, + + b_client_music_create_permanent, + b_client_music_create_semi_permanent, + b_client_music_create_temporary, + b_client_music_modify_permanent, + b_client_music_modify_semi_permanent, + b_client_music_modify_temporary, + i_client_music_create_modify_max_volume, + + i_client_music_limit, + i_client_music_needed_delete_power, + i_client_music_delete_power, + i_client_music_play_power, + i_client_music_needed_play_power, + i_client_music_modify_power, + i_client_music_needed_modify_power, + i_client_music_rename_power, + i_client_music_needed_rename_power, + + b_virtualserver_playlist_permission_list, + b_playlist_create, + i_playlist_view_power, + i_playlist_needed_view_power, + i_playlist_modify_power, + i_playlist_needed_modify_power, + i_playlist_permission_modify_power, + i_playlist_needed_permission_modify_power, + i_playlist_delete_power, + i_playlist_needed_delete_power, + + i_playlist_song_add_power, + i_playlist_song_needed_add_power, + i_playlist_song_remove_power, + i_playlist_song_needed_remove_power, + i_playlist_song_move_power, + i_playlist_song_needed_move_power, + + /* client::info */ + b_client_info_view, + b_client_permissionoverview_view, + b_client_permissionoverview_own, + b_client_remoteaddress_view, + i_client_serverquery_view_power, + i_client_needed_serverquery_view_power, + b_client_custom_info_view, + b_client_music_channel_list, + b_client_music_server_list, + i_client_music_info, + i_client_music_needed_info, + b_virtualserver_channelclient_permission_list, + b_virtualserver_client_permission_list, + + /* client::admin */ + i_client_kick_from_server_power, + i_client_needed_kick_from_server_power, + i_client_kick_from_channel_power, + i_client_needed_kick_from_channel_power, + i_client_ban_power, + i_client_needed_ban_power, + i_client_move_power, + i_client_needed_move_power, + i_client_complain_power, + i_client_needed_complain_power, + b_client_complain_list, + b_client_complain_delete_own, + b_client_complain_delete, + b_client_ban_list, + b_client_ban_list_global, + b_client_ban_trigger_list, + b_client_ban_create, + b_client_ban_create_global, + b_client_ban_name, + b_client_ban_ip, + b_client_ban_hwid, + b_client_ban_edit, + b_client_ban_edit_global, + b_client_ban_delete_own, + b_client_ban_delete, + b_client_ban_delete_own_global, + b_client_ban_delete_global, + i_client_ban_max_bantime, + + /* client::basics */ + i_client_private_textmessage_power, + i_client_needed_private_textmessage_power, + b_client_even_textmessage_send, + b_client_server_textmessage_send, + b_client_channel_textmessage_send, + b_client_offline_textmessage_send, + i_client_talk_power, + i_client_needed_talk_power, + i_client_poke_power, + i_client_needed_poke_power, + b_client_set_flag_talker, + i_client_whisper_power, + i_client_needed_whisper_power, + + /* client::modify */ + b_client_modify_description, + b_client_modify_own_description, + b_client_use_bbcode_any, + b_client_use_bbcode_url, + b_client_use_bbcode_image, + b_client_modify_dbproperties, + b_client_delete_dbproperties, + b_client_create_modify_serverquery_login, + b_client_query_create, + b_client_query_list, + b_client_query_list_own, + b_client_query_rename, + b_client_query_rename_own, + b_client_query_change_password, + b_client_query_change_own_password, + b_client_query_change_password_global, + b_client_query_delete, + b_client_query_delete_own, + + /* file_transfer */ + b_ft_ignore_password, + b_ft_transfer_list, + i_ft_file_upload_power, + i_ft_needed_file_upload_power, + i_ft_file_download_power, + i_ft_needed_file_download_power, + i_ft_file_delete_power, + i_ft_needed_file_delete_power, + i_ft_file_rename_power, + i_ft_needed_file_rename_power, + i_ft_file_browse_power, + i_ft_needed_file_browse_power, + i_ft_directory_create_power, + i_ft_needed_directory_create_power, + i_ft_quota_mb_download_per_client, + i_ft_quota_mb_upload_per_client + }; + inline PermissionType& operator&=(PermissionType& a, int b) { return a = (PermissionType) ((int) a & b); } + + enum PermissionGroup : uint16_t { + group_begin, + global = group_begin, + global_info = b_serverinstance_permission_find, + global_vsmanage = b_virtualserver_change_template, + global_admin = b_serverinstance_stop, + global_settings = b_serverinstance_modify_templates, + + vs = global_settings, /* we dont have any permissions in here */ + vs_info = b_virtualserver_custom_search, + vs_admin = b_virtualserver_permission_reset, +#ifdef LEGENCY + vs_settings = b_virtualserver_modify_channel_temp_delete_delay_default, +#else + vs_settings = b_virtualserver_modify_music_bot_limit, +#endif + + channel = i_channel_needed_permission_modify_power, + channel_info = b_virtualserver_channel_permission_list, + channel_create = i_channel_create_modify_with_temp_delete_delay, + channel_modify = b_channel_modify_temp_delete_delay, + channel_delete = i_channel_needed_delete_power, + channel_access = b_channel_ignore_description_view_power, + + group = i_group_show_name_in_tree, + group_info = b_virtualserver_channelgroup_client_list, + group_create = b_virtualserver_channelgroup_create, + group_modify = b_permission_modify_power_ignore, + group_delete = b_virtualserver_channelgroup_delete, +#ifdef LAGENCY + client = b_client_ignore_sticky, +#else + client = i_playlist_song_needed_move_power, +#endif +#ifdef LAGENCY + client_info = b_client_custom_info_view, +#else + client_info = b_virtualserver_client_permission_list, +#endif + client_admin = i_client_ban_max_bantime, + client_basic = i_client_needed_whisper_power, + client_modify = b_client_query_delete_own, + ft = i_ft_quota_mb_upload_per_client, + group_end + }; + + enum PermissionTestType { + PERMTEST_HIGHEST, + PERMTEST_ORDERED, + }; + + struct PermissionTypeEntry { + static std::shared_ptr unknown; + + PermissionTypeEntry& operator=(const PermissionTypeEntry& other) = delete; + /* + PermissionTypeEntry& operator=(const PermissionTypeEntry& other) { + this->type = other.type; + this->group = other.group; + this->name = other.name; + this->description = other.description; + this->clientSupported = other.clientSupported; + + this->grant_name = std::string() + (name[0] == 'i' ? "i" : "i") + "_needed_modify_power_" + name.substr(2); + return *this; + } + + bool operator==(const PermissionTypeEntry& other) { + return other.type == this->type; + } + */ + + PermissionType type; + PermissionGroup group; + std::string name; + std::string grant_name; + inline std::string grantName(bool useBool = false) const { return this->grant_name; } + std::string description; + + bool clientSupported = true; + + // PermissionTypeEntry(PermissionTypeEntry&& ref) : type(ref.type), group(ref.group), name(ref.name), description(ref.description), clientSupported(ref.clientSupported) {} + //PermissionTypeEntry(const PermissionTypeEntry& ref) : type(ref.type), group(ref.group), name(ref.name), description(ref.description), clientSupported(ref.clientSupported) {} + PermissionTypeEntry(PermissionTypeEntry&& ref) = delete; + PermissionTypeEntry(const PermissionTypeEntry& ref) = delete; + //PermissionTypeEntry(const PermissionTypeEntry& ref) : type(ref.type), group(ref.group), name(ref.name), description(ref.description), clientSupported(ref.clientSupported) {} + + PermissionTypeEntry(PermissionType type, PermissionGroup group, std::string name, std::string description, bool clientSupported = true) : type(type), + group(group), + name(std::move(name)), + description(std::move(description)), + clientSupported(clientSupported) { + this->grant_name = std::string() + (this->name[0] == 'i' ? "i" : "i") + "_needed_modify_power_" + this->name.substr(2); + } + }; + + namespace teamspeak { + enum GroupType { + GENERAL, + SERVER, + CHANNEL, + CLIENT + }; + + typedef std::map>> MapType; + extern MapType unmapping; + extern MapType mapping; + extern std::deque map_key(std::string key, GroupType type); //TeamSpeak -> TeaSpeak + extern std::deque unmap_key(std::string key, GroupType type); //TeaSpeak -> TeamSpeak + } + + namespace update { + enum GroupUpdateType { + NONE = 0, + + CHANNEL_GUEST = 10, + CHANNEL_VOICE = 25, + CHANNEL_OPERATOR = 35, + CHANNEL_ADMIN = 40, + + SERVER_GUEST = 15, + SERVER_NORMAL = 30, + SERVER_ADMIN = 45, + + QUERY_GUEST = 20, + QUERY_ADMIN = 50 + }; + + struct UpdatePermission { + UpdatePermission(std::string name, permission::PermissionValue value, permission::PermissionValue granted, bool negated, bool skipped) : name(std::move(name)), value(value), granted(granted), negated(negated), skipped(skipped) {} + UpdatePermission() = default; + + std::string name; + permission::PermissionValue value = permNotGranted; + permission::PermissionValue granted = permNotGranted; + + bool negated = false; + bool skipped = false; + }; + + struct UpdateEntry { + GroupUpdateType type; + UpdatePermission permission; + }; + + extern std::deque migrate; //TeamSpeak -> TeaSpeak + } + + extern std::deque> availablePermissions; + extern std::deque neededPermissions; + extern std::deque availableGroups; + + std::shared_ptr resolvePermissionData(PermissionType); + std::shared_ptr resolvePermissionData(const std::string&); + +#define PERM_FLAG_PRIVATE 0b1 +#define PERM_FLAG_PUBLIC 0b10 + + class PermissionManager; + struct Permission { + public: + Permission(const std::shared_ptr& type, PermissionValue value, PermissionValue grant, uint16_t flagMask, std::shared_ptr ch) : type(type), channel(std::move(ch)) { + this->type = type; + this->value = value; + this->granted = grant; + this->flagMask = flagMask; + } + + + + Permission(Permission &) = delete; + Permission(const Permission &) = delete; + + Permission() = delete; + ~Permission(){ } + + std::shared_ptr channel = nullptr; + ChannelId channelId(); + + bool hasValue(){ return value != permNotGranted; } + bool hasGrant(){ return granted != permNotGranted; } + std::shared_ptr type; + PermissionValue value; + PermissionValue granted; + uint16_t flagMask; + + bool flag_negate = false; + bool flag_skip = false; + bool dbReference = false; + }; + + class PermissionManager { + public: + PermissionManager(); + ~PermissionManager(); + + std::shared_ptr registerPermission(PermissionType, PermissionValue, const std::shared_ptr& channel, uint16_t = PERM_FLAG_PUBLIC); + std::shared_ptr registerPermission(const std::shared_ptr&, PermissionValue, const std::shared_ptr& channel, uint16_t = PERM_FLAG_PUBLIC); + //void registerAllPermissions(uint16_t flagMask); + + bool setPermissionGranted(PermissionType, PermissionValue, const std::shared_ptr& channel); + bool setPermission(PermissionType, PermissionValue, const std::shared_ptr& channel); + bool setPermission(PermissionType, PermissionValue, const std::shared_ptr& channel, bool negated, bool skiped); + void deletePermission(PermissionType, const std::shared_ptr& channel); + bool hasPermission(PermissionType, const std::shared_ptr& channel, bool testGlobal); + + /** + * @param channel Should be null for general testing + * @return , + */ + std::deque> getPermission(PermissionType, const std::shared_ptr& channel, bool testGlobal = true); + + PermissionValue getPermissionGrand(permission::PermissionTestType test, PermissionType type, const std::shared_ptr& channel, PermissionValue def = permNotGranted){ + PermissionValue result = def; + auto perms = this->getPermission(type, channel); + if(test == PermissionTestType::PERMTEST_HIGHEST) { + PermissionValue higest = permNotGranted; + for(const auto &e : perms) + if((e->granted > higest || e->granted == -1) && higest != -1) higest = e->granted; + if(higest != permNotGranted) + result = higest; + } else if(test == PermissionTestType::PERMTEST_ORDERED) + while(!perms.empty() && result == permNotGranted) { + result = perms.front()->granted; + perms.pop_front(); + }; + return result; + } + + inline PermissionValue getPermissionValue(PermissionType type, const std::shared_ptr& channel = nullptr, PermissionValue default_value = permNotGranted) { + return this->getPermissionValue(permission::PERMTEST_ORDERED, type, channel, default_value); + } + + PermissionValue getPermissionValue(permission::PermissionTestType test, PermissionType type, const std::shared_ptr& channel = nullptr, PermissionValue def = permNotGranted) { + PermissionValue result = permNotGranted; + auto perms = this->getPermission(type, channel); + if(test == permission::PERMTEST_HIGHEST) { + PermissionValue higest = permNotGranted; + for(const auto &e : perms) + if((e->value > higest || e->value == -1) && higest != -1) higest = e->value; + if(higest != permNotGranted) + result = higest; + } else if(test == PermissionTestType::PERMTEST_ORDERED) + while(!perms.empty() && result == permNotGranted) { + result = perms.front()->value; + perms.pop_front(); + }; + return result == permNotGranted ? def : result; + } + + std::vector> listPermissions(uint16_t = ~0); + std::vector> allPermissions(); + + std::deque> all_channel_specific_permissions(); + std::deque> all_channel_unspecific_permissions(); + + void fireUpdate(PermissionType); + void registerUpdateHandler(const std::function)> &fn){ updateHandler.push_back(fn); } + + void clearPermissions(){ + permissions.clear(); + } + private: + std::deque> permissions; + std::deque)>> updateHandler; + }; + } +} + +DEFINE_VARIABLE_TRANSFORM(ts::permission::PermissionType, VARTYPE_INT, std::to_string((int16_t) in), static_cast(in.as())); +DEFINE_VARIABLE_TRANSFORM(ts::permission::PermissionGroup, VARTYPE_INT, std::to_string((uint16_t) in), static_cast(in.as())); +DEFINE_VARIABLE_TRANSFORM(ts::permission::PermissionSqlType, VARTYPE_INT, std::to_string((int8_t) in), static_cast(in.as())); \ No newline at end of file diff --git a/src/Properties.cpp b/src/Properties.cpp new file mode 100644 index 0000000..0231419 --- /dev/null +++ b/src/Properties.cpp @@ -0,0 +1,621 @@ +#include +#include +#include "log/LogUtils.h" +#include "misc/memtracker.h" +#include "Properties.h" + +using namespace ts; +using namespace ts::property; +using namespace std; + +Properties::Properties() { + memtrack::allocated(this); +} +Properties::~Properties() { + memtrack::freed(this); +} + +bool Properties::has(property::PropertyType type, int index) { + for(auto it = this->properties.begin(); it != this->properties.end(); it++) { + if(!*it) continue; + if(it->get()->type != type) continue; + + index -= it->get()->offset; + if(index < 0) return false; + return index < it->get()->length; + } + return false; +} + +PropertyWrapper Properties::find(property::PropertyType type, int index) { + for (auto &bulk : this->properties) { + if(!bulk) continue; + if(bulk->type != type) + continue; + + index -= bulk->offset; + if(index < 0) + break; + + if(index >= bulk->length) + break; + + return PropertyWrapper{this, &bulk->properties[index], bulk}; + } + + throw std::invalid_argument("missing property type"); +} + +std::vector Properties::list_properties(ts::property::flag_type flagMask, ts::property::flag_type negatedFlagMask) { + vector result; + result.reserve(this->properties_count); + + for (auto &bulk : this->properties) { + for(int index = 0; index < bulk->length; index++) { + auto& property = bulk->properties[index]; + if((property.description->flags & flagMask) > 0 && (property.description->flags & negatedFlagMask) == 0) + result.emplace_back(this, &property, bulk); + } + } + return result; +} + +std::vector Properties::all_properties() { + vector result; + result.reserve(this->properties_count); + + for (auto &bulk : this->properties) { + for(int index = 0; index < bulk->length; index++) + result.emplace_back(this, &bulk->properties[index], bulk); + } + return result; +} + +PropertyWrapper::PropertyWrapper(ts::Properties* handle, ts::PropertyData *data, const std::shared_ptr &bundle_lock) : handle(handle), data_ptr(data), bundle_lock(bundle_lock) { +} + +void PropertyWrapper::trigger_update() { + this->data_ptr->flag_modified = true; + + if(this->handle) { + for(const auto& elm : this->handle->notifyFunctions) + elm(*this); + } +} + +bool Properties::register_property_type(ts::property::PropertyType type, size_t length, size_t offset) { + for(auto& bulk : this->properties) + if(bulk->type == type) + return false; + + const auto alloc_length = sizeof(PropertyBundle) + sizeof(PropertyData) * length; + auto ptr = shared_ptr((PropertyBundle*) malloc(alloc_length), [](PropertyBundle* bundle) { + if(!bundle) return; + + for(int index = 0; index < bundle->length; index++) { + auto& property = bundle->properties[index]; + property.description.~shared_ptr(); + property.value.~string(); + property.value_lock.~spin_lock(); + property.casted_value.~any(); + } + ::free(bundle); + }); + + ptr->type = type; + ptr->offset = offset; + ptr->length = length; + + for(int index = 0; index < length; index++) { + auto& property = ptr->properties[index]; + + new (&property.casted_value) any(); + new (&property.value_lock) spin_lock(); + new (&property.value) string(); + new (&property.description) shared_ptr(property::impl::info(type, offset + index)); + property.flag_modified = false; + property.flag_db_reference = false; + + property.value = property.description->default_value; + this->properties_count++; + } + + this->properties.push_back(ptr); + return false; +} + +namespace ts { + namespace property { + PropertyDescription::PropertyDescription(int property_id, PropertyType property_type, const std::string &name, const std::string &default_value, property::ValueType type, property::flag_type flags) noexcept : name(name), default_value(default_value), + type_value(type), flags(flags), property_index(property_id), + type_property(property_type) { + std::transform(this->name.begin(), this->name.end(), this->name.begin(), ::tolower); + } + +#define FLAG_SS (FLAG_SNAPSHOT | FLAG_SAVE) +#define FLAG_SERVER_VV (FLAG_SERVER_VARIABLE | FLAG_SERVER_VIEW) +#define FLAG_SERVER_VVSS (FLAG_SERVER_VV | FLAG_SS) + +#define FLAG_CLIENT_VV (FLAG_CLIENT_VARIABLE | FLAG_CLIENT_VIEW) +#define FLAG_CLIENT_VVSS (FLAG_CLIENT_VV | FLAG_SS) + + array, VirtualServerProperties::VIRTUALSERVER_ENDMARKER> virtualserver_info = { + make_shared(VIRTUALSERVER_UNDEFINED, "VIRTUALSERVER_UNDEFINED", "", TYPE_UNKNOWN, 0), //Must be at index 0! + make_shared(VIRTUALSERVER_ID, "virtualserver_id", "0", TYPE_UNSIGNED_NUMBER, FLAG_SERVER_VIEW), //available when connected + make_shared(VIRTUALSERVER_UNIQUE_IDENTIFIER, "virtualserver_unique_identifier", "", TYPE_STRING, FLAG_SERVER_VV | FLAG_SNAPSHOT), + make_shared(VIRTUALSERVER_NAME, "virtualserver_name", "Another TeaSpeak server software user", TYPE_STRING, FLAG_SERVER_VVSS | FLAG_USER_EDITABLE), + make_shared(VIRTUALSERVER_WELCOMEMESSAGE, "VIRTUALSERVER_WELCOMEMESSAGE", "Welcome on another TeaSpeak server. (Download now and a license fee is not your cup of tea! [URL]www.teaspeak.de[/URL])", TYPE_STRING, FLAG_SERVER_VVSS | FLAG_USER_EDITABLE), + make_shared(VIRTUALSERVER_PLATFORM, "VIRTUALSERVER_PLATFORM", "undefined", TYPE_STRING, FLAG_SERVER_VIEW), + make_shared(VIRTUALSERVER_VERSION, "VIRTUALSERVER_VERSION", "undefined", TYPE_STRING, FLAG_SERVER_VIEW), + make_shared(VIRTUALSERVER_MAXCLIENTS, "VIRTUALSERVER_MAXCLIENTS", "120", TYPE_UNSIGNED_NUMBER, FLAG_SERVER_VVSS | FLAG_USER_EDITABLE), + make_shared(VIRTUALSERVER_PASSWORD, "VIRTUALSERVER_PASSWORD", "", TYPE_STRING, FLAG_SS | FLAG_USER_EDITABLE), + make_shared(VIRTUALSERVER_CLIENTS_ONLINE, "virtualserver_clientsonline", "0", TYPE_UNSIGNED_NUMBER, FLAG_SERVER_VARIABLE), + make_shared(VIRTUALSERVER_CHANNELS_ONLINE, "virtualserver_channelsonline", "0", TYPE_UNSIGNED_NUMBER, FLAG_SERVER_VARIABLE), //only available on request (=> requestServerVariables), + make_shared(VIRTUALSERVER_QUERYCLIENTS_ONLINE, "virtualserver_queryclientsonline", "0", TYPE_UNSIGNED_NUMBER, FLAG_SERVER_VARIABLE), //only available on request (=> requestServerVariables) + make_shared(VIRTUALSERVER_CREATED, "VIRTUALSERVER_CREATED", "0", TYPE_UNSIGNED_NUMBER, FLAG_SERVER_VIEW | FLAG_SS), //available when connected, stores the time when the server was created + make_shared(VIRTUALSERVER_UPTIME, "VIRTUALSERVER_UPTIME", "0", TYPE_UNSIGNED_NUMBER, FLAG_SERVER_VARIABLE), //only available on request (=> requestServerVariables), the time since the server was started + make_shared(VIRTUALSERVER_CODEC_ENCRYPTION_MODE, "VIRTUALSERVER_CODEC_ENCRYPTION_MODE", "0", TYPE_UNSIGNED_NUMBER, FLAG_SERVER_VVSS | FLAG_USER_EDITABLE), //available and always up-to-date when connected + + // Rare properties + make_shared(VIRTUALSERVER_KEYPAIR, "VIRTUALSERVER_KEYPAIR", "", TYPE_STRING, FLAG_SS), //internal use + make_shared(VIRTUALSERVER_HOSTMESSAGE, "VIRTUALSERVER_HOSTMESSAGE", "Welcome", TYPE_STRING, FLAG_SERVER_VVSS | FLAG_USER_EDITABLE), //available when connected, not updated while connected + make_shared(VIRTUALSERVER_HOSTMESSAGE_MODE, "VIRTUALSERVER_HOSTMESSAGE_MODE", "1", TYPE_STRING, FLAG_SERVER_VVSS | FLAG_USER_EDITABLE), //available when connected, not updated while connected + make_shared(VIRTUALSERVER_DEFAULT_SERVER_GROUP, "VIRTUALSERVER_DEFAULT_SERVER_GROUP", "0", TYPE_UNSIGNED_NUMBER, FLAG_SERVER_VVSS | FLAG_USER_EDITABLE), //the manager permissions server group that a new manager gets assigned + make_shared(VIRTUALSERVER_DEFAULT_MUSIC_GROUP, "VIRTUALSERVER_DEFAULT_MUSIC_GROUP", "0", TYPE_UNSIGNED_NUMBER, FLAG_SERVER_VVSS | FLAG_USER_EDITABLE), //the manager permissions server group that a new manager gets assigned + make_shared(VIRTUALSERVER_DEFAULT_CHANNEL_GROUP, "VIRTUALSERVER_DEFAULT_CHANNEL_GROUP", "0", TYPE_UNSIGNED_NUMBER, FLAG_SERVER_VVSS | FLAG_USER_EDITABLE), //the channel permissions group that a new manager gets assigned when joining a channel + make_shared(VIRTUALSERVER_FLAG_PASSWORD, "VIRTUALSERVER_FLAG_PASSWORD", "0", TYPE_BOOL, FLAG_SERVER_VARIABLE | FLAG_SS | FLAG_USER_EDITABLE), //only available on request (=> requestServerVariables) + make_shared(VIRTUALSERVER_DEFAULT_CHANNEL_ADMIN_GROUP, "VIRTUALSERVER_DEFAULT_CHANNEL_ADMIN_GROUP", "0", TYPE_UNSIGNED_NUMBER, FLAG_SERVER_VARIABLE | FLAG_SERVER_VIEW | FLAG_SS | FLAG_USER_EDITABLE), //the channel permissions group that a manager gets assigned when creating a channel + make_shared(VIRTUALSERVER_MAX_DOWNLOAD_TOTAL_BANDWIDTH, "VIRTUALSERVER_MAX_DOWNLOAD_TOTAL_BANDWIDTH", "-1", TYPE_SIGNED_NUMBER, FLAG_SERVER_VARIABLE | FLAG_SS | FLAG_USER_EDITABLE), //only available on request (=> requestServerVariables) + make_shared(VIRTUALSERVER_MAX_UPLOAD_TOTAL_BANDWIDTH, "VIRTUALSERVER_MAX_UPLOAD_TOTAL_BANDWIDTH", "-1", TYPE_SIGNED_NUMBER, FLAG_SERVER_VARIABLE | FLAG_SS | FLAG_USER_EDITABLE), //only available on request (=> requestServerVariables) + make_shared(VIRTUALSERVER_HOSTBANNER_URL, "VIRTUALSERVER_HOSTBANNER_URL", "", TYPE_STRING, FLAG_SERVER_VVSS | FLAG_USER_EDITABLE), //available when connected, always up-to-date + make_shared(VIRTUALSERVER_HOSTBANNER_GFX_URL, "VIRTUALSERVER_HOSTBANNER_GFX_URL", "", TYPE_STRING, FLAG_SERVER_VVSS | FLAG_USER_EDITABLE), //available when connected, always up-to-date + make_shared(VIRTUALSERVER_HOSTBANNER_GFX_INTERVAL, "VIRTUALSERVER_HOSTBANNER_GFX_INTERVAL", "0", TYPE_UNSIGNED_NUMBER, FLAG_SERVER_VVSS | FLAG_USER_EDITABLE), //available when connected, always up-to-date + make_shared(VIRTUALSERVER_COMPLAIN_AUTOBAN_COUNT, "VIRTUALSERVER_COMPLAIN_AUTOBAN_COUNT", "5", TYPE_UNSIGNED_NUMBER, FLAG_SERVER_VARIABLE | FLAG_SS | FLAG_USER_EDITABLE), //only available on request (=> requestServerVariables) + make_shared(VIRTUALSERVER_COMPLAIN_AUTOBAN_TIME, "VIRTUALSERVER_COMPLAIN_AUTOBAN_TIME", "5", TYPE_UNSIGNED_NUMBER, FLAG_SERVER_VARIABLE | FLAG_SS | FLAG_USER_EDITABLE), //only available on request (=> requestServerVariables) + make_shared(VIRTUALSERVER_COMPLAIN_REMOVE_TIME, "VIRTUALSERVER_COMPLAIN_REMOVE_TIME", "5", TYPE_UNSIGNED_NUMBER, FLAG_SERVER_VARIABLE | FLAG_SS | FLAG_USER_EDITABLE), //only available on request (=> requestServerVariables) + make_shared(VIRTUALSERVER_MIN_CLIENTS_IN_CHANNEL_BEFORE_FORCED_SILENCE, "VIRTUALSERVER_MIN_CLIENTS_IN_CHANNEL_BEFORE_FORCED_SILENCE", "20", TYPE_UNSIGNED_NUMBER, FLAG_SERVER_VARIABLE | FLAG_SS | FLAG_USER_EDITABLE),//only available on request (=> requestServerVariables) + make_shared(VIRTUALSERVER_PRIORITY_SPEAKER_DIMM_MODIFICATOR, "VIRTUALSERVER_PRIORITY_SPEAKER_DIMM_MODIFICATOR", "-18", TYPE_FLOAT, FLAG_SERVER_VVSS | FLAG_USER_EDITABLE), //available when connected, always up-to-date + make_shared(VIRTUALSERVER_ANTIFLOOD_POINTS_TICK_REDUCE, "VIRTUALSERVER_ANTIFLOOD_POINTS_TICK_REDUCE", "25", TYPE_UNSIGNED_NUMBER, FLAG_SERVER_VARIABLE | FLAG_SS | FLAG_USER_EDITABLE), //only available on request (=> requestServerVariables) + make_shared(VIRTUALSERVER_ANTIFLOOD_POINTS_NEEDED_COMMAND_BLOCK, "VIRTUALSERVER_ANTIFLOOD_POINTS_NEEDED_COMMAND_BLOCK", "150", TYPE_UNSIGNED_NUMBER, FLAG_SERVER_VARIABLE | FLAG_SS | FLAG_USER_EDITABLE), //only available on request (=> requestServerVariables) + make_shared(VIRTUALSERVER_ANTIFLOOD_POINTS_NEEDED_IP_BLOCK, "VIRTUALSERVER_ANTIFLOOD_POINTS_NEEDED_IP_BLOCK", "300", TYPE_UNSIGNED_NUMBER, FLAG_SERVER_VARIABLE | FLAG_SS | FLAG_USER_EDITABLE), //only available on request (=> requestServerVariables) + make_shared(VIRTUALSERVER_CLIENT_CONNECTIONS, "VIRTUALSERVER_CLIENT_CONNECTIONS", "0", TYPE_UNSIGNED_NUMBER, FLAG_SERVER_VARIABLE | FLAG_SS), //only available on request (=> requestServerVariables) + make_shared(VIRTUALSERVER_QUERY_CLIENT_CONNECTIONS, "VIRTUALSERVER_QUERY_CLIENT_CONNECTIONS", "0", TYPE_UNSIGNED_NUMBER, FLAG_SERVER_VARIABLE | FLAG_SS), //only available on request (=> requestServerVariables) + make_shared(VIRTUALSERVER_HOSTBUTTON_TOOLTIP, "VIRTUALSERVER_HOSTBUTTON_TOOLTIP", "", TYPE_STRING, FLAG_SERVER_VVSS | FLAG_USER_EDITABLE), //available when connected, always up-to-date + make_shared(VIRTUALSERVER_HOSTBUTTON_URL, "VIRTUALSERVER_HOSTBUTTON_URL", "", TYPE_STRING, FLAG_SERVER_VVSS | FLAG_USER_EDITABLE), //available when connected, always up-to-date + make_shared(VIRTUALSERVER_HOSTBUTTON_GFX_URL, "VIRTUALSERVER_HOSTBUTTON_GFX_URL", "", TYPE_STRING, FLAG_SERVER_VVSS | FLAG_USER_EDITABLE), //available when connected, always up-to-date + make_shared(VIRTUALSERVER_HOSTBANNER_MODE, "VIRTUALSERVER_HOSTBANNER_MODE", "0", TYPE_UNSIGNED_NUMBER, FLAG_SERVER_VVSS | FLAG_USER_EDITABLE), //available when connected, always up-to-date + make_shared(VIRTUALSERVER_FILEBASE, "VIRTUALSERVER_FILEBASE", "", TYPE_STRING, FLAG_SERVER_VARIABLE | FLAG_SS | FLAG_USER_EDITABLE), //not available to clients, stores the folder used for file transfers + make_shared(VIRTUALSERVER_DOWNLOAD_QUOTA, "VIRTUALSERVER_DOWNLOAD_QUOTA", "-1", TYPE_SIGNED_NUMBER, FLAG_SERVER_VARIABLE | FLAG_SAVE | FLAG_USER_EDITABLE), //only available on request (=> requestServerVariables) + make_shared(VIRTUALSERVER_UPLOAD_QUOTA, "VIRTUALSERVER_UPLOAD_QUOTA", "-1", TYPE_SIGNED_NUMBER, FLAG_SERVER_VARIABLE | FLAG_SAVE | FLAG_USER_EDITABLE), //only available on request (=> requestServerVariables) + make_shared(VIRTUALSERVER_MONTH_BYTES_DOWNLOADED, "VIRTUALSERVER_MONTH_BYTES_DOWNLOADED", "0", TYPE_UNSIGNED_NUMBER, FLAG_SERVER_VARIABLE | FLAG_SAVE), //only available on request (=> requestServerVariables) + make_shared(VIRTUALSERVER_MONTH_BYTES_UPLOADED, "VIRTUALSERVER_MONTH_BYTES_UPLOADED", "0", TYPE_UNSIGNED_NUMBER, FLAG_SERVER_VARIABLE | FLAG_SAVE), //only available on request (=> requestServerVariables) + make_shared(VIRTUALSERVER_TOTAL_BYTES_DOWNLOADED, "VIRTUALSERVER_TOTAL_BYTES_DOWNLOADED", "0", TYPE_UNSIGNED_NUMBER, FLAG_SERVER_VARIABLE | FLAG_SAVE), //only available on request (=> requestServerVariables) + make_shared(VIRTUALSERVER_TOTAL_BYTES_UPLOADED, "VIRTUALSERVER_TOTAL_BYTES_UPLOADED", "0", TYPE_UNSIGNED_NUMBER, FLAG_SERVER_VARIABLE | FLAG_SAVE), //only available on request (=> requestServerVariables) + make_shared(VIRTUALSERVER_PORT, "VIRTUALSERVER_PORT", "9987", TYPE_UNSIGNED_NUMBER, FLAG_SERVER_VARIABLE | FLAG_SS | FLAG_USER_EDITABLE), //only available on request (=> requestServerVariables) + make_shared(VIRTUALSERVER_HOST, "VIRTUALSERVER_HOST", "0.0.0.0,::", TYPE_STRING, FLAG_SERVER_VARIABLE | FLAG_SS | FLAG_USER_EDITABLE), //internal use | contains comma separated ip list + make_shared(VIRTUALSERVER_AUTOSTART, "VIRTUALSERVER_AUTOSTART", "1", TYPE_BOOL, FLAG_SERVER_VARIABLE | FLAG_SS | FLAG_USER_EDITABLE), //only available on request (=> requestServerVariables) + make_shared(VIRTUALSERVER_MACHINE_ID, "VIRTUALSERVER_MACHINE_ID", "", TYPE_STRING, FLAG_SERVER_VARIABLE | FLAG_SS | FLAG_USER_EDITABLE), //only available on request (=> requestServerVariables) + make_shared(VIRTUALSERVER_NEEDED_IDENTITY_SECURITY_LEVEL, "VIRTUALSERVER_NEEDED_IDENTITY_SECURITY_LEVEL", "8", TYPE_UNSIGNED_NUMBER, FLAG_SERVER_VARIABLE | FLAG_SS | FLAG_USER_EDITABLE), //only available on request (=> requestServerVariables) + make_shared(VIRTUALSERVER_LOG_CLIENT, "VIRTUALSERVER_LOG_CLIENT", "1", TYPE_BOOL, FLAG_SERVER_VARIABLE | FLAG_SS | FLAG_USER_EDITABLE), //only available on request (=> requestServerVariables) + make_shared(VIRTUALSERVER_LOG_QUERY, "VIRTUALSERVER_LOG_QUERY", "1", TYPE_BOOL, FLAG_SERVER_VARIABLE | FLAG_SS | FLAG_USER_EDITABLE), //only available on request (=> requestServerVariables) + make_shared(VIRTUALSERVER_LOG_CHANNEL, "VIRTUALSERVER_LOG_CHANNEL", "1", TYPE_BOOL, FLAG_SERVER_VARIABLE | FLAG_SS | FLAG_USER_EDITABLE), //only available on request (=> requestServerVariables) + make_shared(VIRTUALSERVER_LOG_PERMISSIONS, "VIRTUALSERVER_LOG_PERMISSIONS", "1", TYPE_BOOL, FLAG_SERVER_VARIABLE | FLAG_SS | FLAG_USER_EDITABLE), //only available on request (=> requestServerVariables) + make_shared(VIRTUALSERVER_LOG_SERVER, "VIRTUALSERVER_LOG_SERVER", "1", TYPE_BOOL, FLAG_SERVER_VARIABLE | FLAG_SS | FLAG_USER_EDITABLE), //only available on request (=> requestServerVariables) + make_shared(VIRTUALSERVER_LOG_FILETRANSFER, "VIRTUALSERVER_LOG_FILETRANSFER", "1", TYPE_BOOL, FLAG_SERVER_VARIABLE | FLAG_SS | FLAG_USER_EDITABLE), //only available on request (=> requestServerVariables) + make_shared(VIRTUALSERVER_NAME_PHONETIC, "VIRTUALSERVER_NAME_PHONETIC", "", TYPE_STRING, FLAG_SERVER_VVSS | FLAG_USER_EDITABLE), //available when connected, always up-to-date + make_shared(VIRTUALSERVER_ICON_ID, "VIRTUALSERVER_ICON_ID", "0", TYPE_UNSIGNED_NUMBER, FLAG_SERVER_VVSS | FLAG_USER_EDITABLE), //available when connected, always up-to-date + make_shared(VIRTUALSERVER_RESERVED_SLOTS, "VIRTUALSERVER_RESERVED_SLOTS", "0", TYPE_UNSIGNED_NUMBER, FLAG_SERVER_VARIABLE | FLAG_SS | FLAG_USER_EDITABLE), //available when connected, always up-to-date + make_shared(VIRTUALSERVER_TOTAL_PACKETLOSS_SPEECH, "VIRTUALSERVER_TOTAL_PACKETLOSS_SPEECH", "0", TYPE_UNSIGNED_NUMBER, FLAG_SERVER_VARIABLE), //only available on request (=> requestServerVariables) + make_shared(VIRTUALSERVER_TOTAL_PACKETLOSS_KEEPALIVE, "VIRTUALSERVER_TOTAL_PACKETLOSS_KEEPALIVE", "0", TYPE_FLOAT, FLAG_SERVER_VARIABLE), //only available on request (=> requestServerVariables) + make_shared(VIRTUALSERVER_TOTAL_PACKETLOSS_CONTROL, "VIRTUALSERVER_TOTAL_PACKETLOSS_CONTROL", "0", TYPE_FLOAT, FLAG_SERVER_VARIABLE), //only available on request (=> requestServerVariables) + make_shared(VIRTUALSERVER_TOTAL_PACKETLOSS_TOTAL, "VIRTUALSERVER_TOTAL_PACKETLOSS_TOTAL", "0", TYPE_FLOAT, FLAG_SERVER_VARIABLE), //only available on request (=> requestServerVariables) + make_shared(VIRTUALSERVER_TOTAL_PING, "VIRTUALSERVER_TOTAL_PING", "0", TYPE_FLOAT, FLAG_SERVER_VARIABLE), //only available on request (=> requestServerVariables) + make_shared(VIRTUALSERVER_WEBLIST_ENABLED, "VIRTUALSERVER_WEBLIST_ENABLED", "1", TYPE_BOOL, FLAG_SERVER_VVSS | FLAG_USER_EDITABLE), //only available on request (=> requestServerVariables) + make_shared(VIRTUALSERVER_AUTOGENERATED_PRIVILEGEKEY, "VIRTUALSERVER_AUTOGENERATED_PRIVILEGEKEY", "", TYPE_STRING, FLAG_SAVE), //internal use + make_shared(VIRTUALSERVER_ASK_FOR_PRIVILEGEKEY, "VIRTUALSERVER_ASK_FOR_PRIVILEGEKEY", "1", TYPE_BOOL, FLAG_SERVER_VV | FLAG_SAVE | FLAG_USER_EDITABLE), //available when connected + make_shared(VIRTUALSERVER_CHANNEL_TEMP_DELETE_DELAY_DEFAULT, "VIRTUALSERVER_CHANNEL_TEMP_DELETE_DELAY_DEFAULT", "60", TYPE_UNSIGNED_NUMBER, FLAG_SERVER_VVSS | FLAG_USER_EDITABLE), //available when connected, always up-to-date + make_shared(VIRTUALSERVER_MIN_CLIENT_VERSION, "VIRTUALSERVER_MIN_CLIENT_VERSION", "1445512488", TYPE_UNSIGNED_NUMBER, FLAG_SERVER_VARIABLE | FLAG_USER_EDITABLE), //only available on request (=> requestServerVariables) + make_shared(VIRTUALSERVER_MIN_ANDROID_VERSION, "VIRTUALSERVER_MIN_ANDROID_VERSION", "1407159763", TYPE_UNSIGNED_NUMBER, FLAG_SERVER_VARIABLE | FLAG_USER_EDITABLE), //only available on request (=> requestServerVariables) + make_shared(VIRTUALSERVER_MIN_IOS_VERSION, "VIRTUALSERVER_MIN_IOS_VERSION", "1407159763", TYPE_UNSIGNED_NUMBER, FLAG_SERVER_VARIABLE | FLAG_USER_EDITABLE), //only available on request (=> requestServerVariables) + make_shared(VIRTUALSERVER_MIN_WINPHONE_VERSION, "VIRTUALSERVER_MIN_WINPHONE_VERSION", "1407159763", TYPE_UNSIGNED_NUMBER, FLAG_SERVER_VARIABLE | FLAG_USER_EDITABLE), //only available on request (=> requestServerVariables) + + make_shared(VIRTUALSERVER_LAST_CLIENT_CONNECT, "VIRTUALSERVER_LAST_CLIENT_CONNECT", "0", TYPE_UNSIGNED_NUMBER, FLAG_SERVER_VARIABLE | FLAG_NEW | FLAG_SS), //only available on request (=> requestServerVariables) + make_shared(VIRTUALSERVER_LAST_QUERY_CONNECT, "VIRTUALSERVER_LAST_QUERY_CONNECT", "0", TYPE_UNSIGNED_NUMBER, FLAG_SERVER_VARIABLE | FLAG_NEW | FLAG_SS), //only available on request (=> requestServerVariables) + make_shared(VIRTUALSERVER_LAST_CLIENT_DISCONNECT, "VIRTUALSERVER_LAST_CLIENT_DISCONNECT", "0", TYPE_UNSIGNED_NUMBER, FLAG_SERVER_VARIABLE | FLAG_NEW | FLAG_SS), //only available on request (=> requestServerVariables) + make_shared(VIRTUALSERVER_LAST_QUERY_DISCONNECT, "VIRTUALSERVER_LAST_QUERY_DISCONNECT", "0", TYPE_UNSIGNED_NUMBER, FLAG_SERVER_VARIABLE | FLAG_NEW | FLAG_SS), //only available on request (=> requestServerVariables) + + make_shared(VIRTUALSERVER_WEB_PORT, "VIRTUALSERVER_WEB_PORT", "0", TYPE_UNSIGNED_NUMBER, FLAG_SERVER_VARIABLE | FLAG_NEW | FLAG_SS | FLAG_USER_EDITABLE), //only available on request (=> requestServerVariables) + make_shared(VIRTUALSERVER_WEB_HOST, "VIRTUALSERVER_WEB_HOST", "0.0.0.0", TYPE_STRING, FLAG_SERVER_VARIABLE | FLAG_NEW | FLAG_SS | FLAG_USER_EDITABLE), //only available on request (=> requestServerVariables) + + make_shared(VIRTUALSERVER_DEFAULT_CLIENT_DESCRIPTION, "VIRTUALSERVER_DEFAULT_CLIENT_DESCRIPTION", "I don't have a description", TYPE_STRING, FLAG_SERVER_VARIABLE | FLAG_NEW | FLAG_SS | FLAG_USER_EDITABLE), //only available on request (=> requestServerVariables) + make_shared(VIRTUALSERVER_DEFAULT_CHANNEL_DESCRIPTION, "VIRTUALSERVER_DEFAULT_CHANNEL_DESCRIPTION", "I don't have a description", TYPE_STRING, FLAG_SERVER_VARIABLE | FLAG_NEW | FLAG_SS | FLAG_USER_EDITABLE), //only available on request (=> requestServerVariables) + make_shared(VIRTUALSERVER_DEFAULT_CHANNEL_TOPIC, "VIRTUALSERVER_DEFAULT_CHANNEL_TOPIC", "", TYPE_STRING, FLAG_SERVER_VARIABLE | FLAG_NEW | FLAG_SS | FLAG_USER_EDITABLE), //only available on request (=> requestServerVariables) + + make_shared(VIRTUALSERVER_MUSIC_BOT_LIMIT, "VIRTUALSERVER_MUSIC_BOT_LIMIT", "-1", TYPE_SIGNED_NUMBER, FLAG_SERVER_VARIABLE | FLAG_NEW | FLAG_SS | FLAG_USER_EDITABLE), //only available on request (=> requestServerVariables) + make_shared(VIRTUALSERVER_SPOKEN_TIME, "VIRTUALSERVER_SPOKEN_TIME", "0", TYPE_UNSIGNED_NUMBER, FLAG_INTERNAL | FLAG_NEW | FLAG_SAVE), //only available on request (=> requestServerVariables) + make_shared(VIRTUALSERVER_DISABLE_IP_SAVING, "VIRTUALSERVER_DISABLE_IP_SAVING", "0", TYPE_BOOL, FLAG_INTERNAL | FLAG_NEW | FLAG_SAVE | FLAG_USER_EDITABLE) //only available on request (=> requestServerVariables) + }; + + array, ChannelProperties::CHANNEL_ENDMARKER> channel_info = { + make_shared(CHANNEL_UNDEFINED, "CHANNEL_UNDEFINED", "", TYPE_UNKNOWN, 0), //Must be at index 0! + make_shared(CHANNEL_ID, "cid", "0", TYPE_UNSIGNED_NUMBER, FLAG_CHANNEL_VIEW | FLAG_SS), + make_shared(CHANNEL_PID, "cpid", "0", TYPE_UNSIGNED_NUMBER, FLAG_CHANNEL_VIEW | FLAG_SS), + make_shared(CHANNEL_NAME, "CHANNEL_NAME", "undefined", TYPE_STRING, FLAG_CHANNEL_VIEW | FLAG_SS | FLAG_USER_EDITABLE), //Available for all channels that are "in view", always up-to-date + make_shared(CHANNEL_TOPIC, "CHANNEL_TOPIC", "", TYPE_STRING, FLAG_CHANNEL_VIEW | FLAG_SS | FLAG_USER_EDITABLE), //Available for all channels that are "in view", always up-to-date + make_shared(CHANNEL_DESCRIPTION, "CHANNEL_DESCRIPTION", "", TYPE_STRING, FLAG_CHANNEL_VARIABLE | FLAG_SS | FLAG_USER_EDITABLE), //Must be requested (=> requestChannelDescription) + make_shared(CHANNEL_PASSWORD, "CHANNEL_PASSWORD", "0", TYPE_STRING, FLAG_SS | FLAG_USER_EDITABLE), //not available manager side + make_shared(CHANNEL_CODEC, "CHANNEL_CODEC", "4", TYPE_UNSIGNED_NUMBER, FLAG_CHANNEL_VIEW | FLAG_SS | FLAG_USER_EDITABLE), //Available for all channels that are "in view", always up-to-date + make_shared(CHANNEL_CODEC_QUALITY, "CHANNEL_CODEC_QUALITY", "7", TYPE_UNSIGNED_NUMBER, FLAG_CHANNEL_VIEW | FLAG_SS | FLAG_USER_EDITABLE), //Available for all channels that are "in view", always up-to-date + make_shared(CHANNEL_MAXCLIENTS, "CHANNEL_MAXCLIENTS", "-1", TYPE_SIGNED_NUMBER, FLAG_CHANNEL_VIEW | FLAG_SS | FLAG_USER_EDITABLE), //Available for all channels that are "in view", always up-to-date + make_shared(CHANNEL_MAXFAMILYCLIENTS, "CHANNEL_MAXFAMILYCLIENTS", "-1", TYPE_SIGNED_NUMBER, FLAG_CHANNEL_VIEW | FLAG_SS | FLAG_USER_EDITABLE), //Available for all channels that are "in view", always up-to-date + make_shared(CHANNEL_ORDER, "CHANNEL_ORDER", "0", TYPE_UNSIGNED_NUMBER, FLAG_CHANNEL_VIEW | FLAG_SS | FLAG_USER_EDITABLE), //Available for all channels that are "in view", always up-to-date + make_shared(CHANNEL_FLAG_PERMANENT, "CHANNEL_FLAG_PERMANENT", "1", TYPE_BOOL, FLAG_CHANNEL_VIEW | FLAG_SS | FLAG_USER_EDITABLE), //Available for all channels that are "in view", always up-to-date + make_shared(CHANNEL_FLAG_SEMI_PERMANENT, "CHANNEL_FLAG_SEMI_PERMANENT", "0", TYPE_BOOL, FLAG_CHANNEL_VIEW | FLAG_SS | FLAG_USER_EDITABLE), //Available for all channels that are "in view", always up-to-date + make_shared(CHANNEL_FLAG_DEFAULT, "CHANNEL_FLAG_DEFAULT", "0", TYPE_BOOL, FLAG_CHANNEL_VIEW | FLAG_SS | FLAG_USER_EDITABLE), //Available for all channels that are "in view", always up-to-date + make_shared(CHANNEL_FLAG_PASSWORD, "CHANNEL_FLAG_PASSWORD", "0", TYPE_BOOL, FLAG_CHANNEL_VIEW | FLAG_SS | FLAG_USER_EDITABLE), //Available for all channels that are "in view", always up-to-date + make_shared(CHANNEL_CODEC_LATENCY_FACTOR, "CHANNEL_CODEC_LATENCY_FACTOR", "1", TYPE_UNSIGNED_NUMBER, FLAG_CHANNEL_VIEW | FLAG_SS | FLAG_USER_EDITABLE), //Available for all channels that are "in view", always up-to-date + make_shared(CHANNEL_CODEC_IS_UNENCRYPTED, "CHANNEL_CODEC_IS_UNENCRYPTED", "1", TYPE_BOOL, FLAG_CHANNEL_VIEW | FLAG_SS | FLAG_USER_EDITABLE), //Available for all channels that are "in view", always up-to-date + make_shared(CHANNEL_SECURITY_SALT, "CHANNEL_SECURITY_SALT", "", TYPE_STRING, FLAG_SS), //Not available manager side, not used in teamspeak, only SDK. Sets the options+salt for security hash. + make_shared(CHANNEL_DELETE_DELAY, "CHANNEL_DELETE_DELAY", "0", TYPE_UNSIGNED_NUMBER, FLAG_CHANNEL_VIEW | FLAG_SS | FLAG_USER_EDITABLE), //How many seconds to wait before deleting this channel + make_shared(CHANNEL_FLAG_MAXCLIENTS_UNLIMITED, "CHANNEL_FLAG_MAXCLIENTS_UNLIMITED", "1", TYPE_BOOL, FLAG_CHANNEL_VIEW | FLAG_SS | FLAG_USER_EDITABLE), //Available for all channels that are "in view", always up-to-date + make_shared(CHANNEL_FLAG_MAXFAMILYCLIENTS_UNLIMITED, "CHANNEL_FLAG_MAXFAMILYCLIENTS_UNLIMITED", "1", TYPE_BOOL, FLAG_CHANNEL_VIEW | FLAG_SS | FLAG_USER_EDITABLE),//Available for all channels that are "in view", always up-to-date + make_shared(CHANNEL_FLAG_MAXFAMILYCLIENTS_INHERITED, "CHANNEL_FLAG_MAXFAMILYCLIENTS_INHERITED", "0", TYPE_BOOL, FLAG_CHANNEL_VIEW | FLAG_SS | FLAG_USER_EDITABLE),//Available for all channels that are "in view", always up-to-date + make_shared(CHANNEL_FLAG_ARE_SUBSCRIBED, "CHANNEL_FLAG_ARE_SUBSCRIBED", "1", TYPE_BOOL, FLAG_INTERNAL), //Only available manager side, stores whether we are subscribed to this channel + make_shared(CHANNEL_FILEPATH, "CHANNEL_FILEPATH", "", TYPE_STRING, FLAG_SS), //not available manager side, the folder used for file-transfers for this channel + make_shared(CHANNEL_NEEDED_TALK_POWER, "CHANNEL_NEEDED_TALK_POWER", "0", TYPE_UNSIGNED_NUMBER, FLAG_CHANNEL_VIEW | FLAG_SS | FLAG_USER_EDITABLE), //Available for all channels that are "in view", always up-to-date + make_shared(CHANNEL_FORCED_SILENCE, "CHANNEL_FORCED_SILENCE", "0", TYPE_BOOL, FLAG_CHANNEL_VIEW | FLAG_SS | FLAG_USER_EDITABLE), //Available for all channels that are "in view", always up-to-date + make_shared(CHANNEL_NAME_PHONETIC, "CHANNEL_NAME_PHONETIC", "", TYPE_STRING, FLAG_CHANNEL_VIEW | FLAG_SS | FLAG_USER_EDITABLE), //Available for all channels that are "in view", always up-to-date + make_shared(CHANNEL_ICON_ID, "CHANNEL_ICON_ID", "0", TYPE_UNSIGNED_NUMBER, FLAG_CHANNEL_VIEW | FLAG_SS | FLAG_USER_EDITABLE), //Available for all channels that are "in view", always up-to-date + make_shared(CHANNEL_FLAG_PRIVATE, "CHANNEL_FLAG_PRIVATE", "0", TYPE_BOOL, FLAG_CHANNEL_VIEW | FLAG_SS), //Available for all channels that are "in view", always up-to-date + make_shared(CHANNEL_LAST_LEFT, "CHANNEL_LAST_LEFT", "0", TYPE_UNSIGNED_NUMBER, FLAG_SAVE | FLAG_CHANNEL_VIEW | FLAG_CHANNEL_VARIABLE | FLAG_NEW), //Available for all channels that are "in view", always up-to-date + make_shared(CHANNEL_CREATED_AT, "CHANNEL_CREATED_AT", "0", TYPE_UNSIGNED_NUMBER, FLAG_SAVE | FLAG_CHANNEL_VIEW | FLAG_CHANNEL_VARIABLE | FLAG_NEW), //Available for all channels that are "in view", always up-to-date + make_shared(CHANNEL_CREATED_BY, "CHANNEL_CREATED_BY", "0", TYPE_UNSIGNED_NUMBER, FLAG_SAVE | FLAG_CHANNEL_VIEW | FLAG_CHANNEL_VARIABLE | FLAG_NEW), //Available for all channels that are "in view", always up-to-date + }; + + array, GroupProperties::GROUP_ENDMARKER> group_info = { + make_shared(GROUP_UNDEFINED, "GROUP_UNDEFINED", "", TYPE_UNKNOWN, 0), + make_shared(GROUP_ID, "gid", "0", TYPE_UNSIGNED_NUMBER, FLAG_INTERNAL), + make_shared(GROUP_TYPE, "type", "0", TYPE_UNSIGNED_NUMBER, FLAG_GROUP_VIEW), + make_shared(GROUP_NAME, "name", "Undefined group", TYPE_STRING, FLAG_GROUP_VIEW), + make_shared(GROUP_SORTID, "sortid", "0", TYPE_UNSIGNED_NUMBER, FLAG_GROUP_VIEW), + make_shared(GROUP_SAVEDB, "savedb", "0", TYPE_BOOL, FLAG_GROUP_VIEW), + make_shared(GROUP_NAMEMODE, "namemode", "0", TYPE_UNSIGNED_NUMBER, FLAG_GROUP_VIEW), + make_shared(GROUP_ICONID, "iconid", "0", TYPE_UNSIGNED_NUMBER, FLAG_GROUP_VIEW) + }; + + array, ClientProperties::CLIENT_ENDMARKER> client_info = { + make_shared(CLIENT_UNDEFINED, "CLIENT_UNDEFINED", "undefined", TYPE_UNKNOWN, 0), + make_shared(CLIENT_UNIQUE_IDENTIFIER, "CLIENT_UNIQUE_IDENTIFIER", "", TYPE_STRING, FLAG_CLIENT_VIEW | FLAG_SNAPSHOT | FLAG_GLOBAL), //automatically up-to-date for any manager "in view", can be used to identify this particular manager installation + make_shared(CLIENT_NICKNAME, "CLIENT_NICKNAME", "undefined", TYPE_STRING, FLAG_CLIENT_VIEW | FLAG_SAVE_MUSIC | FLAG_SNAPSHOT | FLAG_GLOBAL | FLAG_USER_EDITABLE), //automatically up-to-date for any manager "in view" + make_shared(CLIENT_VERSION, "CLIENT_VERSION", "unknown", TYPE_STRING, FLAG_CLIENT_VVSS | FLAG_USER_EDITABLE | FLAG_GLOBAL | FLAG_SAVE_MUSIC), //for other clients than ourself, this needs to be requested (=> requestClientVariables) + make_shared(CLIENT_PLATFORM, "CLIENT_PLATFORM", "unknown", TYPE_STRING, FLAG_CLIENT_VVSS | FLAG_USER_EDITABLE | FLAG_GLOBAL | FLAG_SAVE_MUSIC), //for other clients than ourself, this needs to be requested (=> requestClientVariables) + make_shared(CLIENT_FLAG_TALKING, "CLIENT_FLAG_TALKING", "0", TYPE_BOOL, FLAG_INTERNAL), //automatically up-to-date for any manager that can be heard (in room / whisper) + make_shared(CLIENT_INPUT_MUTED, "CLIENT_INPUT_MUTED", "0", TYPE_BOOL, FLAG_CLIENT_VIEW | FLAG_USER_EDITABLE), //automatically up-to-date for any manager "in view", this clients microphone mute status + make_shared(CLIENT_OUTPUT_MUTED, "CLIENT_OUTPUT_MUTED", "0", TYPE_BOOL, FLAG_CLIENT_VIEW | FLAG_USER_EDITABLE), //automatically up-to-date for any manager "in view", this clients headphones/speakers/mic combined mute status + make_shared(CLIENT_OUTPUTONLY_MUTED, "CLIENT_OUTPUTONLY_MUTED", "0", TYPE_BOOL, FLAG_CLIENT_VIEW | FLAG_USER_EDITABLE), //automatically up-to-date for any manager "in view", this clients headphones/speakers only mute status + make_shared(CLIENT_INPUT_HARDWARE, "CLIENT_INPUT_HARDWARE", "0", TYPE_BOOL, FLAG_CLIENT_VIEW | FLAG_USER_EDITABLE), //automatically up-to-date for any manager "in view", this clients microphone hardware status (is the capture device opened?) + make_shared(CLIENT_OUTPUT_HARDWARE, "CLIENT_OUTPUT_HARDWARE", "0", TYPE_BOOL, FLAG_CLIENT_VIEW | FLAG_USER_EDITABLE), //automatically up-to-date for any manager "in view", this clients headphone/speakers hardware status (is the playback device opened?) + make_shared(CLIENT_DEFAULT_CHANNEL, "CLIENT_DEFAULT_CHANNEL", "", TYPE_STRING, FLAG_INTERNAL), //only usable for ourself, the default channel we used to connect on our last connection attempt + make_shared(CLIENT_DEFAULT_CHANNEL_PASSWORD, "CLIENT_DEFAULT_CHANNEL_PASSWORD", "", TYPE_STRING, FLAG_INTERNAL), //internal use + make_shared(CLIENT_SERVER_PASSWORD, "CLIENT_SERVER_PASSWORD", "", TYPE_STRING, FLAG_INTERNAL), //internal use + make_shared(CLIENT_META_DATA, "CLIENT_META_DATA", "", TYPE_STRING, FLAG_CLIENT_VIEW| FLAG_GLOBAL), //automatically up-to-date for any manager "in view", not used by TeamSpeak, free storage for sdk users + make_shared(CLIENT_IS_RECORDING, "CLIENT_IS_RECORDING", "0", TYPE_BOOL, FLAG_CLIENT_VIEW | FLAG_USER_EDITABLE), //automatically up-to-date for any manager "in view" + make_shared(CLIENT_VERSION_SIGN, "CLIENT_VERSION_SIGN", "", TYPE_STRING, FLAG_INTERNAL), //sign + make_shared(CLIENT_SECURITY_HASH, "CLIENT_SECURITY_HASH", "", TYPE_STRING, FLAG_INTERNAL), //SDK use, not used by teamspeak. Hash is provided by an outside source. A channel will use the security salt + other manager data to calculate a hash, which must be the same as the one provided here. + + //Rare properties + make_shared(CLIENT_KEY_OFFSET, "CLIENT_KEY_OFFSET", "0", TYPE_UNSIGNED_NUMBER, FLAG_INTERNAL), //internal use + make_shared(CLIENT_LOGIN_NAME, "CLIENT_LOGIN_NAME", "", TYPE_STRING, FLAG_CLIENT_VARIABLE| FLAG_GLOBAL), //used for serverquery clients, makes no sense on normal clients currently + make_shared(CLIENT_LOGIN_PASSWORD, "CLIENT_LOGIN_PASSWORD", "", TYPE_STRING, FLAG_INTERNAL| FLAG_GLOBAL), //used for serverquery clients, makes no sense on normal clients currently + make_shared(CLIENT_DATABASE_ID, "CLIENT_DATABASE_ID", "0", TYPE_UNSIGNED_NUMBER, FLAG_CLIENT_VIEW | FLAG_GLOBAL), //automatically up-to-date for any manager "in view", only valid with PERMISSION feature, holds database manager id + make_shared(CLIENT_ID, "clid", "0", TYPE_UNSIGNED_NUMBER, FLAG_CLIENT_VV), //clid! + make_shared(CLIENT_HARDWARE_ID, "hwid", "", TYPE_STRING, FLAG_SAVE), //hwid! + make_shared(CLIENT_CHANNEL_GROUP_ID, "CLIENT_CHANNEL_GROUP_ID", "0", TYPE_UNSIGNED_NUMBER, FLAG_CLIENT_VIEW), //automatically up-to-date for any manager "in view", only valid with PERMISSION feature, holds database manager id + make_shared(CLIENT_SERVERGROUPS, "CLIENT_SERVERGROUPS", "0", TYPE_STRING, FLAG_CLIENT_VIEW), //automatically up-to-date for any manager "in view", only valid with PERMISSION feature, holds all servergroups manager belongs too + make_shared(CLIENT_CREATED, "CLIENT_CREATED", "0", TYPE_UNSIGNED_NUMBER, FLAG_CLIENT_VARIABLE | FLAG_SAVE_MUSIC | FLAG_SNAPSHOT | FLAG_GLOBAL), //this needs to be requested (=> requestClientVariables), first time this manager connected to this server + make_shared(CLIENT_LASTCONNECTED, "CLIENT_LASTCONNECTED", "0", TYPE_UNSIGNED_NUMBER, FLAG_CLIENT_VARIABLE | FLAG_SNAPSHOT | FLAG_GLOBAL), //this needs to be requested (=> requestClientVariables), last time this manager connected to this server + make_shared(CLIENT_TOTALCONNECTIONS, "CLIENT_TOTALCONNECTIONS", "0", TYPE_UNSIGNED_NUMBER, FLAG_CLIENT_VARIABLE | FLAG_CLIENT_VIEW | FLAG_SNAPSHOT | FLAG_GLOBAL), //this needs to be requested (=> requestClientVariables), how many times this manager connected to this server + make_shared(CLIENT_AWAY, "CLIENT_AWAY", "0", TYPE_BOOL, FLAG_CLIENT_VIEW | FLAG_USER_EDITABLE), //automatically up-to-date for any manager "in view", this clients away status + make_shared(CLIENT_AWAY_MESSAGE, "CLIENT_AWAY_MESSAGE", "", TYPE_STRING, FLAG_CLIENT_VIEW | FLAG_USER_EDITABLE), //automatically up-to-date for any manager "in view", this clients away message + make_shared(CLIENT_TYPE, "CLIENT_TYPE", "0", TYPE_UNSIGNED_NUMBER, FLAG_CLIENT_VIEW | FLAG_GLOBAL), //automatically up-to-date for any manager "in view", determines if this is a real manager or a server-query connection + make_shared(CLIENT_TYPE_EXACT, "CLIENT_TYPE_EXACT", "0", TYPE_UNSIGNED_NUMBER, FLAG_CLIENT_VIEW | FLAG_NEW | FLAG_GLOBAL), //automatically up-to-date for any manager "in view", determines if this is a real manager or a server-query connection + make_shared(CLIENT_FLAG_AVATAR, "CLIENT_FLAG_AVATAR", "", TYPE_STRING, FLAG_CLIENT_VIEW | FLAG_SAVE | FLAG_USER_EDITABLE), //automatically up-to-date for any manager "in view", this manager got an avatar + make_shared(CLIENT_TALK_POWER, "CLIENT_TALK_POWER", "0", TYPE_UNSIGNED_NUMBER, FLAG_CLIENT_VIEW), //automatically up-to-date for any manager "in view", only valid with PERMISSION feature, holds database manager id + make_shared(CLIENT_TALK_REQUEST, "CLIENT_TALK_REQUEST", "0", TYPE_BOOL, FLAG_CLIENT_VIEW | FLAG_USER_EDITABLE), //automatically up-to-date for any manager "in view", only valid with PERMISSION feature, holds timestamp where manager requested to talk + make_shared(CLIENT_TALK_REQUEST_MSG, "CLIENT_TALK_REQUEST_MSG", "", TYPE_STRING, FLAG_CLIENT_VIEW | FLAG_USER_EDITABLE), //automatically up-to-date for any manager "in view", only valid with PERMISSION feature, holds matter for the request + make_shared(CLIENT_DESCRIPTION, "CLIENT_DESCRIPTION", "", TYPE_STRING, FLAG_CLIENT_VIEW | FLAG_SS | FLAG_USER_EDITABLE), //automatically up-to-date for any manager "in view" + make_shared(CLIENT_IS_TALKER, "CLIENT_IS_TALKER", "0", TYPE_BOOL, FLAG_CLIENT_VIEW | FLAG_USER_EDITABLE), //automatically up-to-date for any manager "in view" + make_shared(CLIENT_MONTH_BYTES_UPLOADED, "CLIENT_MONTH_BYTES_UPLOADED", "0", TYPE_UNSIGNED_NUMBER, FLAG_CLIENT_VARIABLE | FLAG_SAVE), //this needs to be requested (=> requestClientVariables) + make_shared(CLIENT_MONTH_BYTES_DOWNLOADED, "CLIENT_MONTH_BYTES_DOWNLOADED", "0", TYPE_UNSIGNED_NUMBER, FLAG_CLIENT_VARIABLE | FLAG_SAVE), //this needs to be requested (=> requestClientVariables) + make_shared(CLIENT_TOTAL_BYTES_UPLOADED, "CLIENT_TOTAL_BYTES_UPLOADED", "0", TYPE_UNSIGNED_NUMBER, FLAG_CLIENT_VARIABLE | FLAG_SAVE), //this needs to be requested (=> requestClientVariables) + make_shared(CLIENT_TOTAL_BYTES_DOWNLOADED, "CLIENT_TOTAL_BYTES_DOWNLOADED", "0", TYPE_UNSIGNED_NUMBER, FLAG_CLIENT_VARIABLE | FLAG_SAVE), //this needs to be requested (=> requestClientVariables) + make_shared(CLIENT_TOTAL_ONLINE_TIME, "CLIENT_TOTAL_ONLINE_TIME", "0", TYPE_UNSIGNED_NUMBER, FLAG_CLIENT_VARIABLE | FLAG_SAVE | FLAG_NEW), //this needs to be requested (=> requestClientVariables) + make_shared(CLIENT_MONTH_ONLINE_TIME, "CLIENT_MONTH_ONLINE_TIME", "0", TYPE_UNSIGNED_NUMBER, FLAG_CLIENT_VARIABLE | FLAG_SAVE | FLAG_NEW), //this needs to be requested (=> requestClientVariables) + make_shared(CLIENT_IS_PRIORITY_SPEAKER, "CLIENT_IS_PRIORITY_SPEAKER", "0", TYPE_BOOL, FLAG_CLIENT_VIEW | FLAG_USER_EDITABLE), //automatically up-to-date for any manager "in view" + make_shared(CLIENT_UNREAD_MESSAGES, "CLIENT_UNREAD_MESSAGES", "0", TYPE_UNSIGNED_NUMBER, FLAG_CLIENT_VIEW), //automatically up-to-date for any manager "in view" + make_shared(CLIENT_NICKNAME_PHONETIC, "CLIENT_NICKNAME_PHONETIC", "", TYPE_STRING, FLAG_CLIENT_VIEW | FLAG_USER_EDITABLE), //automatically up-to-date for any manager "in view" + make_shared(CLIENT_NEEDED_SERVERQUERY_VIEW_POWER, "CLIENT_NEEDED_SERVERQUERY_VIEW_POWER", "0", TYPE_UNSIGNED_NUMBER, FLAG_CLIENT_VIEW), //automatically up-to-date for any manager "in view" + make_shared(CLIENT_DEFAULT_TOKEN, "CLIENT_DEFAULT_TOKEN", "", TYPE_STRING, FLAG_INTERNAL), //only usable for ourself, the default token we used to connect on our last connection attempt + make_shared(CLIENT_ICON_ID, "CLIENT_ICON_ID", "0", TYPE_UNSIGNED_NUMBER, FLAG_CLIENT_VIEW | FLAG_CLIENT_VARIABLE), //automatically up-to-date for any manager "in view" + make_shared(CLIENT_IS_CHANNEL_COMMANDER, "CLIENT_IS_CHANNEL_COMMANDER", "0", TYPE_BOOL, FLAG_CLIENT_VIEW | FLAG_USER_EDITABLE | FLAG_SAVE_MUSIC), //automatically up-to-date for any manager "in view" + make_shared(CLIENT_COUNTRY, "CLIENT_COUNTRY", "TS", TYPE_STRING, FLAG_CLIENT_VIEW | FLAG_CLIENT_VARIABLE | FLAG_GLOBAL | FLAG_SAVE_MUSIC | FLAG_USER_EDITABLE), //automatically up-to-date for any manager "in view" + make_shared(CLIENT_CHANNEL_GROUP_INHERITED_CHANNEL_ID, "CLIENT_CHANNEL_GROUP_INHERITED_CHANNEL_ID", "0", TYPE_UNSIGNED_NUMBER, FLAG_CLIENT_VIEW), //automatically up-to-date for any manager "in view", only valid with PERMISSION feature, contains channel_id where the channel_group_id is set from + make_shared(CLIENT_BADGES, "CLIENT_BADGES", "", TYPE_STRING, FLAG_CLIENT_VIEW | FLAG_USER_EDITABLE), //automatically up-to-date for any manager "in view", stores icons for partner badges + make_shared(CLIENT_MYTEAMSPEAK_ID, "CLIENT_MYTEAMSPEAK_ID", "", TYPE_STRING, FLAG_CLIENT_VIEW | FLAG_SS | FLAG_USER_EDITABLE), //automatically up-to-date for any manager "in view", stores icons for partner badges + make_shared(CLIENT_INTEGRATIONS, "CLIENT_INTEGRATIONS", "", TYPE_STRING, FLAG_CLIENT_VIEW | FLAG_USER_EDITABLE), //automatically up-to-date for any manager "in view", stores icons for partner badges + make_shared(CLIENT_ACTIVE_INTEGRATIONS_INFO, "CLIENT_ACTIVE_INTEGRATIONS_INFO", "", TYPE_STRING, FLAG_INTERNAL | FLAG_USER_EDITABLE), + + make_shared(CLIENT_TEAFORO_ID, "CLIENT_TEAFORO_ID", "0", TYPE_UNSIGNED_NUMBER, FLAG_NEW | FLAG_CLIENT_VIEW), + make_shared(CLIENT_TEAFORO_NAME, "CLIENT_TEAFORO_NAME", "", TYPE_STRING, FLAG_NEW | FLAG_CLIENT_VIEW), + + //Music bot stuff + make_shared(CLIENT_OWNER, "CLIENT_OWNER", "0", TYPE_UNSIGNED_NUMBER, FLAG_NEW | FLAG_CLIENT_VIEW), + make_shared(CLIENT_BOT_TYPE, "CLIENT_BOT_TYPE", "0", TYPE_UNSIGNED_NUMBER, FLAG_NEW | FLAG_SAVE_MUSIC | FLAG_USER_EDITABLE | FLAG_CLIENT_VIEW), + make_shared(CLIENT_LAST_CHANNEL, "CLIENT_LAST_CHANNEL", "0", TYPE_UNSIGNED_NUMBER, FLAG_NEW | FLAG_INTERNAL | FLAG_SAVE_MUSIC), + make_shared(CLIENT_PLAYER_STATE, "PLAYER_STATE", "0", TYPE_UNSIGNED_NUMBER, FLAG_NEW | FLAG_CLIENT_VIEW | FLAG_SAVE_MUSIC), + make_shared(CLIENT_PLAYER_VOLUME, "PLAYER_VOLUME", "1", TYPE_FLOAT, FLAG_NEW | FLAG_SAVE_MUSIC | FLAG_CLIENT_VIEW | FLAG_USER_EDITABLE), + make_shared(CLIENT_PLAYLIST_ID, "CLIENT_PLAYLIST_ID", "0", TYPE_UNSIGNED_NUMBER, FLAG_NEW | FLAG_CLIENT_VARIABLE | FLAG_SAVE_MUSIC), + make_shared(CLIENT_DISABLED, "CLIENT_DISABLED", "0", TYPE_BOOL, FLAG_NEW | FLAG_CLIENT_VARIABLE), + make_shared(CLIENT_UPTIME_MODE, "CLIENT_UPTIME_MODE", "0", TYPE_UNSIGNED_NUMBER, FLAG_NEW | FLAG_CLIENT_VARIABLE | FLAG_USER_EDITABLE | FLAG_SAVE_MUSIC), + make_shared(CLIENT_FLAG_NOTIFY_SONG_CHANGE, "CLIENT_FLAG_NOTIFY_SONG_CHANGE", "1", TYPE_BOOL, FLAG_NEW | FLAG_CLIENT_VARIABLE | FLAG_USER_EDITABLE | FLAG_SAVE_MUSIC) + }; + + #define str(s) #s + #define V(key, flags) key, str(key), "0", TYPE_UNSIGNED_NUMBER, flags + #define F(key, flags) key, str(key), "0", TYPE_FLOAT, flags + + array, ConnectionProperties::CONNECTION_ENDMARKER> connection_info = { + make_shared(CONNECTION_UNDEFINED, "CONNECTION_UNDEFINED", "undefined", TYPE_UNKNOWN, 0), + make_shared(CONNECTION_PING, "CONNECTION_PING", "0", TYPE_UNSIGNED_NUMBER, 0), //average latency for a round trip through and back this connection + make_shared(CONNECTION_PING_DEVIATION, "CONNECTION_PING_DEVIATION", "0", TYPE_UNSIGNED_NUMBER, 0), //standard deviation of the above average latency + make_shared(CONNECTION_CONNECTED_TIME, "CONNECTION_CONNECTED_TIME", "0", TYPE_UNSIGNED_NUMBER, 0), //how long the connection exists already + make_shared(CONNECTION_IDLE_TIME, "CONNECTION_IDLE_TIME", "0", TYPE_UNSIGNED_NUMBER, 0), //how long since the last action of this manager + make_shared(CONNECTION_CLIENT_IP, "CONNECTION_CLIENT_IP", "", TYPE_STRING, FLAG_SAVE), //NEED DB SAVE! //IP of this manager (as seen from the server side) + make_shared(CONNECTION_CLIENT_PORT, "CONNECTION_CLIENT_PORT", "0", TYPE_UNSIGNED_NUMBER, 0), //Port of this manager (as seen from the server side) + make_shared(CONNECTION_SERVER_IP, "CONNECTION_SERVER_IP", "", TYPE_STRING, 0), //IP of the server (seen from the manager side) - only available on yourself, not for remote clients, not available server side + make_shared(CONNECTION_SERVER_PORT, "CONNECTION_SERVER_PORT", "0", TYPE_UNSIGNED_NUMBER, 0), //Port of the server (seen from the manager side) - only available on yourself, not for remote clients, not available server side + make_shared(V(CONNECTION_PACKETS_SENT_SPEECH, 0)), //how many Speech packets were sent through this connection + make_shared(V(CONNECTION_PACKETS_SENT_KEEPALIVE, 0)), + make_shared(V(CONNECTION_PACKETS_SENT_CONTROL,0)), + make_shared(V(CONNECTION_PACKETS_SENT_TOTAL, FLAG_CLIENT_INFO)), //how many packets were sent totally (this is PACKETS_SENT_SPEECH + PACKETS_SENT_KEEPALIVE + PACKETS_SENT_CONTROL) + make_shared(V(CONNECTION_BYTES_SENT_SPEECH, 0)), + make_shared(V(CONNECTION_BYTES_SENT_KEEPALIVE, 0)), + make_shared(V(CONNECTION_BYTES_SENT_CONTROL, 0)), + make_shared(V(CONNECTION_BYTES_SENT_TOTAL, FLAG_CLIENT_INFO)), + make_shared(V(CONNECTION_PACKETS_RECEIVED_SPEECH, 0)), + make_shared(V(CONNECTION_PACKETS_RECEIVED_KEEPALIVE, 0)), + make_shared(V(CONNECTION_PACKETS_RECEIVED_CONTROL, 0)), + make_shared(V(CONNECTION_PACKETS_RECEIVED_TOTAL, FLAG_CLIENT_INFO)), + make_shared(V(CONNECTION_BYTES_RECEIVED_SPEECH, 0)), + make_shared(V(CONNECTION_BYTES_RECEIVED_KEEPALIVE, 0)), + make_shared(V(CONNECTION_BYTES_RECEIVED_CONTROL, 0)), + make_shared(V(CONNECTION_BYTES_RECEIVED_TOTAL, FLAG_CLIENT_INFO)), + make_shared(F(CONNECTION_PACKETLOSS_SPEECH, 0)), + make_shared(F(CONNECTION_PACKETLOSS_KEEPALIVE, 0)), + make_shared(F(CONNECTION_PACKETLOSS_CONTROL, 0)), + make_shared(F(CONNECTION_PACKETLOSS_TOTAL, FLAG_CLIENT_INFO)), //the probability with which a packet round trip failed because a packet was lost + make_shared(F(CONNECTION_SERVER2CLIENT_PACKETLOSS_SPEECH, 0)), //the probability with which a speech packet failed from the server to the manager + make_shared(F(CONNECTION_SERVER2CLIENT_PACKETLOSS_KEEPALIVE, 0)), + make_shared(F(CONNECTION_SERVER2CLIENT_PACKETLOSS_CONTROL, 0)), + make_shared(F(CONNECTION_SERVER2CLIENT_PACKETLOSS_TOTAL, FLAG_CLIENT_INFO)), + make_shared(F(CONNECTION_CLIENT2SERVER_PACKETLOSS_SPEECH, 0)), + make_shared(F(CONNECTION_CLIENT2SERVER_PACKETLOSS_KEEPALIVE, 0)), + make_shared(F(CONNECTION_CLIENT2SERVER_PACKETLOSS_CONTROL, 0)), + make_shared(F(CONNECTION_CLIENT2SERVER_PACKETLOSS_TOTAL, FLAG_CLIENT_INFO)), + make_shared(V(CONNECTION_BANDWIDTH_SENT_LAST_SECOND_SPEECH, 0)), //howmany bytes of speech packets we sent during the last second + make_shared(V(CONNECTION_BANDWIDTH_SENT_LAST_SECOND_KEEPALIVE, 0)), + make_shared(V(CONNECTION_BANDWIDTH_SENT_LAST_SECOND_CONTROL, 0)), + make_shared(V(CONNECTION_BANDWIDTH_SENT_LAST_SECOND_TOTAL,FLAG_CLIENT_INFO)), + make_shared(V(CONNECTION_BANDWIDTH_SENT_LAST_MINUTE_SPEECH,0)), //howmany bytes/s of speech packets we sent in average during the last minute + make_shared(V(CONNECTION_BANDWIDTH_SENT_LAST_MINUTE_KEEPALIVE,0)), + make_shared(V(CONNECTION_BANDWIDTH_SENT_LAST_MINUTE_CONTROL,0)), + make_shared(V(CONNECTION_BANDWIDTH_SENT_LAST_MINUTE_TOTAL,FLAG_CLIENT_INFO)), + make_shared(V(CONNECTION_BANDWIDTH_RECEIVED_LAST_SECOND_SPEECH, 0)), + make_shared(V(CONNECTION_BANDWIDTH_RECEIVED_LAST_SECOND_KEEPALIVE,0)), + make_shared(V(CONNECTION_BANDWIDTH_RECEIVED_LAST_SECOND_CONTROL,0)), + make_shared(V(CONNECTION_BANDWIDTH_RECEIVED_LAST_SECOND_TOTAL,FLAG_CLIENT_INFO)), + make_shared(V(CONNECTION_BANDWIDTH_RECEIVED_LAST_MINUTE_SPEECH,0)), + make_shared(V(CONNECTION_BANDWIDTH_RECEIVED_LAST_MINUTE_KEEPALIVE,0)), + make_shared(V(CONNECTION_BANDWIDTH_RECEIVED_LAST_MINUTE_CONTROL,0)), + make_shared(V(CONNECTION_BANDWIDTH_RECEIVED_LAST_MINUTE_TOTAL,FLAG_CLIENT_INFO)), + + //Rare properties + make_shared(V(CONNECTION_FILETRANSFER_BANDWIDTH_SENT, FLAG_CLIENT_INFO)), //how many bytes per second are currently being sent by file transfers + make_shared(V(CONNECTION_FILETRANSFER_BANDWIDTH_RECEIVED, FLAG_CLIENT_INFO)), //how many bytes per second are currently being received by file transfers + make_shared(V(CONNECTION_FILETRANSFER_BYTES_RECEIVED_TOTAL, FLAG_CLIENT_INFO)), //how many bytes we received in total through file transfers + make_shared(V(CONNECTION_FILETRANSFER_BYTES_SENT_TOTAL, FLAG_CLIENT_INFO)) //how many bytes we sent in total through file transfers + }; + + + array, InstanceProperties::SERVERINSTANCE_ENDMARKER> instance_info = { + make_shared(SERVERINSTANCE_UNDEFINED, "SERVERINSTANCE_UNDEFINED", "undefined", TYPE_UNKNOWN, 0), + make_shared(SERVERINSTANCE_DATABASE_VERSION, "SERVERINSTANCE_DATABASE_VERSION", "0", TYPE_UNSIGNED_NUMBER, FLAG_INSTANCE_VARIABLE), + make_shared(SERVERINSTANCE_PERMISSIONS_VERSION, "SERVERINSTANCE_PERMISSIONS_VERSION", "0", TYPE_UNSIGNED_NUMBER, FLAG_INSTANCE_VARIABLE), + make_shared(SERVERINSTANCE_FILETRANSFER_HOST, "SERVERINSTANCE_FILETRANSFER_HOST", "0.0.0.0", TYPE_STRING, FLAG_INSTANCE_VARIABLE | FLAG_SAVE), + make_shared(SERVERINSTANCE_FILETRANSFER_PORT, "SERVERINSTANCE_FILETRANSFER_PORT", "30303", TYPE_UNSIGNED_NUMBER, FLAG_INSTANCE_VARIABLE | FLAG_SAVE), + make_shared(SERVERINSTANCE_QUERY_HOST, "SERVERINSTANCE_QUERY_HOST", "0.0.0.0", TYPE_STRING, FLAG_INSTANCE_VARIABLE | FLAG_SAVE), + make_shared(SERVERINSTANCE_QUERY_PORT, "SERVERINSTANCE_QUERY_PORT", "10101", TYPE_UNSIGNED_NUMBER, FLAG_INSTANCE_VARIABLE | FLAG_SAVE), + make_shared(SERVERINSTANCE_MONTHLY_TIMESTAMP, "SERVERINSTANCE_MONTHLY_TIMESTAMP", "0", TYPE_UNSIGNED_NUMBER, FLAG_INSTANCE_VARIABLE | FLAG_SAVE), + make_shared(SERVERINSTANCE_MAX_DOWNLOAD_TOTAL_BANDWIDTH, "SERVERINSTANCE_MAX_DOWNLOAD_TOTAL_BANDWIDTH", "-1", TYPE_SIGNED_NUMBER, FLAG_INSTANCE_VARIABLE | FLAG_SAVE), + make_shared(SERVERINSTANCE_MAX_UPLOAD_TOTAL_BANDWIDTH, "SERVERINSTANCE_MAX_UPLOAD_TOTAL_BANDWIDTH", "-1", TYPE_SIGNED_NUMBER, FLAG_INSTANCE_VARIABLE | FLAG_SAVE), + make_shared(SERVERINSTANCE_SERVERQUERY_FLOOD_COMMANDS, "SERVERINSTANCE_SERVERQUERY_FLOOD_COMMANDS", "3", TYPE_UNSIGNED_NUMBER, FLAG_INSTANCE_VARIABLE | FLAG_SAVE), //how many commands we can issue while in the SERVERINSTANCE_SERVERQUERY_FLOOD_TIME window + make_shared(SERVERINSTANCE_SERVERQUERY_FLOOD_TIME, "SERVERINSTANCE_SERVERQUERY_FLOOD_TIME", "1", TYPE_UNSIGNED_NUMBER, FLAG_INSTANCE_VARIABLE | FLAG_SAVE), //time window in seconds for max command execution check + make_shared(SERVERINSTANCE_SERVERQUERY_BAN_TIME, "SERVERINSTANCE_SERVERQUERY_BAN_TIME", "600", TYPE_UNSIGNED_NUMBER, FLAG_INSTANCE_VARIABLE | FLAG_SAVE), //how many seconds someone get banned if he floods + make_shared(SERVERINSTANCE_TEMPLATE_SERVERADMIN_GROUP, "SERVERINSTANCE_TEMPLATE_SERVERADMIN_GROUP", "0", TYPE_UNSIGNED_NUMBER, FLAG_INSTANCE_VARIABLE | FLAG_SAVE), + make_shared(SERVERINSTANCE_TEMPLATE_SERVERDEFAULT_GROUP, "SERVERINSTANCE_TEMPLATE_SERVERDEFAULT_GROUP", "0", TYPE_UNSIGNED_NUMBER, FLAG_INSTANCE_VARIABLE | FLAG_SAVE), + make_shared(SERVERINSTANCE_TEMPLATE_CHANNELADMIN_GROUP, "SERVERINSTANCE_TEMPLATE_CHANNELADMIN_GROUP", "0", TYPE_UNSIGNED_NUMBER, FLAG_INSTANCE_VARIABLE | FLAG_SAVE), + make_shared(SERVERINSTANCE_TEMPLATE_CHANNELDEFAULT_GROUP, "SERVERINSTANCE_TEMPLATE_CHANNELDEFAULT_GROUP", "0", TYPE_UNSIGNED_NUMBER, FLAG_INSTANCE_VARIABLE | FLAG_SAVE), + make_shared(SERVERINSTANCE_TEMPLATE_MUSICDEFAULT_GROUP, "SERVERINSTANCE_TEMPLATE_MUSICDEFAULT_GROUP", "0", TYPE_UNSIGNED_NUMBER, FLAG_INSTANCE_VARIABLE | FLAG_SAVE), + make_shared(SERVERINSTANCE_GUEST_SERVERQUERY_GROUP, "SERVERINSTANCE_GUEST_SERVERQUERY_GROUP", "0", TYPE_UNSIGNED_NUMBER, FLAG_INSTANCE_VARIABLE | FLAG_SAVE), + make_shared(SERVERINSTANCE_ADMIN_SERVERQUERY_GROUP, "SERVERINSTANCE_ADMIN_SERVERQUERY_GROUP", "0", TYPE_UNSIGNED_NUMBER, FLAG_INSTANCE_VARIABLE | FLAG_SAVE), + make_shared(SERVERINSTANCE_PENDING_CONNECTIONS_PER_IP, "SERVERINSTANCE_PENDING_CONNECTIONS_PER_IP", "0", TYPE_UNSIGNED_NUMBER, FLAG_INSTANCE_VARIABLE | FLAG_SAVE), + make_shared(SERVERINSTANCE_SPOKEN_TIME_TOTAL, "SERVERINSTANCE_SPOKEN_TIME_TOTAL", "0", TYPE_UNSIGNED_NUMBER, FLAG_INTERNAL | FLAG_SAVE | FLAG_NEW), + make_shared(SERVERINSTANCE_SPOKEN_TIME_DELETED, "SERVERINSTANCE_SPOKEN_TIME_DELETED", "0", TYPE_UNSIGNED_NUMBER, FLAG_INTERNAL | FLAG_SAVE | FLAG_NEW), + make_shared(SERVERINSTANCE_SPOKEN_TIME_ALIVE, "SERVERINSTANCE_SPOKEN_TIME_ALIVE", "0", TYPE_UNSIGNED_NUMBER, FLAG_INTERNAL | FLAG_SAVE | FLAG_NEW), + make_shared(SERVERINSTANCE_SPOKEN_TIME_VARIANZ, "SERVERINSTANCE_SPOKEN_TIME_VARIANZ", "0", TYPE_UNSIGNED_NUMBER, FLAG_INTERNAL | FLAG_SAVE | FLAG_NEW), + make_shared(SERVERINSTANCE_UNIQUE_ID, "SERVERINSTANCE_UNIQUE_ID", "", TYPE_STRING, FLAG_INTERNAL | FLAG_SAVE | FLAG_NEW), + }; + + + array, PlaylistProperties::PLAYLIST_ENDMARKER> playlist_info = { + make_shared(PLAYLIST_UNDEFINED, "PLAYLIST_UNDEFINED", "undefined", TYPE_UNKNOWN, 0), + make_shared(PLAYLIST_ID, "PLAYLIST_ID", "0", TYPE_UNSIGNED_NUMBER, FLAG_PLAYLIST_VARIABLE), + make_shared(PLAYLIST_TYPE, "PLAYLIST_TYPE", "0", TYPE_UNSIGNED_NUMBER, FLAG_PLAYLIST_VARIABLE | FLAG_SAVE), + make_shared(PLAYLIST_TITLE, "PLAYLIST_TITLE", "Yet another playlist", TYPE_STRING, FLAG_PLAYLIST_VARIABLE | FLAG_USER_EDITABLE | FLAG_SAVE), + make_shared(PLAYLIST_DESCRIPTION, "PLAYLIST_DESCRIPTION", "", TYPE_STRING, FLAG_PLAYLIST_VARIABLE | FLAG_USER_EDITABLE | FLAG_SAVE), + make_shared(PLAYLIST_OWNER_DBID, "PLAYLIST_OWNER_DBID", "0", TYPE_UNSIGNED_NUMBER, FLAG_PLAYLIST_VARIABLE | FLAG_SAVE), + make_shared(PLAYLIST_OWNER_NAME, "PLAYLIST_OWNER_NAME", "0", TYPE_STRING, FLAG_PLAYLIST_VARIABLE | FLAG_SAVE), + make_shared(PLAYLIST_FLAG_DELETE_PLAYED, "PLAYLIST_FLAG_DELETE_PLAYED", "1", TYPE_BOOL, FLAG_PLAYLIST_VARIABLE | FLAG_USER_EDITABLE | FLAG_SAVE), + make_shared(PLAYLIST_REPLAY_MODE, "PLAYLIST_REPLAY_MODE", "0", TYPE_UNSIGNED_NUMBER, FLAG_PLAYLIST_VARIABLE | FLAG_USER_EDITABLE | FLAG_SAVE), + make_shared(PLAYLIST_CURRENT_SONG_ID, "PLAYLIST_CURRENT_SONG_ID", "0", TYPE_UNSIGNED_NUMBER, FLAG_PLAYLIST_VARIABLE | FLAG_SAVE), + make_shared(PLAYLIST_FLAG_FINISHED, "PLAYLIST_FLAG_FINISHED", "0", TYPE_BOOL, FLAG_PLAYLIST_VARIABLE | FLAG_USER_EDITABLE | FLAG_SAVE), + make_shared(PLAYLIST_MAX_SONGS, "PLAYLIST_MAX_SONGS", "-1", TYPE_SIGNED_NUMBER, FLAG_PLAYLIST_VARIABLE | FLAG_USER_EDITABLE | FLAG_SAVE), + }; + + shared_ptr PropertyDescription::unknown = make_shared(UNKNOWN_UNDEFINED, "undefined", "", ValueType::TYPE_UNKNOWN, 0); + namespace impl { + #define M(type, _info) \ + template <> \ + const std::shared_ptr& info(type type) { \ + for(const auto& element : (_info)) \ + if(element->property_index == (type)) \ + return element; \ + return (_info)[0]; \ + } \ + \ + template <> \ + const std::shared_ptr& info(const std::string& _type) { \ + for(const auto& element : (_info)) \ + if(element->name == _type) \ + return element; \ + return (_info)[0]; \ + } \ + \ + \ + template <> \ + std::deque> list() { \ + return {_info.begin(), _info.end()}; \ + } + + M(VirtualServerProperties, virtualserver_info); + M(ChannelProperties, channel_info); + M(GroupProperties, group_info); + M(ClientProperties, client_info); + M(ConnectionProperties, connection_info); + M(InstanceProperties, instance_info); + M(PlaylistProperties, playlist_info); + + + #define TP(info) \ + { for(const auto& prop : (info)) \ + if(prop->name == key) return prop; } + + const std::shared_ptr& info_key(const std::string& key) { + TP(virtualserver_info); + TP(channel_info); + TP(group_info); + TP(client_info); + TP(connection_info); + TP(instance_info); + TP(playlist_info); + return PropertyDescription::unknown; + } + + + const std::shared_ptr& info_key(PropertyType type, const std::string& key) { + if(type == PropertyType::PROP_TYPE_SERVER) + TP(virtualserver_info) + else if(type == PropertyType::PROP_TYPE_CHANNEL) + TP(channel_info) + else if(type == PropertyType::PROP_TYPE_CLIENT) + TP(client_info) + else if(type == PropertyType::PROP_TYPE_GROUP) + TP(group_info) + else if(type == PropertyType::PROP_TYPE_CONNECTION) + TP(connection_info) + else if(type == PropertyType::PROP_TYPE_INSTANCE) + TP(instance_info) + else if(type == PropertyType::PROP_TYPE_PLAYLIST) + TP(playlist_info); + return PropertyDescription::unknown; + } + + const std::shared_ptr& info(PropertyType type, int index) { + if(type == PropertyType::PROP_TYPE_SERVER) + return impl::info((VirtualServerProperties) index); + else if(type == PropertyType::PROP_TYPE_CHANNEL) + return impl::info((ChannelProperties) index); + else if(type == PropertyType::PROP_TYPE_CLIENT) + return impl::info((ClientProperties) index); + else if(type == PropertyType::PROP_TYPE_GROUP) + return impl::info((GroupProperties) index); + else if(type == PropertyType::PROP_TYPE_CONNECTION) + return impl::info((ConnectionProperties) index); + else if(type == PropertyType::PROP_TYPE_INSTANCE) + return impl::info((InstanceProperties) index); + else if(type == PropertyType::PROP_TYPE_PLAYLIST) + return impl::info((PlaylistProperties) index); + return PropertyDescription::unknown; + } + + #define M1(info) \ + for(const auto& e : (info)){ \ + auto found = find(used.begin(), used.end(), e->name); \ + if(found != used.end()) { \ + cerr << "[CRITICAL] Double registered property! (" << e->name << ")" << endl; \ + return false; \ + } \ + used.push_back(e->name); \ + } + + bool validateUnique() { + deque used; + M1(virtualserver_info); + M1(channel_info); + M1(group_info); + M1(client_info); + M1(connection_info); + M1(instance_info); + M1(playlist_info); + return true; + } + + bool validateInput(const std::string& input, ValueType type) { + if(type == ValueType::TYPE_UNKNOWN) return true; + else if(type == ValueType::TYPE_UNSIGNED_NUMBER) { + if(input.empty() || input.find_first_not_of("0123456789") != string::npos) return false; + try { + stoull(input); + return true; + } catch (std::exception& /* ex */) { return false; } + } + else if(type == ValueType::TYPE_SIGNED_NUMBER) { + if(input.empty() || input.find_first_not_of("-0123456789") != string::npos) return false; + auto minus = input.find_first_of('-'); + auto flag_result = minus == string::npos || (minus == 0 && input.find_first_of('-', minus + 1) == string::npos); + if(!flag_result) return false; + + try { + stoll(input); + return true; + } catch (std::exception& /* ex */) { return false; } + } else if(type == ValueType::TYPE_BOOL) return input.length() == 1 && input.find_first_not_of("01") == string::npos; + else if(type == ValueType::TYPE_STRING) return true; + else if(type == ValueType::TYPE_FLOAT) { + if(input.empty() || input.find_first_not_of("-.0123456789") != string::npos) return false; + auto minus = input.find_first_of('-'); + if(minus != string::npos && (input.find_first_of('-', minus + 1) != string::npos || minus != 0)) return false; + auto point = input.find_first_of('.'); + auto flag_result = point == string::npos || input.find_first_of('.', point + 1) == string::npos; + if(!flag_result) return false; + + try { + stof(input); + return true; + } catch (std::exception& /* ex */) { return false; } + } + return false; + } + } + } +} \ No newline at end of file diff --git a/src/Properties.h b/src/Properties.h new file mode 100644 index 0000000..a37f949 --- /dev/null +++ b/src/Properties.h @@ -0,0 +1,842 @@ +#include + +#pragma once + +#include "misc/memtracker.h" +#include +#include "Variable.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#include "misc/spin_lock.h" +#include "converters/converter.h" + +#define PROPS_DEFINED +namespace ts { + + namespace property { + enum PropertyType { + PROP_TYPE_SERVER = 0, + PROP_TYPE_CHANNEL = 1, + PROP_TYPE_GROUP = 2, + PROP_TYPE_CLIENT = 3, + PROP_TYPE_INSTANCE = 4, + PROP_TYPE_CONNECTION = 5, + PROP_TYPE_PLAYLIST = 6, + PROP_TYPE_UNKNOWN = 7 + }; + static constexpr const char *PropertyType_Names[7] = { + "SERVER", + "CHANNEL", + "GROUP", + "CLIENT", + "INSTANCE", + "CONNECTION", + "UNKNOWN" + }; + + enum ValueType { + TYPE_UNKNOWN, + TYPE_STRING, + TYPE_BOOL, + TYPE_SIGNED_NUMBER, + TYPE_UNSIGNED_NUMBER, + TYPE_FLOAT + }; + + typedef uint32_t flag_type; + enum flag : flag_type { + FLAG_BEGIN = 0b1, + FLAG_INTERNAL = FLAG_BEGIN << 1, //Just for internal usage + FLAG_GLOBAL = FLAG_INTERNAL << 1, //Not server bound + FLAG_SNAPSHOT = FLAG_GLOBAL << 1, //Saved within snapshots + FLAG_SAVE = FLAG_SNAPSHOT << 1, //Saved to database + FLAG_SAVE_MUSIC = FLAG_SAVE << 1, //Saved to database + FLAG_NEW = FLAG_SAVE_MUSIC << 1, //Its a non TeamSpeak property + FLAG_SERVER_VARIABLE = FLAG_NEW << 1, + FLAG_SERVER_VIEW = FLAG_SERVER_VARIABLE << 1, + FLAG_CLIENT_VARIABLE = FLAG_SERVER_VIEW << 1, + FLAG_CLIENT_VIEW = FLAG_CLIENT_VARIABLE << 1, + FLAG_CLIENT_INFO = FLAG_CLIENT_VIEW << 1, + FLAG_CHANNEL_VARIABLE = FLAG_CLIENT_INFO << 1, + FLAG_CHANNEL_VIEW = FLAG_CHANNEL_VARIABLE << 1, + FLAG_GROUP_VIEW = FLAG_CHANNEL_VIEW << 1, + FLAG_INSTANCE_VARIABLE = FLAG_GROUP_VIEW << 1, + FLAG_USER_EDITABLE = FLAG_INSTANCE_VARIABLE << 1, + FLAG_PLAYLIST_VARIABLE = FLAG_USER_EDITABLE << 1, + }; + static constexpr const char *flag_names[sizeof(flag) * 8] = + {"UNDEFINED", "FLAG_INTERNAL", "FLAG_GLOBAL", "FLAG_SNAPSHOT", "FLAG_SAVE", "FLAG_SAVE_MUSIC", "FLAG_NEW", + "FLAG_SERVER_VARIABLE", "FLAG_SERVER_VIEW", "FLAG_CLIENT_VARIABLE", "FLAG_CLIENT_VIEW", "FLAG_CLIENT_INFO", + "FLAG_CHANNEL_VARIABLE", "FLAG_CHANNEL_VIEW", "FLAG_GROUP_VIEW", "FLAG_INSTANCE_VARIABLE", "FLAG_USER_EDITABLE","FLAG_PLAYLIST_VARIABLE"}; + + enum UnknownProperties { + UNKNOWN_UNDEFINED, + UNKNOWN_BEGINMARKER, + UNKNOWN_ENDMARKER = UNKNOWN_BEGINMARKER + }; + + enum InstanceProperties { + SERVERINSTANCE_UNDEFINED, + SERVERINSTANCE_BEGINMARKER, + SERVERINSTANCE_DATABASE_VERSION = SERVERINSTANCE_BEGINMARKER, + SERVERINSTANCE_FILETRANSFER_HOST, + SERVERINSTANCE_FILETRANSFER_PORT, + SERVERINSTANCE_QUERY_HOST, + SERVERINSTANCE_QUERY_PORT, + SERVERINSTANCE_MONTHLY_TIMESTAMP, + SERVERINSTANCE_MAX_DOWNLOAD_TOTAL_BANDWIDTH, + SERVERINSTANCE_MAX_UPLOAD_TOTAL_BANDWIDTH, + SERVERINSTANCE_SERVERQUERY_FLOOD_COMMANDS, //how many commands we can issue while in the SERVERINSTANCE_SERVERQUERY_FLOOD_TIME window + SERVERINSTANCE_SERVERQUERY_FLOOD_TIME, //time window in seconds for max command execution check + SERVERINSTANCE_SERVERQUERY_BAN_TIME, //how many seconds someone get banned if he floods + SERVERINSTANCE_TEMPLATE_SERVERADMIN_GROUP, + SERVERINSTANCE_TEMPLATE_SERVERDEFAULT_GROUP, + SERVERINSTANCE_TEMPLATE_CHANNELADMIN_GROUP, + SERVERINSTANCE_TEMPLATE_CHANNELDEFAULT_GROUP, + SERVERINSTANCE_TEMPLATE_MUSICDEFAULT_GROUP, + SERVERINSTANCE_GUEST_SERVERQUERY_GROUP, + SERVERINSTANCE_ADMIN_SERVERQUERY_GROUP, + SERVERINSTANCE_PERMISSIONS_VERSION, + SERVERINSTANCE_PENDING_CONNECTIONS_PER_IP, + SERVERINSTANCE_SPOKEN_TIME_TOTAL, + SERVERINSTANCE_SPOKEN_TIME_DELETED, + SERVERINSTANCE_SPOKEN_TIME_ALIVE, + SERVERINSTANCE_SPOKEN_TIME_VARIANZ, + SERVERINSTANCE_UNIQUE_ID, + SERVERINSTANCE_ENDMARKER, + }; + + enum VirtualServerProperties { + VIRTUALSERVER_UNDEFINED = 0, + VIRTUALSERVER_BEGINMARKER, + VIRTUALSERVER_UNIQUE_IDENTIFIER = VIRTUALSERVER_BEGINMARKER, //available when connected, can be used to identify this particular server installation + VIRTUALSERVER_NAME, //available and always up-to-date when connected + VIRTUALSERVER_WELCOMEMESSAGE, //available when connected, (=> requestServerVariables) + VIRTUALSERVER_PLATFORM, //available when connected + VIRTUALSERVER_VERSION, //available when connected + VIRTUALSERVER_MAXCLIENTS, //only available on request (=> requestServerVariables), stores the maximum number of clients that may currently join the server + VIRTUALSERVER_PASSWORD, //not available to clients, the server password + VIRTUALSERVER_CLIENTS_ONLINE, //only available on request (=> requestServerVariables), + VIRTUALSERVER_CHANNELS_ONLINE, //only available on request (=> requestServerVariables), + VIRTUALSERVER_CREATED, //available when connected, stores the time when the server was created + VIRTUALSERVER_UPTIME, //only available on request (=> requestServerVariables), the time since the server was started + VIRTUALSERVER_CODEC_ENCRYPTION_MODE, //available and always up-to-date when connected + + //Rare properties + VIRTUALSERVER_KEYPAIR, //internal use + VIRTUALSERVER_HOSTMESSAGE, //available when connected, not updated while connected + VIRTUALSERVER_HOSTMESSAGE_MODE, //available when connected, not updated while connected + VIRTUALSERVER_FILEBASE, //not available to clients, stores the folder used for file transfers + VIRTUALSERVER_DEFAULT_SERVER_GROUP, //the manager permissions server group that a new manager gets assigned + VIRTUALSERVER_DEFAULT_MUSIC_GROUP, //the manager permissions server group that a new manager gets assigned + VIRTUALSERVER_DEFAULT_CHANNEL_GROUP, //the channel permissions group that a new manager gets assigned when joining a channel + VIRTUALSERVER_FLAG_PASSWORD, //only available on request (=> requestServerVariables) + VIRTUALSERVER_DEFAULT_CHANNEL_ADMIN_GROUP, //the channel permissions group that a manager gets assigned when creating a channel + VIRTUALSERVER_MAX_DOWNLOAD_TOTAL_BANDWIDTH, //only available on request (=> requestServerVariables) + VIRTUALSERVER_MAX_UPLOAD_TOTAL_BANDWIDTH, //only available on request (=> requestServerVariables) + VIRTUALSERVER_HOSTBANNER_URL, //available when connected, always up-to-date + VIRTUALSERVER_HOSTBANNER_GFX_URL, //available when connected, always up-to-date + VIRTUALSERVER_HOSTBANNER_GFX_INTERVAL, //available when connected, always up-to-date + VIRTUALSERVER_COMPLAIN_AUTOBAN_COUNT, //only available on request (=> requestServerVariables) + VIRTUALSERVER_COMPLAIN_AUTOBAN_TIME, //only available on request (=> requestServerVariables) + VIRTUALSERVER_COMPLAIN_REMOVE_TIME, //only available on request (=> requestServerVariables) + VIRTUALSERVER_MIN_CLIENTS_IN_CHANNEL_BEFORE_FORCED_SILENCE,//only available on request (=> requestServerVariables) + VIRTUALSERVER_PRIORITY_SPEAKER_DIMM_MODIFICATOR, //available when connected, always up-to-date + VIRTUALSERVER_ID, //available when connected + VIRTUALSERVER_ANTIFLOOD_POINTS_TICK_REDUCE, //only available on request (=> requestServerVariables) + VIRTUALSERVER_ANTIFLOOD_POINTS_NEEDED_COMMAND_BLOCK, //only available on request (=> requestServerVariables) + VIRTUALSERVER_ANTIFLOOD_POINTS_NEEDED_IP_BLOCK, //only available on request (=> requestServerVariables) + VIRTUALSERVER_CLIENT_CONNECTIONS, //only available on request (=> requestServerVariables) + VIRTUALSERVER_QUERY_CLIENT_CONNECTIONS, //only available on request (=> requestServerVariables) + VIRTUALSERVER_HOSTBUTTON_TOOLTIP, //available when connected, always up-to-date + VIRTUALSERVER_HOSTBUTTON_URL, //available when connected, always up-to-date + VIRTUALSERVER_HOSTBUTTON_GFX_URL, //available when connected, always up-to-date + VIRTUALSERVER_QUERYCLIENTS_ONLINE, //only available on request (=> requestServerVariables) + VIRTUALSERVER_DOWNLOAD_QUOTA, //only available on request (=> requestServerVariables) + VIRTUALSERVER_UPLOAD_QUOTA, //only available on request (=> requestServerVariables) + VIRTUALSERVER_MONTH_BYTES_DOWNLOADED, //only available on request (=> requestServerVariables) + VIRTUALSERVER_MONTH_BYTES_UPLOADED, //only available on request (=> requestServerVariables) + VIRTUALSERVER_TOTAL_BYTES_DOWNLOADED, //only available on request (=> requestServerVariables) + VIRTUALSERVER_TOTAL_BYTES_UPLOADED, //only available on request (=> requestServerVariables) + VIRTUALSERVER_PORT, //only available on request (=> requestServerVariables) + VIRTUALSERVER_AUTOSTART, //only available on request (=> requestServerVariables) + VIRTUALSERVER_MACHINE_ID, //only available on request (=> requestServerVariables) + VIRTUALSERVER_NEEDED_IDENTITY_SECURITY_LEVEL, //only available on request (=> requestServerVariables) + VIRTUALSERVER_LOG_CLIENT, //only available on request (=> requestServerVariables) + VIRTUALSERVER_LOG_QUERY, //only available on request (=> requestServerVariables) + VIRTUALSERVER_LOG_CHANNEL, //only available on request (=> requestServerVariables) + VIRTUALSERVER_LOG_PERMISSIONS, //only available on request (=> requestServerVariables) + VIRTUALSERVER_LOG_SERVER, //only available on request (=> requestServerVariables) + VIRTUALSERVER_LOG_FILETRANSFER, //only available on request (=> requestServerVariables) + VIRTUALSERVER_MIN_CLIENT_VERSION, //only available on request (=> requestServerVariables) + VIRTUALSERVER_NAME_PHONETIC, //available when connected, always up-to-date + VIRTUALSERVER_ICON_ID, //available when connected, always up-to-date + VIRTUALSERVER_RESERVED_SLOTS, //available when connected, always up-to-date + VIRTUALSERVER_TOTAL_PACKETLOSS_SPEECH, //only available on request (=> requestServerVariables) + VIRTUALSERVER_TOTAL_PACKETLOSS_KEEPALIVE, //only available on request (=> requestServerVariables) + VIRTUALSERVER_TOTAL_PACKETLOSS_CONTROL, //only available on request (=> requestServerVariables) + VIRTUALSERVER_TOTAL_PACKETLOSS_TOTAL, //only available on request (=> requestServerVariables) + VIRTUALSERVER_TOTAL_PING, //only available on request (=> requestServerVariables) + VIRTUALSERVER_HOST, //internal use | contains comma separated ip list + VIRTUALSERVER_WEBLIST_ENABLED, //only available on request (=> requestServerVariables) + VIRTUALSERVER_AUTOGENERATED_PRIVILEGEKEY, //internal use + VIRTUALSERVER_ASK_FOR_PRIVILEGEKEY, //available when connected + VIRTUALSERVER_HOSTBANNER_MODE, //available when connected, always up-to-date + VIRTUALSERVER_CHANNEL_TEMP_DELETE_DELAY_DEFAULT, //available when connected, always up-to-date + VIRTUALSERVER_MIN_ANDROID_VERSION, //only available on request (=> requestServerVariables) + VIRTUALSERVER_MIN_IOS_VERSION, //only available on request (=> requestServerVariables) + VIRTUALSERVER_MIN_WINPHONE_VERSION, //only available on request (=> requestServerVariables) + + VIRTUALSERVER_LAST_CLIENT_CONNECT, + VIRTUALSERVER_LAST_QUERY_CONNECT, + VIRTUALSERVER_LAST_CLIENT_DISCONNECT, + VIRTUALSERVER_LAST_QUERY_DISCONNECT, + + VIRTUALSERVER_WEB_HOST, + VIRTUALSERVER_WEB_PORT, + + VIRTUALSERVER_DEFAULT_CLIENT_DESCRIPTION, + VIRTUALSERVER_DEFAULT_CHANNEL_DESCRIPTION, + VIRTUALSERVER_DEFAULT_CHANNEL_TOPIC, + + VIRTUALSERVER_MUSIC_BOT_LIMIT, + VIRTUALSERVER_SPOKEN_TIME, + VIRTUALSERVER_DISABLE_IP_SAVING, + + VIRTUALSERVER_ENDMARKER + }; + + enum ChannelProperties { + CHANNEL_UNDEFINED, + CHANNEL_BEGINMARKER, + CHANNEL_ID = CHANNEL_BEGINMARKER, + CHANNEL_PID, + CHANNEL_NAME, //Available for all channels that are "in view", always up-to-date + CHANNEL_TOPIC, //Available for all channels that are "in view", always up-to-date + CHANNEL_DESCRIPTION, //Must be requested (=> requestChannelDescription) + CHANNEL_PASSWORD, //not available manager side + CHANNEL_CODEC, //Available for all channels that are "in view", always up-to-date + CHANNEL_CODEC_QUALITY, //Available for all channels that are "in view", always up-to-date + CHANNEL_MAXCLIENTS, //Available for all channels that are "in view", always up-to-date + CHANNEL_MAXFAMILYCLIENTS, //Available for all channels that are "in view", always up-to-date + CHANNEL_ORDER, //Available for all channels that are "in view", always up-to-date + CHANNEL_FLAG_PERMANENT, //Available for all channels that are "in view", always up-to-date + CHANNEL_FLAG_SEMI_PERMANENT, //Available for all channels that are "in view", always up-to-date + CHANNEL_FLAG_DEFAULT, //Available for all channels that are "in view", always up-to-date + CHANNEL_FLAG_PASSWORD, //Available for all channels that are "in view", always up-to-date + CHANNEL_CODEC_LATENCY_FACTOR, //Available for all channels that are "in view", always up-to-date + CHANNEL_CODEC_IS_UNENCRYPTED, //Available for all channels that are "in view", always up-to-date + CHANNEL_SECURITY_SALT, //Not available manager side, not used in teamspeak, only SDK. Sets the options+salt for security hash. + CHANNEL_DELETE_DELAY, //How many seconds to wait before deleting this channel + CHANNEL_FLAG_MAXCLIENTS_UNLIMITED, //Available for all channels that are "in view", always up-to-date + CHANNEL_FLAG_MAXFAMILYCLIENTS_UNLIMITED,//Available for all channels that are "in view", always up-to-date + CHANNEL_FLAG_MAXFAMILYCLIENTS_INHERITED,//Available for all channels that are "in view", always up-to-date + CHANNEL_FLAG_ARE_SUBSCRIBED, //Only available manager side, stores whether we are subscribed to this channel + CHANNEL_FILEPATH, //not available manager side, the folder used for file-transfers for this channel + CHANNEL_NEEDED_TALK_POWER, //Available for all channels that are "in view", always up-to-date + CHANNEL_FORCED_SILENCE, //Available for all channels that are "in view", always up-to-date + CHANNEL_NAME_PHONETIC, //Available for all channels that are "in view", always up-to-date + CHANNEL_ICON_ID, //Available for all channels that are "in view", always up-to-date + CHANNEL_FLAG_PRIVATE, //Available for all channels that are "in view", always up-to-date + + CHANNEL_LAST_LEFT, + CHANNEL_CREATED_AT, + CHANNEL_CREATED_BY, + + CHANNEL_ENDMARKER + }; + + enum GroupProperties { + GROUP_UNDEFINED, + GROUP_BEGINMARKER, + GROUP_ID = GROUP_BEGINMARKER, + GROUP_TYPE, + GROUP_NAME, + GROUP_SORTID, + GROUP_SAVEDB, + GROUP_NAMEMODE, + GROUP_ICONID, + GROUP_ENDMARKER + }; + + enum ClientProperties { + CLIENT_UNDEFINED, + CLIENT_BEGINMARKER, + CLIENT_UNIQUE_IDENTIFIER = CLIENT_BEGINMARKER, //automatically up-to-date for any manager "in view", can be used to identify this particular manager installation + CLIENT_NICKNAME, //automatically up-to-date for any manager "in view" + CLIENT_VERSION, //for other clients than ourself, this needs to be requested (=> requestClientVariables) + CLIENT_PLATFORM, //for other clients than ourself, this needs to be requested (=> requestClientVariables) + CLIENT_FLAG_TALKING, //automatically up-to-date for any manager that can be heard (in room / whisper) + CLIENT_INPUT_MUTED, //automatically up-to-date for any manager "in view", this clients microphone mute status + CLIENT_OUTPUT_MUTED, //automatically up-to-date for any manager "in view", this clients headphones/speakers/mic combined mute status + CLIENT_OUTPUTONLY_MUTED, //automatically up-to-date for any manager "in view", this clients headphones/speakers only mute status + CLIENT_INPUT_HARDWARE, //automatically up-to-date for any manager "in view", this clients microphone hardware status (is the capture device opened?) + CLIENT_OUTPUT_HARDWARE, //automatically up-to-date for any manager "in view", this clients headphone/speakers hardware status (is the playback device opened?) + CLIENT_DEFAULT_CHANNEL, //only usable for ourself, the default channel we used to connect on our last connection attempt + CLIENT_DEFAULT_CHANNEL_PASSWORD, //internal use + CLIENT_SERVER_PASSWORD, //internal use + CLIENT_META_DATA, //automatically up-to-date for any manager "in view", not used by TeamSpeak, free storage for sdk users + CLIENT_IS_RECORDING, //automatically up-to-date for any manager "in view" + CLIENT_VERSION_SIGN, //sign + CLIENT_SECURITY_HASH, //SDK use, not used by teamspeak. Hash is provided by an outside source. A channel will use the security salt + other manager data to calculate a hash, which must be the same as the one provided here. + + //Rare properties + CLIENT_KEY_OFFSET, //internal use + CLIENT_LOGIN_NAME, //used for serverquery clients, makes no sense on normal clients currently + CLIENT_LOGIN_PASSWORD, //used for serverquery clients, makes no sense on normal clients currently + CLIENT_DATABASE_ID, //automatically up-to-date for any manager "in view", only valid with PERMISSION feature, holds database manager id + CLIENT_ID, //clid! + CLIENT_HARDWARE_ID, //hwid! + CLIENT_CHANNEL_GROUP_ID, //automatically up-to-date for any manager "in view", only valid with PERMISSION feature, holds database manager id + CLIENT_SERVERGROUPS, //automatically up-to-date for any manager "in view", only valid with PERMISSION feature, holds all servergroups manager belongs too + CLIENT_CREATED, //this needs to be requested (=> requestClientVariables), first time this manager connected to this server + CLIENT_LASTCONNECTED, //this needs to be requested (=> requestClientVariables), last time this manager connected to this server + CLIENT_TOTALCONNECTIONS, //this needs to be requested (=> requestClientVariables), how many times this manager connected to this server + CLIENT_AWAY, //automatically up-to-date for any manager "in view", this clients away status + CLIENT_AWAY_MESSAGE, //automatically up-to-date for any manager "in view", this clients away message + CLIENT_TYPE, //automatically up-to-date for any manager "in view", determines if this is a real manager or a server-query connection + CLIENT_TYPE_EXACT, //automatically up-to-date for any manager "in view", determines if this is a real manager or a server-query connection + CLIENT_FLAG_AVATAR, //automatically up-to-date for any manager "in view", this manager got an avatar + CLIENT_TALK_POWER, //automatically up-to-date for any manager "in view", only valid with PERMISSION feature, holds database manager id + CLIENT_TALK_REQUEST, //automatically up-to-date for any manager "in view", only valid with PERMISSION feature, holds timestamp where manager requested to talk + CLIENT_TALK_REQUEST_MSG, //automatically up-to-date for any manager "in view", only valid with PERMISSION feature, holds matter for the request + CLIENT_DESCRIPTION, //automatically up-to-date for any manager "in view" + CLIENT_IS_TALKER, //automatically up-to-date for any manager "in view" + CLIENT_MONTH_BYTES_UPLOADED, //this needs to be requested (=> requestClientVariables) + CLIENT_MONTH_BYTES_DOWNLOADED, //this needs to be requested (=> requestClientVariables) + CLIENT_TOTAL_BYTES_UPLOADED, //this needs to be requested (=> requestClientVariables) + CLIENT_TOTAL_BYTES_DOWNLOADED, //this needs to be requested (=> requestClientVariables) + CLIENT_TOTAL_ONLINE_TIME, + CLIENT_MONTH_ONLINE_TIME, + CLIENT_IS_PRIORITY_SPEAKER, //automatically up-to-date for any manager "in view" + CLIENT_UNREAD_MESSAGES, //automatically up-to-date for any manager "in view" + CLIENT_NICKNAME_PHONETIC, //automatically up-to-date for any manager "in view" + CLIENT_NEEDED_SERVERQUERY_VIEW_POWER, //automatically up-to-date for any manager "in view" + CLIENT_DEFAULT_TOKEN, //only usable for ourself, the default token we used to connect on our last connection attempt + CLIENT_ICON_ID, //automatically up-to-date for any manager "in view" + CLIENT_IS_CHANNEL_COMMANDER, //automatically up-to-date for any manager "in view" + CLIENT_COUNTRY, //automatically up-to-date for any manager "in view" + CLIENT_CHANNEL_GROUP_INHERITED_CHANNEL_ID, //automatically up-to-date for any manager "in view", only valid with PERMISSION feature, contains channel_id where the channel_group_id is set from + CLIENT_BADGES, //automatically up-to-date for any manager "in view", stores icons for partner badges + + CLIENT_MYTEAMSPEAK_ID, + CLIENT_INTEGRATIONS, + CLIENT_ACTIVE_INTEGRATIONS_INFO, + + //Music bot stuff + CLIENT_BOT_TYPE, + CLIENT_OWNER, + CLIENT_PLAYER_VOLUME, + CLIENT_LAST_CHANNEL, + CLIENT_PLAYER_STATE, + CLIENT_PLAYLIST_ID, + CLIENT_DISABLED, + CLIENT_UPTIME_MODE, + CLIENT_FLAG_NOTIFY_SONG_CHANGE, + + CLIENT_TEAFORO_ID, + CLIENT_TEAFORO_NAME, + + CLIENT_ENDMARKER + }; + + enum ConnectionProperties { + CONNECTION_UNDEFINED, + CONNECTION_BEGINMARKER, + CONNECTION_PING = CONNECTION_BEGINMARKER, //average latency for a round trip through and back this connection + CONNECTION_PING_DEVIATION, //standard deviation of the above average latency + CONNECTION_CONNECTED_TIME, //how long the connection exists already + CONNECTION_IDLE_TIME, //how long since the last action of this manager + CONNECTION_CLIENT_IP, //NEED DB SAVE! //IP of this manager (as seen from the server side) + CONNECTION_CLIENT_PORT, //Port of this manager (as seen from the server side) + CONNECTION_SERVER_IP, //IP of the server (seen from the manager side) - only available on yourself, not for remote clients, not available server side + CONNECTION_SERVER_PORT, //Port of the server (seen from the manager side) - only available on yourself, not for remote clients, not available server side + CONNECTION_PACKETS_SENT_SPEECH, //how many Speech packets were sent through this connection + CONNECTION_PACKETS_SENT_KEEPALIVE, + CONNECTION_PACKETS_SENT_CONTROL, + CONNECTION_PACKETS_SENT_TOTAL, //how many packets were sent totally (this is PACKETS_SENT_SPEECH + PACKETS_SENT_KEEPALIVE + PACKETS_SENT_CONTROL) + CONNECTION_BYTES_SENT_SPEECH, + CONNECTION_BYTES_SENT_KEEPALIVE, + CONNECTION_BYTES_SENT_CONTROL, + CONNECTION_BYTES_SENT_TOTAL, + CONNECTION_PACKETS_RECEIVED_SPEECH, + CONNECTION_PACKETS_RECEIVED_KEEPALIVE, + CONNECTION_PACKETS_RECEIVED_CONTROL, + CONNECTION_PACKETS_RECEIVED_TOTAL, + CONNECTION_BYTES_RECEIVED_SPEECH, + CONNECTION_BYTES_RECEIVED_KEEPALIVE, + CONNECTION_BYTES_RECEIVED_CONTROL, + CONNECTION_BYTES_RECEIVED_TOTAL, + CONNECTION_PACKETLOSS_SPEECH, + CONNECTION_PACKETLOSS_KEEPALIVE, + CONNECTION_PACKETLOSS_CONTROL, + CONNECTION_PACKETLOSS_TOTAL, //the probability with which a packet round trip failed because a packet was lost + CONNECTION_SERVER2CLIENT_PACKETLOSS_SPEECH, //the probability with which a speech packet failed from the server to the manager + CONNECTION_SERVER2CLIENT_PACKETLOSS_KEEPALIVE, + CONNECTION_SERVER2CLIENT_PACKETLOSS_CONTROL, + CONNECTION_SERVER2CLIENT_PACKETLOSS_TOTAL, + CONNECTION_CLIENT2SERVER_PACKETLOSS_SPEECH, + CONNECTION_CLIENT2SERVER_PACKETLOSS_KEEPALIVE, + CONNECTION_CLIENT2SERVER_PACKETLOSS_CONTROL, + CONNECTION_CLIENT2SERVER_PACKETLOSS_TOTAL, + CONNECTION_BANDWIDTH_SENT_LAST_SECOND_SPEECH, //howmany bytes of speech packets we sent during the last second + CONNECTION_BANDWIDTH_SENT_LAST_SECOND_KEEPALIVE, + CONNECTION_BANDWIDTH_SENT_LAST_SECOND_CONTROL, + CONNECTION_BANDWIDTH_SENT_LAST_SECOND_TOTAL, + CONNECTION_BANDWIDTH_SENT_LAST_MINUTE_SPEECH, //howmany bytes/s of speech packets we sent in average during the last minute + CONNECTION_BANDWIDTH_SENT_LAST_MINUTE_KEEPALIVE, + CONNECTION_BANDWIDTH_SENT_LAST_MINUTE_CONTROL, + CONNECTION_BANDWIDTH_SENT_LAST_MINUTE_TOTAL, + CONNECTION_BANDWIDTH_RECEIVED_LAST_SECOND_SPEECH, + CONNECTION_BANDWIDTH_RECEIVED_LAST_SECOND_KEEPALIVE, + CONNECTION_BANDWIDTH_RECEIVED_LAST_SECOND_CONTROL, + CONNECTION_BANDWIDTH_RECEIVED_LAST_SECOND_TOTAL, + CONNECTION_BANDWIDTH_RECEIVED_LAST_MINUTE_SPEECH, + CONNECTION_BANDWIDTH_RECEIVED_LAST_MINUTE_KEEPALIVE, + CONNECTION_BANDWIDTH_RECEIVED_LAST_MINUTE_CONTROL, + CONNECTION_BANDWIDTH_RECEIVED_LAST_MINUTE_TOTAL, + + //Rare properties + CONNECTION_FILETRANSFER_BANDWIDTH_SENT, //how many bytes per second are currently being sent by file transfers + CONNECTION_FILETRANSFER_BANDWIDTH_RECEIVED, //how many bytes per second are currently being received by file transfers + CONNECTION_FILETRANSFER_BYTES_RECEIVED_TOTAL, //how many bytes we received in total through file transfers + CONNECTION_FILETRANSFER_BYTES_SENT_TOTAL, //how many bytes we sent in total through file transfers + + CONNECTION_ENDMARKER + }; + + enum PlaylistProperties { + PLAYLIST_UNDEFINED, + PLAYLIST_BEGINMARKER, + + PLAYLIST_ID = PLAYLIST_BEGINMARKER, + PLAYLIST_TITLE, + PLAYLIST_DESCRIPTION, + PLAYLIST_TYPE, + + PLAYLIST_OWNER_DBID, + PLAYLIST_OWNER_NAME, + + PLAYLIST_MAX_SONGS, + PLAYLIST_FLAG_DELETE_PLAYED, + PLAYLIST_FLAG_FINISHED, + PLAYLIST_REPLAY_MODE, /* 0 = normal | 1 = loop list | 2 = loop entry | 3 = shuffle */ + + PLAYLIST_CURRENT_SONG_ID, + + PLAYLIST_ENDMARKER + }; + + /* + enum PlayList { + PLAYLIST_OWNER, + PLAYLIST_NAME, + PLAYLIST_REPEAT_MODE, + PLAYLIST_DELETE_PLAYED_SONGS + }; + */ + + class PropertyDescription; + namespace impl { + template + extern std::deque> list(); + extern std::deque> list_all(); + + template + extern const std::shared_ptr &info(T /* property */); + + template + extern const std::shared_ptr &info(const std::string& /* key */); + + + extern const std::shared_ptr &info_key(const std::string &); + extern const std::shared_ptr &info_key(PropertyType, const std::string &); + extern const std::shared_ptr &info(PropertyType, int); + + template + inline PropertyType type() { + std::cerr << "[CRITICAL] Invalid property type!" << std::endl; + return PropertyType::PROP_TYPE_UNKNOWN; + } + + template<> + constexpr inline PropertyType type() { return PropertyType::PROP_TYPE_SERVER; } + + template<> + constexpr inline PropertyType type() { return PropertyType::PROP_TYPE_CHANNEL; } + + template<> + constexpr inline PropertyType type() { return PropertyType::PROP_TYPE_CLIENT; } + + template<> + constexpr inline PropertyType type() { return PropertyType::PROP_TYPE_CONNECTION; } + + template<> + constexpr inline PropertyType type() { return PropertyType::PROP_TYPE_GROUP; } + + template<> + constexpr inline PropertyType type() { return PropertyType::PROP_TYPE_INSTANCE; } + + template<> + constexpr inline PropertyType type() { return PropertyType::PROP_TYPE_PLAYLIST; } + + template<> + constexpr inline PropertyType type() { return PropertyType::PROP_TYPE_UNKNOWN; } + + inline size_t length(PropertyType type) { + switch (type) { + case PROP_TYPE_SERVER: + return VIRTUALSERVER_ENDMARKER - VIRTUALSERVER_BEGINMARKER; + case PROP_TYPE_CHANNEL: + return CHANNEL_ENDMARKER - CHANNEL_BEGINMARKER; + case PROP_TYPE_CLIENT: + return CLIENT_ENDMARKER - CLIENT_BEGINMARKER; + case PROP_TYPE_CONNECTION: + return CONNECTION_ENDMARKER - CONNECTION_BEGINMARKER; + case PROP_TYPE_GROUP: + return GROUP_ENDMARKER - GROUP_BEGINMARKER; + case PROP_TYPE_INSTANCE: + return SERVERINSTANCE_ENDMARKER - SERVERINSTANCE_BEGINMARKER; + case PROP_TYPE_PLAYLIST: + return PLAYLIST_ENDMARKER - PLAYLIST_BEGINMARKER; + case PROP_TYPE_UNKNOWN: + return UNKNOWN_ENDMARKER - UNKNOWN_BEGINMARKER; + default: + return 0; + } + } + + inline size_t offset(PropertyType type) { + switch (type) { + case PROP_TYPE_SERVER: + return VIRTUALSERVER_BEGINMARKER; + case PROP_TYPE_CHANNEL: + return CHANNEL_BEGINMARKER; + case PROP_TYPE_CLIENT: + return CLIENT_BEGINMARKER; + case PROP_TYPE_CONNECTION: + return CONNECTION_BEGINMARKER; + case PROP_TYPE_GROUP: + return GROUP_BEGINMARKER; + case PROP_TYPE_INSTANCE: + return SERVERINSTANCE_BEGINMARKER; + case PROP_TYPE_PLAYLIST: + return PLAYLIST_BEGINMARKER; + case PROP_TYPE_UNKNOWN: + return UNKNOWN_BEGINMARKER; + default: + return 0; + } + } + + extern bool validateUnique(); + + extern bool validateInput(const std::string &, ValueType); + } + + + template + inline const std::shared_ptr &info(T property) { return impl::info(property); } + + template + inline const std::shared_ptr &info(const std::string& key) { return impl::info(key); } + + class PropertyDescription { + public: + static std::shared_ptr unknown; + + std::string name; + std::string default_value; + + ValueType type_value = property::ValueType::TYPE_UNKNOWN; + PropertyType type_property = PropertyType::PROP_TYPE_UNKNOWN; + + int property_index = 0; + flag_type flags = 0; + + PropertyDescription(int property_id, PropertyType property_type, const std::string &name, const std::string &default_value, property::ValueType type, flag_type flags) noexcept; + + template + PropertyDescription(T property, const std::string &name, const std::string &default_value, property::ValueType type, flag_type flags) noexcept : PropertyDescription((int) property, impl::type(), name, default_value, type, flags) {} + + inline bool operator==(const PropertyDescription& other) const { + return other.property_index == this->property_index && other.type_property == this->type_property; + } + + inline bool operator==(const std::shared_ptr& other) const { + return this->operator==(*other); + } + + template ::value, int>::type = 0> + inline bool operator==(const T& type) const { + return this->property_index == type && impl::type() == this->type_property; + } + + inline bool validate_input(const std::string& input) { + return impl::validateInput(input, this->type_value); + } + }; + } + class Properties; + + struct PropertyData { + spin_lock value_lock; + std::any casted_value; + std::string value; + std::shared_ptr description; + + bool flag_db_reference; + bool flag_modified; + }; + + struct PropertyBundle { + property::PropertyType type; + size_t length; + size_t offset; + + PropertyData properties[0]; + }; + + template + struct PropertyAccess { + inline static T get(PropertyData* data_ptr) { + std::lock_guard lock(data_ptr->value_lock); + if(data_ptr->casted_value.type() == typeid(T)) + return std::any_cast(data_ptr->casted_value); + + data_ptr->casted_value = ts::converter::from_string(data_ptr->value); + return std::any_cast(data_ptr->casted_value); + } + }; + + template <> + struct PropertyAccess { + inline static std::string get(PropertyData* data_ptr) { return data_ptr->value; } + }; + + struct PropertyWrapper { + friend class Properties; + public: + bool operator==(const PropertyWrapper& other) { + if(this->data_ptr == other.data_ptr) + return true; + + return this->data_ptr->value == other.data_ptr->value; + } + + template + bool operator==(const T& other){ + return this->as() == other; + } + template + bool operator!=(const T& other){ + return !operator==(other); + } + + //Math operators + PropertyWrapper&operator++(){ return operator=(as() + 1); } + PropertyWrapper&operator++(int){ return operator=(as() + 1); } + PropertyWrapper&operator+=(uint16_t val){ return operator=(as() + val); } + PropertyWrapper&operator+=(int64_t val){ return operator=(as() + val); } + PropertyWrapper&operator+=(uint64_t val){ return operator=(as() + val); } + + bool hasDbReference() const { return this->data_ptr->flag_db_reference; } + void setDbReference(bool flag){ this->data_ptr->flag_db_reference = flag; } + + bool isModified() const { return this->data_ptr->flag_modified; } + void setModified(bool flag){ this->data_ptr->flag_modified = flag; } + + template + T as() const { + static_assert(ts::converter::supported, "as isn't supported for type"); + + return PropertyAccess::get(this->data_ptr); + } + + + template + operator T(){ return this->as(); } + + template + T as_save() const { + try { + std::lock_guard lock(this->data_ptr->value_lock); + if(this->data_ptr->casted_value.type() == typeid(T)) + return std::any_cast(this->data_ptr->casted_value); + + this->data_ptr->casted_value = ts::converter::from_string(this->data_ptr->value); + return std::any_cast(this->data_ptr->casted_value); + } catch(std::exception&) { + return 0; + } + } + + const property::PropertyDescription& type() const { return *this->data_ptr->description; } + std::string value() const { + std::lock_guard lock(this->data_ptr->value_lock); + return this->data_ptr->value; + } + + void value(const std::string &value, bool trigger_update = true){ + { + std::lock_guard lock(this->data_ptr->value_lock); + if(this->data_ptr->value == value) + return; + this->data_ptr->casted_value.reset(); + this->data_ptr->value = value; + } + if(trigger_update) + this->trigger_update(); + } + + std::string default_value() const { + return this->type().default_value; + } + + template + PropertyWrapper& operator=(const T& value) { + static_assert(ts::converter::supported, "type isn't supported for type"); + + { + std::any any_value{value}; + auto value_string = ts::converter::to_string(any_value); + + std::lock_guard lock(this->data_ptr->value_lock); + if(value_string == this->data_ptr->value) + return *this; + this->data_ptr->casted_value = any_value; + this->data_ptr->value = value_string; + } + this->trigger_update(); + + return *this; + } + + + PropertyWrapper& operator=(const std::string& value) { + this->value(value); + return *this; + } + PropertyWrapper& operator=(const char* value) { + this->value(value); + return *this; + } + + template + PropertyWrapper& operator=(char(value)[N]) { + this->value(value); + return *this; + } + + void trigger_update(); + + PropertyWrapper(Properties* /* handle */, PropertyData* /* ptr */, const std::shared_ptr& /* bundle */); + + inline Properties* get_handle() { return this->handle; } + private: + Properties* handle = nullptr; + PropertyData* data_ptr = nullptr; + std::shared_ptr bundle_lock; + }; + typedef PropertyWrapper Property; + typedef std::function PropertyNotifyFn; + + class Properties { + friend struct PropertyWrapper; + public: + Properties(); + ~Properties(); + Properties(const Properties&) = delete; + Properties(Properties&&) = delete; + + std::vector list_properties(property::flag_type flagMask = ~0, property::flag_type negatedFlagMask = 0); + std::vector all_properties(); + /* + std::deque> listProperties(property::flag_type flagMask = ~0, property::flag_type negatedFlagMask = 0); + std::deque> allProperties(); + */ + + template + bool register_property_type() { + constexpr auto type = property::impl::type(); + return this->register_property_type(type, property::impl::length(type), property::impl::offset(type)); + } + + /* + template + std::shared_ptr registerProperty(PT property){ + auto info = property::impl::info(property); + { + threads::MutexLock lock(this->propsLock); + for(const auto& elm : this->properties) { + if(*info == elm->type()){ + std::cerr << "Double registration of " << elm->type().name << "\n"; + return this->find(property::impl::type(), property); + } + } + + properties.push_back(std::make_shared(this, info)); + } + return this->find(property::impl::type(), property); + } + */ + + template + bool hasProperty(T type) { return this->has(property::impl::type(), type); } + + template ::value, int>::type = 0> + PropertyWrapper operator[](T type) { + return this->find(property::impl::type(), type); + } + + PropertyWrapper operator[](const property::PropertyDescription& type) { + return this->find(type.type_property, type.property_index); + } + + PropertyWrapper operator[](const std::shared_ptr& type) { + return this->find(type->type_property, type->property_index); + } + + void registerNotifyHandler(const PropertyNotifyFn &fn){ + this->notifyFunctions.push_back(fn); + } + + void triggerAllModified(){ + for(auto& prop : this->all_properties()) + if(prop.isModified()) + for(auto& elm : notifyFunctions) + elm(prop); + } + + void toggleSave(bool flag) { this->save = flag; } + bool isSaveEnabled(){ return this->save; } + + + PropertyWrapper find(property::PropertyType type, int index); + bool has(property::PropertyType type, int index); + + template ::value, int>::type = 0> + bool has(T type) { return this->has(property::impl::type(), type); } + private: + bool register_property_type(property::PropertyType /* type */, size_t /* length */, size_t /* offset */); + + bool save = true; + std::vector> notifyFunctions{}; + std::recursive_mutex propsLock{}; + //std::deque> properties{}; + + size_t properties_count = 0; + std::deque> properties; + }; +}; + +//DEFINE_TRANSFORMS(ts::property::PropertyType, uint8_t); +DEFINE_CONVERTER_ENUM(ts::property::PropertyType, uint8_t); +DEFINE_VARIABLE_TRANSFORM_ENUM(ts::property::PropertyType, uint8_t); \ No newline at end of file diff --git a/src/Variable.cpp b/src/Variable.cpp new file mode 100644 index 0000000..0a0d345 --- /dev/null +++ b/src/Variable.cpp @@ -0,0 +1,17 @@ +// +// Created by wolverindev on 22.11.17. +// + +#include "Variable.h" + +variable_data::variable_data(const std::pair &pair, VariableType _type) : pair(pair), _type(_type) {} + +variable& variable::operator=(const variable &ref) { + this->data = ref.data; + return *this; +} + +variable& variable::operator=(variable &&ref) { + this->data = ref.data; + return *this; +} \ No newline at end of file diff --git a/src/Variable.h b/src/Variable.h new file mode 100644 index 0000000..1eb0fbe --- /dev/null +++ b/src/Variable.h @@ -0,0 +1,143 @@ +#pragma once + +#include +#include +#include +#include + +enum VariableType { + VARTYPE_NULL, + VARTYPE_TEXT, + VARTYPE_INT, + VARTYPE_LONG, + VARTYPE_DOUBLE, + VARTYPE_FLOAT, + VARTYPE_BOOLEAN +}; + +class variable; + +namespace typecast { + template + struct FalseType : std::false_type {}; + + template + inline T variable_cast(const variable& in) { static_assert( FalseType::value , "this function has to be implemented for desired type"); return nullptr; }; //str to type + + template + inline std::string variable_cast(T& in) { static_assert( FalseType::value , "this function has to be implemented for desired type"); return "undefined"; }; //type to str + + template + inline VariableType variable_type() { static_assert( FalseType::value , "this function has to be implemented for desired type"); return VARTYPE_NULL; }; //type to str +} + +struct variable_data { + variable_data(const std::pair &pair, VariableType _type); + ~variable_data() = default; + + std::pair pair; + VariableType _type; +}; + +class variable { + public: + variable() noexcept : data(std::make_shared(std::pair{"", ""}, VariableType::VARTYPE_NULL)) {} + variable(const std::string& key) noexcept : variable() { r_key() = key; } + variable(const std::string& key, const variable& value) noexcept : variable(key) { + this->r_value() = value.string(); + this->data->pair.second = value.string(); + } + variable(const std::string& key, const std::string& value, VariableType type) noexcept : data(std::make_shared(std::pair{key, value}, VariableType::VARTYPE_TEXT)) {} + variable(const variable& ref) : data(ref.data) {} + variable(variable&& ref) : data(ref.data) { } + virtual ~variable() = default; + + template + variable(const std::string& key, T value) : variable(key) { operator=(value); } + + variable& operator=(const variable& ref); + variable& operator=(variable&& ref); + + std::string key() const { return data->pair.first; } + std::string value() const { return data->pair.second; } + VariableType type() const { return data->_type; } + variable clone(){ return variable(key(), value(), type()); } + + variable(std::string key, std::nullptr_t) : variable() { r_key() = std::move(key); data->_type = VariableType ::VARTYPE_NULL; } + explicit variable(std::nullptr_t) noexcept : variable() { data->_type = VariableType ::VARTYPE_NULL; } + variable&operator=(std::nullptr_t) { r_value() = ""; data->_type = VARTYPE_NULL; return *this;} + + template + T as() const { return typecast::variable_cast(*this); } + std::string string() const { return as(); } //fast + + template //TODO more secure and not just try and fail + bool castable() { + try { + as(); + return true; + } catch (...) { return false; } + } + + + template + variable& operator=(T obj){ + r_value() = typecast::variable_cast(obj); + data->_type = typecast::variable_type(); + return *this; + } + + template + operator T() const { return as(); } + private: + std::string& r_key() const { return data->pair.first; } + std::string& r_value() const { return data->pair.second; } + + std::shared_ptr data; +}; + +#define DEFINE_VARIABLE_TRANSFORM_TO_STR(type, to_str) \ +namespace typecast { \ + template <> \ + inline std::string variable_cast(type& in) { return to_str; } \ +} + +#define DEFINE_VARIABLE_TRANSFORM_TO_VAR(type, from_str) \ +namespace typecast { \ + template <> \ + inline type variable_cast(const variable& in) { return from_str; } \ +} + +#define DEFINE_VARIABLE_TRANSFORM_TYPE(type, vartype) \ +namespace typecast { \ + template <> \ + inline VariableType variable_type() { return VariableType::vartype; } \ +} + +#define DEFINE_VARIABLE_TRANSFORM(type, ntype, to_str, from_str) \ +DEFINE_VARIABLE_TRANSFORM_TO_STR(type, to_str); \ +DEFINE_VARIABLE_TRANSFORM_TO_VAR(type, from_str) \ +DEFINE_VARIABLE_TRANSFORM_TYPE(type, ntype) + +#define DEFINE_VARIABLE_TRANSFORM_ENUM(class, size_type) \ +DEFINE_VARIABLE_TRANSFORM(class, VARTYPE_INT, std::to_string((size_type) in), static_cast(in.as())); + +DEFINE_VARIABLE_TRANSFORM(std::string, VARTYPE_TEXT, in, in.value()); +DEFINE_VARIABLE_TRANSFORM(char*, VARTYPE_TEXT, std::string((const char*) in), (char*) in.value().c_str()); +DEFINE_VARIABLE_TRANSFORM(const char*, VARTYPE_TEXT, std::string((const char*) in), in.value().c_str()); + +DEFINE_VARIABLE_TRANSFORM(int8_t, VARTYPE_INT, std::to_string(in), std::stoi(in.value())); +DEFINE_VARIABLE_TRANSFORM(uint8_t, VARTYPE_INT, std::to_string(in), std::stoul(in.value())); + +DEFINE_VARIABLE_TRANSFORM(int16_t, VARTYPE_INT, std::to_string(in), std::stoi(in.value())); +DEFINE_VARIABLE_TRANSFORM(uint16_t, VARTYPE_INT, std::to_string(in), std::stoul(in.value())); + +DEFINE_VARIABLE_TRANSFORM(int32_t, VARTYPE_INT, std::to_string(in), std::stoi(in.value())); +DEFINE_VARIABLE_TRANSFORM(uint32_t, VARTYPE_INT, std::to_string(in), std::stoul(in.value())); + +DEFINE_VARIABLE_TRANSFORM(int64_t, VARTYPE_LONG, std::to_string(in), std::stoll(in.value())); +DEFINE_VARIABLE_TRANSFORM(uint64_t, VARTYPE_LONG, std::to_string(in), std::stoull(in.value())); + +DEFINE_VARIABLE_TRANSFORM(bool, VARTYPE_INT, in ? "1" : "0", in.value() == "1"); +DEFINE_VARIABLE_TRANSFORM(double, VARTYPE_DOUBLE, std::to_string(in), std::stod(in.value())); +DEFINE_VARIABLE_TRANSFORM(float, VARTYPE_FLOAT, std::to_string(in), std::stof(in.value())); \ No newline at end of file diff --git a/src/bbcode/bbcodes.cpp b/src/bbcode/bbcodes.cpp new file mode 100644 index 0000000..17f4f0f --- /dev/null +++ b/src/bbcode/bbcodes.cpp @@ -0,0 +1,94 @@ +#include +#include +#include +#include +#include "bbcodes.h" + +using namespace std; +using namespace bbcode; + +struct BBStack { + shared_ptr head = make_shared(); + shared_ptr tail = head; + + string key; + string content; +}; + +shared_ptr bbcode::parse(string message) { + string current_text; + string current_key; + + deque> stack; + stack.push_back(make_shared()); + + bool escaped = false; + bool key_begin = false; + bool key_end = false; + for(size_t i = 0; i < message.length(); i++) { + if(message[i] == '[' && (i == 0 || message[i - 1] != '\\')) { + if(!current_text.empty()) { + stack.back()->tail->next(make_shared(current_text)); + stack.back()->tail = stack.back()->tail->next(); + } + if(i + 2 < message.length() && message[i + 1] == '/') { //Should be a key end + key_end = true; + i += 1; + continue; + } else { + key_begin = true; + cout << "Message: " << current_text << endl; + stack.push_back(make_shared()); + continue; + } + } else if(message[i] == ']') { + if(key_begin) { + cout << "Got key begin '" << current_key << "'" << endl; + key_begin = false; + stack.back()->key = current_key; + current_key = ""; + continue; + } else if(key_end) { + cout << "Got key end '" << current_key << "'" << endl; + key_end = false; + continue; + } + } + + if(key_begin || key_end) current_key += message[i]; + else current_text += message[i]; + } + + return stack.front()->head; +} + +bool bbcode::sloppy::has_tag(std::string message, std::deque tag) { + std::transform(message.begin(), message.end(), message.begin(), ::tolower); + for(auto& entry : tag) + std::transform(entry.begin(), entry.end(), entry.begin(), ::tolower); + + std::deque begins; + size_t index = 0, found, length = 0; + do { + found = string::npos; + for(auto it = tag.begin(); it != tag.end() && found == string::npos; it++) { + found = message.find(*it, index); + length = it->length(); + }; + if(found > 0 && found + length < message.length()) { + if(message[found + length] == ']' || (message[found + length] == '=' && message.find(']', found + length) != string::npos)) { + if(message[found - 1] == '/') { + auto found_tag = message.substr(found, length); + for(const auto& entry : begins) + if(entry == found_tag) return true; + } else if(message[found - 1] == '[' && (found < 2 || message[found - 2] != '\\')) + begins.push_back(message.substr(found, length)); + if(message[found + length] != ']') + found = message.find(']', found + length); + } + } + index = found + 1; + } while(index != 0); + + return false; +} \ No newline at end of file diff --git a/src/bbcode/bbcodes.h b/src/bbcode/bbcodes.h new file mode 100644 index 0000000..f076e2e --- /dev/null +++ b/src/bbcode/bbcodes.h @@ -0,0 +1,57 @@ +#pragma once + +#include +#include + +namespace bbcode { + enum BBType { + TEXT, + URL, + IMG, + LIST, + UNKNOWN + }; + + class BBEntry { + public: + virtual ~BBEntry() = default; + virtual BBType type() const = 0; + virtual std::string build() const = 0; + + inline std::shared_ptr next() const { return this->_next; } + inline std::shared_ptr previus() const { return this->_previus; } + inline std::shared_ptr parent() const { return this->_parent.lock(); } + + inline void next(std::shared_ptr next) { this->_next = std::move(next); } + inline void previus(std::shared_ptr previus) { this->_previus = std::move(previus); } + inline void parent(std::shared_ptr parent) { this->_parent = std::move(parent); } + protected: + std::weak_ptr _parent; + std::shared_ptr _previus; + std::shared_ptr _next; + }; + + class BBText : public BBEntry { + public: + BBText(const std::string& text = "") { this->_text = text; } + + inline std::string text() const { return this->_text; } + inline void text(const std::string& text) { this->_text = text; } + + BBType type() const override { return BBType::TEXT; } + + std::string build() const override { return _text; } + + private: + std::string _text; + }; + + extern std::shared_ptr parse(std::string); + + namespace sloppy { + extern bool has_tag(std::string message, std::deque tag); + + inline bool has_url(const std::string& message) { return has_tag(message, {"url"}); } + inline bool has_image(const std::string& message) { return has_tag(message, {"img"}); } + } +} \ No newline at end of file diff --git a/src/channel/TreeView.cpp b/src/channel/TreeView.cpp new file mode 100644 index 0000000..1a26034 --- /dev/null +++ b/src/channel/TreeView.cpp @@ -0,0 +1,325 @@ +#include +#include "TreeView.h" + +using namespace ts; +using namespace std; +using LinkedTreeEntry = ts::TreeView::LinkedTreeEntry; + +TreeView::TreeView() {} +TreeView::~TreeView() { + std::deque> heads = {this->head}; + while(!heads.empty()) { + auto e = std::move(heads.front()); + heads.pop_front(); + + while(e) { + if(e->child_head) + heads.push_back(e->child_head); + + //Release reference + if(e->previous) e->previous->next = nullptr; + e->previous = nullptr; + e->child_head = nullptr; + e = e->next; + } + } +} + +std::shared_ptr TreeView::linked(const std::shared_ptr& entry) const { + if(!entry) return nullptr; + + std::deque> heads = {this->head}; + while(!heads.empty()) { + auto e = std::move(heads.front()); + heads.pop_front(); + + while(e) { + if(e->entry->channelId() == entry->channelId()) + return e; + else { + if(e->child_head) + heads.push_back(e->child_head); + e = e->next; + } + } + } + + return nullptr; +} + +std::deque> TreeView::query_deep(const std::shared_ptr& root, int deep) const { + std::deque> result; + this->query_deep_(result, root ? root : this->head, deep); + return result; +} + +void TreeView::query_deep_(deque>& result, const shared_ptr& root, int deep) const { + if(deep == 0) return; + + shared_ptr entry = root; + while(entry) { + result.push_back(entry); + if(entry->child_head) + this->query_deep_(result, entry->child_head, deep - 1); + entry = entry->next; + } +} + +size_t TreeView::entry_count() const { + size_t result = 0; + + std::deque> heads = {this->head}; + while(!heads.empty()) { + auto e = std::move(heads.front()); + heads.pop_front(); + + while(e) { + result++; + if(e->child_head) + heads.push_back(e->child_head); + e = e->next; + } + } + + return result; +} + +std::shared_ptr TreeView::find_entry(ts::ChannelId channelId) const { + auto result = this->find_linked_entry(channelId); + return result ? result->entry : nullptr; +} + +std::shared_ptr TreeView::find_linked_entry(ChannelId channelId, const std::shared_ptr& head, int deep) const { + std::deque> heads; + heads.push_back(head ? head : this->head); + + + while(!heads.empty()) { + auto e = std::move(heads.front()); + heads.pop_front(); + + while(e) { + if(e->entry->channelId() == channelId) + return e; + if(e->child_head) { + if(deep-- == 0) { //Reached max deep. Dont go deeper + deep++; + } else { + if(e->next) heads.push_back(e->next); + + e = e->child_head; + continue; + } + } + e = e->next; + } + deep++; + } + + return nullptr; +} + +std::deque> TreeView::entries(const std::shared_ptr& root, int deep) const { + return this->query_deep_entry(this->linked(root), deep); +} + +std::deque> TreeView::entries_sub(const std::shared_ptr &root, int deep) const { + auto l_root = this->linked(root); + if(l_root && !l_root->child_head) return {root}; + + auto result = this->query_deep_entry(l_root ? l_root->child_head : l_root, deep); + result.push_back(root); + return result; +} + +std::deque> TreeView::query_deep_entry(const shared_ptr& root, int deep) const { + auto result = this->query_deep(root, deep); + std::deque> mapped; + + for(const auto& e : result) + if(e->entry) + mapped.push_back(e->entry); + return mapped; +} + +bool TreeView::has_entry(const std::shared_ptr &entry, const std::shared_ptr &root, int deep) const { + auto l_root = this->linked(root); + if(!l_root && root) return false; + + return this->has_entry_(entry, l_root ? l_root : this->head, deep); +} + +bool TreeView::has_entry_(const shared_ptr &entry, const shared_ptr &head, int deep) const { + shared_ptr element = head; + while(element && deep != 0) { + if(element->entry->channelId() == entry->channelId()) + return true; + if(element->child_head) + if(this->has_entry_(entry, element->child_head, deep - 1)) + return true; + element = element->next; + } + return false; +} + +bool TreeView::insert_entry(const shared_ptr &entry, const std::shared_ptr &t_parent, const shared_ptr &t_previous) { + auto linked = make_shared(entry); + linked->entry->setLinkedHandle(linked); + + /* Insert channel at the root at the back */ + if(!this->head) { + this->head = linked; + linked->entry->setPreviousChannelId(0); + linked->entry->setParentChannelId(0); + return true; + } + auto last = this->head; + while(last->next) last = last->next; + last->next = linked; + linked->previous = last; + + if(!this->move_entry(entry, t_parent, t_previous)) { + //FIXME delete it again + return false; + } + return true; +} + +bool TreeView::move_entry(const std::shared_ptr &t_entry, const std::shared_ptr &t_parent, + const std::shared_ptr &t_previous) { + if(t_entry == t_parent || t_entry == t_previous || (t_parent && t_parent == t_previous)) return false; + + auto entry = this->linked(t_entry); + if(!entry) return false; + + auto parent = this->linked(t_parent); + if(!parent && t_parent) return false; + if(parent && entry->child_head) { + auto childs = this->query_deep(entry->child_head); + for(const auto& child : childs) + if(child == parent) return false; + } + + auto previous = this->linked(t_previous); + if(!previous && t_previous) return false; + + if(previous && !this->has_entry_(t_previous, parent ? parent : this->head, 2)) return false; //Test if the t_parent channel contains t_previous + + /* cut the entry out */ + this->cut_entry(entry); + entry->parent.reset(); + + /* insert again */ + if(!this->head) { + this->head = entry; + entry->entry->setPreviousChannelId(0); + return true; + } + entry->parent = parent; + entry->entry->setParentChannelId(parent ? parent->entry->channelId() : 0); + + if(previous) { + //Insert within the mid + auto old_next = previous->next; + + //previous insert + previous->next = entry; + entry->previous = previous; + entry->entry->setPreviousChannelId(previous->entry->channelId()); + + //next insert + entry->next = old_next; + if(old_next) { + old_next->previous = entry; + old_next->entry->setPreviousChannelId(entry->entry->channelId()); + } + } else { + if(parent) { + entry->next = parent->child_head; + parent->child_head = entry; + } else { + entry->next = this->head; + this->head = entry; + } + if(entry->next) { + entry->next->previous = entry; + entry->next->entry->setPreviousChannelId(entry->entry->channelId()); + } + entry->entry->setPreviousChannelId(0); + } + + return true; +} + +void TreeView::cut_entry(const std::shared_ptr& entry) { + if(this->head == entry) { + this->head = entry->next; + if(this->head) { + this->head->previous = nullptr; + this->head->entry->setPreviousChannelId(0); + } + } else { + if(entry->previous) { + assert(entry->previous->next == entry); + entry->previous->next = entry->next; + } else if(entry->parent.lock()) { + auto e_parent = entry->parent.lock(); + assert(e_parent->child_head == entry); + e_parent->child_head = entry->next; + } + if(entry->next) { + assert(entry->next->previous == entry); + + entry->next->previous = entry->previous; + entry->next->entry->setPreviousChannelId(entry->previous ? entry->previous->entry->channelId() : 0); + } + } + entry->next = nullptr; + entry->previous = nullptr; +} + +std::deque> TreeView::delete_entry(shared_ptr t_entry) { + auto entry = this->linked(t_entry); + if(!entry) return {}; + this->cut_entry(entry); + + std::deque> result; + std::deque> heads = {entry}; + while(!heads.empty()) { + auto e = std::move(heads.front()); + heads.pop_front(); + + while(e) { + if(e->child_head) + heads.push_back(e->child_head); + result.push_back(e->entry); + + //Release reference + if(e->previous) e->previous->next = nullptr; + e->previous = nullptr; + e->child_head = nullptr; + + //e->entry = nullptr; + if(e->entry->channelId() == t_entry->channelId()) { + e = nullptr; + } else { + e = e->next; + } + } + } + std::reverse(result.begin(), result.end()); //Delete channels from the bottom to the top + + return result; +} + +void TreeView::print_tree(const std::function &, int)> &print) const { + return this->print_tree_(print, this->head, 0); +} + +void TreeView::print_tree_(const std::function &, int)> &print, shared_ptr head, int deep) const { + while(head) { + print(head->entry, deep); + this->print_tree_(print, head->child_head, deep + 1); + head = head->next; + } +} \ No newline at end of file diff --git a/src/channel/TreeView.h b/src/channel/TreeView.h new file mode 100644 index 0000000..0efdf8c --- /dev/null +++ b/src/channel/TreeView.h @@ -0,0 +1,76 @@ +#include + +#pragma once + +#include "misc/advanced_mutex.h" +#include +#include +#include +#include "misc/memtracker.h" + +#ifndef __attribute_deprecated__ + #define __attribute_deprecated__ [[deprecated]] +#endif + +namespace ts { + class TreeEntry; + class TreeView { + public: + struct LinkedTreeEntry { + std::shared_ptr previous; + std::shared_ptr next; + std::shared_ptr child_head; + std::weak_ptr parent; + + const std::shared_ptr entry; + + explicit LinkedTreeEntry(std::shared_ptr entry) : entry(std::move(entry)) { + memtrack::allocated(this); + } + + virtual ~LinkedTreeEntry() { + memtrack::freed(this); + } + }; + + public: + TreeView(); + virtual ~TreeView(); + + size_t entry_count() const; + std::deque> entries(const std::shared_ptr& /* head */ = nullptr, int /* deep */ = -1) const; + std::deque> entries_sub(const std::shared_ptr& /* parent */ = nullptr, int /* deep */ = -1) const; + std::shared_ptr find_entry(ChannelId /* channel id */) const; + std::shared_ptr find_linked_entry(ChannelId /* channel id */, const std::shared_ptr& /* head */ = nullptr, int deep = -1) const; + + bool has_entry(const std::shared_ptr& /* entry */, const std::shared_ptr& /* head */ = nullptr, int deep = -1) const; + + bool insert_entry(const std::shared_ptr& entry, const std::shared_ptr& /* parent */ = nullptr, const std::shared_ptr& /* previous */ = nullptr); + bool move_entry(const std::shared_ptr& /* entry */, const std::shared_ptr& /* parent */ = nullptr, const std::shared_ptr& /* previous */ = nullptr); + std::deque> delete_entry(std::shared_ptr /* entry */); //Copy that here because of reference could be changed (linked->entry) + + void print_tree(const std::function& /* entry */, int /* deep */)>&) const; + protected: + std::shared_ptr head; + private: + inline std::shared_ptr linked(const std::shared_ptr& /* entry */) const; + inline std::deque> query_deep(const std::shared_ptr& /* layer */ = nullptr,int /* max deep */ = -1) const; + inline void query_deep_(std::deque>& /* result */, const std::shared_ptr& /* layer */ = nullptr,int /* max deep */ = -1) const; + inline std::deque> query_deep_entry(const std::shared_ptr& /* layer */ = nullptr,int /* max deep */ = -1) const; + inline bool has_entry_(const std::shared_ptr& /* entry */, const std::shared_ptr& /* head */ = nullptr, int deep = -1) const; + void print_tree_(const std::function& /* entry */, int /* deep */)>&, std::shared_ptr /* head */, int /* deep */) const; + + inline void cut_entry(const std::shared_ptr&); + }; + + class TreeEntry { + public: + virtual ChannelId channelId() const = 0; + + virtual ChannelId previousChannelId() const = 0; + virtual void setPreviousChannelId(ChannelId) = 0; + virtual void setParentChannelId(ChannelId) = 0; + + virtual void setLinkedHandle(const std::weak_ptr& /* self */) {} + }; +} \ No newline at end of file diff --git a/src/converters/converter.h b/src/converters/converter.h new file mode 100644 index 0000000..2c196a1 --- /dev/null +++ b/src/converters/converter.h @@ -0,0 +1,84 @@ +#pragma once + +#include + +namespace ts { + typedef long double long_double; + + /* Converter stuff */ + template + struct converter { + static constexpr bool supported = false; + + static constexpr std::string(*to_string)(const std::any&) = nullptr; + static constexpr T(*from_string)(const std::string&) = nullptr; + }; + + #define DECLARE_CONVERTER(type, decode, encode) \ + template <> \ + struct converter { \ + static constexpr bool supported = true; \ + \ + static constexpr std::string(*to_string)(const std::any&) = encode; \ + static constexpr type(*from_string)(const std::string&) = decode; \ + }; + + #define CONVERTER_METHOD_DECODE(type, name) type name(const std::string& str) + #define CONVERTER_METHOD_ENCODE(type, name) std::string name(const std::any& value) + + /* helper for primitive types */ + #define CONVERTER_PRIMITIVE(type) \ + namespace impl { \ + CONVERTER_METHOD_DECODE(type, converter_ ##type ##_decode); \ + CONVERTER_METHOD_ENCODE(type, converter_ ##type ##_encode); \ + } \ + DECLARE_CONVERTER(type, ::ts::impl::converter_ ##type ##_decode, ::ts::impl::converter_ ##type ##_encode); + + CONVERTER_PRIMITIVE(bool); + CONVERTER_PRIMITIVE(float); + CONVERTER_PRIMITIVE(double); + CONVERTER_PRIMITIVE(long_double); + + CONVERTER_PRIMITIVE(int8_t); + CONVERTER_PRIMITIVE(uint8_t); + + CONVERTER_PRIMITIVE(int16_t); + CONVERTER_PRIMITIVE(uint16_t); + + CONVERTER_PRIMITIVE(int32_t); + CONVERTER_PRIMITIVE(uint32_t); + + CONVERTER_PRIMITIVE(int64_t); + CONVERTER_PRIMITIVE(uint64_t); + + typedef std::string std__string; + typedef const char* const_char__; + CONVERTER_PRIMITIVE(std__string); + CONVERTER_PRIMITIVE(const_char__); + + /* const expr char literal */ + template + struct converter { + using type = char[length]; + static constexpr bool supported = true; + + static constexpr std::string(*to_string)(const std::any&) = [](const std::any& value) { return std::string(std::any_cast(value), length - 1); }; + }; + + #undef CONVERTER_PRIMITIVE +} + +#define DEFINE_CONVERTER_ENUM(class, size_type) \ +namespace ts { \ + template <> \ + struct converter { \ + static constexpr bool supported = true; \ + \ + static constexpr std::string(*to_string)(const std::any&) = [](const std::any& val) { \ + return std::to_string(std::any_cast(val)); \ + }; \ + static constexpr class(*from_string)(const std::string&) = [](const std::string& val) { \ + return ((class(*)(const std::string&)) ts::converter::from_string)(val); \ + }; \ + }; \ +} \ No newline at end of file diff --git a/src/linked_helper.cpp b/src/linked_helper.cpp new file mode 100644 index 0000000..719bbba --- /dev/null +++ b/src/linked_helper.cpp @@ -0,0 +1,133 @@ +#include +#include +#include +#include "linked_helper.h" + +using namespace std; +using namespace linked; + +std::shared_ptr linked::build_chain(const std::deque> &entries, std::deque &log) { + //TODO filter only for one layer + auto find_entry = [&](uint64_t id) -> shared_ptr { + for(const auto& entry : entries) + if(entry->entry_id == id) + return entry; + return nullptr; + }; + + deque> heads; + { + //first linking + for(const auto& entry : entries) { + auto previous = find_entry(entry->previous_id); + if(!previous && entry->previous_id > 0) { + log.emplace_back("missing " + to_string(entry->entry_id) + "'s previous entry (" + to_string(entry->previous_id) + "). Removing previous"); + entry->previous_id = 0; + entry->modified = true; + } + + if(previous) { + /* validate previous stuff */ + if((previous->next && previous->next != entry)) { + log.emplace_back(to_string(entry->entry_id) + "'s previous node has already someone linked (" + to_string(previous->next->entry_id) + "). Removing previous"); + entry->previous_id = 0; + entry->modified = true; + previous = nullptr; + } else if(previous == entry) { + log.emplace_back(to_string(entry->entry_id) + "'s previous node references to himself. Removing previous"); + entry->previous_id = 0; + entry->modified = true; + previous = nullptr; + } + } + if(previous) { + previous->next = entry; + entry->previous = previous; + } else { + heads.push_back(entry); + } + } + } + + { + /* + * Now test for circles (the heads could not contain a circle because they have one open end) + * But we've may nodes which are not within the heads + */ + + unordered_map used_nodes; + deque> unused_nodes; + + for(auto head : heads) { + while(head) { + auto& value = used_nodes[&*head]; + if(value) { + //Node has been already used + log.emplace_back(to_string(head->entry_id) + "'s has already been used, but is linked in another chain! We could not recover from that"); + return nullptr; + } else + value = 1; + + head = head->next; + } + } + + for(const auto& node : entries) { + if(!used_nodes[&*node]) + unused_nodes.push_back(node); + } + + while(!unused_nodes.empty()) { + auto head = std::move(unused_nodes.front()); + unused_nodes.pop_front(); + + log.emplace_back("Found circle. Cutting circle between " + to_string(head->previous->entry_id) + " and " + to_string(head->entry_id)); + if(head->previous->next == head) + head->previous->next = nullptr; + + head->previous = nullptr; + head->previous_id = 0; + head->modified = true; + + heads.push_back(head); + while(head->next) { + auto it = find(unused_nodes.begin(), unused_nodes.end(), head->next); + if(it != unused_nodes.end()) { + unused_nodes.erase(it); + } else { + log.emplace_back(to_string(head->entry_id) + "'s has already been used, but is linked in another chain! We could not recover from that"); + return nullptr; + } + } + } + + if(heads.empty()) { + if(!entries.empty()) + log.emplace_back("failed to detect heads! We could not recover from that"); + return nullptr; + } + + auto head = heads.front(); + heads.pop_front(); + + auto tail = head; + while(tail->next) tail = tail->next; + + while(!heads.empty()) { + + auto local_head = heads.front(); + heads.pop_front(); + + log.emplace_back("Appending open begin (" + to_string(local_head->entry_id) + ") to current tail (" + to_string(tail->entry_id) + ")"); + local_head->previous = tail; + local_head->previous_id = tail->entry_id; + local_head->modified = true; + + tail->next = local_head; + + while(tail->next) tail = tail->next; /* walk to the tail again */ + } + + return head; + } +} \ No newline at end of file diff --git a/src/linked_helper.h b/src/linked_helper.h new file mode 100644 index 0000000..bfeb2d3 --- /dev/null +++ b/src/linked_helper.h @@ -0,0 +1,30 @@ +#pragma once + +#include +#include + +namespace linked { + struct entry { + std::shared_ptr previous; + std::shared_ptr next; + std::shared_ptr child_head; + + uint64_t parent_id; + uint64_t entry_id; + uint64_t previous_id; + + bool fully_linked = false; + bool modified = false; + }; + + inline std::shared_ptr create_entry(uint64_t parent_id, uint64_t entry_id, uint64_t previous_id) { + auto result = std::make_shared(); + result->parent_id = parent_id; + result->entry_id = entry_id; + result->previous_id = previous_id; + + return result; + } + + extern std::shared_ptr build_chain(const std::deque>& /* entried */, std::deque& /* error log */); +} \ No newline at end of file diff --git a/src/log/LogSinks.cpp b/src/log/LogSinks.cpp new file mode 100644 index 0000000..7d5641e --- /dev/null +++ b/src/log/LogSinks.cpp @@ -0,0 +1,118 @@ +#include "LogUtils.h" +#include "LogSinks.h" +#include +#include +#include +#include + +using namespace std; +using namespace spdlog; +namespace fs = std::experimental::filesystem; + +namespace logger { + void TerminalSink::log(const spdlog::details::log_msg &msg) { +#ifdef HAVE_CXX_TERMINAL + if (terminal::active()) { + auto strMsg = msg.formatted.str(); + size_t index = 0; + do { + auto eIndex = strMsg.find('\n', index); + auto str = terminal::parseCharacterCodes(strMsg.substr(index, eIndex - index)); + terminal::instance()->writeMessage(str); + index = eIndex + 1; + } while (index != 0 && index < strMsg.length()); //if eindex == npos and we add the 1 we get 0 :) + } else +#endif + cout << msg.formatted.str(); + } + + void TerminalSink::flush() {} + + bool TerminalSink::should_log_(const details::log_msg &msg) const { + auto _force_message = dynamic_cast(&msg); + if(_force_message && _force_message->force) + return true; + return sink::should_log_(msg); + } + + ColoredFileSink::ColoredFileSink(const filename_t &base_filename, size_t max_size, size_t max_files) + : rotating_file_sink( + base_filename, max_size, max_files) {} + + void ColoredFileSink::_sink_it(const details::log_msg &msg) { + details::log_msg _clone; + +#ifdef HAVE_CXX_TERMINAL + if (::logger::currentConfig()->file_colored) + _clone.formatted << ANSI_RESET << terminal::parseCharacterCodes(msg.formatted.str()); + else + _clone.formatted << terminal::stripCharacterCodes(msg.formatted.str()); +#else + _clone.formatted << msg.formatted.str(); +#endif + sinks::rotating_file_sink_mt::_sink_it(_clone); + } + + void CostumeFormatter::format(spdlog::details::log_msg &msg) { + msg.formatted.clear(); + + string lvlName = level::to_str(msg.level); + transform(lvlName.begin(), lvlName.end(), lvlName.begin(), ::toupper); + auto org_length = lvlName.length(); + + string msgColor; + string msgSuffix; +#ifdef HAVE_CXX_TERMINAL + switch (msg.level) { + case level::trace: + lvlName = "§9" + lvlName; + break; + case level::info: + lvlName = "§e" + lvlName; + break; + case level::warn: + lvlName = "§6" + lvlName; + break; + case level::err: + lvlName = "§4" + lvlName; + break; + case level::critical: + lvlName = ANSI_BOLD ANSI_REVERSE ANSI_RED + lvlName; + msgColor = ANSI_BOLD ANSI_REVERSE ANSI_RED; + msgSuffix = ANSI_RESET; + break; + case level::debug: + lvlName = "§9" + lvlName; + break; + default: + break; + } +#endif + + auto strMsg = msg.raw.str(); + + auto tp = std::chrono::system_clock::to_time_t(msg.time); + + stringstream prefix; + prefix << "[" << std::put_time(std::localtime(&tp), "%F %T") << "] [" << lvlName << "§r] "; + for(size_t i = org_length; i < 5; i++) + prefix << " "; + prefix << msgColor; + + size_t index = 0; + do { + auto eIndex = strMsg.find('\n', index); + auto m = strMsg.substr(index, eIndex - index); + msg.formatted << "§r" << prefix.str() << m << msgSuffix << "\n"; + index = eIndex + 1; //if eindex == npos and we add the 1 we get 0 :) + } while (index != 0 && index < strMsg.length()); + } + + inline std::string CostumeFormatter::time(chrono::time_point point) { + std::time_t time = log_clock::to_time_t(point); + std::tm timetm = *std::localtime(&time); + char buffer[9]; + strftime(buffer, 9, "%H:%M:%S", &timetm); + return string(buffer, 8); + } +} \ No newline at end of file diff --git a/src/log/LogSinks.h b/src/log/LogSinks.h new file mode 100644 index 0000000..7bafcd9 --- /dev/null +++ b/src/log/LogSinks.h @@ -0,0 +1,50 @@ +#pragma once + +#define SPDLOG_FINAL +#define SPDLOG_ALLOW_PROTECT +#define SPDLOG_NO_FINAL //We need to override the rotating logger + +#include +#include + +namespace logger { + struct force_log_msg : public spdlog::details::log_msg + { + force_log_msg() = default; + virtual ~force_log_msg() = default; + force_log_msg(const std::string *loggers_name, spdlog::level::level_enum lvl, bool force) : log_msg(loggers_name, lvl), force(force) { } + + force_log_msg(const log_msg& other) = delete; + force_log_msg(log_msg&& other) = delete; + force_log_msg(const force_log_msg& other) = delete; + force_log_msg& operator=(force_log_msg&& other) = delete; + force_log_msg(force_log_msg&& other) = delete; + + + bool force; + }; + + class TerminalSink : public spdlog::sinks::sink { + public: + void log(const spdlog::details::log_msg &msg) override; + void flush(); + + bool should_log_(const spdlog::details::log_msg &msg) const override; + }; + + class ColoredFileSink : public spdlog::sinks::rotating_file_sink_mt { + public: + ColoredFileSink(const spdlog::filename_t &base_filename, size_t max_size, size_t max_files); + + protected: + void _sink_it(const spdlog::details::log_msg &msg) override; + }; + + class CostumeFormatter : public spdlog::formatter { + public: + void format(spdlog::details::log_msg &msg) override; + + private: + inline std::string time(std::chrono::time_point point); + }; +} diff --git a/src/log/LogUtils.cpp b/src/log/LogUtils.cpp new file mode 100644 index 0000000..7ce9d00 --- /dev/null +++ b/src/log/LogUtils.cpp @@ -0,0 +1,206 @@ +#include "LogUtils.h" +#include "LogSinks.h" +#include +#include +#include +#include +#include +#include +#include + +using namespace std; +using namespace std::chrono; +using namespace spdlog; +namespace fs = std::experimental::filesystem; + +#define ASYNC_LOG +namespace logger { + recursive_mutex loggerLock; + map> loggers; + shared_ptr logConfig; + shared_ptr<::logger::TerminalSink> terminalSink; + shared_ptr<::logger::CostumeFormatter> costumeFormatter; + + shared_ptr logger(int serverId) { + if(!::logger::currentConfig()) + return nullptr; + + int group = 0; + if(::logger::currentConfig()->vs_group_size > 0 && serverId > 0) + group = serverId / ::logger::currentConfig()->vs_group_size; + else group = -1; + + if(loggers.count(group) == 0) { + lock_guard lock(loggerLock); + if(loggers.count(group) > 0) return loggers[group]; + //Create a new logger + if(group > 0){ + logger(0)->debug("Creating new grouped logger for group {}", group); + } + + vector sinks; + string path; + if(logConfig->logfileLevel != spdlog::level::off) { + path = strvar::transform(logConfig->logPath, + strvar::StringValue{"group", group != -1 ? to_string(group) : "general"}, + strvar::FunctionValue("time", (strvar::FunctionValue::FValueFNEasy) [](std::deque value) -> std::string { + auto pattern = !value.empty() ? value[0] : "%Y-%m-%d_%H:%M:%S"; + + tm* tm_info; + auto secs = duration_cast(logConfig->timestamp.time_since_epoch()).count(); + tm_info = localtime((time_t*) &secs); + + char timeBuffer[1024]; + if(strftime(timeBuffer, 1024, pattern.c_str(), tm_info) == 0) { + return string("string is longer than the buffer"); + } + + return string(timeBuffer); + }) + ); + auto logFile = fs::u8path(path); + if(!logFile.parent_path().empty()) + fs::create_directories(logFile.parent_path()); + + auto fileSink = make_shared(logFile.string(), 1024 * 1024 * 50, 12); + sinks.push_back(fileSink); + } else { + path = "/dev/null (" + to_string(serverId) + ")"; + } + sinks.push_back(terminalSink); + +#ifdef ASYNC_LOG + auto logger = create_async("Logger (" + path + ")", sinks.begin(), sinks.end(), 8192, async_overflow_policy::discard_log_msg, [](){}, std::chrono::milliseconds(500)); +#else + auto logger = create("Logger (" + path + ")", sinks.begin(), sinks.end()); +#endif + logger->set_formatter(costumeFormatter); + for(const auto& sink : logger->sinks()) + if(dynamic_pointer_cast(sink)) { + sink->set_level(::logger::currentConfig()->terminalLevel); + } else if(dynamic_pointer_cast(sink)) { + sink->set_level(::logger::currentConfig()->logfileLevel); + } else { + sink->set_level(min(::logger::currentConfig()->logfileLevel, ::logger::currentConfig()->terminalLevel)); + } + logger->set_level(min(::logger::currentConfig()->logfileLevel, ::logger::currentConfig()->terminalLevel)); + loggers[group] = logger; + } + + return loggers[group]; + } + + const std::shared_ptr& currentConfig() { + return logConfig; + } + + extern void setup(const shared_ptr& config) { + logConfig = config; + config->timestamp = system_clock::now(); + + terminalSink = make_shared(); + terminalSink->set_level(::logger::currentConfig()->terminalLevel); + costumeFormatter = make_shared(); + logger(0)->debug("Log successfully started!"); + } + + void updateLogLevels() { + lock_guard lock(loggerLock); + for(const auto& loggerEntry : loggers) { + auto logger = loggerEntry.second; + for(const auto& sink : logger->sinks()) + if(dynamic_pointer_cast(sink)) + sink->set_level(::logger::currentConfig()->terminalLevel); + else if(dynamic_pointer_cast(sink)) + sink->set_level(::logger::currentConfig()->logfileLevel); + else + sink->set_level(min(::logger::currentConfig()->logfileLevel, ::logger::currentConfig()->terminalLevel)); + logger->set_level(min(::logger::currentConfig()->logfileLevel, ::logger::currentConfig()->terminalLevel)); + } + } + + void flush() { + unique_lock lock(loggerLock); + auto _loggers = loggers; + lock.unlock(); + + for(const auto& loggerEntry : _loggers) { + loggerEntry.second->flush(); + } + } + + extern void uninstall() { + lock_guard lock(loggerLock); + for(auto& loggerEntry : loggers) { + loggerEntry.second->flush(); + loggerEntry.second.reset(); + } + loggers.clear(); + + logConfig = nullptr; + terminalSink = nullptr; + costumeFormatter = nullptr; + } +} + +void hexDump(void *addr, int len, int pad,int columnLength, void (*print)(string)); +void hexDump(void *addr, int len, int pad,int columnLength) { + hexDump(addr, len, pad, columnLength, [](string str){ logMessage(0, "\n{}", str); }); +} + +void hexDump(void *addr, int len, int pad,int columnLength, void (*print)(string)) { + int i; + uint8_t* buff = new uint8_t[pad+1]; + unsigned char* pc = (unsigned char*)addr; + + if (len <= 0) { + return; + } + + stringstream line; + line << uppercase << hex << setfill('0'); + // Process every byte in the data. + for (i = 0; i < len; i++) { + // Multiple of 16 means new line (with line offset). + + if ((i % pad) == 0) { + // Just don't print ASCII for the zeroth line. + if (i != 0) { + line << buff; + print(line.str()); + line = stringstream{}; + line << hex; + }; + + // Output the offset. + line << setw(4) << i << " "; + } + if(i % columnLength == 0 && i % pad != 0){ + line << "| "; + } + + // Now the hex code for the specific character. + line << setw(2) << (int) pc[i] << " "; + + // And store a printable ASCII character for later. + if ((pc[i] < 0x20) || (pc[i] > 0x7e)) + buff[i % pad] = '.'; + else + buff[i % pad] = pc[i]; + buff[(i % pad) + 1] = '\0'; + } + + // Pad out last line if not exactly 16 characters. + while ((i % pad) != 0) { + line << " "; + i++; + } + + line << buff; + delete[] buff; + + print(line.str()); + line = stringstream{}; + line << "Length: " << dec << len << " Addr: " << addr; + print(line.str()); +} diff --git a/src/log/LogUtils.h b/src/log/LogUtils.h new file mode 100644 index 0000000..63fa90a --- /dev/null +++ b/src/log/LogUtils.h @@ -0,0 +1,136 @@ +#pragma once + +#ifdef SPDLOG_FINAL + #undef SPDLOG_FINAL +#endif +#define SPDLOG_FINAL +#define SPDLOG_ALLOW_PROTECT +#define SPDLOG_NO_FINAL //We need to override the rotating logger +#ifdef byte + #undef byte +#endif + +#include +#include +#include +#include +#include "../Definitions.h" +#include "LogSinks.h" + +#ifdef HAVE_CXX_TERMINAL + #include +#endif + +#define lstream std::stringstream() + +#ifdef log +#undef log +#endif +namespace logger { + struct LoggerConfig { + spdlog::level::level_enum logfileLevel = spdlog::level::info; + spdlog::level::level_enum terminalLevel = spdlog::level::info; + bool file_colored; + std::string logPath = "log_${time}.log"; + size_t vs_group_size = 1; + std::chrono::system_clock::time_point timestamp; + }; + + extern std::shared_ptr logger(int); + extern void setup(const std::shared_ptr&); + extern const std::shared_ptr& currentConfig(); + extern void uninstall(); + + extern void updateLogLevels(); + extern void flush(); +} + +#define LOG_LICENSE_CONTROLL (-0x10) +#define LOG_LICENSE_WEB (-0x11) + +#define LOG_INSTANCE (-1) +#define LOG_QUERY (-2) +#define LOG_FT (-3) +#define LOG_GENERAL 0 + +#ifdef HAVE_CXX_TERMINAL + #define DEFINE_LOG_IMPL_NO_LOGGER(prefix, message) \ + if(terminal::active()) \ + terminal::instance()->writeMessage("[" + std::string(prefix) + "] " + message); \ + else \ + std::cout << "[" + std::string(prefix) + "] " + message << std::endl; +#else + #define DEFINE_LOG_IMPL_NO_LOGGER(prefix, message) \ + std::cout << "[" + std::string(prefix) + "] " + message << std::endl; +#endif + +#define DEFINE_LOG_IMPL(name, level, _default_prefix) \ +template \ +inline void name ##Fmt(bool forced, int serverId, const std::string& message, const Args&... args){ \ + auto _logger = ::logger::logger(serverId); \ + std::string fmt_message; \ + try { \ + fmt::MemoryWriter writer; \ + writer.write(message, args...); \ + fmt_message = writer.str(); \ + } catch (const std::exception &ex) { \ + fmt_message = "failed to format message '" + message + "': " + ex.what(); \ + } \ + if(!_logger) { \ + DEFINE_LOG_IMPL_NO_LOGGER(_default_prefix, fmt_message); \ + return; \ + } \ + if (!_logger->should_log(level) && !forced) return; \ + \ + try { \ + ::logger::force_log_msg log_msg(&_logger->_name, level, forced); \ + auto fmt = "§8{0:>5} | §r{1}"; \ + if(serverId > 0); \ + else if(serverId == LOG_INSTANCE) \ + fmt = "§8GLOBL | §r{1}"; \ + else if(serverId == LOG_QUERY) \ + fmt = "§8QUERY | §r{1}"; \ + else if(serverId == LOG_FT) \ + fmt = "§8 FILE | §r{1}"; \ + else if(serverId == LOG_GENERAL) \ + fmt = "§8 GEN | §r{1}"; \ + else if(serverId == LOG_LICENSE_CONTROLL) \ + fmt = "§8 CONTR | §r{1}"; \ + else if(serverId == LOG_LICENSE_WEB) \ + fmt = "§8 WEBST | §r{1}"; \ + log_msg.raw.write(fmt, serverId, fmt_message); \ + _logger->_sink_it(log_msg); \ + } catch (const std::exception &ex) { \ + _logger->_err_handler(ex.what()); \ + } catch(...) { \ + _logger->_err_handler("Unknown exception in logger " + _logger->_name); \ + throw; \ + } \ +} + +DEFINE_LOG_IMPL(logMessage, spdlog::level::info, "INFO") +DEFINE_LOG_IMPL(logError, spdlog::level::err, "ERROR") +DEFINE_LOG_IMPL(logWarning, spdlog::level::warn, "WARNING") +DEFINE_LOG_IMPL(logCritical, spdlog::level::critical, "CRITICAL") +DEFINE_LOG_IMPL(logTrace, spdlog::level::trace, "TRACE") +DEFINE_LOG_IMPL(debugMessage, spdlog::level::debug, "DEBUG") + +#define LOG_METHOD(name) \ +template \ +inline void name(int serverId, const std::string& message, const Args&... args){ name ##Fmt(false, serverId, message, args...); } \ +inline void name(int serverId, const std::string& message){ name ##Fmt(false, serverId, message); } \ +inline void name(int serverId, std::ostream& str){ std::stringstream s; s << str.rdbuf(); name(serverId, s.str()); } \ +inline void name(const std::string& message) { name ##Fmt(false, 0, message); } \ +inline void name(std::ostream& str){ std::stringstream s; s << str.rdbuf(); name(s.str()); } \ +inline void name(bool, int, const std::string&) = delete; + +LOG_METHOD(logError); +LOG_METHOD(logWarning); +LOG_METHOD(logMessage); +LOG_METHOD(logCritical); +LOG_METHOD(logTrace); +LOG_METHOD(debugMessage); + +#undef LOG_METHOD +void hexDump(void* addr, int length, int numLength = 16, int columnLength = 8); +void hexDump(void* addr, int length, int numLength, int columnLength, void (*)(std::string)); \ No newline at end of file diff --git a/src/log/translation.cpp b/src/log/translation.cpp new file mode 100644 index 0000000..5691c78 --- /dev/null +++ b/src/log/translation.cpp @@ -0,0 +1,5 @@ +// +// Created by wolverindev on 12.06.18. +// + +#include "translation.h" diff --git a/src/log/translation.h b/src/log/translation.h new file mode 100644 index 0000000..c6e00c6 --- /dev/null +++ b/src/log/translation.h @@ -0,0 +1,13 @@ +#pragma once + +namespace tr { + enum Messages { + kick_invalid_badges, + kick_invalid_packet, + kick_invalid_command, + + crash_instance, + shutdown_instance, + shutdown_server, + }; +} \ No newline at end of file diff --git a/src/misc/TraceUtils.cpp b/src/misc/TraceUtils.cpp new file mode 100644 index 0000000..fd523b1 --- /dev/null +++ b/src/misc/TraceUtils.cpp @@ -0,0 +1,119 @@ +#include // for backtrace +#include +#include +#include +#include + +#include "TraceUtils.h" +#include "../../../music/providers/shared/pstream.h" + +#define READ_BUFFER_SIZE 128 + +namespace TraceUtils { + static bool addr2line_present = true; + inline std::string addr2lineInfo(StackTraceElement *element) { + if(!addr2line_present) + return "??\n??:0"; + + char buffer[READ_BUFFER_SIZE]; + + sprintf(buffer, "addr2line -Cif -e %s %p", element->file.c_str(), element->offset); //last parameter is the name of this app + redi::pstream stream(buffer, redi::pstream::pstdout | redi::pstream::pstderr); + + std::string result; + std::string error; + do { + + auto read = stream.err().readsome(buffer, READ_BUFFER_SIZE); + if(read > 0) error += string(buffer, read); + + read = stream.out().readsome(buffer, READ_BUFFER_SIZE); + if(read > 0) result += string(buffer, read); + } while(stream.good()); + + if(!error.empty()) { + while(!error.empty() && (error.back() == '\n' || error.back() == '\r')) error = error.substr(0, error.length() - 1); + logError("Could not resolve symbols. (Error: " + error + ")"); + addr2line_present = false; + return "??\n??:0"; + } + return result; + } + + + void StackTrace::printStackTrace() { + printStackTrace([](StackTraceElement* e) { + cerr << " at "+e->getFunctionName()+"( " + e->getSourceFile() + ":" + to_string(e->getSourceLine()) + ")" << endl; + }); + } + + void StackTrace::printStackTrace(std::function writeMessage) { + for (int i = 0; i < stackSize; i++) { + writeMessage((StackTraceElement*) elements[i]); + } + } + + StackTrace backTrace(int size) { + int backtraceLength; + void *buffer[BT_BUF_SIZE]; + char **symbols; + + backtraceLength = backtrace(buffer, BT_BUF_SIZE); + symbols = backtrace_symbols(buffer, backtraceLength); + if (symbols == nullptr) { + perror("backtrace_symbols"); + exit(EXIT_FAILURE); + } + + StackTrace out(backtraceLength); + + for (int i = 0; i < backtraceLength; i++) { + auto sym = std::string(symbols[i]); + string file = "undefined"; + if (sym.find_first_of('(') != std::string::npos) file = sym.substr(0, sym.find_first_of('(')); + out.elements[i] = new StackTraceElement{i, buffer[i], file, sym}; + } + + free(symbols); + + return out; + } + + + StackTrace::StackTrace(int size) : stackSize(size), elements(static_cast(malloc(size * sizeof(void *)))) {} + + StackTrace::~StackTrace() { + for (int i = 0; i < this->stackSize; i++) + if (this->elements[i]) delete this->elements[i]; + free(this->elements); + } + + void StackTraceElement::loadSymbols() { + if (this->symbolLoadState == 0) { + auto strInfo = addr2lineInfo(this); + this->fnName = strInfo.substr(0, strInfo.find_first_of('\n')); + + auto srcInfo = strInfo.substr(strInfo.find_first_of('\n') + 1); + this->srcFile = srcInfo.substr(0, srcInfo.find_first_of(':')); + this->srcLine = atoi(srcInfo.substr(srcInfo.find_first_of(':') + 1).c_str()); + this->symbolLoadState = 1; + } + } + + string StackTraceElement::getFunctionName() { + loadSymbols(); + return this->fnName; + } + + string StackTraceElement::getSourceFile() { + loadSymbols(); + return this->srcFile; + } + + int StackTraceElement::getSourceLine() { + loadSymbols(); + return this->srcLine; + } + + StackTraceElement::StackTraceElement(int elementIndex, void *offset, string file, string symbol) : elementIndex(elementIndex), offset(offset), file(file), symbol(symbol) {} +} \ No newline at end of file diff --git a/src/misc/TraceUtils.h b/src/misc/TraceUtils.h new file mode 100644 index 0000000..f9ef781 --- /dev/null +++ b/src/misc/TraceUtils.h @@ -0,0 +1,56 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#define BT_BUF_SIZE 100 + +using namespace std; + +namespace TraceUtils { + struct StackTraceElement { + public: + StackTraceElement(int elementIndex, void *offset,string file,string symbol); + + int elementIndex; + + void *offset; + string file; + + string symbol; + + + string getFunctionName(); + string getSourceFile(); + int getSourceLine(); + + private: + int symbolLoadState = 0; + + string fnName; + string srcFile; + int srcLine; + + void loadSymbols(); + }; + + struct StackTrace { + public: + explicit StackTrace(int size); + ~StackTrace(); + + const StackTraceElement** elements; + const int stackSize; + + void printStackTrace(); + + void printStackTrace(std::function format); + }; + + extern StackTrace backTrace(int size); +}; diff --git a/src/misc/advanced_mutex.h b/src/misc/advanced_mutex.h new file mode 100644 index 0000000..0dd3948 --- /dev/null +++ b/src/misc/advanced_mutex.h @@ -0,0 +1,299 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace std { + template + struct lock_guarded { + Lock l; + T *t; + + T *operator->() &&{ return t; } + + template + auto operator[](Arg &&arg) && -> decltype(std::declval()[std::declval()]) { + return (*t)[std::forward(arg)]; + } + + T &operator*() &&{ return *t; } + }; + + template + struct lock_guarded_shared { + Lock l; + std::shared_ptr t; + + T *operator->() &&{ return t.operator->(); } + + template + auto operator[](Arg &&arg) && -> decltype(std::declval()[std::declval()]) { + return (*t)[std::forward(arg)]; + } + + T &operator*() &&{ return *t; } + + operator bool() { + return !!t; + } + + bool operator !() { + return !t; + } + }; + + constexpr struct emplace_t { } emplace{}; + + template + struct observer_locked { + public: + observer_locked(observer_locked &&o) : t(std::move(o.t)), m(std::move(o.m)) {} + observer_locked(observer_locked const &o) : t(o.t), m(o.m) {} + + observer_locked(M lock,T entry) : m(std::forward(lock)), t(std::forward(entry)) {} + + observer_locked() = default; + ~observer_locked() = default; + + T operator->() { + return t; + } + + T const operator->() const { + return t; + } + + T get() { return this->t; } + T const get() const { return this->t; } + + template + std::result_of_t operator->*(F &&f) { + return std::forward(f)(t); + } + + template + std::result_of_t operator->*(F &&f) const { + return std::forward(f)(t); + } + + observer_locked &operator=(observer_locked &&o) { + this->m = std::move(o.m); + this->t = std::move(o.t); + return *this; + } + + observer_locked &operator=(observer_locked const &o) { + this->m = o.m; + this->t = o.t; + return *this; + } + + observer_locked &reset() { + observer_locked empty(M(),NULL); + *this = empty; + return *this; + } + private: + M m; + T t; + }; + + template + struct mutex_guarded { + lock_guarded> get_locked() { + return {std::unique_lock{m}, &t}; + } + + lock_guarded> get_locked() const { + return {{m}, &t}; + } + + lock_guarded> operator->() { + return get_locked(); + } + + lock_guarded> operator->() const { + return get_locked(); + } + + template + std::result_of_t operator->*(F &&f) { + return std::forward(f)(*get_locked()); + } + + template + std::result_of_t operator->*(F &&f) const { + return std::forward(f)(*get_locked()); + } + + template + mutex_guarded(emplace_t, Args &&...args) : t(std::forward(args)...) {} + + mutex_guarded(mutex_guarded &&o) : t(std::move(*o.get_locked())) {} + + mutex_guarded(mutex_guarded const &o) : t(*o.get_locked()) {} + + mutex_guarded() = default; + + ~mutex_guarded() = default; + + mutex_guarded &operator=(mutex_guarded &&o) { + T tmp = std::move(o.get_locked()); + *get_locked() = std::move(tmp); + return *this; + } + + mutex_guarded &operator=(mutex_guarded const &o) { + T tmp = o.get_locked(); + *get_locked() = std::move(tmp); + return *this; + } + + private: + std::mutex m; + T t; + }; + + class shared_recursive_mutex { + std::shared_mutex handle; + public: + void lock(void) { + std::thread::id this_id = std::this_thread::get_id(); + if (owner == this_id) { + // recursive locking + ++count; + } else { + // normal locking + if (shared_counts->count(this_id)) {//Already shared locked, write lock is not available +#ifdef WIN32 + throw std::logic_error("resource_deadlock_would_occur"); +#else + __throw_system_error(int(errc::resource_deadlock_would_occur)); +#endif + } + handle.lock(); //Now wait until everyone else has finished + owner = this_id; + count = 1; + } + } + + void unlock(void) { + std::thread::id this_id = std::this_thread::get_id(); + assert(this_id == this->owner); + + if (count > 1) { + // recursive unlocking + count--; + } else { + // normal unlocking + owner = std::thread::id(); + count = 0; + handle.unlock(); + } + } + + void lock_shared() { + std::thread::id this_id = std::this_thread::get_id(); + if(this->owner == this_id) { +#ifdef WIN32 + throw std::logic_error("resource_deadlock_would_occur"); +#else + __throw_system_error(int(errc::resource_deadlock_would_occur)); +#endif + } + + if (shared_counts->count(this_id)) { + ++(shared_counts.get_locked()[this_id]); + } else { + handle.lock_shared(); + shared_counts.get_locked()[this_id] = 1; + } + } + + void unlock_shared() { + std::thread::id this_id = std::this_thread::get_id(); + auto it = shared_counts->find(this_id); + if (it->second > 1) { + --(it->second); + } else { + shared_counts->erase(it); + handle.unlock_shared(); + } + } + + bool try_lock() { + std::thread::id this_id = std::this_thread::get_id(); + if (owner == this_id) { + // recursive locking + ++count; + return true; + } else { + // normal locking + if (shared_counts->count(this_id)){ //Already shared locked, write lock is not available +#ifdef WIN32 + throw std::logic_error("resource_deadlock_would_occur"); +#else + __throw_system_error(int(errc::resource_deadlock_would_occur)); +#endif + } + + if(!handle.try_lock()) return false; + + owner = this_id; + count = 1; + return true; + } + } + + bool try_lock_shared() { + std::thread::id this_id = std::this_thread::get_id(); + if(this->owner == this_id){ +#ifdef WIN32 + throw std::logic_error("resource_deadlock_would_occur"); +#else + __throw_system_error(int(errc::resource_deadlock_would_occur)); +#endif + } + + if (shared_counts->count(this_id)) { + ++(shared_counts.get_locked()[this_id]); + } else { + if(!handle.try_lock_shared()) return false; + + shared_counts.get_locked()[this_id] = 1; + } + return true; + } + + private: + std::atomic owner; + std::atomic count; + mutex_guarded> shared_counts; + }; + + template + inline bool mutex_locked(T& mutex) { + return true; + try { + unique_lock lock_try(mutex, try_to_lock); /* should throw EDEADLK */ + return false; + } catch(const std::system_error& ex) { + return ex.code() == errc::resource_deadlock_would_occur; + } + } + + template + inline bool mutex_shared_locked(T& mutex) { + return true; + try { + shared_lock lock_try(mutex, try_to_lock); /* should throw EDEADLK */ + return false; + } catch(const std::system_error& ex) { + return ex.code() == errc::resource_deadlock_would_occur; + } + } +} \ No newline at end of file diff --git a/src/misc/base64.h b/src/misc/base64.h new file mode 100644 index 0000000..53b6a62 --- /dev/null +++ b/src/misc/base64.h @@ -0,0 +1,71 @@ +#pragma once + +#include +#include +#include + +namespace base64 { + /** + * Encodes a given string in Base64 + * @param input The input string to Base64-encode + * @param inputSize The size of the input to decode + * @return A Base64-encoded version of the encoded string + */ + inline std::string encode(const char* input, const unsigned long inputSize) { + auto outlen = static_cast(inputSize + (inputSize / 3.0) + 16); + auto outbuf = new unsigned char[outlen]; //Reserve output memory + if(base64_encode((unsigned char*) input, inputSize, outbuf, &outlen) != CRYPT_OK){ + std::cerr << "Invalid input '" << input << "'" << std::endl; + return ""; + } + std::string ret((char*) outbuf, outlen); + delete[] outbuf; + return ret; + } + + /** + * Encodes a given string in Base64 + * @param input The input string to Base64-encode + * @return A Base64-encoded version of the encoded string + */ + inline std::string encode(const std::string& input) { return encode(input.c_str(), input.size()); } + + + /** + * Decodes a Base64-encoded string. + * @param input The input string to decode + * @return A string (binary) that represents the Base64-decoded data of the input + */ + inline std::string decode(const char* input, size_t size) { + auto out = new unsigned char[size]; + if(base64_strict_decode((unsigned char*) input, size, out, (unsigned long*) &size) != CRYPT_OK){ + std::cerr << "Invalid base 64 string '" << input << "'" << std::endl; + return ""; + } + std::string ret((char*) out, size); + delete[] out; + return ret; + } + + /** + * Decodes a Base64-encoded string. + * @param input The input string to decode + * @return A string (binary) that represents the Base64-decoded data of the input + */ + inline std::string decode(const std::string& input) { return decode(input.c_str(), input.size()); } + + //A–Z, a–z, 0–9, + und / + inline bool validate(const std::string& input) { + for(char c : input) { + if(c >= 'A' && c <= 'Z') continue; + if(c >= 'a' && c <= 'z') continue; + if(c >= '0' && c <= '9') continue; + if(c == '+' || c == '/' || c == '=') continue; + + return false; + } + return true; + } +} +inline std::string base64_encode(const char* input, const unsigned long inputSize) { return base64::encode(input, inputSize); } +inline std::string base64_encode(const std::string& input) { return base64::encode(input.c_str(), input.size()); } \ No newline at end of file diff --git a/src/misc/cast.h b/src/misc/cast.h new file mode 100644 index 0000000..63cd179 --- /dev/null +++ b/src/misc/cast.h @@ -0,0 +1,24 @@ +#pragma once + +#include + +template +inline std::shared_ptr static_pointer_cast(const std::shared_ptr &r) noexcept { + auto p = static_cast::element_type *>(r.get()); + return std::shared_ptr(r, p); +} + +template +inline std::shared_ptr dynamic_pointer_cast(const std::shared_ptr &r) noexcept { + if (auto p = dynamic_cast::element_type *>(r.get())) { + return std::shared_ptr(r, p); + } else { + return std::shared_ptr(); + } +} + +template +inline std::shared_ptr const_pointer_cast(const std::shared_ptr &r) noexcept { + auto p = const_cast::element_type *>(r.get()); + return std::shared_ptr(r, p); +} \ No newline at end of file diff --git a/src/misc/digest.h b/src/misc/digest.h new file mode 100644 index 0000000..5441e56 --- /dev/null +++ b/src/misc/digest.h @@ -0,0 +1,58 @@ +#pragma once + +#include +#include +#include + +#ifdef NO_OPEN_SSL + #include + + #define SHA_DIGEST_LENGTH 20 + #define SHA256_DIGEST_LENGTH 32 + #define SHA512_DIGEST_LENGTH 64 + + #define DECLARE_DIGEST(name, _unused_, digestLength) \ + inline std::string name(const std::string& input) { \ + hash_state hash{}; \ + \ + uint8_t buffer[digestLength]; \ + \ + name ##_init(&hash); \ + name ##_process(&hash, (uint8_t*) input.data(), input.length()); \ + name ##_done(&hash, buffer); \ + \ + return std::string((const char*) buffer, digestLength); \ + } \ + \ + inline std::string name(const char* input, int64_t length = -1) { \ + if(length == -1) length = strlen(input); \ + return name(std::string{input, (size_t) length}); \ + } \ + +#else + #include + + #define DECLARE_DIGEST(name, method, digestLength) \ + inline std::string name(const std::string& input) { \ + u_char buffer[digestLength]; \ + method((u_char*) input.data(), input.length(), buffer); \ + return std::string((const char*) buffer, digestLength); \ + } \ + \ + inline std::string name(const char* input, ssize_t length = -1) { \ + if(length == -1) length = strlen(input); \ + return name(std::string(input, length)); \ + } \ + \ + inline void name(const char* input, size_t length, uint8_t(& result)[digestLength]) { \ + method((u_char*) input, length, result); \ + } +#endif + +namespace digest { + DECLARE_DIGEST(sha1, SHA1, SHA_DIGEST_LENGTH) + DECLARE_DIGEST(sha256, SHA256, SHA256_DIGEST_LENGTH) + DECLARE_DIGEST(sha512, SHA512, SHA512_DIGEST_LENGTH) +} + +#undef DECLARE_DIGEST \ No newline at end of file diff --git a/src/misc/endianness.h b/src/misc/endianness.h new file mode 100644 index 0000000..016cda5 --- /dev/null +++ b/src/misc/endianness.h @@ -0,0 +1,110 @@ +#pragma once + +#include +#include + +using namespace std; + +#define _LE2BE(size, convert) \ +template ::type, uint8_t>::value || \ + std::is_same::type, int8_t>::value || \ + std::is_same::type, char>::value || \ + std::is_same::type, unsigned char>::value \ + , int>::type = 0> \ +inline void le2be ##size(uint ##size ##_t num, BufferType* buffer,T offset = 0, T* offsetCounter = nullptr){ \ + convert; \ + if(offsetCounter) *offsetCounter += (size) / 8; \ +} + +#define _BE2LE(size, convert) \ +template ::type, uint8_t>::value || \ + std::is_same::type, int8_t>::value || \ + std::is_same::type, char>::value || \ + std::is_same::type, unsigned char>::value \ + , int>::type = 0, typename ResultType = uint ##size ##_t> \ +inline ResultType be2le ##size(BufferType* buffer,T offset = 0, T* offsetCounter = nullptr){ \ + ResultType result = 0; \ + convert; \ + if(offsetCounter) *offsetCounter += (size) / 8; \ + return result; \ +} + +//LE -> BE +_LE2BE(8, { + buffer[offset + 0] = (BufferType) ((num) & 0xFF); +}); + +_LE2BE(16, { + buffer[offset + 0] = (BufferType) ((num >> 8) & 0xFF); + buffer[offset + 1] = (BufferType) ((num >> 0) & 0xFF); +}); + +_LE2BE(32, { + buffer[offset + 0] = (BufferType) ((num >> 24) & 0xFF); + buffer[offset + 1] = (BufferType) ((num >> 16) & 0xFF); + buffer[offset + 2] = (BufferType) ((num >> 8) & 0xFF); + buffer[offset + 3] = (BufferType) ((num >> 0) & 0xFF); +}); + +_LE2BE(64, { + buffer[offset + 0] = (BufferType) ((num >> 56) & 0xFF); + buffer[offset + 1] = (BufferType) ((num >> 48) & 0xFF); + buffer[offset + 2] = (BufferType) ((num >> 40) & 0xFF); + buffer[offset + 3] = (BufferType) ((num >> 32) & 0xFF); + buffer[offset + 4] = (BufferType) ((num >> 24) & 0xFF); + buffer[offset + 5] = (BufferType) ((num >> 16) & 0xFF); + buffer[offset + 6] = (BufferType) ((num >> 8) & 0xFF); + buffer[offset + 7] = (BufferType) ((num >> 0) & 0xFF); +}); + + +//BE -> LE +_BE2LE(8, { + result |= (ResultType) (uint8_t) buffer[offset + 0]; +}); + +_BE2LE(16, { + result |= (ResultType) (uint8_t) buffer[offset + 0] << 8; + result |= (ResultType) (uint8_t) buffer[offset + 1] << 0; +}); + +_BE2LE(32, { + result |= (ResultType) (uint8_t) buffer[offset + 0] << 24; + result |= (ResultType) (uint8_t) buffer[offset + 1] << 16; + result |= (ResultType) (uint8_t) buffer[offset + 2] << 8; + result |= (ResultType) (uint8_t) buffer[offset + 3] << 0; +}); + +_BE2LE(64, { + result += (ResultType) (uint8_t) buffer[offset + 0] << 56; + result += (ResultType) (uint8_t) buffer[offset + 1] << 48; + result += (ResultType) (uint8_t) buffer[offset + 2] << 40; + result += (ResultType) (uint8_t) buffer[offset + 3] << 32; + result += (ResultType) (uint8_t) buffer[offset + 4] << 24; + result += (ResultType) (uint8_t) buffer[offset + 5] << 16; + result += (ResultType) (uint8_t) buffer[offset + 6] << 8; + result += (ResultType) (uint8_t) buffer[offset + 7] << 0; +}); + +template +inline void le2le16(uint16_t num, char *buffer,T offset = 0, T* offsetCounter = nullptr){ + buffer[offset + 0] = (char) (num >> 0); + buffer[offset + 1] = (char) (num >> 8); + if(offsetCounter) *offsetCounter += 2; + static_assert(true, ""); +} + +template +inline void le2le64(uint64_t num, char *buffer,T offset = 0, T* offsetCounter = nullptr){ + buffer[offset + 0] = (char) (num >> 0); + buffer[offset + 1] = (char) (num >> 8); + buffer[offset + 2] = (char) (num >> 16); + buffer[offset + 3] = (char) (num >> 24); + buffer[offset + 4] = (char) (num >> 32); + buffer[offset + 5] = (char) (num >> 40); + buffer[offset + 6] = (char) (num >> 48); + buffer[offset + 7] = (char) (num >> 56); + if(offsetCounter) *offsetCounter += 2; +} \ No newline at end of file diff --git a/src/misc/hex.h b/src/misc/hex.h new file mode 100644 index 0000000..f14e489 --- /dev/null +++ b/src/misc/hex.h @@ -0,0 +1,20 @@ +#pragma once + +#include +#include + +namespace hex { + inline std::string hex(const std::string& input, char beg, char end){ + assert(end - beg == 16); + + int len = input.length() * 2; + char output[len]; + int idx = 0; + for (char elm : input) { + output[idx++] = static_cast(beg + ((elm >> 4) & 0x0F)); + output[idx++] = static_cast(beg + ((elm & 0x0F) >> 0)); + } + + return std::string(output, len); + } +} \ No newline at end of file diff --git a/src/misc/lambda.h b/src/misc/lambda.h new file mode 100644 index 0000000..b4a3191 --- /dev/null +++ b/src/misc/lambda.h @@ -0,0 +1,59 @@ +#pragma once + +#include +#include +#include + +//https://qiita.com/angeart/items/94734d68999eca575881 +namespace stx { + namespace lambda_detail { + template + struct member_type; + + template + struct member_type { + using member = std::true_type; + using invoker_function = std::function; + }; + template + struct member_type { + using member = std::false_type; + using invoker_function = std::function; + }; + + template + struct types : member_type { + public: + static constexpr bool has_klass = std::is_class::value; + static constexpr int argc = sizeof...(t_args); + + using flag_member = t_member; + using klass = t_klass; + using return_type = t_return_type; + using is_mutable = flag_mutable; + + using args = std::tuple; + + template + struct arg { + typedef typename std::tuple_element>::type type; + }; + }; + + template + struct lambda_type_impl; + + template + struct lambda_type_impl : lambda_detail::types {}; + } + + template + struct lambda_type : lambda_detail::lambda_type_impl { }; + + template + struct lambda_type : lambda_detail::types::type,ret,klass,std::true_type,args...> { }; + + template + struct lambda_type : lambda_detail::types::type, ret, klass, std::false_type, args...> { }; + +}; \ No newline at end of file diff --git a/src/misc/memtracker.cpp b/src/misc/memtracker.cpp new file mode 100644 index 0000000..64aeb1f --- /dev/null +++ b/src/misc/memtracker.cpp @@ -0,0 +1,227 @@ +#include "log/LogUtils.h" + +#include +#include +#include +#include +#include + +#define TRACK_OBJECT_ALLOCATION +#include "memtracker.h" + +#define NO_IMPL //For fast disable (e.g. when you dont want to recompile the whole source) + +#ifndef __GLIBC__ + #define _GLIBCXX_NOEXCEPT +#endif +#ifndef WIN32 + #include +#endif +#ifdef WIN32 + typedef int64_t ssize_t; +#endif + +//#define MEMTRACK_VERBOSE + +inline bool should_track_mangled(const char* mangled) { + if(strstr(mangled, "ViewEntry")) return true; + if(strstr(mangled, "ViewEntry")) return true; + if(strstr(mangled, "ClientChannelView")) return true; + if(strstr(mangled, "LinkedTreeEntry")) return true; + + return false; +} + +using namespace std; +namespace memtrack { + struct TypeInfo { + const char* name; + std::string mangled; + + explicit TypeInfo(const char* name) : name(name) {} + + bool operator==(const TypeInfo& other) { + return other.name == this->name || strcmp(other.name, this->name) == 0; + } + bool operator!=(const TypeInfo& other) { + return ! this->operator==(other); + } + + bool operator<(const TypeInfo& __rhs) const noexcept + { return this->before(__rhs); } + + bool operator<=(const TypeInfo& __rhs) const noexcept + { return !__rhs.before(*this); } + + bool operator>(const TypeInfo& __rhs) const noexcept + { return __rhs.before(*this); } + + bool operator>=(const TypeInfo& __rhs) const noexcept + { return !this->before(__rhs); } + + inline bool before(const TypeInfo& __arg) const _GLIBCXX_NOEXCEPT + { return (name[0] == '*' && __arg.name[0] == '*') + ? name < __arg.name + : strcmp (name, __arg.name) < 0; } + + inline std::string as_mangled() { +#ifndef WIN32 + int status; + std::unique_ptr result(abi::__cxa_demangle(name, 0, 0, &status), std::free); + if(status != 0) + return "error: " + to_string(status); + + this->mangled = result.get(); +#else + //FIXME Implement! + this->mangled = this->name; +#endif + return this->mangled; + } + }; + class entry { + public: + /* std::string name; */ + size_t type; + void* address = nullptr; + + entry() {} + entry(size_t type, void* address) : type(type), address(address) {} + ~entry() {} + }; + + template + class brick { + public: + inline bool insert(size_t type, void* address) { + auto slot = free_slot(); + if(slot == N) return false; + entries[slot] = entry{type, address}; + findex = slot + 1; + return true; + } + + inline bool remove(size_t type, void* address) { + for(int index = 0; index < N; index++) { + auto& e = entries[index]; + if(e.address == address && e.type == type) { + e = entry{}; + findex = index; + return true; + } + } + return false; + } + + inline int capacity() { return N; } + + array entries; + private: + inline int free_slot() { + while (findex < N && entries[findex].address) findex++; + return findex; + } + int findex = 0; + }; + typedef brick<1024> InfoBrick; + + template + struct DefaultValued { + T value = N; + }; + + + map> type_indexes; + vector bricks; + mutex bricks_lock; + + void allocated(const char* name, void* address) { +#ifdef NO_IMPL + return; +#else +#ifdef MEMTRACK_VERBOSE + logTrace(lstream << "[MEMORY] Allocated a new instance of '" << name << "' at " << address); +#endif + if(!should_track_mangled(name)) return; + + lock_guard lock(bricks_lock); + TypeInfo local_info(name); + auto& type_index = type_indexes[local_info]; + if(type_index.value == -1) { + type_index.value = type_indexes.size() - 1; + } + + auto _value = (size_t) type_index.value; + for(auto it = bricks.begin(); it != bricks.end(); it++) + if((*it)->insert(_value, address)) return; + bricks.push_back(new InfoBrick{}); + auto success = bricks.back()->insert(type_index.value, address); + assert(success); +#endif + } + + void freed(const char* name, void* address) { +#ifdef NO_IMPL + return; +#else +#ifdef MEMTRACK_VERBOSE + logTrace(lstream << "[MEMORY] Deallocated a instance of '" << name << "' at " << address); +#endif + if(!should_track_mangled(name)) return; + + lock_guard lock(bricks_lock); + TypeInfo local_info(name); + auto& type_index = type_indexes[local_info]; + if(type_index.value == -1) + type_index.value = type_indexes.size() - 1; + + auto _value = (size_t) type_index.value; + for (auto &brick : bricks) + if(brick->remove(_value, address)) return; + logError(lstream << "[MEMORY] Got deallocated notify, but never the allocated! (Address: " << address << " Name: " << name << ")"); +#endif + } + + void statistics() { +#ifdef NO_IMPL + logError("memtracker::statistics() does not work due compiler flags (NO_IMPL)"); + return; +#else + map> objects; + map mapping; + + { + lock_guard lock(bricks_lock); + for(auto& brick : bricks) + for(auto& entry : brick->entries) + if(entry.address) { + objects[entry.type].push_back(entry.address); + } + for(auto& type : type_indexes) + mapping[type.second.value] = type.first.as_mangled(); + } + + logMessage("Allocated object types: " + to_string(objects.size())); + for(const auto& entry : objects) { + logMessage(" " + mapping[entry.first] + ": " + to_string(entry.second.size())); + if (entry.second.size() < 50) { + stringstream ss; + for (int index = 0; index < entry.second.size(); index++) { + if (index % 16 == 0) { + if (index + 1 >= entry.second.size()) break; + if (index != 0) + logMessage(ss.str()); + ss = stringstream(); + ss << " "; + } + ss << entry.second[index] << " "; + } + if (!ss.str().empty()) + logMessage(ss.str()); + } else { + logMessage(""); + } + } +#endif + } +} \ No newline at end of file diff --git a/src/misc/memtracker.h b/src/misc/memtracker.h new file mode 100644 index 0000000..fe28869 --- /dev/null +++ b/src/misc/memtracker.h @@ -0,0 +1,30 @@ +#pragma once + +#include +#include + +namespace memtrack { +#define TRACK_OBJECT_ALLOCATION +#ifdef TRACK_OBJECT_ALLOCATION + extern void allocated(const char* name, void* address); + extern void freed(const char* name, void* address); + template + void allocated(void* address) { allocated(typeid(T).name(), address); } + + template + void freed(void* address) { freed(typeid(T).name(), address); } + + void statistics(); +#else + template + inline void __empty(...) { } + + #define freed __empty + #define allocated __empty + + #define allocated_mangled __empty + #define freed_mangled __empty + + inline void statistics() {} +#endif +} \ No newline at end of file diff --git a/src/misc/net.h b/src/misc/net.h new file mode 100644 index 0000000..bb9ef93 --- /dev/null +++ b/src/misc/net.h @@ -0,0 +1,200 @@ +#pragma once + +#include +#include + +#ifdef WIN32 + #include + #include + #include + #include +#else + #include + #include + #include + #include +#endif + +namespace net { + inline std::string to_string(const in6_addr& address) { + char buffer[INET6_ADDRSTRLEN]; + if(!inet_ntop(AF_INET6, (void*) &address, buffer, INET6_ADDRSTRLEN)) return ""; + return std::string(buffer); + } + + inline std::string to_string(const in_addr& address) { + char buffer[INET_ADDRSTRLEN]; + if(!inet_ntop(AF_INET, (void*) &address, buffer, INET_ADDRSTRLEN)) return ""; + return std::string(buffer); + } + + inline std::string to_string(const sockaddr_storage& address, bool port = true) { + switch(address.ss_family) { + case AF_INET: + return to_string(((sockaddr_in*) &address)->sin_addr) + (port ? ":" + std::to_string(htons(((sockaddr_in*) &address)->sin_port)) : ""); + case AF_INET6: + return to_string(((sockaddr_in6*) &address)->sin6_addr) + (port ? ":" + std::to_string(htons(((sockaddr_in6*) &address)->sin6_port)) : ""); + default: + return "unknown_type"; + } + } + + inline uint16_t port(const sockaddr_storage& address) { + switch(address.ss_family) { + case AF_INET: + return htons(((sockaddr_in*) &address)->sin_port); + case AF_INET6: + return htons(((sockaddr_in6*) &address)->sin6_port); + default: + return 0; + } + } + + inline socklen_t address_size(const sockaddr_storage& address) { + switch (address.ss_family) { + case AF_INET: return sizeof(sockaddr_in); + case AF_INET6: return sizeof(sockaddr_in6); + default: return 0; + } + } + + inline bool address_equal(const sockaddr_storage& a, const sockaddr_storage& b) { + if(a.ss_family != b.ss_family) return false; + if(a.ss_family == AF_INET) return ((sockaddr_in*) &a)->sin_addr.s_addr == ((sockaddr_in*) &b)->sin_addr.s_addr; + else if(a.ss_family == AF_INET6) { +#ifdef WIN32 + return memcmp(((sockaddr_in6*) &a)->sin6_addr.u.Byte, ((sockaddr_in6*) &b)->sin6_addr.u.Byte, 16) == 0; +#else + return memcmp(((sockaddr_in6*) &a)->sin6_addr.__in6_u.__u6_addr8, ((sockaddr_in6*) &b)->sin6_addr.__in6_u.__u6_addr8, 16) == 0; +#endif + } + return false; + } + + inline bool address_equal_ranged(const sockaddr_storage& a, const sockaddr_storage& b, uint8_t range) { + if(a.ss_family != b.ss_family) return false; + if(a.ss_family == AF_INET) { + auto address_a = ((sockaddr_in*) &a)->sin_addr.s_addr; + auto address_b = ((sockaddr_in*) &b)->sin_addr.s_addr; + + if(range > 32) + range = 32; + + range = (uint8_t) (32 - range); + + address_a <<= range; + address_b <<= range; + + return address_a == address_b; + } else if(a.ss_family == AF_INET6) { +#ifdef WIN32 + throw std::runtime_error("not implemented"); + //FIXME: Implement me! +#elif defined(__x86_64__) && false + static_assert(sizeof(__int128) == 16); + auto address_a = (__int128) ((sockaddr_in6*) &a)->sin6_addr.__in6_u.__u6_addr32; + auto address_b = (__int128) ((sockaddr_in6*) &b)->sin6_addr.__in6_u.__u6_addr32; + + if(range > 128) + range = 128; + range = (uint8_t) (128 - range); + + address_a <<= range; + address_b <<= range; + + return address_a == address_b; +#else + static_assert(sizeof(uint64_t) == 8); + + if(range > 128) + range = 128; + range = (uint8_t) (128 - range); + + + auto address_ah = (uint64_t) (((sockaddr_in6*) &a)->sin6_addr.__in6_u.__u6_addr8 + 0); + auto address_al = (uint64_t) (((sockaddr_in6*) &a)->sin6_addr.__in6_u.__u6_addr8 + 8); + auto address_bh = (uint64_t) (((sockaddr_in6*) &b)->sin6_addr.__in6_u.__u6_addr8 + 0); + auto address_bl = (uint64_t) (((sockaddr_in6*) &b)->sin6_addr.__in6_u.__u6_addr8 + 8); + + if(range > 64) { + /* only lower counts */ + return (address_al << (range - 64)) == (address_bl << (range - 64)); + } else { + return address_al == address_bl &&(address_bh << (range - 64)) == (address_ah << (range - 64)); + } +#endif + } + return false; + } + + + inline bool is_ipv6(const std::string& str) { + sockaddr_in6 sa{}; + return inet_pton(AF_INET6, str.c_str(), &(sa.sin6_addr)) != 0; + } + + inline bool is_ipv4(const std::string& str) { + sockaddr_in sa{}; + return inet_pton(AF_INET, str.c_str(), &(sa.sin_addr)) != 0; + } + + inline bool is_anybind(sockaddr_storage& storage) { + if(storage.ss_family == AF_INET) { + auto data = (sockaddr_in*) &storage; + return data->sin_addr.s_addr == 0; + } else if(storage.ss_family == AF_INET6) { + auto data = (sockaddr_in6*) &storage; +#ifdef WIN32 + auto& blocks = data->sin6_addr.u.Word; + return + blocks[0] == 0 && + blocks[1] == 0 && + blocks[2] == 0 && + blocks[3] == 0 && + blocks[4] == 0 && + blocks[5] == 0 && + blocks[6] == 0 && + blocks[7] == 0; +#else + auto& blocks = data->sin6_addr.__in6_u.__u6_addr32; + return blocks[0] == 0 && blocks[1] == 0 && blocks[2] == 0 && blocks[3] == 0; +#endif + } + return false; + } + + inline bool resolve_address(const std::string& address, sockaddr_storage& result) { + if(is_ipv4(address)) { + sockaddr_in s{}; + s.sin_port = 0; + s.sin_family = AF_INET; + + auto record = gethostbyname(address.c_str()); + if(!record) + return false; + s.sin_addr.s_addr = ((in_addr*) record->h_addr)->s_addr; + + memcpy(&result, &s, sizeof(s)); + return true; + } else if(is_ipv6(address)) { + sockaddr_in6 s{}; + s.sin6_family = AF_INET6; + s.sin6_port = 0; + s.sin6_flowinfo = 0; + s.sin6_scope_id = 0; + +#ifdef WIN32 + auto record = gethostbyname(address.c_str()); +#else + auto record = gethostbyname2(address.c_str(), AF_INET6); +#endif + if(!record) return false; + s.sin6_addr = *(in6_addr*) record->h_addr; + + memcpy(&result, &s, sizeof(s)); + return true; + } + + return false; + } +} \ No newline at end of file diff --git a/src/misc/queue.h b/src/misc/queue.h new file mode 100644 index 0000000..fe7f8da --- /dev/null +++ b/src/misc/queue.h @@ -0,0 +1,527 @@ +/* $OpenBSD: queue.h,v 1.31 2005/11/25 08:06:25 otto Exp $ */ +/* $NetBSD: queue.h,v 1.11 1996/05/16 05:17:14 mycroft Exp $ */ + +/* + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)queue.h 8.5 (Berkeley) 8/20/94 + */ + +#ifndef _SYS_QUEUE_H_ +#define _SYS_QUEUE_H_ + +/* + * This file defines five types of data structures: singly-linked lists, + * lists, simple queues, tail queues, and circular queues. + * + * + * A singly-linked list is headed by a single forward pointer. The elements + * are singly linked for minimum space and pointer manipulation overhead at + * the expense of O(n) removal for arbitrary elements. New elements can be + * added to the list after an existing element or at the head of the list. + * Elements being removed from the head of the list should use the explicit + * macro for this purpose for optimum efficiency. A singly-linked list may + * only be traversed in the forward direction. Singly-linked lists are ideal + * for applications with large datasets and few or no removals or for + * implementing a LIFO queue. + * + * A list is headed by a single forward pointer (or an array of forward + * pointers for a hash table header). The elements are doubly linked + * so that an arbitrary element can be removed without a need to + * traverse the list. New elements can be added to the list before + * or after an existing element or at the head of the list. A list + * may only be traversed in the forward direction. + * + * A simple queue is headed by a pair of pointers, one the head of the + * list and the other to the tail of the list. The elements are singly + * linked to save space, so elements can only be removed from the + * head of the list. New elements can be added to the list before or after + * an existing element, at the head of the list, or at the end of the + * list. A simple queue may only be traversed in the forward direction. + * + * A tail queue is headed by a pair of pointers, one to the head of the + * list and the other to the tail of the list. The elements are doubly + * linked so that an arbitrary element can be removed without a need to + * traverse the list. New elements can be added to the list before or + * after an existing element, at the head of the list, or at the end of + * the list. A tail queue may be traversed in either direction. + * + * A circle queue is headed by a pair of pointers, one to the head of the + * list and the other to the tail of the list. The elements are doubly + * linked so that an arbitrary element can be removed without a need to + * traverse the list. New elements can be added to the list before or after + * an existing element, at the head of the list, or at the end of the list. + * A circle queue may be traversed in either direction, but has a more + * complex end of list detection. + * + * For details on the use of these macros, see the queue(3) manual page. + */ + +#ifdef QUEUE_MACRO_DEBUG +#define _Q_INVALIDATE(a) (a) = ((void *)-1) +#else +#define _Q_INVALIDATE(a) +#endif + +/* + * Singly-linked List definitions. + */ +#define SLIST_HEAD(name, type) \ +struct name { \ + struct type *slh_first; /* first element */ \ +} + +#define SLIST_HEAD_INITIALIZER(head) \ + { NULL } + +#define SLIST_ENTRY(type) \ +struct { \ + struct type *sle_next; /* next element */ \ +} + +/* + * Singly-linked List access methods. + */ +#define SLIST_FIRST(head) ((head)->slh_first) +#define SLIST_END(head) NULL +#define SLIST_EMPTY(head) (SLIST_FIRST(head) == SLIST_END(head)) +#define SLIST_NEXT(elm, field) ((elm)->field.sle_next) + +#define SLIST_FOREACH(var, head, field) \ + for((var) = SLIST_FIRST(head); \ + (var) != SLIST_END(head); \ + (var) = SLIST_NEXT(var, field)) + +#define SLIST_FOREACH_PREVPTR(var, varp, head, field) \ + for ((varp) = &SLIST_FIRST((head)); \ + ((var) = *(varp)) != SLIST_END(head); \ + (varp) = &SLIST_NEXT((var), field)) + +/* + * Singly-linked List functions. + */ +#define SLIST_INIT(head) { \ + SLIST_FIRST(head) = SLIST_END(head); \ +} + +#define SLIST_INSERT_AFTER(slistelm, elm, field) do { \ + (elm)->field.sle_next = (slistelm)->field.sle_next; \ + (slistelm)->field.sle_next = (elm); \ +} while (0) + +#define SLIST_INSERT_HEAD(head, elm, field) do { \ + (elm)->field.sle_next = (head)->slh_first; \ + (head)->slh_first = (elm); \ +} while (0) + +#define SLIST_REMOVE_NEXT(head, elm, field) do { \ + (elm)->field.sle_next = (elm)->field.sle_next->field.sle_next; \ +} while (0) + +#define SLIST_REMOVE_HEAD(head, field) do { \ + (head)->slh_first = (head)->slh_first->field.sle_next; \ +} while (0) + +#define SLIST_REMOVE(head, elm, type, field) do { \ + if ((head)->slh_first == (elm)) { \ + SLIST_REMOVE_HEAD((head), field); \ + } else { \ + struct type *curelm = (head)->slh_first; \ + \ + while (curelm->field.sle_next != (elm)) \ + curelm = curelm->field.sle_next; \ + curelm->field.sle_next = \ + curelm->field.sle_next->field.sle_next; \ + _Q_INVALIDATE((elm)->field.sle_next); \ + } \ +} while (0) + +/* + * List definitions. + */ +#define LIST_HEAD(name, type) \ +struct name { \ + struct type *lh_first; /* first element */ \ +} + +#define LIST_HEAD_INITIALIZER(head) \ + { NULL } + +#define LIST_ENTRY(type) \ +struct { \ + struct type *le_next; /* next element */ \ + struct type **le_prev; /* address of previous next element */ \ +} + +/* + * List access methods + */ +#define LIST_FIRST(head) ((head)->lh_first) +#define LIST_END(head) NULL +#define LIST_EMPTY(head) (LIST_FIRST(head) == LIST_END(head)) +#define LIST_NEXT(elm, field) ((elm)->field.le_next) + +#define LIST_FOREACH(var, head, field) \ + for((var) = LIST_FIRST(head); \ + (var)!= LIST_END(head); \ + (var) = LIST_NEXT(var, field)) + +/* + * List functions. + */ +#define LIST_INIT(head) do { \ + LIST_FIRST(head) = LIST_END(head); \ +} while (0) + +#define LIST_INSERT_AFTER(listelm, elm, field) do { \ + if (((elm)->field.le_next = (listelm)->field.le_next) != NULL) \ + (listelm)->field.le_next->field.le_prev = \ + &(elm)->field.le_next; \ + (listelm)->field.le_next = (elm); \ + (elm)->field.le_prev = &(listelm)->field.le_next; \ +} while (0) + +#define LIST_INSERT_BEFORE(listelm, elm, field) do { \ + (elm)->field.le_prev = (listelm)->field.le_prev; \ + (elm)->field.le_next = (listelm); \ + *(listelm)->field.le_prev = (elm); \ + (listelm)->field.le_prev = &(elm)->field.le_next; \ +} while (0) + +#define LIST_INSERT_HEAD(head, elm, field) do { \ + if (((elm)->field.le_next = (head)->lh_first) != NULL) \ + (head)->lh_first->field.le_prev = &(elm)->field.le_next;\ + (head)->lh_first = (elm); \ + (elm)->field.le_prev = &(head)->lh_first; \ +} while (0) + +#define LIST_REMOVE(elm, field) do { \ + if ((elm)->field.le_next != NULL) \ + (elm)->field.le_next->field.le_prev = \ + (elm)->field.le_prev; \ + *(elm)->field.le_prev = (elm)->field.le_next; \ + _Q_INVALIDATE((elm)->field.le_prev); \ + _Q_INVALIDATE((elm)->field.le_next); \ +} while (0) + +#define LIST_REPLACE(elm, elm2, field) do { \ + if (((elm2)->field.le_next = (elm)->field.le_next) != NULL) \ + (elm2)->field.le_next->field.le_prev = \ + &(elm2)->field.le_next; \ + (elm2)->field.le_prev = (elm)->field.le_prev; \ + *(elm2)->field.le_prev = (elm2); \ + _Q_INVALIDATE((elm)->field.le_prev); \ + _Q_INVALIDATE((elm)->field.le_next); \ +} while (0) + +/* + * Simple queue definitions. + */ +#define SIMPLEQ_HEAD(name, type) \ +struct name { \ + struct type *sqh_first; /* first element */ \ + struct type **sqh_last; /* addr of last next element */ \ +} + +#define SIMPLEQ_HEAD_INITIALIZER(head) \ + { NULL, &(head).sqh_first } + +#define SIMPLEQ_ENTRY(type) \ +struct { \ + struct type *sqe_next; /* next element */ \ +} + +/* + * Simple queue access methods. + */ +#define SIMPLEQ_FIRST(head) ((head)->sqh_first) +#define SIMPLEQ_END(head) NULL +#define SIMPLEQ_EMPTY(head) (SIMPLEQ_FIRST(head) == SIMPLEQ_END(head)) +#define SIMPLEQ_NEXT(elm, field) ((elm)->field.sqe_next) + +#define SIMPLEQ_FOREACH(var, head, field) \ + for((var) = SIMPLEQ_FIRST(head); \ + (var) != SIMPLEQ_END(head); \ + (var) = SIMPLEQ_NEXT(var, field)) + +/* + * Simple queue functions. + */ +#define SIMPLEQ_INIT(head) do { \ + (head)->sqh_first = NULL; \ + (head)->sqh_last = &(head)->sqh_first; \ +} while (0) + +#define SIMPLEQ_INSERT_HEAD(head, elm, field) do { \ + if (((elm)->field.sqe_next = (head)->sqh_first) == NULL) \ + (head)->sqh_last = &(elm)->field.sqe_next; \ + (head)->sqh_first = (elm); \ +} while (0) + +#define SIMPLEQ_INSERT_TAIL(head, elm, field) do { \ + (elm)->field.sqe_next = NULL; \ + *(head)->sqh_last = (elm); \ + (head)->sqh_last = &(elm)->field.sqe_next; \ +} while (0) + +#define SIMPLEQ_INSERT_AFTER(head, listelm, elm, field) do { \ + if (((elm)->field.sqe_next = (listelm)->field.sqe_next) == NULL)\ + (head)->sqh_last = &(elm)->field.sqe_next; \ + (listelm)->field.sqe_next = (elm); \ +} while (0) + +#define SIMPLEQ_REMOVE_HEAD(head, field) do { \ + if (((head)->sqh_first = (head)->sqh_first->field.sqe_next) == NULL) \ + (head)->sqh_last = &(head)->sqh_first; \ +} while (0) + +/* + * Tail queue definitions. + */ +#define TAILQ_HEAD(name, type) \ +struct name { \ + struct type *tqh_first; /* first element */ \ + struct type **tqh_last; /* addr of last next element */ \ +} + +#define TAILQ_HEAD_INITIALIZER(head) \ + { NULL, &(head).tqh_first } + +#define TAILQ_ENTRY(type) \ +struct { \ + struct type *tqe_next; /* next element */ \ + struct type **tqe_prev; /* address of previous next element */ \ +} + +/* + * tail queue access methods + */ +#define TAILQ_FIRST(head) ((head)->tqh_first) +#define TAILQ_END(head) NULL +#define TAILQ_NEXT(elm, field) ((elm)->field.tqe_next) +#define TAILQ_LAST(head, headname) \ + (*(((struct headname *)((head)->tqh_last))->tqh_last)) +/* XXX */ +#define TAILQ_PREV(elm, headname, field) \ + (*(((struct headname *)((elm)->field.tqe_prev))->tqh_last)) +#define TAILQ_EMPTY(head) \ + (TAILQ_FIRST(head) == TAILQ_END(head)) + +#define TAILQ_FOREACH(var, head, field) \ + for((var) = TAILQ_FIRST(head); \ + (var) != TAILQ_END(head); \ + (var) = TAILQ_NEXT(var, field)) + +#define TAILQ_FOREACH_REVERSE(var, head, headname, field) \ + for((var) = TAILQ_LAST(head, headname); \ + (var) != TAILQ_END(head); \ + (var) = TAILQ_PREV(var, headname, field)) + +/* + * Tail queue functions. + */ +#define TAILQ_INIT(head) do { \ + (head)->tqh_first = NULL; \ + (head)->tqh_last = &(head)->tqh_first; \ +} while (0) + +#define TAILQ_INSERT_HEAD(head, elm, field) do { \ + if (((elm)->field.tqe_next = (head)->tqh_first) != NULL) \ + (head)->tqh_first->field.tqe_prev = \ + &(elm)->field.tqe_next; \ + else \ + (head)->tqh_last = &(elm)->field.tqe_next; \ + (head)->tqh_first = (elm); \ + (elm)->field.tqe_prev = &(head)->tqh_first; \ +} while (0) + +#define TAILQ_INSERT_TAIL(head, elm, field) do { \ + (elm)->field.tqe_next = NULL; \ + (elm)->field.tqe_prev = (head)->tqh_last; \ + *(head)->tqh_last = (elm); \ + (head)->tqh_last = &(elm)->field.tqe_next; \ +} while (0) + +#define TAILQ_INSERT_AFTER(head, listelm, elm, field) do { \ + if (((elm)->field.tqe_next = (listelm)->field.tqe_next) != NULL)\ + (elm)->field.tqe_next->field.tqe_prev = \ + &(elm)->field.tqe_next; \ + else \ + (head)->tqh_last = &(elm)->field.tqe_next; \ + (listelm)->field.tqe_next = (elm); \ + (elm)->field.tqe_prev = &(listelm)->field.tqe_next; \ +} while (0) + +#define TAILQ_INSERT_BEFORE(listelm, elm, field) do { \ + (elm)->field.tqe_prev = (listelm)->field.tqe_prev; \ + (elm)->field.tqe_next = (listelm); \ + *(listelm)->field.tqe_prev = (elm); \ + (listelm)->field.tqe_prev = &(elm)->field.tqe_next; \ +} while (0) + +#define TAILQ_REMOVE(head, elm, field) do { \ + if (((elm)->field.tqe_next) != NULL) \ + (elm)->field.tqe_next->field.tqe_prev = \ + (elm)->field.tqe_prev; \ + else \ + (head)->tqh_last = (elm)->field.tqe_prev; \ + *(elm)->field.tqe_prev = (elm)->field.tqe_next; \ + _Q_INVALIDATE((elm)->field.tqe_prev); \ + _Q_INVALIDATE((elm)->field.tqe_next); \ +} while (0) + +#define TAILQ_REPLACE(head, elm, elm2, field) do { \ + if (((elm2)->field.tqe_next = (elm)->field.tqe_next) != NULL) \ + (elm2)->field.tqe_next->field.tqe_prev = \ + &(elm2)->field.tqe_next; \ + else \ + (head)->tqh_last = &(elm2)->field.tqe_next; \ + (elm2)->field.tqe_prev = (elm)->field.tqe_prev; \ + *(elm2)->field.tqe_prev = (elm2); \ + _Q_INVALIDATE((elm)->field.tqe_prev); \ + _Q_INVALIDATE((elm)->field.tqe_next); \ +} while (0) + +/* + * Circular queue definitions. + */ +#define CIRCLEQ_HEAD(name, type) \ +struct name { \ + struct type *cqh_first; /* first element */ \ + struct type *cqh_last; /* last element */ \ +} + +#define CIRCLEQ_HEAD_INITIALIZER(head) \ + { CIRCLEQ_END(&head), CIRCLEQ_END(&head) } + +#define CIRCLEQ_ENTRY(type) \ +struct { \ + struct type *cqe_next; /* next element */ \ + struct type *cqe_prev; /* previous element */ \ +} + +/* + * Circular queue access methods + */ +#define CIRCLEQ_FIRST(head) ((head)->cqh_first) +#define CIRCLEQ_LAST(head) ((head)->cqh_last) +#define CIRCLEQ_END(head) ((void *)(head)) +#define CIRCLEQ_NEXT(elm, field) ((elm)->field.cqe_next) +#define CIRCLEQ_PREV(elm, field) ((elm)->field.cqe_prev) +#define CIRCLEQ_EMPTY(head) \ + (CIRCLEQ_FIRST(head) == CIRCLEQ_END(head)) + +#define CIRCLEQ_FOREACH(var, head, field) \ + for((var) = CIRCLEQ_FIRST(head); \ + (var) != CIRCLEQ_END(head); \ + (var) = CIRCLEQ_NEXT(var, field)) + +#define CIRCLEQ_FOREACH_REVERSE(var, head, field) \ + for((var) = CIRCLEQ_LAST(head); \ + (var) != CIRCLEQ_END(head); \ + (var) = CIRCLEQ_PREV(var, field)) + +/* + * Circular queue functions. + */ +#define CIRCLEQ_INIT(head) do { \ + (head)->cqh_first = CIRCLEQ_END(head); \ + (head)->cqh_last = CIRCLEQ_END(head); \ +} while (0) + +#define CIRCLEQ_INSERT_AFTER(head, listelm, elm, field) do { \ + (elm)->field.cqe_next = (listelm)->field.cqe_next; \ + (elm)->field.cqe_prev = (listelm); \ + if ((listelm)->field.cqe_next == CIRCLEQ_END(head)) \ + (head)->cqh_last = (elm); \ + else \ + (listelm)->field.cqe_next->field.cqe_prev = (elm); \ + (listelm)->field.cqe_next = (elm); \ +} while (0) + +#define CIRCLEQ_INSERT_BEFORE(head, listelm, elm, field) do { \ + (elm)->field.cqe_next = (listelm); \ + (elm)->field.cqe_prev = (listelm)->field.cqe_prev; \ + if ((listelm)->field.cqe_prev == CIRCLEQ_END(head)) \ + (head)->cqh_first = (elm); \ + else \ + (listelm)->field.cqe_prev->field.cqe_next = (elm); \ + (listelm)->field.cqe_prev = (elm); \ +} while (0) + +#define CIRCLEQ_INSERT_HEAD(head, elm, field) do { \ + (elm)->field.cqe_next = (head)->cqh_first; \ + (elm)->field.cqe_prev = CIRCLEQ_END(head); \ + if ((head)->cqh_last == CIRCLEQ_END(head)) \ + (head)->cqh_last = (elm); \ + else \ + (head)->cqh_first->field.cqe_prev = (elm); \ + (head)->cqh_first = (elm); \ +} while (0) + +#define CIRCLEQ_INSERT_TAIL(head, elm, field) do { \ + (elm)->field.cqe_next = CIRCLEQ_END(head); \ + (elm)->field.cqe_prev = (head)->cqh_last; \ + if ((head)->cqh_first == CIRCLEQ_END(head)) \ + (head)->cqh_first = (elm); \ + else \ + (head)->cqh_last->field.cqe_next = (elm); \ + (head)->cqh_last = (elm); \ +} while (0) + +#define CIRCLEQ_REMOVE(head, elm, field) do { \ + if ((elm)->field.cqe_next == CIRCLEQ_END(head)) \ + (head)->cqh_last = (elm)->field.cqe_prev; \ + else \ + (elm)->field.cqe_next->field.cqe_prev = \ + (elm)->field.cqe_prev; \ + if ((elm)->field.cqe_prev == CIRCLEQ_END(head)) \ + (head)->cqh_first = (elm)->field.cqe_next; \ + else \ + (elm)->field.cqe_prev->field.cqe_next = \ + (elm)->field.cqe_next; \ + _Q_INVALIDATE((elm)->field.cqe_prev); \ + _Q_INVALIDATE((elm)->field.cqe_next); \ +} while (0) + +#define CIRCLEQ_REPLACE(head, elm, elm2, field) do { \ + if (((elm2)->field.cqe_next = (elm)->field.cqe_next) == \ + CIRCLEQ_END(head)) \ + (head).cqh_last = (elm2); \ + else \ + (elm2)->field.cqe_next->field.cqe_prev = (elm2); \ + if (((elm2)->field.cqe_prev = (elm)->field.cqe_prev) == \ + CIRCLEQ_END(head)) \ + (head).cqh_first = (elm2); \ + else \ + (elm2)->field.cqe_prev->field.cqe_next = (elm2); \ + _Q_INVALIDATE((elm)->field.cqe_prev); \ + _Q_INVALIDATE((elm)->field.cqe_next); \ +} while (0) + +#endif /* !_SYS_QUEUE_H_ */ \ No newline at end of file diff --git a/src/misc/rnd.cpp b/src/misc/rnd.cpp new file mode 100644 index 0000000..f463d9f --- /dev/null +++ b/src/misc/rnd.cpp @@ -0,0 +1,18 @@ +#include "rnd.h" + +const char* rnd_string_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; +std::string rnd_string(int length, const char* source) { + char* buffer = new char[length]; + auto source_length = strlen(source); + + std::default_random_engine generator{0}; + generator.seed(static_cast(std::chrono::system_clock::now().time_since_epoch().count())); + std::uniform_int_distribution gen(0, static_cast(source_length - 1)); + for(int i = 0; i < length; i++){ + buffer[i] = source[gen(generator)]; + } + + auto result = std::string(buffer, static_cast(length)); + delete[] buffer; + return result; +} \ No newline at end of file diff --git a/src/misc/rnd.h b/src/misc/rnd.h new file mode 100644 index 0000000..169c4ee --- /dev/null +++ b/src/misc/rnd.h @@ -0,0 +1,8 @@ +#pragma once + +#include +#include +#include + +extern const char* rnd_string_chars; //"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" +extern std::string rnd_string(int length = 20, const char* avariable = rnd_string_chars); \ No newline at end of file diff --git a/src/misc/sassert.h b/src/misc/sassert.h new file mode 100644 index 0000000..e291966 --- /dev/null +++ b/src/misc/sassert.h @@ -0,0 +1,15 @@ +#pragma once + +#include + +//#define ALLOW_ASSERT +#ifdef ALLOW_ASSERT + #define sassert(exp) assert(exp) +#else + #define S(s) #s + #define sassert(exp) \ + do { \ + if(!(exp)) \ + logCritical(0, "Soft assertion @{}:{} '{}' failed! This could cause fatal fails!", __FILE__, __LINE__, #exp); \ + } while(0) +#endif \ No newline at end of file diff --git a/src/misc/spin_lock.h b/src/misc/spin_lock.h new file mode 100644 index 0000000..6f0c3f1 --- /dev/null +++ b/src/misc/spin_lock.h @@ -0,0 +1,25 @@ +#pragma once + +#include +#include + +class spin_lock { + std::atomic_flag locked = ATOMIC_FLAG_INIT; + public: + void lock() { + uint8_t round = 0; + while (locked.test_and_set(std::memory_order_acquire)) { + //Yield when we're using this lock for a longer time, which we usually not doing + if(round++ % 8 == 0) + std::this_thread::yield(); + } + } + + inline bool try_lock() { + return !locked.test_and_set(std::memory_order_acquire); + } + + void unlock() { + locked.clear(std::memory_order_release); + } +}; \ No newline at end of file diff --git a/src/misc/std_unique_ptr.h b/src/misc/std_unique_ptr.h new file mode 100644 index 0000000..1a51044 --- /dev/null +++ b/src/misc/std_unique_ptr.h @@ -0,0 +1,38 @@ +#pragma once +#include +#include +#include +#include + +#if __cplusplus <= 201103L +namespace std { + template struct _Unique_if { + typedef unique_ptr _Single_object; + }; + + template struct _Unique_if { + typedef unique_ptr _Unknown_bound; + }; + + template struct _Unique_if { + typedef void _Known_bound; + }; + + template + typename _Unique_if::_Single_object + make_unique(Args&&... args) { + return unique_ptr(new T(std::forward(args)...)); + } + + template + typename _Unique_if::_Unknown_bound + make_unique(size_t n) { + typedef typename remove_extent::type U; + return unique_ptr(new U[n]()); + } + + template + typename _Unique_if::_Known_bound + make_unique(Args&&...) = delete; +} +#endif \ No newline at end of file diff --git a/src/misc/time.cpp b/src/misc/time.cpp new file mode 100644 index 0000000..24d80d2 --- /dev/null +++ b/src/misc/time.cpp @@ -0,0 +1,59 @@ +#include +#include +#include "time.h" + +using namespace std; +using namespace chrono; + +struct TimeEntry { + std::string indice; + std::function parser; +}; + +auto parsers = std::vector({ + {"h", [](const std::string& number) -> nanoseconds { return hours(stoll(number)); }}, + {"m", [](const std::string& number) -> nanoseconds { return minutes(stoll(number)); }}, + {"s", [](const std::string& number) -> nanoseconds { return seconds(stoll(number)); }}, + {"ms", [](const std::string& number) -> nanoseconds { return milliseconds(stoll(number)); }}, + {"us", [](const std::string& number) -> nanoseconds { return microseconds(stoll(number)); }}, + {"ns", [](const std::string& number) -> nanoseconds { return nanoseconds(stoll(number)); }} +}); + +std::chrono::nanoseconds period::parse(const std::string& input, std::string& error) { + nanoseconds result{}; + + size_t index = 0; + do { + auto found = input.find(':', index); + auto str = input.substr(index, found - index); + + auto indiceIndex = str.find_first_not_of("0123456789"); + if(indiceIndex == std::string::npos) { + error = "Missing indice for " + str + " at " + to_string(index); + return nanoseconds(0); + } + auto indice = str.substr(indiceIndex); + auto number = str.substr(0, indiceIndex); + + bool foundIndice = false; + for(const auto& parser : parsers) { + if(parser.indice == indice) { + if(number.length() == 0) { + error = "Invalid number at " + to_string(index); + return nanoseconds(0); + } + result += parser.parser(number); + foundIndice = true; + break; + } + } + if(!foundIndice) { + error = "Invalid indice for " + str + " at " + to_string(index + indiceIndex); + return nanoseconds(0); + } + + index = found + 1; + } while(index != 0); + + return result; +} \ No newline at end of file diff --git a/src/misc/time.h b/src/misc/time.h new file mode 100644 index 0000000..16076df --- /dev/null +++ b/src/misc/time.h @@ -0,0 +1,9 @@ +#pragma once + +#include +#include +#include + +namespace period { + std::chrono::nanoseconds parse(const std::string&, std::string&); +} \ No newline at end of file diff --git a/src/misc/timer.h b/src/misc/timer.h new file mode 100644 index 0000000..334f4a3 --- /dev/null +++ b/src/misc/timer.h @@ -0,0 +1,36 @@ +#pragma once + +#define TIMING_START(_name) \ +struct { \ + struct entry { \ + std::string name; \ + std::chrono::system_clock::time_point ts; \ + }; \ + \ + std::string name; \ + std::chrono::system_clock::time_point begin; \ + std::chrono::system_clock::time_point end; \ + std::deque timings; \ +} _name ##_timings; \ +_name ##_timings.begin = std::chrono::system_clock::now(); \ + +#define TIMING_STEP(name, step) \ +name ##_timings.timings.push_back({step, std::chrono::system_clock::now()}); + +#define TIMING_FINISH(_name) \ +([&](){ \ + _name ##_timings.end = std::chrono::system_clock::now(); \ + std::string result; \ + result = "timings for " + _name ##_timings.name + ": "; \ + result += std::to_string(std::chrono::duration_cast(_name ##_timings.end - _name ##_timings.begin).count()) + "ms"; \ + \ + auto tp = _name ##_timings.begin; \ + for(const auto& entry : _name ##_timings.timings) { \ + result += "\n "; \ + result += "- " + entry.name + ": "; \ + result += "@" + std::to_string(std::chrono::duration_cast(entry.ts - _name ##_timings.begin).count()) + "ms"; \ + result += ": " + std::to_string(std::chrono::duration_cast(entry.ts - tp).count()) + "ms"; \ + tp = entry.ts; \ + } \ + return result; \ +})() \ No newline at end of file diff --git a/src/protocol/AcknowledgeManager.cpp b/src/protocol/AcknowledgeManager.cpp new file mode 100644 index 0000000..b427c81 --- /dev/null +++ b/src/protocol/AcknowledgeManager.cpp @@ -0,0 +1,157 @@ +#include "AcknowledgeManager.h" +#include +#include + +using namespace ts; +using namespace ts::connection; +using namespace ts::protocol; +using namespace std; +using namespace std::chrono; + +AcknowledgeManager::AcknowledgeManager() {} + +AcknowledgeManager::~AcknowledgeManager() { + { + lock_guard lock(this->entry_lock); + for(const auto& entry : this->entries) + if(entry->acknowledge_listener) + entry->acknowledge_listener->executionFailed("deleted"); + this->entries.clear(); + } +} + +void AcknowledgeManager::reset() { + { + lock_guard lock(this->entry_lock); + for(const auto& entry : this->entries) + if(entry->acknowledge_listener) + entry->acknowledge_listener->executionFailed("reset"); + this->entries.clear(); + } +} + +size_t AcknowledgeManager::awaiting_acknowledge() { + lock_guard lock(this->entry_lock); + return this->entries.size(); +} + +void AcknowledgeManager::process_packet(ts::protocol::BasicPacket &packet) { + if(!packet.type().requireAcknowledge()) return; + + auto entry = make_shared(); + entry->acknowledge_listener = std::move(packet.getListener()); + + entry->buffer = packet.buffer(); + + entry->resend_count = 0; + entry->resend_period = milliseconds((int) ceil(this->average_response * 3/2)); + entry->first_send = system_clock::now(); + entry->next_resend = entry->first_send + entry->resend_period; + + entry->packet_type = packet.type().type(); + entry->packet_id = packet.packetId(); + + entry->acknowledged = false; + entry->send_count = 1; + { + lock_guard lock(this->entry_lock); + this->entries.push_front(std::move(entry)); + } +} + +bool AcknowledgeManager::process_acknowledge(const ts::protocol::BasicPacket &packet, std::string& error) { + PacketType target_type = PacketType::UNDEFINED; + uint16_t target_id = 0; + + if(packet.type().type() == PacketType::ACK_LOW) target_type = PacketType::COMMAND_LOW; + else if(packet.type().type() == PacketType::ACK) target_type = PacketType::COMMAND; + target_id = be2le16((char*) packet.data().data_ptr()); + //debugMessage(0, "Got ack for {} {}", target_type, target_id); + + if(target_type == PacketType::UNDEFINED) { + error = "Invalid packet type (" + to_string(target_type) + ")"; + return false; + } + + std::shared_ptr entry; + { + lock_guard lock(this->entry_lock); + for(auto it = this->entries.begin(); it != this->entries.end(); it++) { + if((*it)->packet_type == target_type && (*it)->packet_id == target_id) { + entry = *it; + entry->send_count--; + if(entry->send_count == 0) + this->entries.erase(it); + break; + } + } + } + if(!entry) { + error = "Missing packet id (" + to_string(target_id) + ")"; + return false; + } + + auto time = system_clock::now() - entry->next_resend + entry->resend_period; + auto ms_time = duration_cast(time).count(); + if(ms_time > 5) { + this->average_response = this->average_response / 2 + ms_time / 2; + } + + entry->acknowledged = true; + if(entry->acknowledge_listener) + entry->acknowledge_listener->executionSucceed(true); + entry->acknowledge_listener.reset(); + + return true; +} + +ssize_t AcknowledgeManager::execute_resend(const system_clock::time_point& now , std::chrono::system_clock::time_point &next_resend,std::deque& buffers, string& error) { + ssize_t resend_count = 0; + + deque> need_resend; + { + deque> erase; + + lock_guard lock(this->entry_lock); + for (auto &entry : this->entries) { + if(!entry->acknowledged && entry->next_resend <= now) { + entry->resend_period = entry->resend_period + milliseconds((int) ceil(this->average_response * 2)); + if(entry->resend_period.count() > 1000) + entry->resend_period = milliseconds(1000); + else if(entry->resend_period.count() < 25) + entry->resend_period = milliseconds(25); + + entry->next_resend = now + entry->resend_period; + need_resend.push_front(entry); + } + if(entry->acknowledged) { + if(entry->next_resend + entry->resend_period <= now) { //Timeout for may (more acknowledges) + erase.push_back(entry); + } + } else { + if(next_resend > entry->next_resend) + next_resend = entry->next_resend; + } + } + + for(const auto& e : erase) { + auto it = find(this->entries.begin(), this->entries.end(), e); + if(it != this->entries.end()) + this->entries.erase(it); + } + } + + for(const auto& packet : need_resend) { + if(packet->resend_count > 15 && packet->first_send + seconds(15) < now) { //FIXME configurable + error = "Failed to receive acknowledge for packet " + to_string(packet->packet_id) + " of type " + PacketTypeInfo::fromid(packet->packet_type).name(); + return -1; + } + + resend_count++; + packet->resend_count++; + packet->send_count++; + buffers.push_back(packet->buffer); + } + + return resend_count; +} \ No newline at end of file diff --git a/src/protocol/AcknowledgeManager.h b/src/protocol/AcknowledgeManager.h new file mode 100644 index 0000000..3e87ac6 --- /dev/null +++ b/src/protocol/AcknowledgeManager.h @@ -0,0 +1,51 @@ +#pragma once + +#include +#include + +#define DEBUG_ACKNOWLEDGE +namespace ts { + namespace connection { + class VoiceClientConnection; + class AcknowledgeManager { + struct Entry { + uint16_t packet_id = 0; + uint8_t packet_type = 0xFF; + uint8_t resend_count = 0; + bool acknowledged : 1; + uint8_t send_count : 7; + + + pipes::buffer buffer; + std::chrono::system_clock::time_point first_send; + std::chrono::system_clock::time_point next_resend; + std::chrono::milliseconds resend_period; + + std::unique_ptr> acknowledge_listener; + }; + public: + AcknowledgeManager(); + virtual ~AcknowledgeManager(); + + size_t awaiting_acknowledge(); + void reset(); + + void process_packet(ts::protocol::BasicPacket& /* packet */); + bool process_acknowledge(const ts::protocol::BasicPacket& /* packet */, std::string& /* error */); + + ssize_t execute_resend( + const std::chrono::system_clock::time_point& /* now */, + std::chrono::system_clock::time_point& /* next resend */, + std::deque& /* buffers to resend */, + std::string& /* error */ + ); + private: + std::recursive_mutex entry_lock; + std::deque> entries; + + std::chrono::milliseconds resend_delay{500}; + + double average_response = 20; + }; + } +} \ No newline at end of file diff --git a/src/protocol/CompressionHandler.cpp b/src/protocol/CompressionHandler.cpp new file mode 100644 index 0000000..24f77b0 --- /dev/null +++ b/src/protocol/CompressionHandler.cpp @@ -0,0 +1,81 @@ +#include +#include +#include "CompressionHandler.h" +#define QLZ_COMPRESSION_LEVEL 1 +#define QLZ_MEMORY_SAFE +#include "qlz/QuickLZ.h" +#include "buffers.h" + +using namespace ts; +using namespace ts::connection; +using namespace std; + +bool CompressionHandler::compress(protocol::BasicPacket* packet, std::string &error) { + //// "Always allocate size + 400 bytes for the destination buffer when compressing." <= http://www.quicklz.com/manual.html + auto length = packet->data().length(); + auto header_length = packet->header().length() + packet->mac().length(); + size_t expected_bytes = min(length * 2, length + 400); + auto buffer = buffer::allocate_buffer(expected_bytes + header_length); + + qlz_state_compress state_compress{}; + size_t actualLength = qlz_compress(packet->data().data_ptr(), (char*) &buffer[header_length], packet->data().length(), &state_compress); + if(actualLength > buffer.length()) { + logCritical(0, "Buffer overflow! Compressed data is longer than expected. (Expected: {}, Written: {}, Source length: {}, Allocated block size: {})", + expected_bytes, + actualLength, + packet->data().length(), + buffer.capacity() + ); + error = "overflow"; + return false; + } + if(actualLength <= 0){ + error = "Cloud not compress packet"; + return false; + } + + memcpy(buffer.data_ptr(), packet->buffer().data_ptr(), header_length); + packet->buffer(buffer.range(0, actualLength + header_length)); + return true; +} + +bool CompressionHandler::decompress(protocol::BasicPacket* packet, std::string &error) { + qlz_state_decompress state_decompress{}; + + size_t expected_length = qlz_size_decompressed((char*) packet->data().data_ptr()); + if(expected_length > this->max_packet_size){ //Max 16MB. (97% Compression!) + error = "Invalid packet size. (Calculated target length of " + to_string(expected_length) + ". Max length: " + to_string(this->max_packet_size) + ")"; + return false; + } + + auto header_length = packet->header().length() + packet->mac().length(); + auto buffer = buffer::allocate_buffer(expected_length + header_length); + size_t data_length = qlz_decompress((char*) packet->data().data_ptr(), &buffer[header_length], &state_decompress); + if(data_length <= 0){ + error = "Could not decompress packet."; + return false; + } + + memcpy(buffer.data_ptr(), packet->buffer().data_ptr(), header_length); + packet->buffer(buffer.range(0, data_length + header_length)); + return true; +} + +bool CompressionHandler::progressPacketIn(protocol::BasicPacket* packet, std::string &error) { + if(packet->isCompressed()) { + if(!this->decompress(packet, error)) return false; + packet->setCompressed(false); + } + return true; +} + +bool CompressionHandler::progressPacketOut(protocol::BasicPacket* packet, std::string& error) { + if(packet->hasFlag(protocol::PacketFlag::Compressed) && !packet->isCompressed()) { + if(!this->compress(packet, error)) return false; + packet->setCompressed(true); + } + return true; +} + +CompressionHandler::CompressionHandler() { } +CompressionHandler::~CompressionHandler() { } \ No newline at end of file diff --git a/src/protocol/CompressionHandler.h b/src/protocol/CompressionHandler.h new file mode 100644 index 0000000..a753543 --- /dev/null +++ b/src/protocol/CompressionHandler.h @@ -0,0 +1,21 @@ +#pragma once + +#include "Packet.h" + +namespace ts { + namespace connection { + class CompressionHandler { + public: + CompressionHandler(); + virtual ~CompressionHandler(); + + bool progressPacketOut(protocol::BasicPacket*, std::string&); + bool progressPacketIn(protocol::BasicPacket*, std::string&); + + size_t max_packet_size = 16 * 1024; + private: + bool compress(protocol::BasicPacket*, std::string &error); + bool decompress(protocol::BasicPacket*, std::string &error); + }; + } +} \ No newline at end of file diff --git a/src/protocol/CryptionHandler.cpp b/src/protocol/CryptionHandler.cpp new file mode 100644 index 0000000..c87b9b8 --- /dev/null +++ b/src/protocol/CryptionHandler.cpp @@ -0,0 +1,378 @@ +#define NO_OPEN_SSL /* because we're lazy and dont want to build this lib extra for the TeaClient */ +#define FIXEDINT_H_INCLUDED /* else it will be included by ge */ + +#include "misc/endianness.h" +#include +#include +#include +#include "misc/memtracker.h" +#include "misc/digest.h" +#include "CryptionHandler.h" + +using namespace std; +using namespace ts; +using namespace ts::connection; +using namespace ts::protocol; + + +CryptionHandler::CryptionHandler() { + memtrack::allocated(this); +} + +CryptionHandler::~CryptionHandler() { + memtrack::freed(this); +} + +void CryptionHandler::reset() { + this->useDefaultChipherKeyNonce = true; + this->iv_struct_length = 0; + memset(this->iv_struct, 0, sizeof(this->iv_struct)); + memcpy(this->current_mac, this->default_mac, sizeof(this->default_mac)); + + for(auto& cache : this->cache_key_client) + cache.generation = 0xFFEF; + for(auto& cache : this->cache_key_server) + cache.generation = 0xFFEF; +} + +bool CryptionHandler::setupSharedSecret(const std::string& alpha, const std::string& beta, ecc_key *publicKey, ecc_key *ownKey, std::string &error) { + size_t bufferLength = 128; + uint8_t* buffer = new uint8_t[bufferLength]; + int err; + if((err = ecc_shared_secret(ownKey, publicKey, buffer, (unsigned long*) &bufferLength)) != CRYPT_OK){ + delete[] buffer; + error = "Could not calculate shared secret. Message: " + string(error_to_string(err)); + return false; + } + auto result = this->setupSharedSecret(alpha, beta, string((const char*) buffer, bufferLength), error); + delete[] buffer; + return result; +} + +bool CryptionHandler::setupSharedSecret(const std::string& alpha, const std::string& beta, std::string sharedKey, std::string &error) { + auto secret_hash = digest::sha1(sharedKey); + + char ivStruct[SHA_DIGEST_LENGTH]; + memcpy(ivStruct, alpha.data(), 10); + memcpy(&ivStruct[10], beta.data(), 10); + + for (int index = 0; index < SHA_DIGEST_LENGTH; index++) { + ivStruct[index] ^= (uint8_t) secret_hash[index]; + } + + { + lock_guard lock(this->cache_key_lock); + memcpy(this->iv_struct, ivStruct, SHA_DIGEST_LENGTH); + this->iv_struct_length = SHA_DIGEST_LENGTH; + + auto iv_hash = digest::sha1(ivStruct, SHA_DIGEST_LENGTH); + memcpy(this->current_mac, iv_hash.data(), 8); + + this->useDefaultChipherKeyNonce = false; + } + + return true; +} + +void _fe_neg(fe h, const fe f) { + int32_t f0 = f[0]; + int32_t f1 = f[1]; + int32_t f2 = f[2]; + int32_t f3 = f[3]; + int32_t f4 = f[4]; + int32_t f5 = f[5]; + int32_t f6 = f[6]; + int32_t f7 = f[7]; + int32_t f8 = f[8]; + int32_t f9 = f[9]; + int32_t h0 = -f0; + int32_t h1 = -f1; + int32_t h2 = -f2; + int32_t h3 = -f3; + int32_t h4 = -f4; + int32_t h5 = -f5; + int32_t h6 = -f6; + int32_t h7 = -f7; + int32_t h8 = -f8; + int32_t h9 = -f9; + + h[0] = h0; + h[1] = h1; + h[2] = h2; + h[3] = h3; + h[4] = h4; + h[5] = h5; + h[6] = h6; + h[7] = h7; + h[8] = h8; + h[9] = h9; +} + +inline std::string keyMul(const uint8_t* publicKey /* compressed */, const uint8_t* privateKey /* uncompressed */, bool negate){ + ge_p3 keyA{}; + ge_p2 result{}; + + ge_frombytes_negate_vartime(&keyA, publicKey); + if(negate) { + _fe_neg(*(fe*) &keyA.X, *(const fe*) &keyA.X); /* undo negate */ + _fe_neg(*(fe*) &keyA.T, *(const fe*) &keyA.T); /* undo negate */ + } + ge_scalarmult_vartime(&result, privateKey, &keyA); + + char buffer[32]; + ge_tobytes((uint8_t*) buffer, &result); + return string(buffer, 32); +} + +bool CryptionHandler::setupSharedSecretNew(const std::string &alpha, const std::string &beta, const char* privateKey /* uncompressed */, const char* publicKey /* compressed */) { + assert(alpha.length() == 10); + assert(beta.length() == 54); + + string shared; + string sharedIv; + shared.resize(32, '\0'); + sharedIv.resize(64, '\0'); + ed25519_key_exchange((uint8_t*) shared.data(), (uint8_t*) publicKey, (uint8_t*) privateKey); + shared = keyMul(reinterpret_cast(publicKey), reinterpret_cast(privateKey), true); //Remote key get negated + sharedIv = digest::sha512(shared); + + auto xor_key = alpha + beta; + for(int i = 0; i < 64; i++) + sharedIv[i] ^= xor_key[i]; + + { + lock_guard lock(this->cache_key_lock); + memcpy(this->iv_struct, sharedIv.data(), 64); + this->iv_struct_length = 64; + + auto digest_buffer = digest::sha1((char*) this->iv_struct, 64); + memcpy(this->current_mac, digest_buffer.data(), 8); + this->useDefaultChipherKeyNonce = false; + } + + return true; +} + +bool CryptionHandler::generate_key_nonce(protocol::BasicPacket* packet, bool use_default, uint8_t(& key)[16], uint8_t(& nonce)[16]){ + return this->generate_key_nonce( + dynamic_cast(packet) != nullptr, + packet->type().type(), + packet->packetId(), + packet->generationId(), + use_default, + key, + nonce + ); +} + +bool CryptionHandler::generate_key_nonce( + bool to_server, /* its from the client to the server */ + protocol::PacketType type, + uint16_t packet_id, + uint16_t generation, + bool use_default, + uint8_t (& key)[16], + uint8_t (& nonce)[16] +) { + if (this->useDefaultChipherKeyNonce || use_default) { + memcpy(key, this->default_key, 16); + memcpy(nonce, this->default_nonce, 16); + return true; + } + + auto& key_cache_array = to_server ? this->cache_key_client : this->cache_key_server; + if(type < 0 || type >= key_cache_array.max_size()) { + logError(0, "Tried to generate a crypt key with invalid type ({})!", type); + return false; + } + + auto& key_cache = key_cache_array[type]; + if(key_cache.generation != generation) { + const size_t buffer_length = 6 + this->iv_struct_length; + char* buffer = new char[buffer_length]; + memset(buffer, 0, 6 + this->iv_struct_length); + + if (to_server) { + buffer[0] = 0x31; + } else { + buffer[0] = 0x30; + } + buffer[1] = (char) (type & 0xF); + + le2be32(generation, buffer, 2); + memcpy(&buffer[6], this->iv_struct, this->iv_struct_length); + auto key_nonce = digest::sha256(buffer, 6 + this->iv_struct_length); + + memcpy(key_cache.key, key_nonce.data(), 16); + memcpy(key_cache.nonce, key_nonce.data() + 16, 16); + key_cache.generation = generation; + + delete[] buffer; + } + + memcpy(key, key_cache.key, 16); + memcpy(nonce, key_cache.nonce, 16); + + //Xor the key + key[0] ^= (uint8_t) ((packet_id >> 8) & 0xFF); + key[1] ^=(packet_id & 0xFF); + + return true; +} + +bool CryptionHandler::verify_encryption(const pipes::buffer_view &packet, uint16_t packet_id, uint16_t generation) { + int err; + int success = false; + + uint8_t key[16], nonce[16]; + if(!generate_key_nonce(true, (protocol::PacketType) (packet[12] & 0xF), packet_id, generation, false, key, nonce)) + return false; + + auto mac = packet.view(0, 8); + auto header = packet.view(8, 5); + auto data = packet.view(13); + + auto length = data.length(); + + const unsigned long target_length = 2048; + uint8_t target_buffer[2048]; + if(target_length < length) + return false; + + err = eax_decrypt_verify_memory(find_cipher("rijndael"), + (uint8_t *) key, /* the key */ + (size_t) 16, /* key is 16 bytes */ + (uint8_t *) nonce, /* the nonce */ + (size_t) 16, /* nonce is 16 bytes */ + (uint8_t *) header.data_ptr(), /* example header */ + (unsigned long) header.length(), /* header length */ + (const unsigned char *) data.data_ptr(), + (unsigned long) data.length(), + (unsigned char *) target_buffer, + (unsigned char *) mac.data_ptr(), + (unsigned long) mac.length(), + &success + ); + + return err == CRYPT_OK && success; +} + +bool CryptionHandler::decryptPacket(protocol::BasicPacket *packet, std::string &error, bool use_default) { + int err; + int success = false; + + auto header = packet->header(); + auto data = packet->data(); + + uint8_t key[16], nonce[16]; + if(!generate_key_nonce(packet, use_default, key, nonce)) { + error = "Could not generate key/nonce"; + return false; + } + + size_t target_length = 2048; + uint8_t target_buffer[2048]; + auto length = data.length(); + if(target_length < length) { + error = "buffer too large"; + return false; + } + + err = eax_decrypt_verify_memory(find_cipher("rijndael"), + (uint8_t *) key, /* the key */ + (unsigned long) 16, /* key is 16 bytes */ + (uint8_t *) nonce, /* the nonce */ + (unsigned long) 16, /* nonce is 16 bytes */ + (uint8_t *) header.data_ptr(), /* example header */ + (unsigned long) header.length(), /* header length */ + (const unsigned char *) data.data_ptr(), + (unsigned long) data.length(), + (unsigned char *) target_buffer, + (unsigned char *) packet->mac().data_ptr(), + (unsigned long) packet->mac().length(), + &success + ); + + if((err) != CRYPT_OK){ + error = "eax_decrypt_verify_memory(...) returned " + to_string(err) + "/" + error_to_string(err); + return false; + } + if(!success){ + error = "memory verify failed!"; + return false; + } + + packet->data(pipes::buffer_view{target_buffer, length}); + packet->setEncrypted(false); + return true; +} + +bool CryptionHandler::encryptPacket(protocol::BasicPacket *packet, std::string &error, bool use_default) { + uint8_t key[16], nonce[16]; + if(!generate_key_nonce(packet, use_default, key, nonce)) { + error = "Could not generate key/nonce"; + return false; + } + + size_t length = packet->data().length(); + + size_t tag_length = 8; + char tag_buffer[8]; + + size_t target_length = 2048; + uint8_t target_buffer[2048]; + if(target_length < length) { + error = "buffer too large"; + return false; + } + + int err; + if((err = eax_encrypt_authenticate_memory(find_cipher("rijndael"), + (uint8_t *) key, /* the key */ + (unsigned long) 16, /* key is 16 bytes */ + (uint8_t *) nonce, /* the nonce */ + (unsigned long) 16, /* nonce is 16 bytes */ + (uint8_t *) packet->header().data_ptr(), /* example header */ + (unsigned long) packet->header().length(), /* header length */ + (uint8_t *) packet->data().data_ptr(), /* The plain text */ + (unsigned long) packet->data().length(), /* Plain text length */ + (uint8_t *) target_buffer, /* The result buffer */ + (uint8_t *) tag_buffer, + (unsigned long *) &tag_length + )) != CRYPT_OK){ + error = "eax_encrypt_authenticate_memory(...) returned " + to_string(err) + "/" + error_to_string(err); + return false; + } + assert(tag_length == 8); + + packet->data(pipes::buffer_view{target_buffer, length}); + packet->mac().write(tag_buffer, tag_length); + packet->setEncrypted(true); + return true; +} + +bool CryptionHandler::progressPacketIn(protocol::BasicPacket* packet, std::string& error, bool use_default) { + while(blocked) + this_thread::sleep_for(chrono::microseconds(100)); + + if(packet->isEncrypted()){ + bool success = decryptPacket(packet, error, use_default); + if(success) packet->setEncrypted(false); + return success; + } + return true; +} + +bool CryptionHandler::progressPacketOut(protocol::BasicPacket* packet, std::string& error, bool use_default) { + while(blocked) + this_thread::sleep_for(chrono::microseconds(100)); + + if(packet->hasFlag(PacketFlag::Unencrypted)) { + packet->mac().write(this->current_mac, 8); + } else { + bool success = encryptPacket(packet, error, use_default); + if(success) packet->setEncrypted(true); + return success; + } + return true; +} \ No newline at end of file diff --git a/src/protocol/CryptionHandler.h b/src/protocol/CryptionHandler.h new file mode 100644 index 0000000..a19af23 --- /dev/null +++ b/src/protocol/CryptionHandler.h @@ -0,0 +1,74 @@ +#pragma once + +#include +#include +#include +#include "Packet.h" + +namespace ts { + namespace connection { + class CryptionHandler { + enum Methode { + TEAMSPEAK_3_1, + TEAMSPEAK_3 + }; + struct KeyCache { + uint16_t generation = 0xFFEF; + uint8_t key[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + uint8_t nonce[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + }; + public: + CryptionHandler(); + ~CryptionHandler(); + + void reset(); + + //TeamSpeak old + bool setupSharedSecret(const std::string& alpha, const std::string& beta, ecc_key* publicKey, ecc_key* ownKey, std::string &error); + bool setupSharedSecret(const std::string& alpha, const std::string& beta, std::string sharedKey, std::string &error); + + //TeamSpeak new + bool setupSharedSecretNew(const std::string& alpha, const std::string& beta, const char privateKey[32], const char publicKey[32]); + + bool progressPacketOut(protocol::BasicPacket*, std::string&, bool use_default); + bool progressPacketIn(protocol::BasicPacket*, std::string&, bool use_default); + + bool verify_encryption(const pipes::buffer_view& data, uint16_t packet_id, uint16_t generation); + + bool block(){ blocked = true; return true; } + bool unblock(){ blocked = false; return true; } + bool isBlocked(){ return blocked; } + + bool use_default() { return this->useDefaultChipherKeyNonce; } + private: + static constexpr char default_key[16] = {'c', ':', '\\', 'w', 'i', 'n', 'd', 'o', 'w', 's', '\\', 's', 'y', 's', 't', 'e'}; //c:\windows\syste + static constexpr char default_nonce[16] = {'m', '\\', 'f', 'i', 'r', 'e', 'w', 'a', 'l', 'l', '3', '2', '.', 'c', 'p', 'l'}; //m\firewall32.cpl + static constexpr char default_mac[8] = {'T', 'S', '3', 'I', 'N', 'I', 'T', '1'}; //TS3INIT1 + + + bool decryptPacket(protocol::BasicPacket *, std::string &, bool use_default); + bool encryptPacket(protocol::BasicPacket *, std::string &, bool use_default); + + bool generate_key_nonce(bool /* to server */, protocol::PacketType /* type */, uint16_t /* packet id */, uint16_t /* generation */, bool /* use default */, uint8_t(&)[16] /* key */, uint8_t(&)[16] /* nonce */); + bool generate_key_nonce(protocol::BasicPacket* packet, bool use_default, uint8_t(&)[16] /* key */, uint8_t(&)[16] /* nonce */); + + + //The default key and nonce + bool useDefaultChipherKeyNonce = true; + bool blocked = false; + + /* for the old protocol SHA1 length for the new 64 bytes */ + uint8_t iv_struct[64]; + uint8_t iv_struct_length = 0; + + uint8_t current_mac[8]; + + std::mutex cache_key_lock; + std::array cache_key_client; + std::array cache_key_server; + + static_assert(sizeof(current_mac) == sizeof(default_mac), "invalid mac"); + static_assert(sizeof(iv_struct) == 64, "invalid iv struct"); + }; + } +} \ No newline at end of file diff --git a/src/protocol/Packet.cpp b/src/protocol/Packet.cpp new file mode 100644 index 0000000..a17d4a7 --- /dev/null +++ b/src/protocol/Packet.cpp @@ -0,0 +1,233 @@ +// +// Created by wolverindev on 07.10.17. +// + +#include +#include +#include +#include "Packet.h" +#include "buffers.h" +#include "misc/endianness.h" + +using namespace std; +namespace ts { + namespace protocol { + + PacketTypeInfo::PacketTypeInfo(std::string name, PacketType type, bool ack, int max_length) noexcept { + this->data = new PacketTypeProperties{name, type, max_length, ack}; + if(type < 0x0F) + types.insert({type, *this}); + } + + PacketTypeInfo::PacketTypeInfo(PacketTypeInfo &red) : data(red.data) { } + PacketTypeInfo::PacketTypeInfo(const PacketTypeInfo &red) : data(red.data) { } + + std::map PacketTypeInfo::types; + PacketTypeInfo PacketTypeInfo::fromid(int id) { + for(auto elm : types) + if(elm.first == id) return elm.second; + return PacketTypeInfo::Undefined; + } + + PacketTypeInfo PacketTypeInfo::Voice = {"Voice", PacketType::VOICE, false, 1024}; + PacketTypeInfo PacketTypeInfo::VoiceWhisper = {"VoiceWhisper", PacketType::VOICE_WHISPER, false, 1024}; + PacketTypeInfo PacketTypeInfo::Command = {"Command", PacketType::COMMAND, true, 487}; + PacketTypeInfo PacketTypeInfo::CommandLow = {"CommandLow", PacketType::COMMAND_LOW, true, 487}; + PacketTypeInfo PacketTypeInfo::Ping = {"Ping", PacketType::PING, false, 1024}; + PacketTypeInfo PacketTypeInfo::Pong = {"Pong", PacketType::PONG, false, 1024}; + PacketTypeInfo PacketTypeInfo::Ack = {"Ack", PacketType::ACK, false, 1024}; + PacketTypeInfo PacketTypeInfo::AckLow = {"AckLow", PacketType::ACK_LOW, false, 1024}; + PacketTypeInfo PacketTypeInfo::Init1 = {"Init1", PacketType::INIT1, false, 1024}; + PacketTypeInfo PacketTypeInfo::Undefined = {"Undefined", PacketType::UNDEFINED, false, 1024}; + + namespace PacketFlag { + std::string to_string(PacketFlag flag){ + switch(flag){ + case Fragmented: + return "Fragmented"; + case NewProtocol: + return "NewProtocol"; + case Compressed: + return "Compressed"; + case Unencrypted: + return "Unencrypted"; + default: + return "None"; + } + } + } + + BasicPacket::BasicPacket(size_t header_length, size_t data_length) { + this->_header_length = header_length; + this->_buffer = pipes::buffer(MAC_SIZE + this->_header_length + data_length); + memset(this->_buffer.data_ptr(), 0, this->_buffer.length()); + } + + BasicPacket::BasicPacket(size_t header_length, const pipes::buffer &buffer) { + this->_header_length = header_length; + this->_buffer = buffer; + } + + BasicPacket::~BasicPacket() {} + + void BasicPacket::append_data(const std::vector &data) { + size_t length = 0; + for(const auto& buffer : data) + length += buffer.length(); + + /* we've to allocate a new buffer because out buffer is fixed in size */ + size_t index = this->_buffer.length(); + auto new_buffer = buffer::allocate_buffer(length + index); + new_buffer.write(this->_buffer, index); + + for(const auto& buffer : data) { + new_buffer.write(buffer, buffer.length(), index); + index += buffer.length(); + } + + this->_buffer = new_buffer; + } + + std::string BasicPacket::flags() const { + std::string result; + + if(hasFlag(PacketFlag::Unencrypted)) result += string(result.empty() ? "" : " | ") + "Unencrypted"; + if(hasFlag(PacketFlag::Compressed)) result += string(result.empty() ? "" : " | ") + "Compressed"; + if(hasFlag(PacketFlag::Fragmented)) result += string(result.empty() ? "" : " | ") + "Fragmented"; + if(hasFlag(PacketFlag::NewProtocol)) result += string(result.empty() ? "" : " | ") + "NewProtocol"; + + if(result.empty()) result = "none"; + return result; + } + + void BasicPacket::applyPacketId(PacketIdManager& manager) { + this->applyPacketId(manager.nextPacketId(this->type()), manager.generationId(this->type())); + } + void BasicPacket::applyPacketId(uint16_t packetId, uint16_t generationId) { + if(this->memory_state.id_branded) + throw std::logic_error("Packet already got a packet id!"); + this->memory_state.id_branded = true; + this->setPacketId(packetId, generationId); + } + Command BasicPacket::asCommand() { + return Command::parse(this->data()); + } + + /** + * @param buffer -> [mac][Header [uint16 BE packetId | [uint8](4bit flags | 4bit type)]][Data] + * @return + */ + + ServerPacket::ServerPacket(const pipes::buffer_view& buffer) : BasicPacket(SERVER_HEADER_SIZE, buffer.own_buffer()) { } + + ServerPacket::ServerPacket(uint8_t flagMask, const pipes::buffer_view& data) : BasicPacket(SERVER_HEADER_SIZE, data.length()) { + this->header()[2] = flagMask; + memcpy(this->data().data_ptr(), data.data_ptr(), data.length()); + } + + ServerPacket::ServerPacket(PacketTypeInfo type, const pipes::buffer_view& data) : BasicPacket(SERVER_HEADER_SIZE, data.length()) { + this->header()[2] |= type.type(); + memcpy(this->data().data_ptr(), data.data_ptr(), data.length()); + } + + ServerPacket::~ServerPacket() {} + + uint16_t ServerPacket::packetId() const { + return be2le16(&this->header()[0]); + } + + void ServerPacket::setPacketId(uint16_t pkId, uint16_t gen) { + le2be16(pkId, &this->header()[0]); + this->genId = gen; + } + + uint16_t ServerPacket::generationId() const { + return this->genId; + } + + PacketTypeInfo ServerPacket::type() const { + return PacketTypeInfo::fromid(this->header()[2] & 0xF); + } + + uint8_t ServerPacket::flagMask() const { + return this->header()[2]; + } + + bool ServerPacket::hasFlag(PacketFlag::PacketFlag flag) const { + return (this->header()[2] & flag) > 0; + } + + void ServerPacket::toggle(PacketFlag::PacketFlag flag, bool state) { + if(state){ + this->header()[2] |= flag; + } else { + this->header()[2] &= ~flag; + } + } + + + ClientPacket::ClientPacket(const pipes::buffer_view& buffer) : BasicPacket(CLIENT_HEADER_SIZE, buffer.own_buffer()) { } + + ClientPacket::ClientPacket(const PacketTypeInfo &type, const pipes::buffer_view& data) : BasicPacket(CLIENT_HEADER_SIZE, data.length()) { + this->header()[4] = type.type() & 0xF; + memcpy(this->data().data_ptr(), data.data_ptr(), data.length()); + } + + + ClientPacket::ClientPacket(const PacketTypeInfo &type, uint8_t flag_mask, const pipes::buffer_view& data) : ClientPacket(type, data) { + this->header()[4] |= flag_mask; + } + + ClientPacket::~ClientPacket() {} + + uint16_t ClientPacket::packetId() const { + return be2le16(&this->header()[0]); + } + + uint16_t ClientPacket::generationId() const { + return this->genId; + } + + PacketTypeInfo ClientPacket::type() const { + return PacketTypeInfo::fromid(this->header()[4] & 0xF); + } + + void ClientPacket::type(const ts::protocol::PacketTypeInfo &type) { + auto& field = this->header().data_ptr()[4]; + field &= ~0xF; + field |= type.type(); + } + + bool ClientPacket::hasFlag(PacketFlag::PacketFlag flag) const { + return (this->header()[4] & flag) > 0; + } + + uint8_t ClientPacket::flagMask() const { + return this->header()[4]; + } + + + void ClientPacket::toggle(PacketFlag::PacketFlag flag, bool state) { + if(state){ + this->header()[4] |= flag; + } else { + this->header()[4] &= ~flag; + } + } + + void ClientPacket::setPacketId(uint16_t pkId, uint16_t gen) { + this->header()[0] = (uint8_t) ((pkId >> 8) & 0xFF); + this->header()[1] = (uint8_t) ((pkId >> 0) & 0xFF); + this->genId = gen; + } + + uint16_t ClientPacket::clientId() const { + return be2le16(&this->header()[2]); + } + + void ClientPacket::clientId(uint16_t clId) { + this->header()[2] = clId >> 8; + this->header()[3] = clId & 0xFF; + } + } +} \ No newline at end of file diff --git a/src/protocol/Packet.h b/src/protocol/Packet.h new file mode 100644 index 0000000..4f01b45 --- /dev/null +++ b/src/protocol/Packet.h @@ -0,0 +1,290 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include "../query/Command.h" + +namespace ts { + namespace protocol { + enum PacketType : uint8_t { + VOICE = 0x00, + VOICE_WHISPER = 0x01, + COMMAND = 0x02, + COMMAND_LOW = 0x03, + PING = 0x04, + PONG = 0x05, + ACK = 0x06, + ACK_LOW = 0x07, + INIT1 = 0x08, + + PACKET_MAX = INIT1, + UNDEFINED = 0xFF + }; + + struct PacketTypeProperties { + std::string name; + PacketType type; + int max_length; + bool requireAcknowledge; + }; + + class PacketTypeInfo { + public: + static PacketTypeInfo Voice; + static PacketTypeInfo VoiceWhisper; + static PacketTypeInfo Command; + static PacketTypeInfo CommandLow; + static PacketTypeInfo Ping; + static PacketTypeInfo Pong; + static PacketTypeInfo Ack; + static PacketTypeInfo AckLow; + static PacketTypeInfo Init1; + static PacketTypeInfo Undefined; + + static PacketTypeInfo fromid(int id); + + std::string name() const { return data->name; } + PacketType type() const { return data->type; } + + bool requireAcknowledge(){ return data->requireAcknowledge; } + + bool operator==(const PacketTypeInfo& other) const { + return other.data->type == this->data->type; + } + + bool operator!=(const PacketTypeInfo& other){ + return other.data->type != this->data->type; + } + + int max_length() const { return data->max_length; } + inline bool fragmentable() { return *this == PacketTypeInfo::Command || *this == PacketTypeInfo::CommandLow; } + inline bool compressable() { return *this == PacketTypeInfo::Command || *this == PacketTypeInfo::CommandLow; } + + PacketTypeInfo(PacketTypeInfo&); + PacketTypeInfo(const PacketTypeInfo&); + PacketTypeInfo(PacketTypeInfo&& remote) : data(remote.data) {} + private: + static std::map types; + PacketTypeInfo(std::string, PacketType, bool, int) noexcept; + PacketTypeProperties* data; + }; + + struct PacketIdManagerData { + PacketIdManagerData(){ + memset(this->packetCounter, 0, sizeof(uint32_t) * 16); + } + uint32_t packetCounter[16]{}; + }; + + class PacketIdManager { + public: + PacketIdManager() : data(new PacketIdManagerData){} + ~PacketIdManager() = default; + PacketIdManager(const PacketIdManager& ref) : data(ref.data) {} + PacketIdManager(PacketIdManager& ref) : data(ref.data) {} + + uint16_t nextPacketId(const PacketTypeInfo &type){ + return static_cast(data->packetCounter[type.type()]++ & 0xFFFF); + } + + uint16_t currentPacketId(const PacketTypeInfo &type){ + return static_cast(data->packetCounter[type.type()] & 0xFFFF); + } + + uint16_t generationId(const PacketTypeInfo &type){ + return static_cast((data->packetCounter[type.type()] >> 16) & 0xFFFF); + } + + void reset() { + memset(&data->packetCounter[0], 0, sizeof(uint32_t) * 16); + } + private: + std::shared_ptr data; + }; + + namespace PacketFlag { + enum PacketFlag { + None = 0x00, + Fragmented = 0x10, //If packet type voice then its toggle the CELT Mono + NewProtocol = 0x20, + Compressed = 0x40, //If packet type voice than its the header + Unencrypted = 0x80 + }; + + std::string to_string(PacketFlag flag); + } + + #define MAC_SIZE 8 + #define SERVER_HEADER_SIZE 3 + #define CLIENT_HEADER_SIZE 5 + + class BasicPacket { + public: + explicit BasicPacket(size_t header_length, size_t data_length); + explicit BasicPacket(size_t header_length, const pipes::buffer& buffer); + virtual ~BasicPacket(); + + BasicPacket(const BasicPacket&) = delete; + BasicPacket(BasicPacket&&) = delete; + + virtual uint16_t packetId() const = 0; + virtual uint16_t generationId() const = 0; + virtual PacketTypeInfo type() const = 0; + virtual bool hasFlag(PacketFlag::PacketFlag flag) const = 0; + virtual uint8_t flagMask() const = 0; + virtual std::string flags() const; + virtual void enableFlag(PacketFlag::PacketFlag flag){ toggle(flag, true); } + virtual void toggle(PacketFlag::PacketFlag flag, bool state) = 0; + virtual void applyPacketId(PacketIdManager &); + virtual void applyPacketId(uint16_t, uint16_t); + + void setListener(std::unique_ptr> listener){ + if(!this->type().requireAcknowledge()) + throw std::logic_error("Packet type does not support a acknowledge listener!"); + this->listener = std::move(listener); + } + inline std::unique_ptr>& getListener() { return this->listener; } + + inline pipes::buffer_view mac() const { return this->_buffer.view(0, MAC_SIZE); } + inline pipes::buffer mac() { return this->_buffer.range(0, MAC_SIZE); } + + inline pipes::buffer_view header() const { return this->_buffer.view(MAC_SIZE, this->_header_length); } + inline pipes::buffer header() { return this->_buffer.range(MAC_SIZE, this->_header_length); } + + inline size_t data_length() const { return this->_buffer.length() - MAC_SIZE - this->_header_length; } + inline pipes::buffer_view data() const { return this->_buffer.view(MAC_SIZE + this->_header_length); } + inline pipes::buffer data() { return this->_buffer.range(MAC_SIZE + this->_header_length); } + + void append_data(const std::vector &data); + + inline void data(const pipes::buffer_view &data){ + this->_buffer.resize(MAC_SIZE + this->_header_length + data.length()); + memcpy((char*) this->_buffer.data_ptr() + MAC_SIZE + this->_header_length, data.data_ptr(), data.length()); + } + + inline void mac(const pipes::buffer_view &_new){ + assert(_new.length() >= MAC_SIZE); + memcpy(this->_buffer.data_ptr(), _new.data_ptr(), MAC_SIZE); + } + + inline bool isEncrypted() const { return this->memory_state.encrypted; } + inline void setEncrypted(bool flag){ this->memory_state.encrypted = flag; } + + inline bool isCompressed() const { return this->memory_state.compressed; } + inline void setCompressed(bool flag){ this->memory_state.compressed = flag; } + + inline bool isFragmentEntry() const { return this->memory_state.fragment_entry; } + inline void setFragmentedEntry(bool flag){ this->memory_state.fragment_entry = flag; } + + Command asCommand(); + + //Has the size of a byte + union { +#ifdef WIN32 + __pragma(pack(push, 1)) +#endif + struct { + bool encrypted: 1; + bool compressed: 1; + bool fragment_entry: 1; + + bool id_branded: 1; + } +#ifdef WIN32 + __pragma(pack(pop)); +#else + __attribute__((packed)); +#endif + + uint8_t flags = 0; + } memory_state; + + pipes::buffer buffer() { return this->_buffer; } + void buffer(pipes::buffer buffer) { this->_buffer = std::move(buffer); } + protected: + virtual void setPacketId(uint16_t, uint16_t) = 0; + uint8_t _header_length; + pipes::buffer _buffer; + //std::unique_ptr buffer; + /* + std::string _header; + std::string _data; + std::string _mac; + */ + + uint16_t genId = 0; + std::unique_ptr> listener; + }; + + + /** + * Packet from the client + */ + class ClientPacket : public BasicPacket { + public: + static constexpr size_t META_MAC_SIZE = 8; + static constexpr size_t META_HEADER_SIZE = CLIENT_HEADER_SIZE; + static constexpr size_t META_SIZE = META_MAC_SIZE + META_HEADER_SIZE; + + explicit ClientPacket(const pipes::buffer_view& buffer); + ClientPacket(const PacketTypeInfo& type, const pipes::buffer_view& data); + ClientPacket(const PacketTypeInfo& type, uint8_t flag_mask, const pipes::buffer_view& data); + ~ClientPacket() override; + ClientPacket(const ClientPacket&) = delete; + ClientPacket(ClientPacket&&) = delete; + + void toggle(PacketFlag::PacketFlag flag, bool state) override; + + uint16_t clientId() const; + void clientId(uint16_t); + + uint16_t packetId() const override; + + uint16_t generationId() const override; + void generationId(uint16_t generation) { this->genId = generation; } + + PacketTypeInfo type() const override; + void type(const PacketTypeInfo&); + + bool hasFlag(PacketFlag::PacketFlag flag) const override; + uint8_t flagMask() const override; + + private: + void setPacketId(uint16_t, uint16_t) override; + }; + + /** + * Packet from the server + */ + class ServerPacket : public BasicPacket { + public: + static constexpr size_t META_MAC_SIZE = 8; + static constexpr size_t META_HEADER_SIZE = SERVER_HEADER_SIZE; + static constexpr size_t META_SIZE = META_MAC_SIZE + META_HEADER_SIZE; + + explicit ServerPacket(const pipes::buffer_view& buffer); + ServerPacket(uint8_t flagMask, const pipes::buffer_view& data); + ServerPacket(PacketTypeInfo type, const pipes::buffer_view& data); + ~ServerPacket() override; + ServerPacket(const ServerPacket&) = delete; + ServerPacket(ServerPacket&&) = delete; + + uint16_t packetId() const override; + uint16_t generationId() const override; + void generationId(uint16_t generation) { this->genId = generation; } + PacketTypeInfo type() const override; + + bool hasFlag(PacketFlag::PacketFlag flag) const override; + uint8_t flagMask() const override; + + void toggle(PacketFlag::PacketFlag flag, bool state) override; + + private: + void setPacketId(uint16_t, uint16_t) override; + }; + } +} \ No newline at end of file diff --git a/src/protocol/buffers.cpp b/src/protocol/buffers.cpp new file mode 100644 index 0000000..1cdd606 --- /dev/null +++ b/src/protocol/buffers.cpp @@ -0,0 +1,18 @@ +#include "buffers.h" + +using namespace std; +using namespace ts; +using namespace ts::protocol; +using namespace ts::buffer; + +#pragma GCC optimize ("O3") + +meminfo buffer::buffer_memory() { + size_t bytes_buffer = 0; + size_t bytes_buffer_used = 0; + size_t bytes_internal = 0; + size_t nodes = 0; + size_t nodes_full = 0; + + return {bytes_buffer, bytes_buffer_used, bytes_internal, nodes, nodes_full}; +} \ No newline at end of file diff --git a/src/protocol/buffers.h b/src/protocol/buffers.h new file mode 100644 index 0000000..6d6ae9a --- /dev/null +++ b/src/protocol/buffers.h @@ -0,0 +1,269 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include "Packet.h" +#include "../misc/queue.h" +#include +#include + +#ifndef NO_LOG + #include +#endif + +namespace ts { + namespace buffer { + struct RawBuffer { + public: + RawBuffer() : RawBuffer(0) {} + RawBuffer(size_t length) : index(0), length(length) { + if(length > 0) buffer = (char *) malloc(length); + else buffer = nullptr; + this->length = length; + this->index = 0; + } + + RawBuffer(const RawBuffer &other) : RawBuffer(other.length) { + if(other.length > 0) memcpy(this->buffer, other.buffer, this->length); + this->index = other.index; + } + + virtual ~RawBuffer() { + if(buffer) + free(buffer); + this->buffer = nullptr; + } + + void slice(size_t length) { + char *oldBuff = this->buffer; + + this->buffer = (char *) malloc(length); + memcpy(this->buffer, oldBuff, length); + this->length = length; + + free(oldBuff); + } + + char *buffer = nullptr; + size_t length = 0; + size_t index = 0; + + TAILQ_ENTRY(ts::buffer::RawBuffer) tail; + }; + + template + struct SortedBufferQueue { + SortedBufferQueue(ts::protocol::PacketTypeInfo type, bool ignoreOrder) : _type(std::move(type)), ignoreOrder(ignoreOrder) { + this->current.index = 0; + } + + SortedBufferQueue(const SortedBufferQueue &ref) = delete; + SortedBufferQueue(SortedBufferQueue&&ref) = delete; + + ~SortedBufferQueue() = default; + + ts::protocol::PacketTypeInfo type() { return this->_type; } + + void skipPacket(){ + threads::MutexLock lock(this->lock); + this->current.index++; + } + + size_t available(){ + threads::MutexLock lock(this->lock); + if(this->ignoreOrder) return this->packets.size(); + + uint16_t index = 0; + while(true) { + if(!this->find_packet(this->current.index + index)) + return index; + else + index++; + } + } + + std::shared_ptr find_packet(uint32_t pktId){ + pktId &= 0xFFFF; + + threads::MutexLock lock(this->lock); + for(const auto& elm : this->packets) + if(elm->packetId() == pktId) + return elm; + return nullptr; + } + + std::shared_ptr peekNext(uint32_t index) { + threads::MutexLock lock(this->lock); + if(this->ignoreOrder) { + if(this->packets.size() > index) + return this->packets[index]; + else + return nullptr; + } + return this->find_packet(this->current.index + index); + } + + void pop_packets(int32_t count = -1) { + if(count == -1) count = 1; + + threads::MutexLock lock(this->lock); + if(this->ignoreOrder) { + while(count-- > 0 && !this->packets.empty()) this->packets.pop_front(); + return; + } + + auto until = this->current.index + count; + while(this->current.index < until) { + for(auto it = this->packets.begin(); it != this->packets.end(); it++) { + if((*it)->packetId() == this->current.packet_id) { + this->packets.erase(it); + break; + } + } + this->current.index++; + } + } + + bool push_pack(const std::shared_ptr& pkt){ + threads::MutexLock lock(this->lock); + if(this->ignoreOrder) { + this->packets.push_back(pkt); + if(this->current.packet_id > pkt->packetId()) { + if(this->current.packet_id > 0xFF00 && pkt->packetId() < 0xFF) { + this->current.packet_id = pkt->packetId(); + this->current.generation++; + } + } else this->current.packet_id = pkt->packetId(); + + return true; + } + + if(this->current.packet_id > pkt->packetId()) { + if(this->current.packet_id < 0xFF00 || pkt->packetId() > 0xFF) { +#ifndef NO_LOG + debugMessage(0, "Invalid packed pushpack! Current index {} (generation {}) Packet index {}", this->current.packet_id, this->current.generation, pkt->packetId()); +#endif + return false; + } + } + this->packets.push_back(pkt); + return true; + } + + void reset(){ + this->current.index = 0; + } + + std::unique_lock try_lock_queue() { + threads::MutexTryLock lock(this->lock); + if(!lock) return {}; + return std::unique_lock(this->lock); + } + + std::unique_lock try_lock_execute() { + threads::MutexTryLock lock(this->execute_lock); + if(!lock) return {}; + return std::unique_lock(this->execute_lock); + } + + uint16_t current_packet_id() { return this->current.packet_id; } + uint16_t current_generation_id() { return this->current.generation; } + + uint16_t calculate_generation(uint16_t packetId) { + if(packetId >= this->current.packet_id) return this->current.generation; + + if(packetId < 0xFF && this->current.packet_id > 0xFF00) + return this->current.generation + 1; + + return this->current.generation; + } + + union PacketPair { + uint32_t index; + struct { + uint16_t packet_id; + uint16_t generation; + }; + }; + + PacketPair current{0}; + private: + ts::protocol::PacketTypeInfo _type; + bool ignoreOrder = false; + std::deque> packets{}; + std::recursive_mutex lock; + std::recursive_mutex execute_lock; + }; + + struct size { + enum value : uint8_t { + unset, + min, + Bytes_512 = min, + Bytes_1024, + Bytes_1536, + max + }; + + static inline size_t byte_length(value size) { + switch (size) { + case Bytes_512: + return 512; + case Bytes_1024: + return 1024; + case Bytes_1536: + return 1536; + default: + return 0; + } + } + }; + + //typedef std::unique_ptr buffer_t; + typedef pipes::buffer buffer_t; + + extern buffer_t allocate_buffer(size::value /* size */); + inline buffer_t allocate_buffer(size_t length) { + pipes::buffer result; + if(length <= 512) + result = allocate_buffer(size::Bytes_512); + else if(length <= 1024) + result = allocate_buffer(size::Bytes_1024); + else if(length <= 1536) + result = allocate_buffer(size::Bytes_1536); + else { + return pipes::buffer{length}; + } + result.resize(length); + return result; + } + + struct cleaninfo { + size_t bytes_freed_internal; + size_t bytes_freed_buffer; + }; + struct cleanmode { + enum value { + CHUNKS = 0x01, + BLOCKS = 0x02, + + CHUNKS_BLOCKS = 0x03 + }; + }; + extern cleaninfo cleanup_buffers(cleanmode::value /* mode */); + + struct meminfo { + size_t bytes_buffer = 0; + size_t bytes_buffer_used = 0; + size_t bytes_internal = 0; + + size_t nodes = 0; + size_t nodes_full = 0; + }; + extern meminfo buffer_memory(); + } +} \ No newline at end of file diff --git a/src/protocol/buffers_allocator_a.cpp b/src/protocol/buffers_allocator_a.cpp new file mode 100644 index 0000000..b2882d6 --- /dev/null +++ b/src/protocol/buffers_allocator_a.cpp @@ -0,0 +1,392 @@ +#include "buffers.h" +#include + +using namespace std; +using namespace ts; +using namespace ts::protocol; +using namespace ts::buffer; + +#pragma GCC optimize ("O3") + +#define BLOCK_BUFFER_MASK 0xFFFFFFFF +#define BLOCK_BUFFERS 32 + +#define MEM_BUFFER_MAGIC 0xABCD +#define MEM_BLOCK_MAGIC 0xCDEF + +class spin_lock { + std::atomic_flag locked = ATOMIC_FLAG_INIT; + public: + void lock() { + uint8_t round = 0; + while (locked.test_and_set(std::memory_order_acquire)) { + //Yield when we're using this lock for a longer time, which we usually not doing + if(round++ % 8 == 0) + std::this_thread::yield(); + } + } + + inline bool try_lock() { + return !locked.test_and_set(std::memory_order_acquire); + } + + void unlock() { + locked.clear(std::memory_order_release); + } +}; +typedef spin_lock fast_lock_t; + +#pragma pack(push, 1) +struct buffer_entry_head { +#ifdef MEM_BUFFER_MAGIC + uint16_t magic; +#endif + uint32_t base_offset : 24; + uint8_t base_index : 8; +}; + +struct buffer_entry : buffer_entry_head { + char buffer[0]; +}; + +struct block_chain; + +/* 32 buffers/block */ +struct buffer_block { +#ifdef MEM_BLOCK_MAGIC + uint16_t magic; +#endif + uint8_t block_index; /* block index within the chain */ + block_chain* chain_entry; + fast_lock_t block_lock{}; + size::value type = size::unset; + union { + uint32_t flag_free = 0; + uint8_t flag_used8[4]; + }; + + buffer_entry buffers[0]; +}; +#pragma pack(pop) + +struct block_chain { + uint8_t type_index[buffer::size::max - buffer::size::min]; + block_chain* previous = nullptr; + block_chain* next = nullptr; + + uint8_t block_count = 0; + buffer_block* blocks[0]; +}; + +fast_lock_t chain_lock; +block_chain* chain_head = nullptr; + +inline void destroy_block(buffer_block* block) { + block->block_lock.~fast_lock_t(); + free(block); +} + +inline buffer_block* allocate_block(size::value type) { + auto base_size = sizeof(buffer_block); + auto buffer_size = size::byte_length(type); + auto base_entry_size = sizeof(buffer_entry) + buffer_size; + auto size = base_size + BLOCK_BUFFERS * base_entry_size; + + auto block = (buffer_block*) malloc(size); + new (&block->block_lock) fast_lock_t(); /* initialize spin lock */ +#ifdef MEM_BLOCK_MAGIC + block->magic = MEM_BLOCK_MAGIC; +#endif + block->type = type; + + for(uint8_t index = 0; index < BLOCK_BUFFERS; index++) { + auto entry_ptr = (uintptr_t) block->buffers + index * (uintptr_t) base_entry_size; + ((buffer_entry*) entry_ptr)->base_offset = index * (uintptr_t) base_entry_size + sizeof(buffer_block); + ((buffer_entry*) entry_ptr)->base_index = index; + } + block->flag_free = BLOCK_BUFFER_MASK; + return block; +} + +inline void destroy_chain_entry(block_chain* chain) { + free(chain); +} + +inline block_chain* allocate_chain_entry(uint8_t entries) { + auto base_size = sizeof(block_chain); + auto chain = (block_chain*) malloc(base_size + sizeof(void*) * entries); + + chain->next = nullptr; + chain->previous = nullptr; + chain->block_count = entries; + for(auto& index : chain->type_index) + index = 0; + for(uint8_t index = 0; index < entries; index++) + chain->blocks[index] = nullptr; + + return chain; +} + +struct BufferDeallocator { + bool operator()(void* buffer) { + buffer::release_buffer(buffer); + return true; + } +}; + +struct BufferAllocator { + bool operator()(size_t& /* length */, void*& /* result ptr */) { + __throw_logic_error("Cant reallocate a fixed buffer"); + } +}; + +buffer_t buffer::allocate_buffer(size::value size) { + return pipes::buffer{size}; + + fast_lock_t* block_lock = nullptr; + buffer_block* block; + + { + block_chain* tmp_chain; + + lock_guard lock_chain(chain_lock); + auto head = chain_head; + + auto type_index = (size::value) (size - size::min); + iterate_head: + while(head) { + uint8_t& index = head->type_index[type_index]; + while(index < head->block_count) { + auto entry = head->blocks[index]; + if(entry) { + if(entry->type != size || (entry->flag_free & BLOCK_BUFFER_MASK) == 0) + goto next_block; + + block_lock = &entry->block_lock; + if(!block_lock->try_lock() || (entry->flag_free & BLOCK_BUFFER_MASK) == 0) { + block_lock->unlock(); + goto next_block; + } + + block = entry; + /* we've found an entry with a free block */ + goto break_head_loop; + } else { + /* lets insert a new block */ + head->blocks[index] = (entry = allocate_block(size)); + entry->chain_entry = head; + entry->block_index = index; + + block_lock = &entry->block_lock; + block_lock->lock(); + + block = entry; + /* we've a new block which has to have free slots */ + goto break_head_loop; + } + + next_block: + index++; + } + + if(!head->next) + break; + + head = head->next; + } + + tmp_chain = allocate_chain_entry(128); + tmp_chain->previous = head; + + if(!head) { /* we've to create a chain head */ + chain_head = (head = tmp_chain); + } else { /* we've to append a new entry */ + head = (head->next = tmp_chain); + } + goto iterate_head; + /* insert new entry */ + + break_head_loop:; + } + + auto index = __builtin_ctz(block->flag_free); + block->flag_free &= ~(1 << index); + block_lock->unlock(); + + auto buffer_size = size::byte_length(size); + auto buffer_entry_size = buffer_size + sizeof(buffer_entry_head); + auto entry = (buffer_entry_head*) ((uintptr_t) block->buffers + (uintptr_t) buffer_entry_size * index); +#ifdef MEM_BUFFER_MAGIC + entry->magic = MEM_BUFFER_MAGIC; +#endif + + return pipes::buffer{(void*) entry, buffer_entry_size, false, BufferAllocator(), BufferDeallocator()}.range(sizeof(buffer_entry_head)); +} + +inline bool valid_buffer_size(size_t size) { + return size == 512 || size == 1024 || size == 1536; +} + +void buffer::release_buffer(void *buffer) { + return; + + auto head = (buffer_entry_head*) buffer; +#ifdef MEM_BUFFER_MAGIC + assert(head->magic == MEM_BUFFER_MAGIC); +#endif + + auto block = (buffer_block*) ((uintptr_t) head - (uintptr_t) head->base_offset); +#ifdef MEM_BLOCK_MAGIC + assert(block->magic == MEM_BLOCK_MAGIC); +#endif + + block->flag_free |= (1 << head->base_index); /* set the slot free flag */ + + auto type_index = (size::value) (block->type - size::min); + auto& index = block->chain_entry->type_index[type_index]; + if(index > block->block_index) + index = block->block_index; +} + +void buffer::release_buffer(pipes::buffer *buffer) { + assert(buffer->capacity_origin() > sizeof(buffer_entry_head)); + assert(valid_buffer_size(buffer->capacity_origin() - sizeof(buffer_entry_head))); + + release_buffer(buffer->data_ptr_origin()); + delete buffer; +} + +meminfo buffer::buffer_memory() { + size_t bytes_buffer = 0; + size_t bytes_buffer_used = 0; + size_t bytes_internal = 0; + size_t nodes = 0; + size_t nodes_full = 0; + + { + lock_guard lock_chain(chain_lock); + auto head = chain_head; + bool full; + while(head) { + full = true; + nodes++; + bytes_internal += sizeof(chain_head) + sizeof(void*) * head->block_count; + + for(uint8_t index = 0; index < head->block_count; index++) { + auto block = head->blocks[index]; + if(block) { + bytes_internal += sizeof(buffer_block); + bytes_internal += sizeof(buffer_entry) * BLOCK_BUFFERS; + + full = full && (block->flag_free & BLOCK_BUFFER_MASK) == 0; + auto length = size::byte_length(block->type); + bytes_buffer += length * BLOCK_BUFFERS; + bytes_buffer_used += (BLOCK_BUFFERS - __builtin_popcount(block->flag_free & BLOCK_BUFFER_MASK)) * length; + } else + full = false; + } + + head = head->next; + } + } + + return {bytes_buffer, bytes_buffer_used, bytes_internal, nodes, nodes_full}; +} + +cleaninfo buffer::cleanup_buffers(cleanmode::value mode) { + std::deque orphaned_blocks; + std::deque orphaned_chunks; + + bool flag_blocks = (mode & cleanmode::BLOCKS) > 0; + bool flag_chunks = (mode & cleanmode::CHUNKS) > 0; + { + lock_guard lock_chain(chain_lock); + + auto head = chain_head; + uint32_t free_value = BLOCK_BUFFER_MASK; + vector> block_locks; + + while(head) { + bool flag_used = false; + + if(flag_blocks) { + for(uint8_t index = 0; index < head->block_count; index++) { + auto block = head->blocks[index]; + if(!block) continue; /* block isn't set */ + + if(block->flag_free == free_value) { + lock_guard block_lock(block->block_lock); + if(block->flag_free != free_value) { /* block had been used while locking */ + flag_used |= true; + continue; + } + + head->blocks[index] = nullptr; + orphaned_blocks.push_back(block); + } else { + flag_used |= true; + } + } + } + + if(flag_chunks) { + if(!flag_blocks) { /* we've to calculate flag_used */ + block_locks.resize(head->block_count); + + for(uint8_t index = 0; index < head->block_count; index++) { + auto block = head->blocks[index]; + if(block) { + block_locks[index] = unique_lock{block->block_lock}; + if(block->flag_free == free_value) { + /* delete that block later */ + continue; + } + + flag_used = true; + break; + } + } + } + + if(!flag_used) { + if(head->previous) + head->previous->next = head->next; + else if(head == chain_head) + chain_head = head->next; + + if(head->next) + head->next->previous = head->previous; + + orphaned_chunks.push_back(head); + } + + block_locks.clear(); + } + + head = head->next; + } + } + + size_t bytes_internal = 0; + size_t bytes_buffer = 0; + + for(auto chain : orphaned_chunks) { + for(uint8_t index = 0; index < chain->block_count; index++) { + if(chain->blocks[index]) + orphaned_blocks.push_back(chain->blocks[index]); + } + bytes_internal += sizeof(block_chain); + + destroy_chain_entry(chain); + } + + for(auto block : orphaned_blocks) { + bytes_buffer += size::byte_length(block->type) * BLOCK_BUFFERS; + bytes_internal += sizeof(buffer_entry_head) * BLOCK_BUFFERS; + bytes_internal += sizeof(buffer_block); + + destroy_block(block); + } + + return {bytes_internal, bytes_buffer}; +} \ No newline at end of file diff --git a/src/protocol/buffers_allocator_b.cpp b/src/protocol/buffers_allocator_b.cpp new file mode 100644 index 0000000..0c2b9fb --- /dev/null +++ b/src/protocol/buffers_allocator_b.cpp @@ -0,0 +1,179 @@ +#include "buffers.h" +#include + +using namespace std; +using namespace ts; +using namespace ts::protocol; +using namespace ts::buffer; + +#pragma GCC optimize ("O3") + +class spin_lock { + std::atomic_flag locked = ATOMIC_FLAG_INIT; + public: + void lock() { + uint8_t round = 0; + while (locked.test_and_set(std::memory_order_acquire)) { + //Yield when we're using this lock for a longer time, which we usually not doing + if(round++ % 8 == 0) + std::this_thread::yield(); + } + } + + inline bool try_lock() { + return !locked.test_and_set(std::memory_order_acquire); + } + + void unlock() { + locked.clear(std::memory_order_release); + } +}; +typedef spin_lock fast_lock_t; + +struct BufferAllocator { + bool operator()(size_t& length, void*& result) { + __throw_logic_error("Cant reallocate a fixed buffer"); + } +}; + +struct Freelist { + struct Buffer { + Buffer* next_buffer; + char data_ptr[0]; + }; + + std::function deallocator; + BufferAllocator allocator{}; + + fast_lock_t lock{}; + + Buffer* head = nullptr; + Buffer* tail = nullptr; + ssize_t length = 0; + + size_t buffer_size; + ssize_t max_length; + +#ifdef DEBUG_FREELIST + std::atomic extra_alloc = 0; + std::atomic extra_free = 0; + + std::atomic total_alloc = 0; + std::atomic total_free = 0; +#endif + + pipes::buffer next_buffer() { +#ifdef DEBUG_FREELIST + this->total_alloc++; +#endif + Buffer* buffer = nullptr; + { + lock_guard lock(this->lock); + if(this->head) { + buffer = this->head; + if(buffer == this->tail) { + this->tail = nullptr; + this->head = nullptr; + assert(this->length == 0); + } else { + this->head = buffer->next_buffer; + assert(this->length > 0); + } + this->length--; + } + } + if(!buffer) { +#ifdef DEBUG_FREELIST + this->extra_alloc++; +#endif + buffer = (Buffer*) malloc(sizeof(Buffer) + this->buffer_size); + } + + return pipes::buffer{(void*) buffer, this->buffer_size + sizeof(Buffer), false, allocator, deallocator}.range(sizeof(Buffer)); + } + + bool release_buffer(void* ptr) { +#ifdef DEBUG_FREELIST + this->total_free++; +#endif + auto buffer = (Buffer*) ptr; + + if(this->max_length > 0 && this->length > this->max_length) { + /* dont push anymore stuff into the freelist! */ +#ifdef DEBUG_FREELIST + this->extra_free++; +#endif + free(ptr); + return true; + } + + { + lock_guard lock(this->lock); + if(this->tail) { + this->tail->next_buffer = buffer; + } else { + this->head = buffer; + } + this->tail = buffer; + buffer->next_buffer = nullptr; + + this->length++; + } + + return true; + } + + cleaninfo cleanup() { + size_t buffers_deallocated = 0; + + { + lock_guard lock(this->lock); + while(this->head) { + auto buffer = this->head; + this->head = buffer->next_buffer; + free(buffer); + + buffers_deallocated++; + } + + this->tail = nullptr; + this->head = nullptr; + this->length = 0; + } + + return {buffers_deallocated * 8, buffers_deallocated * this->buffer_size}; + } + + explicit Freelist(size_t size) { + this->buffer_size = size; + this->max_length = 1024 * 1024 * 1024; /* 1GB */ + this->deallocator = bind(&Freelist::release_buffer, this, placeholders::_1); + } +}; + +Freelist freelists[size::max - size::min] = { + Freelist(size::byte_length(size::Bytes_512)), + Freelist(size::byte_length(size::Bytes_1024)), + Freelist(size::byte_length(size::Bytes_1536)) +}; + +buffer_t buffer::allocate_buffer(size::value size) { + assert((size - size::min) > 0 && (size - size::min) < (size::max - size::min)); + return freelists[size - size::min].next_buffer(); +} + +cleaninfo& operator+=(cleaninfo& self, const cleaninfo& other) { + self.bytes_freed_internal += other.bytes_freed_internal; + self.bytes_freed_buffer += other.bytes_freed_buffer; + return self; +} + +cleaninfo buffer::cleanup_buffers(cleanmode::value mode) { + cleaninfo info{0, 0}; + info += freelists[0].cleanup(); + info += freelists[1].cleanup(); + info += freelists[2].cleanup(); + return info; +} +//cleanup_buffers +//$4 = {bytes_freed_internal = 8389144, bytes_freed_buffer = 536948736} \ No newline at end of file diff --git a/src/protocol/buffers_allocator_c.cpp b/src/protocol/buffers_allocator_c.cpp new file mode 100644 index 0000000..4e29d31 --- /dev/null +++ b/src/protocol/buffers_allocator_c.cpp @@ -0,0 +1,16 @@ +#include "buffers.h" + +using namespace std; +using namespace ts; +using namespace ts::protocol; +using namespace ts::buffer; + +#pragma GCC optimize ("O3") + +buffer_t buffer::allocate_buffer(size::value size) { + return pipes::buffer{buffer::size::byte_length(size)}; +} + +cleaninfo buffer::cleanup_buffers(cleanmode::value mode) { + return {0, 0}; +} \ No newline at end of file diff --git a/src/protocol/ringbuffer.cpp b/src/protocol/ringbuffer.cpp new file mode 100644 index 0000000..96216cc --- /dev/null +++ b/src/protocol/ringbuffer.cpp @@ -0,0 +1,5 @@ +// +// Created by wolverindev on 14.01.19. +// + +//#include "ringbuffer.h" diff --git a/src/protocol/ringbuffer.h b/src/protocol/ringbuffer.h new file mode 100644 index 0000000..200cc6d --- /dev/null +++ b/src/protocol/ringbuffer.h @@ -0,0 +1,225 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace ts { + namespace protocol { + template + struct RingEntry { + bool flag_set = false; + T entry{}; + }; + + template + class RingBuffer { + public: + static constexpr size_type _max_index = ~((size_type) 0); + + RingBuffer() : _capacity(capacity_t) { + this->_ring_index_full = 0; + this->_ring_base_index = 0; + } + + inline size_type current_index() { return this->_ring_index; } + + /** + * @param index + * @return -1 underflow | 0 success | 1 overflow + */ + inline int accept_index(size_type index) { + size_t relative_index; + if(this->calculate_index(index, relative_index)) + return 0; + if(index < this->current_index()) + return -1; + return 1; + } + + inline bool index_set(size_type index) { + size_t relative_index = 0; + if(!this->calculate_index(index, relative_index)) + return false; + return this->index(relative_index).flag_set; + } + + inline E& index_value(size_type index) { + size_t relative_index = 0; + if(!this->calculate_index(index, relative_index)) + return {}; + + return this->index(relative_index).entry; + } + + inline size_t current_slot() { return this->_ring_base_index; } + + inline bool slot_set(size_t slot) { + return this->index(slot).flag_set; + } + + inline E& slot_value(size_t slot) { + return this->index(slot).entry; + } + + + inline bool front_set() { return this->_slots[this->_ring_base_index].flag_set; } + + inline E pop_front() { + auto& slot = this->_slots[this->_ring_base_index]; + slot.flag_set = false; + auto entry = std::move(slot.entry); + this->_ring_base_index += 1; + this->_ring_index += 1; + if(this->_ring_base_index >= this->_capacity) + this->_ring_base_index -= this->_capacity; + return entry; + } + + inline void push_front(E&& entry) { + /* go to the back of the ring and set this as front */ + if(this->_ring_base_index == 0) + this->_ring_base_index = (size_type) this->_capacity; + + this->_ring_base_index -= 1; + this->_ring_index -= 1; + + auto& slot = this->_slots[this->_ring_base_index]; + slot.entry = std::forward(entry); + slot.flag_set = 1; + } + + inline bool push_back(E&& entry) { + size_t count = 0; + size_t index = this->_ring_base_index; + while(count < this->_capacity) { + if(index >= this->_capacity) + index -= this->_capacity; + + auto& slot = this->_slots[index]; + if(slot.flag_set) { + count++; + index++; + continue; + } + + slot.entry = std::forward(entry); + slot.flag_set = 1; + break; + } + + return count != this->_capacity; + } + + inline void set_full_index_to(size_type index) { + if(index > this->_ring_index) + this->_ring_index = index; + else if(index < 100 && this->_ring_index > std::numeric_limits::max() - 100) { + this->_ring_index += 200; /* let the index overflow into the generation counter */ + this->_ring_index = index; /* set the lover (16) bytes */ + } + } + + inline bool insert_index(size_type index, E&& entry) { + size_t relative_index = 0; + if(!this->calculate_index(index, relative_index)) + return false; + + auto& slot = this->index(relative_index); + if(slot.flag_set) + return false; + + slot.entry = std::forward(entry); + slot.flag_set = true; + return true; + } + + inline size_t capacity() { return this->_capacity; } + + + inline void clear() { + this->_ring_base_index = 0; + for(RingEntry& element : this->_slots) { + element.flag_set = false; + (void) std::move(element.entry); + } + } + + inline void reset() { + this->clear(); + this->_ring_index_full = 0; + } + protected: + size_t _capacity; + size_t _ring_base_index; /* index of slot 0 */ + + union { + uint64_t _ring_index_full; + struct { + static_assert(8 - sizeof(size_type) > 0, "Invalid size type!"); + + uint8_t padding[8 - sizeof(size_type)]; + size_type _ring_index; /* index of the insert index | overflow is welcome here */ + }; + }; + + std::array, capacity_t> _slots; + + inline RingEntry& index(size_t relative_index) { + assert(relative_index < this->_capacity); + auto index = this->_ring_base_index + relative_index; + if(index >= this->_capacity) + index -= this->_capacity; + return this->_slots[index]; + } + + inline bool calculate_index(size_type index, size_t& relative_index) { + if(this->_ring_index > index) { /* test if index is an overflow of the counter */ + if(index >= (size_type) (this->_ring_index + this->_capacity)) /* not anymore in bounds */ + return false; + else + relative_index = index + (_max_index - this->_ring_index + 1); + if(relative_index >= this->_capacity) + return false; + } else if(this->_ring_index < index) { + /* check upper bounds */ + relative_index = index - this->_ring_index; + if(relative_index >= this->_capacity) + return false; + } else { + /* index is equal, do nothing */ + relative_index = 0; + } + return true; + } + }; + + template > + class PacketRingBuffer : public RingBuffer { + public: + std::recursive_timed_mutex buffer_lock; + std::recursive_timed_mutex execute_lock; + + inline uint16_t generation(uint16_t packet_id) { + size_t relative_index = 0; + if(!this->calculate_index(packet_id, relative_index)) + relative_index = 0; /* okey we dont give a fuck */ + + return ((this->_ring_index_full + relative_index) >> 16) & 0xFFFF; + } + + inline void set_generation(uint16_t value) { + this->_ring_index_full = (value << 16) | (this->_ring_index_full & 0xFFFF); + } + + inline void set_generation_packet(uint16_t generation, uint16_t packet) { + this->_ring_index_full = (generation << 16) | (packet & 0xFFFF); + } + + inline uint64_t full_index() { return this->_ring_index_full; } + }; + } +} \ No newline at end of file diff --git a/src/qlz/QuickLZ.cpp b/src/qlz/QuickLZ.cpp new file mode 100644 index 0000000..08e24dc --- /dev/null +++ b/src/qlz/QuickLZ.cpp @@ -0,0 +1,26 @@ +// Fast data compression library +// Copyright (C) 2006-2011 Lasse Mikkel Reinhold +// lar@quicklz.com +// +// QuickLZ can be used for free under the GPL 1, 2 or 3 license (where anything +// released into public must be open source) or under a commercial license if such +// has been acquired (see http://www.quicklz.com/order.html). The commercial license +// does not cover derived or ported versions created by third parties under GPL. + +// 1.5.0 final + +#include "QuickLZ.h" + +#if QLZ_VERSION_MAJOR != 1 || QLZ_VERSION_MINOR != 5 || QLZ_VERSION_REVISION != 0 +#error quicklz.c and quicklz.h have different versions +#endif + +#if (defined(__X86__) || defined(__i386__) || defined(i386) || defined(_M_IX86) || defined(__386__) || defined(__x86_64__) || defined(_M_X64)) +#define X86X64 +#endif + +size_t qlz_size_header(const char *source) +{ + size_t n = 2*((((*source) & 2) == 2) ? 4 : 1) + 1; + return n; +} \ No newline at end of file diff --git a/src/qlz/QuickLZ.h b/src/qlz/QuickLZ.h new file mode 100644 index 0000000..ab62f67 --- /dev/null +++ b/src/qlz/QuickLZ.h @@ -0,0 +1,162 @@ +#ifndef QLZ_HEADER +#define QLZ_HEADER + +// Fast data compression library +// Copyright (C) 2006-2011 Lasse Mikkel Reinhold +// lar@quicklz.com +// +// QuickLZ can be used for free under the GPL 1, 2 or 3 license (where anything +// released into public must be open source) or under a commercial license if such +// has been acquired (see http://www.quicklz.com/order.html). The commercial license +// does not cover derived or ported versions created by third parties under GPL. + +// You can edit following user settings. Data must be decompressed with the same +// setting of QLZ_COMPRESSION_LEVEL and QLZ_STREAMING_BUFFER as it was compressed +// (see manual). If QLZ_STREAMING_BUFFER > 0, scratch buffers must be initially +// zeroed out (see manual). First #ifndef makes it possible to define settings from +// the outside like the compiler command line. + +// 1.5.0 final + +#ifndef QLZ_COMPRESSION_LEVEL +// 1 gives fastest compression speed. 3 gives fastest decompression speed and best +// compression ratio. +#define QLZ_COMPRESSION_LEVEL 3 +//#define QLZ_COMPRESSION_LEVEL 2 +//#define QLZ_COMPRESSION_LEVEL 3 +#endif + +#ifndef QLZ_STREAMING_BUFFER +// If > 0, zero out both states prior to first call to qlz_compress() or qlz_decompress() +// and decompress packets in the same order as they were compressed +#define QLZ_STREAMING_BUFFER 0 +//#define QLZ_STREAMING_BUFFER 100000 +//#define QLZ_STREAMING_BUFFER 1000000 + +// Guarantees that decompression of corrupted data cannot crash. Decreases decompression +// speed 10-20%. Compression speed not affected. +//#define QLZ_MEMORY_SAFE +#endif + +#define QLZ_VERSION_MAJOR 1 +#define QLZ_VERSION_MINOR 5 +#define QLZ_VERSION_REVISION 0 + +// Using size_t, memset() and memcpy() +#include + +// Verify compression level +#if QLZ_COMPRESSION_LEVEL != 1 && QLZ_COMPRESSION_LEVEL != 2 && QLZ_COMPRESSION_LEVEL != 3 +#error QLZ_COMPRESSION_LEVEL must be 1, 2 or 3 +#endif + +typedef unsigned int ui32; +typedef unsigned short int ui16; + +// Decrease QLZ_POINTERS for level 3 to increase compression speed. Do not touch any other values! +#if QLZ_COMPRESSION_LEVEL == 1 +#define QLZ_POINTERS 1 +#define QLZ_HASH_VALUES 4096 +#elif QLZ_COMPRESSION_LEVEL == 2 +#define QLZ_POINTERS 4 +#define QLZ_HASH_VALUES 2048 +#elif QLZ_COMPRESSION_LEVEL == 3 +#define QLZ_POINTERS 16 +#define QLZ_HASH_VALUES 4096 +#endif + +// Detect if pointer size is 64-bit. It's not fatal if some 64-bit target is not detected because this is only for adding an optional 64-bit optimization. +#if defined _LP64 || defined __LP64__ || defined __64BIT__ || _ADDR64 || defined _WIN64 || defined __arch64__ || __WORDSIZE == 64 || (defined __sparc && defined __sparcv9) || defined __x86_64 || defined __amd64 || defined __x86_64__ || defined _M_X64 || defined _M_IA64 || defined __ia64 || defined __IA64__ +#define QLZ_PTR_64 +#endif + +// hash entry +typedef struct { +#if QLZ_COMPRESSION_LEVEL == 1 + ui32 cache; +#if defined QLZ_PTR_64 && QLZ_STREAMING_BUFFER == 0 + unsigned int offset; +#else + const unsigned char *offset; +#endif +#else + const unsigned char *offset[QLZ_POINTERS]; +#endif + +} qlz_hash_compress; + +typedef struct { +#if QLZ_COMPRESSION_LEVEL == 1 + const unsigned char *offset; +#else + const unsigned char *offset[QLZ_POINTERS]; +#endif +} qlz_hash_decompress; + + +// states +typedef struct { +#if QLZ_STREAMING_BUFFER > 0 + unsigned char stream_buffer[QLZ_STREAMING_BUFFER]; +#endif + size_t stream_counter; + qlz_hash_compress hash[QLZ_HASH_VALUES]; + unsigned char hash_counter[QLZ_HASH_VALUES]; +} qlz_state_compress; + + +#if QLZ_COMPRESSION_LEVEL == 1 || QLZ_COMPRESSION_LEVEL == 2 +typedef struct { +#if QLZ_STREAMING_BUFFER > 0 + unsigned char stream_buffer[QLZ_STREAMING_BUFFER]; +#endif + qlz_hash_decompress hash[QLZ_HASH_VALUES]; + unsigned char hash_counter[QLZ_HASH_VALUES]; + size_t stream_counter; +} qlz_state_decompress; +#elif QLZ_COMPRESSION_LEVEL == 3 +typedef struct + { +#if QLZ_STREAMING_BUFFER > 0 + unsigned char stream_buffer[QLZ_STREAMING_BUFFER]; +#endif +#if QLZ_COMPRESSION_LEVEL <= 2 + qlz_hash_decompress hash[QLZ_HASH_VALUES]; +#endif + size_t stream_counter; + } qlz_state_decompress; +#endif + + +#if defined (__cplusplus) +extern "C" { +#endif + +#if QLZ_COMPRESSION_LEVEL == 1 + #define qlz_size_decompressed qlz_size_decompressed_level1 + #define qlz_size_compressed qlz_size_compressed_level1 + #define qlz_compress qlz_compress_level1 + #define qlz_decompress qlz_decompress_level1 + #define qlz_get_setting qlz_get_setting_level1 +#elif QLZ_COMPRESSION_LEVEL == 2 + #define DEFINE_QLZ_FN(type, name, ...) type name##_level2(#__VA_ARGS__); +#elif QLZ_COMPRESSION_LEVEL == 3 + #define qlz_size_decompressed qlz_size_decompressed_level3 + #define qlz_size_compressed qlz_size_compressed_level3 + #define qlz_compress qlz_compress_level3 + #define qlz_decompress qlz_decompress_level3 + #define qlz_get_setting qlz_get_setting_level3 +#endif + +// Public functions of QuickLZ +size_t qlz_size_decompressed(const char *source); +size_t qlz_size_compressed(const char *source); +size_t qlz_compress(const void *source, char *destination, size_t size, qlz_state_compress *state); +size_t qlz_decompress(const char *source, void *destination, qlz_state_decompress *state); +int qlz_get_setting(int setting); + +#if defined (__cplusplus) +} +#endif + +#endif diff --git a/src/qlz/QuickLZ_L1.cpp b/src/qlz/QuickLZ_L1.cpp new file mode 100644 index 0000000..a158e13 --- /dev/null +++ b/src/qlz/QuickLZ_L1.cpp @@ -0,0 +1,843 @@ +// Fast data compression library +// Copyright (C) 2006-2011 Lasse Mikkel Reinhold +// lar@quicklz.com +// +// QuickLZ can be used for free under the GPL 1, 2 or 3 license (where anything +// released into public must be open source) or under a commercial license if such +// has been acquired (see http://www.quicklz.com/order.html). The commercial license +// does not cover derived or ported versions created by third parties under GPL. + +// 1.5.0 final + +#define QLZ_COMPRESSION_LEVEL 1 +#define QLZ_MEMORY_SAFE +#include "QuickLZ.h" + +#if QLZ_VERSION_MAJOR != 1 || QLZ_VERSION_MINOR != 5 || QLZ_VERSION_REVISION != 0 +#error quicklz.c and quicklz.h have different versions +#endif + +#if (defined(__X86__) || defined(__i386__) || defined(i386) || defined(_M_IX86) || defined(__386__) || defined(__x86_64__) || defined(_M_X64)) +#define X86X64 +#endif + +#define MINOFFSET 2 +#define UNCONDITIONAL_MATCHLEN 6 +#define UNCOMPRESSED_END 4 +#define CWORD_LEN 4 + +#if QLZ_COMPRESSION_LEVEL == 1 && defined QLZ_PTR_64 && QLZ_STREAMING_BUFFER == 0 +#define OFFSET_BASE source + #define CAST (ui32)(size_t) +#else +#define OFFSET_BASE 0 +#define CAST +#endif + +int qlz_get_setting(int setting) +{ + switch (setting) + { + case 0: return QLZ_COMPRESSION_LEVEL; + case 1: return sizeof(qlz_state_compress); + case 2: return sizeof(qlz_state_decompress); + case 3: return QLZ_STREAMING_BUFFER; +#ifdef QLZ_MEMORY_SAFE + case 6: return 1; +#else + case 6: return 0; +#endif + case 7: return QLZ_VERSION_MAJOR; + case 8: return QLZ_VERSION_MINOR; + case 9: return QLZ_VERSION_REVISION; + } + return -1; +} + +#if QLZ_COMPRESSION_LEVEL == 1 +static int same(const unsigned char *src, size_t n) +{ + while(n > 0 && *(src + n) == *src) + n--; + return n == 0 ? 1 : 0; +} +#endif + +static void reset_table_compress(qlz_state_compress *state) +{ + int i; + for(i = 0; i < QLZ_HASH_VALUES; i++) + { +#if QLZ_COMPRESSION_LEVEL == 1 + state->hash[i].offset = 0; +#else + state->hash_counter[i] = 0; +#endif + } +} + +static void reset_table_decompress(qlz_state_decompress *state) +{ + int i; + (void)state; + (void)i; +#if QLZ_COMPRESSION_LEVEL == 2 + for(i = 0; i < QLZ_HASH_VALUES; i++) + { + state->hash_counter[i] = 0; + } +#endif +} + +static __inline ui32 hash_func(ui32 i) +{ +#if QLZ_COMPRESSION_LEVEL == 2 + return ((i >> 9) ^ (i >> 13) ^ i) & (QLZ_HASH_VALUES - 1); +#else + return ((i >> 12) ^ i) & (QLZ_HASH_VALUES - 1); +#endif +} + +static __inline ui32 fast_read(void const *src, ui32 bytes) +{ +#ifndef X86X64 + unsigned char *p = (unsigned char*)src; + switch (bytes) + { + case 4: + return(*p | *(p + 1) << 8 | *(p + 2) << 16 | *(p + 3) << 24); + case 3: + return(*p | *(p + 1) << 8 | *(p + 2) << 16); + case 2: + return(*p | *(p + 1) << 8); + case 1: + return(*p); + } + return 0; +#else + if (bytes >= 1 && bytes <= 4) + return *((ui32*)src); + else + return 0; +#endif +} + +static __inline ui32 hashat(const unsigned char *src) +{ + ui32 fetch, hash; + fetch = fast_read(src, 3); + hash = hash_func(fetch); + return hash; +} + +static __inline void fast_write(ui32 f, void *dst, size_t bytes) +{ +#ifndef X86X64 + unsigned char *p = (unsigned char*)dst; + + switch (bytes) + { + case 4: + *p = (unsigned char)f; + *(p + 1) = (unsigned char)(f >> 8); + *(p + 2) = (unsigned char)(f >> 16); + *(p + 3) = (unsigned char)(f >> 24); + return; + case 3: + *p = (unsigned char)f; + *(p + 1) = (unsigned char)(f >> 8); + *(p + 2) = (unsigned char)(f >> 16); + return; + case 2: + *p = (unsigned char)f; + *(p + 1) = (unsigned char)(f >> 8); + return; + case 1: + *p = (unsigned char)f; + return; + } +#else + switch (bytes) + { + case 4: + *((ui32*)dst) = f; + return; + case 3: + *((ui32*)dst) = f; + return; + case 2: + *((ui16 *)dst) = (ui16)f; + return; + case 1: + *((unsigned char*)dst) = (unsigned char)f; + return; + } +#endif +} + + +size_t qlz_size_decompressed(const char *source) +{ + ui32 n, r; + n = (((*source) & 2) == 2) ? 4 : 1; + r = fast_read(source + 1 + n, n); + r = r & (0xffffffff >> ((4 - n)*8)); + return r; +} + +size_t qlz_size_compressed(const char *source) +{ + ui32 n, r; + n = (((*source) & 2) == 2) ? 4 : 1; + r = fast_read(source + 1, n); + r = r & (0xffffffff >> ((4 - n)*8)); + return r; +} + +static __inline void memcpy_up(unsigned char *dst, const unsigned char *src, ui32 n) +{ + // Caution if modifying memcpy_up! Overlap of dst and src must be special handled. +#ifndef X86X64 + unsigned char *end = dst + n; + while(dst < end) + { + *dst = *src; + dst++; + src++; + } +#else + ui32 f = 0; + do + { + *(ui32 *)(dst + f) = *(ui32 *)(src + f); + f += MINOFFSET + 1; + } + while (f < n); +#endif +} + +static __inline void update_hash(qlz_state_decompress *state, const unsigned char *s) +{ +#if QLZ_COMPRESSION_LEVEL == 1 + ui32 hash; + hash = hashat(s); + state->hash[hash].offset = s; + state->hash_counter[hash] = 1; +#elif QLZ_COMPRESSION_LEVEL == 2 + ui32 hash; + unsigned char c; + hash = hashat(s); + c = state->hash_counter[hash]; + state->hash[hash].offset[c & (QLZ_POINTERS - 1)] = s; + c++; + state->hash_counter[hash] = c; +#endif + (void)state; + (void)s; +} + +#if QLZ_COMPRESSION_LEVEL <= 2 +static void update_hash_upto(qlz_state_decompress *state, unsigned char **lh, const unsigned char *max) +{ + while(*lh < max) + { + (*lh)++; + update_hash(state, *lh); + } +} +#endif + +static size_t qlz_compress_core(const unsigned char *source, unsigned char *destination, size_t size, qlz_state_compress *state) +{ + const unsigned char *last_byte = source + size - 1; + const unsigned char *src = source; + unsigned char *cword_ptr = destination; + unsigned char *dst = destination + CWORD_LEN; + ui32 cword_val = 1U << 31; + const unsigned char *last_matchstart = last_byte - UNCONDITIONAL_MATCHLEN - UNCOMPRESSED_END; + ui32 fetch = 0; + unsigned int lits = 0; + + (void) lits; + + if(src <= last_matchstart) + fetch = fast_read(src, 3); + + while(src <= last_matchstart) + { + if ((cword_val & 1) == 1) + { + // store uncompressed if compression ratio is too low + if (src > source + (size >> 1) && dst - destination > src - source - ((src - source) >> 5)) + return 0; + + fast_write((cword_val >> 1) | (1U << 31), cword_ptr, CWORD_LEN); + + cword_ptr = dst; + dst += CWORD_LEN; + cword_val = 1U << 31; + fetch = fast_read(src, 3); + } +#if QLZ_COMPRESSION_LEVEL == 1 + { + const unsigned char *o; + ui32 hash, cached; + + hash = hash_func(fetch); + cached = fetch ^ state->hash[hash].cache; + state->hash[hash].cache = fetch; + + o = state->hash[hash].offset + OFFSET_BASE; + state->hash[hash].offset = CAST(src - OFFSET_BASE); + +#ifdef X86X64 + if ((cached & 0xffffff) == 0 && o != OFFSET_BASE && (src - o > MINOFFSET || (src == o + 1 && lits >= 3 && src > source + 3 && same(src - 3, 6)))) + { + if(cached != 0) + { +#else + if (cached == 0 && o != OFFSET_BASE && (src - o > MINOFFSET || (src == o + 1 && lits >= 3 && src > source + 3 && same(src - 3, 6)))) + { + if (*(o + 3) != *(src + 3)) + { +#endif + hash <<= 4; + cword_val = (cword_val >> 1) | (1U << 31); + fast_write((3 - 2) | hash, dst, 2); + src += 3; + dst += 2; + } + else + { + const unsigned char *old_src = src; + size_t matchlen; + hash <<= 4; + + cword_val = (cword_val >> 1) | (1U << 31); + src += 4; + + if(*(o + (src - old_src)) == *src) + { + src++; + if(*(o + (src - old_src)) == *src) + { + size_t q = last_byte - UNCOMPRESSED_END - (src - 5) + 1; + size_t remaining = q > 255 ? 255 : q; + src++; + while(*(o + (src - old_src)) == *src && (size_t)(src - old_src) < remaining) + src++; + } + } + + matchlen = src - old_src; + if (matchlen < 18) + { + fast_write((ui32)(matchlen - 2) | hash, dst, 2); + dst += 2; + } + else + { + fast_write((ui32)(matchlen << 16) | hash, dst, 3); + dst += 3; + } + } + fetch = fast_read(src, 3); + lits = 0; + } + else + { + lits++; + *dst = *src; + src++; + dst++; + cword_val = (cword_val >> 1); +#ifdef X86X64 + fetch = fast_read(src, 3); +#else + fetch = (fetch >> 8 & 0xffff) | (*(src + 2) << 16); +#endif + } + } +#elif QLZ_COMPRESSION_LEVEL >= 2 + { + const unsigned char *o, *offset2; + ui32 hash, matchlen, k, m, best_k = 0; + unsigned char c; + size_t remaining = (last_byte - UNCOMPRESSED_END - src + 1) > 255 ? 255 : (last_byte - UNCOMPRESSED_END - src + 1); + (void)best_k; + + + //hash = hashat(src); + fetch = fast_read(src, 3); + hash = hash_func(fetch); + + c = state->hash_counter[hash]; + + offset2 = state->hash[hash].offset[0]; + if(offset2 < src - MINOFFSET && c > 0 && ((fast_read(offset2, 3) ^ fetch) & 0xffffff) == 0) + { + matchlen = 3; + if(*(offset2 + matchlen) == *(src + matchlen)) + { + matchlen = 4; + while(*(offset2 + matchlen) == *(src + matchlen) && matchlen < remaining) + matchlen++; + } + } + else + matchlen = 0; + for(k = 1; k < QLZ_POINTERS && c > k; k++) + { + o = state->hash[hash].offset[k]; +#if QLZ_COMPRESSION_LEVEL == 3 + if(((fast_read(o, 3) ^ fetch) & 0xffffff) == 0 && o < src - MINOFFSET) +#elif QLZ_COMPRESSION_LEVEL == 2 + if(*(src + matchlen) == *(o + matchlen) && ((fast_read(o, 3) ^ fetch) & 0xffffff) == 0 && o < src - MINOFFSET) +#endif + { + m = 3; + while(*(o + m) == *(src + m) && m < remaining) + m++; +#if QLZ_COMPRESSION_LEVEL == 3 + if ((m > matchlen) || (m == matchlen && o > offset2)) +#elif QLZ_COMPRESSION_LEVEL == 2 + if (m > matchlen) +#endif + { + offset2 = o; + matchlen = m; + best_k = k; + } + } + } + o = offset2; + state->hash[hash].offset[c & (QLZ_POINTERS - 1)] = src; + c++; + state->hash_counter[hash] = c; + +#if QLZ_COMPRESSION_LEVEL == 3 + if(matchlen > 2 && src - o < 131071) + { + ui32 u; + size_t offset = src - o; + + for(u = 1; u < matchlen; u++) + { + hash = hashat(src + u); + c = state->hash_counter[hash]++; + state->hash[hash].offset[c & (QLZ_POINTERS - 1)] = src + u; + } + + cword_val = (cword_val >> 1) | (1U << 31); + src += matchlen; + + if(matchlen == 3 && offset <= 63) + { + *dst = (unsigned char)(offset << 2); + dst++; + } + else if (matchlen == 3 && offset <= 16383) + { + ui32 f = (ui32)((offset << 2) | 1); + fast_write(f, dst, 2); + dst += 2; + } + else if (matchlen <= 18 && offset <= 1023) + { + ui32 f = ((matchlen - 3) << 2) | ((ui32)offset << 6) | 2; + fast_write(f, dst, 2); + dst += 2; + } + + else if(matchlen <= 33) + { + ui32 f = ((matchlen - 2) << 2) | ((ui32)offset << 7) | 3; + fast_write(f, dst, 3); + dst += 3; + } + else + { + ui32 f = ((matchlen - 3) << 7) | ((ui32)offset << 15) | 3; + fast_write(f, dst, 4); + dst += 4; + } + } + else + { + *dst = *src; + src++; + dst++; + cword_val = (cword_val >> 1); + } +#elif QLZ_COMPRESSION_LEVEL == 2 + + if(matchlen > 2) + { + cword_val = (cword_val >> 1) | (1U << 31); + src += matchlen; + + if (matchlen < 10) + { + ui32 f = best_k | ((matchlen - 2) << 2) | (hash << 5); + fast_write(f, dst, 2); + dst += 2; + } + else + { + ui32 f = best_k | (matchlen << 16) | (hash << 5); + fast_write(f, dst, 3); + dst += 3; + } + } + else + { + *dst = *src; + src++; + dst++; + cword_val = (cword_val >> 1); + } +#endif + } +#endif + } + while (src <= last_byte) + { + if ((cword_val & 1) == 1) + { + fast_write((cword_val >> 1) | (1U << 31), cword_ptr, CWORD_LEN); + cword_ptr = dst; + dst += CWORD_LEN; + cword_val = 1U << 31; + } +#if QLZ_COMPRESSION_LEVEL < 3 + if (src <= last_byte - 3) + { +#if QLZ_COMPRESSION_LEVEL == 1 + ui32 hash, fetch; + fetch = fast_read(src, 3); + hash = hash_func(fetch); + state->hash[hash].offset = CAST(src - OFFSET_BASE); + state->hash[hash].cache = fetch; +#elif QLZ_COMPRESSION_LEVEL == 2 + ui32 hash; + unsigned char c; + hash = hashat(src); + c = state->hash_counter[hash]; + state->hash[hash].offset[c & (QLZ_POINTERS - 1)] = src; + c++; + state->hash_counter[hash] = c; +#endif + } +#endif + *dst = *src; + src++; + dst++; + cword_val = (cword_val >> 1); + } + + while((cword_val & 1) != 1) + cword_val = (cword_val >> 1); + + fast_write((cword_val >> 1) | (1U << 31), cword_ptr, CWORD_LEN); + + // min. size must be 9 bytes so that the qlz_size functions can take 9 bytes as argument + return dst - destination < 9 ? 9 : dst - destination; +} + +extern size_t qlz_size_header(const char *source); +static size_t qlz_decompress_core(const unsigned char *source, unsigned char *destination, size_t size, qlz_state_decompress *state, const unsigned char *history) +{ + const unsigned char *src = source + qlz_size_header((const char *)source); + unsigned char *dst = destination; + const unsigned char *last_destination_byte = destination + size - 1; + ui32 cword_val = 1; + const unsigned char *last_matchstart = last_destination_byte - UNCONDITIONAL_MATCHLEN - UNCOMPRESSED_END; + unsigned char *last_hashed = destination - 1; + const unsigned char *last_source_byte = source + qlz_size_compressed((const char *)source) - 1; + static const ui32 bitlut[16] = {4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0}; + + (void) last_source_byte; + (void) last_hashed; + (void) state; + (void) history; + + for(;;) + { + ui32 fetch; + + if (cword_val == 1) + { +#ifdef QLZ_MEMORY_SAFE + if(src + CWORD_LEN - 1 > last_source_byte) + return 0; +#endif + cword_val = fast_read(src, CWORD_LEN); + src += CWORD_LEN; + } + +#ifdef QLZ_MEMORY_SAFE + if(src + 4 - 1 > last_source_byte) + return 0; +#endif + + fetch = fast_read(src, 4); + + if ((cword_val & 1) == 1) + { + ui32 matchlen; + const unsigned char *offset2; + +#if QLZ_COMPRESSION_LEVEL == 1 + ui32 hash; + cword_val = cword_val >> 1; + hash = (fetch >> 4) & 0xfff; + offset2 = (const unsigned char *)(size_t)state->hash[hash].offset; + + if((fetch & 0xf) != 0) + { + matchlen = (fetch & 0xf) + 2; + src += 2; + } + else + { + matchlen = *(src + 2); + src += 3; + } + +#elif QLZ_COMPRESSION_LEVEL == 2 + ui32 hash; + unsigned char c; + cword_val = cword_val >> 1; + hash = (fetch >> 5) & 0x7ff; + c = (unsigned char)(fetch & 0x3); + offset2 = state->hash[hash].offset[c]; + + if((fetch & (28)) != 0) + { + matchlen = ((fetch >> 2) & 0x7) + 2; + src += 2; + } + else + { + matchlen = *(src + 2); + src += 3; + } + +#elif QLZ_COMPRESSION_LEVEL == 3 + ui32 offset; + cword_val = cword_val >> 1; + if ((fetch & 3) == 0) + { + offset = (fetch & 0xff) >> 2; + matchlen = 3; + src++; + } + else if ((fetch & 2) == 0) + { + offset = (fetch & 0xffff) >> 2; + matchlen = 3; + src += 2; + } + else if ((fetch & 1) == 0) + { + offset = (fetch & 0xffff) >> 6; + matchlen = ((fetch >> 2) & 15) + 3; + src += 2; + } + else if ((fetch & 127) != 3) + { + offset = (fetch >> 7) & 0x1ffff; + matchlen = ((fetch >> 2) & 0x1f) + 2; + src += 3; + } + else + { + offset = (fetch >> 15); + matchlen = ((fetch >> 7) & 255) + 3; + src += 4; + } + + offset2 = dst - offset; +#endif + +#ifdef QLZ_MEMORY_SAFE + if(offset2 < history || offset2 > dst - MINOFFSET - 1) + return 0; + + if(matchlen > (ui32)(last_destination_byte - dst - UNCOMPRESSED_END + 1)) + return 0; +#endif + + memcpy_up(dst, offset2, matchlen); + dst += matchlen; + +#if QLZ_COMPRESSION_LEVEL <= 2 + update_hash_upto(state, &last_hashed, dst - matchlen); + last_hashed = dst - 1; +#endif + } + else + { + if (dst < last_matchstart) + { + unsigned int n = bitlut[cword_val & 0xf]; +#ifdef X86X64 + *(ui32 *)dst = *(ui32 *)src; +#else + memcpy_up(dst, src, 4); +#endif + cword_val = cword_val >> n; + dst += n; + src += n; +#if QLZ_COMPRESSION_LEVEL <= 2 + update_hash_upto(state, &last_hashed, dst - 3); +#endif + } + else + { + while(dst <= last_destination_byte) + { + if (cword_val == 1) + { + src += CWORD_LEN; + cword_val = 1U << 31; + } +#ifdef QLZ_MEMORY_SAFE + if(src >= last_source_byte + 1) + return 0; +#endif + *dst = *src; + dst++; + src++; + cword_val = cword_val >> 1; + } + +#if QLZ_COMPRESSION_LEVEL <= 2 + update_hash_upto(state, &last_hashed, last_destination_byte - 3); // todo, use constant +#endif + return size; + } + + } + } +} + +size_t qlz_compress(const void *source, char *destination, size_t size, qlz_state_compress *state) +{ + size_t r; + ui32 compressed; + size_t base; + + if(size == 0 || size > 0xffffffff - 400) + return 0; + + if(size < 216) + base = 3; + else + base = 9; + +#if QLZ_STREAMING_BUFFER > 0 + if (state->stream_counter + size - 1 >= QLZ_STREAMING_BUFFER) +#endif + { + reset_table_compress(state); + r = base + qlz_compress_core((const unsigned char *)source, (unsigned char*)destination + base, size, state); +#if QLZ_STREAMING_BUFFER > 0 + reset_table_compress(state); +#endif + if(r == base) + { + memcpy(destination + base, source, size); + r = size + base; + compressed = 0; + } + else + { + compressed = 1; + } + state->stream_counter = 0; + } +#if QLZ_STREAMING_BUFFER > 0 + else + { + unsigned char *src = state->stream_buffer + state->stream_counter; + + memcpy(src, source, size); + r = base + qlz_compress_core(src, (unsigned char*)destination + base, size, state); + + if(r == base) + { + memcpy(destination + base, src, size); + r = size + base; + compressed = 0; + reset_table_compress(state); + } + else + { + compressed = 1; + } + state->stream_counter += size; + } +#endif + if(base == 3) + { + *destination = (unsigned char)(0 | compressed); + *(destination + 1) = (unsigned char)r; + *(destination + 2) = (unsigned char)size; + } + else + { + *destination = (unsigned char)(2 | compressed); + fast_write((ui32)r, destination + 1, 4); + fast_write((ui32)size, destination + 5, 4); + } + + *destination |= (QLZ_COMPRESSION_LEVEL << 2); + *destination |= (1 << 6); + *destination |= ((QLZ_STREAMING_BUFFER == 0 ? 0 : (QLZ_STREAMING_BUFFER == 100000 ? 1 : (QLZ_STREAMING_BUFFER == 1000000 ? 2 : 3))) << 4); + +// 76543210 +// 01SSLLHC + + return r; +} + +size_t qlz_decompress(const char *source, void *destination, qlz_state_decompress *state) +{ + size_t dsiz = qlz_size_decompressed(source); + +#if QLZ_STREAMING_BUFFER > 0 + if (state->stream_counter + qlz_size_decompressed(source) - 1 >= QLZ_STREAMING_BUFFER) +#endif + { + if((*source & 1) == 1) + { + reset_table_decompress(state); + dsiz = qlz_decompress_core((const unsigned char *)source, (unsigned char *)destination, dsiz, state, (const unsigned char *)destination); + } + else + { + memcpy(destination, source + qlz_size_header(source), dsiz); + } + state->stream_counter = 0; + reset_table_decompress(state); + } +#if QLZ_STREAMING_BUFFER > 0 + else + { + unsigned char *dst = state->stream_buffer + state->stream_counter; + if((*source & 1) == 1) + { + dsiz = qlz_decompress_core((const unsigned char *)source, dst, dsiz, state, (const unsigned char *)state->stream_buffer); + } + else + { + memcpy(dst, source + qlz_size_header(source), dsiz); + reset_table_decompress(state); + } + memcpy(destination, dst, dsiz); + state->stream_counter += dsiz; + } +#endif + return dsiz; +} diff --git a/src/qlz/QuickLZ_L2.cpp b/src/qlz/QuickLZ_L2.cpp new file mode 100644 index 0000000..aacfb79 --- /dev/null +++ b/src/qlz/QuickLZ_L2.cpp @@ -0,0 +1,848 @@ +// Fast data compression library +// Copyright (C) 2006-2011 Lasse Mikkel Reinhold +// lar@quicklz.com +// +// QuickLZ can be used for free under the GPL 1, 2 or 3 license (where anything +// released into public must be open source) or under a commercial license if such +// has been acquired (see http://www.quicklz.com/order.html). The commercial license +// does not cover derived or ported versions created by third parties under GPL. + +// 1.5.0 final + +#define QLZ_COMPRESSION_LEVEL 2 +#include "QuickLZ.h" + +#if QLZ_VERSION_MAJOR != 1 || QLZ_VERSION_MINOR != 5 || QLZ_VERSION_REVISION != 0 +#error quicklz.c and quicklz.h have different versions +#endif + +#if (defined(__X86__) || defined(__i386__) || defined(i386) || defined(_M_IX86) || defined(__386__) || defined(__x86_64__) || defined(_M_X64)) +#define X86X64 +#endif + +#define MINOFFSET 2 +#define UNCONDITIONAL_MATCHLEN 6 +#define UNCOMPRESSED_END 4 +#define CWORD_LEN 4 + +#if QLZ_COMPRESSION_LEVEL == 1 && defined QLZ_PTR_64 && QLZ_STREAMING_BUFFER == 0 +#define OFFSET_BASE source + #define CAST (ui32)(size_t) +#else +#define OFFSET_BASE 0 +#define CAST +#endif + +int qlz_get_setting(int setting) +{ + switch (setting) + { + case 0: return QLZ_COMPRESSION_LEVEL; + case 1: return sizeof(qlz_state_compress); + case 2: return sizeof(qlz_state_decompress); + case 3: return QLZ_STREAMING_BUFFER; +#ifdef QLZ_MEMORY_SAFE + case 6: return 1; +#else + case 6: return 0; +#endif + case 7: return QLZ_VERSION_MAJOR; + case 8: return QLZ_VERSION_MINOR; + case 9: return QLZ_VERSION_REVISION; + } + return -1; +} + +#if QLZ_COMPRESSION_LEVEL == 1 +static int same(const unsigned char *src, size_t n) +{ + while(n > 0 && *(src + n) == *src) + n--; + return n == 0 ? 1 : 0; +} +#endif + +static void reset_table_compress(qlz_state_compress *state) +{ + int i; + for(i = 0; i < QLZ_HASH_VALUES; i++) + { +#if QLZ_COMPRESSION_LEVEL == 1 + state->hash[i].offset = 0; +#else + state->hash_counter[i] = 0; +#endif + } +} + +static void reset_table_decompress(qlz_state_decompress *state) +{ + int i; + (void)state; + (void)i; +#if QLZ_COMPRESSION_LEVEL == 2 + for(i = 0; i < QLZ_HASH_VALUES; i++) + { + state->hash_counter[i] = 0; + } +#endif +} + +static __inline ui32 hash_func(ui32 i) +{ +#if QLZ_COMPRESSION_LEVEL == 2 + return ((i >> 9) ^ (i >> 13) ^ i) & (QLZ_HASH_VALUES - 1); +#else + return ((i >> 12) ^ i) & (QLZ_HASH_VALUES - 1); +#endif +} + +static __inline ui32 fast_read(void const *src, ui32 bytes) +{ +#ifndef X86X64 + unsigned char *p = (unsigned char*)src; + switch (bytes) + { + case 4: + return(*p | *(p + 1) << 8 | *(p + 2) << 16 | *(p + 3) << 24); + case 3: + return(*p | *(p + 1) << 8 | *(p + 2) << 16); + case 2: + return(*p | *(p + 1) << 8); + case 1: + return(*p); + } + return 0; +#else + if (bytes >= 1 && bytes <= 4) + return *((ui32*)src); + else + return 0; +#endif +} + +static __inline ui32 hashat(const unsigned char *src) +{ + ui32 fetch, hash; + fetch = fast_read(src, 3); + hash = hash_func(fetch); + return hash; +} + +static __inline void fast_write(ui32 f, void *dst, size_t bytes) +{ +#ifndef X86X64 + unsigned char *p = (unsigned char*)dst; + + switch (bytes) + { + case 4: + *p = (unsigned char)f; + *(p + 1) = (unsigned char)(f >> 8); + *(p + 2) = (unsigned char)(f >> 16); + *(p + 3) = (unsigned char)(f >> 24); + return; + case 3: + *p = (unsigned char)f; + *(p + 1) = (unsigned char)(f >> 8); + *(p + 2) = (unsigned char)(f >> 16); + return; + case 2: + *p = (unsigned char)f; + *(p + 1) = (unsigned char)(f >> 8); + return; + case 1: + *p = (unsigned char)f; + return; + } +#else + switch (bytes) + { + case 4: + *((ui32*)dst) = f; + return; + case 3: + *((ui32*)dst) = f; + return; + case 2: + *((ui16 *)dst) = (ui16)f; + return; + case 1: + *((unsigned char*)dst) = (unsigned char)f; + return; + } +#endif +} + + +size_t qlz_size_decompressed(const char *source) +{ + ui32 n, r; + n = (((*source) & 2) == 2) ? 4 : 1; + r = fast_read(source + 1 + n, n); + r = r & (0xffffffff >> ((4 - n)*8)); + return r; +} + +size_t qlz_size_compressed(const char *source) +{ + ui32 n, r; + n = (((*source) & 2) == 2) ? 4 : 1; + r = fast_read(source + 1, n); + r = r & (0xffffffff >> ((4 - n)*8)); + return r; +} + +size_t qlz_size_header(const char *source) +{ + size_t n = 2*((((*source) & 2) == 2) ? 4 : 1) + 1; + return n; +} + + +static __inline void memcpy_up(unsigned char *dst, const unsigned char *src, ui32 n) +{ + // Caution if modifying memcpy_up! Overlap of dst and src must be special handled. +#ifndef X86X64 + unsigned char *end = dst + n; + while(dst < end) + { + *dst = *src; + dst++; + src++; + } +#else + ui32 f = 0; + do + { + *(ui32 *)(dst + f) = *(ui32 *)(src + f); + f += MINOFFSET + 1; + } + while (f < n); +#endif +} + +static __inline void update_hash(qlz_state_decompress *state, const unsigned char *s) +{ +#if QLZ_COMPRESSION_LEVEL == 1 + ui32 hash; + hash = hashat(s); + state->hash[hash].offset = s; + state->hash_counter[hash] = 1; +#elif QLZ_COMPRESSION_LEVEL == 2 + ui32 hash; + unsigned char c; + hash = hashat(s); + c = state->hash_counter[hash]; + state->hash[hash].offset[c & (QLZ_POINTERS - 1)] = s; + c++; + state->hash_counter[hash] = c; +#endif + (void)state; + (void)s; +} + +#if QLZ_COMPRESSION_LEVEL <= 2 +static void update_hash_upto(qlz_state_decompress *state, unsigned char **lh, const unsigned char *max) +{ + while(*lh < max) + { + (*lh)++; + update_hash(state, *lh); + } +} +#endif + +static size_t qlz_compress_core(const unsigned char *source, unsigned char *destination, size_t size, qlz_state_compress *state) +{ + const unsigned char *last_byte = source + size - 1; + const unsigned char *src = source; + unsigned char *cword_ptr = destination; + unsigned char *dst = destination + CWORD_LEN; + ui32 cword_val = 1U << 31; + const unsigned char *last_matchstart = last_byte - UNCONDITIONAL_MATCHLEN - UNCOMPRESSED_END; + ui32 fetch = 0; + unsigned int lits = 0; + + (void) lits; + + if(src <= last_matchstart) + fetch = fast_read(src, 3); + + while(src <= last_matchstart) + { + if ((cword_val & 1) == 1) + { + // store uncompressed if compression ratio is too low + if (src > source + (size >> 1) && dst - destination > src - source - ((src - source) >> 5)) + return 0; + + fast_write((cword_val >> 1) | (1U << 31), cword_ptr, CWORD_LEN); + + cword_ptr = dst; + dst += CWORD_LEN; + cword_val = 1U << 31; + fetch = fast_read(src, 3); + } +#if QLZ_COMPRESSION_LEVEL == 1 + { + const unsigned char *o; + ui32 hash, cached; + + hash = hash_func(fetch); + cached = fetch ^ state->hash[hash].cache; + state->hash[hash].cache = fetch; + + o = state->hash[hash].offset + OFFSET_BASE; + state->hash[hash].offset = CAST(src - OFFSET_BASE); + +#ifdef X86X64 + if ((cached & 0xffffff) == 0 && o != OFFSET_BASE && (src - o > MINOFFSET || (src == o + 1 && lits >= 3 && src > source + 3 && same(src - 3, 6)))) + { + if(cached != 0) + { +#else + if (cached == 0 && o != OFFSET_BASE && (src - o > MINOFFSET || (src == o + 1 && lits >= 3 && src > source + 3 && same(src - 3, 6)))) + { + if (*(o + 3) != *(src + 3)) + { +#endif + hash <<= 4; + cword_val = (cword_val >> 1) | (1U << 31); + fast_write((3 - 2) | hash, dst, 2); + src += 3; + dst += 2; + } + else + { + const unsigned char *old_src = src; + size_t matchlen; + hash <<= 4; + + cword_val = (cword_val >> 1) | (1U << 31); + src += 4; + + if(*(o + (src - old_src)) == *src) + { + src++; + if(*(o + (src - old_src)) == *src) + { + size_t q = last_byte - UNCOMPRESSED_END - (src - 5) + 1; + size_t remaining = q > 255 ? 255 : q; + src++; + while(*(o + (src - old_src)) == *src && (size_t)(src - old_src) < remaining) + src++; + } + } + + matchlen = src - old_src; + if (matchlen < 18) + { + fast_write((ui32)(matchlen - 2) | hash, dst, 2); + dst += 2; + } + else + { + fast_write((ui32)(matchlen << 16) | hash, dst, 3); + dst += 3; + } + } + fetch = fast_read(src, 3); + lits = 0; + } + else + { + lits++; + *dst = *src; + src++; + dst++; + cword_val = (cword_val >> 1); +#ifdef X86X64 + fetch = fast_read(src, 3); +#else + fetch = (fetch >> 8 & 0xffff) | (*(src + 2) << 16); +#endif + } + } +#elif QLZ_COMPRESSION_LEVEL >= 2 + { + const unsigned char *o, *offset2; + ui32 hash, matchlen, k, m, best_k = 0; + unsigned char c; + size_t remaining = (last_byte - UNCOMPRESSED_END - src + 1) > 255 ? 255 : (last_byte - UNCOMPRESSED_END - src + 1); + (void)best_k; + + + //hash = hashat(src); + fetch = fast_read(src, 3); + hash = hash_func(fetch); + + c = state->hash_counter[hash]; + + offset2 = state->hash[hash].offset[0]; + if(offset2 < src - MINOFFSET && c > 0 && ((fast_read(offset2, 3) ^ fetch) & 0xffffff) == 0) + { + matchlen = 3; + if(*(offset2 + matchlen) == *(src + matchlen)) + { + matchlen = 4; + while(*(offset2 + matchlen) == *(src + matchlen) && matchlen < remaining) + matchlen++; + } + } + else + matchlen = 0; + for(k = 1; k < QLZ_POINTERS && c > k; k++) + { + o = state->hash[hash].offset[k]; +#if QLZ_COMPRESSION_LEVEL == 3 + if(((fast_read(o, 3) ^ fetch) & 0xffffff) == 0 && o < src - MINOFFSET) +#elif QLZ_COMPRESSION_LEVEL == 2 + if(*(src + matchlen) == *(o + matchlen) && ((fast_read(o, 3) ^ fetch) & 0xffffff) == 0 && o < src - MINOFFSET) +#endif + { + m = 3; + while(*(o + m) == *(src + m) && m < remaining) + m++; +#if QLZ_COMPRESSION_LEVEL == 3 + if ((m > matchlen) || (m == matchlen && o > offset2)) +#elif QLZ_COMPRESSION_LEVEL == 2 + if (m > matchlen) +#endif + { + offset2 = o; + matchlen = m; + best_k = k; + } + } + } + o = offset2; + state->hash[hash].offset[c & (QLZ_POINTERS - 1)] = src; + c++; + state->hash_counter[hash] = c; + +#if QLZ_COMPRESSION_LEVEL == 3 + if(matchlen > 2 && src - o < 131071) + { + ui32 u; + size_t offset = src - o; + + for(u = 1; u < matchlen; u++) + { + hash = hashat(src + u); + c = state->hash_counter[hash]++; + state->hash[hash].offset[c & (QLZ_POINTERS - 1)] = src + u; + } + + cword_val = (cword_val >> 1) | (1U << 31); + src += matchlen; + + if(matchlen == 3 && offset <= 63) + { + *dst = (unsigned char)(offset << 2); + dst++; + } + else if (matchlen == 3 && offset <= 16383) + { + ui32 f = (ui32)((offset << 2) | 1); + fast_write(f, dst, 2); + dst += 2; + } + else if (matchlen <= 18 && offset <= 1023) + { + ui32 f = ((matchlen - 3) << 2) | ((ui32)offset << 6) | 2; + fast_write(f, dst, 2); + dst += 2; + } + + else if(matchlen <= 33) + { + ui32 f = ((matchlen - 2) << 2) | ((ui32)offset << 7) | 3; + fast_write(f, dst, 3); + dst += 3; + } + else + { + ui32 f = ((matchlen - 3) << 7) | ((ui32)offset << 15) | 3; + fast_write(f, dst, 4); + dst += 4; + } + } + else + { + *dst = *src; + src++; + dst++; + cword_val = (cword_val >> 1); + } +#elif QLZ_COMPRESSION_LEVEL == 2 + + if(matchlen > 2) + { + cword_val = (cword_val >> 1) | (1U << 31); + src += matchlen; + + if (matchlen < 10) + { + ui32 f = best_k | ((matchlen - 2) << 2) | (hash << 5); + fast_write(f, dst, 2); + dst += 2; + } + else + { + ui32 f = best_k | (matchlen << 16) | (hash << 5); + fast_write(f, dst, 3); + dst += 3; + } + } + else + { + *dst = *src; + src++; + dst++; + cword_val = (cword_val >> 1); + } +#endif + } +#endif + } + while (src <= last_byte) + { + if ((cword_val & 1) == 1) + { + fast_write((cword_val >> 1) | (1U << 31), cword_ptr, CWORD_LEN); + cword_ptr = dst; + dst += CWORD_LEN; + cword_val = 1U << 31; + } +#if QLZ_COMPRESSION_LEVEL < 3 + if (src <= last_byte - 3) + { +#if QLZ_COMPRESSION_LEVEL == 1 + ui32 hash, fetch; + fetch = fast_read(src, 3); + hash = hash_func(fetch); + state->hash[hash].offset = CAST(src - OFFSET_BASE); + state->hash[hash].cache = fetch; +#elif QLZ_COMPRESSION_LEVEL == 2 + ui32 hash; + unsigned char c; + hash = hashat(src); + c = state->hash_counter[hash]; + state->hash[hash].offset[c & (QLZ_POINTERS - 1)] = src; + c++; + state->hash_counter[hash] = c; +#endif + } +#endif + *dst = *src; + src++; + dst++; + cword_val = (cword_val >> 1); + } + + while((cword_val & 1) != 1) + cword_val = (cword_val >> 1); + + fast_write((cword_val >> 1) | (1U << 31), cword_ptr, CWORD_LEN); + + // min. size must be 9 bytes so that the qlz_size functions can take 9 bytes as argument + return dst - destination < 9 ? 9 : dst - destination; +} + +static size_t qlz_decompress_core(const unsigned char *source, unsigned char *destination, size_t size, qlz_state_decompress *state, const unsigned char *history) +{ + const unsigned char *src = source + qlz_size_header((const char *)source); + unsigned char *dst = destination; + const unsigned char *last_destination_byte = destination + size - 1; + ui32 cword_val = 1; + const unsigned char *last_matchstart = last_destination_byte - UNCONDITIONAL_MATCHLEN - UNCOMPRESSED_END; + unsigned char *last_hashed = destination - 1; + const unsigned char *last_source_byte = source + qlz_size_compressed((const char *)source) - 1; + static const ui32 bitlut[16] = {4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0}; + + (void) last_source_byte; + (void) last_hashed; + (void) state; + (void) history; + + for(;;) + { + ui32 fetch; + + if (cword_val == 1) + { +#ifdef QLZ_MEMORY_SAFE + if(src + CWORD_LEN - 1 > last_source_byte) + return 0; +#endif + cword_val = fast_read(src, CWORD_LEN); + src += CWORD_LEN; + } + +#ifdef QLZ_MEMORY_SAFE + if(src + 4 - 1 > last_source_byte) + return 0; +#endif + + fetch = fast_read(src, 4); + + if ((cword_val & 1) == 1) + { + ui32 matchlen; + const unsigned char *offset2; + +#if QLZ_COMPRESSION_LEVEL == 1 + ui32 hash; + cword_val = cword_val >> 1; + hash = (fetch >> 4) & 0xfff; + offset2 = (const unsigned char *)(size_t)state->hash[hash].offset; + + if((fetch & 0xf) != 0) + { + matchlen = (fetch & 0xf) + 2; + src += 2; + } + else + { + matchlen = *(src + 2); + src += 3; + } + +#elif QLZ_COMPRESSION_LEVEL == 2 + ui32 hash; + unsigned char c; + cword_val = cword_val >> 1; + hash = (fetch >> 5) & 0x7ff; + c = (unsigned char)(fetch & 0x3); + offset2 = state->hash[hash].offset[c]; + + if((fetch & (28)) != 0) + { + matchlen = ((fetch >> 2) & 0x7) + 2; + src += 2; + } + else + { + matchlen = *(src + 2); + src += 3; + } + +#elif QLZ_COMPRESSION_LEVEL == 3 + ui32 offset; + cword_val = cword_val >> 1; + if ((fetch & 3) == 0) + { + offset = (fetch & 0xff) >> 2; + matchlen = 3; + src++; + } + else if ((fetch & 2) == 0) + { + offset = (fetch & 0xffff) >> 2; + matchlen = 3; + src += 2; + } + else if ((fetch & 1) == 0) + { + offset = (fetch & 0xffff) >> 6; + matchlen = ((fetch >> 2) & 15) + 3; + src += 2; + } + else if ((fetch & 127) != 3) + { + offset = (fetch >> 7) & 0x1ffff; + matchlen = ((fetch >> 2) & 0x1f) + 2; + src += 3; + } + else + { + offset = (fetch >> 15); + matchlen = ((fetch >> 7) & 255) + 3; + src += 4; + } + + offset2 = dst - offset; +#endif + +#ifdef QLZ_MEMORY_SAFE + if(offset2 < history || offset2 > dst - MINOFFSET - 1) + return 0; + + if(matchlen > (ui32)(last_destination_byte - dst - UNCOMPRESSED_END + 1)) + return 0; +#endif + + memcpy_up(dst, offset2, matchlen); + dst += matchlen; + +#if QLZ_COMPRESSION_LEVEL <= 2 + update_hash_upto(state, &last_hashed, dst - matchlen); + last_hashed = dst - 1; +#endif + } + else + { + if (dst < last_matchstart) + { + unsigned int n = bitlut[cword_val & 0xf]; +#ifdef X86X64 + *(ui32 *)dst = *(ui32 *)src; +#else + memcpy_up(dst, src, 4); +#endif + cword_val = cword_val >> n; + dst += n; + src += n; +#if QLZ_COMPRESSION_LEVEL <= 2 + update_hash_upto(state, &last_hashed, dst - 3); +#endif + } + else + { + while(dst <= last_destination_byte) + { + if (cword_val == 1) + { + src += CWORD_LEN; + cword_val = 1U << 31; + } +#ifdef QLZ_MEMORY_SAFE + if(src >= last_source_byte + 1) + return 0; +#endif + *dst = *src; + dst++; + src++; + cword_val = cword_val >> 1; + } + +#if QLZ_COMPRESSION_LEVEL <= 2 + update_hash_upto(state, &last_hashed, last_destination_byte - 3); // todo, use constant +#endif + return size; + } + + } + } +} + +size_t qlz_compress(const void *source, char *destination, size_t size, qlz_state_compress *state) +{ + size_t r; + ui32 compressed; + size_t base; + + if(size == 0 || size > 0xffffffff - 400) + return 0; + + if(size < 216) + base = 3; + else + base = 9; + +#if QLZ_STREAMING_BUFFER > 0 + if (state->stream_counter + size - 1 >= QLZ_STREAMING_BUFFER) +#endif + { + reset_table_compress(state); + r = base + qlz_compress_core((const unsigned char *)source, (unsigned char*)destination + base, size, state); +#if QLZ_STREAMING_BUFFER > 0 + reset_table_compress(state); +#endif + if(r == base) + { + memcpy(destination + base, source, size); + r = size + base; + compressed = 0; + } + else + { + compressed = 1; + } + state->stream_counter = 0; + } +#if QLZ_STREAMING_BUFFER > 0 + else + { + unsigned char *src = state->stream_buffer + state->stream_counter; + + memcpy(src, source, size); + r = base + qlz_compress_core(src, (unsigned char*)destination + base, size, state); + + if(r == base) + { + memcpy(destination + base, src, size); + r = size + base; + compressed = 0; + reset_table_compress(state); + } + else + { + compressed = 1; + } + state->stream_counter += size; + } +#endif + if(base == 3) + { + *destination = (unsigned char)(0 | compressed); + *(destination + 1) = (unsigned char)r; + *(destination + 2) = (unsigned char)size; + } + else + { + *destination = (unsigned char)(2 | compressed); + fast_write((ui32)r, destination + 1, 4); + fast_write((ui32)size, destination + 5, 4); + } + + *destination |= (QLZ_COMPRESSION_LEVEL << 2); + *destination |= (1 << 6); + *destination |= ((QLZ_STREAMING_BUFFER == 0 ? 0 : (QLZ_STREAMING_BUFFER == 100000 ? 1 : (QLZ_STREAMING_BUFFER == 1000000 ? 2 : 3))) << 4); + +// 76543210 +// 01SSLLHC + + return r; +} + +size_t qlz_decompress(const char *source, void *destination, qlz_state_decompress *state) +{ + size_t dsiz = qlz_size_decompressed(source); + +#if QLZ_STREAMING_BUFFER > 0 + if (state->stream_counter + qlz_size_decompressed(source) - 1 >= QLZ_STREAMING_BUFFER) +#endif + { + if((*source & 1) == 1) + { + reset_table_decompress(state); + dsiz = qlz_decompress_core((const unsigned char *)source, (unsigned char *)destination, dsiz, state, (const unsigned char *)destination); + } + else + { + memcpy(destination, source + qlz_size_header(source), dsiz); + } + state->stream_counter = 0; + reset_table_decompress(state); + } +#if QLZ_STREAMING_BUFFER > 0 + else + { + unsigned char *dst = state->stream_buffer + state->stream_counter; + if((*source & 1) == 1) + { + dsiz = qlz_decompress_core((const unsigned char *)source, dst, dsiz, state, (const unsigned char *)state->stream_buffer); + } + else + { + memcpy(dst, source + qlz_size_header(source), dsiz); + reset_table_decompress(state); + } + memcpy(destination, dst, dsiz); + state->stream_counter += dsiz; + } +#endif + return dsiz; +} diff --git a/src/qlz/QuickLZ_L3.cpp b/src/qlz/QuickLZ_L3.cpp new file mode 100644 index 0000000..4f4f5ee --- /dev/null +++ b/src/qlz/QuickLZ_L3.cpp @@ -0,0 +1,843 @@ +// Fast data compression library +// Copyright (C) 2006-2011 Lasse Mikkel Reinhold +// lar@quicklz.com +// +// QuickLZ can be used for free under the GPL 1, 2 or 3 license (where anything +// released into public must be open source) or under a commercial license if such +// has been acquired (see http://www.quicklz.com/order.html). The commercial license +// does not cover derived or ported versions created by third parties under GPL. + +// 1.5.0 final + +#define QLZ_COMPRESSION_LEVEL 3 +#include "QuickLZ.h" + +#if QLZ_VERSION_MAJOR != 1 || QLZ_VERSION_MINOR != 5 || QLZ_VERSION_REVISION != 0 +#error quicklz.c and quicklz.h have different versions +#endif + +#if (defined(__X86__) || defined(__i386__) || defined(i386) || defined(_M_IX86) || defined(__386__) || defined(__x86_64__) || defined(_M_X64)) +#define X86X64 +#endif + +#define MINOFFSET 2 +#define UNCONDITIONAL_MATCHLEN 6 +#define UNCOMPRESSED_END 4 +#define CWORD_LEN 4 + +#if QLZ_COMPRESSION_LEVEL == 1 && defined QLZ_PTR_64 && QLZ_STREAMING_BUFFER == 0 +#define OFFSET_BASE source + #define CAST (ui32)(size_t) +#else +#define OFFSET_BASE 0 +#define CAST +#endif + +int qlz_get_setting(int setting) +{ + switch (setting) + { + case 0: return QLZ_COMPRESSION_LEVEL; + case 1: return sizeof(qlz_state_compress); + case 2: return sizeof(qlz_state_decompress); + case 3: return QLZ_STREAMING_BUFFER; +#ifdef QLZ_MEMORY_SAFE + case 6: return 1; +#else + case 6: return 0; +#endif + case 7: return QLZ_VERSION_MAJOR; + case 8: return QLZ_VERSION_MINOR; + case 9: return QLZ_VERSION_REVISION; + } + return -1; +} + +#if QLZ_COMPRESSION_LEVEL == 1 +static int same(const unsigned char *src, size_t n) +{ + while(n > 0 && *(src + n) == *src) + n--; + return n == 0 ? 1 : 0; +} +#endif + +static void reset_table_compress(qlz_state_compress *state) +{ + int i; + for(i = 0; i < QLZ_HASH_VALUES; i++) + { +#if QLZ_COMPRESSION_LEVEL == 1 + state->hash[i].offset = 0; +#else + state->hash_counter[i] = 0; +#endif + } +} + +static void reset_table_decompress(qlz_state_decompress *state) +{ + int i; + (void)state; + (void)i; +#if QLZ_COMPRESSION_LEVEL == 2 + for(i = 0; i < QLZ_HASH_VALUES; i++) + { + state->hash_counter[i] = 0; + } +#endif +} + +static __inline ui32 hash_func(ui32 i) +{ +#if QLZ_COMPRESSION_LEVEL == 2 + return ((i >> 9) ^ (i >> 13) ^ i) & (QLZ_HASH_VALUES - 1); +#else + return ((i >> 12) ^ i) & (QLZ_HASH_VALUES - 1); +#endif +} + +static __inline ui32 fast_read(void const *src, ui32 bytes) +{ +#ifndef X86X64 + unsigned char *p = (unsigned char*)src; + switch (bytes) + { + case 4: + return(*p | *(p + 1) << 8 | *(p + 2) << 16 | *(p + 3) << 24); + case 3: + return(*p | *(p + 1) << 8 | *(p + 2) << 16); + case 2: + return(*p | *(p + 1) << 8); + case 1: + return(*p); + } + return 0; +#else + if (bytes >= 1 && bytes <= 4) + return *((ui32*)src); + else + return 0; +#endif +} + +static __inline ui32 hashat(const unsigned char *src) +{ + ui32 fetch, hash; + fetch = fast_read(src, 3); + hash = hash_func(fetch); + return hash; +} + +static __inline void fast_write(ui32 f, void *dst, size_t bytes) +{ +#ifndef X86X64 + unsigned char *p = (unsigned char*)dst; + + switch (bytes) + { + case 4: + *p = (unsigned char)f; + *(p + 1) = (unsigned char)(f >> 8); + *(p + 2) = (unsigned char)(f >> 16); + *(p + 3) = (unsigned char)(f >> 24); + return; + case 3: + *p = (unsigned char)f; + *(p + 1) = (unsigned char)(f >> 8); + *(p + 2) = (unsigned char)(f >> 16); + return; + case 2: + *p = (unsigned char)f; + *(p + 1) = (unsigned char)(f >> 8); + return; + case 1: + *p = (unsigned char)f; + return; + } +#else + switch (bytes) + { + case 4: + *((ui32*)dst) = f; + return; + case 3: + *((ui32*)dst) = f; + return; + case 2: + *((ui16 *)dst) = (ui16)f; + return; + case 1: + *((unsigned char*)dst) = (unsigned char)f; + return; + } +#endif +} + + +size_t qlz_size_decompressed(const char *source) +{ + ui32 n, r; + n = (((*source) & 2) == 2) ? 4 : 1; + r = fast_read(source + 1 + n, n); + r = r & (0xffffffff >> ((4 - n)*8)); + return r; +} + +size_t qlz_size_compressed(const char *source) +{ + ui32 n, r; + n = (((*source) & 2) == 2) ? 4 : 1; + r = fast_read(source + 1, n); + r = r & (0xffffffff >> ((4 - n)*8)); + return r; +} + + +static __inline void memcpy_up(unsigned char *dst, const unsigned char *src, ui32 n) +{ + // Caution if modifying memcpy_up! Overlap of dst and src must be special handled. +#ifndef X86X64 + unsigned char *end = dst + n; + while(dst < end) + { + *dst = *src; + dst++; + src++; + } +#else + ui32 f = 0; + do + { + *(ui32 *)(dst + f) = *(ui32 *)(src + f); + f += MINOFFSET + 1; + } + while (f < n); +#endif +} + +static __inline void update_hash(qlz_state_decompress *state, const unsigned char *s) +{ +#if QLZ_COMPRESSION_LEVEL == 1 + ui32 hash; + hash = hashat(s); + state->hash[hash].offset = s; + state->hash_counter[hash] = 1; +#elif QLZ_COMPRESSION_LEVEL == 2 + ui32 hash; + unsigned char c; + hash = hashat(s); + c = state->hash_counter[hash]; + state->hash[hash].offset[c & (QLZ_POINTERS - 1)] = s; + c++; + state->hash_counter[hash] = c; +#endif + (void)state; + (void)s; +} + +#if QLZ_COMPRESSION_LEVEL <= 2 +static void update_hash_upto(qlz_state_decompress *state, unsigned char **lh, const unsigned char *max) +{ + while(*lh < max) + { + (*lh)++; + update_hash(state, *lh); + } +} +#endif + +static size_t qlz_compress_core(const unsigned char *source, unsigned char *destination, size_t size, qlz_state_compress *state) +{ + const unsigned char *last_byte = source + size - 1; + const unsigned char *src = source; + unsigned char *cword_ptr = destination; + unsigned char *dst = destination + CWORD_LEN; + ui32 cword_val = 1U << 31; + const unsigned char *last_matchstart = last_byte - UNCONDITIONAL_MATCHLEN - UNCOMPRESSED_END; + ui32 fetch = 0; + unsigned int lits = 0; + + (void) lits; + + if(src <= last_matchstart) + fetch = fast_read(src, 3); + + while(src <= last_matchstart) + { + if ((cword_val & 1) == 1) + { + // store uncompressed if compression ratio is too low + if (src > source + (size >> 1) && dst - destination > src - source - ((src - source) >> 5)) + return 0; + + fast_write((cword_val >> 1) | (1U << 31), cword_ptr, CWORD_LEN); + + cword_ptr = dst; + dst += CWORD_LEN; + cword_val = 1U << 31; + fetch = fast_read(src, 3); + } +#if QLZ_COMPRESSION_LEVEL == 1 + { + const unsigned char *o; + ui32 hash, cached; + + hash = hash_func(fetch); + cached = fetch ^ state->hash[hash].cache; + state->hash[hash].cache = fetch; + + o = state->hash[hash].offset + OFFSET_BASE; + state->hash[hash].offset = CAST(src - OFFSET_BASE); + +#ifdef X86X64 + if ((cached & 0xffffff) == 0 && o != OFFSET_BASE && (src - o > MINOFFSET || (src == o + 1 && lits >= 3 && src > source + 3 && same(src - 3, 6)))) + { + if(cached != 0) + { +#else + if (cached == 0 && o != OFFSET_BASE && (src - o > MINOFFSET || (src == o + 1 && lits >= 3 && src > source + 3 && same(src - 3, 6)))) + { + if (*(o + 3) != *(src + 3)) + { +#endif + hash <<= 4; + cword_val = (cword_val >> 1) | (1U << 31); + fast_write((3 - 2) | hash, dst, 2); + src += 3; + dst += 2; + } + else + { + const unsigned char *old_src = src; + size_t matchlen; + hash <<= 4; + + cword_val = (cword_val >> 1) | (1U << 31); + src += 4; + + if(*(o + (src - old_src)) == *src) + { + src++; + if(*(o + (src - old_src)) == *src) + { + size_t q = last_byte - UNCOMPRESSED_END - (src - 5) + 1; + size_t remaining = q > 255 ? 255 : q; + src++; + while(*(o + (src - old_src)) == *src && (size_t)(src - old_src) < remaining) + src++; + } + } + + matchlen = src - old_src; + if (matchlen < 18) + { + fast_write((ui32)(matchlen - 2) | hash, dst, 2); + dst += 2; + } + else + { + fast_write((ui32)(matchlen << 16) | hash, dst, 3); + dst += 3; + } + } + fetch = fast_read(src, 3); + lits = 0; + } + else + { + lits++; + *dst = *src; + src++; + dst++; + cword_val = (cword_val >> 1); +#ifdef X86X64 + fetch = fast_read(src, 3); +#else + fetch = (fetch >> 8 & 0xffff) | (*(src + 2) << 16); +#endif + } + } +#elif QLZ_COMPRESSION_LEVEL >= 2 + { + const unsigned char *o, *offset2; + ui32 hash, matchlen, k, m, best_k = 0; + unsigned char c; + size_t remaining = (last_byte - UNCOMPRESSED_END - src + 1) > 255 ? 255 : (last_byte - UNCOMPRESSED_END - src + 1); + (void)best_k; + + + //hash = hashat(src); + fetch = fast_read(src, 3); + hash = hash_func(fetch); + + c = state->hash_counter[hash]; + + offset2 = state->hash[hash].offset[0]; + if(offset2 < src - MINOFFSET && c > 0 && ((fast_read(offset2, 3) ^ fetch) & 0xffffff) == 0) + { + matchlen = 3; + if(*(offset2 + matchlen) == *(src + matchlen)) + { + matchlen = 4; + while(*(offset2 + matchlen) == *(src + matchlen) && matchlen < remaining) + matchlen++; + } + } + else + matchlen = 0; + for(k = 1; k < QLZ_POINTERS && c > k; k++) + { + o = state->hash[hash].offset[k]; +#if QLZ_COMPRESSION_LEVEL == 3 + if(((fast_read(o, 3) ^ fetch) & 0xffffff) == 0 && o < src - MINOFFSET) +#elif QLZ_COMPRESSION_LEVEL == 2 + if(*(src + matchlen) == *(o + matchlen) && ((fast_read(o, 3) ^ fetch) & 0xffffff) == 0 && o < src - MINOFFSET) +#endif + { + m = 3; + while(*(o + m) == *(src + m) && m < remaining) + m++; +#if QLZ_COMPRESSION_LEVEL == 3 + if ((m > matchlen) || (m == matchlen && o > offset2)) +#elif QLZ_COMPRESSION_LEVEL == 2 + if (m > matchlen) +#endif + { + offset2 = o; + matchlen = m; + best_k = k; + } + } + } + o = offset2; + state->hash[hash].offset[c & (QLZ_POINTERS - 1)] = src; + c++; + state->hash_counter[hash] = c; + +#if QLZ_COMPRESSION_LEVEL == 3 + if(matchlen > 2 && src - o < 131071) + { + ui32 u; + size_t offset = src - o; + + for(u = 1; u < matchlen; u++) + { + hash = hashat(src + u); + c = state->hash_counter[hash]++; + state->hash[hash].offset[c & (QLZ_POINTERS - 1)] = src + u; + } + + cword_val = (cword_val >> 1) | (1U << 31); + src += matchlen; + + if(matchlen == 3 && offset <= 63) + { + *dst = (unsigned char)(offset << 2); + dst++; + } + else if (matchlen == 3 && offset <= 16383) + { + ui32 f = (ui32)((offset << 2) | 1); + fast_write(f, dst, 2); + dst += 2; + } + else if (matchlen <= 18 && offset <= 1023) + { + ui32 f = ((matchlen - 3) << 2) | ((ui32)offset << 6) | 2; + fast_write(f, dst, 2); + dst += 2; + } + + else if(matchlen <= 33) + { + ui32 f = ((matchlen - 2) << 2) | ((ui32)offset << 7) | 3; + fast_write(f, dst, 3); + dst += 3; + } + else + { + ui32 f = ((matchlen - 3) << 7) | ((ui32)offset << 15) | 3; + fast_write(f, dst, 4); + dst += 4; + } + } + else + { + *dst = *src; + src++; + dst++; + cword_val = (cword_val >> 1); + } +#elif QLZ_COMPRESSION_LEVEL == 2 + + if(matchlen > 2) + { + cword_val = (cword_val >> 1) | (1U << 31); + src += matchlen; + + if (matchlen < 10) + { + ui32 f = best_k | ((matchlen - 2) << 2) | (hash << 5); + fast_write(f, dst, 2); + dst += 2; + } + else + { + ui32 f = best_k | (matchlen << 16) | (hash << 5); + fast_write(f, dst, 3); + dst += 3; + } + } + else + { + *dst = *src; + src++; + dst++; + cword_val = (cword_val >> 1); + } +#endif + } +#endif + } + while (src <= last_byte) + { + if ((cword_val & 1) == 1) + { + fast_write((cword_val >> 1) | (1U << 31), cword_ptr, CWORD_LEN); + cword_ptr = dst; + dst += CWORD_LEN; + cword_val = 1U << 31; + } +#if QLZ_COMPRESSION_LEVEL < 3 + if (src <= last_byte - 3) + { +#if QLZ_COMPRESSION_LEVEL == 1 + ui32 hash, fetch; + fetch = fast_read(src, 3); + hash = hash_func(fetch); + state->hash[hash].offset = CAST(src - OFFSET_BASE); + state->hash[hash].cache = fetch; +#elif QLZ_COMPRESSION_LEVEL == 2 + ui32 hash; + unsigned char c; + hash = hashat(src); + c = state->hash_counter[hash]; + state->hash[hash].offset[c & (QLZ_POINTERS - 1)] = src; + c++; + state->hash_counter[hash] = c; +#endif + } +#endif + *dst = *src; + src++; + dst++; + cword_val = (cword_val >> 1); + } + + while((cword_val & 1) != 1) + cword_val = (cword_val >> 1); + + fast_write((cword_val >> 1) | (1U << 31), cword_ptr, CWORD_LEN); + + // min. size must be 9 bytes so that the qlz_size functions can take 9 bytes as argument + return dst - destination < 9 ? 9 : dst - destination; +} + +extern size_t qlz_size_header(const char *source); +static size_t qlz_decompress_core(const unsigned char *source, unsigned char *destination, size_t size, qlz_state_decompress *state, const unsigned char *history) +{ + const unsigned char *src = source + qlz_size_header((const char *)source); + unsigned char *dst = destination; + const unsigned char *last_destination_byte = destination + size - 1; + ui32 cword_val = 1; + const unsigned char *last_matchstart = last_destination_byte - UNCONDITIONAL_MATCHLEN - UNCOMPRESSED_END; + unsigned char *last_hashed = destination - 1; + const unsigned char *last_source_byte = source + qlz_size_compressed((const char *)source) - 1; + static const ui32 bitlut[16] = {4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0}; + + (void) last_source_byte; + (void) last_hashed; + (void) state; + (void) history; + + for(;;) + { + ui32 fetch; + + if (cword_val == 1) + { +#ifdef QLZ_MEMORY_SAFE + if(src + CWORD_LEN - 1 > last_source_byte) + return 0; +#endif + cword_val = fast_read(src, CWORD_LEN); + src += CWORD_LEN; + } + +#ifdef QLZ_MEMORY_SAFE + if(src + 4 - 1 > last_source_byte) + return 0; +#endif + + fetch = fast_read(src, 4); + + if ((cword_val & 1) == 1) + { + ui32 matchlen; + const unsigned char *offset2; + +#if QLZ_COMPRESSION_LEVEL == 1 + ui32 hash; + cword_val = cword_val >> 1; + hash = (fetch >> 4) & 0xfff; + offset2 = (const unsigned char *)(size_t)state->hash[hash].offset; + + if((fetch & 0xf) != 0) + { + matchlen = (fetch & 0xf) + 2; + src += 2; + } + else + { + matchlen = *(src + 2); + src += 3; + } + +#elif QLZ_COMPRESSION_LEVEL == 2 + ui32 hash; + unsigned char c; + cword_val = cword_val >> 1; + hash = (fetch >> 5) & 0x7ff; + c = (unsigned char)(fetch & 0x3); + offset2 = state->hash[hash].offset[c]; + + if((fetch & (28)) != 0) + { + matchlen = ((fetch >> 2) & 0x7) + 2; + src += 2; + } + else + { + matchlen = *(src + 2); + src += 3; + } + +#elif QLZ_COMPRESSION_LEVEL == 3 + ui32 offset; + cword_val = cword_val >> 1; + if ((fetch & 3) == 0) + { + offset = (fetch & 0xff) >> 2; + matchlen = 3; + src++; + } + else if ((fetch & 2) == 0) + { + offset = (fetch & 0xffff) >> 2; + matchlen = 3; + src += 2; + } + else if ((fetch & 1) == 0) + { + offset = (fetch & 0xffff) >> 6; + matchlen = ((fetch >> 2) & 15) + 3; + src += 2; + } + else if ((fetch & 127) != 3) + { + offset = (fetch >> 7) & 0x1ffff; + matchlen = ((fetch >> 2) & 0x1f) + 2; + src += 3; + } + else + { + offset = (fetch >> 15); + matchlen = ((fetch >> 7) & 255) + 3; + src += 4; + } + + offset2 = dst - offset; +#endif + +#ifdef QLZ_MEMORY_SAFE + if(offset2 < history || offset2 > dst - MINOFFSET - 1) + return 0; + + if(matchlen > (ui32)(last_destination_byte - dst - UNCOMPRESSED_END + 1)) + return 0; +#endif + + memcpy_up(dst, offset2, matchlen); + dst += matchlen; + +#if QLZ_COMPRESSION_LEVEL <= 2 + update_hash_upto(state, &last_hashed, dst - matchlen); + last_hashed = dst - 1; +#endif + } + else + { + if (dst < last_matchstart) + { + unsigned int n = bitlut[cword_val & 0xf]; +#ifdef X86X64 + *(ui32 *)dst = *(ui32 *)src; +#else + memcpy_up(dst, src, 4); +#endif + cword_val = cword_val >> n; + dst += n; + src += n; +#if QLZ_COMPRESSION_LEVEL <= 2 + update_hash_upto(state, &last_hashed, dst - 3); +#endif + } + else + { + while(dst <= last_destination_byte) + { + if (cword_val == 1) + { + src += CWORD_LEN; + cword_val = 1U << 31; + } +#ifdef QLZ_MEMORY_SAFE + if(src >= last_source_byte + 1) + return 0; +#endif + *dst = *src; + dst++; + src++; + cword_val = cword_val >> 1; + } + +#if QLZ_COMPRESSION_LEVEL <= 2 + update_hash_upto(state, &last_hashed, last_destination_byte - 3); // todo, use constant +#endif + return size; + } + + } + } +} + +size_t qlz_compress(const void *source, char *destination, size_t size, qlz_state_compress *state) +{ + size_t r; + ui32 compressed; + size_t base; + + if(size == 0 || size > 0xffffffff - 400) + return 0; + + if(size < 216) + base = 3; + else + base = 9; + +#if QLZ_STREAMING_BUFFER > 0 + if (state->stream_counter + size - 1 >= QLZ_STREAMING_BUFFER) +#endif + { + reset_table_compress(state); + r = base + qlz_compress_core((const unsigned char *)source, (unsigned char*)destination + base, size, state); +#if QLZ_STREAMING_BUFFER > 0 + reset_table_compress(state); +#endif + if(r == base) + { + memcpy(destination + base, source, size); + r = size + base; + compressed = 0; + } + else + { + compressed = 1; + } + state->stream_counter = 0; + } +#if QLZ_STREAMING_BUFFER > 0 + else + { + unsigned char *src = state->stream_buffer + state->stream_counter; + + memcpy(src, source, size); + r = base + qlz_compress_core(src, (unsigned char*)destination + base, size, state); + + if(r == base) + { + memcpy(destination + base, src, size); + r = size + base; + compressed = 0; + reset_table_compress(state); + } + else + { + compressed = 1; + } + state->stream_counter += size; + } +#endif + if(base == 3) + { + *destination = (unsigned char)(0 | compressed); + *(destination + 1) = (unsigned char)r; + *(destination + 2) = (unsigned char)size; + } + else + { + *destination = (unsigned char)(2 | compressed); + fast_write((ui32)r, destination + 1, 4); + fast_write((ui32)size, destination + 5, 4); + } + + *destination |= (QLZ_COMPRESSION_LEVEL << 2); + *destination |= (1 << 6); + *destination |= ((QLZ_STREAMING_BUFFER == 0 ? 0 : (QLZ_STREAMING_BUFFER == 100000 ? 1 : (QLZ_STREAMING_BUFFER == 1000000 ? 2 : 3))) << 4); + +// 76543210 +// 01SSLLHC + + return r; +} + +size_t qlz_decompress(const char *source, void *destination, qlz_state_decompress *state) +{ + size_t dsiz = qlz_size_decompressed(source); + +#if QLZ_STREAMING_BUFFER > 0 + if (state->stream_counter + qlz_size_decompressed(source) - 1 >= QLZ_STREAMING_BUFFER) +#endif + { + if((*source & 1) == 1) + { + reset_table_decompress(state); + dsiz = qlz_decompress_core((const unsigned char *)source, (unsigned char *)destination, dsiz, state, (const unsigned char *)destination); + } + else + { + memcpy(destination, source + qlz_size_header(source), dsiz); + } + state->stream_counter = 0; + reset_table_decompress(state); + } +#if QLZ_STREAMING_BUFFER > 0 + else + { + unsigned char *dst = state->stream_buffer + state->stream_counter; + if((*source & 1) == 1) + { + dsiz = qlz_decompress_core((const unsigned char *)source, dst, dsiz, state, (const unsigned char *)state->stream_buffer); + } + else + { + memcpy(dst, source + qlz_size_header(source), dsiz); + reset_table_decompress(state); + } + memcpy(destination, dst, dsiz); + state->stream_counter += dsiz; + } +#endif + return dsiz; +} diff --git a/src/query/Command.cpp b/src/query/Command.cpp new file mode 100644 index 0000000..ee7bc38 --- /dev/null +++ b/src/query/Command.cpp @@ -0,0 +1,243 @@ +#include "Command.h" +#include +#include +#include +#include +#include "log/LogUtils.h" +#include "escape.h" +#include "command2.h" + +using namespace std; + +namespace ts { + /* + Command Command::parse(const pipes::buffer_view& _buffer, bool expect_command) { //FIXME improve! + string buffer = _buffer.string(); + Command result; + + size_t nextSpace; + if(expect_command) { + nextSpace = buffer.find(' '); + if(nextSpace == -1){ + result._command = buffer; + return result; + } + + result._command = buffer.substr(0, nextSpace); + buffer = buffer.substr(nextSpace + 1); + } + + int splitIndex = 0; + do { + auto nextSplit = buffer.find('|'); + auto splitBuffer = buffer.substr(0, nextSplit); + if(nextSplit != std::string::npos) + buffer = buffer.substr(nextSplit + 1); + else + buffer = ""; + if(splitBuffer.empty()) continue; + + + std::string element; + ssize_t elementEnd; + do { + elementEnd = splitBuffer.find(' '); + element = splitBuffer.substr(0, elementEnd); + if(element.empty()) goto nextElement; + + if(element[0] == '-'){ + result.paramethers.push_back(element.substr(1)); + } else { + auto key = element.substr(0, element.find('=')); + key.erase(std::remove_if(key.begin(), key.end(), [](const char c){ return !((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_' || c == '-'); }), key.end()); + if(key.length() > 0 && (key.front() == ' ' || key.back() == ' ')) { + auto first = key.find_first_not_of(' '); + if(first == std::string::npos) continue; + auto last = key.find_last_not_of(' '); + key = key.substr(first, last - first); + } + std::string value; + if(element.find('=') != std::string::npos) + value = element.substr(element.find('=') + 1); + if(key.empty() && value.empty()) goto nextElement; + if(result[splitIndex].has(key)) goto nextElement; //No double insert. Sometimes malformed input 'banadd ip name=WolverinDEV time=3600 banreason=Test\sban! return_code=1:38 return_code=__1:38_1:38' + result[splitIndex][key] = query::unescape(value, true); + } + + nextElement: + if(elementEnd != std::string::npos && elementEnd + 1 < splitBuffer.size()) + splitBuffer = splitBuffer.substr(elementEnd + 1); + else + splitBuffer = ""; + } while(!splitBuffer.empty()); + splitIndex++; + } while(!buffer.empty()); + + + return result; + } + */ + + Command Command::parse(const pipes::buffer_view &buffer, bool expect_type, bool drop_non_utf8) { + string_view data{buffer.data_ptr(), buffer.length()}; + + Command result; + + size_t current_index = std::string::npos, end_index; + if(expect_type) { + current_index = data.find(' ', 0); + if(current_index == std::string::npos){ + result._command = std::string(data); + return result; + } else { + result._command = std::string(data.substr(0, current_index)); + } + } + + size_t bulk_index = 0; + while(++current_index > 0 || (current_index == 0 && !expect_type && (expect_type = true))) { + end_index = data.find_first_of(" |", current_index); + + if(end_index != current_index) { /* else we've found another space or a pipe */ + if(data[current_index] == '-') { + string trigger(data.substr(current_index + 1, end_index - current_index - 1)); + result.paramethers.push_back(trigger); + } else { + auto index_assign = data.find_first_of('=', current_index); + string key, value; + if(index_assign == string::npos || index_assign > end_index) { + key = data.substr(current_index, end_index - current_index); + } else { + key = data.substr(current_index, index_assign - current_index); + try { + value = query::unescape(string(data.substr(index_assign + 1, end_index - index_assign - 1)), true); + } catch(std::invalid_argument& ex) { + /* invalid character at index X */ + if(!drop_non_utf8) + throw; + goto skip_assign; + } + } + + { + const static auto key_validator = [](char c){ return !((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_' || c == '-'); }; + auto invalid_index = find_if(key.begin(), key.end(), key_validator); + if(invalid_index != key.end()) + throw command_malformed_exception(current_index + distance(key.begin(), invalid_index)); + + if(!key.empty() && !result[bulk_index].has(key)) //No double insert. Sometimes malformed input 'banadd ip name=WolverinDEV time=3600 banreason=Test\sban! return_code=1:38 return_code=__1:38_1:38' + result[bulk_index][key] = value; + } + skip_assign:; + } + } + + if(end_index < data.length() && data[end_index] == '|') + bulk_index++; + current_index = end_index; + } + + return result; + } + + Command::Command(const std::string& command) { + this->_command = command; + } + + Command::Command(const std::string& command, std::initializer_list parms) : Command(command, {parms}) { } + + + Command::Command(const std::string& command, std::initializer_list> bulks) : Command(command) { + int blkIndex = 0; + for(auto blk = bulks.begin(); blk != bulks.end(); blk++, blkIndex++) + for(auto it = blk->begin(); it != blk->end(); it++) + operator[](blkIndex).parms.push_back(*it); + } + + Command::Command(const Command& other) { + this->_command = other._command; + this->bulks = other.bulks; + this->paramethers = other.paramethers; + } + + Command::Command() = default; + + Command::~Command() = default; + + std::string Command::build(bool escaped) const { + std::stringstream out; + out << this->_command; + + bool bulkBegin = false; + for(auto it = this->bulks.begin(); it != this->bulks.end(); it++){ + for(const auto& elm : it->parms) { + if(!bulkBegin) out << " "; + else bulkBegin = false; + if(elm.key().empty()) { + out << elm.value(); /* special case used for permission list */ + } else { + out << elm.key(); + if(!elm.value().empty()){ + out << "=" << (escaped ? query::escape(elm.value()) : ("'" + elm.value() + "'")); + } + } + } + if(it + 1 != this->bulks.end()){ + out << "|"; + bulkBegin = true; + } + } + for(const auto& parm : this->paramethers) out << " -" << parm; + + auto str = out.str(); + if(str.length() > 1) if(str[0] == ' ') str = str.substr(1); + return str; + } + +#ifdef HAVE_JSON + Json::Value Command::buildJson() const { + Json::Value result; + result["command"] = this->_command; + + int index = 0; + for(auto it = this->bulks.begin(); it != this->bulks.end(); it++){ + Json::Value& node = result["data"][index++]; + for(const auto& elm : it->parms) + node[elm.key()] = elm.value(); + } + + Json::Value& triggers = result["triggers"]; + index = 0; + for(const auto& parm : this->paramethers) + triggers[index++] = parm; + + return result; + } +#endif + + std::deque Command::parms() { return this->paramethers; } + bool Command::hasParm(std::string parm) { return std::find(this->paramethers.begin(), this->paramethers.end(), parm) != this->paramethers.end(); } + + void Command::toggleParm(const std::string& key, bool flag) { + if(flag){ + if(!hasParm(key)) this->paramethers.push_back(key); + } else { + auto found = std::find(this->paramethers.begin(), this->paramethers.end(), key); + if(found != this->paramethers.end()) + this->paramethers.erase(found); + }; + } + + void Command::reverseBulks() { + std::reverse(this->bulks.begin(), this->bulks.end()); + } + + void Command::pop_bulk() { + if(!this->bulks.empty()) + this->bulks.pop_front(); + } + + void Command::push_bulk_front() { + this->bulks.push_front(ParameterBulk{}); + } +} diff --git a/src/query/Command.h b/src/query/Command.h new file mode 100644 index 0000000..0947312 --- /dev/null +++ b/src/query/Command.h @@ -0,0 +1,218 @@ +#pragma once + +#ifdef byte + #ifndef WIN32 + #warning The byte macro is already defined! Undefining it! + #endif + #undef byte +#endif + +#include +#include +#include +#include +#include +#include +#include +#include "../Variable.h" + +#ifdef HAVE_JSON + #include +#endif + +namespace ts { +#define PARM_TYPE(type, fromString, toString) \ + BaseCommandParm(std::string key, type value) : BaseCommandParm(std::pair(key, "")) {\ + toString; \ + } \ +BaseCommandParm& operator=(type value){ \ + toString; \ + return *this; \ +} \ + \ +operator type(){ \ + fromString; \ +} + + class Command; + class ValueList; + + + //PARM_TYPE(ts::Property, return ts::Property(nullptr, key(), value(), 0), f_value() = value.value()); + + class ParameterBulk { + friend class Command; + friend class ValueList; + public: + ParameterBulk(const ParameterBulk& ref) : parms(ref.parms) {} + + + variable operator[](size_t index){ + if(parms.size() > index) return parms[index]; + return variable{"", "", VARTYPE_NULL}; + } + + const variable& operator[](const std::string& key) const { + for(const auto& elm : parms) + if(elm.key() == key){ + return elm; + } + + throw std::invalid_argument("could not find key [" + key + "]"); + } + + variable& operator[](const std::string& key) { + for(auto& elm : parms) + if(elm.key() == key){ + return elm; + } + this->parms.push_back(variable(key, "", VariableType::VARTYPE_NULL)); + return this->operator[](key); + } + + bool has(const std::string& key) const { + for(const auto& elm : parms) + if(elm.key() == key && elm.type() != VariableType::VARTYPE_NULL) return true; + return false; + } + + std::deque keys() const { + std::deque result; + for(const auto& elm : parms) + result.push_back(elm.key()); + return result; + } + + ParameterBulk& operator=(const ParameterBulk& ref){ + parms = ref.parms; + return *this; + } + private: + ParameterBulk() {} + ParameterBulk(std::deque p) : parms(p) {} + std::deque parms; + }; + + class ValueList { + friend class Command; + public: + ValueList() = delete; + ValueList(const ValueList& ref) : key(ref.key), bulkList(ref.bulkList) {} + + variable operator[](int index){ + while(index >= bulkList.size()) bulkList.push_back(ParameterBulk()); + return bulkList[index][key]; + } + + variable first() const { + int index = 0; + while(index < bulkList.size() && !bulkList[index].has(key)) index++; + if(index < bulkList.size()) return bulkList[index][key]; + + return variable{this->key, "", VariableType::VARTYPE_NULL}; + throw std::invalid_argument("could not find key [" + key + "]"); + } + + size_t size(){ + size_t count = 0; + for(const auto& blk : this->bulkList) + if(blk.has(this->key)) count++; + return count; + } + + template + ValueList& operator=(T var){ operator[](0) = var; return *this; } + ValueList& operator=(ts::ValueList& var){ operator[](0) = var.first().value(); return *this; } + + + template + T as() const { return first().as(); } + + template + operator T() { return as(); } + + template + bool operator==(T other){ return as() == other; } + template + bool operator!=(T other){ return as() != other; } + + std::string string() const { return as(); } + + friend std::ostream& operator<<(std::ostream&, const ValueList&); + private: + ValueList(std::string& key, std::deque& bulkList) : key(key), bulkList(bulkList) {} + std::string key; + public: + std::deque& bulkList; + }; + + inline std::ostream& operator<<(std::ostream& stream,const ValueList& list) { + stream << "{ Key: " << list.key << " = ["; + for(auto it = list.bulkList.begin(); it != list.bulkList.end(); it++) + if(it->has(list.key)) { + stream << (*it)[list.key].value(); + if(it + 1 != list.bulkList.end()) + stream << ", "; + } + stream << "]}"; + return stream; + } + + class Command { + public: + static Command parse(const pipes::buffer_view& buffer, bool expect_command = true, bool drop_non_utf8 = false); + + explicit Command(const std::string& command); + explicit Command(const std::string& command, std::initializer_list); + explicit Command(const std::string& command, std::initializer_list>); + + Command(const Command&); + ~Command(); + + inline std::string command() const { return getCommand(); } + std::string getCommand() const { return _command; } + + std::string build(bool escaped = true) const; + +#ifdef HAVE_JSON + Json::Value buildJson() const; +#endif + + const ParameterBulk& operator[](int index) const { + if(bulks.size() <= index) throw std::invalid_argument("got out of length"); + return bulks[index]; + } + + ParameterBulk& operator[](int index){ + while(bulks.size() <= index) bulks.push_back(ParameterBulk{}); + return bulks[index]; + } + + ValueList operator[](std::string key){ + return ValueList(key, bulks); + } + + size_t bulkCount() const { return bulks.size(); } + void pop_bulk(); + void push_bulk_front(); + + bool hasParm(std::string); + std::deque parms(); + void enableParm(const std::string& key){ toggleParm(key, true); } + void disableParm(const std::string& key){ toggleParm(key, false); } + void toggleParm(const std::string& key, bool flag); + + void reverseBulks(); + private: + Command(); + + std::string _command; + std::deque bulks; + std::deque paramethers; + }; +} + +DEFINE_VARIABLE_TRANSFORM_TO_STR(ts::ValueList, in.string()); +DEFINE_VARIABLE_TRANSFORM_TYPE(ts::ValueList, VARTYPE_TEXT); + +#undef PARM_TYPE \ No newline at end of file diff --git a/src/query/command2.cpp b/src/query/command2.cpp new file mode 100644 index 0000000..c1e8aba --- /dev/null +++ b/src/query/command2.cpp @@ -0,0 +1,251 @@ +#include "command2.h" +#include "escape.h" +#include +#include + +using namespace std; +using namespace ts; + +#define CONVERTER_ST(type, m_decode, m_encode) \ +CONVERTER_METHOD_DECODE(type, impl::converter_ ##type ##_decode) { \ + m_decode; \ +} \ + \ +CONVERTER_METHOD_ENCODE(type, impl::converter_ ##type ##_encode) { \ + m_encode \ +} + +#define CONVERTER_PRIMITIVE_ST(type, m_decode) CONVERTER_ST(type, \ + return m_decode;, \ + return std::to_string(std::any_cast(value)); \ +) + +CONVERTER_PRIMITIVE_ST(int8_t, std::stol(str) & 0xFF); +CONVERTER_PRIMITIVE_ST(uint8_t, std::stoul(str) & 0xFF); + +CONVERTER_PRIMITIVE_ST(int16_t, std::stol(str) & 0xFFFF); +CONVERTER_PRIMITIVE_ST(uint16_t, std::stoul(str) & 0xFFFF); + +CONVERTER_PRIMITIVE_ST(int32_t, std::stol(str) & 0xFFFFFFFF); +CONVERTER_PRIMITIVE_ST(uint32_t, std::stoul(str) & 0xFFFFFFFF); + +CONVERTER_PRIMITIVE_ST(int64_t, std::stoll(str)); +CONVERTER_PRIMITIVE_ST(uint64_t, std::stoull(str)) + +CONVERTER_PRIMITIVE_ST(bool, str == "1" || str == "true"); +CONVERTER_PRIMITIVE_ST(float, std::stof(str)); +CONVERTER_PRIMITIVE_ST(double, std::stod(str)); +CONVERTER_PRIMITIVE_ST(long_double, std::stold(str)); + +CONVERTER_ST(std__string, return str;, return std::any_cast(value);); +CONVERTER_ST(const_char__ , return str.c_str();, return std::string(std::any_cast(value));); + +command::command(const std::string& command, bool editable) { + this->handle = make_shared(); + this->handle->editiable = editable; + this->handle->command = command; +} + +std::string command::identifier() const { + return this->handle->command; +} + +void command::set_identifier(const std::string &command) { + this->handle->command = command; +} + +command_bulk command::bulk(size_t index) { + if(this->handle->bulks.size() <= index) { + if(!this->handle->editiable) + throw command_bulk_exceed_index_exception(); + + while(this->handle->bulks.size() <= index) { + auto bulk = make_shared(); + bulk->handle = this->handle.get(); + this->handle->bulks.push_back(std::move(bulk)); + } + } + + return {index, this->handle->bulks[index]}; +} + +const command_bulk command::bulk(size_t index) const { + if(this->handle->bulks.size() <= index) + throw command_bulk_exceed_index_exception(); + + return {index, this->handle->bulks[index]}; +} + +size_t command::bulk_count() const { + return this->handle->bulks.size(); +} + + +command_entry command::value(const std::string &key) { + return this->bulk(0)[key]; +} + +const command_entry command::value(const std::string &key) const { + return this->bulk(0)[key]; +} + +bool command::has_value(const std::string &key) const { + if(this->bulk_count() == 0) return false; + + return this->bulk(0).has(key); +} + +bool command::has_trigger(const std::string &key) const { + for(const auto& trigger : this->handle->triggers) + if(trigger == key) + return true; + return false; +} + +void command::set_trigger(const std::string &key, bool value) { + auto& triggers = this->handle->triggers; + auto it = find(triggers.begin(), triggers.end(), key); + if(it == triggers.end() && value) { + triggers.push_back(key); + } else if(it != triggers.end() && !value) { + triggers.erase(it); + } +} + +command command::parse(const std::string_view &data, bool expect_type, bool drop_non_utf8) { + command result; + + size_t current_index = std::string::npos, end_index; + if(expect_type) { + current_index = data.find(' ', 0); + if(current_index == std::string::npos){ + result.set_identifier(std::string(data)); + return result; + } else { + result.set_identifier(std::string(data.substr(0, current_index))); + } + } + + size_t bulk_index = 0; + while(++current_index > 0 || (current_index == 0 && !expect_type && (expect_type = true))) { + end_index = data.find_first_of(" |", current_index); + + if(end_index != current_index) { /* else we've found another space or a pipe */ + if(data[current_index] == '-') { + string trigger(data.substr(current_index + 1, end_index - current_index - 1)); + result.set_trigger(trigger); + } else { + auto index_assign = data.find_first_of('=', current_index); + string key, value; + if(index_assign == string::npos || index_assign > end_index) { + key = data.substr(current_index, end_index - current_index); + } else { + key = data.substr(current_index, index_assign - current_index); + try { + value = query::unescape(string(data.substr(index_assign + 1, end_index - index_assign - 1)), true); + } catch(std::invalid_argument& ex) { + /* invalid character at index X */ + if(!drop_non_utf8) + throw; + goto skip_assign; + } + } + + { + const static auto key_validator = [](char c){ return !((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_' || c == '-'); }; + auto invalid_index = find_if(key.begin(), key.end(), key_validator); + if(invalid_index != key.end()) + throw command_malformed_exception(current_index + distance(key.begin(), invalid_index)); + + if(!key.empty() && !result[bulk_index].has(key)) + result[bulk_index][key] = value; + } + + skip_assign:; + } + } + + if(end_index < data.length() && data[end_index] == '|') + bulk_index++; + current_index = end_index; + } + + return result; +} + +std::string command::build(ts::command::format::value type) { + if(type == format::QUERY || type == format::BRACE_ESCAPED_QUERY) { + std::stringstream ss; + + if(!this->handle->command.empty()) + ss << this->handle->command << " "; + + if(!this->handle->bulks.empty()) { + auto max_bulk_index = this->handle->bulks.size() - 1; + for(size_t bulk_index = 0; bulk_index <= max_bulk_index; bulk_index++) { + auto& bulk = this->handle->bulks[bulk_index]; + if(bulk->values.empty()) continue; //Do not remove me! + + auto max_pair_index = bulk->values.size() - 1; + auto pair_it = bulk->values.begin(); + + for(size_t pair_index = 0; pair_index <= max_pair_index; pair_index++) { + auto pair = *(pair_it++); + + auto value = pair.second->casted ? pair.second->to_string(pair.second->value) : pair.second->value.has_value() ? any_cast(pair.second->value) : ""; + ss << pair.first << "="; + if(type == format::BRACE_ESCAPED_QUERY) { + ss << "\"" << value << "\""; + } else ss << query::escape(value); + + if(pair_index != max_pair_index) + ss << " "; + } + + if(bulk_index != max_bulk_index) + ss << " | "; + } + } + + if(!this->handle->triggers.empty()) { + auto max_trigger_index = this->handle->triggers.size() - 1; + for(size_t trigger_index = 0; trigger_index <= max_trigger_index; trigger_index++) { + ss << " -" << this->handle->triggers[trigger_index]; + } + } + + return ss.str(); + } + return ""; +} + +bool command_bulk::has(const std::string &key) const { + auto& values = this->handle->values; + auto index = values.find(key); + return index != values.end(); +} + +command_entry command_bulk::value(const std::string &key) { + auto& values = this->handle->values; + auto index = values.find(key); + if(index == values.end()) { + if(!this->handle->handle->editiable) + throw command_value_missing_exception(this->bulk_index, key); + + auto result = make_shared(); + values[key] = result; + return command_entry(result); + } + return command_entry(index->second); +} + +const command_entry command_bulk::value(const std::string &key) const { + auto& values = this->handle->values; + auto index = values.find(key); + if(index == values.end()) + throw command_value_missing_exception(this->bulk_index, key); + + return command_entry(index->second); +} + +command_entry command_entry::empty{}; \ No newline at end of file diff --git a/src/query/command2.h b/src/query/command2.h new file mode 100644 index 0000000..7a9cb27 --- /dev/null +++ b/src/query/command2.h @@ -0,0 +1,713 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Error.h" + +#include "command_exception.h" +#include "converters/converter.h" + +#ifdef WIN32 + #define __attribute__used +#else + #define __attribute__used __attribute__((used)) +#endif + +namespace ts { + /* data impl stuff */ + namespace impl { + struct command_data; + + struct command_value { + bool casted = false; /* true if value isn't a std::string */ + std::any value; + std::string(*to_string)(const std::any&); + }; + + struct command_bulk { + command_data* handle; + std::map> values; + }; + + struct command_data { + std::string command; + bool editiable; + std::deque> bulks; + std::deque triggers; + }; + } + + /* Container stuff */ + class command_bulk; + class command_entry; + class command { + public: + struct format { + enum value { + QUERY, + BRACE_ESCAPED_QUERY, + JSON + }; + }; + static command parse(const std::string_view& /* command data */, bool /* expect type */ = true, bool /* drop non UTF-8 characters */ = false); + explicit command(const std::string& /* command */ = "", bool /* editable */ = true); + + std::string identifier() const; + void set_identifier(const std::string& /* command */); + + command_bulk bulk(size_t /* bulk index */); + const command_bulk bulk(size_t /* bulk index */) const; + size_t bulk_count() const; + inline command_bulk operator[](size_t /* index */); + inline const command_bulk operator[](size_t /* index */) const; + + command_entry value(const std::string& /* key */); + const command_entry value(const std::string& /* key */) const; + bool has_value(const std::string& /* key */) const; + command_entry operator[](const std::string& /* key */); + const command_entry operator[](const std::string& /* key */) const; + + bool has_trigger(const std::string& /* key */) const; + void set_trigger(const std::string& /* key */, bool /* value */ = true); + + std::string build(format::value /* format */ = format::QUERY); + /* TODO add a json object build method */ + private: + std::shared_ptr handle; + }; + + class command_bulk { + friend class command; + public: + bool has(const std::string& /* key */) const; + command_entry value(const std::string& /* key */); + command_entry const value(const std::string& /* key */) const; + + + inline command_entry operator[](const std::string& /* key */); + inline const command_entry operator[](const std::string& /* key */) const; + private: + command_bulk(size_t index, std::shared_ptr handle) : bulk_index(index), handle(std::move(handle)) {} + + size_t bulk_index; + std::shared_ptr handle; + }; + + class command_entry { + public: + static command_entry empty; + + command_entry() : handle(std::make_shared()) {} + command_entry(const command_entry& ref) : handle(ref.handle) {} + command_entry(command_entry&& ref) : handle(std::move(ref.handle)) {} + + command_entry&operator=(const command_entry& other) { + this->handle = other.handle; + return *this; + } + + inline bool is_empty() const { return !this->handle->value.has_value(); } + + command_entry& reset() { + this->handle->value.reset(); + return *this; + } + + const std::string string() const { + if(this->is_empty()) return ""; + + if(!this->handle->casted || this->handle->value.type() == typeid(std::string)) //No cast needed + return std::any_cast(this->handle->value); + + if(!this->handle->to_string) throw command_cannot_uncast_exception(); + return this->handle->to_string(this->handle->value); + } + + inline const std::string value() const { return (std::string) this->string(); } + + inline operator std::string() const { + return this->string(); + } + + template ::value && !std::is_same::value, int> = 0> + T as() { + static_assert(converter::supported, "Target type isn't supported!"); + static_assert(!converter::supported || converter::from_string, "Target type dosn't support parsing"); + + if(this->is_empty()) return T(); + + if(this->handle->casted) { + if(this->handle->value.type() == typeid(T)) + return std::any_cast(this->handle->value); + else + throw command_casted_exception(); + } else { + const auto& ref = std::any_cast(this->handle->value); + this->handle->value = converter::from_string(ref); + this->handle->to_string = converter::to_string; + this->handle->casted = true; + } + + return std::any_cast(this->handle->value); + } + + template ::value, int> = 0> + T as() { + return this->string(); + } + + template + inline operator T() { + return this->as(); + } + + command_entry& melt() { + if(this->handle->casted) { + this->handle->value = this->handle->to_string(this->handle->value); + this->handle->casted = false; + this->handle->to_string = nullptr; + } + return *this; + } + + template ::value && !std::is_same::value && !std::is_same::value, int>::type = 0> + void set(const T& value) { + static_assert(converter::supported, "Target type isn't supported!"); + static_assert(!converter::supported || converter::to_string, "Target type dosn't support encode"); + + this->handle->casted = true; + this->handle->value = std::move(value); + this->handle->to_string = converter::to_string; + } + + template ::value, int>::type = 0> + void set(const T& value) { + this->handle->value = value; + this->handle->casted = false; + this->handle->to_string = nullptr; + } + + template + void set(const char (&string)[N]) { + this->set(std::string(string, N - 1)); + } + + template + command_entry&operator=(const T& value) { + this->set(value); + return *this; + } + + explicit command_entry(std::shared_ptr handle) : handle(std::move(handle)) {} + + private: + std::shared_ptr handle; + }; + + namespace descriptor { + namespace tliterals { + template + using tstring = std::integer_sequence; + +#ifndef WIN32 + template + constexpr tstring operator""_tstr() { return { }; } +#endif + template + struct tliteral; + + template + struct tliteral> { + static constexpr char string[sizeof...(elements) + 1] = { elements..., '\0' }; + }; + } + + namespace impl { + namespace templates { + template + struct _or_ { + constexpr static bool value = false; + }; + + template + struct _or_ { + constexpr static bool value = T || _or_::value; + }; + + template + struct index; + + template + struct tuple_index; + + template + struct index : std::integral_constant + { }; + + template + struct index : std::integral_constant::value> + { }; + + template + struct tuple_index> : std::integral_constant::value> + { }; + + template + struct remove_cr { + typedef T type; + }; + + template + struct remove_cr { + typedef T type; + }; + } + + struct base; + + template + struct field; + + struct field_data; + struct field_base; + struct optional_extend; + struct bulk_extend; + + template + struct command_parser { + constexpr static bool supported = false; + }; + + inline void parse_field(const std::shared_ptr& description, field_base* field, command& cmd); + + struct option_data { + bool bulked; + bool optional; + }; + + template + struct options { + using object_data_t = option_data; + static constexpr auto is_bulked = bulked_t; + static constexpr auto is_optional = optional_t; + + protected: + inline static object_data_t options_object() { + return { + is_bulked, + is_optional + }; + } + }; + using default_options = options; + + struct base { }; + + struct base_data { + int type; /* 1 = field | 2 = switch | 3 = command handle */ + option_data options; + }; + + struct field_data : public base_data { + const char* key; + const std::type_info& field_type; + + void* from_string; + void* to_string; + }; + + struct field_base { + typedef std::vector&(*ref_values_fn)(); + static inline std::vector& ref_values(const void* _this) { + void** vtable = *(void***) _this; + return ((ref_values_fn) vtable[0])(); + } + }; + + template + struct field : public base, public options, public extends... { + friend struct command_parser>; + static_assert(converter::supported, "Target type isn't supported!"); + static_assert(!converter::supported || converter::from_string, "Target type dosn't support parsing"); + + protected: + using object_t = field_data; + using value_type = value_type_t; + static constexpr auto key = key_t::string; + static constexpr auto from_string = converter::from_string; + + public: + + template ::value...>::value, int> = 0 */> + using as_optional = field, optional_extend, extends...>; + + template ::value...>::value, int> = 0 */> + using as_bulked = field, bulk_extend, extends...>; + + using optional = as_optional; + using bulked = as_bulked; + + inline static std::shared_ptr describe() { + return std::make_shared( + object_t { + 1, + options::options_object(), + key, + typeid(value_type_t), + (void*) converter::from_string, + (void*) converter::to_string + } + ); + } + + inline value_type_t value() const { + command_entry& value = this->get_command_entry(); + return value.as(); + } + + inline command_entry& get_command_entry() const { + if(this->values.empty()) + throw command_value_missing_exception{0, key}; + + const auto& front = this->values.front(); + return *(command_entry*) &front; + } + + template + inline T as() const { + command_entry& value = this->get_command_entry(); + return value.as(); + } + + template + inline operator T() const { + return this->as(); + } + + protected: + /* ATTENTION: This must be placed at index 0 within the VTable */ + virtual __attribute__used std::vector& _v_ref_values() { + return this->values; + } + + std::vector values; + }; + + struct optional_extend { + public: + inline bool has_value() const { + auto& values = field_base::ref_values(this); + return !values.empty() && !values[0].is_empty(); + } + + template + inline T get_or(T&& value = T{}) const { + if(this->has_value()) + return field_base::ref_values(this).front().as(); + return value; + } + }; + + struct bulk_extend { + public: + inline bool has_index(size_t index) const { + return !this->at(index).is_empty(); + } + + inline size_t length() const { return field_base::ref_values(this).size(); } + + inline command_entry at(size_t index) const { + auto& values = field_base::ref_values(this); + if(index > values.size()) + throw command_bulk_exceed_index_exception(); + + return values[index]; + } + + inline command_entry operator[](size_t index) const { + return this->at(index); + } + }; + + struct trigger_data : public base_data { + const char* key; + }; + + struct trigger_base : public base { + typedef bool&(*ref_flag_fn)(); + static inline bool& ref_flag(void* _this) { + void** vtable = *(void***) _this; + return ((ref_flag_fn) vtable[0])(); + } + }; + + template + struct trigger : public trigger_base, public options { + protected: + static constexpr auto key = key_t::string; + public: + using object_t = trigger_data; + + inline static std::shared_ptr describe() { + return std::make_shared( + object_t { + 2, + options::options_object(), + key + } + ); + } + + inline bool is_set() const { return this->flag_set; } + operator bool() const { return this->flag_set; } + + private: + /* ATTENTION: This must be placed at index 0 within the VTable */ + virtual __attribute__used bool& _v_ref_values() { + return this->flag_set; + } + + bool flag_set; + }; + + struct command_handle_data : public base_data { }; + + struct command_handle_base : public base { + typedef command&(*ref_flag_fn)(); + static inline command& ref_command(void* _this) { + void** vtable = *(void***) _this; + return ((ref_flag_fn) vtable[0])(); + } + }; + + template + struct command_handle : public command_handle_base, public options { + public: + using object_t = command_handle_data; + inline static std::shared_ptr describe() { + return std::make_shared( + command_handle_data { + 3, + options::options_object() + } + ); + } + + inline command get_command() { return this->_command; } + operator command() { return this->_command; } + + inline command* operator->() const noexcept { + return (command*) &_command; + } + + /* + template + auto operator[](Arg &&arg) -> decltype(get_command()[std::forward(arg)]) { + return _command[std::forward(arg)]; + } + */ + template + decltype(std::declval()[std::forward(Arg{})]) operator[](Arg &&arg) { + return _command[std::forward(arg)]; + } + private: + /* ATTENTION: This must be placed at index 0 within the VTable */ + virtual __attribute__used class command& _v_ref_values() { + return this->_command; + } + + command _command; + }; + + typedef std::vector> function_descriptors_t; + + template + struct describe { + inline static void apply(function_descriptors_t& result) {} + }; + + template + struct describe { + inline static void apply(function_descriptors_t& result) { + result.push_back(T::describe()); + describe::apply(result); + } + }; + + inline void parse_field(const std::shared_ptr& description, void* field, command& cmd) { + //if(!description->options.optional && !cmd.has_value(description->key)) + // throw command_value_missing_exception(); + + auto& values = field_base::ref_values(field); + values.clear(); + + if(description->options.bulked) { + values.resize(cmd.bulk_count()); + for(size_t bulk_index = 0; bulk_index < cmd.bulk_count(); bulk_index++) { + if(!cmd[bulk_index].has(description->key)) { + if(!description->options.optional) + throw command_value_missing_exception(bulk_index, description->key); + else + values[bulk_index] = command_entry::empty; + } else { + values[bulk_index] = cmd[bulk_index][description->key]; + } + } + } else { + if(!cmd.has_value(description->key)) { + if(!description->options.optional) + throw command_value_missing_exception(0, description->key); + else + values.push_back(command_entry::empty); + } else + values.push_back(cmd[description->key]); + } + } + + template + struct command_parser> { + constexpr static bool supported = true; + + typedef field field_t; + using descriptor_t = std::shared_ptr; + + inline static descriptor_t describe() { + return field_t::describe(); + } + + inline static field_t apply(descriptor_t& descriptor, command& cmd) { + assert(descriptor->type == 1); + + field_t result{}; + parse_field(descriptor, (void*) &result, cmd); + + return result; + } + }; + + template + struct command_parser> { + constexpr static bool supported = true; + + typedef trigger trigger_t; + using descriptor_t = std::shared_ptr; + + inline static descriptor_t describe() { + return trigger_t::describe(); + } + + inline static trigger_t apply(descriptor_t& descriptor, command& cmd) { + assert(descriptor->type == 2); + + trigger_t result{}; + trigger_base::ref_flag(&result) = cmd.has_trigger(descriptor->key); + return result; + } + }; + + template + struct command_parser> { + constexpr static bool supported = true; + + typedef command_handle command_handle_t; + using descriptor_t = std::shared_ptr; + + inline static descriptor_t describe() { + return command_handle_t::describe(); + } + + inline static command_handle_t apply(descriptor_t& descriptor, command& cmd) { + assert(descriptor->type == 3); + + command_handle_t result{}; + command_handle_base::ref_command(&result) = cmd; + return result; + } + }; + + template + struct command_parser { + constexpr static bool supported = std::is_same::type>::type, command>::value; + static_assert(supported, "This type isn't supported!"); + + using descriptor_t = std::shared_ptr; + + inline static descriptor_t describe() { + return nullptr; + } + + inline static command& apply(descriptor_t& descriptor, command& cmd) { + return cmd; + } + }; + } + + /* direct use of command is possible as well! */ + using command_handle = impl::command_handle; + + template + using field = impl::field; + + template + using trigger = impl::trigger; + + template + inline impl::function_descriptors_t describe_function() { + impl::function_descriptors_t result; + impl::describe::apply(result); + return result; + } + + struct invocable_function { + void operator()(command& command) { + this->invoke(command); + } + + virtual void invoke(command& command) = 0; + }; + + template + struct typed_invocable_function : public invocable_function { + using args_tuple_t = std::tuple; + template + using command_parser_t = impl::command_parser::type>; + using descriptors_t = std::tuple::descriptor_t...>; + + descriptors_t descriptors; + void(*function)(args_t...); + + void invoke(command& command) override { + function(command_parser_t::apply(std::get::value>(descriptors), command)...); + } + }; + + template > + std::shared_ptr parse_function(void(*function)(args_t...)) { + auto result = std::make_shared(); + result->function = function; + result->descriptors = {impl::command_parser::type>::describe()...}; + return result; + } + + template + void invoke_function(void(*function)(args_t...), command& command) { + auto descriptor = parse_function(function); + (*descriptor)(command); + } + + /* converts a literal into a template literal */ + #define _tlit(literal) ::ts::descriptor::tliterals::tliteral + #define tl(lit) _tlit(lit) + } + + //using desc = descriptor::base; +} + +#include "command_internal.h" \ No newline at end of file diff --git a/src/query/command_constants.h b/src/query/command_constants.h new file mode 100644 index 0000000..ac7baf0 --- /dev/null +++ b/src/query/command_constants.h @@ -0,0 +1,28 @@ +#pragma once + +#include "Definitions.h" +#include "command2.h" + +namespace ts { + namespace cconstants { + + typedef descriptor::field return_code; + + typedef descriptor::field server_id; + + typedef descriptor::field client_id; + typedef descriptor::field client_database_id; + + typedef descriptor::field channel_id; + typedef descriptor::field channel_parent_id; + + typedef descriptor::field channel_group_id; + typedef descriptor::field server_group_id; + + //FIXME + /* typedef descriptor::field permission_id; + typedef descriptor::field permission_name; + typedef descriptor::field permission_value; + */ + } +} \ No newline at end of file diff --git a/src/query/command_exception.h b/src/query/command_exception.h new file mode 100644 index 0000000..898bf3f --- /dev/null +++ b/src/query/command_exception.h @@ -0,0 +1,26 @@ +#pragma once + +namespace ts { + class command_exception : public std::exception {}; + + class command_casted_exception : public command_exception { }; + class command_cannot_uncast_exception : public command_exception { }; + class command_bulk_exceed_index_exception : public command_exception { }; + class command_value_missing_exception : public command_exception { + public: + command_value_missing_exception(size_t index, std::string key) : _index(index), _key(move(key)) { } + + inline size_t index() const { return this->_index; } + inline std::string key() const { return this->_key; } + private: + size_t _index; + std::string _key; + }; + class command_malformed_exception : public command_exception { + public: + command_malformed_exception(size_t index) : _index(index) {} + inline size_t index() const { return this->_index; } + private: + size_t _index; + }; +} \ No newline at end of file diff --git a/src/query/command_internal.h b/src/query/command_internal.h new file mode 100644 index 0000000..6c50daf --- /dev/null +++ b/src/query/command_internal.h @@ -0,0 +1,32 @@ +#pragma once + +namespace ts { + /* some inlineable implementations */ + inline command_bulk command::operator[](size_t index) { + return this->bulk(index); + } + + inline command_entry command::operator[](const std::string &key) { + return this->value(key); + } + + inline command_entry command_bulk::operator[](const std::string &key) { + return this->value(key); + } + + inline const command_bulk command::operator[](size_t index) const { + return this->bulk(index); + } + + inline const command_entry command::operator[](const std::string &key) const { + return this->value(key); + } + + inline const command_entry command_bulk::operator[](const std::string &key) const { + return this->value(key); + } +} + +#ifndef WIN32 + using ts::descriptor::tliterals::operator""_tstr; +#endif \ No newline at end of file diff --git a/src/query/escape.cpp b/src/query/escape.cpp new file mode 100644 index 0000000..ccd7013 --- /dev/null +++ b/src/query/escape.cpp @@ -0,0 +1,143 @@ +#include "escape.h" +#include + +using namespace ts; +using namespace std; + +std::string query::escape(std::string in) { + size_t index = 0; + while(index < in.length()) { + if(in[index] == '\\') + in.replace(index, 1, "\\\\", 2); + else if(in[index] == ' ') + in.replace(index, 1, "\\s", 2); + else if(in[index] == '/') + in.replace(index, 1, "\\/", 2); + else if(in[index] == '|') + in.replace(index, 1, "\\p", 2); + else if(in[index] == '\b') + in.replace(index, 1, "\\b", 2); + else if(in[index] == '\f') + in.replace(index, 1, "\\f", 2); + else if(in[index] == '\n') + in.replace(index, 1, "\\n", 2); + else if(in[index] == '\r') + in.replace(index, 1, "\\r", 2); + else if(in[index] == '\t') + in.replace(index, 1, "\\t", 2); + else if(in[index] == '\x07') + in.replace(index, 1, "\\a", 2); + else if(in[index] == '\x0B') + in.replace(index, 1, "\\v", 2); + else { + index += 1; + continue; + } + index += 2; + } + + return in; +} + +/* +if (input[i] = $ED) and // Prüfe auf High and Low Surrogate +(input[i+3] = $ED) and +(input[i+1] or $F = $AF) and +(input[i+4] or $F = $BF) then +begin +output := output + + AnsiChar((input[i+1] and $F + 1) shr 2 and 7 or $F0) + // and 7 kann man auch weglassen + AnsiChar((input[i+2] shr 2 and $F + (input[i+1] and $F + 1) shl 4 and $30) or $80) + + AnsiChar((input[i+4] and $F + input[i+2] shl 4 and $30) or $80) + + AnsiChar(input[i+5]); +end +else +output := output + AnsiChar(input); + */ +std::string query::unescape(std::string in, bool throw_error) { + size_t index = 0; + while(index < in.length()){ + if(in[index] == '\\'){ + if(in.length() <= index + 1) break; + char replace = 0; + + switch (in[index + 1]){ + case 's': replace = ' '; break; + case '/': replace = '/'; break; + case 'p': replace = '|'; break; + case 'b': replace = '\b'; break; + case 'f': replace = '\f'; break; + case 'n': replace = '\n'; break; + case 'r': replace = '\r'; break; + case 't': replace = '\t'; break; + case 'a': replace = '\x07'; break; + case 'v': replace = '\x0B'; break; + case '\\': replace = '\\'; break; + default: + replace = '\x00'; break; + } + if(replace != 0x00) + in.replace(index, 2, string(&replace, 1)); + } + + uint8_t current = (uint8_t) in[index]; + if(in.length() - index >= 6) { //Check for CESU-8 + if((current == 0xED) && ((uint8_t) in[index + 3] == 0xED) && ((((uint8_t) in[index + 1] | 0xF) == 0xAF)) && ((((uint8_t) in[index + 4] | 0xF) == 0xBF))){ + char replaced[4]; + + replaced[0] = (((((uint8_t) in[index + 1] & 0xF) + 1) >> 2) & 7) | 0xF0; + replaced[1] = ((((uint8_t) in[index + 2] >> 2) & 0xF) + (((((uint8_t) in[index + 1] & 0xF)) + (1 << 4)) | 0x80)); + replaced[2] = (((uint8_t) in[index + 4] & 0xF) + (((uint8_t) in[index + 2] << 4) & 0x30)) | 0x80; + replaced[3] = ((uint8_t) in[index + 5]); + + in.replace(index, 6, string(replaced, 4)); + index -= 2; //Substract the replace difference + + index += 5; //Skip 5 bytes (CESU-8 length for emoty) + } + } + //"\221\210\200\200\200\210\221\221\221\221\204\210\221\221\221\221\221\221\221\221\221\204\210\221\221\221\221\221\204\210\221\221" + if(current >= 128) { //UTF8 check + if(current >= 192 && (current <= 193 || current >= 245)) { + in.replace(index, 1, "", 0); //Cut the character out + index--; + } else if(current >= 194 && current <= 223) { + if(in.length() - index <= 1) + in.replace(index, in.length() - index, "", 0); + else if((uint8_t) in[index + 1] >= 128 && (uint8_t) in[index + 1] <= 191) index += 1; //Valid + else { + if(throw_error) throw invalid_argument("Invalid UTF-8 character at index " + to_string(index)); + in.replace(index, 2, "", 0); //Cut the two characters out + index--; + } + } else if(current >= 224 && current <= 239) { + if(in.length() - index <= 2) + in.replace(index, in.length() - index, "", 0); + else if((uint8_t) in[index + 1] >= 128 && (uint8_t) in[index + 1] <= 191 && + (uint8_t) in[index + 2] >= 128 && (uint8_t) in[index + 2] <= 191) index += 2; //Valid + else { + if(throw_error) throw invalid_argument("Invalid UTF-8 character at index " + to_string(index)); + in.replace(index, 3, "", 0); //Cut the three characters out + index--; + } + } else if(current >= 240 && current <= 244) { + if(in.length() - index <= 3) + in.replace(index, in.length() - index, "", 0); + else if((uint8_t) in[index + 1] >= 128 && (uint8_t) in[index + 1] <= 191 && + (uint8_t) in[index + 2] >= 128 && (uint8_t) in[index + 2] <= 191 && + (uint8_t) in[index + 3] >= 128 && (uint8_t) in[index + 3] <= 191) index += 3; //Valid + else { + if(throw_error) throw invalid_argument("Invalid UTF-8 character at index " + to_string(index)); + in.replace(index, 4, "", 0); //Cut the three characters out + index--; + } + } else { + if(throw_error) throw invalid_argument("Invalid UTF-8 character at index " + to_string(index)); + in.replace(index, 1, "", 0); //Cut the character out + index--; + } + } + index++; + } + return in; +} \ No newline at end of file diff --git a/src/query/escape.h b/src/query/escape.h new file mode 100644 index 0000000..fb00bed --- /dev/null +++ b/src/query/escape.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +namespace ts { + namespace query { + extern std::string escape(std::string); + extern std::string unescape(std::string, bool /* throw error */); + } +} \ No newline at end of file diff --git a/src/sql/SqlQuery.cpp b/src/sql/SqlQuery.cpp new file mode 100644 index 0000000..e189856 --- /dev/null +++ b/src/sql/SqlQuery.cpp @@ -0,0 +1,53 @@ +#include +#include +#include +#include "log/LogUtils.h" +#include "SqlQuery.h" + +using namespace std; +using namespace std::chrono; + +namespace sql { + result result::success = result("", 0, "success"); + sql::command model::command() { return ::sql::command(*this); } + sql::model model::copy() { return sql::model(this->_data); } + model::model(const std::shared_ptr& data) : command_base(data->handle->copyCommandData(data)) {} + /** + * Command class itself + */ + + threads::Future command::executeLater() { + return this->_data->handle->pool->executeLater(*this); + } + + AsyncSqlPool::AsyncSqlPool(size_t threads) : _threads(new threads::ThreadPool(threads, "AsyncSqlPool")) { + debugMessage("Created a new async thread pool!"); + } + AsyncSqlPool::~AsyncSqlPool() { + delete _threads; + } + + threads::Future AsyncSqlPool::executeLater(const command& cmd) { + threads::Future fut; + this->_threads->execute([cmd, fut]{ //cmd for copy and stmt survive + command copy = cmd; + result res; + while((res = copy.execute()).code() == SQLITE_BUSY){ + cerr << "Execute busy!" << endl; + usleep(1000); + } + fut.executionSucceed(res); + }); + return fut; + } + + SqlManager::SqlManager(SqlType type) : type(type) { + this->pool = new AsyncSqlPool(1); + } + + SqlManager::~SqlManager() { + if(this->pool) this->pool->threads()->wait_for(); + delete this->pool; + this->pool = nullptr; + } +} \ No newline at end of file diff --git a/src/sql/SqlQuery.h b/src/sql/SqlQuery.h new file mode 100644 index 0000000..c14adbb --- /dev/null +++ b/src/sql/SqlQuery.h @@ -0,0 +1,436 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include "../Variable.h" +#include +#include + +#define ALLOW_STACK_ALLOCATION +#define LOG_SQL_CMD [](const sql::result &res){ if(!res) logCritical("Failed to execute sql command: " + std::to_string(res.code()) + "/" + res.msg() + " (" __FILE__ + ":" + to_string(__LINE__) + ")"); } +namespace sql { + class result; + class SqlManager; + class AsyncSqlPool; + class command; + class model; + namespace impl { + template class command_base; + } + + inline std::ostream& operator<<(std::ostream& s,const result& res); + inline std::ostream& operator<<(std::ostream& s,const result* res); + + using QueryCallback = std::function; + + class result { + public: + static result success; + result() : result(success) { } + result(std::string query, int code, std::string msg) : _code(code), _msg(std::move(msg)), _sql(std::move(query)) { } + result(int code, const std::string &msg) : _code(code), _msg(std::move(msg)) { } + result(const result& ref) : _code(ref._code), _msg(ref._msg), _sql(ref._sql) { } + result(result&& ref) : _code(ref._code), _msg(std::move(ref._msg)), _sql(std::move(ref._sql)) { } + virtual ~result() { }; + + int code() const { return _code; } + std::string msg() const { return _msg; } + std::string sql() const { return _sql; } + + //Returns true on success + operator bool() const { return _code == 0; } + + result&operator=(const result& other) { + this->_code = other._code; + this->_msg = other._msg; + this->_sql = other._sql; + return *this; + } + + std::string fmtStr(){ + std::stringstream s; + operator<<(s, *this); + return s.str(); + } + private: + int _code = 0; + std::string _msg{}; + std::string _sql{}; + }; + + enum SqlType { + TYPE_SQLITE, + TYPE_MYSQL + }; + + class CommandData { + public: + CommandData() = default; + ~CommandData() = default; + + SqlManager* handle = nullptr; + + template + inline H* sqlHandle() { return dynamic_cast(handle); } + + std::string sql_command; //variable : + std::vector variables{}; + threads::Mutex lock; + }; + + class SqlManager { + template friend class impl::command_base; + friend class command; + friend class model; + public: + explicit SqlManager(SqlType); + virtual ~SqlManager(); + virtual result connect(const std::string&) = 0; + virtual bool connected() = 0; + virtual result disconnect() = 0; + + AsyncSqlPool* pool; + + SqlType getType(){ return this->type; } + + protected: + virtual std::shared_ptr allocateCommandData() = 0; + virtual std::shared_ptr copyCommandData(std::shared_ptr) = 0; + virtual result executeCommand(std::shared_ptr) = 0; + virtual result queryCommand(std::shared_ptr, const QueryCallback& fn) = 0; + private: + SqlType type; + }; + + #define SQL_FWD(...) ::std::forward(__VA_ARGS__) + namespace impl { + template + struct merge; + + template + struct merge, std::tuple> { + using result = std::tuple; + }; + + /* let me stuff reverse */ + template + struct append_reversed { }; + + template + struct append_reversed> { + using type = std::tuple; + }; + + template + struct reverse_types { + using type = std::tuple<>; + }; + + template + struct reverse_types { + using type = typename append_reversed::type>::type; + }; + + /* Return type stuff */ + typedef int stardart_return_type; + template + using stardart_return = std::function; + + template + struct transformer_return { + using supported = std::false_type; + }; + + /* transforming (no transform) from int(...) to int(...) */ + template + struct transformer_return::value>::type> { + using supported = std::true_type; + + template + static stardart_return transform(const std::function& function) { + return function; + } + }; + + /* transforming from void(...) to int(...) */ + template + struct transformer_return::value>::type> { + using supported = std::true_type; + + template + static stardart_return transform(const std::function& function) { + return [function](args... parms) -> int { + function(parms...); + return 0; + }; + } + }; + + /* transforming from ~integral~(...) to int(...) */ + template + struct transformer_return::value && !std::is_same::value>::type> { + using supported = std::true_type; + + template + static stardart_return transform(const std::function& function) { + return [function](args... parms) -> int { + return (int) function(parms...); + }; + } + }; + + /* method stuff */ + using stardart_function = std::function; + + template + struct transformer_arguments { + using supported = std::false_type; + using arguments_reversed = std::tuple<>; + static constexpr int argument_count = 0; + }; + + + /* we don't need to proxy a standard function */ + template <> + struct transformer_arguments, std::tuple<>> { + using supported = std::true_type; + using arguments_reversed = std::tuple<>; + static constexpr int argument_count = 0; + + typedef std::function typed_function; + + static stardart_function transform(const typed_function& function) { + return function; + } + }; + + /* proxy a standard function with left sided arguments */ + template + struct transformer_arguments, std::tuple> { + using supported = std::true_type; + using arguments_reversed = std::tuple::type...>; /* note: these are still reversed */ + static constexpr int argument_count = sizeof...(additional_reversed); + + typedef std::function typed_function; + static stardart_function transform(const typed_function& function, additional&&... args) { + return [function, &args...](int length, std::string* values, std::string* names) { + return function(args..., length, values, names); + }; + } + }; + + /* proxy int(..., int, char**, char**) function with left sided arguments */ + template + struct transformer_arguments, std::tuple> { + using supported = std::true_type; + using arguments_reversed = std::tuple::type...>; + static constexpr int argument_count = sizeof...(additional); + + typedef std::function typed_function; + static stardart_function transform(const typed_function& function, additional&&... args) { + return [function, &args...](int length, std::string* values, std::string* names) { +#ifdef ALLOW_STACK_ALLOCATION + char* array_values[length]; + char* array_names[length]; +#else + char** array_values = (char**) malloc(length * sizeof(char*)); + char** array_names = (char**) malloc(length * sizeof(char*)); +#endif + for(int i = 0; i < length; i++) { + array_values[i] = (char*) values[i].c_str(); + array_names[i] = (char*) names[i].c_str(); + } + auto result = function(args..., length, array_values, array_names); + +#ifndef ALLOW_STACK_ALLOCATION + free(array_values); + free(array_names); +#endif + return result; + }; + } + }; + + template + class command_base { + friend class ::sql::command; + friend class ::sql::model; + public: + command_base(SqlManager* handle, const std::string &sql, std::initializer_list values) { + assert(handle); + assert(!sql.empty()); + this->_data = handle->allocateCommandData(); + this->_data->handle = handle; + this->_data->sql_command = sql; + this->__data = this->_data.get(); + for(const auto& val : values) this->value(val); + } + + template + command_base(SqlManager* handle, std::string sql, Ts&&... vars) : command_base(handle, sql, {}) { values(vars...); } + + command_base(const command_base& ref) : _data(ref._data), __data(ref._data.get()) {} + command_base(command_base&& ref) noexcept : _data(ref._data), __data(ref._data.get()) { } + + virtual ~command_base() = default; + + virtual SelfType& value(const variable& val) { + this->_data->variables.push_back(val); + return *(SelfType*) this; + } + + template + SelfType& value(const std::initializer_list& val) { + //this->_data->variables.push_back(val.begin()); + return *(SelfType*) this; + } + + template + SelfType& value(const std::string& key, T&& value) { + this->_data->variables.push_back(variable{key, value}); + + return *(SelfType*) this; + } + + SelfType& values(){ return *(SelfType*) this; } + + template + SelfType& values(const value_t& firstValue, values_t&&... values){ + this->value(firstValue); + this->values(values...); + return *(SelfType*) this; + } + + std::string sqlCommand(){ return _data->sql_command; } + SqlManager* handle(){ return _data->handle; } + protected: + explicit command_base(const std::shared_ptr& data) : _data(data), __data(data.get()) {} + std::shared_ptr _data; + CommandData* __data = nullptr; + }; + } + + class model : public impl::command_base { + public: + model(SqlManager* db, const std::string &sql, std::initializer_list values) : command_base(db, sql, values){}; + + template + model(SqlManager* handle, const std::string &sql, Ts... vars) : model(handle, sql, {}) { values(vars...); } + + model(const model& v) : command_base(v) {}; + model(model&& v) noexcept : command_base(v){}; + ~model() override {}; + + sql::command command(); + sql::model copy(); + + private: + explicit model(const std::shared_ptr&); + }; + + class command : public impl::command_base { + public: + command(SqlManager* db, const std::string &sql, std::initializer_list values) : command_base(db, sql, values) {}; + + template + command(SqlManager* handle, const std::string &sql, Ts... vars) : command_base(handle, sql, {}) { values(SQL_FWD(vars)...); } + + /* + template + command(SqlManager* handle, const std::string &sql, std::initializer_list arg_0, std::initializer_list arg_1) : command_base(handle, sql, {}) { + //static_assert(false, "testing"); + } + + command(SqlManager* handle, const std::string &sql) : command_base(handle, sql, {}) {} + */ + + explicit command(model& c) : command_base(c.handle(), c.sqlCommand()) { + this->_data = c._data->handle->copyCommandData(c._data); + } + + command(const command& v): command_base(v) {}; + command(command&& v) noexcept : command_base(v){}; + ~command() override = default;; + + result execute() { + return this->_data->handle->executeCommand(this->_data); + } + + threads::Future executeLater(); + + //Convert lambdas to std::function + template + result query(const lambda& lam, arguments&&... args) { + typedef stx::lambda_type info; + return this->query((typename info::invoker_function) lam, SQL_FWD(args)...); + } + + template + result query(const std::function& callback, args&&... parms) { //Query without data + typedef impl::transformer_return ret_transformer; + typedef impl::transformer_arguments::type, std::tuple> args_transformer; + + constexpr bool valid_return_type = ret_transformer::supported::value; + constexpr bool valid_arguments = args_transformer::supported::value; + constexpr bool valid_argument_count = + !valid_arguments || //Don't throw when function is invalid + !valid_return_type || //Don't throw when function is invalid + args_transformer::argument_count == sizeof...(args); + constexpr bool valid_argument_types = + !valid_arguments || //Don't throw when function is invalid + !valid_return_type || //Don't throw when function is invalid + !valid_argument_count || //Don't throw when arg count is invalid + std::is_same::type>::value; + + static_assert(valid_return_type, "Return type isn't supported!"); + static_assert(valid_arguments, "Arguments not supported!"); + static_assert(valid_argument_count, "Invalid argument count!"); + static_assert(valid_argument_types, "Invalid argument types!"); + + return this->query_invoke(callback, SQL_FWD(parms)...); + }; + private: + template + inline result query_invoke(const type_call& callback, args&&... parms) { + auto standard_return = ret_transformer::transform(callback); + auto standard_function = args_transformer::transform(standard_return, SQL_FWD(parms)...); + + return this->_data->handle->queryCommand(this->_data, standard_function); + } + }; + + class AsyncSqlPool { + public: + explicit AsyncSqlPool(size_t threads); + ~AsyncSqlPool(); + AsyncSqlPool(AsyncSqlPool&) = delete; + AsyncSqlPool(const AsyncSqlPool&) = delete; + AsyncSqlPool(AsyncSqlPool&&) = delete; + + threads::Future executeLater(const command& cmd); + threads::ThreadPool* threads(){ return _threads; } + private: + threads::ThreadPool* _threads = nullptr; + }; +} + + +inline std::ostream& sql::operator<<(std::ostream& s,const result* res){ + if(!res) s << "nullptr"; + else { + if(!res->sql().empty()) + s << " sql: " << res->sql() << " returned -> "; + s << res->code() << "/" << res->msg(); + } + return s; +} + +inline std::ostream& sql::operator<<(std::ostream& s,const result& res){ + return sql::operator<<(s, &res); +} \ No newline at end of file diff --git a/src/sql/mysql/MySQL.cpp b/src/sql/mysql/MySQL.cpp new file mode 100644 index 0000000..83eabe2 --- /dev/null +++ b/src/sql/mysql/MySQL.cpp @@ -0,0 +1,384 @@ +#include + +#include "MySQL.h" +#include "mysql_connection.h" +#include "src/log/LogUtils.h" +#include + +#include +#include + +using namespace std; +using namespace sql; +using namespace sql::mysql; + +MySQLManager::MySQLManager() : SqlManager(SqlType::TYPE_MYSQL) {} +MySQLManager::~MySQLManager() {} + +//Property info: https://dev.mysql.com/doc/connector-j/5.1/en/connector-j-reference-configuration-properties.html +//mysql://[host][:port]/[database][?propertyName1=propertyValue1[&propertyName2=propertyValue2]...] + +#define MYSQL_PREFIX "mysql://" +inline result parse_url(const string& url, ConnectOptionsMap& connect_map) { + string target_url; + if(url.find(MYSQL_PREFIX) != 0) return {ERROR_MYSQL_INVLID_URL, "Missing mysql:// at begin"}; + auto index_parms = url.find('?'); + if(index_parms == string::npos) { + target_url = "tcp://" + url.substr(strlen(MYSQL_PREFIX)); + } else { + target_url = "tcp://" + url.substr(strlen(MYSQL_PREFIX), index_parms - strlen(MYSQL_PREFIX)); + auto parms = url.substr(index_parms + 1); + size_t index = 0; + do { + auto idx = parms.find('&', index); + auto element = parms.substr(index, idx - index); + + auto key_idx = element.find('='); + auto key = element.substr(0, key_idx); + auto value = element.substr(key_idx + 1); + connect_map[key] = http::decode_url(value); + logTrace(LOG_GENERAL, "Got mysql property {}. Value: {}", key, value); + + index = idx + 1; + } while(index != 0); + } + + //if(!connect_map["hostName"].get() || strcmp(*connect_map["hostName"].get(), "")) + connect_map["hostName"] = target_url; + logTrace(LOG_GENERAL, "Got mysql property {}. Value: {}", "hostName", target_url); + return result::success; +} + +result MySQLManager::connect(const std::string &url) { + this->disconnecting = false; + + ConnectOptionsMap connect_map; + connect_map["connections"] = "1"; + auto res = parse_url(url, connect_map); + if(!res) return res; + + this->driver = get_driver_instance(); + if(!this->driver) return {ERROR_MYSQL_MISSING_DRIVER, "Missing driver!"}; + + try { + auto entry = connect_map["connections"]; + auto connections = std::stoll(connect_map["connections"].get()->asStdString()); + if(connections < 1) return {ERROR_MYSQL_INVLID_PROPERTIES, "Invalid connection count"}; + + for(int i = 0; i < connections; i++) { + auto connection = unique_ptr(this->driver->connect(connect_map)); + if(!connection) return {ERROR_MYSQL_INVLID_CONNECT, "Could not spawn new connection"}; + if(!connection->isValid()) return {ERROR_MYSQL_INVLID_CONNECT, "Could not validate connection"}; + if(connection->getSchema().length() == 0) return {ERROR_MYSQL_INVLID_CONNECT, "Missing schema!"}; + + this->connections.push_back(shared_ptr(new ConnectionEntry(std::move(connection), false))); + } + } catch (sql::SQLException& ex) { + return {ERROR_MYSQL_INVLID_CONNECT, ex.what()}; + } + return result::success; +} + +bool MySQLManager::connected() { + lock_guard lock(this->connections_lock); + for(const auto& conn : this->connections) + if(conn->used || conn->connection->isValid()) return true; + return false; +} + +result MySQLManager::disconnect() { + lock_guard lock(this->connections_lock); + this->disconnecting = true; + + for(const auto& entry : this->connections) + entry->connection->close(); + + this->connections.clear(); + this->connections_condition.notify_all(); + + this->disconnecting = false; + return result::success; +} + +std::shared_ptr MySQLManager::allocateCommandData() { + return make_shared(); +} + +std::shared_ptr MySQLManager::copyCommandData(std::shared_ptr ptr) { + auto _new = this->allocateCommandData(); + _new->handle = ptr->handle; + _new->lock = ptr->lock; + _new->sql_command = ptr->sql_command; + _new->variables = ptr->variables; + + auto __new = static_pointer_cast(_new); + auto __ptr = static_pointer_cast(ptr); + //__new->stmt = __ptr->stmt; + return __new; +} + +void prepared_statement_release(sql::PreparedStatement* stmt) { + if(stmt) stmt->close(); + delete stmt; +}; +typedef unique_ptr PreparedStatementHandle; + +void statement_release(sql::Statement* stmt) { + if(stmt) stmt->close(); + delete stmt; +}; +typedef unique_ptr StatementHandle; + +void result_release(sql::ResultSet* set) { + if(set && !set->isClosed()) set->close(); + delete set; +} +typedef unique_ptr ResultHandle; + + +namespace sql { + namespace mysql { + bool evaluate_sql_query(string& sql, const std::vector& vars, std::vector& result) { + char quote = 0; + for(int index = 0; index < sql.length(); index++) { + if(sql[index] == '\'' || sql[index] == '"' || sql[index] == '`') { + if(quote > 0) { + if(quote == sql[index]) quote = 0; + } else { + quote = sql[index]; + } + continue; + } + if(quote > 0) continue; + if(sql[index] != ':') continue; + + auto index_end = sql.find_first_not_of("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_", index + 1); + if(index_end == string::npos) index_end = sql.length(); + + string key = sql.substr(index, index_end - index); + //Now we can replace it with a ? + sql.replace(index, index_end - index, "?", 1); + + bool insert = false; + for(const auto& e : vars) + if(e.key() == key) { + result.push_back(e); + insert = true; + break; + } + if(!insert) + result.push_back(variable{}); + } + return true; + } + + inline bool bind_parms(const PreparedStatementHandle& stmt, const std::vector& vars) { + uint32_t index = 1; + for(const auto& var : vars) { + switch (var.type()) { + case VARTYPE_NULL: + stmt->setNull(index, 0); + break; + case VARTYPE_BOOLEAN: + stmt->setBoolean(index, var.as()); + break; + case VARTYPE_INT: + stmt->setInt(index, var.as()); + break; + case VARTYPE_LONG: + stmt->setInt64(index, var.as()); + break; + case VARTYPE_DOUBLE: + stmt->setDouble(index, var.as()); + break; + case VARTYPE_FLOAT: + stmt->setDouble(index, var.as()); + break; + case VARTYPE_TEXT: + stmt->setString(index, var.value()); + break; + default: + cerr << "[MySQL] Invalid var type (" << var.type() << ")" << endl; + break; + } + index++; + } + return true; + } + } +} + +LocalConnection::LocalConnection(MySQLManager* mgr, const std::shared_ptr& entry) : _mgr(mgr), _connection(entry) { + _mgr->driver->threadInit(); + _connection->used = true; + //logMessage(LOG_GENERAL, "Allocate local connection {} and thread {}", (void*) _connection.get(), (void*) threads::self::id()); +} + +LocalConnection::~LocalConnection() { + //logMessage(LOG_GENERAL, "Deallocate local connection {} and thread {}", (void*) this->_connection.get(), (void*) threads::self::id()); + _mgr->driver->threadEnd(); + _connection->used = false; + + { + lock_guard lock(_mgr->connections_lock); + _mgr->connections_condition.notify_all(); + } +} + +std::unique_ptr MySQLManager::next_connection() { + unique_ptr result; + { + unique_lock connections_lock(this->connections_lock); + + while(!result) { + size_t available_connections = 0; + for(const auto& connection : this->connections) { + available_connections++; + if(connection->used) continue; + + result = std::make_unique(this, connection); + break; + } + + if(!result) { + if(available_connections == 0) { + if(this->listener_disconnected) + this->listener_disconnected(false); + this->disconnect(); + return nullptr; + } + this->connections_condition.wait(connections_lock); /* wait for the next connection */ + } + } + } + + if(!result->_connection->connection->isValid()) { + try { + logError(0, "MySQL connection is invalid! Closing connection!"); + result->_connection->connection->close(); + } catch(sql::SQLException& ex) {} + } + if(result->_connection->connection->isClosed()) { + logError(0, "MySQL connection was closed! Attempt reconnect!"); + try { + if(!result->_connection->connection->reconnect()) { + logError(0, "MySQL connection reconnect attempt failed! Dropping connection!"); + { + lock_guard connections_lock(this->connections_lock); + auto index = find(this->connections.begin(), this->connections.end(), result->_connection); + if(index != this->connections.end()) + this->connections.erase(index); + } + return this->next_connection(); + } + } catch (sql::SQLException& ex) { + logError(0, "Got an exception while reconnecting! Message: " + string(ex.what())); + logError(0, "Dropping connection!"); + { + lock_guard connections_lock(this->connections_lock); + auto index = find(this->connections.begin(), this->connections.end(), result->_connection); + if(index != this->connections.end()) + this->connections.erase(index); + } + return this->next_connection(); + } + } + return result; +} + +result MySQLManager::executeCommand(std::shared_ptr _ptr) { + auto ptr = static_pointer_cast(_ptr); + std::lock_guard command_lock(ptr->lock); + auto command = ptr->sql_command; + auto variables = ptr->variables; + vector mapped_variables; + if(!sql::mysql::evaluate_sql_query(command, variables, mapped_variables)) return {ptr->sql_command, -1, "Could not map sqlite vars to mysql!"}; + + unique_ptr connection = this->next_connection(); + if(!connection) return {ptr->sql_command, -1, "Could not get a valid connection!"}; + try { + PreparedStatementHandle stmt(connection->_connection->connection->prepareStatement(command), prepared_statement_release); + //logMessage(LOG_GENERAL, "Deleting prepered statement {} and thread {}", (void*) stmt.get(), (void*) threads::self::id()); + if(!stmt) return {ptr->sql_command, -1, "Could not span a prepared statement"}; + + if(!sql::mysql::bind_parms(stmt, mapped_variables)) return {ptr->sql_command, -1, "Could not bind variables!"}; + + auto update_count = stmt->executeUpdate(); + if(update_count < 0) + return {ptr->sql_command, -1, "Could not execute update. Code: " + to_string(update_count)}; + + stmt.reset(); + return result::success; + } catch (sql::SQLException& ex) { + logError(0, "SQL Error: {}", ex.what()); + return {ptr->sql_command, -1, ex.what()}; + } +} + +result MySQLManager::queryCommand(shared_ptr _ptr, const QueryCallback &fn) { + auto ptr = static_pointer_cast(_ptr); + std::lock_guard lock(ptr->lock); + auto command = ptr->sql_command; + auto variables = ptr->variables; + vector mapped_variables; + if(!sql::mysql::evaluate_sql_query(command, variables, mapped_variables)) return {ptr->sql_command, -1, "Could not map sqlite vars to mysql!"}; + + unique_ptr connection = this->next_connection(); + if(!connection) return {ptr->sql_command, -1, "Could not get a valid connection!"}; + try { + PreparedStatementHandle stmt(connection->_connection->connection->prepareStatement(command), prepared_statement_release); + //logMessage(LOG_GENERAL, "Deleting prepered statement {} and thread {}", (void*) stmt.get(), (void*) threads::self::id()); + if(!sql::mysql::bind_parms(stmt, mapped_variables)) return {ptr->sql_command, -1, "Could not bind variables!"}; + ResultHandle result(stmt->executeQuery(), result_release); + + auto column_count = result->getMetaData()->getColumnCount(); + std::string columnNames[column_count]; + std::string columnValues[column_count]; + + for(int index = 0; index < column_count; index++) + columnNames[index] = result->getMetaData()->getColumnName(index + 1); + + bool userQuit = false; + while(result->next() && !userQuit) { + for(int index = 0; index < column_count; index++) + columnValues[index] = result->getString(index + 1); + if(fn(column_count, columnValues, columnNames) != 0) { + userQuit = true; + break; + } + } + + stmt.reset(); + return result::success; + } catch (sql::SQLException& ex) { + return {ptr->sql_command, -1, ex.what()}; + } +} + +result MySQLManager::execute_raw(const std::string &command) { + auto connection = this->next_connection(); + if(!connection) + return {command, -1, "no connection available"}; + + auto& mysql_connection = connection->_connection->connection; + mysql_connection->clearWarnings(); + + StatementHandle statement(mysql_connection->createStatement(), statement_release); + statement->clearWarnings(); + + try { + auto result = statement->execute(command); + if(statement->getWarnings()) { + cerr << "Got some warnings: " << endl; + while(statement->getWarnings()) { + cerr << " - " << statement->getWarnings()->getMessage() << endl; + statement->getWarnings()->setNextWarning(statement->getWarnings()->getNextWarning()); + } + } + if(result) + return result::success; + return {command, -1, "return false"}; + } catch (sql::SQLException& ex) { + return {command, -1, ex.what()}; + } +} \ No newline at end of file diff --git a/src/sql/mysql/MySQL.h b/src/sql/mysql/MySQL.h new file mode 100644 index 0000000..a7d4132 --- /dev/null +++ b/src/sql/mysql/MySQL.h @@ -0,0 +1,91 @@ +#pragma once + +#if __cplusplus >= 201703L + /* MySQL override. This needed to be inclided before cppconn/exception.h to define them */ + #include + #include + #include + + /* Now remove the trow */ + #define throw(...) + #include + #undef throw /* reset */ +#endif + +#include +#include "sql/SqlQuery.h" +#include +#include + +#define ERROR_MYSQL_MISSING_DRIVER -1 +#define ERROR_MYSQL_INVLID_CONNECT -2 +#define ERROR_MYSQL_INVLID_PROPERTIES -3 +#define ERROR_MYSQL_INVLID_URL -4 + +namespace sql { + namespace mysql { + class MySQLManager; + struct LocalConnection; + + bool evaluate_sql_query(std::string& sql, const std::vector& vars, std::vector& result); + + class MySQLCommand : public CommandData { }; + + struct ConnectionEntry { + friend class MySQLManager; + friend class LocalConnection; + public: + typedef std::function DisconnectListener; + + private: + ConnectionEntry(std::unique_ptr&& connection, bool used) : connection(std::move(connection)), used(used) {} + + std::auto_ptr save_point; + std::unique_ptr connection; + bool used = false; + }; + struct LocalConnection { + LocalConnection(MySQLManager* mgr, const std::shared_ptr& entry); + ~LocalConnection(); + + MySQLManager* _mgr; + std::shared_ptr _connection; + }; + + class MySQLManager : public SqlManager { + friend class LocalConnection; + public: + typedef std::function&)> ListenerConnectionDisconnect; + typedef std::function&)> ListenerConnectionCreated; + + typedef std::function ListenerConnected; + typedef std::function ListenerDisconnected; + + MySQLManager(); + virtual ~MySQLManager(); + + result connect(const std::string &string) override; + bool connected() override; + result disconnect() override; + + ListenerDisconnected listener_disconnected; + + result execute_raw(const std::string& /* command */); + protected: + std::shared_ptr copyCommandData(std::shared_ptr ptr) override; + std::shared_ptr allocateCommandData() override; + result executeCommand(std::shared_ptr ptr) override; + result queryCommand(std::shared_ptr ptr, const QueryCallback &fn) override; + + public: + inline std::unique_ptr next_connection(); + + std::mutex connections_lock; + std::condition_variable connections_condition; + std::deque> connections; + + sql::Driver* driver = nullptr; + bool disconnecting = false; + }; + } +} \ No newline at end of file diff --git a/src/sql/sqlite/SqliteSQL.cpp b/src/sql/sqlite/SqliteSQL.cpp new file mode 100644 index 0000000..7ffc11b --- /dev/null +++ b/src/sql/sqlite/SqliteSQL.cpp @@ -0,0 +1,169 @@ +#include +#include "SqliteSQL.h" + +using namespace std; +using namespace sql; +using namespace sqlite; + +SqliteManager::SqliteManager() : SqlManager(SqlType::TYPE_SQLITE) { } + +SqliteManager::~SqliteManager() { + if(this->connected()) this->disconnect(); +} + +result SqliteManager::connect(const std::string &string) { + auto url = string; + if(url.find("sqlite://") == 0) url = url.substr(strlen("sqlite://")); + + auto result = sqlite3_open(url.c_str(), &this->database); + if(!this->database) + return {"connect", -1, "could not open database. Code: " + to_string(result)}; + return result::success; +} + +bool SqliteManager::connected() { + return this->database != nullptr; +} + +result SqliteManager::disconnect() { + if(!this->database) + return {"disconnect", -1, "database not open"}; + this->pool->threads()->wait_for(); + auto result = sqlite3_close(this->database); + if(result == 0) { + this->database = nullptr; + return result::success; + }; + return {"disconnect", -1, "Failed to close database. Code: " + to_string(result)}; +} + +std::shared_ptr SqliteManager::allocateCommandData() { + return std::make_shared(); +} + +std::shared_ptr SqliteManager::copyCommandData(std::shared_ptr ptr) { + auto _new = this->allocateCommandData(); + _new->handle = ptr->handle; + _new->lock = ptr->lock; + _new->sql_command = ptr->sql_command; + _new->variables = ptr->variables; + + auto __new = static_pointer_cast(_new); + auto __ptr = static_pointer_cast(ptr); + __new->stmt = __ptr->stmt; + return __new; +} + +namespace sql { + namespace sqlite { + inline void bindVariable(sqlite3_stmt* stmt, int& valueIndex, const variable& val){ + valueIndex = sqlite3_bind_parameter_index(stmt, val.key().c_str()); + if(valueIndex == 0){ //TODO maybe throw an exception + //cerr << "Cant find variable '" + val.key() + "' -> '" + val.value() + "' in query '" + sqlite3_sql(stmt) + "'" << endl; + return; + } + + + int resultState = 0; + if(val.type() == VARTYPE_NULL) + resultState = sqlite3_bind_null(stmt, valueIndex); + else if(val.type() == VARTYPE_TEXT) + resultState = sqlite3_bind_text(stmt, valueIndex, val.value().c_str(), val.value().length(), SQLITE_TRANSIENT); + else if(val.type() == VARTYPE_INT || val.type() == VARTYPE_BOOLEAN) + resultState = sqlite3_bind_int(stmt, valueIndex, val.as()); + else if(val.type() == VARTYPE_LONG) + resultState = sqlite3_bind_int64(stmt, valueIndex, val.as()); + else if(val.type() == VARTYPE_DOUBLE || val.type() == VARTYPE_FLOAT) + resultState = sqlite3_bind_double(stmt, valueIndex, val.as()); + else cerr << "Invalid value type!" << endl; //TODO throw exception + + if(resultState != SQLITE_OK){ + cerr << "Invalid bind. " << sqlite3_errmsg(sqlite3_db_handle(stmt)) << " Index: " << valueIndex << endl; //TODO throw exception + } + } + } +} + +std::shared_ptr SqliteManager::allocateStatement(const std::string& command) { + sqlite3_stmt* stmt; + if(sqlite3_prepare_v2(this->database, command.data(), static_cast(command.length()), &stmt, nullptr) != SQLITE_OK) + return nullptr; + + return std::shared_ptr(stmt, [](void* _ptr) { + auto _stmt = static_cast(_ptr); + if(_stmt) sqlite3_finalize(_stmt); + }); +} + +result SqliteManager::queryCommand(std::shared_ptr _ptr, const QueryCallback &fn) { + auto ptr = static_pointer_cast(_ptr); + std::lock_guard lock(ptr->lock); + + result res; + std::shared_ptr stmt; + if(ptr->stmt){ + stmt = ptr->stmt; + sqlite3_reset(stmt.get()); + } else { + ptr->stmt = this->allocateStatement(ptr->sql_command); + if(!ptr->stmt) + return {_ptr->sql_command,1, sqlite3_errmsg(ptr->sqlHandle()->database)}; + stmt = ptr->stmt; + } + + int varIndex = 0; + for(auto& var : ptr->variables) + bindVariable(stmt.get(), varIndex, var); + + int result = 0; + int columnCount = sqlite3_column_count(stmt.get()); + std::string columnNames[columnCount]; + std::string columnValues[columnCount]; + + for(int column = 0; column < columnCount; column++) { + auto tmp = sqlite3_column_name(stmt.get(), column); + columnNames[column] = tmp ? tmp : ""; + } + + bool userQuit = false; + while((result = sqlite3_step(stmt.get())) == SQLITE_ROW){ + for(int column = 0; column < columnCount; column++) { + const auto * tmp = reinterpret_cast(sqlite3_column_text(stmt.get(), column)); + columnValues[column] = tmp ? tmp : ""; + } + if(fn(columnCount, columnValues, columnNames) != 0) { + userQuit = true; + break; + } + } + + if(result != SQLITE_DONE && !userQuit) return {_ptr->sql_command,result, sqlite3_errstr(result)}; + return {_ptr->sql_command,0, "success"}; +} + +result SqliteManager::executeCommand(std::shared_ptr _ptr) { + auto ptr = static_pointer_cast(_ptr); + std::lock_guard lock(ptr->lock); + + result res; + sqlite3_stmt* stmt; + if(ptr->stmt){ + stmt = ptr->stmt.get(); + sqlite3_reset(stmt); + } else { + ptr->stmt = this->allocateStatement(ptr->sql_command); + if(!ptr->stmt) + return {_ptr->sql_command,1, sqlite3_errmsg(ptr->sqlHandle()->database)}; + stmt = ptr->stmt.get(); + } + + int varIndex = 0; + for(const auto& var : ptr->variables) + bindVariable(stmt, varIndex, var); + + int result = sqlite3_step(stmt); + if(result == SQLITE_DONE) + return {_ptr->sql_command,0, "success"}; + if(result == SQLITE_ROW) return {_ptr->sql_command,-1, "query has a result"}; + return {_ptr->sql_command, 1, sqlite3_errstr(result)}; +} \ No newline at end of file diff --git a/src/sql/sqlite/SqliteSQL.h b/src/sql/sqlite/SqliteSQL.h new file mode 100644 index 0000000..de9cabd --- /dev/null +++ b/src/sql/sqlite/SqliteSQL.h @@ -0,0 +1,32 @@ +#pragma once + +#include "../SqlQuery.h" +namespace sql { + namespace sqlite { + class SqliteCommand : public CommandData { + public: + std::shared_ptr stmt = nullptr; + }; + + class SqliteManager : public SqlManager { + public: + SqliteManager(); + virtual ~SqliteManager(); + + result connect(const std::string &string) override; + bool connected() override; + result disconnect() override; + + sqlite3* getDatabase() { return this->database; } + protected: + std::shared_ptr copyCommandData(std::shared_ptr ptr) override; + std::shared_ptr allocateCommandData() override; + result executeCommand(std::shared_ptr ptr) override; + result queryCommand(std::shared_ptr ptr, const QueryCallback&fn) override; + + private: + std::shared_ptr allocateStatement(const std::string&); + sqlite3* database = nullptr; + }; + } +} \ No newline at end of file diff --git a/src/ssl/SSLManager.cpp b/src/ssl/SSLManager.cpp new file mode 100644 index 0000000..2b2f73f --- /dev/null +++ b/src/ssl/SSLManager.cpp @@ -0,0 +1,371 @@ +#include +#include +#include +#include +#include +#include "SSLManager.h" + +namespace fs = std::experimental::filesystem; + +/* +openssl req -x509 -nodes -out default_certificate.pem -newkey rsa:2048 -keyout default_privatekey.pem -days 3650 -config <( +cat <<-EOF +[req] +default_bits = 2048 +prompt = no +default_md = sha256 +distinguished_name = dn + +[ dn ] +C=DE +O=TeaSpeak +OU=Web Server +emailAddress=contact@teaspeak.de +CN = web.teaspeak.de +EOF +) + */ + +/* +static std::string SSL_DEFAULT_KEY = R"( +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCrnuiIrA/uK/VI +yUDFIc8aClGNzlWvCL18LmhCdE0Mlp0X6aYS/lergGJZark8dNbARNcsU41535L/ +0IrqhQr3zsaZqK4sQ5xKtRpUXjknFPMRIcfx7efA/8687yxJ90IJbov7EOT2cVF+ +nzE4DNPFyih6G42wMeMTfnuy60fABblWZikeYNM+/YtFcB1uTiJhPWid0URo85d+ +e0ifrO7y3EszGd1Hl27oroyFAeGRP5MyU3cj0ZUBjxEdEnJXmAQnHM4cO6tC9E62 +775J4G+abJy6TdyxUUK9LhtfTpMnLYpGhMzJwslrfqQ9almY2K7X0xzBYxRNRXsA +B6sBI1llAgMBAAECggEBAJ7pic/j4uxa78jx8XOYFri6DUINaPGmWi5+mjPOlPmv +DM9znj/AG1XGj0rUs6jzV1a5Z7S3uSy8hNUzOS5m+vzzDpqBwqViBXp3r2WnyawS +je+zI/00mX/wXnI71Pq4ZQFux1c3EYvQ6fEhXuXTmtRumIRYtx4LU4RdfhTyH4IB +Rqsb6tPyO0gVPdTL5V3O74bSs0k2QVkm4U/UKiuDJeY5Upy/MX0y+ObEzkxCL/e5 +o7jB0DkvCu5wjHWWoA/hduvBXLelbPqxXSuz6YGGPiOaM131Zmk1JIkmeANX1T8b +raWg4yV7oiOprAyx2ioU6Q55w+AYfq2q+noJwEOOrYECgYEA0fJ+pfywksV2eXBu +N2TkruTYsMGE7bSvM8E6ABkYutUz4bC8ytHszs6e3vMhojO6aHqp389vFrPHSu23 +1Uqk/eVBbRSHzbvRXl35OzNzeSfmJKkzbG8OnLnukoHzDJUPrhFUHBR8KHnuIycv +87Lj+wmpIaAfaFCct8sKbgnB3e0CgYEA0UQ0GLS6BnwfwdKJVubaIc6P1wtr7vam +UVFntusx3v8AI1qiQ6EzQ73NX9eh3L5sKVcrMtBLur3G/Ferp135J6V2+ZYc1nUA +67x/9wquo42SxlRCfx00zeCCGU0Q56/UEND626So7E6k1sgKnWXq5uuQYZU0wpbi +InD1oo+bulkCgYA2wd2AY2CWV0QoNke40OrIJs3RhBese9S6VepPvjvx9st6UMNc +ztXJtqA/HACosn8q4ttNkWey7x7KjyfETJytz855qcIlyZe42h+37hpu/hYLd8n+ +vRR9kg0ETzpaDMKzLrfWPw2G7Q5MQttB32WQwxtGtuGaLnRBh4Zn3smenQKBgANF +DYtVR5LSXaypnXu+H6pnj9fMVeNl9zNOElDJW/4f/eCPifmEi0iDrrHQrLbGQupi +ckpY9tX0ISfQNt5mmX4FF9bOgaTYLyt/xoAVqqTjkWeH6YIS8sBEwcOjcKAuHyIk +IcdMy1bl4613crMC5Ki3BYqAylJACUiAe1YO6GABAoGBALk+VuDvY8IxcSTOZXxe +hGG/TZlHz/xfvuOC0ngsfX+C7Q98KDh72SzBDk4wSqWTLTLPn4jZArmD+gVagNz9 +GSb4gpxinfXnb1px0BjR7YoVxJnk8FEjxe7PPSeYWt5e0Liyl0LPBm6xK0LXppyr +N9G+1ojrRslgE5HGdZ9Axi54 +-----END PRIVATE KEY----- +)"; + +static std::string SSL_DEFAULT_CERT = R"( +-----BEGIN CERTIFICATE----- +MIIDYjCCAkoCCQCagl242EEilDANBgkqhkiG9w0BAQsFADBzMQswCQYDVQQGEwJE +RTERMA8GA1UECgwIVGVhU3BlYWsxEzARBgNVBAsMCldlYiBTZXJ2ZXIxIjAgBgkq +hkiG9w0BCQEWE2NvbnRhY3RAdGVhc3BlYWsuZGUxGDAWBgNVBAMMD3dlYi50ZWFz +cGVhay5kZTAeFw0xODAzMjIxNTM4MzVaFw0yODAzMTkxNTM4MzVaMHMxCzAJBgNV +BAYTAkRFMREwDwYDVQQKDAhUZWFTcGVhazETMBEGA1UECwwKV2ViIFNlcnZlcjEi +MCAGCSqGSIb3DQEJARYTY29udGFjdEB0ZWFzcGVhay5kZTEYMBYGA1UEAwwPd2Vi +LnRlYXNwZWFrLmRlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAq57o +iKwP7iv1SMlAxSHPGgpRjc5Vrwi9fC5oQnRNDJadF+mmEv5Xq4BiWWq5PHTWwETX +LFONed+S/9CK6oUK987GmaiuLEOcSrUaVF45JxTzESHH8e3nwP/OvO8sSfdCCW6L ++xDk9nFRfp8xOAzTxcooehuNsDHjE357sutHwAW5VmYpHmDTPv2LRXAdbk4iYT1o +ndFEaPOXfntIn6zu8txLMxndR5du6K6MhQHhkT+TMlN3I9GVAY8RHRJyV5gEJxzO +HDurQvROtu++SeBvmmycuk3csVFCvS4bX06TJy2KRoTMycLJa36kPWpZmNiu19Mc +wWMUTUV7AAerASNZZQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQCGNR1J3ynUVwuR +D7RrxfsVYiW/Wx6/i+MQCsk0R+lrsjlkPIUr8iIQu762QszWFaubdh8jqXVu5psT +utQk5RoZ5XrKUcE2av9G4o6Grj3BcsT3JXQcHtIpuDQnJDFoRe950YSVmZKbpvL/ +STN46EjSSiDUpv1qqXeVr9CEyCZftj4esJ6RvJwYeKBG8HXoNzMYK32N6JWGbZFu +U76TSNwcjXNId43V4OVFZ/ReaD8Nvzq10GwgKb2HshQtdIvOVNfAAk/mX4e+5I1k +9zTEm+IBljBFvNzAKQRxUiiUDTjazKVt51ToJhRVBGXtPmVvhKacWWC3tbHdWisG +5vm7hxLQ +-----END CERTIFICATE----- +)"; +*/ + +using namespace ts; +using namespace ts::ssl; +using namespace std; + +SSLManager::SSLManager() = default; +SSLManager::~SSLManager() = default; + +bool SSLManager::initialize() { + SSL_load_error_strings(); + OpenSSL_add_ssl_algorithms(); + + return true; +} + +static auto ERR_TO_STRING = [](const char* err, size_t length, void* ptrTarget){ + auto target = (string*) ptrTarget; + target->resize(length); + memcpy((void *) target->data(), err, length); + return 0; +}; + +#define SSL_ERROR(message) \ +do { \ + ERR_print_errors_cb(ERR_TO_STRING, &error); \ + error = (message) + error; \ + return nullptr; \ +} while(false) + +std::shared_ptr SSLManager::initializeContext(const std::string &key, std::string &privateKey, std::string &certificate, std::string &error, bool raw, const std::shared_ptr& generator) { + auto load = this->loadContext(privateKey, certificate, error, raw, generator); + if(!load) return nullptr; + + this->contexts[key] = load; + return load; +} + +std::shared_ptr SSLManager::initializeSSLKey(const std::string &key, const std::string &rsaKey, std::string &error, bool raw) { + auto load = this->loadSSL(rsaKey, error, raw); + if(!load) return nullptr; + + this->rsa[key] = load; + return load; +} + +EVP_PKEY* SSLGenerator::generateKey() { + auto key = std::unique_ptr(EVP_PKEY_new(), ::EVP_PKEY_free); + + auto rsa = RSA_new(); + auto e = std::unique_ptr(BN_new(), ::BN_free); + BN_set_word(e.get(), RSA_F4); + if(!RSA_generate_key_ex(rsa, 2048, e.get(), nullptr)) return nullptr; + EVP_PKEY_assign_RSA(key.get(), rsa); + return key.release(); +} + +X509* SSLGenerator::generateCertificate(EVP_PKEY* key) { + + auto cert = X509_new(); + X509_set_pubkey(cert, key); + + ASN1_INTEGER_set(X509_get_serialNumber(cert), 3); + X509_gmtime_adj(X509_get_notBefore(cert), 0); + X509_gmtime_adj(X509_get_notAfter(cert), 31536000L); + + X509_NAME* name = nullptr; + name = X509_get_subject_name(cert); + for(const auto& subject : this->subjects) + X509_NAME_add_entry_by_txt(name, subject.first.c_str(), MBSTRING_ASC, (unsigned char *) subject.second.c_str(), subject.second.length(), -1, 0); + X509_set_subject_name(cert, name); + + name = X509_get_issuer_name(cert); + for(const auto& subject : this->issues) + X509_NAME_add_entry_by_txt(name, subject.first.c_str(), MBSTRING_ASC, (unsigned char *) subject.second.c_str(), subject.second.length(), -1, 0); + + X509_set_issuer_name(cert, name); + + X509_sign(cert, key, EVP_sha512()); + return cert; +} + +//TODO passwords +std::shared_ptr SSLManager::loadContext(std::string &rawKey, std::string &rawCert, std::string &error, bool rawData, const shared_ptr& generator) { + std::shared_ptr certBio = nullptr; + std::shared_ptr keyBio = nullptr; + std::shared_ptr cert = nullptr; + std::shared_ptr key = nullptr; + bool flagCertModified = false; + bool flagKeyModified = false; + std::shared_ptr context = nullptr; + std::shared_ptr result = nullptr; + + if(rawData) { + certBio = shared_ptr(BIO_new(BIO_s_mem()), ::BIO_free); + BIO_write(certBio.get(), rawCert.c_str(), rawCert.length()); + + keyBio = shared_ptr(BIO_new(BIO_s_mem()), ::BIO_free); + BIO_write(keyBio.get(), rawKey.c_str(), rawKey.length()); + } else { + auto keyPath = fs::u8path(rawKey); + auto certPath = fs::u8path(rawCert); + + if(!fs::exists(keyPath)) { + if(!generator) { + error = "Missing key file"; + return nullptr; + } + try { + if(keyPath.has_parent_path()) + fs::create_directories(keyPath.parent_path()); + } catch (fs::filesystem_error& error) { + logError("Could not create key directory: " + string(error.what())); + } + + { + std::ofstream { keyPath }; + } + } + + if(!fs::exists(certPath)) { + if(!generator) { + error = "Missing certificate file"; + return nullptr; + } + try { + if(certPath.has_parent_path()) + fs::create_directories(certPath.parent_path()); + } catch (fs::filesystem_error& error) { + logError("Could not create certificate directory: " + string(error.what())); + } + + { + std::ofstream { certPath }; + } + } + + auto mode = generator ? "rw" : "r"; + certBio = shared_ptr(BIO_new_file(rawCert.c_str(), mode), ::BIO_free); + if(!certBio) SSL_ERROR("Could not load certificate: "); + keyBio = shared_ptr(BIO_new_file(rawKey.c_str(), mode), ::BIO_free); + if(!keyBio) SSL_ERROR("Could not load key: "); + } + + + cert = shared_ptr(PEM_read_bio_X509(certBio.get(), nullptr, nullptr, nullptr), ::X509_free); + if(!cert && !generator) SSL_ERROR("Could not read certificate: "); + + key = shared_ptr(PEM_read_bio_PrivateKey(keyBio.get(), nullptr, nullptr, nullptr), ::EVP_PKEY_free); + if(!key && !generator) SSL_ERROR("Could not read key: "); + + if(!key) { + key = shared_ptr(generator->generateKey(), ::EVP_PKEY_free); + flagKeyModified = true; + } + + if(!cert) { + cert = shared_ptr(generator->generateCertificate(key.get()), ::X509_free); + flagCertModified = true; + } + + //Create context + context = shared_ptr(SSL_CTX_new(SSLv23_server_method()), ::SSL_CTX_free); + if (!context) SSL_ERROR("Could not create context: "); + + + if (SSL_CTX_use_PrivateKey(context.get(), key.get()) <= 0) SSL_ERROR("Could not use private key: "); + if (SSL_CTX_use_certificate(context.get(), cert.get()) <= 0) SSL_ERROR("Could not use certificate: "); + + result = std::make_shared(); + result->context = context; + result->certificate = cert; + result->privateKey = key; + + if(flagCertModified) { + if(!rawData) { + certBio = shared_ptr(BIO_new_file(rawCert.c_str(), "w"), ::BIO_free); + if(PEM_write_bio_X509(certBio.get(), cert.get()) != 1) SSL_ERROR("Could not write new certificate: "); + flagCertModified = false; + } else { + assert(false); //TODO implement with membuf + /* + void* ptr = nullptr; + auto length = BIO_get_mem_data(certBio, &ptr); + if(!ptr) SSL_ERROR("Could not get cert bio mem pointer: "); + if(length <= 0) SSL_ERROR("Could not get cert bio mem length (" + to_string(length) + "): "); + rawCert.reserve(length); + memcpy((void*) rawCert.data(), ptr, length); + */ + } + } + + if(flagKeyModified) { + if(!rawData) { + keyBio = shared_ptr(BIO_new_file(rawKey.c_str(), "w"), ::BIO_free); + if(PEM_write_bio_PrivateKey(keyBio.get(), key.get(), nullptr, nullptr, 0, nullptr, nullptr) != 1) SSL_ERROR("Could not write new key: "); + flagKeyModified = false; + } else { + assert(false); //TODO implement with membuf + /* + void* ptr = nullptr; + auto length = BIO_get_mem_data(certBio, &ptr); + if(!ptr) SSL_ERROR("Could not get cert bio mem pointer: "); + if(length <= 0) SSL_ERROR("Could not get cert bio mem length (" + to_string(length) + "): "); + rawCert.reserve(length); + memcpy((void*) rawCert.data(), ptr, length); + */ + } + } + + return result; +} + +std::shared_ptr SSLManager::loadSSL(const std::string &rawKey, std::string &error, bool rawData, bool readPublic) { + std::shared_ptr keyBio = nullptr; + std::shared_ptr key = nullptr; + std::shared_ptr result = make_shared(); +// SSL_CTX_set_ecdh_auto(ctx, 1); + if(rawData) { + keyBio = shared_ptr(BIO_new(BIO_s_mem()), ::BIO_free); + BIO_write(keyBio.get(), rawKey.c_str(), rawKey.length()); + } else { + auto keyPath = fs::u8path(rawKey); + + if(!fs::exists(keyPath)) { + try { + if(keyPath.has_parent_path()) + fs::create_directories(keyPath.parent_path()); + } catch (fs::filesystem_error& error) { + logError("Could not create key directory: " + string(error.what())); + } + + { + std::ofstream { keyPath }; + } + } + keyBio = shared_ptr(BIO_new_file(rawKey.c_str(), "r"), ::BIO_free); + if(!keyBio) SSL_ERROR("Could not load key: "); + } + + if(readPublic) + key = shared_ptr(PEM_read_bio_PUBKEY(keyBio.get(), nullptr, nullptr, nullptr), ::EVP_PKEY_free); + else + key = shared_ptr(PEM_read_bio_PrivateKey(keyBio.get(), nullptr, nullptr, nullptr), ::EVP_PKEY_free); + result->contains_private = !readPublic; + if(!key) { + if(readPublic) { + SSL_ERROR("Could not read key!"); + } else return this->loadSSL(rawKey, error, rawData, true); + } + + result->key = key; + return result; +} + +bool SSLManager::verifySign(const std::shared_ptr &key, const std::string &message, const std::string &sign) { + assert(key); + auto hash = digest::sha256(message); + return RSA_verify(NID_sha256, (u_char*) hash.data(), hash.length(), (u_char*) sign.data(), sign.length(), EVP_PKEY_get1_RSA(key->key.get())) == 1; +} + +std::shared_ptr SSLManager::web_ssl_options() { + lock_guard lock(this->_web_options_lock); + if(this->_web_options || this->_web_disabled) + return this->_web_options; + + this->_web_options = make_shared(); + this->_web_options->type = pipes::SSL::SERVER; + this->_web_options->context_method = TLS_method(); + this->_web_options->free_unused_keypairs = false; /* we dont want our keys get removed */ + + string web_prefix = "web_"; + for(auto& context : this->contexts) { + auto name = context.first; + if(name.length() < web_prefix.length()) + continue; + if(name.substr(0, web_prefix.length()) != web_prefix) + continue; + + auto servername = context.first.substr(web_prefix.length()); + if(servername == "default") { + this->_web_options->default_keypair({context.second->privateKey, context.second->certificate}); + } else { + this->_web_options->servername_keys[servername] = {context.second->privateKey, context.second->certificate}; + } + } + return this->_web_options; +} \ No newline at end of file diff --git a/src/ssl/SSLManager.h b/src/ssl/SSLManager.h new file mode 100644 index 0000000..5fa83fb --- /dev/null +++ b/src/ssl/SSLManager.h @@ -0,0 +1,62 @@ +#pragma once + +#include "openssl/ssl.h" +#include "openssl/err.h" +#include +#include +#include + +namespace ts { + namespace ssl { + struct SSLContext { + std::shared_ptr context = nullptr; + std::shared_ptr privateKey = nullptr; + std::shared_ptr certificate = nullptr; + }; + + struct SSLGenerator { + std::deque> subjects; + std::deque> issues; + + EVP_PKEY* generateKey(); + X509* generateCertificate(EVP_PKEY*); + }; + + struct SSLKeyPair { + bool contains_private = false; + std::shared_ptr key = nullptr; + }; + + class SSLManager { + public: + SSLManager(); + virtual ~SSLManager(); + + bool initialize(); + void printDetails(); + + std::shared_ptr initializeSSLKey(const std::string &key, const std::string &rsaKey, std::string &error, bool raw = false); + std::shared_ptr initializeContext(const std::string& key, std::string& privateKey, std::string& certificate, std::string& error, bool raw = false, const std::shared_ptr& = nullptr); + + std::shared_ptr getContext(const std::string& key){ return this->contexts[key]; } + std::shared_ptr getRsaKey(const std::string& key){ return this->rsa[key]; } + + bool verifySign(const std::shared_ptr& key, const std::string& message, const std::string& sign); + + void disable_web() { this->_web_disabled = true; } + std::shared_ptr web_ssl_options(); + std::shared_ptr getQueryContext() { return this->getContext("query"); } + + private: + std::map> contexts; + std::map> rsa; + + std::mutex _web_options_lock; + bool _web_disabled = false; + std::shared_ptr _web_options; + + std::shared_ptr loadContext(std::string& rawKey, std::string& rawCert, std::string& error, bool rawData = false, const std::shared_ptr& = nullptr); + std::shared_ptr loadSSL(const std::string &key, std::string &error, bool rawData = false, bool readPublic = false); + }; + } +} \ No newline at end of file diff --git a/test/BBTest.cpp b/test/BBTest.cpp new file mode 100644 index 0000000..6d61aef --- /dev/null +++ b/test/BBTest.cpp @@ -0,0 +1,29 @@ +#include +#include +#include +#include +#include + +#define TEST(message, tag) \ +cout << "Testing '" << message << "' for " << tag << " results in " << bbcode::sloppy::has_tag(message, {tag}) << endl; + +using namespace std; +using namespace std::chrono; +int main() { + /* + auto beg = system_clock::now(); + TEST("Hello [img]World[/img]", "img"); + TEST("[img]World[/img] Hello", "img"); + TEST("[img]World[img] Hello", "img"); + TEST("[img=https://www.teaspeak.de]World[/img] Hello", "img"); + TEST("\\[img]World[/img] Hello", "img"); + TEST("[img]World[img] Hello", "img"); + TEST("[img=https://www.teaspeak.de]World[/img] Hello", "img"); + TEST("\\[img]World[/img] Hello", "img"); + TEST("[img=https://www.teaspeak.de]World[/img] Hello", "img"); + TEST("\\[img]World[/img] Hello", "img"); + auto end = system_clock::now(); + cout << "Needed nanoseconds: " << duration_cast(end - beg).count() << endl; + */ + return 0; +} \ No newline at end of file diff --git a/test/ChannelTest.cpp b/test/ChannelTest.cpp new file mode 100644 index 0000000..eb6816a --- /dev/null +++ b/test/ChannelTest.cpp @@ -0,0 +1,121 @@ + +#include +#include +#include +#include "channel/TreeView.h" + +using namespace std; +using namespace std::chrono; +using namespace ts; + +struct TEntry : public ts::TreeEntry { + public: + TEntry(ChannelId channel_id) : channel_id(channel_id), previous_id(0) {} + + ChannelId channelId() const override { + return channel_id; + } + + ChannelId previousChannelId() const override { + return previous_id; + } + + void setParentChannelId(ChannelId id) override { + + } + + void setPreviousChannelId(ChannelId id) override { + previous_id = id; + } + + bool deleted() const override { + return _deleted; + } + + void set_deleted(bool b) override { + _deleted = b; + } + + bool _deleted = false; + ChannelId channel_id; + ChannelId previous_id; +}; + +void tree_print_entry(const std::shared_ptr& t_entry, int deep) { + auto entry = dynamic_pointer_cast(t_entry); + assert(entry); + + string prefix; + while(deep-- > 0) prefix += " "; + + cout << prefix << "- " << entry->channel_id << endl; +} + + +#define PT \ +cout << " --------- TREE --------- " << endl; \ +tree.print_tree(tree_print_entry); \ +cout << " --------- TREE --------- " << endl; + +template +void print_address(const T& idx) { + cout << &idx << endl; + [idx]() { + cout << &idx << endl; + }(); +} + +int main() { + auto index = shared_ptr(); + print_address(index); + return 0; + /* + BasicChannelTree tree; + auto ch3 = tree.createChannel(0, 0, "test channel"); + auto ch2 = tree.createChannel(0, 0, "test channel2"); + auto ch = tree.createChannel(ch2->channelId(), 0, "test channel 2"); + tree.deleteChannelRoot(ch2); + ch2 = nullptr; + ch = nullptr; + + threads::self::sleep_for(seconds(1)); + cout << "XX" << endl; + */ + ChannelId channel_id_index = 0; + + TreeView tree; + + /* Create 10 channels */ + while(channel_id_index < 20) + assert(tree.insert_entry(make_shared(channel_id_index++))); + PT + + /* Test order id */ + for(int i = 0; i < 10000; i++) { //Random test + auto channel_id = tree.find_entry(rand() % channel_id_index); + auto channel_target = tree.find_entry(rand() % channel_id_index); + assert(channel_id); + assert(channel_target); + printf("Move channel %lu after %lu => ", channel_id->channelId(), channel_target->channelId()); + printf("%x\n", tree.move_entry(channel_id, nullptr, channel_target)); + } + PT + + /* Test move */ + for(int i = 0; i < 10002; i++) { //Random test + auto channel_id = tree.find_entry(rand() % channel_id_index); + auto channel_target = tree.find_entry(rand() % channel_id_index); + printf("Move channel parent of %lu to %lu => ", channel_id->channelId(), channel_target->channelId()); + printf("%x\n", tree.move_entry(channel_id, channel_target)); + } + PT + + memtrack::statistics(); + + for(int i = 0; i < 20; i++) { + tree.delete_entry(tree.find_entry(rand() % channel_id_index)); + PT + memtrack::statistics(); + } + return 0; +} \ No newline at end of file diff --git a/test/CommandTest.cpp b/test/CommandTest.cpp new file mode 100644 index 0000000..4875440 --- /dev/null +++ b/test/CommandTest.cpp @@ -0,0 +1,156 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "PermissionManager.h" + +#include "src/query/command_constants.h" + +using namespace std; +using namespace ts; +using namespace license::teamspeak; + +template +using field = ts::descriptor::field; + +template +using trigger = ts::descriptor::trigger; + +void handleCommand( + ts::command& _cmd, + cconstants::return_code::optional return_code, + field key_a, + field::optional key_b, + field::optional::bulked key_c, + trigger switch_test +) { + if(key_a.value() < 10) + cout << "ERROR" << endl; + auto b = key_c.as(); + string key_c_str = key_c; + + auto c = key_b.has_value(); + + cout << key_c[1].value() << endl; + cout << "Return code: " << return_code.get_or("XXX") << endl; + __asm__("nop"); +} + +command_result test() { + return command_result{error::vs_critical}; +} + +command_result test2() { + return command_result{permission::b_virtualserver_select_godmode}; +} + +command_result test3() { + return command_result{error::vs_critical, "unknown error"}; +} + +void eval_test(command_result x) { + if(x.is_detailed()) { + cout << "Detailed!" << endl; + delete x.release_details(); + } else { + auto a = x.permission_id(); + auto b = x.error_code(); + cout << (void*) a << " - " << (void*) b << endl; + } +} + +int main() { + //for(const auto& error : avariableErrors) + // cout << error.name << " = " << hex << "0x" << error.errorId << "," << endl; + #if false + { + string error; + istringstream is(base64::decode("AQCvbHFTQDY/terPeilrp/ECU9xCH5U3xC92lYTNaY/0KQAJFueAazbsgAAAACVUZWFtU3BlYWsgU3lzdGVtcyBHbWJIAADCxw83PomiAdX11jLso/hdMVov4e8N79iq2NRhwkIjqgAKSyfbMFu6mwAAACRUZWFtU3BlYWsgc3lzdGVtcyBHbWJIAAA+kiuhufX/V96lZzq4MICxnYxFPkV/mG03Pu+VYgUISwIKU0+AC1RkAAQAAAIASmFuLU5pa2xhcyBLZXR0ZW5idXJnAADlcwyCViOfHJKYvWHHqyL9R1Ba5ZP+cL7MQ5nI7s1R2yAKnICdCp0pXQ==")); + auto chain = LicenseChain::parse(is, error); + chain->print(); + cout << base64::encode(chain->generatePublicKey(public_root)) << endl; + cout << base64::encode(chain->generatePublicKey(public_root, 1)) << endl; + cout << base64::encode(chain->generatePublicKey(public_root, 2)) << endl; + cout << base64::encode(chain->generatePublicKey(public_root, 3)) << endl; + } + #endif + + eval_test(test()); + eval_test(test2()); + eval_test(test3()); + /* + ios_base::sync_with_stdio(false); // Avoids synchronization with C stdio on gcc + // (either localize both or disable sync) + + wcout.imbue(locale("de_DE.ISO-8859-1")); // change default locale + + //░█▀▀▀█░░░░▄█░░░░░░░░░▄█░░░░░▄█░░ + const auto message = "\221\210\200\200\200\210\221\221\221\221\204\210\221\221\221\221\221\221\221\221\221\204\210\221\221\221\221\221\204\210\221\221"; + const auto auto_message = "░█▀▀▀█░░░░▄█░░░░░░░░░▄█░░░░░▄█░░"; + cout << " -> " << message << endl; + cout << " -> " << utf8_check_is_valid(message) << endl; + cout << " -> " << utf8_check_is_valid("░█▀▀▀█░░░░▄█░░░░░░░░░▄█░░░░░▄█░░") << endl; + + Command cmd = Command::parse("test -mapping test=░█▀▀▀█░░░░▄█░░░░░░░░░▄█░░░░░▄█░░_ -x"); + cout << "Build: " << cmd.build() << endl; + cout << "X: " << cmd["test"] << endl; + cout << " -> " << endl; + for(const auto& e : cmd.parms()) + cout << e << endl; + */ + + /* + auto handle = make_shared(); + + ts::command_entry entry(handle); + entry = 255; + cout << "Value: " << entry.as() << endl; + cout << "Value: " << entry.melt().as() << endl; + + cout << "Str: " << entry.string() << endl; + cout << "U8: " << (int) entry.melt().as() << endl; + */ + + //register_function(handleCommand); + + cout << sizeof(command_result) << endl; + ts::command cmd("notify"); + + cout << ts::command::parse("test a=b ").build(ts::command::format::BRACE_ESCAPED_QUERY) << endl; + cout << ts::command::parse("test a=").build(ts::command::format::BRACE_ESCAPED_QUERY) << endl; + cout << ts::command::parse("test a").build(ts::command::format::BRACE_ESCAPED_QUERY) << endl; + cout << ts::command::parse("a=c", false).build(ts::command::format::BRACE_ESCAPED_QUERY) << endl; + cout << ts::command::parse("a=c | a=c -x", false).build(ts::command::format::BRACE_ESCAPED_QUERY) << endl; + cout << ts::command::parse("a a=c|a=c").build(ts::command::format::BRACE_ESCAPED_QUERY) << endl; + cout << ts::command::parse("a a=c a=c2 -z | a=c").build(ts::command::format::BRACE_ESCAPED_QUERY) << endl; + + /* + * + cmd[0]["key_a"] = 2; + cmd["b"] = 3; + cmd["c"] = "Hello World"; + cmd["c"] = "Hello World" + string(); + cmd["c"] = 2; + cmd.set_trigger("test"); + cmd.set_trigger("test2"); + cout << "Key_A => " << cmd[0]["key_a"].string() << endl; + */ + cmd["key_a"] = "0"; + cmd["key_b"] = "key_b_value"; + cmd["return_code"] = "ASD"; + cmd[0]["key_c"] = "key_c_value_0"; + cmd[1]["key_c"] = "key_c_value_1"; + cmd.set_trigger("test"); + + auto cmd_handler = ts::descriptor::parse_function(handleCommand); + cmd_handler->invoke(cmd); + cout << cmd.build() << endl; + + //auto v = ts::descriptor::entry::bulked::val; + return 0; +} \ No newline at end of file diff --git a/test/CrashTest.cpp b/test/CrashTest.cpp new file mode 100644 index 0000000..3cf1f64 --- /dev/null +++ b/test/CrashTest.cpp @@ -0,0 +1,67 @@ +#include +#include +#include +#include + +using namespace std; +using namespace std::chrono; + +inline void test(const std::string& str, const nanoseconds& expected, bool expectResult) { + string error; + auto result = period::parse(str, error); + if(expectResult) { + if(!error.empty()) { + cerr << "Test '" << str << "' failed. Got unexpected error: " << error << endl; + } else { + if(result == expected) { + cout << "Test '" << str << "' Succeed. Got expected result" << endl; + } else if(result != expected) { + cout << "Test '" << str << "' failed. Got unexpected result: " << result.count() << ". Expected: " << expected.count() << endl; + } else { + cout << "Test '" << str << "' Succeed. Got expected result" << endl; + } + } + } else { + if(error.empty()) { + cerr << "Test '" << str << "' failed. Expected error, but got success!" << endl; + } else { + cout << "Test '" << str << "' Succeed. Got expected error: " << error << endl; + } + } +} + +int stack() { + char buffer[1]; + for(register int i = 0; i <= 32; i++) { + *(buffer + i) = 3; + *(buffer - i) = 3; + } + return buffer[0]; +} + +int main(int argc, char* argv[]) { + cout << "Stack: " << stack() << endl; + terminal::install(); + auto config = make_shared(); + config->logfileLevel = spdlog::level::off; + config->terminalLevel = spdlog::level::trace; + logger::setup(config); + + test("1h", hours(1), true); + test("2h", hours(2), true); + test("30h", hours(30), true); + + test("30s", seconds(30), true); + test("30s?", seconds(30), false); + test("s", seconds(30), false); + + test("1m:30s", seconds(90), true); + + logMessageFmt(false, 0, "Hello {}", "A", "B", "C"); + logTrace("Hello World"); + logTrace(0, "Hello World"); + logTrace(0, "Hello World"); + logTrace(0, "Hello {:1}", "World"); + logTraceFmt(true, 0, "Hello {:2}", "World", "Dux"); + return 0; +} \ No newline at end of file diff --git a/test/EndianessTest.cpp b/test/EndianessTest.cpp new file mode 100644 index 0000000..453e6ca --- /dev/null +++ b/test/EndianessTest.cpp @@ -0,0 +1,59 @@ +#include +#include +#include + +union TypeBuffer { + char buf[8]; + union { + char n8_pad[7]; + uint8_t un8; + }; + union { + char n16_pad[6]; + uint16_t un16; + }; + union { + char n32_pad[4]; + uint32_t un32; + }; + union { + uint64_t un64; + }; +}; + +#define _T(size, n, rn) \ +le2be ##size(0x ##n, buffer.buf); \ +assert(buffer.un ##size == 0x ##rn); \ +assert(be2le ##size(buffer.buf) == 0x ##n) + +#define T8(_1) _T(8, _1, _1) +#define T16(_1, _2) _T(16, _1 ##_2, _2 ##_1) +#define T32(_1, _2, _3, _4) _T(32, _1 ##_2 ##_3 ##_4, _4 ##_3 ##_2 ##_1) +#define T64(_1, _2, _3, _4, _5, _6, _7, _8) _T(64, _1 ##_2 ##_3 ##_4 ##_5 ##_6 ##_7 ##_8, _8 ##_7 ##_6 ##_5 ##_4 ##_3 ##_2 ##_1) + +int main() { + TypeBuffer buffer{}; + static_assert(sizeof(buffer) == 8, ""); + + T8(FF); + T8(00); + T8(AF); + T8(7F); + + T16(FF, 00); + T16(00, FF); + T16(7F, 8F); + T16(23, CA); + + T32(FF, FF, FF, FF); + T32(FF, FF, 00, FF); + T32(F0, FF, 00, 0F); + T32(F0, CF, 00, 0F); + + T64(FF, FF, FF, FF, FF, FF, FF, FF); + T64(FF, 00, 00, 00, 00, 00, 00, 00); + + T64(FF, FF, 00, FF, 00, 00, 00, 00); + T64(F0, FF, 00, 0F, AF, BF, 0F, CF); + T64(F0, CF, 00, 0F, A2, 00, 03, 22); +} \ No newline at end of file diff --git a/test/LinkedTest.cpp b/test/LinkedTest.cpp new file mode 100644 index 0000000..0b761fc --- /dev/null +++ b/test/LinkedTest.cpp @@ -0,0 +1,29 @@ +#include +#include + +using namespace std; + +int main() { + deque entries = { + linked::create_entry(0, 1, 0), + linked::create_entry(0, 2, 1), + linked::create_entry(0, 3, 3), + linked::create_entry(0, 4, 5), + linked::create_entry(0, 5, 3), + }; + + deque errors; + auto head = linked::build_chain(entries, errors); + if(!errors.empty()) { + cout << "got " << errors.size() << " errors" << endl; + for(const auto& error : errors) + cout << " " << error << endl; + } + + while(head) { + cout << " => " << head->entry_id << endl; + head = head->next; + } + + return 0; +} \ No newline at end of file diff --git a/test/PropertyTest.cpp b/test/PropertyTest.cpp new file mode 100644 index 0000000..3caf2e9 --- /dev/null +++ b/test/PropertyTest.cpp @@ -0,0 +1,69 @@ +#include +#include +#include "src/misc/timer.h" + +using namespace ts; +using namespace std; + + +int main() { + assert(property::impl::validateUnique()); + + cout << property::impl::info(property::VIRTUALSERVER_HOST)->name << endl; + cout << property::impl::info("virtualserver_host")->name<< endl; + + Properties props; + props.register_property_type(); + + cout << "X: " << property::impl::info(property::SERVERINSTANCE_QUERY_PORT)->default_value << endl; + auto property = props[property::SERVERINSTANCE_QUERY_PORT]; + + cout << "Port: " << props[property::SERVERINSTANCE_QUERY_PORT].as() << endl; + props[property::SERVERINSTANCE_QUERY_PORT] = "XX"; + cout << "Port: " << props[property::SERVERINSTANCE_QUERY_PORT].as() << endl; + props[property::SERVERINSTANCE_QUERY_PORT] = 2; + cout << "Port: " << props[property::SERVERINSTANCE_QUERY_PORT].as() << endl; + cout << "Port: " << props[property::SERVERINSTANCE_QUERY_PORT].as() << endl; + + + { + assert(property::impl::validateInput("022222", property::TYPE_UNSIGNED_NUMBER) == true); + assert(property::impl::validateInput("000000", property::TYPE_UNSIGNED_NUMBER) == true); + assert(property::impl::validateInput("011011", property::TYPE_UNSIGNED_NUMBER) == true); + + assert(property::impl::validateInput("-022222", property::TYPE_UNSIGNED_NUMBER) == false); + assert(property::impl::validateInput(" 00000", property::TYPE_UNSIGNED_NUMBER) == false); + assert(property::impl::validateInput("01101.", property::TYPE_UNSIGNED_NUMBER) == false); + + assert(property::impl::validateInput("022222", property::TYPE_SIGNED_NUMBER) == true); + assert(property::impl::validateInput("000000", property::TYPE_SIGNED_NUMBER) == true); + assert(property::impl::validateInput("011011", property::TYPE_SIGNED_NUMBER) == true); + assert(property::impl::validateInput("-022222", property::TYPE_SIGNED_NUMBER) == true); + assert(property::impl::validateInput("-00000", property::TYPE_SIGNED_NUMBER) == true); + + assert(property::impl::validateInput("01101.", property::TYPE_SIGNED_NUMBER) == false); + assert(property::impl::validateInput("01-101", property::TYPE_SIGNED_NUMBER) == false); + + assert(property::impl::validateInput("01101.", property::TYPE_FLOAT) == true); + assert(property::impl::validateInput("-01101.", property::TYPE_FLOAT) == true); + assert(property::impl::validateInput("-.1", property::TYPE_FLOAT) == true); + assert(property::impl::validateInput("-2.22222", property::TYPE_FLOAT) == true); + + assert(property::impl::validateInput("01101.-2", property::TYPE_FLOAT) == false); + assert(property::impl::validateInput("-011.01.", property::TYPE_FLOAT) == false); + assert(property::impl::validateInput("-.1-", property::TYPE_FLOAT) == false); + assert(property::impl::validateInput("-2.22222.2", property::TYPE_FLOAT) == false); + } + + { + TIMING_START(timings); + this_thread::sleep_for(chrono::milliseconds(100)); + TIMING_STEP(timings, "01"); + this_thread::sleep_for(chrono::milliseconds(200)); + TIMING_STEP(timings, "02"); + this_thread::sleep_for(chrono::milliseconds(50)); + TIMING_STEP(timings, "03"); + this_thread::sleep_for(chrono::milliseconds(150)); + cout << TIMING_FINISH(timings) << endl; + } +} \ No newline at end of file diff --git a/test/RingTest.cpp b/test/RingTest.cpp new file mode 100644 index 0000000..774dd6f --- /dev/null +++ b/test/RingTest.cpp @@ -0,0 +1,98 @@ +// +// Created by wolverindev on 14.01.19. +// + +#include +#include +#include +#include +#include + +using namespace std; +using namespace std::chrono; +using namespace ts; +using namespace ts::protocol; + +template +void print_queue(RingBuffer& buffer) { + cout << "Buffer size: " << buffer.capacity() << endl; + for(size_t index = 0; index < buffer.capacity(); index++) { + auto set = buffer.slot_set((T) index); + if(buffer.current_slot() == index) + cout << " "; + else + cout << " "; + cout << index << " => " << set; + if(set) + cout << " " << buffer.slot_value(index); + cout << endl; + } +} + +void print_meminfo() { + auto meminfo = buffer::buffer_memory(); + printf("Used buffer bytes: %zu, Allocated buffer bytes %zu Internal: %zu. Nodes: %zu Nodes full: %zu\n", meminfo.bytes_buffer_used, meminfo.bytes_buffer, meminfo.bytes_internal, meminfo.nodes,meminfo.nodes_full); +} + +void print_cleanup_memory(buffer::cleanmode::value mode) { + auto info = buffer::cleanup_buffers(mode); + printf("Cleanuped %zu, Buffer: %zu, Internal: %zu\n", info.bytes_freed_buffer + info.bytes_freed_internal, info.bytes_freed_buffer, info.bytes_freed_internal); +} + +#define TEST_COUNT (1000000) +void malloc_speedtest() { + auto alloc_begin = system_clock::now(); + deque buffers; + for(size_t i = 0; i < TEST_COUNT; i++) + buffers.push_back(buffer::allocate_buffer(((buffer::size::value) i % 3) + 1)); + auto alloc_end = system_clock::now(); + buffers.clear(); + auto free_end = system_clock::now(); + printf("Alloc: %li Free: %li\n", duration_cast(alloc_end - alloc_begin).count() / TEST_COUNT, duration_cast(free_end - alloc_end).count() / TEST_COUNT); +} + +int main() { + /* + RingBuffer buffer; + + buffer.insert_index(0, 1); + buffer.insert_index(1, 2); + buffer.insert_index(2, 3); + buffer.pop_front(); + buffer.insert_index(0, 1); + + + //assert(buffer.front_set()); + + print_queue(buffer); + cout << " => " << buffer.current_index() << " = " << buffer.pop_front() << endl; + cout << " => " << buffer.current_index() << " = " << buffer.pop_front() << endl; + cout << " => " << buffer.current_index() << " = " << buffer.pop_front() << endl; + / + assert(buffer.accept_index(0)); + assert(buffer.accept_index(1)); + assert(!buffer.accept_index(2)); + + print_queue(buffer); + assert(buffer.insert_index(1, 2)); + print_queue(buffer); + assert(buffer.insert_index(0, 1)); + print_queue(buffer); + assert(!buffer.insert_index(3, 2)); + print_queue(buffer); + + cout << buffer.pop_front() << endl; + print_queue(buffer); + assert(buffer.insert_index(2, 3)); + print_queue(buffer); + cout << buffer.pop_front() << endl; + print_queue(buffer); + + assert(buffer.accept_index(3)); + assert(!buffer.accept_index(4)); + assert(!buffer.accept_index(5)); + buffer.push_front(2); + buffer.push_front(1); + print_queue(buffer); + */ +} \ No newline at end of file diff --git a/test/SQLTest.cpp b/test/SQLTest.cpp new file mode 100644 index 0000000..1b64bd5 --- /dev/null +++ b/test/SQLTest.cpp @@ -0,0 +1,150 @@ +#include +#include +#include +#include + +using namespace sql; +using namespace std; +using namespace std::chrono; + + +int f(int i); +void testCompiler() { + /* + command(nullptr, "Hello World", variable{"x", "y"}, variable{"x", "y"}, variable{"x", "y"}).query([](int length, std::string* values, std::string* names) -> int { + return 0; + }); + + + command(nullptr, "Hello World", variable{"x", "y"}).query([](int length, char** values, char** names) { + + return 0; + }); + */ + + + /* + //This should fail! + command(nullptr, "Hello World", variable{"x", "y"}).query([](int length, char*** values, char** names) { + + return 0; + }); + */ + + /* + command(nullptr, "Hello World", variable{"x", "y"}).query([](void*, int length, char** values, char** names) { + + return 0; + }, (void*) nullptr); + + command(nullptr, "Hello World", variable{"x", "y"}).query([](int*, int length, std::string* values, std::string* names) { + + return 0; + }, nullptr); + + + command(nullptr, "Hello World", variable{"x", "y"}).query([](int length, char** values, char** names) { + + return 0; + }); + + int d = 0; + command((SqlManager*) nullptr, std::string("Hello World")).query([](const int& data, int length, std::string* values, std::string* names) -> int { + + return 0; + }, (const int&) d); + + auto cmd = command(nullptr, "", variable{"X", "Y"}); + cmd.value({"", "b"}); + */ + + { + //auto lambda = [](int, string*, string*) -> int { return false; }; + //command(nullptr, "").query_(lambda); + } + { + auto lambda = [](int, string*, int, string*, string*) -> int { return false; }; + command(nullptr, "").query(lambda, 1, (string*) nullptr); + } + auto lambda = [](int, int, char**, char**) -> bool { return false; }; + command(nullptr, "").query(lambda, 1); + + { + struct ProxyClass { + void handle(int, int, string*, string*) {} + } proxy; + + command(nullptr, "").query(&ProxyClass::handle, &proxy, 1); + } +} + +int main() { + //testCompiler(); + +#if false + sql::sqlite::SqliteManager manager; + sql::result res{}; + + manager.connect("test.sqlite"); + + cout << command(&manager, "CREATE TABLE `test` (`key` TEXT, `value` TEXT)").execute() << endl; + //cout << sql::command(&manager, "INSERT INTO `test` (`key`, `value`) VALUES (:key,:value)", variable{":key", "date"}, variable{":value", "test: " + to_string(system_clock::now().time_since_epoch().count())}).execute() << endl; + int64_t result = 0; + res = sql::command(&manager, "SELECT * FROM `test`").query([](int64_t& r, int64_t* a, int length, string* names, string* values) { + cout << "Got entry: Key: " << names[0] << " Value: " << names[1] << endl; + r = 1; + *a = 2; + return 0; + }, result, &result); + cout << " -> " << result << endl; + assert(res); +#endif +#if false + sql::mysql::MySQLManager manager; + sql::result res{}; + assert(res = manager.connect("mysql://localhost:3306/teaspeak?userName=root&password=markus&connections=4")); + /* + assert(res = sql::command(&manager, "CREATE TABLE IF NOT EXISTS `test` (`key` TEXT, `value` TEXT)").execute()); + cout << "Old:" << endl; + assert(res = sql::command(&manager, "SELECT * FROM `test`").query([](int length, string* names, string* values) { + cout << "Got entry: Key: " << names[0] << " Value: " << names[1] << endl; + return 0; + })); + assert(res = sql::command(&manager, "INSERT INTO `test` (`key`, `value`) VALUES (:key,:value)", variable{":key", "date"}, variable{":value", "test: " + to_string(system_clock::now().time_since_epoch().count())}).execute()); + cout << "New:" << endl; + assert(res = sql::command(&manager, "SELECT * FROM `test`").query([](int length, string* names, string* values) { + cout << "Got entry: Key: " << names[0] << " Value: " << names[1] << endl; + return 0; + })); + */ + for(int i = 0; i < 80; i++) { + assert(res = sql::command(&manager, "SHOW TABLES").query([](int length, string* names, string* values) { + //cout << "Got entry: Key: " << names[0] << ":" << length << endl; + return 0; + })); + //threads::self::sleep_for(seconds(1)); + } + sql::command(&manager, "SHOW status;").query([](int length, string* values, string* names) { + if(values[0].find("Com_stmt_") != -1) + cout << values[0] << " => " << values[1] << endl; + }); + + cout << " ------------------- " << endl; + for(int i = 0; i < 80; i++) { + assert(res = sql::command(&manager, "SHOW TABLES").query([](int length, string* names, string* values) { + //cout << "Got entry: Key: " << names[0] << ":" << length << endl; + return 0; + })); + //threads::self::sleep_for(seconds(1)); + } + sql::command(&manager, "SHOW status;").query([](int length, string* values, string* names) { + if(values[0].find("Com_stmt_") != -1) + cout << values[0] << " => " << values[1] << endl; + }); +#endif + +#if true + //{":hello", "world"}, {":yyyy", "xxx"}, {":numeric", 2} + sql::command((SqlManager*) nullptr, std::string("SELECT *"), {":hello", "world"}, {":numeric", 2}); +#endif +} \ No newline at end of file diff --git a/test/WSSTest.cpp b/test/WSSTest.cpp new file mode 100644 index 0000000..5f87fa4 --- /dev/null +++ b/test/WSSTest.cpp @@ -0,0 +1,303 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; +int create_socket(int port) +{ + int s; + struct sockaddr_in addr; + + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + addr.sin_addr.s_addr = htonl(INADDR_ANY); + + s = socket(AF_INET, SOCK_STREAM, 0); + if (s < 0) { + perror("Unable to create socket"); + exit(EXIT_FAILURE); + } + int optval = 1; + if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(int)) < 0) + printf("Cannot set SO_REUSEADDR option on listen socket (%s)\n", strerror(errno)); + + if (setsockopt(s, SOL_SOCKET, SO_REUSEPORT, &optval, sizeof(int)) < 0) + printf("Cannot set SO_REUSEADDR option on listen socket (%s)\n", strerror(errno)); + + if (bind(s, (struct sockaddr*)&addr, sizeof(addr)) < 0) { + perror("Unable to bind"); + exit(EXIT_FAILURE); + } + if (listen(s, 1) < 0) { + perror("Unable to listen"); + exit(EXIT_FAILURE); + } + + return s; +} + +void setNonBlock(int fd) { + +} + +void init_openssl() +{ + SSL_load_error_strings(); + OpenSSL_add_ssl_algorithms(); +} + +void cleanup_openssl() +{ + EVP_cleanup(); +} + +SSL_CTX *create_context() +{ + const SSL_METHOD *method; + SSL_CTX *ctx; + + method = SSLv23_server_method(); + + ctx = SSL_CTX_new(method); + if (!ctx) { + perror("Unable to create SSL context"); + ERR_print_errors_fp(stderr); + exit(EXIT_FAILURE); + } + + return ctx; +} + +//#define CERTIFICATE_FILE "cert.pem" +//#define KEY_FILE "key.pem" +#define CERTIFICATE_FILE "/home/wolverindev/TeamSpeak/server/environment/default_certificate.pem" +#define KEY_FILE "/home/wolverindev/TeamSpeak/server/environment/default_privatekey.pem" + +std::pair createCerts(pem_password_cb* password) { +/* + auto bio = BIO_new_file("cert.pem", "r"); + if(!bio) { + ERR_print_errors_fp(stderr); + exit(EXIT_FAILURE); + } + + auto cert = PEM_read_bio_X509(bio, nullptr, password, nullptr); + if(!cert) { + ERR_print_errors_fp(stderr); + exit(EXIT_FAILURE); + } + BIO_free(bio); + + + bio = BIO_new_file("key.pem", "r"); + if(!bio) { + ERR_print_errors_fp(stderr); + exit(EXIT_FAILURE); + } + auto key = PEM_read_bio_PrivateKey(bio, nullptr, password, nullptr); + + if(!key) { + ERR_print_errors_fp(stderr); + exit(EXIT_FAILURE); + } + BIO_free(bio); +*/ + + + + auto key = std::unique_ptr(EVP_PKEY_new(), ::EVP_PKEY_free); + + auto rsa = RSA_new(); + auto e = std::unique_ptr(BN_new(), ::BN_free); + BN_set_word(e.get(), RSA_F4); + if(!RSA_generate_key_ex(rsa, 2048, e.get(), nullptr)) return {nullptr, nullptr}; + EVP_PKEY_assign_RSA(key.get(), rsa); + + auto cert = X509_new(); + X509_set_pubkey(cert, key.get()); + + ASN1_INTEGER_set(X509_get_serialNumber(cert), 3); + X509_gmtime_adj(X509_get_notBefore(cert), 0); + X509_gmtime_adj(X509_get_notAfter(cert), 31536000L); + + X509_NAME* name = X509_get_subject_name(cert); + X509_NAME_add_entry_by_txt(name, "C", MBSTRING_ASC, (unsigned char *) "DE", -1, -1, 0); + X509_NAME_add_entry_by_txt(name, "O", MBSTRING_ASC, (unsigned char *) "TeaSpeak", -1, -1, 0); + X509_NAME_add_entry_by_txt(name, "OU", MBSTRING_ASC, (unsigned char *) "Web Server", -1, -1, 0); + X509_NAME_add_entry_by_txt(name, "emailAddress", MBSTRING_ASC, (unsigned char *)"contact@teaspeak.de", -1, -1, 0); + X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, (unsigned char *)"web.teaspeak.de", -1, -1, 0); + + X509_set_issuer_name(cert, name); + X509_set_subject_name(cert, name); + + X509_sign(cert, key.get(), EVP_sha512()); + + + + return {key.release(), cert}; +}; + +/* + * Generate key: + +openssl req -x509 -out cert.pem -newkey rsa:2048 -keyout key.pem -days 365 -passout pass:markus -config <( +cat <<-EOF +[req] +default_bits = 2048 +prompt = no +default_md = sha256 +distinguished_name = dn + +[ dn ] +C=DE +O=TeaSpeak +OU=Web Server +emailAddress=contact@teaspeak.de +CN = web.teaspeak.de +EOF +) + + */ + +void configure_context(SSL_CTX *ctx) +{ + SSL_CTX_set_ecdh_auto(ctx, 1); + auto certs = createCerts([](char* buffer, int length, int rwflag, void* data) -> int { + std::string password = "markus"; + memcpy(buffer, password.data(), password.length()); + return password.length(); + }); + + PEM_write_X509(stdout, certs.second); + + if (SSL_CTX_use_PrivateKey(ctx, certs.first) <= 0 ) { + ERR_print_errors_fp(stderr); + exit(EXIT_FAILURE); + } + + if (SSL_CTX_use_certificate(ctx, certs.second) <= 0 ) { + ERR_print_errors_fp(stderr); + exit(EXIT_FAILURE); + } + +} + +struct Client { + int fd; + ssl::SSLSocket ssl; + ws::WebSocket ws; + + std::deque writeQueue; + + event* read; + event* write; +}; + +void handleRead(int fd, short, void* ptrClient) { + auto client = (Client*) ptrClient; + + ssize_t bufferLength = 1024; + char buffer[bufferLength]; + + bufferLength = recv(client->fd, buffer, bufferLength, MSG_DONTWAIT); + if(bufferLength < 0){ + cout << "Invalid read: " << bufferLength << " / " << errno << endl; + event_del(client->read); + event_del(client->write); + return; + } else if(bufferLength == 0) return; + + cout << "Read " << bufferLength << " bytes" << endl; + client->ssl.proceedMessage(string(buffer, bufferLength)); +} + +void handleWrite(int fd, short, void* ptrClient) { + auto client = (Client*) ptrClient; + while(!client->writeQueue.empty()) { + auto message = std::move(client->writeQueue.front()); + client->writeQueue.pop_front(); + send(client->fd, message.data(), message.length(), 0); + cout << "Send " << message.length() << " bytes" << endl; + } +} + +int main(int argc, char **argv) +{ + int sock; + SSL_CTX *ctx; + + init_openssl(); + ctx = create_context(); + configure_context(ctx); + + sock = create_socket(4433); + + auto evLoop = event_base_new(); + threads::Thread([evLoop](){ + while(true) { + event_base_dispatch(evLoop); + threads::self::sleep_for(std::chrono::milliseconds(10)); + }; + cerr << "event_base_dispatch() exited!" << endl; + }).detach(); + /* Handle connections */ + cout << "Waiting!" << endl; + while(1) { + struct sockaddr_in addr{}; + uint len = sizeof(addr); + + int fd = accept(sock, (struct sockaddr*)&addr, &len); + if (fd < 0) { + perror("Unable to accept"); + exit(EXIT_FAILURE); + } + auto client = new Client{}; + client->fd = fd; + + + client->read = event_new(evLoop, fd, EV_READ | EV_PERSIST, handleRead, client); + client->write = event_new(evLoop, fd, EV_WRITE, handleWrite, client); + + client->ssl.callback_error = [](ssl::SSLSocketError, const std::string& error) { + cout << error << endl; + }; + client->ssl.callback_write = [client](const std::string& buffer) { + client->writeQueue.push_back(buffer); + event_add(client->write, nullptr); + }; + client->ssl.callback_read = [client](const std::string& buffer) { + cout << buffer; + client->ws.proceedMessage(buffer); + }; + auto ptr = std::shared_ptr(ctx, [](SSL_CTX*){}); + client->ssl.initialize(ptr); + + client->ws.on_message = [](const std::string& message) { + cout << "[WS] Message: " << endl << message << endl; + }; + client->ws.write_message = [client](const std::string& data) { + cout << "[WS] Send: " << endl << data << endl; + client->ssl.sendMessage(data); + }; + + client->ws.initialize(); + + event_add(client->read, nullptr); + } + + close(sock); + SSL_CTX_free(ctx); + cleanup_openssl(); +} \ No newline at end of file