Initial commit

This commit is contained in:
WolverinDEV 2019-06-26 22:11:22 +02:00
commit ff36addda5
99 changed files with 18395 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
cmake-build-*/
.idea/

229
CMakeLists.txt Normal file
View File

@ -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()

73
main.cpp Normal file
View File

@ -0,0 +1,73 @@
#include <iostream>
#include <src/sql/SqlQuery.h>
#include <src/sql/sqlite/SqliteSQL.h>
#include <src/sql/mysql/MySQL.h>
#include <cppconn/exception.h>
#include <cppconn/statement.h>
#include <cppconn/prepared_statement.h>
#include <cppconn/resultset.h>
#include <cppconn/metadata.h>
#include <cppconn/driver.h>
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>([](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<int>([](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<sql::Connection> con(driver->connect("", "root", "markus"));
if(!con->isValid()) {
cerr << "Invalid connection!" << endl;
}
con->setSchema("test");
//std::shared_ptr<sql::Statement> stmt(con->createStatement());
/*
sql::mysql::MySQLManager manager;
manager.connect("hellop");
*/
return 0;
}

291
src/BasicChannel.cpp Normal file
View File

@ -0,0 +1,291 @@
#include "log/LogUtils.h"
#include <algorithm>
#include <iostream>
#include <mutex>
#include <sstream>
#include <misc/base64.h>
#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<Properties>());
this->_properties->register_property_type<property::ChannelProperties>();
this->properties()[property::CHANNEL_ID] = channelId;
this->properties()[property::CHANNEL_PID] = parentId;
}
void BasicChannel::setPermissionManager(const std::shared_ptr<ts::permission::PermissionManager>& manager) {
auto handler = [&](std::shared_ptr<permission::Permission> 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<ts::Properties>& 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> 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<BasicChannel>(parent_lock->entry);
}
BasicChannel::BasicChannel(std::shared_ptr<BasicChannel> parent, ChannelId channelId) : BasicChannel(parent ? parent->channelId() : 0, channelId) { }
BasicChannel::~BasicChannel() { }
ChannelType::ChannelType BasicChannel::channelType() {
if (this->properties()[property::CHANNEL_FLAG_PERMANENT].as<bool>()) return ChannelType::ChannelType::permanent;
else if (this->properties()[property::CHANNEL_FLAG_SEMI_PERMANENT].as<bool>()) 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<bool>() || this->properties()[property::CHANNEL_PASSWORD].value().empty()) return true;
if (password.empty()) return false;
if(this->properties()[property::CHANNEL_PASSWORD].as<string>() == password)
return true;
password = base64::encode(digest::sha1(password));
return this->properties()[property::CHANNEL_PASSWORD].as<string>() == password;
}
int BasicChannel::emptySince() {
if (!properties().hasProperty(property::CHANNEL_LAST_LEFT))
return 0;
time_point<system_clock> lastLeft = time_point<system_clock>() + milliseconds(properties()[property::CHANNEL_LAST_LEFT].as<uint64_t>());
return duration_cast<seconds>(system_clock::now() - lastLeft).count();
}
void BasicChannel::setLinkedHandle(const std::weak_ptr<TreeView::LinkedTreeEntry> &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<std::shared_ptr<BasicChannel>> BasicChannelTree::channels(const shared_ptr<BasicChannel> &root, int deep) {
auto result = root ? this->entries_sub(root, deep) : this->entries(root, deep);
std::deque<std::shared_ptr<BasicChannel>> res;
for(const auto& entry : result) {
auto e = dynamic_pointer_cast<BasicChannel>(entry);
if(e) res.push_back(e);
}
return res;
}
shared_ptr<TreeView::LinkedTreeEntry> BasicChannelTree::findLinkedChannel(ts::ChannelId channelId) {
return this->find_linked_entry(channelId);
}
std::shared_ptr<BasicChannel> BasicChannelTree::findChannel(ChannelId channelId) {
if(channelId == 0) return nullptr; /* we start counting at 1! */
return this->findChannel(channelId, this->channels());
}
std::shared_ptr<BasicChannel> BasicChannelTree::findChannel(ChannelId channelId, std::deque<std::shared_ptr<BasicChannel>> avariable) {
for (auto elm : avariable)
if (elm->channelId() == channelId)
return elm;
return nullptr;
}
std::shared_ptr<BasicChannel> BasicChannelTree::findChannel(const std::string &name, const shared_ptr<BasicChannel> &layer) {
for (auto elm : this->channels())
if (elm->name() == name && elm->parent() == layer)
return elm;
return nullptr;
}
std::shared_ptr<BasicChannel> BasicChannelTree::findChannelByPath(const std::string &path) {
int maxChannelDeep = 255; //FIXME
std::deque<std::string> 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<BasicChannel> 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<BasicChannel> 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<std::shared_ptr<ts::BasicChannel>> BasicChannelTree::delete_channel_root(const std::shared_ptr<ts::BasicChannel> &root) {
deque<std::shared_ptr<ts::BasicChannel>> 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<BasicChannel>(channel);
assert(c);
this->on_channel_entry_deleted(c);
result.push_back(c);
}
return result;
}
bool BasicChannelTree::setDefaultChannel(const shared_ptr<BasicChannel> &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<BasicChannel> BasicChannelTree::getDefaultChannel() {
for (auto elm : this->channels())
if (elm->properties()[property::CHANNEL_FLAG_DEFAULT].as<bool>()) return elm;
return nullptr;
}
void BasicChannelTree::on_channel_entry_deleted(const shared_ptr<BasicChannel> &ptr) {
}
std::shared_ptr<BasicChannel> BasicChannelTree::allocateChannel(const shared_ptr<BasicChannel> &parent, ChannelId channelId) {
return std::make_shared<BasicChannel>(parent, channelId);
}
ChannelId BasicChannelTree::generateChannelId() {
ChannelId currentChannelId = 1;
while (this->findChannel(currentChannelId)) currentChannelId++;
return currentChannelId;
}
void BasicChannelTree::printChannelTree(std::function<void(std::string)> fn) {
this->print_tree([&](const std::shared_ptr<TreeEntry>& t_entry, int deep) {
auto entry = dynamic_pointer_cast<BasicChannel>(t_entry);
assert(entry);
string prefix;
while(deep-- > 0) prefix += " ";
fn(prefix + " - " + to_string(entry->channelId()) + " | " + to_string(entry->previousChannelId()) + " (" + entry->name() + ")");
});
}

116
src/BasicChannel.h Normal file
View File

@ -0,0 +1,116 @@
#pragma once
#include <cstdint>
#include <cstdlib>
#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<BasicChannel> current = nullptr;
BasicChannelEntry* next{};
BasicChannelEntry* children{};
~BasicChannelEntry() {
this->current = nullptr;
}
};
class BasicChannel : public TreeEntry {
public:
BasicChannel(ChannelId parentId, ChannelId channelId);
BasicChannel(std::shared_ptr<BasicChannel> parent, ChannelId channelId);
virtual ~BasicChannel();
bool hasParent(){ return !!this->parent(); }
std::shared_ptr<BasicChannel> 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<int64_t>());
}
inline std::shared_ptr<permission::PermissionManager> permissions(){ return this->_permissions; }
virtual void setPermissionManager(const std::shared_ptr<permission::PermissionManager>&);
virtual void setProperties(const std::shared_ptr<Properties>&);
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<TreeView::LinkedTreeEntry> &) override;
protected:
std::weak_ptr<TreeView::LinkedTreeEntry> _link;
std::shared_ptr<Properties> _properties;
std::shared_ptr<permission::PermissionManager> _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<LinkedTreeEntry> findLinkedChannel(ChannelId channelId);
std::shared_ptr<BasicChannel> findChannel(ChannelId channelId);
std::shared_ptr<BasicChannel> findChannel(ChannelId channelId, std::deque<std::shared_ptr<BasicChannel>> avariable);
std::shared_ptr<BasicChannel> findChannel(const std::string &name, const std::shared_ptr<BasicChannel> &layer);
std::shared_ptr<BasicChannel> findChannelByPath(const std::string& path);
//std::deque<std::shared_ptr<BasicChannel>> topChannels();
std::deque<std::shared_ptr<BasicChannel>> channels(const std::shared_ptr<BasicChannel> &root = nullptr, int deep = -1);
virtual std::shared_ptr<BasicChannel> createChannel(ChannelId parentId, ChannelId orderId, const std::string &name);
virtual std::deque<std::shared_ptr<ts::BasicChannel>> delete_channel_root(const std::shared_ptr<BasicChannel> &);
inline bool change_order(const std::shared_ptr<BasicChannel>& channel, ChannelId channelId) {
return this->move_entry(channel, channel->parent(), this->find_entry(channelId));
}
inline bool move_channel(const std::shared_ptr<BasicChannel>& channel, const std::shared_ptr<BasicChannel>& parent, const std::shared_ptr<BasicChannel>& order) {
return this->move_entry(channel, parent, order);
}
bool setDefaultChannel(const std::shared_ptr<BasicChannel> &);
std::shared_ptr<BasicChannel> getDefaultChannel();
void printChannelTree(std::function<void(std::string)> = [](std::string msg) { std::cout << msg << std::endl; });
protected:
virtual ChannelId generateChannelId();
virtual void on_channel_entry_deleted(const std::shared_ptr<BasicChannel> &);
virtual std::shared_ptr<BasicChannel> allocateChannel(const std::shared_ptr<BasicChannel> &parent, ChannelId channelId);
};
}
DEFINE_TRANSFORMS(ts::ChannelType::ChannelType, uint8_t);

155
src/Definitions.h Normal file
View File

@ -0,0 +1,155 @@
#pragma once
#include "Variable.h"
#include "converters/converter.h"
#define tsclient std::shared_ptr<ts::server::ConnectedClient>
#define tsclientmusic std::shared_ptr<ts::server::MusicClient>
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);

176
src/Error.cpp Normal file
View File

@ -0,0 +1,176 @@
//
// Created by wolverindev on 17.10.17.
//
#include "Error.h"
using namespace ts;
const std::vector<ErrorType> 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;
}

333
src/Error.h Normal file
View File

@ -0,0 +1,333 @@
#pragma once
#include <cassert>
#include <cstring>
#include <cstdint>
#include <string>
#include <utility>
#include <vector>
#include <map>
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<std::string, std::string> 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<ErrorType> 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<std::string, std::string> extraProperties;
CommandResultType _type = CommandResultType::GENERAL;
};
struct CommandResultPermissionError : public CommandResult {
public:
CommandResultPermissionError(permission::PermissionType error, const std::string &extraMsg = "");
};
}

212
src/EventLoop.cpp Normal file
View File

@ -0,0 +1,212 @@
#include <thread>
#include <vector>
#include <condition_variable>
#include <cassert>
#include <algorithm>
#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<std::mutex>& 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<event::EventEntry> &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<event::EventEntry> &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<std::mutex> &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<std::mutex> &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<std::mutex> &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;
}
}

88
src/EventLoop.h Normal file
View File

@ -0,0 +1,88 @@
#pragma once
#include <mutex>
#include <memory>
#include <vector>
#include <string>
#include <thread>
#include <condition_variable>
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 <typename class_t>
class ProxiedEventEntry : public event::EventEntry {
public:
using callback_t = void(class_t::*)(const std::chrono::system_clock::time_point &);
ProxiedEventEntry(const std::shared_ptr<class_t>& _instance, callback_t callback) : instance(_instance), callback(callback) { }
std::weak_ptr<class_t> 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<EventEntry>& /* entry */);
bool cancel(const std::shared_ptr<EventEntry>& /* 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<EventEntry> entry;
};
static void _executor(EventExecutor*);
void _spawn_executor(std::unique_lock<std::mutex>&);
void _shutdown(std::unique_lock<std::mutex>&);
void _reset_events(std::unique_lock<std::mutex>&);
#ifndef WIN32
void _reassign_thread_names(std::unique_lock<std::mutex>&);
#endif
bool should_shutdown = true;
bool should_adjust = false; /* thread adjustments */
int target_threads = 0;
std::vector<std::thread> _threads;
std::mutex lock;
std::condition_variable condition;
LinkedEntry* head = nullptr;
LinkedEntry* tail = nullptr;
std::string _thread_prefix;
};
}
}

376
src/License.cpp Normal file
View File

@ -0,0 +1,376 @@
#include <log/LogUtils.h>
#include <cstring>
#include <misc/base64.h>
#include "misc/endianness.h"
#include <misc/digest.h>
#define FIXEDINT_H_INCLUDED
#include <ed25519/ge.h>
#include <ed25519/sc.h>
#include <ed25519/ed25519.h>
#include <iomanip>
#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<LicenseChain> license::teamspeak::Anonymous::chain = []() -> shared_ptr<LicenseChain> {
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> 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<LicenseEntry> result;
switch ((int) buffer[33]) {
case 0x00:
result = make_shared<IntermediateLicenseEntry>();
break;
case 0x02:
result = make_shared<ServerLicenseEntry>();
break;
case 0x03:
result = make_shared<CodeLicenseEntry>();
break;
case 0x05:
result = make_shared<LicenseSignLicenseEntry>();
break;
case 0x20:
result = make_shared<EphemeralLicenseEntry>();
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<seconds>(this->_begin.time_since_epoch()).count() - LicenseEntry::TIMESTAMP_OFFSET), timeBuffer, 0);
le2be32((uint32_t) (duration_cast<seconds>(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> 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<shared_ptr<LicenseEntry>> 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<LicenseChain>();
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<seconds>(entry->begin().time_since_epoch()).count() - LicenseEntry::TIMESTAMP_OFFSET) << ")" << endl;
cout << " End : " << to_string(entry->end()) << " (" << (duration_cast<seconds>(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<IntermediateLicenseEntry>(entry)) {
cout << " Issuer : " << dynamic_pointer_cast<IntermediateLicenseEntry>(entry)->issuer << endl;
cout << " Slots : " << dynamic_pointer_cast<IntermediateLicenseEntry>(entry)->unknown << endl;
} else if(dynamic_pointer_cast<ServerLicenseEntry>(entry)) {
cout << " Issuer : " << dynamic_pointer_cast<ServerLicenseEntry>(entry)->issuer << endl;
cout << " Server license type : " << (int) dynamic_pointer_cast<ServerLicenseEntry>(entry)->licenseType << endl;
cout << " Slots : " << dynamic_pointer_cast<ServerLicenseEntry>(entry)->slots << endl;
} else if(dynamic_pointer_cast<CodeLicenseEntry>(entry)){
cout << " Issuer : " << dynamic_pointer_cast<CodeLicenseEntry>(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<IntermediateLicenseEntry>();
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<LicenseEntry> LicenseChain::addServerEntry(ServerLicenseType type, const std::string &issuer, uint32_t slots) {
auto entry = make_shared<ServerLicenseEntry>();
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<EphemeralLicenseEntry>();
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);
}

197
src/License.h Normal file
View File

@ -0,0 +1,197 @@
#pragma once
#include <sstream>
#include <chrono>
#include <memory>
#include <deque>
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<LicenseEntry> 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 <typename Unit = std::chrono::seconds>
Unit lifetime() { return std::chrono::duration_cast<Unit>(this->_end - this->_begin); }
template <typename Unit = std::chrono::seconds>
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<LicenseChain> parse(std::istream&, std::string&, bool return_on_error = false);
inline static std::shared_ptr<LicenseChain> 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<LicenseChain> copy() {
auto result = std::make_shared<LicenseChain>();
result->entries = this->entries;
return result;
}
void print();
std::string exportChain();
void addEphemeralEntry();
void addIntermediateEntry();
std::shared_ptr<LicenseEntry> addServerEntry(ServerLicenseType, const std::string& issuer, uint32_t slots);
void addEntry(const std::shared_ptr<LicenseEntry>& 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<std::shared_ptr<LicenseEntry>> entries;
private:
};
namespace Anonymous {
extern std::shared_ptr<LicenseChain> chain; //Thu Jun 1 00:00:00 2017 - Sat Sep 1 00:00:00 2018
extern LicensePublicKey root_key;
extern size_t root_index;
}
}
}

1010
src/PermissionManager.cpp Normal file

File diff suppressed because it is too large Load Diff

725
src/PermissionManager.h Normal file
View File

@ -0,0 +1,725 @@
#pragma once
#include <deque>
#include <string>
#include <utility>
#include <vector>
#include <memory>
#include <iostream>
#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<PermissionTypeEntry> 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<GroupType, std::multimap<std::string, std::deque<std::string>>> MapType;
extern MapType unmapping;
extern MapType mapping;
extern std::deque<std::string> map_key(std::string key, GroupType type); //TeamSpeak -> TeaSpeak
extern std::deque<std::string> 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<UpdateEntry> migrate; //TeamSpeak -> TeaSpeak
}
extern std::deque<std::shared_ptr<PermissionTypeEntry>> availablePermissions;
extern std::deque<PermissionType> neededPermissions;
extern std::deque<PermissionGroup> availableGroups;
std::shared_ptr<PermissionTypeEntry> resolvePermissionData(PermissionType);
std::shared_ptr<PermissionTypeEntry> resolvePermissionData(const std::string&);
#define PERM_FLAG_PRIVATE 0b1
#define PERM_FLAG_PUBLIC 0b10
class PermissionManager;
struct Permission {
public:
Permission(const std::shared_ptr<PermissionTypeEntry>& type, PermissionValue value, PermissionValue grant, uint16_t flagMask, std::shared_ptr<BasicChannel> 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<BasicChannel> channel = nullptr;
ChannelId channelId();
bool hasValue(){ return value != permNotGranted; }
bool hasGrant(){ return granted != permNotGranted; }
std::shared_ptr<PermissionTypeEntry> 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<Permission> registerPermission(PermissionType, PermissionValue, const std::shared_ptr<BasicChannel>& channel, uint16_t = PERM_FLAG_PUBLIC);
std::shared_ptr<Permission> registerPermission(const std::shared_ptr<PermissionTypeEntry>&, PermissionValue, const std::shared_ptr<BasicChannel>& channel, uint16_t = PERM_FLAG_PUBLIC);
//void registerAllPermissions(uint16_t flagMask);
bool setPermissionGranted(PermissionType, PermissionValue, const std::shared_ptr<BasicChannel>& channel);
bool setPermission(PermissionType, PermissionValue, const std::shared_ptr<BasicChannel>& channel);
bool setPermission(PermissionType, PermissionValue, const std::shared_ptr<BasicChannel>& channel, bool negated, bool skiped);
void deletePermission(PermissionType, const std::shared_ptr<BasicChannel>& channel);
bool hasPermission(PermissionType, const std::shared_ptr<BasicChannel>& channel, bool testGlobal);
/**
* @param channel Should be null for general testing
* @return <channel>, <global>
*/
std::deque<std::shared_ptr<Permission>> getPermission(PermissionType, const std::shared_ptr<BasicChannel>& channel, bool testGlobal = true);
PermissionValue getPermissionGrand(permission::PermissionTestType test, PermissionType type, const std::shared_ptr<BasicChannel>& 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<BasicChannel>& 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<BasicChannel>& 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<std::shared_ptr<Permission>> listPermissions(uint16_t = ~0);
std::vector<std::shared_ptr<Permission>> allPermissions();
std::deque<std::shared_ptr<Permission>> all_channel_specific_permissions();
std::deque<std::shared_ptr<Permission>> all_channel_unspecific_permissions();
void fireUpdate(PermissionType);
void registerUpdateHandler(const std::function<void(std::shared_ptr<Permission>)> &fn){ updateHandler.push_back(fn); }
void clearPermissions(){
permissions.clear();
}
private:
std::deque<std::shared_ptr<Permission>> permissions;
std::deque<std::function<void(std::shared_ptr<Permission>)>> updateHandler;
};
}
}
DEFINE_VARIABLE_TRANSFORM(ts::permission::PermissionType, VARTYPE_INT, std::to_string((int16_t) in), static_cast<ts::permission::PermissionType>(in.as<int16_t>()));
DEFINE_VARIABLE_TRANSFORM(ts::permission::PermissionGroup, VARTYPE_INT, std::to_string((uint16_t) in), static_cast<ts::permission::PermissionGroup>(in.as<uint16_t>()));
DEFINE_VARIABLE_TRANSFORM(ts::permission::PermissionSqlType, VARTYPE_INT, std::to_string((int8_t) in), static_cast<ts::permission::PermissionSqlType>(in.as<int8_t>()));

621
src/Properties.cpp Normal file
View File

@ -0,0 +1,621 @@
#include <algorithm>
#include <mutex>
#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<Properties>(this);
}
Properties::~Properties() {
memtrack::freed<Properties>(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<PropertyWrapper> Properties::list_properties(ts::property::flag_type flagMask, ts::property::flag_type negatedFlagMask) {
vector<PropertyWrapper> 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<PropertyWrapper> Properties::all_properties() {
vector<PropertyWrapper> 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<ts::PropertyBundle> &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>((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::PropertyDescription>();
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::PropertyDescription>(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<shared_ptr<PropertyDescription>, VirtualServerProperties::VIRTUALSERVER_ENDMARKER> virtualserver_info = {
make_shared<PropertyDescription>(VIRTUALSERVER_UNDEFINED, "VIRTUALSERVER_UNDEFINED", "", TYPE_UNKNOWN, 0), //Must be at index 0!
make_shared<PropertyDescription>(VIRTUALSERVER_ID, "virtualserver_id", "0", TYPE_UNSIGNED_NUMBER, FLAG_SERVER_VIEW), //available when connected
make_shared<PropertyDescription>(VIRTUALSERVER_UNIQUE_IDENTIFIER, "virtualserver_unique_identifier", "", TYPE_STRING, FLAG_SERVER_VV | FLAG_SNAPSHOT),
make_shared<PropertyDescription>(VIRTUALSERVER_NAME, "virtualserver_name", "Another TeaSpeak server software user", TYPE_STRING, FLAG_SERVER_VVSS | FLAG_USER_EDITABLE),
make_shared<PropertyDescription>(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<PropertyDescription>(VIRTUALSERVER_PLATFORM, "VIRTUALSERVER_PLATFORM", "undefined", TYPE_STRING, FLAG_SERVER_VIEW),
make_shared<PropertyDescription>(VIRTUALSERVER_VERSION, "VIRTUALSERVER_VERSION", "undefined", TYPE_STRING, FLAG_SERVER_VIEW),
make_shared<PropertyDescription>(VIRTUALSERVER_MAXCLIENTS, "VIRTUALSERVER_MAXCLIENTS", "120", TYPE_UNSIGNED_NUMBER, FLAG_SERVER_VVSS | FLAG_USER_EDITABLE),
make_shared<PropertyDescription>(VIRTUALSERVER_PASSWORD, "VIRTUALSERVER_PASSWORD", "", TYPE_STRING, FLAG_SS | FLAG_USER_EDITABLE),
make_shared<PropertyDescription>(VIRTUALSERVER_CLIENTS_ONLINE, "virtualserver_clientsonline", "0", TYPE_UNSIGNED_NUMBER, FLAG_SERVER_VARIABLE),
make_shared<PropertyDescription>(VIRTUALSERVER_CHANNELS_ONLINE, "virtualserver_channelsonline", "0", TYPE_UNSIGNED_NUMBER, FLAG_SERVER_VARIABLE), //only available on request (=> requestServerVariables),
make_shared<PropertyDescription>(VIRTUALSERVER_QUERYCLIENTS_ONLINE, "virtualserver_queryclientsonline", "0", TYPE_UNSIGNED_NUMBER, FLAG_SERVER_VARIABLE), //only available on request (=> requestServerVariables)
make_shared<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(VIRTUALSERVER_KEYPAIR, "VIRTUALSERVER_KEYPAIR", "", TYPE_STRING, FLAG_SS), //internal use
make_shared<PropertyDescription>(VIRTUALSERVER_HOSTMESSAGE, "VIRTUALSERVER_HOSTMESSAGE", "Welcome", TYPE_STRING, FLAG_SERVER_VVSS | FLAG_USER_EDITABLE), //available when connected, not updated while connected
make_shared<PropertyDescription>(VIRTUALSERVER_HOSTMESSAGE_MODE, "VIRTUALSERVER_HOSTMESSAGE_MODE", "1", TYPE_STRING, FLAG_SERVER_VVSS | FLAG_USER_EDITABLE), //available when connected, not updated while connected
make_shared<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(VIRTUALSERVER_FLAG_PASSWORD, "VIRTUALSERVER_FLAG_PASSWORD", "0", TYPE_BOOL, FLAG_SERVER_VARIABLE | FLAG_SS | FLAG_USER_EDITABLE), //only available on request (=> requestServerVariables)
make_shared<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(VIRTUALSERVER_HOSTBANNER_URL, "VIRTUALSERVER_HOSTBANNER_URL", "", TYPE_STRING, FLAG_SERVER_VVSS | FLAG_USER_EDITABLE), //available when connected, always up-to-date
make_shared<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(VIRTUALSERVER_CLIENT_CONNECTIONS, "VIRTUALSERVER_CLIENT_CONNECTIONS", "0", TYPE_UNSIGNED_NUMBER, FLAG_SERVER_VARIABLE | FLAG_SS), //only available on request (=> requestServerVariables)
make_shared<PropertyDescription>(VIRTUALSERVER_QUERY_CLIENT_CONNECTIONS, "VIRTUALSERVER_QUERY_CLIENT_CONNECTIONS", "0", TYPE_UNSIGNED_NUMBER, FLAG_SERVER_VARIABLE | FLAG_SS), //only available on request (=> requestServerVariables)
make_shared<PropertyDescription>(VIRTUALSERVER_HOSTBUTTON_TOOLTIP, "VIRTUALSERVER_HOSTBUTTON_TOOLTIP", "", TYPE_STRING, FLAG_SERVER_VVSS | FLAG_USER_EDITABLE), //available when connected, always up-to-date
make_shared<PropertyDescription>(VIRTUALSERVER_HOSTBUTTON_URL, "VIRTUALSERVER_HOSTBUTTON_URL", "", TYPE_STRING, FLAG_SERVER_VVSS | FLAG_USER_EDITABLE), //available when connected, always up-to-date
make_shared<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(VIRTUALSERVER_MONTH_BYTES_DOWNLOADED, "VIRTUALSERVER_MONTH_BYTES_DOWNLOADED", "0", TYPE_UNSIGNED_NUMBER, FLAG_SERVER_VARIABLE | FLAG_SAVE), //only available on request (=> requestServerVariables)
make_shared<PropertyDescription>(VIRTUALSERVER_MONTH_BYTES_UPLOADED, "VIRTUALSERVER_MONTH_BYTES_UPLOADED", "0", TYPE_UNSIGNED_NUMBER, FLAG_SERVER_VARIABLE | FLAG_SAVE), //only available on request (=> requestServerVariables)
make_shared<PropertyDescription>(VIRTUALSERVER_TOTAL_BYTES_DOWNLOADED, "VIRTUALSERVER_TOTAL_BYTES_DOWNLOADED", "0", TYPE_UNSIGNED_NUMBER, FLAG_SERVER_VARIABLE | FLAG_SAVE), //only available on request (=> requestServerVariables)
make_shared<PropertyDescription>(VIRTUALSERVER_TOTAL_BYTES_UPLOADED, "VIRTUALSERVER_TOTAL_BYTES_UPLOADED", "0", TYPE_UNSIGNED_NUMBER, FLAG_SERVER_VARIABLE | FLAG_SAVE), //only available on request (=> requestServerVariables)
make_shared<PropertyDescription>(VIRTUALSERVER_PORT, "VIRTUALSERVER_PORT", "9987", TYPE_UNSIGNED_NUMBER, FLAG_SERVER_VARIABLE | FLAG_SS | FLAG_USER_EDITABLE), //only available on request (=> requestServerVariables)
make_shared<PropertyDescription>(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<PropertyDescription>(VIRTUALSERVER_AUTOSTART, "VIRTUALSERVER_AUTOSTART", "1", TYPE_BOOL, FLAG_SERVER_VARIABLE | FLAG_SS | FLAG_USER_EDITABLE), //only available on request (=> requestServerVariables)
make_shared<PropertyDescription>(VIRTUALSERVER_MACHINE_ID, "VIRTUALSERVER_MACHINE_ID", "", TYPE_STRING, FLAG_SERVER_VARIABLE | FLAG_SS | FLAG_USER_EDITABLE), //only available on request (=> requestServerVariables)
make_shared<PropertyDescription>(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<PropertyDescription>(VIRTUALSERVER_LOG_CLIENT, "VIRTUALSERVER_LOG_CLIENT", "1", TYPE_BOOL, FLAG_SERVER_VARIABLE | FLAG_SS | FLAG_USER_EDITABLE), //only available on request (=> requestServerVariables)
make_shared<PropertyDescription>(VIRTUALSERVER_LOG_QUERY, "VIRTUALSERVER_LOG_QUERY", "1", TYPE_BOOL, FLAG_SERVER_VARIABLE | FLAG_SS | FLAG_USER_EDITABLE), //only available on request (=> requestServerVariables)
make_shared<PropertyDescription>(VIRTUALSERVER_LOG_CHANNEL, "VIRTUALSERVER_LOG_CHANNEL", "1", TYPE_BOOL, FLAG_SERVER_VARIABLE | FLAG_SS | FLAG_USER_EDITABLE), //only available on request (=> requestServerVariables)
make_shared<PropertyDescription>(VIRTUALSERVER_LOG_PERMISSIONS, "VIRTUALSERVER_LOG_PERMISSIONS", "1", TYPE_BOOL, FLAG_SERVER_VARIABLE | FLAG_SS | FLAG_USER_EDITABLE), //only available on request (=> requestServerVariables)
make_shared<PropertyDescription>(VIRTUALSERVER_LOG_SERVER, "VIRTUALSERVER_LOG_SERVER", "1", TYPE_BOOL, FLAG_SERVER_VARIABLE | FLAG_SS | FLAG_USER_EDITABLE), //only available on request (=> requestServerVariables)
make_shared<PropertyDescription>(VIRTUALSERVER_LOG_FILETRANSFER, "VIRTUALSERVER_LOG_FILETRANSFER", "1", TYPE_BOOL, FLAG_SERVER_VARIABLE | FLAG_SS | FLAG_USER_EDITABLE), //only available on request (=> requestServerVariables)
make_shared<PropertyDescription>(VIRTUALSERVER_NAME_PHONETIC, "VIRTUALSERVER_NAME_PHONETIC", "", TYPE_STRING, FLAG_SERVER_VVSS | FLAG_USER_EDITABLE), //available when connected, always up-to-date
make_shared<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(VIRTUALSERVER_TOTAL_PACKETLOSS_SPEECH, "VIRTUALSERVER_TOTAL_PACKETLOSS_SPEECH", "0", TYPE_UNSIGNED_NUMBER, FLAG_SERVER_VARIABLE), //only available on request (=> requestServerVariables)
make_shared<PropertyDescription>(VIRTUALSERVER_TOTAL_PACKETLOSS_KEEPALIVE, "VIRTUALSERVER_TOTAL_PACKETLOSS_KEEPALIVE", "0", TYPE_FLOAT, FLAG_SERVER_VARIABLE), //only available on request (=> requestServerVariables)
make_shared<PropertyDescription>(VIRTUALSERVER_TOTAL_PACKETLOSS_CONTROL, "VIRTUALSERVER_TOTAL_PACKETLOSS_CONTROL", "0", TYPE_FLOAT, FLAG_SERVER_VARIABLE), //only available on request (=> requestServerVariables)
make_shared<PropertyDescription>(VIRTUALSERVER_TOTAL_PACKETLOSS_TOTAL, "VIRTUALSERVER_TOTAL_PACKETLOSS_TOTAL", "0", TYPE_FLOAT, FLAG_SERVER_VARIABLE), //only available on request (=> requestServerVariables)
make_shared<PropertyDescription>(VIRTUALSERVER_TOTAL_PING, "VIRTUALSERVER_TOTAL_PING", "0", TYPE_FLOAT, FLAG_SERVER_VARIABLE), //only available on request (=> requestServerVariables)
make_shared<PropertyDescription>(VIRTUALSERVER_WEBLIST_ENABLED, "VIRTUALSERVER_WEBLIST_ENABLED", "1", TYPE_BOOL, FLAG_SERVER_VVSS | FLAG_USER_EDITABLE), //only available on request (=> requestServerVariables)
make_shared<PropertyDescription>(VIRTUALSERVER_AUTOGENERATED_PRIVILEGEKEY, "VIRTUALSERVER_AUTOGENERATED_PRIVILEGEKEY", "", TYPE_STRING, FLAG_SAVE), //internal use
make_shared<PropertyDescription>(VIRTUALSERVER_ASK_FOR_PRIVILEGEKEY, "VIRTUALSERVER_ASK_FOR_PRIVILEGEKEY", "1", TYPE_BOOL, FLAG_SERVER_VV | FLAG_SAVE | FLAG_USER_EDITABLE), //available when connected
make_shared<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(VIRTUALSERVER_SPOKEN_TIME, "VIRTUALSERVER_SPOKEN_TIME", "0", TYPE_UNSIGNED_NUMBER, FLAG_INTERNAL | FLAG_NEW | FLAG_SAVE), //only available on request (=> requestServerVariables)
make_shared<PropertyDescription>(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<shared_ptr<PropertyDescription>, ChannelProperties::CHANNEL_ENDMARKER> channel_info = {
make_shared<PropertyDescription>(CHANNEL_UNDEFINED, "CHANNEL_UNDEFINED", "", TYPE_UNKNOWN, 0), //Must be at index 0!
make_shared<PropertyDescription>(CHANNEL_ID, "cid", "0", TYPE_UNSIGNED_NUMBER, FLAG_CHANNEL_VIEW | FLAG_SS),
make_shared<PropertyDescription>(CHANNEL_PID, "cpid", "0", TYPE_UNSIGNED_NUMBER, FLAG_CHANNEL_VIEW | FLAG_SS),
make_shared<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(CHANNEL_DESCRIPTION, "CHANNEL_DESCRIPTION", "", TYPE_STRING, FLAG_CHANNEL_VARIABLE | FLAG_SS | FLAG_USER_EDITABLE), //Must be requested (=> requestChannelDescription)
make_shared<PropertyDescription>(CHANNEL_PASSWORD, "CHANNEL_PASSWORD", "0", TYPE_STRING, FLAG_SS | FLAG_USER_EDITABLE), //not available manager side
make_shared<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(CHANNEL_FILEPATH, "CHANNEL_FILEPATH", "", TYPE_STRING, FLAG_SS), //not available manager side, the folder used for file-transfers for this channel
make_shared<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(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<shared_ptr<PropertyDescription>, GroupProperties::GROUP_ENDMARKER> group_info = {
make_shared<PropertyDescription>(GROUP_UNDEFINED, "GROUP_UNDEFINED", "", TYPE_UNKNOWN, 0),
make_shared<PropertyDescription>(GROUP_ID, "gid", "0", TYPE_UNSIGNED_NUMBER, FLAG_INTERNAL),
make_shared<PropertyDescription>(GROUP_TYPE, "type", "0", TYPE_UNSIGNED_NUMBER, FLAG_GROUP_VIEW),
make_shared<PropertyDescription>(GROUP_NAME, "name", "Undefined group", TYPE_STRING, FLAG_GROUP_VIEW),
make_shared<PropertyDescription>(GROUP_SORTID, "sortid", "0", TYPE_UNSIGNED_NUMBER, FLAG_GROUP_VIEW),
make_shared<PropertyDescription>(GROUP_SAVEDB, "savedb", "0", TYPE_BOOL, FLAG_GROUP_VIEW),
make_shared<PropertyDescription>(GROUP_NAMEMODE, "namemode", "0", TYPE_UNSIGNED_NUMBER, FLAG_GROUP_VIEW),
make_shared<PropertyDescription>(GROUP_ICONID, "iconid", "0", TYPE_UNSIGNED_NUMBER, FLAG_GROUP_VIEW)
};
array<shared_ptr<PropertyDescription>, ClientProperties::CLIENT_ENDMARKER> client_info = {
make_shared<PropertyDescription>(CLIENT_UNDEFINED, "CLIENT_UNDEFINED", "undefined", TYPE_UNKNOWN, 0),
make_shared<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(CLIENT_DEFAULT_CHANNEL_PASSWORD, "CLIENT_DEFAULT_CHANNEL_PASSWORD", "", TYPE_STRING, FLAG_INTERNAL), //internal use
make_shared<PropertyDescription>(CLIENT_SERVER_PASSWORD, "CLIENT_SERVER_PASSWORD", "", TYPE_STRING, FLAG_INTERNAL), //internal use
make_shared<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(CLIENT_VERSION_SIGN, "CLIENT_VERSION_SIGN", "", TYPE_STRING, FLAG_INTERNAL), //sign
make_shared<PropertyDescription>(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<PropertyDescription>(CLIENT_KEY_OFFSET, "CLIENT_KEY_OFFSET", "0", TYPE_UNSIGNED_NUMBER, FLAG_INTERNAL), //internal use
make_shared<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(CLIENT_ID, "clid", "0", TYPE_UNSIGNED_NUMBER, FLAG_CLIENT_VV), //clid!
make_shared<PropertyDescription>(CLIENT_HARDWARE_ID, "hwid", "", TYPE_STRING, FLAG_SAVE), //hwid!
make_shared<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(CLIENT_UNREAD_MESSAGES, "CLIENT_UNREAD_MESSAGES", "0", TYPE_UNSIGNED_NUMBER, FLAG_CLIENT_VIEW), //automatically up-to-date for any manager "in view"
make_shared<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(CLIENT_ACTIVE_INTEGRATIONS_INFO, "CLIENT_ACTIVE_INTEGRATIONS_INFO", "", TYPE_STRING, FLAG_INTERNAL | FLAG_USER_EDITABLE),
make_shared<PropertyDescription>(CLIENT_TEAFORO_ID, "CLIENT_TEAFORO_ID", "0", TYPE_UNSIGNED_NUMBER, FLAG_NEW | FLAG_CLIENT_VIEW),
make_shared<PropertyDescription>(CLIENT_TEAFORO_NAME, "CLIENT_TEAFORO_NAME", "", TYPE_STRING, FLAG_NEW | FLAG_CLIENT_VIEW),
//Music bot stuff
make_shared<PropertyDescription>(CLIENT_OWNER, "CLIENT_OWNER", "0", TYPE_UNSIGNED_NUMBER, FLAG_NEW | FLAG_CLIENT_VIEW),
make_shared<PropertyDescription>(CLIENT_BOT_TYPE, "CLIENT_BOT_TYPE", "0", TYPE_UNSIGNED_NUMBER, FLAG_NEW | FLAG_SAVE_MUSIC | FLAG_USER_EDITABLE | FLAG_CLIENT_VIEW),
make_shared<PropertyDescription>(CLIENT_LAST_CHANNEL, "CLIENT_LAST_CHANNEL", "0", TYPE_UNSIGNED_NUMBER, FLAG_NEW | FLAG_INTERNAL | FLAG_SAVE_MUSIC),
make_shared<PropertyDescription>(CLIENT_PLAYER_STATE, "PLAYER_STATE", "0", TYPE_UNSIGNED_NUMBER, FLAG_NEW | FLAG_CLIENT_VIEW | FLAG_SAVE_MUSIC),
make_shared<PropertyDescription>(CLIENT_PLAYER_VOLUME, "PLAYER_VOLUME", "1", TYPE_FLOAT, FLAG_NEW | FLAG_SAVE_MUSIC | FLAG_CLIENT_VIEW | FLAG_USER_EDITABLE),
make_shared<PropertyDescription>(CLIENT_PLAYLIST_ID, "CLIENT_PLAYLIST_ID", "0", TYPE_UNSIGNED_NUMBER, FLAG_NEW | FLAG_CLIENT_VARIABLE | FLAG_SAVE_MUSIC),
make_shared<PropertyDescription>(CLIENT_DISABLED, "CLIENT_DISABLED", "0", TYPE_BOOL, FLAG_NEW | FLAG_CLIENT_VARIABLE),
make_shared<PropertyDescription>(CLIENT_UPTIME_MODE, "CLIENT_UPTIME_MODE", "0", TYPE_UNSIGNED_NUMBER, FLAG_NEW | FLAG_CLIENT_VARIABLE | FLAG_USER_EDITABLE | FLAG_SAVE_MUSIC),
make_shared<PropertyDescription>(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<shared_ptr<PropertyDescription>, ConnectionProperties::CONNECTION_ENDMARKER> connection_info = {
make_shared<PropertyDescription>(CONNECTION_UNDEFINED, "CONNECTION_UNDEFINED", "undefined", TYPE_UNKNOWN, 0),
make_shared<PropertyDescription>(CONNECTION_PING, "CONNECTION_PING", "0", TYPE_UNSIGNED_NUMBER, 0), //average latency for a round trip through and back this connection
make_shared<PropertyDescription>(CONNECTION_PING_DEVIATION, "CONNECTION_PING_DEVIATION", "0", TYPE_UNSIGNED_NUMBER, 0), //standard deviation of the above average latency
make_shared<PropertyDescription>(CONNECTION_CONNECTED_TIME, "CONNECTION_CONNECTED_TIME", "0", TYPE_UNSIGNED_NUMBER, 0), //how long the connection exists already
make_shared<PropertyDescription>(CONNECTION_IDLE_TIME, "CONNECTION_IDLE_TIME", "0", TYPE_UNSIGNED_NUMBER, 0), //how long since the last action of this manager
make_shared<PropertyDescription>(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<PropertyDescription>(CONNECTION_CLIENT_PORT, "CONNECTION_CLIENT_PORT", "0", TYPE_UNSIGNED_NUMBER, 0), //Port of this manager (as seen from the server side)
make_shared<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(V(CONNECTION_PACKETS_SENT_SPEECH, 0)), //how many Speech packets were sent through this connection
make_shared<PropertyDescription>(V(CONNECTION_PACKETS_SENT_KEEPALIVE, 0)),
make_shared<PropertyDescription>(V(CONNECTION_PACKETS_SENT_CONTROL,0)),
make_shared<PropertyDescription>(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<PropertyDescription>(V(CONNECTION_BYTES_SENT_SPEECH, 0)),
make_shared<PropertyDescription>(V(CONNECTION_BYTES_SENT_KEEPALIVE, 0)),
make_shared<PropertyDescription>(V(CONNECTION_BYTES_SENT_CONTROL, 0)),
make_shared<PropertyDescription>(V(CONNECTION_BYTES_SENT_TOTAL, FLAG_CLIENT_INFO)),
make_shared<PropertyDescription>(V(CONNECTION_PACKETS_RECEIVED_SPEECH, 0)),
make_shared<PropertyDescription>(V(CONNECTION_PACKETS_RECEIVED_KEEPALIVE, 0)),
make_shared<PropertyDescription>(V(CONNECTION_PACKETS_RECEIVED_CONTROL, 0)),
make_shared<PropertyDescription>(V(CONNECTION_PACKETS_RECEIVED_TOTAL, FLAG_CLIENT_INFO)),
make_shared<PropertyDescription>(V(CONNECTION_BYTES_RECEIVED_SPEECH, 0)),
make_shared<PropertyDescription>(V(CONNECTION_BYTES_RECEIVED_KEEPALIVE, 0)),
make_shared<PropertyDescription>(V(CONNECTION_BYTES_RECEIVED_CONTROL, 0)),
make_shared<PropertyDescription>(V(CONNECTION_BYTES_RECEIVED_TOTAL, FLAG_CLIENT_INFO)),
make_shared<PropertyDescription>(F(CONNECTION_PACKETLOSS_SPEECH, 0)),
make_shared<PropertyDescription>(F(CONNECTION_PACKETLOSS_KEEPALIVE, 0)),
make_shared<PropertyDescription>(F(CONNECTION_PACKETLOSS_CONTROL, 0)),
make_shared<PropertyDescription>(F(CONNECTION_PACKETLOSS_TOTAL, FLAG_CLIENT_INFO)), //the probability with which a packet round trip failed because a packet was lost
make_shared<PropertyDescription>(F(CONNECTION_SERVER2CLIENT_PACKETLOSS_SPEECH, 0)), //the probability with which a speech packet failed from the server to the manager
make_shared<PropertyDescription>(F(CONNECTION_SERVER2CLIENT_PACKETLOSS_KEEPALIVE, 0)),
make_shared<PropertyDescription>(F(CONNECTION_SERVER2CLIENT_PACKETLOSS_CONTROL, 0)),
make_shared<PropertyDescription>(F(CONNECTION_SERVER2CLIENT_PACKETLOSS_TOTAL, FLAG_CLIENT_INFO)),
make_shared<PropertyDescription>(F(CONNECTION_CLIENT2SERVER_PACKETLOSS_SPEECH, 0)),
make_shared<PropertyDescription>(F(CONNECTION_CLIENT2SERVER_PACKETLOSS_KEEPALIVE, 0)),
make_shared<PropertyDescription>(F(CONNECTION_CLIENT2SERVER_PACKETLOSS_CONTROL, 0)),
make_shared<PropertyDescription>(F(CONNECTION_CLIENT2SERVER_PACKETLOSS_TOTAL, FLAG_CLIENT_INFO)),
make_shared<PropertyDescription>(V(CONNECTION_BANDWIDTH_SENT_LAST_SECOND_SPEECH, 0)), //howmany bytes of speech packets we sent during the last second
make_shared<PropertyDescription>(V(CONNECTION_BANDWIDTH_SENT_LAST_SECOND_KEEPALIVE, 0)),
make_shared<PropertyDescription>(V(CONNECTION_BANDWIDTH_SENT_LAST_SECOND_CONTROL, 0)),
make_shared<PropertyDescription>(V(CONNECTION_BANDWIDTH_SENT_LAST_SECOND_TOTAL,FLAG_CLIENT_INFO)),
make_shared<PropertyDescription>(V(CONNECTION_BANDWIDTH_SENT_LAST_MINUTE_SPEECH,0)), //howmany bytes/s of speech packets we sent in average during the last minute
make_shared<PropertyDescription>(V(CONNECTION_BANDWIDTH_SENT_LAST_MINUTE_KEEPALIVE,0)),
make_shared<PropertyDescription>(V(CONNECTION_BANDWIDTH_SENT_LAST_MINUTE_CONTROL,0)),
make_shared<PropertyDescription>(V(CONNECTION_BANDWIDTH_SENT_LAST_MINUTE_TOTAL,FLAG_CLIENT_INFO)),
make_shared<PropertyDescription>(V(CONNECTION_BANDWIDTH_RECEIVED_LAST_SECOND_SPEECH, 0)),
make_shared<PropertyDescription>(V(CONNECTION_BANDWIDTH_RECEIVED_LAST_SECOND_KEEPALIVE,0)),
make_shared<PropertyDescription>(V(CONNECTION_BANDWIDTH_RECEIVED_LAST_SECOND_CONTROL,0)),
make_shared<PropertyDescription>(V(CONNECTION_BANDWIDTH_RECEIVED_LAST_SECOND_TOTAL,FLAG_CLIENT_INFO)),
make_shared<PropertyDescription>(V(CONNECTION_BANDWIDTH_RECEIVED_LAST_MINUTE_SPEECH,0)),
make_shared<PropertyDescription>(V(CONNECTION_BANDWIDTH_RECEIVED_LAST_MINUTE_KEEPALIVE,0)),
make_shared<PropertyDescription>(V(CONNECTION_BANDWIDTH_RECEIVED_LAST_MINUTE_CONTROL,0)),
make_shared<PropertyDescription>(V(CONNECTION_BANDWIDTH_RECEIVED_LAST_MINUTE_TOTAL,FLAG_CLIENT_INFO)),
//Rare properties
make_shared<PropertyDescription>(V(CONNECTION_FILETRANSFER_BANDWIDTH_SENT, FLAG_CLIENT_INFO)), //how many bytes per second are currently being sent by file transfers
make_shared<PropertyDescription>(V(CONNECTION_FILETRANSFER_BANDWIDTH_RECEIVED, FLAG_CLIENT_INFO)), //how many bytes per second are currently being received by file transfers
make_shared<PropertyDescription>(V(CONNECTION_FILETRANSFER_BYTES_RECEIVED_TOTAL, FLAG_CLIENT_INFO)), //how many bytes we received in total through file transfers
make_shared<PropertyDescription>(V(CONNECTION_FILETRANSFER_BYTES_SENT_TOTAL, FLAG_CLIENT_INFO)) //how many bytes we sent in total through file transfers
};
array<shared_ptr<PropertyDescription>, InstanceProperties::SERVERINSTANCE_ENDMARKER> instance_info = {
make_shared<PropertyDescription>(SERVERINSTANCE_UNDEFINED, "SERVERINSTANCE_UNDEFINED", "undefined", TYPE_UNKNOWN, 0),
make_shared<PropertyDescription>(SERVERINSTANCE_DATABASE_VERSION, "SERVERINSTANCE_DATABASE_VERSION", "0", TYPE_UNSIGNED_NUMBER, FLAG_INSTANCE_VARIABLE),
make_shared<PropertyDescription>(SERVERINSTANCE_PERMISSIONS_VERSION, "SERVERINSTANCE_PERMISSIONS_VERSION", "0", TYPE_UNSIGNED_NUMBER, FLAG_INSTANCE_VARIABLE),
make_shared<PropertyDescription>(SERVERINSTANCE_FILETRANSFER_HOST, "SERVERINSTANCE_FILETRANSFER_HOST", "0.0.0.0", TYPE_STRING, FLAG_INSTANCE_VARIABLE | FLAG_SAVE),
make_shared<PropertyDescription>(SERVERINSTANCE_FILETRANSFER_PORT, "SERVERINSTANCE_FILETRANSFER_PORT", "30303", TYPE_UNSIGNED_NUMBER, FLAG_INSTANCE_VARIABLE | FLAG_SAVE),
make_shared<PropertyDescription>(SERVERINSTANCE_QUERY_HOST, "SERVERINSTANCE_QUERY_HOST", "0.0.0.0", TYPE_STRING, FLAG_INSTANCE_VARIABLE | FLAG_SAVE),
make_shared<PropertyDescription>(SERVERINSTANCE_QUERY_PORT, "SERVERINSTANCE_QUERY_PORT", "10101", TYPE_UNSIGNED_NUMBER, FLAG_INSTANCE_VARIABLE | FLAG_SAVE),
make_shared<PropertyDescription>(SERVERINSTANCE_MONTHLY_TIMESTAMP, "SERVERINSTANCE_MONTHLY_TIMESTAMP", "0", TYPE_UNSIGNED_NUMBER, FLAG_INSTANCE_VARIABLE | FLAG_SAVE),
make_shared<PropertyDescription>(SERVERINSTANCE_MAX_DOWNLOAD_TOTAL_BANDWIDTH, "SERVERINSTANCE_MAX_DOWNLOAD_TOTAL_BANDWIDTH", "-1", TYPE_SIGNED_NUMBER, FLAG_INSTANCE_VARIABLE | FLAG_SAVE),
make_shared<PropertyDescription>(SERVERINSTANCE_MAX_UPLOAD_TOTAL_BANDWIDTH, "SERVERINSTANCE_MAX_UPLOAD_TOTAL_BANDWIDTH", "-1", TYPE_SIGNED_NUMBER, FLAG_INSTANCE_VARIABLE | FLAG_SAVE),
make_shared<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(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<PropertyDescription>(SERVERINSTANCE_TEMPLATE_SERVERADMIN_GROUP, "SERVERINSTANCE_TEMPLATE_SERVERADMIN_GROUP", "0", TYPE_UNSIGNED_NUMBER, FLAG_INSTANCE_VARIABLE | FLAG_SAVE),
make_shared<PropertyDescription>(SERVERINSTANCE_TEMPLATE_SERVERDEFAULT_GROUP, "SERVERINSTANCE_TEMPLATE_SERVERDEFAULT_GROUP", "0", TYPE_UNSIGNED_NUMBER, FLAG_INSTANCE_VARIABLE | FLAG_SAVE),
make_shared<PropertyDescription>(SERVERINSTANCE_TEMPLATE_CHANNELADMIN_GROUP, "SERVERINSTANCE_TEMPLATE_CHANNELADMIN_GROUP", "0", TYPE_UNSIGNED_NUMBER, FLAG_INSTANCE_VARIABLE | FLAG_SAVE),
make_shared<PropertyDescription>(SERVERINSTANCE_TEMPLATE_CHANNELDEFAULT_GROUP, "SERVERINSTANCE_TEMPLATE_CHANNELDEFAULT_GROUP", "0", TYPE_UNSIGNED_NUMBER, FLAG_INSTANCE_VARIABLE | FLAG_SAVE),
make_shared<PropertyDescription>(SERVERINSTANCE_TEMPLATE_MUSICDEFAULT_GROUP, "SERVERINSTANCE_TEMPLATE_MUSICDEFAULT_GROUP", "0", TYPE_UNSIGNED_NUMBER, FLAG_INSTANCE_VARIABLE | FLAG_SAVE),
make_shared<PropertyDescription>(SERVERINSTANCE_GUEST_SERVERQUERY_GROUP, "SERVERINSTANCE_GUEST_SERVERQUERY_GROUP", "0", TYPE_UNSIGNED_NUMBER, FLAG_INSTANCE_VARIABLE | FLAG_SAVE),
make_shared<PropertyDescription>(SERVERINSTANCE_ADMIN_SERVERQUERY_GROUP, "SERVERINSTANCE_ADMIN_SERVERQUERY_GROUP", "0", TYPE_UNSIGNED_NUMBER, FLAG_INSTANCE_VARIABLE | FLAG_SAVE),
make_shared<PropertyDescription>(SERVERINSTANCE_PENDING_CONNECTIONS_PER_IP, "SERVERINSTANCE_PENDING_CONNECTIONS_PER_IP", "0", TYPE_UNSIGNED_NUMBER, FLAG_INSTANCE_VARIABLE | FLAG_SAVE),
make_shared<PropertyDescription>(SERVERINSTANCE_SPOKEN_TIME_TOTAL, "SERVERINSTANCE_SPOKEN_TIME_TOTAL", "0", TYPE_UNSIGNED_NUMBER, FLAG_INTERNAL | FLAG_SAVE | FLAG_NEW),
make_shared<PropertyDescription>(SERVERINSTANCE_SPOKEN_TIME_DELETED, "SERVERINSTANCE_SPOKEN_TIME_DELETED", "0", TYPE_UNSIGNED_NUMBER, FLAG_INTERNAL | FLAG_SAVE | FLAG_NEW),
make_shared<PropertyDescription>(SERVERINSTANCE_SPOKEN_TIME_ALIVE, "SERVERINSTANCE_SPOKEN_TIME_ALIVE", "0", TYPE_UNSIGNED_NUMBER, FLAG_INTERNAL | FLAG_SAVE | FLAG_NEW),
make_shared<PropertyDescription>(SERVERINSTANCE_SPOKEN_TIME_VARIANZ, "SERVERINSTANCE_SPOKEN_TIME_VARIANZ", "0", TYPE_UNSIGNED_NUMBER, FLAG_INTERNAL | FLAG_SAVE | FLAG_NEW),
make_shared<PropertyDescription>(SERVERINSTANCE_UNIQUE_ID, "SERVERINSTANCE_UNIQUE_ID", "", TYPE_STRING, FLAG_INTERNAL | FLAG_SAVE | FLAG_NEW),
};
array<shared_ptr<PropertyDescription>, PlaylistProperties::PLAYLIST_ENDMARKER> playlist_info = {
make_shared<PropertyDescription>(PLAYLIST_UNDEFINED, "PLAYLIST_UNDEFINED", "undefined", TYPE_UNKNOWN, 0),
make_shared<PropertyDescription>(PLAYLIST_ID, "PLAYLIST_ID", "0", TYPE_UNSIGNED_NUMBER, FLAG_PLAYLIST_VARIABLE),
make_shared<PropertyDescription>(PLAYLIST_TYPE, "PLAYLIST_TYPE", "0", TYPE_UNSIGNED_NUMBER, FLAG_PLAYLIST_VARIABLE | FLAG_SAVE),
make_shared<PropertyDescription>(PLAYLIST_TITLE, "PLAYLIST_TITLE", "Yet another playlist", TYPE_STRING, FLAG_PLAYLIST_VARIABLE | FLAG_USER_EDITABLE | FLAG_SAVE),
make_shared<PropertyDescription>(PLAYLIST_DESCRIPTION, "PLAYLIST_DESCRIPTION", "", TYPE_STRING, FLAG_PLAYLIST_VARIABLE | FLAG_USER_EDITABLE | FLAG_SAVE),
make_shared<PropertyDescription>(PLAYLIST_OWNER_DBID, "PLAYLIST_OWNER_DBID", "0", TYPE_UNSIGNED_NUMBER, FLAG_PLAYLIST_VARIABLE | FLAG_SAVE),
make_shared<PropertyDescription>(PLAYLIST_OWNER_NAME, "PLAYLIST_OWNER_NAME", "0", TYPE_STRING, FLAG_PLAYLIST_VARIABLE | FLAG_SAVE),
make_shared<PropertyDescription>(PLAYLIST_FLAG_DELETE_PLAYED, "PLAYLIST_FLAG_DELETE_PLAYED", "1", TYPE_BOOL, FLAG_PLAYLIST_VARIABLE | FLAG_USER_EDITABLE | FLAG_SAVE),
make_shared<PropertyDescription>(PLAYLIST_REPLAY_MODE, "PLAYLIST_REPLAY_MODE", "0", TYPE_UNSIGNED_NUMBER, FLAG_PLAYLIST_VARIABLE | FLAG_USER_EDITABLE | FLAG_SAVE),
make_shared<PropertyDescription>(PLAYLIST_CURRENT_SONG_ID, "PLAYLIST_CURRENT_SONG_ID", "0", TYPE_UNSIGNED_NUMBER, FLAG_PLAYLIST_VARIABLE | FLAG_SAVE),
make_shared<PropertyDescription>(PLAYLIST_FLAG_FINISHED, "PLAYLIST_FLAG_FINISHED", "0", TYPE_BOOL, FLAG_PLAYLIST_VARIABLE | FLAG_USER_EDITABLE | FLAG_SAVE),
make_shared<PropertyDescription>(PLAYLIST_MAX_SONGS, "PLAYLIST_MAX_SONGS", "-1", TYPE_SIGNED_NUMBER, FLAG_PLAYLIST_VARIABLE | FLAG_USER_EDITABLE | FLAG_SAVE),
};
shared_ptr<PropertyDescription> PropertyDescription::unknown = make_shared<PropertyDescription>(UNKNOWN_UNDEFINED, "undefined", "", ValueType::TYPE_UNKNOWN, 0);
namespace impl {
#define M(type, _info) \
template <> \
const std::shared_ptr<property::PropertyDescription>& info<type>(type type) { \
for(const auto& element : (_info)) \
if(element->property_index == (type)) \
return element; \
return (_info)[0]; \
} \
\
template <> \
const std::shared_ptr<property::PropertyDescription>& info<type>(const std::string& _type) { \
for(const auto& element : (_info)) \
if(element->name == _type) \
return element; \
return (_info)[0]; \
} \
\
\
template <> \
std::deque<std::shared_ptr<property::PropertyDescription>> list<type>() { \
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<property::PropertyDescription>& 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<property::PropertyDescription>& 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<property::PropertyDescription>& info(PropertyType type, int index) {
if(type == PropertyType::PROP_TYPE_SERVER)
return impl::info<VirtualServerProperties>((VirtualServerProperties) index);
else if(type == PropertyType::PROP_TYPE_CHANNEL)
return impl::info<ChannelProperties>((ChannelProperties) index);
else if(type == PropertyType::PROP_TYPE_CLIENT)
return impl::info<ClientProperties>((ClientProperties) index);
else if(type == PropertyType::PROP_TYPE_GROUP)
return impl::info<GroupProperties>((GroupProperties) index);
else if(type == PropertyType::PROP_TYPE_CONNECTION)
return impl::info<ConnectionProperties>((ConnectionProperties) index);
else if(type == PropertyType::PROP_TYPE_INSTANCE)
return impl::info<InstanceProperties>((InstanceProperties) index);
else if(type == PropertyType::PROP_TYPE_PLAYLIST)
return impl::info<PlaylistProperties>((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<string> 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;
}
}
}
}

842
src/Properties.h Normal file
View File

@ -0,0 +1,842 @@
#include <utility>
#pragma once
#include "misc/memtracker.h"
#include <ThreadPool/Mutex.h>
#include "Variable.h"
#include <map>
#include <deque>
#include <vector>
#include <memory>
#include <utility>
#include <vector>
#include <cstdint>
#include <any>
#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<typename T>
extern std::deque<std::shared_ptr<property::PropertyDescription>> list();
extern std::deque<std::shared_ptr<property::PropertyDescription>> list_all();
template<typename T>
extern const std::shared_ptr<property::PropertyDescription> &info(T /* property */);
template<typename T>
extern const std::shared_ptr<property::PropertyDescription> &info(const std::string& /* key */);
extern const std::shared_ptr<property::PropertyDescription> &info_key(const std::string &);
extern const std::shared_ptr<property::PropertyDescription> &info_key(PropertyType, const std::string &);
extern const std::shared_ptr<property::PropertyDescription> &info(PropertyType, int);
template<typename V>
inline PropertyType type() {
std::cerr << "[CRITICAL] Invalid property type!" << std::endl;
return PropertyType::PROP_TYPE_UNKNOWN;
}
template<>
constexpr inline PropertyType type<VirtualServerProperties>() { return PropertyType::PROP_TYPE_SERVER; }
template<>
constexpr inline PropertyType type<ChannelProperties>() { return PropertyType::PROP_TYPE_CHANNEL; }
template<>
constexpr inline PropertyType type<ClientProperties>() { return PropertyType::PROP_TYPE_CLIENT; }
template<>
constexpr inline PropertyType type<ConnectionProperties>() { return PropertyType::PROP_TYPE_CONNECTION; }
template<>
constexpr inline PropertyType type<GroupProperties>() { return PropertyType::PROP_TYPE_GROUP; }
template<>
constexpr inline PropertyType type<InstanceProperties>() { return PropertyType::PROP_TYPE_INSTANCE; }
template<>
constexpr inline PropertyType type<PlaylistProperties>() { return PropertyType::PROP_TYPE_PLAYLIST; }
template<>
constexpr inline PropertyType type<UnknownProperties>() { 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<typename T>
inline const std::shared_ptr<property::PropertyDescription> &info(T property) { return impl::info<T>(property); }
template<typename T>
inline const std::shared_ptr<property::PropertyDescription> &info(const std::string& key) { return impl::info<T>(key); }
class PropertyDescription {
public:
static std::shared_ptr<PropertyDescription> 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<typename T>
PropertyDescription(T property, const std::string &name, const std::string &default_value, property::ValueType type, flag_type flags) noexcept : PropertyDescription((int) property, impl::type<T>(), 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<PropertyDescription>& other) const {
return this->operator==(*other);
}
template <typename T, typename std::enable_if<std::is_enum<T>::value, int>::type = 0>
inline bool operator==(const T& type) const {
return this->property_index == type && impl::type<T>() == 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<property::PropertyDescription> description;
bool flag_db_reference;
bool flag_modified;
};
struct PropertyBundle {
property::PropertyType type;
size_t length;
size_t offset;
PropertyData properties[0];
};
template <typename T>
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<T>(data_ptr->casted_value);
data_ptr->casted_value = ts::converter<T>::from_string(data_ptr->value);
return std::any_cast<T>(data_ptr->casted_value);
}
};
template <>
struct PropertyAccess<std::string> {
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 <typename T>
bool operator==(const T& other){
return this->as<T>() == other;
}
template <typename T>
bool operator!=(const T& other){
return !operator==(other);
}
//Math operators
PropertyWrapper&operator++(){ return operator=(as<int64_t>() + 1); }
PropertyWrapper&operator++(int){ return operator=(as<int64_t>() + 1); }
PropertyWrapper&operator+=(uint16_t val){ return operator=(as<uint16_t>() + val); }
PropertyWrapper&operator+=(int64_t val){ return operator=(as<int64_t>() + val); }
PropertyWrapper&operator+=(uint64_t val){ return operator=(as<uint64_t>() + 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 <typename T>
T as() const {
static_assert(ts::converter<T>::supported, "as<T> isn't supported for type");
return PropertyAccess<T>::get(this->data_ptr);
}
template <typename T>
operator T(){ return this->as<T>(); }
template <typename T>
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<T>(this->data_ptr->casted_value);
this->data_ptr->casted_value = ts::converter<T>::from_string(this->data_ptr->value);
return std::any_cast<T>(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 <typename T>
PropertyWrapper& operator=(const T& value) {
static_assert(ts::converter<T>::supported, "type isn't supported for type");
{
std::any any_value{value};
auto value_string = ts::converter<T>::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 <int N>
PropertyWrapper& operator=(char(value)[N]) {
this->value(value);
return *this;
}
void trigger_update();
PropertyWrapper(Properties* /* handle */, PropertyData* /* ptr */, const std::shared_ptr<PropertyBundle>& /* bundle */);
inline Properties* get_handle() { return this->handle; }
private:
Properties* handle = nullptr;
PropertyData* data_ptr = nullptr;
std::shared_ptr<PropertyBundle> bundle_lock;
};
typedef PropertyWrapper Property;
typedef std::function<void(PropertyWrapper&)> PropertyNotifyFn;
class Properties {
friend struct PropertyWrapper;
public:
Properties();
~Properties();
Properties(const Properties&) = delete;
Properties(Properties&&) = delete;
std::vector<PropertyWrapper> list_properties(property::flag_type flagMask = ~0, property::flag_type negatedFlagMask = 0);
std::vector<PropertyWrapper> all_properties();
/*
std::deque<std::shared_ptr<Property>> listProperties(property::flag_type flagMask = ~0, property::flag_type negatedFlagMask = 0);
std::deque<std::shared_ptr<Property>> allProperties();
*/
template <typename Type>
bool register_property_type() {
constexpr auto type = property::impl::type<Type>();
return this->register_property_type(type, property::impl::length(type), property::impl::offset(type));
}
/*
template <typename PT>
std::shared_ptr<Property> registerProperty(PT property){
auto info = property::impl::info<PT>(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<PT>(), property);
}
}
properties.push_back(std::make_shared<Property>(this, info));
}
return this->find(property::impl::type<PT>(), property);
}
*/
template <typename T>
bool hasProperty(T type) { return this->has(property::impl::type<T>(), type); }
template <typename T, typename std::enable_if<std::is_enum<T>::value, int>::type = 0>
PropertyWrapper operator[](T type) {
return this->find(property::impl::type<T>(), type);
}
PropertyWrapper operator[](const property::PropertyDescription& type) {
return this->find(type.type_property, type.property_index);
}
PropertyWrapper operator[](const std::shared_ptr<property::PropertyDescription>& 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 <typename T, typename std::enable_if<std::is_enum<T>::value, int>::type = 0>
bool has(T type) { return this->has(property::impl::type<T>(), type); }
private:
bool register_property_type(property::PropertyType /* type */, size_t /* length */, size_t /* offset */);
bool save = true;
std::vector<std::function<void(PropertyWrapper&)>> notifyFunctions{};
std::recursive_mutex propsLock{};
//std::deque<std::shared_ptr<Property>> properties{};
size_t properties_count = 0;
std::deque<std::shared_ptr<PropertyBundle>> 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);

17
src/Variable.cpp Normal file
View File

@ -0,0 +1,17 @@
//
// Created by wolverindev on 22.11.17.
//
#include "Variable.h"
variable_data::variable_data(const std::pair<std::string, std::string> &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;
}

143
src/Variable.h Normal file
View File

@ -0,0 +1,143 @@
#pragma once
#include <string>
#include <memory>
#include <iostream>
#include <utility>
enum VariableType {
VARTYPE_NULL,
VARTYPE_TEXT,
VARTYPE_INT,
VARTYPE_LONG,
VARTYPE_DOUBLE,
VARTYPE_FLOAT,
VARTYPE_BOOLEAN
};
class variable;
namespace typecast {
template<typename T>
struct FalseType : std::false_type {};
template <typename T>
inline T variable_cast(const variable& in) { static_assert( FalseType<T>::value , "this function has to be implemented for desired type"); return nullptr; }; //str to type
template <typename T>
inline std::string variable_cast(T& in) { static_assert( FalseType<T>::value , "this function has to be implemented for desired type"); return "undefined"; }; //type to str
template <typename T>
inline VariableType variable_type() { static_assert( FalseType<T>::value , "this function has to be implemented for desired type"); return VARTYPE_NULL; }; //type to str
}
struct variable_data {
variable_data(const std::pair<std::string, std::string> &pair, VariableType _type);
~variable_data() = default;
std::pair<std::string, std::string> pair;
VariableType _type;
};
class variable {
public:
variable() noexcept : data(std::make_shared<variable_data>(std::pair<std::string, std::string>{"", ""}, 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<variable_data>(std::pair<std::string, std::string>{key, value}, VariableType::VARTYPE_TEXT)) {}
variable(const variable& ref) : data(ref.data) {}
variable(variable&& ref) : data(ref.data) { }
virtual ~variable() = default;
template <typename T>
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 <typename T>
T as() const { return typecast::variable_cast<T>(*this); }
std::string string() const { return as<std::string>(); } //fast
template <typename T> //TODO more secure and not just try and fail
bool castable() {
try {
as<T>();
return true;
} catch (...) { return false; }
}
template <typename T>
variable& operator=(T obj){
r_value() = typecast::variable_cast<T>(obj);
data->_type = typecast::variable_type<T>();
return *this;
}
template <typename T>
operator T() const { return as<T>(); }
private:
std::string& r_key() const { return data->pair.first; }
std::string& r_value() const { return data->pair.second; }
std::shared_ptr<variable_data> data;
};
#define DEFINE_VARIABLE_TRANSFORM_TO_STR(type, to_str) \
namespace typecast { \
template <> \
inline std::string variable_cast<type>(type& in) { return to_str; } \
}
#define DEFINE_VARIABLE_TRANSFORM_TO_VAR(type, from_str) \
namespace typecast { \
template <> \
inline type variable_cast<type>(const variable& in) { return from_str; } \
}
#define DEFINE_VARIABLE_TRANSFORM_TYPE(type, vartype) \
namespace typecast { \
template <> \
inline VariableType variable_type<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<class>(in.as<size_type>()));
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()));

94
src/bbcode/bbcodes.cpp Normal file
View File

@ -0,0 +1,94 @@
#include <iostream>
#include <deque>
#include <string>
#include <algorithm>
#include "bbcodes.h"
using namespace std;
using namespace bbcode;
struct BBStack {
shared_ptr<BBEntry> head = make_shared<BBText>();
shared_ptr<BBEntry> tail = head;
string key;
string content;
};
shared_ptr<BBEntry> bbcode::parse(string message) {
string current_text;
string current_key;
deque<shared_ptr<BBStack>> stack;
stack.push_back(make_shared<BBStack>());
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<BBText>(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<BBStack>());
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<std::string> 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<std::string> 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;
}

57
src/bbcode/bbcodes.h Normal file
View File

@ -0,0 +1,57 @@
#pragma once
#include <deque>
#include <memory>
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<BBEntry> next() const { return this->_next; }
inline std::shared_ptr<BBEntry> previus() const { return this->_previus; }
inline std::shared_ptr<BBEntry> parent() const { return this->_parent.lock(); }
inline void next(std::shared_ptr<BBEntry> next) { this->_next = std::move(next); }
inline void previus(std::shared_ptr<BBEntry> previus) { this->_previus = std::move(previus); }
inline void parent(std::shared_ptr<BBEntry> parent) { this->_parent = std::move(parent); }
protected:
std::weak_ptr<BBEntry> _parent;
std::shared_ptr<BBEntry> _previus;
std::shared_ptr<BBEntry> _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<BBEntry> parse(std::string);
namespace sloppy {
extern bool has_tag(std::string message, std::deque<std::string> 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"}); }
}
}

325
src/channel/TreeView.cpp Normal file
View File

@ -0,0 +1,325 @@
#include <algorithm>
#include "TreeView.h"
using namespace ts;
using namespace std;
using LinkedTreeEntry = ts::TreeView::LinkedTreeEntry;
TreeView::TreeView() {}
TreeView::~TreeView() {
std::deque<std::shared_ptr<LinkedTreeEntry>> 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<LinkedTreeEntry> TreeView::linked(const std::shared_ptr<ts::TreeEntry>& entry) const {
if(!entry) return nullptr;
std::deque<std::shared_ptr<LinkedTreeEntry>> 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<std::shared_ptr<LinkedTreeEntry>> TreeView::query_deep(const std::shared_ptr<LinkedTreeEntry>& root, int deep) const {
std::deque<std::shared_ptr<LinkedTreeEntry>> result;
this->query_deep_(result, root ? root : this->head, deep);
return result;
}
void TreeView::query_deep_(deque<shared_ptr<LinkedTreeEntry>>& result, const shared_ptr<LinkedTreeEntry>& root, int deep) const {
if(deep == 0) return;
shared_ptr<LinkedTreeEntry> 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<std::shared_ptr<LinkedTreeEntry>> 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<TreeEntry> TreeView::find_entry(ts::ChannelId channelId) const {
auto result = this->find_linked_entry(channelId);
return result ? result->entry : nullptr;
}
std::shared_ptr<LinkedTreeEntry> TreeView::find_linked_entry(ChannelId channelId, const std::shared_ptr<LinkedTreeEntry>& head, int deep) const {
std::deque<std::shared_ptr<LinkedTreeEntry>> 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<std::shared_ptr<TreeEntry>> TreeView::entries(const std::shared_ptr<ts::TreeEntry>& root, int deep) const {
return this->query_deep_entry(this->linked(root), deep);
}
std::deque<std::shared_ptr<TreeEntry>> TreeView::entries_sub(const std::shared_ptr<ts::TreeEntry> &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<std::shared_ptr<TreeEntry>> TreeView::query_deep_entry(const shared_ptr<LinkedTreeEntry>& root, int deep) const {
auto result = this->query_deep(root, deep);
std::deque<std::shared_ptr<TreeEntry>> mapped;
for(const auto& e : result)
if(e->entry)
mapped.push_back(e->entry);
return mapped;
}
bool TreeView::has_entry(const std::shared_ptr<ts::TreeEntry> &entry, const std::shared_ptr<ts::TreeEntry> &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<TreeEntry> &entry, const shared_ptr<LinkedTreeEntry> &head, int deep) const {
shared_ptr<LinkedTreeEntry> 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<TreeEntry> &entry, const std::shared_ptr<TreeEntry> &t_parent, const shared_ptr<TreeEntry> &t_previous) {
auto linked = make_shared<LinkedTreeEntry>(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<ts::TreeEntry> &t_entry, const std::shared_ptr<ts::TreeEntry> &t_parent,
const std::shared_ptr<ts::TreeEntry> &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<LinkedTreeEntry>& 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<std::shared_ptr<TreeEntry>> TreeView::delete_entry(shared_ptr<TreeEntry> t_entry) {
auto entry = this->linked(t_entry);
if(!entry) return {};
this->cut_entry(entry);
std::deque<std::shared_ptr<TreeEntry>> result;
std::deque<std::shared_ptr<LinkedTreeEntry>> 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<void(const std::shared_ptr<ts::TreeEntry> &, int)> &print) const {
return this->print_tree_(print, this->head, 0);
}
void TreeView::print_tree_(const std::function<void(const std::shared_ptr<ts::TreeEntry> &, int)> &print, shared_ptr<LinkedTreeEntry> head, int deep) const {
while(head) {
print(head->entry, deep);
this->print_tree_(print, head->child_head, deep + 1);
head = head->next;
}
}

76
src/channel/TreeView.h Normal file
View File

@ -0,0 +1,76 @@
#include <utility>
#pragma once
#include "misc/advanced_mutex.h"
#include <Definitions.h>
#include <deque>
#include <ThreadPool/Mutex.h>
#include "misc/memtracker.h"
#ifndef __attribute_deprecated__
#define __attribute_deprecated__ [[deprecated]]
#endif
namespace ts {
class TreeEntry;
class TreeView {
public:
struct LinkedTreeEntry {
std::shared_ptr<LinkedTreeEntry> previous;
std::shared_ptr<LinkedTreeEntry> next;
std::shared_ptr<LinkedTreeEntry> child_head;
std::weak_ptr<LinkedTreeEntry> parent;
const std::shared_ptr<TreeEntry> entry;
explicit LinkedTreeEntry(std::shared_ptr<TreeEntry> entry) : entry(std::move(entry)) {
memtrack::allocated<LinkedTreeEntry>(this);
}
virtual ~LinkedTreeEntry() {
memtrack::freed<LinkedTreeEntry>(this);
}
};
public:
TreeView();
virtual ~TreeView();
size_t entry_count() const;
std::deque<std::shared_ptr<TreeEntry>> entries(const std::shared_ptr<TreeEntry>& /* head */ = nullptr, int /* deep */ = -1) const;
std::deque<std::shared_ptr<TreeEntry>> entries_sub(const std::shared_ptr<TreeEntry>& /* parent */ = nullptr, int /* deep */ = -1) const;
std::shared_ptr<TreeEntry> find_entry(ChannelId /* channel id */) const;
std::shared_ptr<LinkedTreeEntry> find_linked_entry(ChannelId /* channel id */, const std::shared_ptr<LinkedTreeEntry>& /* head */ = nullptr, int deep = -1) const;
bool has_entry(const std::shared_ptr<TreeEntry>& /* entry */, const std::shared_ptr<TreeEntry>& /* head */ = nullptr, int deep = -1) const;
bool insert_entry(const std::shared_ptr<TreeEntry>& entry, const std::shared_ptr<TreeEntry>& /* parent */ = nullptr, const std::shared_ptr<TreeEntry>& /* previous */ = nullptr);
bool move_entry(const std::shared_ptr<TreeEntry>& /* entry */, const std::shared_ptr<TreeEntry>& /* parent */ = nullptr, const std::shared_ptr<TreeEntry>& /* previous */ = nullptr);
std::deque<std::shared_ptr<TreeEntry>> delete_entry(std::shared_ptr<TreeEntry> /* entry */); //Copy that here because of reference could be changed (linked->entry)
void print_tree(const std::function<void(const std::shared_ptr<TreeEntry>& /* entry */, int /* deep */)>&) const;
protected:
std::shared_ptr<LinkedTreeEntry> head;
private:
inline std::shared_ptr<LinkedTreeEntry> linked(const std::shared_ptr<TreeEntry>& /* entry */) const;
inline std::deque<std::shared_ptr<LinkedTreeEntry>> query_deep(const std::shared_ptr<LinkedTreeEntry>& /* layer */ = nullptr,int /* max deep */ = -1) const;
inline void query_deep_(std::deque<std::shared_ptr<LinkedTreeEntry>>& /* result */, const std::shared_ptr<LinkedTreeEntry>& /* layer */ = nullptr,int /* max deep */ = -1) const;
inline std::deque<std::shared_ptr<TreeEntry>> query_deep_entry(const std::shared_ptr<LinkedTreeEntry>& /* layer */ = nullptr,int /* max deep */ = -1) const;
inline bool has_entry_(const std::shared_ptr<TreeEntry>& /* entry */, const std::shared_ptr<LinkedTreeEntry>& /* head */ = nullptr, int deep = -1) const;
void print_tree_(const std::function<void(const std::shared_ptr<TreeEntry>& /* entry */, int /* deep */)>&, std::shared_ptr<LinkedTreeEntry> /* head */, int /* deep */) const;
inline void cut_entry(const std::shared_ptr<LinkedTreeEntry>&);
};
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<TreeView::LinkedTreeEntry>& /* self */) {}
};
}

View File

@ -0,0 +1,84 @@
#pragma once
#include <any>
namespace ts {
typedef long double long_double;
/* Converter stuff */
template <typename T>
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<type> { \
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 <int length>
struct converter<char[length]> {
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<const char*>(value), length - 1); };
};
#undef CONVERTER_PRIMITIVE
}
#define DEFINE_CONVERTER_ENUM(class, size_type) \
namespace ts { \
template <> \
struct converter<class> { \
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<class>(val)); \
}; \
static constexpr class(*from_string)(const std::string&) = [](const std::string& val) { \
return ((class(*)(const std::string&)) ts::converter<size_type>::from_string)(val); \
}; \
}; \
}

133
src/linked_helper.cpp Normal file
View File

@ -0,0 +1,133 @@
#include <unordered_map>
#include <algorithm>
#include <string>
#include "linked_helper.h"
using namespace std;
using namespace linked;
std::shared_ptr<entry> linked::build_chain(const std::deque<std::shared_ptr<linked::entry>> &entries, std::deque<std::string> &log) {
//TODO filter only for one layer
auto find_entry = [&](uint64_t id) -> shared_ptr<entry> {
for(const auto& entry : entries)
if(entry->entry_id == id)
return entry;
return nullptr;
};
deque<shared_ptr<entry>> 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<void*, uint8_t> used_nodes;
deque<shared_ptr<entry>> 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;
}
}

30
src/linked_helper.h Normal file
View File

@ -0,0 +1,30 @@
#pragma once
#include <memory>
#include <deque>
namespace linked {
struct entry {
std::shared_ptr<entry> previous;
std::shared_ptr<entry> next;
std::shared_ptr<entry> 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<entry> create_entry(uint64_t parent_id, uint64_t entry_id, uint64_t previous_id) {
auto result = std::make_shared<entry>();
result->parent_id = parent_id;
result->entry_id = entry_id;
result->previous_id = previous_id;
return result;
}
extern std::shared_ptr<entry> build_chain(const std::deque<std::shared_ptr<entry>>& /* entried */, std::deque<std::string>& /* error log */);
}

118
src/log/LogSinks.cpp Normal file
View File

@ -0,0 +1,118 @@
#include "LogUtils.h"
#include "LogSinks.h"
#include <iomanip>
#include <fstream>
#include <spdlog/spdlog.h>
#include <experimental/filesystem>
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<const ::logger::force_log_msg*>(&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<log_clock> 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);
}
}

50
src/log/LogSinks.h Normal file
View File

@ -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 <spdlog/logger.h>
#include <spdlog/sinks/file_sinks.h>
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 <spdlog::log_clock> point);
};
}

206
src/log/LogUtils.cpp Normal file
View File

@ -0,0 +1,206 @@
#include "LogUtils.h"
#include "LogSinks.h"
#include <iomanip>
#include <fstream>
#include <map>
#include <spdlog/spdlog.h>
#include <experimental/filesystem>
#include <StringVariable.h>
#include <mutex>
using namespace std;
using namespace std::chrono;
using namespace spdlog;
namespace fs = std::experimental::filesystem;
#define ASYNC_LOG
namespace logger {
recursive_mutex loggerLock;
map<int, std::shared_ptr<spdlog::logger>> loggers;
shared_ptr<LoggerConfig> logConfig;
shared_ptr<::logger::TerminalSink> terminalSink;
shared_ptr<::logger::CostumeFormatter> costumeFormatter;
shared_ptr<spdlog::logger> 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<spdlog::sink_ptr> 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<std::string> value) -> std::string {
auto pattern = !value.empty() ? value[0] : "%Y-%m-%d_%H:%M:%S";
tm* tm_info;
auto secs = duration_cast<seconds>(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<ColoredFileSink>(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<TerminalSink>(sink)) {
sink->set_level(::logger::currentConfig()->terminalLevel);
} else if(dynamic_pointer_cast<ColoredFileSink>(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<LoggerConfig>& currentConfig() {
return logConfig;
}
extern void setup(const shared_ptr<LoggerConfig>& config) {
logConfig = config;
config->timestamp = system_clock::now();
terminalSink = make_shared<TerminalSink>();
terminalSink->set_level(::logger::currentConfig()->terminalLevel);
costumeFormatter = make_shared<CostumeFormatter>();
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<TerminalSink>(sink))
sink->set_level(::logger::currentConfig()->terminalLevel);
else if(dynamic_pointer_cast<ColoredFileSink>(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());
}

136
src/log/LogUtils.h Normal file
View File

@ -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 <spdlog/logger.h>
#include <sstream>
#include <string>
#include <chrono>
#include "../Definitions.h"
#include "LogSinks.h"
#ifdef HAVE_CXX_TERMINAL
#include <CXXTerminal/Terminal.h>
#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<spdlog::logger> logger(int);
extern void setup(const std::shared_ptr<LoggerConfig>&);
extern const std::shared_ptr<LoggerConfig>& 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 <typename... Args> \
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 <typename... Args> \
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));

5
src/log/translation.cpp Normal file
View File

@ -0,0 +1,5 @@
//
// Created by wolverindev on 12.06.18.
//
#include "translation.h"

13
src/log/translation.h Normal file
View File

@ -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,
};
}

119
src/misc/TraceUtils.cpp Normal file
View File

@ -0,0 +1,119 @@
#include <execinfo.h> // for backtrace
#include <string>
#include <sstream>
#include <functional>
#include <src/log/LogUtils.h>
#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<void(StackTraceElement*)> 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<const StackTraceElement **>(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) {}
}

56
src/misc/TraceUtils.h Normal file
View File

@ -0,0 +1,56 @@
#pragma once
#include <string>
#include <stdio.h>
#include <execinfo.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>
#include <iostream>
#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<void(StackTraceElement*)> format);
};
extern StackTrace backTrace(int size);
};

299
src/misc/advanced_mutex.h Normal file
View File

@ -0,0 +1,299 @@
#pragma once
#include <cassert>
#include <thread>
#include <mutex>
#include <shared_mutex>
#include <atomic>
#include <map>
namespace std {
template<class T, class Lock>
struct lock_guarded {
Lock l;
T *t;
T *operator->() &&{ return t; }
template<class Arg>
auto operator[](Arg &&arg) && -> decltype(std::declval<T &>()[std::declval<Arg>()]) {
return (*t)[std::forward<Arg>(arg)];
}
T &operator*() &&{ return *t; }
};
template<class T, class Lock>
struct lock_guarded_shared {
Lock l;
std::shared_ptr<T> t;
T *operator->() &&{ return t.operator->(); }
template<class Arg>
auto operator[](Arg &&arg) && -> decltype(std::declval<T &>()[std::declval<Arg>()]) {
return (*t)[std::forward<Arg>(arg)];
}
T &operator*() &&{ return *t; }
operator bool() {
return !!t;
}
bool operator !() {
return !t;
}
};
constexpr struct emplace_t { } emplace{};
template<class T, class M>
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<M>(lock)), t(std::forward<T>(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<class F>
std::result_of_t<F(T &)> operator->*(F &&f) {
return std::forward<F>(f)(t);
}
template<class F>
std::result_of_t<F(T const &)> operator->*(F &&f) const {
return std::forward<F>(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<class T>
struct mutex_guarded {
lock_guarded<T, std::unique_lock<std::mutex>> get_locked() {
return {std::unique_lock{m}, &t};
}
lock_guarded<T const, std::unique_lock<std::mutex>> get_locked() const {
return {{m}, &t};
}
lock_guarded<T, std::unique_lock<std::mutex>> operator->() {
return get_locked();
}
lock_guarded<T const, std::unique_lock<std::mutex>> operator->() const {
return get_locked();
}
template<class F>
std::result_of_t<F(T &)> operator->*(F &&f) {
return std::forward<F>(f)(*get_locked());
}
template<class F>
std::result_of_t<F(T const &)> operator->*(F &&f) const {
return std::forward<F>(f)(*get_locked());
}
template<class...Args>
mutex_guarded(emplace_t, Args &&...args) : t(std::forward<Args>(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<std::thread::id> owner;
std::atomic<std::size_t> count;
mutex_guarded<std::map<std::thread::id, std::size_t>> shared_counts;
};
template <typename T>
inline bool mutex_locked(T& mutex) {
return true;
try {
unique_lock<T> 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 <typename T>
inline bool mutex_shared_locked(T& mutex) {
return true;
try {
shared_lock<T> 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;
}
}
}

71
src/misc/base64.h Normal file
View File

@ -0,0 +1,71 @@
#pragma once
#include <string>
#include <tomcrypt.h>
#include <iostream>
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<unsigned long>(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()); }
//AZ, az, 09, + 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()); }

24
src/misc/cast.h Normal file
View File

@ -0,0 +1,24 @@
#pragma once
#include <memory>
template<class T, class U>
inline std::shared_ptr <T> static_pointer_cast(const std::shared_ptr <U> &r) noexcept {
auto p = static_cast<typename std::shared_ptr<T>::element_type *>(r.get());
return std::shared_ptr<T>(r, p);
}
template<class T, class U>
inline std::shared_ptr <T> dynamic_pointer_cast(const std::shared_ptr <U> &r) noexcept {
if (auto p = dynamic_cast<typename std::shared_ptr<T>::element_type *>(r.get())) {
return std::shared_ptr<T>(r, p);
} else {
return std::shared_ptr<T>();
}
}
template<class T, class U>
inline std::shared_ptr <T> const_pointer_cast(const std::shared_ptr <U> &r) noexcept {
auto p = const_cast<typename std::shared_ptr<T>::element_type *>(r.get());
return std::shared_ptr<T>(r, p);
}

58
src/misc/digest.h Normal file
View File

@ -0,0 +1,58 @@
#pragma once
#include <string>
#include <string_view>
#include <cstring>
#ifdef NO_OPEN_SSL
#include <tomcrypt.h>
#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 <openssl/sha.h>
#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

110
src/misc/endianness.h Normal file
View File

@ -0,0 +1,110 @@
#pragma once
#include <stdint.h>
#include <iostream>
using namespace std;
#define _LE2BE(size, convert) \
template <typename T = int, typename BufferType, typename std::enable_if< \
std::is_same<typename std::remove_const<BufferType>::type, uint8_t>::value || \
std::is_same<typename std::remove_const<BufferType>::type, int8_t>::value || \
std::is_same<typename std::remove_const<BufferType>::type, char>::value || \
std::is_same<typename std::remove_const<BufferType>::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 <typename T = int, typename BufferType, typename std::enable_if< \
std::is_same<typename std::remove_const<BufferType>::type, uint8_t>::value || \
std::is_same<typename std::remove_const<BufferType>::type, int8_t>::value || \
std::is_same<typename std::remove_const<BufferType>::type, char>::value || \
std::is_same<typename std::remove_const<BufferType>::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 <typename T = uint32_t>
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 <typename T = uint32_t>
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;
}

20
src/misc/hex.h Normal file
View File

@ -0,0 +1,20 @@
#pragma once
#include <string>
#include <cassert>
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<char>(beg + ((elm >> 4) & 0x0F));
output[idx++] = static_cast<char>(beg + ((elm & 0x0F) >> 0));
}
return std::string(output, len);
}
}

59
src/misc/lambda.h Normal file
View File

@ -0,0 +1,59 @@
#pragma once
#include <tuple>
#include <type_traits>
#include <regex>
//https://qiita.com/angeart/items/94734d68999eca575881
namespace stx {
namespace lambda_detail {
template <typename...>
struct member_type;
template <typename ret, typename klass, typename... args>
struct member_type<std::true_type, ret, klass, args...> {
using member = std::true_type;
using invoker_function = std::function<ret(klass*, args...)>;
};
template <typename ret, typename klass, typename... args>
struct member_type<std::false_type, ret, klass, args...> {
using member = std::false_type;
using invoker_function = std::function<ret(args...)>;
};
template<typename t_member, class t_return_type, class t_klass, class flag_mutable, class... t_args>
struct types : member_type<t_member, t_return_type, t_klass, t_args...> {
public:
static constexpr bool has_klass = std::is_class<t_klass>::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<t_args...>;
template<size_t i>
struct arg {
typedef typename std::tuple_element<i, std::tuple<t_args...>>::type type;
};
};
template<class lambda>
struct lambda_type_impl;
template<class ret, class klass, class... args>
struct lambda_type_impl<ret(klass::*)(args...) const> : lambda_detail::types<std::false_type, ret, klass, std::true_type, args...> {};
}
template<class lambda>
struct lambda_type : lambda_detail::lambda_type_impl<decltype(&lambda::operator())> { };
template<class ret, class klass, class... args>
struct lambda_type<ret(klass::*)(args...)> : lambda_detail::types<typename std::is_member_function_pointer<ret(klass::*)(args...)>::type,ret,klass,std::true_type,args...> { };
template<class ret, class klass, class... args>
struct lambda_type<ret(klass::*)(args...) const> : lambda_detail::types<typename std::is_member_function_pointer<ret(klass::*)(args...) const>::type, ret, klass, std::false_type, args...> { };
};

227
src/misc/memtracker.cpp Normal file
View File

@ -0,0 +1,227 @@
#include "log/LogUtils.h"
#include <mutex>
#include <array>
#include <deque>
#include <map>
#include <typeindex>
#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 <cxxabi.h>
#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<char[], void (*)(void*)> 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 <int N>
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<entry, N> entries;
private:
inline int free_slot() {
while (findex < N && entries[findex].address) findex++;
return findex;
}
int findex = 0;
};
typedef brick<1024> InfoBrick;
template <typename T, T N>
struct DefaultValued {
T value = N;
};
map<TypeInfo, DefaultValued<ssize_t, -1>> type_indexes;
vector<InfoBrick*> 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<mutex> 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<mutex> 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<size_t, deque<void*>> objects;
map<size_t, std::string> mapping;
{
lock_guard<mutex> 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("<snipped>");
}
}
#endif
}
}

30
src/misc/memtracker.h Normal file
View File

@ -0,0 +1,30 @@
#pragma once
#include <string>
#include <typeinfo>
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 <typename T>
void allocated(void* address) { allocated(typeid(T).name(), address); }
template <typename T>
void freed(void* address) { freed(typeid(T).name(), address); }
void statistics();
#else
template <typename... T>
inline void __empty(...) { }
#define freed __empty
#define allocated __empty
#define allocated_mangled __empty
#define freed_mangled __empty
inline void statistics() {}
#endif
}

200
src/misc/net.h Normal file
View File

@ -0,0 +1,200 @@
#pragma once
#include <string>
#include <cstring>
#ifdef WIN32
#include <WS2tcpip.h>
#include <WinSock2.h>
#include <Windows.h>
#include <in6addr.h>
#else
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <netdb.h>
#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;
}
}

527
src/misc/queue.h Normal file
View File

@ -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_ */

18
src/misc/rnd.cpp Normal file
View File

@ -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<unsigned long>(std::chrono::system_clock::now().time_since_epoch().count()));
std::uniform_int_distribution<int> gen(0, static_cast<int>(source_length - 1));
for(int i = 0; i < length; i++){
buffer[i] = source[gen(generator)];
}
auto result = std::string(buffer, static_cast<unsigned long>(length));
delete[] buffer;
return result;
}

8
src/misc/rnd.h Normal file
View File

@ -0,0 +1,8 @@
#pragma once
#include <random>
#include <cstring>
#include <chrono>
extern const char* rnd_string_chars; //"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
extern std::string rnd_string(int length = 20, const char* avariable = rnd_string_chars);

15
src/misc/sassert.h Normal file
View File

@ -0,0 +1,15 @@
#pragma once
#include <cassert>
//#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

25
src/misc/spin_lock.h Normal file
View File

@ -0,0 +1,25 @@
#pragma once
#include <atomic>
#include <thread>
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);
}
};

38
src/misc/std_unique_ptr.h Normal file
View File

@ -0,0 +1,38 @@
#pragma once
#include <cstddef>
#include <memory>
#include <type_traits>
#include <utility>
#if __cplusplus <= 201103L
namespace std {
template<class T> struct _Unique_if {
typedef unique_ptr<T> _Single_object;
};
template<class T> struct _Unique_if<T[]> {
typedef unique_ptr<T[]> _Unknown_bound;
};
template<class T, size_t N> struct _Unique_if<T[N]> {
typedef void _Known_bound;
};
template<class T, class... Args>
typename _Unique_if<T>::_Single_object
make_unique(Args&&... args) {
return unique_ptr<T>(new T(std::forward<Args>(args)...));
}
template<class T>
typename _Unique_if<T>::_Unknown_bound
make_unique(size_t n) {
typedef typename remove_extent<T>::type U;
return unique_ptr<T>(new U[n]());
}
template<class T, class... Args>
typename _Unique_if<T>::_Known_bound
make_unique(Args&&...) = delete;
}
#endif

59
src/misc/time.cpp Normal file
View File

@ -0,0 +1,59 @@
#include <vector>
#include <functional>
#include "time.h"
using namespace std;
using namespace chrono;
struct TimeEntry {
std::string indice;
std::function<nanoseconds(const std::string&)> parser;
};
auto parsers = std::vector<TimeEntry>({
{"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;
}

9
src/misc/time.h Normal file
View File

@ -0,0 +1,9 @@
#pragma once
#include <string>
#include <chrono>
#include <deque>
namespace period {
std::chrono::nanoseconds parse(const std::string&, std::string&);
}

36
src/misc/timer.h Normal file
View File

@ -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<entry> 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<std::chrono::milliseconds>(_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<std::chrono::milliseconds>(entry.ts - _name ##_timings.begin).count()) + "ms"; \
result += ": " + std::to_string(std::chrono::duration_cast<std::chrono::milliseconds>(entry.ts - tp).count()) + "ms"; \
tp = entry.ts; \
} \
return result; \
})()

View File

@ -0,0 +1,157 @@
#include "AcknowledgeManager.h"
#include <math.h>
#include <misc/endianness.h>
using namespace ts;
using namespace ts::connection;
using namespace ts::protocol;
using namespace std;
using namespace std::chrono;
AcknowledgeManager::AcknowledgeManager() {}
AcknowledgeManager::~AcknowledgeManager() {
{
lock_guard<recursive_mutex> 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<recursive_mutex> 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<recursive_mutex> 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>();
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<recursive_mutex> 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> entry;
{
lock_guard<recursive_mutex> 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<milliseconds>(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<pipes::buffer>& buffers, string& error) {
ssize_t resend_count = 0;
deque<shared_ptr<Entry>> need_resend;
{
deque<shared_ptr<Entry>> erase;
lock_guard<recursive_mutex> 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;
}

View File

@ -0,0 +1,51 @@
#pragma once
#include <memory>
#include <protocol/Packet.h>
#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<threads::Future<bool>> 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<pipes::buffer>& /* buffers to resend */,
std::string& /* error */
);
private:
std::recursive_mutex entry_lock;
std::deque<std::shared_ptr<Entry>> entries;
std::chrono::milliseconds resend_delay{500};
double average_response = 20;
};
}
}

View File

@ -0,0 +1,81 @@
#include <csignal>
#include <misc/memtracker.h>
#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() { }

View File

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

View File

@ -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 <ed25519/ed25519.h>
#include <ed25519/ge.h>
#include <log/LogUtils.h>
#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<CryptionHandler>(this);
}
CryptionHandler::~CryptionHandler() {
memtrack::freed<CryptionHandler>(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<const uint8_t *>(publicKey), reinterpret_cast<const uint8_t *>(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<protocol::ClientPacket *>(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;
}

View File

@ -0,0 +1,74 @@
#pragma once
#include <array>
#include <string>
#include <tomcrypt.h>
#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<KeyCache, protocol::PACKET_MAX> cache_key_client;
std::array<KeyCache, protocol::PACKET_MAX> cache_key_server;
static_assert(sizeof(current_mac) == sizeof(default_mac), "invalid mac");
static_assert(sizeof(iv_struct) == 64, "invalid iv struct");
};
}
}

233
src/protocol/Packet.cpp Normal file
View File

@ -0,0 +1,233 @@
//
// Created by wolverindev on 07.10.17.
//
#include <cstring>
#include <iostream>
#include <bitset>
#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<int, PacketTypeInfo> 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<pipes::buffer> &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<uint8_t>()[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;
}
}
}

290
src/protocol/Packet.h Normal file
View File

@ -0,0 +1,290 @@
#pragma once
#include <cstring>
#include <string>
#include <map>
#include <utility>
#include <ThreadPool/Future.h>
#include <pipes/buffer.h>
#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<int, PacketTypeInfo> 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<uint16_t>(data->packetCounter[type.type()]++ & 0xFFFF);
}
uint16_t currentPacketId(const PacketTypeInfo &type){
return static_cast<uint16_t>(data->packetCounter[type.type()] & 0xFFFF);
}
uint16_t generationId(const PacketTypeInfo &type){
return static_cast<uint16_t>((data->packetCounter[type.type()] >> 16) & 0xFFFF);
}
void reset() {
memset(&data->packetCounter[0], 0, sizeof(uint32_t) * 16);
}
private:
std::shared_ptr<PacketIdManagerData> 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<threads::Future<bool>> 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<threads::Future<bool>>& 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<pipes::buffer> &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<uint8_t, decltype(::free)*> buffer;
/*
std::string _header;
std::string _data;
std::string _mac;
*/
uint16_t genId = 0;
std::unique_ptr<threads::Future<bool>> 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;
};
}
}

18
src/protocol/buffers.cpp Normal file
View File

@ -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};
}

269
src/protocol/buffers.h Normal file
View File

@ -0,0 +1,269 @@
#pragma once
#include <chrono>
#include <memory>
#include <list>
#include <cstring>
#include <ThreadPool/Mutex.h>
#include <sstream>
#include "Packet.h"
#include "../misc/queue.h"
#include <cassert>
#include <utility>
#ifndef NO_LOG
#include <log/LogUtils.h>
#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 <typename PktType>
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<PktType> 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<PktType> 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<PktType>& 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<std::recursive_mutex> try_lock_queue() {
threads::MutexTryLock lock(this->lock);
if(!lock) return {};
return std::unique_lock(this->lock);
}
std::unique_lock<std::recursive_mutex> 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<std::shared_ptr<PktType>> 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<pipes::buffer, void(*)(pipes::buffer*)> 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();
}
}

View File

@ -0,0 +1,392 @@
#include "buffers.h"
#include <unistd.h>
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<buffer_block*> orphaned_blocks;
std::deque<block_chain*> 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<unique_lock<fast_lock_t>> 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};
}

View File

@ -0,0 +1,179 @@
#include "buffers.h"
#include <unistd.h>
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<bool(void*)> 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<size_t> extra_alloc = 0;
std::atomic<size_t> extra_free = 0;
std::atomic<size_t> total_alloc = 0;
std::atomic<size_t> 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}

View File

@ -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};
}

View File

@ -0,0 +1,5 @@
//
// Created by wolverindev on 14.01.19.
//
//#include "ringbuffer.h"

225
src/protocol/ringbuffer.h Normal file
View File

@ -0,0 +1,225 @@
#pragma once
#include <cstddef>
#include <cstdint>
#include <cassert>
#include <utility>
#include <memory>
#include <mutex>
namespace ts {
namespace protocol {
template <typename T>
struct RingEntry {
bool flag_set = false;
T entry{};
};
template <typename E, size_t capacity_t, typename size_type>
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<E>(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<E>(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<size_type>::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<E>(entry);
slot.flag_set = true;
return true;
}
inline size_t capacity() { return this->_capacity; }
inline void clear() {
this->_ring_base_index = 0;
for(RingEntry<E>& 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<RingEntry<E>, capacity_t> _slots;
inline RingEntry<E>& 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 <typename E, uint16_t SIZE = 32, typename PTR_TYPE = std::shared_ptr<E>>
class PacketRingBuffer : public RingBuffer<PTR_TYPE, SIZE, uint16_t> {
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; }
};
}
}

26
src/qlz/QuickLZ.cpp Normal file
View File

@ -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;
}

162
src/qlz/QuickLZ.h Normal file
View File

@ -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 <string.h>
// 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

843
src/qlz/QuickLZ_L1.cpp Normal file
View File

@ -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;
}

848
src/qlz/QuickLZ_L2.cpp Normal file
View File

@ -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;
}

843
src/qlz/QuickLZ_L3.cpp Normal file
View File

@ -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;
}

243
src/query/Command.cpp Normal file
View File

@ -0,0 +1,243 @@
#include "Command.h"
#include <sstream>
#include <iostream>
#include <algorithm>
#include <pipes/buffer.h>
#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<const char>(), 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<variable> parms) : Command(command, {parms}) { }
Command::Command(const std::string& command, std::initializer_list<std::initializer_list<variable>> 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<std::string> 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{});
}
}

218
src/query/Command.h Normal file
View File

@ -0,0 +1,218 @@
#pragma once
#ifdef byte
#ifndef WIN32
#warning The byte macro is already defined! Undefining it!
#endif
#undef byte
#endif
#include <stdexcept>
#include <string>
#include <map>
#include <list>
#include <deque>
#include <memory>
#include <pipes/buffer.h>
#include "../Variable.h"
#ifdef HAVE_JSON
#include <json/json.h>
#endif
namespace ts {
#define PARM_TYPE(type, fromString, toString) \
BaseCommandParm(std::string key, type value) : BaseCommandParm(std::pair<std::string, std::string>(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<std::string> keys() const {
std::deque<std::string> 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<variable> p) : parms(p) {}
std::deque<variable> 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 <typename T>
ValueList& operator=(T var){ operator[](0) = var; return *this; }
ValueList& operator=(ts::ValueList& var){ operator[](0) = var.first().value(); return *this; }
template <typename T>
T as() const { return first().as<T>(); }
template <typename T>
operator T() { return as<T>(); }
template <typename T>
bool operator==(T other){ return as<T>() == other; }
template <typename T>
bool operator!=(T other){ return as<T>() != other; }
std::string string() const { return as<std::string>(); }
friend std::ostream& operator<<(std::ostream&, const ValueList&);
private:
ValueList(std::string& key, std::deque<ParameterBulk>& bulkList) : key(key), bulkList(bulkList) {}
std::string key;
public:
std::deque<ParameterBulk>& 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<variable>);
explicit Command(const std::string& command, std::initializer_list<std::initializer_list<variable>>);
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<std::string> 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<ParameterBulk> bulks;
std::deque<std::string> paramethers;
};
}
DEFINE_VARIABLE_TRANSFORM_TO_STR(ts::ValueList, in.string());
DEFINE_VARIABLE_TRANSFORM_TYPE(ts::ValueList, VARTYPE_TEXT);
#undef PARM_TYPE

251
src/query/command2.cpp Normal file
View File

@ -0,0 +1,251 @@
#include "command2.h"
#include "escape.h"
#include <sstream>
#include <algorithm>
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<type>(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<std__string>(value););
CONVERTER_ST(const_char__ , return str.c_str();, return std::string(std::any_cast<const_char__>(value)););
command::command(const std::string& command, bool editable) {
this->handle = make_shared<impl::command_data>();
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<impl::command_bulk>();
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<std::string>(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<impl::command_value>();
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{};

713
src/query/command2.h Normal file
View File

@ -0,0 +1,713 @@
#pragma once
#include <any>
#include <memory>
#include <vector>
#include <map>
#include <cassert>
#include <string_view>
#include <deque>
#include <tuple>
#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<std::string, std::shared_ptr<command_value>> values;
};
struct command_data {
std::string command;
bool editiable;
std::deque<std::shared_ptr<command_bulk>> bulks;
std::deque<std::string> 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<impl::command_data> 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<impl::command_bulk> handle) : bulk_index(index), handle(std::move(handle)) {}
size_t bulk_index;
std::shared_ptr<impl::command_bulk> handle;
};
class command_entry {
public:
static command_entry empty;
command_entry() : handle(std::make_shared<impl::command_value>()) {}
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<std::string>(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 <typename T, std::enable_if_t<!std::is_same<T, std::string>::value && !std::is_same<T, std::string>::value, int> = 0>
T as() {
static_assert(converter<T>::supported, "Target type isn't supported!");
static_assert(!converter<T>::supported || converter<T>::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<T>(this->handle->value);
else
throw command_casted_exception();
} else {
const auto& ref = std::any_cast<const std::string&>(this->handle->value);
this->handle->value = converter<T>::from_string(ref);
this->handle->to_string = converter<T>::to_string;
this->handle->casted = true;
}
return std::any_cast<T>(this->handle->value);
}
template <typename T, std::enable_if_t<std::is_same<T, std::string>::value, int> = 0>
T as() {
return this->string();
}
template <typename T>
inline operator T() {
return this->as<T>();
}
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 <typename T, typename std::enable_if<!std::is_same<T, std::string>::value && !std::is_same<T, const char*>::value && !std::is_same<T, const char (&)[]>::value, int>::type = 0>
void set(const T& value) {
static_assert(converter<T>::supported, "Target type isn't supported!");
static_assert(!converter<T>::supported || converter<T>::to_string, "Target type dosn't support encode");
this->handle->casted = true;
this->handle->value = std::move(value);
this->handle->to_string = converter<T>::to_string;
}
template <typename T, typename std::enable_if<std::is_same<T, std::string>::value, int>::type = 0>
void set(const T& value) {
this->handle->value = value;
this->handle->casted = false;
this->handle->to_string = nullptr;
}
template <int N>
void set(const char (&string)[N]) {
this->set(std::string(string, N - 1));
}
template <typename T>
command_entry&operator=(const T& value) {
this->set(value);
return *this;
}
explicit command_entry(std::shared_ptr<impl::command_value> handle) : handle(std::move(handle)) {}
private:
std::shared_ptr<impl::command_value> handle;
};
namespace descriptor {
namespace tliterals {
template <char... chars>
using tstring = std::integer_sequence<char, chars...>;
#ifndef WIN32
template <typename T, T... chars>
constexpr tstring<chars...> operator""_tstr() { return { }; }
#endif
template <typename>
struct tliteral;
template <char... elements>
struct tliteral<tstring<elements...>> {
static constexpr char string[sizeof...(elements) + 1] = { elements..., '\0' };
};
}
namespace impl {
namespace templates {
template <bool...>
struct _or_ {
constexpr static bool value = false;
};
template <bool T, bool... Args>
struct _or_<T, Args...> {
constexpr static bool value = T || _or_<Args...>::value;
};
template <typename... >
struct index;
template <typename... >
struct tuple_index;
template <typename T, typename... R>
struct index<T, T, R...> : std::integral_constant<size_t, 0>
{ };
template <typename T, typename F, typename... R>
struct index<T, F, R...> : std::integral_constant<size_t, 1 + index<T, R...>::value>
{ };
template <typename T, typename... R>
struct tuple_index<T, std::tuple<R...>> : std::integral_constant<size_t, index<T, R...>::value>
{ };
template <typename T>
struct remove_cr {
typedef T type;
};
template <typename T>
struct remove_cr<const T&> {
typedef T type;
};
}
struct base;
template <class key_t, typename value_type_t, class options, class... extends>
struct field;
struct field_data;
struct field_base;
struct optional_extend;
struct bulk_extend;
template <typename...>
struct command_parser {
constexpr static bool supported = false;
};
inline void parse_field(const std::shared_ptr<field_data>& description, field_base* field, command& cmd);
struct option_data {
bool bulked;
bool optional;
};
template <bool bulked_t, bool optional_t>
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<false, false>;
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<command_entry>&(*ref_values_fn)();
static inline std::vector<command_entry>& ref_values(const void* _this) {
void** vtable = *(void***) _this;
return ((ref_values_fn) vtable[0])();
}
};
template <class key_t, typename value_type_t, class options, class... extends>
struct field : public base, public options, public extends... {
friend struct command_parser<field<key_t, value_type_t, options, extends...>>;
static_assert(converter<value_type_t>::supported, "Target type isn't supported!");
static_assert(!converter<value_type_t>::supported || converter<value_type_t>::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<value_type_t>::from_string;
public:
template <bool flag = true /*, std::enable_if_t<!templates::_or_<std::is_same<extends, optional_extend>::value...>::value, int> = 0 */>
using as_optional = field<key_t, value_type_t, impl::options<options::is_bulked, flag>, optional_extend, extends...>;
template <bool flag = true /*, std::enable_if_t<!templates::_or_<std::is_same<extends, bulk_extend>::value...>::value, int> = 0 */>
using as_bulked = field<key_t, value_type_t, impl::options<flag, options::is_optional>, bulk_extend, extends...>;
using optional = as_optional<true>;
using bulked = as_bulked<true>;
inline static std::shared_ptr<object_t> describe() {
return std::make_shared<object_t>(
object_t {
1,
options::options_object(),
key,
typeid(value_type_t),
(void*) converter<value_type_t>::from_string,
(void*) converter<value_type_t>::to_string
}
);
}
inline value_type_t value() const {
command_entry& value = this->get_command_entry();
return value.as<value_type_t>();
}
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 <typename T>
inline T as() const {
command_entry& value = this->get_command_entry();
return value.as<T>();
}
template <typename T>
inline operator T() const {
return this->as<T>();
}
protected:
/* ATTENTION: This must be placed at index 0 within the VTable */
virtual __attribute__used std::vector<command_entry>& _v_ref_values() {
return this->values;
}
std::vector<command_entry> 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 <typename T>
inline T get_or(T&& value = T{}) const {
if(this->has_value())
return field_base::ref_values(this).front().as<T>();
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 <class key_t, class options>
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<object_t> describe() {
return std::make_shared<object_t>(
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 <class options>
struct command_handle : public command_handle_base, public options {
public:
using object_t = command_handle_data;
inline static std::shared_ptr<object_t> describe() {
return std::make_shared<object_t>(
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<class Arg>
auto operator[](Arg &&arg) -> decltype(get_command()[std::forward<Arg>(arg)]) {
return _command[std::forward<Arg>(arg)];
}
*/
template<class Arg>
decltype(std::declval<command>()[std::forward<Arg>(Arg{})]) operator[](Arg &&arg) {
return _command[std::forward<Arg>(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<std::shared_ptr<impl::base_data>> function_descriptors_t;
template <typename...>
struct describe {
inline static void apply(function_descriptors_t& result) {}
};
template <typename T, typename... Args>
struct describe<T, Args...> {
inline static void apply(function_descriptors_t& result) {
result.push_back(T::describe());
describe<Args...>::apply(result);
}
};
inline void parse_field(const std::shared_ptr<field_data>& 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 <class key_t, class options, class... extends>
struct command_parser<field<key_t, options, extends...>> {
constexpr static bool supported = true;
typedef field<key_t, options, extends...> field_t;
using descriptor_t = std::shared_ptr<typename field_t::object_t>;
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 <typename key_t, class options>
struct command_parser<trigger<key_t, options>> {
constexpr static bool supported = true;
typedef trigger<key_t, options> trigger_t;
using descriptor_t = std::shared_ptr<typename trigger_t::object_t>;
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 <class options>
struct command_parser<command_handle<options>> {
constexpr static bool supported = true;
typedef command_handle<options> command_handle_t;
using descriptor_t = std::shared_ptr<typename command_handle_t::object_t>;
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 <typename C>
struct command_parser<C> {
constexpr static bool supported = std::is_same<typename std::remove_reference<typename std::remove_cv<C>::type>::type, command>::value;
static_assert(supported, "This type isn't supported!");
using descriptor_t = std::shared_ptr<void>;
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<impl::default_options>;
template <class key_t, typename value_type_t>
using field = impl::field<key_t, value_type_t, impl::default_options>;
template <class key_t>
using trigger = impl::trigger<key_t, impl::default_options>;
template <typename... args_t>
inline impl::function_descriptors_t describe_function() {
impl::function_descriptors_t result;
impl::describe<args_t...>::apply(result);
return result;
}
struct invocable_function {
void operator()(command& command) {
this->invoke(command);
}
virtual void invoke(command& command) = 0;
};
template <typename... args_t>
struct typed_invocable_function : public invocable_function {
using args_tuple_t = std::tuple<args_t...>;
template <typename arg_t>
using command_parser_t = impl::command_parser<typename impl::templates::remove_cr<arg_t>::type>;
using descriptors_t = std::tuple<typename command_parser_t<args_t>::descriptor_t...>;
descriptors_t descriptors;
void(*function)(args_t...);
void invoke(command& command) override {
function(command_parser_t<args_t>::apply(std::get<impl::templates::tuple_index<args_t, args_tuple_t>::value>(descriptors), command)...);
}
};
template <typename... args_t, typename typed_function = typed_invocable_function<args_t...>>
std::shared_ptr<invocable_function> parse_function(void(*function)(args_t...)) {
auto result = std::make_shared<typed_function>();
result->function = function;
result->descriptors = {impl::command_parser<typename impl::templates::remove_cr<args_t>::type>::describe()...};
return result;
}
template <typename... args_t>
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<decltype(literal ##_tstr)>
#define tl(lit) _tlit(lit)
}
//using desc = descriptor::base<descriptor::impl::default_options>;
}
#include "command_internal.h"

View File

@ -0,0 +1,28 @@
#pragma once
#include "Definitions.h"
#include "command2.h"
namespace ts {
namespace cconstants {
typedef descriptor::field<tl("return_code"), std::string> return_code;
typedef descriptor::field<tl("sid"), ServerId> server_id;
typedef descriptor::field<tl("clid"), ClientId> client_id;
typedef descriptor::field<tl("cldbid"), ClientDbId> client_database_id;
typedef descriptor::field<tl("cid"), ChannelId> channel_id;
typedef descriptor::field<tl("cpid"), ChannelId> channel_parent_id;
typedef descriptor::field<tl("cgid"), GroupId> channel_group_id;
typedef descriptor::field<tl("cgid"), GroupId> server_group_id;
//FIXME
/* typedef descriptor::field<tl("permid"), permission::PermissionType> permission_id;
typedef descriptor::field<tl("permsid"), std::string> permission_name;
typedef descriptor::field<tl("permvalue"), permission::PermissionValue> permission_value;
*/
}
}

View File

@ -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;
};
}

View File

@ -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

143
src/query/escape.cpp Normal file
View File

@ -0,0 +1,143 @@
#include "escape.h"
#include <stdexcept>
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;
}

10
src/query/escape.h Normal file
View File

@ -0,0 +1,10 @@
#pragma once
#include <string>
namespace ts {
namespace query {
extern std::string escape(std::string);
extern std::string unescape(std::string, bool /* throw error */);
}
}

53
src/sql/SqlQuery.cpp Normal file
View File

@ -0,0 +1,53 @@
#include <functional>
#include <iostream>
#include <utility>
#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<CommandData>& data) : command_base(data->handle->copyCommandData(data)) {}
/**
* Command class itself
*/
threads::Future<result> 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<result> AsyncSqlPool::executeLater(const command& cmd) {
threads::Future<result> 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;
}
}

436
src/sql/SqlQuery.h Normal file
View File

@ -0,0 +1,436 @@
#pragma once
#include <string>
#include <sstream>
#include <cstring>
#include <sqlite3.h>
#include <functional>
#include <utility>
#include <ThreadPool/ThreadPool.h>
#include <ThreadPool/Future.h>
#include "../Variable.h"
#include <misc/memtracker.h>
#include <misc/lambda.h>
#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 <typename SelfType> 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<int(int, std::string*, std::string*)>;
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 <typename H = SqlManager>
inline H* sqlHandle() { return dynamic_cast<H*>(handle); }
std::string sql_command; //variable :<varname>
std::vector<variable> variables{};
threads::Mutex lock;
};
class SqlManager {
template <typename SelfType> 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<CommandData> allocateCommandData() = 0;
virtual std::shared_ptr<CommandData> copyCommandData(std::shared_ptr<CommandData>) = 0;
virtual result executeCommand(std::shared_ptr<CommandData>) = 0;
virtual result queryCommand(std::shared_ptr<CommandData>, const QueryCallback& fn) = 0;
private:
SqlType type;
};
#define SQL_FWD(...) ::std::forward<decltype(__VA_ARGS__)>(__VA_ARGS__)
namespace impl {
template <typename ...>
struct merge;
template <typename... A, typename... B>
struct merge<std::tuple<A...>, std::tuple<B...>> {
using result = std::tuple<A..., B...>;
};
/* let me stuff reverse */
template<typename, typename>
struct append_reversed { };
template<typename T, typename... Ts>
struct append_reversed<T, std::tuple<Ts...>> {
using type = std::tuple<Ts..., T>;
};
template<typename... Ts>
struct reverse_types {
using type = std::tuple<>;
};
template<typename T, typename... Ts>
struct reverse_types<T, Ts...> {
using type = typename append_reversed<T, typename reverse_types<Ts...>::type>::type;
};
/* Return type stuff */
typedef int stardart_return_type;
template <typename... args>
using stardart_return = std::function<int(args...)>;
template <typename type_return, typename enabled = void>
struct transformer_return {
using supported = std::false_type;
};
/* transforming (no transform) from int(...) to int(...) */
template <typename return_type>
struct transformer_return<return_type, typename std::enable_if<std::is_same<return_type, int>::value>::type> {
using supported = std::true_type;
template <typename... args>
static stardart_return<args...> transform(const std::function<return_type(args...)>& function) {
return function;
}
};
/* transforming from void(...) to int(...) */
template <typename return_type>
struct transformer_return<return_type, typename std::enable_if<std::is_void<return_type>::value>::type> {
using supported = std::true_type;
template <typename... args>
static stardart_return<args...> transform(const std::function<return_type(args...)>& function) {
return [function](args... parms) -> int {
function(parms...);
return 0;
};
}
};
/* transforming from ~integral~(...) to int(...) */
template <typename return_type>
struct transformer_return<return_type, typename std::enable_if<std::is_integral<return_type>::value && !std::is_same<return_type, int>::value>::type> {
using supported = std::true_type;
template <typename... args>
static stardart_return<args...> transform(const std::function<return_type(args...)>& function) {
return [function](args... parms) -> int {
return (int) function(parms...);
};
}
};
/* method stuff */
using stardart_function = std::function<stardart_return_type(int, std::string*, std::string*)>;
template <typename, typename>
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<std::string*, std::string*, int>, std::tuple<>> {
using supported = std::true_type;
using arguments_reversed = std::tuple<>;
static constexpr int argument_count = 0;
typedef std::function<stardart_return_type(int, std::string*, std::string*)> typed_function;
static stardart_function transform(const typed_function& function) {
return function;
}
};
/* proxy a standard function with left sided arguments */
template <typename... additional_reversed, typename... additional>
struct transformer_arguments<std::tuple<std::string*, std::string*, int, additional_reversed...>, std::tuple<additional...>> {
using supported = std::true_type;
using arguments_reversed = std::tuple<typename std::remove_const<additional_reversed>::type...>; /* note: these are still reversed */
static constexpr int argument_count = sizeof...(additional_reversed);
typedef std::function<stardart_return_type(additional..., int, std::string*, std::string*)> 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 <typename... additional_reversed, typename... additional>
struct transformer_arguments<std::tuple<char**, char**, int, additional_reversed...>, std::tuple<additional...>> {
using supported = std::true_type;
using arguments_reversed = std::tuple<typename std::remove_const<additional_reversed>::type...>;
static constexpr int argument_count = sizeof...(additional);
typedef std::function<stardart_return_type(additional..., int, char**, char**)> 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 <typename SelfType>
class command_base {
friend class ::sql::command;
friend class ::sql::model;
public:
command_base(SqlManager* handle, const std::string &sql, std::initializer_list<variable> 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<typename... Ts>
command_base(SqlManager* handle, std::string sql, Ts&&... vars) : command_base(handle, sql, {}) { values(vars...); }
command_base(const command_base<SelfType>& ref) : _data(ref._data), __data(ref._data.get()) {}
command_base(command_base<SelfType>&& 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 <typename T>
SelfType& value(const std::initializer_list<T>& val) {
//this->_data->variables.push_back(val.begin());
return *(SelfType*) this;
}
template <typename T>
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<typename value_t, typename... values_t>
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<CommandData>& data) : _data(data), __data(data.get()) {}
std::shared_ptr<CommandData> _data;
CommandData* __data = nullptr;
};
}
class model : public impl::command_base<model> {
public:
model(SqlManager* db, const std::string &sql, std::initializer_list<variable> values) : command_base(db, sql, values){};
template<typename... Ts>
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<CommandData>&);
};
class command : public impl::command_base<command> {
public:
command(SqlManager* db, const std::string &sql, std::initializer_list<variable> values) : command_base(db, sql, values) {};
template<typename... Ts>
command(SqlManager* handle, const std::string &sql, Ts... vars) : command_base(handle, sql, {}) { values(SQL_FWD(vars)...); }
/*
template<typename arg_0_t, typename arg_1_t>
command(SqlManager* handle, const std::string &sql, std::initializer_list<typename V> arg_0, std::initializer_list<arg_1_t> 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<result> executeLater();
//Convert lambdas to std::function
template <typename lambda, typename... arguments>
result query(const lambda& lam, arguments&&... args) {
typedef stx::lambda_type<lambda> info;
return this->query((typename info::invoker_function) lam, SQL_FWD(args)...);
}
template <typename call_ret, typename... call_args, typename... args>
result query(const std::function<call_ret(call_args...)>& callback, args&&... parms) { //Query without data
typedef impl::transformer_return<call_ret> ret_transformer;
typedef impl::transformer_arguments<typename impl::reverse_types<call_args...>::type, std::tuple<args...>> 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<typename args_transformer::arguments_reversed, typename impl::reverse_types<args...>::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<ret_transformer, args_transformer>(callback, SQL_FWD(parms)...);
};
private:
template <typename ret_transformer, typename args_transformer, typename type_call, typename... args>
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<result> 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);
}

384
src/sql/mysql/MySQL.cpp Normal file
View File

@ -0,0 +1,384 @@
#include <memory>
#include "MySQL.h"
#include "mysql_connection.h"
#include "src/log/LogUtils.h"
#include <pipes/misc/http.h>
#include <memory>
#include <utility>
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<const char*>() || strcmp(*connect_map["hostName"].get<const char*>(), ""))
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<sql::SQLString>()->asStdString());
if(connections < 1) return {ERROR_MYSQL_INVLID_PROPERTIES, "Invalid connection count"};
for(int i = 0; i < connections; i++) {
auto connection = unique_ptr<Connection>(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<ConnectionEntry>(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<mutex> 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<mutex> 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<CommandData> MySQLManager::allocateCommandData() {
return make_shared<MySQLCommand>();
}
std::shared_ptr<CommandData> MySQLManager::copyCommandData(std::shared_ptr<CommandData> 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<MySQLCommand>(_new);
auto __ptr = static_pointer_cast<MySQLCommand>(ptr);
//__new->stmt = __ptr->stmt;
return __new;
}
void prepared_statement_release(sql::PreparedStatement* stmt) {
if(stmt) stmt->close();
delete stmt;
};
typedef unique_ptr<sql::PreparedStatement, decltype(prepared_statement_release)*> PreparedStatementHandle;
void statement_release(sql::Statement* stmt) {
if(stmt) stmt->close();
delete stmt;
};
typedef unique_ptr<sql::Statement, decltype(statement_release)*> StatementHandle;
void result_release(sql::ResultSet* set) {
if(set && !set->isClosed()) set->close();
delete set;
}
typedef unique_ptr<sql::ResultSet, decltype(result_release)*> ResultHandle;
namespace sql {
namespace mysql {
bool evaluate_sql_query(string& sql, const std::vector<variable>& vars, std::vector<variable>& 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<variable>& 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<bool>());
break;
case VARTYPE_INT:
stmt->setInt(index, var.as<int32_t>());
break;
case VARTYPE_LONG:
stmt->setInt64(index, var.as<int64_t>());
break;
case VARTYPE_DOUBLE:
stmt->setDouble(index, var.as<double>());
break;
case VARTYPE_FLOAT:
stmt->setDouble(index, var.as<float>());
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<ConnectionEntry>& 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<mutex> lock(_mgr->connections_lock);
_mgr->connections_condition.notify_all();
}
}
std::unique_ptr<LocalConnection> MySQLManager::next_connection() {
unique_ptr<LocalConnection> 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<LocalConnection>(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<mutex> 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<mutex> 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<CommandData> _ptr) {
auto ptr = static_pointer_cast<MySQLCommand>(_ptr);
std::lock_guard<threads::Mutex> command_lock(ptr->lock);
auto command = ptr->sql_command;
auto variables = ptr->variables;
vector<variable> 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<LocalConnection> 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<CommandData> _ptr, const QueryCallback &fn) {
auto ptr = static_pointer_cast<MySQLCommand>(_ptr);
std::lock_guard<threads::Mutex> lock(ptr->lock);
auto command = ptr->sql_command;
auto variables = ptr->variables;
vector<variable> 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<LocalConnection> 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()};
}
}

91
src/sql/mysql/MySQL.h Normal file
View File

@ -0,0 +1,91 @@
#pragma once
#if __cplusplus >= 201703L
/* MySQL override. This needed to be inclided before cppconn/exception.h to define them */
#include <stdexcept>
#include <string>
#include <memory>
/* Now remove the trow */
#define throw(...)
#include <cppconn/exception.h>
#undef throw /* reset */
#endif
#include <condition_variable>
#include "sql/SqlQuery.h"
#include <cppconn/prepared_statement.h>
#include <cppconn/driver.h>
#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<variable>& vars, std::vector<variable>& result);
class MySQLCommand : public CommandData { };
struct ConnectionEntry {
friend class MySQLManager;
friend class LocalConnection;
public:
typedef std::function<void()> DisconnectListener;
private:
ConnectionEntry(std::unique_ptr<Connection>&& connection, bool used) : connection(std::move(connection)), used(used) {}
std::auto_ptr<sql::Savepoint> save_point;
std::unique_ptr<sql::Connection> connection;
bool used = false;
};
struct LocalConnection {
LocalConnection(MySQLManager* mgr, const std::shared_ptr<ConnectionEntry>& entry);
~LocalConnection();
MySQLManager* _mgr;
std::shared_ptr<ConnectionEntry> _connection;
};
class MySQLManager : public SqlManager {
friend class LocalConnection;
public:
typedef std::function<void(const std::shared_ptr<ConnectionEntry>&)> ListenerConnectionDisconnect;
typedef std::function<void(const std::shared_ptr<ConnectionEntry>&)> ListenerConnectionCreated;
typedef std::function<void()> ListenerConnected;
typedef std::function<void(bool /* wanted */)> 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<CommandData> copyCommandData(std::shared_ptr<CommandData> ptr) override;
std::shared_ptr<CommandData> allocateCommandData() override;
result executeCommand(std::shared_ptr<CommandData> ptr) override;
result queryCommand(std::shared_ptr<CommandData> ptr, const QueryCallback &fn) override;
public:
inline std::unique_ptr<LocalConnection> next_connection();
std::mutex connections_lock;
std::condition_variable connections_condition;
std::deque<std::shared_ptr<ConnectionEntry>> connections;
sql::Driver* driver = nullptr;
bool disconnecting = false;
};
}
}

View File

@ -0,0 +1,169 @@
#include <src/log/LogUtils.h>
#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<CommandData> SqliteManager::allocateCommandData() {
return std::make_shared<SqliteCommand>();
}
std::shared_ptr<CommandData> SqliteManager::copyCommandData(std::shared_ptr<CommandData> 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<SqliteCommand>(_new);
auto __ptr = static_pointer_cast<SqliteCommand>(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<int32_t>());
else if(val.type() == VARTYPE_LONG)
resultState = sqlite3_bind_int64(stmt, valueIndex, val.as<int64_t>());
else if(val.type() == VARTYPE_DOUBLE || val.type() == VARTYPE_FLOAT)
resultState = sqlite3_bind_double(stmt, valueIndex, val.as<double>());
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<sqlite3_stmt> SqliteManager::allocateStatement(const std::string& command) {
sqlite3_stmt* stmt;
if(sqlite3_prepare_v2(this->database, command.data(), static_cast<int>(command.length()), &stmt, nullptr) != SQLITE_OK)
return nullptr;
return std::shared_ptr<sqlite3_stmt>(stmt, [](void* _ptr) {
auto _stmt = static_cast<sqlite3_stmt*>(_ptr);
if(_stmt) sqlite3_finalize(_stmt);
});
}
result SqliteManager::queryCommand(std::shared_ptr<CommandData> _ptr, const QueryCallback &fn) {
auto ptr = static_pointer_cast<SqliteCommand>(_ptr);
std::lock_guard<threads::Mutex> lock(ptr->lock);
result res;
std::shared_ptr<sqlite3_stmt> 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<SqliteManager>()->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<const char *>(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<CommandData> _ptr) {
auto ptr = static_pointer_cast<SqliteCommand>(_ptr);
std::lock_guard<threads::Mutex> 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<SqliteManager>()->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)};
}

View File

@ -0,0 +1,32 @@
#pragma once
#include "../SqlQuery.h"
namespace sql {
namespace sqlite {
class SqliteCommand : public CommandData {
public:
std::shared_ptr<sqlite3_stmt> 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<CommandData> copyCommandData(std::shared_ptr<CommandData> ptr) override;
std::shared_ptr<CommandData> allocateCommandData() override;
result executeCommand(std::shared_ptr<CommandData> ptr) override;
result queryCommand(std::shared_ptr<CommandData> ptr, const QueryCallback&fn) override;
private:
std::shared_ptr<sqlite3_stmt> allocateStatement(const std::string&);
sqlite3* database = nullptr;
};
}
}

371
src/ssl/SSLManager.cpp Normal file
View File

@ -0,0 +1,371 @@
#include <fstream>
#include <cstring>
#include <log/LogUtils.h>
#include <experimental/filesystem>
#include <misc/digest.h>
#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<SSLContext> SSLManager::initializeContext(const std::string &key, std::string &privateKey, std::string &certificate, std::string &error, bool raw, const std::shared_ptr<SSLGenerator>& generator) {
auto load = this->loadContext(privateKey, certificate, error, raw, generator);
if(!load) return nullptr;
this->contexts[key] = load;
return load;
}
std::shared_ptr<SSLKeyPair> 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, decltype(&EVP_PKEY_free)>(EVP_PKEY_new(), ::EVP_PKEY_free);
auto rsa = RSA_new();
auto e = std::unique_ptr<BIGNUM, decltype(&BN_free)>(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<SSLContext> SSLManager::loadContext(std::string &rawKey, std::string &rawCert, std::string &error, bool rawData, const shared_ptr<SSLGenerator>& generator) {
std::shared_ptr<BIO> certBio = nullptr;
std::shared_ptr<BIO> keyBio = nullptr;
std::shared_ptr<X509> cert = nullptr;
std::shared_ptr<EVP_PKEY> key = nullptr;
bool flagCertModified = false;
bool flagKeyModified = false;
std::shared_ptr<SSL_CTX> context = nullptr;
std::shared_ptr<SSLContext> result = nullptr;
if(rawData) {
certBio = shared_ptr<BIO>(BIO_new(BIO_s_mem()), ::BIO_free);
BIO_write(certBio.get(), rawCert.c_str(), rawCert.length());
keyBio = shared_ptr<BIO>(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>(BIO_new_file(rawCert.c_str(), mode), ::BIO_free);
if(!certBio) SSL_ERROR("Could not load certificate: ");
keyBio = shared_ptr<BIO>(BIO_new_file(rawKey.c_str(), mode), ::BIO_free);
if(!keyBio) SSL_ERROR("Could not load key: ");
}
cert = shared_ptr<X509>(PEM_read_bio_X509(certBio.get(), nullptr, nullptr, nullptr), ::X509_free);
if(!cert && !generator) SSL_ERROR("Could not read certificate: ");
key = shared_ptr<EVP_PKEY>(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<EVP_PKEY>(generator->generateKey(), ::EVP_PKEY_free);
flagKeyModified = true;
}
if(!cert) {
cert = shared_ptr<X509>(generator->generateCertificate(key.get()), ::X509_free);
flagCertModified = true;
}
//Create context
context = shared_ptr<SSL_CTX>(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<SSLContext>();
result->context = context;
result->certificate = cert;
result->privateKey = key;
if(flagCertModified) {
if(!rawData) {
certBio = shared_ptr<BIO>(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>(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<SSLKeyPair> SSLManager::loadSSL(const std::string &rawKey, std::string &error, bool rawData, bool readPublic) {
std::shared_ptr<BIO> keyBio = nullptr;
std::shared_ptr<EVP_PKEY> key = nullptr;
std::shared_ptr<SSLKeyPair> result = make_shared<SSLKeyPair>();
// SSL_CTX_set_ecdh_auto(ctx, 1);
if(rawData) {
keyBio = shared_ptr<BIO>(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>(BIO_new_file(rawKey.c_str(), "r"), ::BIO_free);
if(!keyBio) SSL_ERROR("Could not load key: ");
}
if(readPublic)
key = shared_ptr<EVP_PKEY>(PEM_read_bio_PUBKEY(keyBio.get(), nullptr, nullptr, nullptr), ::EVP_PKEY_free);
else
key = shared_ptr<EVP_PKEY>(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<SSLKeyPair> &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<pipes::SSL::Options> 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<pipes::SSL::Options>();
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;
}

62
src/ssl/SSLManager.h Normal file
View File

@ -0,0 +1,62 @@
#pragma once
#include "openssl/ssl.h"
#include "openssl/err.h"
#include <Definitions.h>
#include <map>
#include <pipes/ssl.h>
namespace ts {
namespace ssl {
struct SSLContext {
std::shared_ptr<SSL_CTX> context = nullptr;
std::shared_ptr<EVP_PKEY> privateKey = nullptr;
std::shared_ptr<X509> certificate = nullptr;
};
struct SSLGenerator {
std::deque<std::pair<std::string, std::string>> subjects;
std::deque<std::pair<std::string, std::string>> issues;
EVP_PKEY* generateKey();
X509* generateCertificate(EVP_PKEY*);
};
struct SSLKeyPair {
bool contains_private = false;
std::shared_ptr<EVP_PKEY> key = nullptr;
};
class SSLManager {
public:
SSLManager();
virtual ~SSLManager();
bool initialize();
void printDetails();
std::shared_ptr<SSLKeyPair> initializeSSLKey(const std::string &key, const std::string &rsaKey, std::string &error, bool raw = false);
std::shared_ptr<SSLContext> initializeContext(const std::string& key, std::string& privateKey, std::string& certificate, std::string& error, bool raw = false, const std::shared_ptr<SSLGenerator>& = nullptr);
std::shared_ptr<SSLContext> getContext(const std::string& key){ return this->contexts[key]; }
std::shared_ptr<SSLKeyPair> getRsaKey(const std::string& key){ return this->rsa[key]; }
bool verifySign(const std::shared_ptr<SSLKeyPair>& key, const std::string& message, const std::string& sign);
void disable_web() { this->_web_disabled = true; }
std::shared_ptr<pipes::SSL::Options> web_ssl_options();
std::shared_ptr<SSLContext> getQueryContext() { return this->getContext("query"); }
private:
std::map<std::string, std::shared_ptr<SSLContext>> contexts;
std::map<std::string, std::shared_ptr<SSLKeyPair>> rsa;
std::mutex _web_options_lock;
bool _web_disabled = false;
std::shared_ptr<pipes::SSL::Options> _web_options;
std::shared_ptr<SSLContext> loadContext(std::string& rawKey, std::string& rawCert, std::string& error, bool rawData = false, const std::shared_ptr<SSLGenerator>& = nullptr);
std::shared_ptr<SSLKeyPair> loadSSL(const std::string &key, std::string &error, bool rawData = false, bool readPublic = false);
};
}
}

29
test/BBTest.cpp Normal file
View File

@ -0,0 +1,29 @@
#include <string>
#include <src/bbcode/bbcodes.h>
#include <src/misc/advanced_mutex.h>
#include <iostream>
#include <chrono>
#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<nanoseconds>(end - beg).count() << endl;
*/
return 0;
}

121
test/ChannelTest.cpp Normal file
View File

@ -0,0 +1,121 @@
#include <src/query/Command.h>
#include <src/BasicChannel.h>
#include <ThreadPool/Thread.h>
#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<TreeEntry>& t_entry, int deep) {
auto entry = dynamic_pointer_cast<TEntry>(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 <typename T>
void print_address(const T& idx) {
cout << &idx << endl;
[idx]() {
cout << &idx << endl;
}();
}
int main() {
auto index = shared_ptr<int>();
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<TEntry>(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;
}

156
test/CommandTest.cpp Normal file
View File

@ -0,0 +1,156 @@
#include <src/query/command2.h>
#include <codecvt>
#include <locale>
#include <string>
#include <cassert>
#include <src/misc/base64.h>
#include <sstream>
#include <src/License.h>
#include <functional>
#include "PermissionManager.h"
#include "src/query/command_constants.h"
using namespace std;
using namespace ts;
using namespace license::teamspeak;
template <class key_t, typename value_type_t>
using field = ts::descriptor::field<key_t, value_type_t>;
template <class key_t>
using trigger = ts::descriptor::trigger<key_t>;
void handleCommand(
ts::command& _cmd,
cconstants::return_code::optional return_code,
field<tl("key_a"), int> key_a,
field<tl("key_b"), string>::optional key_b,
field<tl("key_c"), uint64_t>::optional::bulked key_c,
trigger<tl("test")> switch_test
) {
if(key_a.value() < 10)
cout << "ERROR" << endl;
auto b = key_c.as<string>();
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<string>("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::impl::command_value>();
ts::command_entry entry(handle);
entry = 255;
cout << "Value: " << entry.as<int>() << endl;
cout << "Value: " << entry.melt().as<uint32_t>() << endl;
cout << "Str: " << entry.string() << endl;
cout << "U8: " << (int) entry.melt().as<uint8_t>() << 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;
}

67
test/CrashTest.cpp Normal file
View File

@ -0,0 +1,67 @@
#include <cstdio>
#include <src/misc/time.h>
#include <src/log/LogUtils.h>
#include <iostream>
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<logger::LoggerConfig>();
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;
}

59
test/EndianessTest.cpp Normal file
View File

@ -0,0 +1,59 @@
#include <iostream>
#include <misc/endianness.h>
#include <cassert>
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);
}

29
test/LinkedTest.cpp Normal file
View File

@ -0,0 +1,29 @@
#include <linked_helper.h>
#include <iostream>
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<string> 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;
}

69
test/PropertyTest.cpp Normal file
View File

@ -0,0 +1,69 @@
#include <iostream>
#include <src/Properties.h>
#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<property::VirtualServerProperties>("virtualserver_host")->name<< endl;
Properties props;
props.register_property_type<property::InstanceProperties>();
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<string>() << endl;
props[property::SERVERINSTANCE_QUERY_PORT] = "XX";
cout << "Port: " << props[property::SERVERINSTANCE_QUERY_PORT].as<string>() << endl;
props[property::SERVERINSTANCE_QUERY_PORT] = 2;
cout << "Port: " << props[property::SERVERINSTANCE_QUERY_PORT].as<string>() << endl;
cout << "Port: " << props[property::SERVERINSTANCE_QUERY_PORT].as<int32_t>() << 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;
}
}

98
test/RingTest.cpp Normal file
View File

@ -0,0 +1,98 @@
//
// Created by wolverindev on 14.01.19.
//
#include <misc/net.h>
#include <protocol/buffers.h>
#include <protocol/ringbuffer.h>
#include <iostream>
#include <array>
using namespace std;
using namespace std::chrono;
using namespace ts;
using namespace ts::protocol;
template <typename E, size_t S, typename T>
void print_queue(RingBuffer<E, S, T>& 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<pipes::buffer> 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<nanoseconds>(alloc_end - alloc_begin).count() / TEST_COUNT, duration_cast<nanoseconds>(free_end - alloc_end).count() / TEST_COUNT);
}
int main() {
/*
RingBuffer<int, 16, uint16_t> 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);
*/
}

150
test/SQLTest.cpp Normal file
View File

@ -0,0 +1,150 @@
#include <iostream>
#include <src/sql/SqlQuery.h>
#include <src/sql/mysql/MySQL.h>
#include <src/sql/sqlite/SqliteSQL.h>
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*, 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<int>([](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
}

303
test/WSSTest.cpp Normal file
View File

@ -0,0 +1,303 @@
#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <cstring>
#include <iostream>
#include <deque>
#include <ThreadPool/Mutex.h>
#include <ThreadPool/Thread.h>
#include <src/log/LogUtils.h>
#include <src/ssl/SSLSocket.h>
#include <event.h>
#include <src/ws/WebSocket.h>
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<EVP_PKEY*, X509*> 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, decltype(&EVP_PKEY_free)>(EVP_PKEY_new(), ::EVP_PKEY_free);
auto rsa = RSA_new();
auto e = std::unique_ptr<BIGNUM, decltype(&BN_free)>(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<std::string> 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<SSL_CTX>(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();
}