Updating the deploy algorithm
This commit is contained in:
parent
72abd7e20e
commit
3dea4906e1
@ -61,6 +61,8 @@ find_package(Opus REQUIRED)
|
||||
find_package(spdlog REQUIRED)
|
||||
find_package(Jemalloc REQUIRED)
|
||||
find_package(Protobuf REQUIRED)
|
||||
message("${zstd_DIR}")
|
||||
find_package(zstd REQUIRED)
|
||||
|
||||
include_directories(${StringVariable_INCLUDE_DIR})
|
||||
add_subdirectory(music/)
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit 0dc8e28125f9727f2dffef4b6f93b397099db1f0
|
||||
Subproject commit ef2d6842d547b8f84ebbe4fe9fa6f132bcd84d35
|
@ -296,6 +296,7 @@ target_link_libraries(TeaSpeakServer
|
||||
|
||||
jsoncpp_lib
|
||||
${ed25519_LIBRARIES_STATIC}
|
||||
zstd::libzstd_static
|
||||
)
|
||||
|
||||
if (COMPILE_WEB_CLIENT)
|
||||
|
@ -13,38 +13,42 @@ using namespace ts::permission;
|
||||
|
||||
//#define DISABLE_CACHING
|
||||
|
||||
struct ts::server::CachedPermissionManager {
|
||||
ServerId server_id{0};
|
||||
ClientDbId client_database_id{0};
|
||||
std::weak_ptr<permission::v2::PermissionManager> instance{};
|
||||
|
||||
std::shared_ptr<permission::v2::PermissionManager> instance_ref{}; /* reference to the current instance, will be refreshed every time the instance gets accessed */
|
||||
std::chrono::time_point<std::chrono::system_clock> last_access{};
|
||||
};
|
||||
|
||||
|
||||
struct ts::server::StartupCacheEntry {
|
||||
ServerId sid{0};
|
||||
|
||||
std::deque<std::unique_ptr<StartupPermissionEntry>> permissions{};
|
||||
std::deque<std::unique_ptr<StartupPropertyEntry>> properties{};
|
||||
};
|
||||
|
||||
DatabaseHelper::DatabaseHelper(sql::SqlManager* srv) : sql(srv) {}
|
||||
DatabaseHelper::~DatabaseHelper() {
|
||||
for(const auto& elm : cachedPermissionManagers)
|
||||
delete elm;
|
||||
cachedPermissionManagers.clear();
|
||||
this->cached_permission_managers.clear();
|
||||
}
|
||||
|
||||
void DatabaseHelper::tick() {
|
||||
auto cache_timeout = std::chrono::system_clock::now() - std::chrono::minutes{10};
|
||||
{
|
||||
threads::MutexLock l(this->permManagerLock);
|
||||
auto cpy = this->cachedPermissionManagers;
|
||||
for(const auto& mgr : cpy){
|
||||
//if(mgr->ownLock && system_clock::now() - mgr->lastAccess > minutes(5)) //TODO remove instand delete!
|
||||
mgr->ownLock.reset();
|
||||
if(mgr->manager.expired()){
|
||||
this->cachedPermissionManagers.erase(std::find(this->cachedPermissionManagers.begin(), this->cachedPermissionManagers.end(), mgr));
|
||||
delete mgr;
|
||||
}
|
||||
}
|
||||
}
|
||||
std::lock_guard cp_lock{this->cached_permission_manager_lock};
|
||||
|
||||
{
|
||||
threads::MutexLock l(this->propsLock);
|
||||
auto pcpy = this->cachedProperties;
|
||||
for(const auto& mgr : pcpy) {
|
||||
if(mgr->ownLock && system_clock::now() - mgr->lastAccess > minutes(5))
|
||||
mgr->ownLock.reset();
|
||||
if(mgr->properties.expired()) {
|
||||
this->cachedProperties.erase(std::find(this->cachedProperties.begin(), this->cachedProperties.end(), mgr));
|
||||
delete mgr;
|
||||
}
|
||||
}
|
||||
this->cached_permission_managers.erase(std::remove_if(this->cached_permission_managers.begin(), this->cached_permission_managers.end(), [&](const std::unique_ptr<CachedPermissionManager>& manager) {
|
||||
if(manager->last_access < cache_timeout)
|
||||
manager->instance_ref = nullptr;
|
||||
|
||||
if(manager->instance.expired())
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}), this->cached_permission_managers.end());
|
||||
}
|
||||
}
|
||||
|
||||
@ -136,15 +140,12 @@ bool DatabaseHelper::validClientDatabaseId(const std::shared_ptr<VirtualServer>&
|
||||
void DatabaseHelper::deleteClient(const std::shared_ptr<VirtualServer>& server, ClientDbId cldbid) {
|
||||
auto serverId = (ServerId) (server ? server->getServerId() : 0);
|
||||
{
|
||||
lock_guard<threads::Mutex> lock(permManagerLock);
|
||||
for(auto permMgr : this->cachedPermissionManagers)
|
||||
if(serverId == 0 || (permMgr->cldbid == cldbid && permMgr->sid == serverId)) {
|
||||
this->cachedPermissionManagers.erase(std::find(this->cachedPermissionManagers.begin(), this->cachedPermissionManagers.end(), permMgr));
|
||||
delete permMgr;
|
||||
break;
|
||||
}
|
||||
lock_guard lock{cached_permission_manager_lock};
|
||||
|
||||
this->cached_permission_managers.erase(std::remove_if(this->cached_permission_managers.begin(), this->cached_permission_managers.end(), [&](const auto& entry) {
|
||||
return entry->server_id == serverId && entry->client_database_id == cldbid;
|
||||
}), this->cached_permission_managers.end());
|
||||
}
|
||||
//TODO remove from props cache?
|
||||
|
||||
sql::result state{};
|
||||
|
||||
@ -236,26 +237,38 @@ inline sql::result load_permissions_v2(
|
||||
logTrace(server_id, "[SQL] load_permissions(\"{}\") took {}ms", command.sqlCommand(), duration_cast<milliseconds>(time).count());
|
||||
}
|
||||
|
||||
#define UPDATE_COMMAND "UPDATE `permissions` SET `value` = :value, `grant` = :grant, `flag_skip` = :flag_skip, `flag_negate` = :flag_negate WHERE `serverId` = :serverId AND `type` = :type AND `id` = :id AND `permId` = :permId AND `channelId` = :chId"
|
||||
#define INSERT_COMMAND "INSERT INTO `permissions` (`serverId`, `type`, `id`, `channelId`, `permId`, `value`, `grant`, `flag_skip`, `flag_negate`) VALUES (:serverId, :type, :id, :chId, :permId, :value, :grant, :flag_skip, :flag_negate)"
|
||||
#define DELETE_COMMAND "DELETE FROM `permissions` WHERE `serverId` = :serverId AND `type` = :type AND `id` = :id AND `permId` = :permId AND `channelId` = :chId"
|
||||
constexpr static std::string_view kPermissionUpdateCommand{"UPDATE `permissions` SET `value` = :value, `grant` = :grant, `flag_skip` = :flag_skip, `flag_negate` = :flag_negate WHERE `serverId` = :serverId AND `type` = :type AND `id` = :id AND `permId` = :permId AND `channelId` = :chId"};
|
||||
constexpr static std::string_view kPermissionInsertCommand{"INSERT INTO `permissions` (`serverId`, `type`, `id`, `channelId`, `permId`, `value`, `grant`, `flag_skip`, `flag_negate`) VALUES (:serverId, :type, :id, :chId, :permId, :value, :grant, :flag_skip, :flag_negate)"};
|
||||
constexpr static std::string_view kPermissionDeleteCommand{"DELETE FROM `permissions` WHERE `serverId` = :serverId AND `type` = :type AND `id` = :id AND `permId` = :permId AND `channelId` = :chId"};
|
||||
|
||||
std::shared_ptr<permission::v2::PermissionManager> DatabaseHelper::find_cached_permission_manager(ServerId server_id,
|
||||
ClientDbId client_database_id) {
|
||||
for(auto it = this->cached_permission_managers.begin(); it != this->cached_permission_managers.end(); it++) {
|
||||
auto& cached_manager = *it;
|
||||
|
||||
if(cached_manager->client_database_id == client_database_id && cached_manager->server_id == server_id) {
|
||||
auto manager = cached_manager->instance.lock();
|
||||
if(!manager){
|
||||
this->cached_permission_managers.erase(it);
|
||||
break;
|
||||
}
|
||||
|
||||
cached_manager->last_access = system_clock::now();
|
||||
cached_manager->instance_ref = manager;
|
||||
return manager;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<v2::PermissionManager> DatabaseHelper::loadClientPermissionManager(const std::shared_ptr<VirtualServer>& server, ClientDbId cldbid) {
|
||||
auto server_id = server ? server->getServerId() : 0;
|
||||
#ifndef DISABLE_CACHING
|
||||
{
|
||||
lock_guard<threads::Mutex> lock(permManagerLock);
|
||||
for(auto permMgr : this->cachedPermissionManagers)
|
||||
if(permMgr->cldbid == cldbid && permMgr->sid == (server ? server->getServerId() : 0)) {
|
||||
auto ptr = permMgr->manager.lock();
|
||||
if(!ptr){
|
||||
this->cachedPermissionManagers.erase(std::find(this->cachedPermissionManagers.begin(), this->cachedPermissionManagers.end(), permMgr));
|
||||
delete permMgr;
|
||||
break;
|
||||
}
|
||||
permMgr->lastAccess = system_clock::now();
|
||||
return ptr;
|
||||
}
|
||||
std::lock_guard lock{cached_permission_manager_lock};
|
||||
auto manager = this->find_cached_permission_manager(server_id, cldbid);
|
||||
if(manager) return manager;
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -298,15 +311,23 @@ std::shared_ptr<v2::PermissionManager> DatabaseHelper::loadClientPermissionManag
|
||||
|
||||
|
||||
#ifndef DISABLE_CACHING
|
||||
this->permManagerLock.lock();
|
||||
auto entry = new CachedPermissionManager();
|
||||
entry->sid = server_id;
|
||||
entry->manager = permission_manager;
|
||||
entry->ownLock = permission_manager;
|
||||
entry->cldbid = cldbid;
|
||||
entry->lastAccess = system_clock::now();
|
||||
this->cachedPermissionManagers.push_back(entry);
|
||||
this->permManagerLock.unlock();
|
||||
auto cache_entry = std::make_unique<CachedPermissionManager>();
|
||||
cache_entry->server_id = server_id;
|
||||
cache_entry->instance = permission_manager;
|
||||
cache_entry->instance_ref = permission_manager;
|
||||
cache_entry->client_database_id = cldbid;
|
||||
cache_entry->last_access = system_clock::now();
|
||||
|
||||
{
|
||||
std::lock_guard cache_lock{this->cached_permission_manager_lock};
|
||||
|
||||
/* test if we might not got a second instance */
|
||||
auto manager = this->find_cached_permission_manager(server_id, cldbid);
|
||||
if(manager) return manager;
|
||||
|
||||
this->cached_permission_managers.push_back(std::move(cache_entry));
|
||||
}
|
||||
|
||||
#endif
|
||||
return permission_manager;
|
||||
}
|
||||
@ -319,7 +340,7 @@ void DatabaseHelper::saveClientPermissions(const std::shared_ptr<ts::server::Vir
|
||||
|
||||
auto server_id = server ? server->getServerId() : 0;
|
||||
for(auto& update : updates) {
|
||||
std::string query = update.flag_delete ? DELETE_COMMAND : (update.flag_db ? UPDATE_COMMAND : INSERT_COMMAND);
|
||||
std::string query{update.flag_delete ? kPermissionDeleteCommand : (update.flag_db ? kPermissionUpdateCommand : kPermissionInsertCommand)};
|
||||
|
||||
auto permission_data = permission::resolvePermissionData(update.permission);
|
||||
auto value = update.update_value == v2::delete_value ? permNotGranted : update.values.value;
|
||||
@ -386,7 +407,7 @@ void DatabaseHelper::saveGroupPermissions(const std::shared_ptr<ts::server::Virt
|
||||
|
||||
auto server_id = server ? server->getServerId() : 0;
|
||||
for(auto& update : updates) {
|
||||
std::string query = update.flag_delete ? DELETE_COMMAND : (update.flag_db ? UPDATE_COMMAND : INSERT_COMMAND);
|
||||
std::string query{update.flag_delete ? kPermissionDeleteCommand : (update.flag_db ? kPermissionUpdateCommand : kPermissionInsertCommand)};
|
||||
|
||||
auto permission_data = permission::resolvePermissionData(update.permission);
|
||||
auto value = update.update_value == v2::delete_value ? permNotGranted : update.values.value;
|
||||
@ -458,7 +479,7 @@ void DatabaseHelper::savePlaylistPermissions(const std::shared_ptr<VirtualServer
|
||||
|
||||
auto server_id = server ? server->getServerId() : 0;
|
||||
for(auto& update : updates) {
|
||||
std::string query = update.flag_delete ? DELETE_COMMAND : (update.flag_db ? UPDATE_COMMAND : INSERT_COMMAND);
|
||||
std::string query{update.flag_delete ? kPermissionDeleteCommand : (update.flag_db ? kPermissionUpdateCommand : kPermissionInsertCommand)};
|
||||
|
||||
auto permission_data = permission::resolvePermissionData(update.permission);
|
||||
auto value = update.update_value == v2::delete_value ? permNotGranted : update.values.value;
|
||||
@ -524,7 +545,7 @@ void DatabaseHelper::saveChannelPermissions(const std::shared_ptr<ts::server::Vi
|
||||
|
||||
auto server_id = server ? server->getServerId() : 0;
|
||||
for(auto& update : updates) {
|
||||
std::string query = update.flag_delete ? DELETE_COMMAND : (update.flag_db ? UPDATE_COMMAND : INSERT_COMMAND);
|
||||
std::string query{update.flag_delete ? kPermissionDeleteCommand : (update.flag_db ? kPermissionUpdateCommand : kPermissionInsertCommand)};
|
||||
|
||||
auto value = update.update_value == v2::delete_value ? permNotGranted : update.values.value;
|
||||
auto grant = update.update_grant == v2::delete_value ? permNotGranted : update.values.grant;
|
||||
@ -1058,6 +1079,15 @@ void DatabaseHelper::clearStartupCache(ts::ServerId sid) {
|
||||
}
|
||||
}
|
||||
|
||||
void DatabaseHelper::handleServerDelete(ServerId server_id) {
|
||||
{
|
||||
std::lock_guard pm_lock{this->cached_permission_manager_lock};
|
||||
this->cached_permission_managers.erase(std::remove_if(this->cached_permission_managers.begin(), this->cached_permission_managers.end(), [&](const auto& entry) {
|
||||
return entry->server_id == server_id;
|
||||
}), this->cached_permission_managers.end());
|
||||
}
|
||||
}
|
||||
|
||||
//SELECT `serverId`, `type`, `id`, `key`, `value` FROM properties ORDER BY `serverId`
|
||||
//SELECT `serverId`, `type`, `id`, `channelId`, `permId`, `value`, `grant`, `flag_skip`, `flag_negate` FROM permissions ORDER BY `serverId`
|
||||
struct StartupPermissionArgument {
|
||||
@ -1247,3 +1277,81 @@ bool DatabaseHelper::deletePlaylist(const std::shared_ptr<ts::server::VirtualSer
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
constexpr static auto kDBListQuery{R"(
|
||||
SELECT `clients`.*, `properties`.`value` as `client_description` FROM (
|
||||
SELECT
|
||||
`clients_server`.`client_database_id`,
|
||||
`clients_server`.`client_unique_id`,
|
||||
`clients_server`.`client_nickname`,
|
||||
`clients_server`.`client_ip`,
|
||||
`clients_server`.`client_created`,
|
||||
`clients_server`.`client_last_connected`,
|
||||
`clients_server`.`client_total_connections`,
|
||||
`clients`.`client_login_name` FROM `clients_server`
|
||||
INNER JOIN `clients` ON `clients`.`client_database_id` = `clients_server`.`client_database_id`
|
||||
WHERE `server_id` = :serverId LIMIT :offset, :limit
|
||||
) AS `clients`
|
||||
LEFT JOIN `properties` ON `properties`.serverId = :serverId AND `properties`.key = 'client_description' AND `properties`.`id` = `clients`.`client_database_id`
|
||||
)"};
|
||||
|
||||
void DatabaseHelper::listDatabaseClients(
|
||||
ServerId server_id,
|
||||
const std::optional<int64_t>& offset,
|
||||
const std::optional<int64_t>& limit,
|
||||
void (* callback)(void *, const DatabaseClient &),
|
||||
void *user_argument) {
|
||||
|
||||
DatabaseClient client;
|
||||
size_t set_index{0};
|
||||
auto sqlResult = sql::command{this->sql, kDBListQuery,
|
||||
variable{":serverId", server_id},
|
||||
variable{":offset", offset.has_value() ? *offset : 0},
|
||||
variable{":limit", limit.has_value() ? *limit : -1}
|
||||
}.query([&](int length, std::string* values, std::string* names) {
|
||||
set_index++;
|
||||
|
||||
auto index{0};
|
||||
try {
|
||||
assert(names[index] == "client_database_id");
|
||||
client.client_database_id = std::stoull(values[index++]);
|
||||
|
||||
assert(names[index] == "client_unique_id");
|
||||
client.client_unique_id = values[index++];
|
||||
|
||||
assert(names[index] == "client_nickname");
|
||||
client.client_nickname = values[index++];
|
||||
|
||||
assert(names[index] == "client_ip");
|
||||
client.client_ip = values[index++];
|
||||
|
||||
assert(names[index] == "client_created");
|
||||
client.client_last_connected = values[index++];
|
||||
|
||||
assert(names[index] == "client_last_connected");
|
||||
client.client_created = values[index++];
|
||||
|
||||
assert(names[index] == "client_total_connections");
|
||||
client.client_total_connections = values[index++];
|
||||
|
||||
assert(names[index] == "client_login_name");
|
||||
client.client_login_name = values[index++];
|
||||
|
||||
assert(names[index] == "client_description");
|
||||
client.client_description = values[index++];
|
||||
|
||||
assert(index == length);
|
||||
} catch (std::exception& ex) {
|
||||
logError(server_id, "Failed to parse client base properties at index {}: {}. Offset: {} Limit: {} Set: {}",
|
||||
index - 1,
|
||||
ex.what(),
|
||||
offset.has_value() ? std::to_string(*offset) : "not given",
|
||||
limit.has_value() ? std::to_string(*limit) : "not given",
|
||||
set_index - 1
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
callback(user_argument, client);
|
||||
});
|
||||
}
|
@ -25,26 +25,6 @@ namespace ts::server {
|
||||
uint32_t client_total_connections;
|
||||
};
|
||||
|
||||
struct CachedPermissionManager {
|
||||
ServerId sid;
|
||||
ClientDbId cldbid;
|
||||
std::weak_ptr<permission::v2::PermissionManager> manager;
|
||||
std::shared_ptr<permission::v2::PermissionManager> ownLock;
|
||||
std::chrono::time_point<std::chrono::system_clock> lastAccess;
|
||||
};
|
||||
|
||||
struct CachedProperties {
|
||||
ServerId sid;
|
||||
ClientDbId cldbid;
|
||||
std::weak_ptr<Properties> properties;
|
||||
std::shared_ptr<Properties> ownLock;
|
||||
std::chrono::time_point<std::chrono::system_clock> lastAccess;
|
||||
};
|
||||
|
||||
/*
|
||||
CREATE_TABLE("properties", "`serverId` INTEGER DEFAULT -1, `type` INTEGER, `id` INTEGER, `key` VARCHAR(" UNKNOWN_KEY_LENGTH "), `value` TEXT", command_append_utf8);
|
||||
CREATE_TABLE("permissions", "`serverId` INT NOT NULL, `type` INT, `id` INT, `channelId` INT, `permId` VARCHAR(" UNKNOWN_KEY_LENGTH "), `value` INT, `grant` INT", command_append_utf8);
|
||||
*/
|
||||
struct StartupPermissionEntry {
|
||||
permission::PermissionSqlType type = permission::SQL_PERM_CHANNEL;
|
||||
uint64_t id = 0;
|
||||
@ -64,11 +44,19 @@ namespace ts::server {
|
||||
std::string value;
|
||||
};
|
||||
|
||||
struct StartupCacheEntry {
|
||||
ServerId sid;
|
||||
struct DatabaseClient {
|
||||
ClientDbId client_database_id;
|
||||
std::string client_unique_id;
|
||||
|
||||
std::deque<std::unique_ptr<StartupPermissionEntry>> permissions;
|
||||
std::deque<std::unique_ptr<StartupPropertyEntry>> properties;
|
||||
std::string client_nickname;
|
||||
std::string client_ip;
|
||||
|
||||
std::string client_created; /* seconds since epoch */
|
||||
std::string client_last_connected; /* seconds since epoch */
|
||||
std::string client_total_connections;
|
||||
|
||||
std::string client_login_name;
|
||||
std::string client_description; /* optional and only given sometimes */
|
||||
};
|
||||
|
||||
struct FastPropertyEntry {
|
||||
@ -76,6 +64,8 @@ namespace ts::server {
|
||||
std::string value;
|
||||
};
|
||||
|
||||
struct CachedPermissionManager;
|
||||
struct StartupCacheEntry;
|
||||
class DatabaseHelper {
|
||||
public:
|
||||
static std::shared_ptr<Properties> default_properties_client(std::shared_ptr<Properties> /* properties */, ClientType /* type */);
|
||||
@ -88,6 +78,15 @@ namespace ts::server {
|
||||
size_t cacheBinarySize();
|
||||
void clearStartupCache(ServerId sid = 0);
|
||||
|
||||
void handleServerDelete(ServerId /* server id */);
|
||||
|
||||
void listDatabaseClients(
|
||||
ServerId /* server id */,
|
||||
const std::optional<int64_t>& offset,
|
||||
const std::optional<int64_t>& limit,
|
||||
void(* /* callback */)(void* /* user argument */, const DatabaseClient& /* client */),
|
||||
void* /* user argument */);
|
||||
|
||||
void deleteClient(const std::shared_ptr<VirtualServer>&,ClientDbId);
|
||||
bool validClientDatabaseId(const std::shared_ptr<VirtualServer>&, ClientDbId);
|
||||
std::deque<std::shared_ptr<ClientDatabaseInfo>> queryDatabaseInfo(const std::shared_ptr<VirtualServer>&, const std::deque<ClientDbId>&);
|
||||
@ -125,9 +124,11 @@ namespace ts::server {
|
||||
std::deque<std::shared_ptr<StartupCacheEntry>> startup_entries;
|
||||
|
||||
sql::SqlManager* sql = nullptr;
|
||||
threads::Mutex permManagerLock;
|
||||
std::deque<CachedPermissionManager*> cachedPermissionManagers;
|
||||
threads::Mutex propsLock;
|
||||
std::deque<CachedProperties*> cachedProperties;
|
||||
|
||||
threads::Mutex cached_permission_manager_lock;
|
||||
std::deque<std::unique_ptr<CachedPermissionManager>> cached_permission_managers;
|
||||
|
||||
/* Attention: cached_permission_manager_lock should be locked! */
|
||||
[[nodiscard]] inline std::shared_ptr<permission::v2::PermissionManager> find_cached_permission_manager(ServerId /* server id */, ClientDbId /* client id */);
|
||||
};
|
||||
}
|
@ -163,6 +163,7 @@ struct SnapshotPermissionEntry {
|
||||
}
|
||||
};
|
||||
|
||||
#if 0
|
||||
std::shared_ptr<VirtualServer> VirtualServerManager::createServerFromSnapshot(shared_ptr<VirtualServer> old, std::string host,
|
||||
uint16_t port, const ts::Command &arguments,
|
||||
std::string &error) {
|
||||
@ -692,6 +693,7 @@ std::shared_ptr<VirtualServer> VirtualServerManager::createServerFromSnapshot(sh
|
||||
server->ensureValidDefaultGroups();
|
||||
return server;
|
||||
}
|
||||
#endif
|
||||
|
||||
struct CommandTuple {
|
||||
Command& cmd;
|
||||
@ -771,8 +773,8 @@ bool VirtualServerManager::createServerSnapshot(Command &cmd, shared_ptr<Virtual
|
||||
|
||||
int index = 0;
|
||||
|
||||
if(version == -1) version = 2; //Auto versioned
|
||||
if(version < 0 || version > 2) {
|
||||
if(version == -1) version = 3; //Auto versioned
|
||||
if(version < 0 || version > 3) {
|
||||
error = "Invalid snapshot version!";
|
||||
return false;
|
||||
}
|
||||
@ -817,44 +819,26 @@ bool VirtualServerManager::createServerSnapshot(Command &cmd, shared_ptr<Virtual
|
||||
//Clients
|
||||
{
|
||||
cmd[index]["begin_clients"] = "";
|
||||
CommandTuple parm{cmd, index, version};
|
||||
auto res = sql::command(server->getSql(), "SELECT `cldbid`, `clientUid`, `firstConnect`, `lastName` FROM `clients` WHERE `serverId` = :sid",
|
||||
variable{":sid", server->getServerId()}
|
||||
).query([&](CommandTuple* commandIndex, int length, char** value, char** name) {
|
||||
ClientDbId cldbid = 0;
|
||||
int64_t clientCreated = 0;
|
||||
string clientUid;
|
||||
string lastName;
|
||||
string description; //TODO description
|
||||
|
||||
for(int idx = 0; idx < length; idx++) {
|
||||
try {
|
||||
if(strcmp(name[idx], "cldbid") == 0)
|
||||
cldbid = value[idx] && strlen(value[idx]) > 0 ? stoul(value[idx]) : 0;
|
||||
else if(strcmp(name[idx], "clientUid") == 0)
|
||||
clientUid = value[idx];
|
||||
else if(strcmp(name[idx], "firstConnect") == 0)
|
||||
clientCreated = value[idx] && strlen(value[idx]) > 0 ? stoll(value[idx]) : 0L;
|
||||
else if(strcmp(name[idx], "lastName") == 0)
|
||||
lastName = value[idx];
|
||||
} catch (std::exception& ex) {
|
||||
logError(0, "Failed to write snapshot client (Skipping it)! Message: {} @ {} => {}", ex.what(), name[idx], value[idx]);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
struct CallbackArgument {
|
||||
Command& command;
|
||||
int& index;
|
||||
int version;
|
||||
};
|
||||
|
||||
CallbackArgument callback_argument{cmd, index, version};
|
||||
this->handle->databaseHelper()->listDatabaseClients(server->getServerId(), std::nullopt, std::nullopt, [](void* ptr_argument, const DatabaseClient& client) {
|
||||
auto argument = (CallbackArgument*) ptr_argument;
|
||||
|
||||
commandIndex->cmd[commandIndex->index]["client_id"] = cldbid;
|
||||
commandIndex->cmd[commandIndex->index]["client_unique_id"] = clientUid;
|
||||
commandIndex->cmd[commandIndex->index]["client_nickname"] = lastName;
|
||||
commandIndex->cmd[commandIndex->index]["client_created"] = clientCreated;
|
||||
commandIndex->cmd[commandIndex->index]["client_description"] = description;
|
||||
if(commandIndex->version == 0)
|
||||
commandIndex->cmd[commandIndex->index]["client_unread_messages"] = 0;
|
||||
commandIndex->index++;
|
||||
return 0;
|
||||
}, &parm);
|
||||
LOG_SQL_CMD(res);
|
||||
argument->command[argument->index]["client_id"] = client.client_database_id;
|
||||
argument->command[argument->index]["client_unique_id"] = client.client_unique_id;
|
||||
argument->command[argument->index]["client_nickname"] = client.client_nickname;
|
||||
argument->command[argument->index]["client_created"] = client.client_created;
|
||||
argument->command[argument->index]["client_description"] = client.client_description;
|
||||
if(argument->version == 0)
|
||||
argument->command[argument->index]["client_unread_messages"] = 0;
|
||||
argument->index++;
|
||||
}, &callback_argument);
|
||||
cmd[index++]["end_clients"] = "";
|
||||
}
|
||||
|
||||
|
@ -110,6 +110,9 @@ bool VirtualServerManager::initialize(bool autostart) {
|
||||
if(id == 0) {
|
||||
logError(LOG_INSTANCE, "Failed to load virtual server from database. Server id is zero!");
|
||||
return 0;
|
||||
} else if(id == 0xFFFF) {
|
||||
/* snapshot server */
|
||||
return 0;
|
||||
}
|
||||
|
||||
if(host.empty()) {
|
||||
@ -341,7 +344,7 @@ shared_ptr<VirtualServer> VirtualServerManager::create_server(std::string hosts,
|
||||
if(!sid_success)
|
||||
return nullptr;
|
||||
|
||||
this->delete_server_in_db(serverId, true); /* just to ensure */
|
||||
this->delete_server_in_db(serverId, false); /* just to ensure */
|
||||
sql::command(this->handle->getSql(), "INSERT INTO `servers` (`serverId`, `host`, `port`) VALUES (:sid, :host, :port)", variable{":sid", serverId}, variable{":host", hosts}, variable{":port", port}).executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"});
|
||||
|
||||
auto prop_copy = sql::command(this->handle->getSql(), "INSERT INTO `properties` (`serverId`, `type`, `id`, `key`, `value`) SELECT :target_sid AS `serverId`, `type`, `id`, `key`, `value` FROM `properties` WHERE `type` = :type AND `id` = 0 AND `serverId` = 0;",
|
||||
@ -419,7 +422,8 @@ bool VirtualServerManager::deleteServer(shared_ptr<VirtualServer> server) {
|
||||
}
|
||||
|
||||
this->handle->properties()[property::SERVERINSTANCE_SPOKEN_TIME_DELETED] += server->properties()[property::VIRTUALSERVER_SPOKEN_TIME].as<uint64_t>();
|
||||
this->delete_server_in_db(server->serverId, true);
|
||||
this->delete_server_in_db(server->serverId, false);
|
||||
this->handle->databaseHelper()->handleServerDelete(server->serverId);
|
||||
|
||||
if(auto file_vs = file::server()->find_virtual_server(server->getServerId()); file_vs) {
|
||||
using ErrorType = file::filesystem::ServerCommandErrorType;
|
||||
@ -517,7 +521,7 @@ if(!result) { \
|
||||
}
|
||||
|
||||
#define execute_change(table, column) \
|
||||
result = sql::command(this->handle->getSql(), "UPDATE TABLE `" table "` SET `" column "` = :nsid WHERE `" column "` = :osid;", \
|
||||
result = sql::command(this->handle->getSql(), "UPDATE `" table "` SET `" column "` = :nsid WHERE `" column "` = :osid;", \
|
||||
variable{":osid", old_id}, variable{":nsid", new_id}).execute(); \
|
||||
if(!result) { \
|
||||
logWarning(LOG_INSTANCE, "Failed to execute server id change on table {} (column {}): {}", table, column, result.fmtStr()); \
|
||||
|
@ -64,12 +64,9 @@ namespace ts::server {
|
||||
void executeAutostart();
|
||||
void shutdownAll(const std::string&);
|
||||
|
||||
//Dotn use shared_ptr references to keep sure that they be hold in memory
|
||||
//Don't use shared_ptr references to keep sure that they be hold in memory
|
||||
bool createServerSnapshot(Command &cmd, std::shared_ptr<VirtualServer> server, int version, std::string &error);
|
||||
std::shared_ptr<VirtualServer> createServerFromSnapshot(std::shared_ptr<VirtualServer> old, std::string, uint16_t, const ts::Command &, std::string &);
|
||||
|
||||
//Dotn use shared_ptr references to keep sure that they be hold in memory
|
||||
SnapshotDeployResult deploy_snapshot(std::string& /* error */, std::shared_ptr<VirtualServer> /* target server */, const command_parser& /* source */);
|
||||
SnapshotDeployResult deploy_snapshot(std::string& /* error */, std::shared_ptr<VirtualServer>& /* target server */, const command_parser& /* source */);
|
||||
|
||||
udp::PuzzleManager* rsaPuzzles() { return this->puzzles; }
|
||||
|
||||
|
@ -529,6 +529,8 @@ int ServerChannelTree::loadChannelFromData(int argc, char **data, char **column)
|
||||
|
||||
//assert(type != 0xFF);
|
||||
assert(channelId != 0);
|
||||
if(channelId == 0)
|
||||
return 0;
|
||||
|
||||
auto channel = std::make_shared<ServerChannel>(parentId, channelId);
|
||||
auto server = this->server.lock();
|
||||
|
@ -798,6 +798,30 @@ bool ConnectedClient::handleCommandFull(Command& cmd, bool disconnectOnFail) {
|
||||
command_result result;
|
||||
try {
|
||||
result.reset(this->handleCommand(cmd));
|
||||
} catch(command_value_cast_failed& ex){
|
||||
auto message = ex.key() + " at " + std::to_string(ex.index()) + " could not be casted to " + ex.target_type().name();
|
||||
if(disconnectOnFail) {
|
||||
this->disconnect(message);
|
||||
return false;
|
||||
} else {
|
||||
result.reset(command_result{error::parameter_convert, message});
|
||||
}
|
||||
} catch(command_bulk_index_out_of_bounds_exception& ex){
|
||||
auto message = "missing bulk for index " + std::to_string(ex.index());
|
||||
if(disconnectOnFail) {
|
||||
this->disconnect(message);
|
||||
return false;
|
||||
} else {
|
||||
result.reset(command_result{error::parameter_invalid_count, message});
|
||||
}
|
||||
} catch(command_value_missing_exception& ex){
|
||||
auto message = "missing value for " + ex.key() + (ex.bulk_index() == std::string::npos ? "" : " at " + std::to_string(ex.bulk_index()));
|
||||
if(disconnectOnFail) {
|
||||
this->disconnect(message);
|
||||
return false;
|
||||
} else {
|
||||
result.reset(command_result{error::parameter_missing, message});
|
||||
}
|
||||
} catch(invalid_argument& ex){
|
||||
logWarning(this->getServerId(), "{}[Command] Failed to handle command. Received invalid argument exception: {}", CLIENT_STR_LOG_PREFIX, ex.what());
|
||||
if(disconnectOnFail) {
|
||||
|
@ -647,7 +647,7 @@ bool ConnectedClient::handle_text_command(
|
||||
//send_message(_this.lock(), " IN " + type.name() + " => generation: " + to_string(genestis[type.type()].generation()) + " id: " + to_string(genestis[type.type()].current_packet_id()));
|
||||
}
|
||||
return true;
|
||||
} else if(TARG(0, "ping")) {
|
||||
} else if(TARG(0, "rtt")) {
|
||||
auto vc = dynamic_pointer_cast<VoiceClient>(_this.lock());
|
||||
if(!vc) return false;
|
||||
|
||||
@ -681,6 +681,16 @@ bool ConnectedClient::handle_text_command(
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
} else if(TARG(0, "ping")) {
|
||||
auto vc = dynamic_pointer_cast<VoiceClient>(_this.lock());
|
||||
if(!vc) return false;
|
||||
|
||||
auto ping_handler = vc->getConnection()->ping_handler();
|
||||
send_message(this->ref(), "Ping: " + std::to_string(ping_handler.current_ping().count()));
|
||||
|
||||
auto last_response_ms = std::chrono::ceil<std::chrono::milliseconds>(std::chrono::system_clock::now() - ping_handler.last_ping_response()).count();
|
||||
send_message(this->ref(), "Last ping response: " + std::to_string(last_response_ms));
|
||||
return true;
|
||||
} else if(TARG(0, "disconnect")) {
|
||||
auto vc = dynamic_pointer_cast<VoiceClient>(_this.lock());
|
||||
if(!vc) return false;
|
||||
|
@ -35,7 +35,7 @@ command_result SpeakingClient::handleCommandHandshakeBegin(Command& cmd) { //If
|
||||
if(authenticationMethod == IdentityType::TEAMSPEAK) {
|
||||
this->handshake.identityType = IdentityType::TEAMSPEAK;
|
||||
|
||||
auto identity = base64::decode(cmd["publicKey"]);
|
||||
auto identity = base64::decode(cmd["publicKey"].string());
|
||||
this->properties()[property::CLIENT_UNIQUE_IDENTIFIER] = base64::encode(digest::sha1(cmd["publicKey"].string()));
|
||||
|
||||
this->handshake.identityKey = shared_ptr<ecc_key>(new ecc_key{}, free_ecc);
|
||||
@ -144,7 +144,7 @@ command_result SpeakingClient::handleCommandHandshakeIdentityProof(Command& cmd)
|
||||
this->properties()[property::CLIENT_TEAFORO_NAME] = (*this->handshake.identityData)["user_name"].asString();
|
||||
this->handshake.state = HandshakeState::SUCCEEDED;
|
||||
} else if(this->handshake.identityType == IdentityType::TEAMSPEAK) {
|
||||
auto proof = base64::decode(cmd["proof"]);
|
||||
auto proof = base64::decode(cmd["proof"].string());
|
||||
|
||||
int result;
|
||||
if(ecc_verify_hash((u_char*) proof.data(), proof.length(), (u_char*) this->handshake.proof_message.data(), this->handshake.proof_message.length(), &result, this->handshake.identityKey.get()) != CRYPT_OK)
|
||||
|
@ -367,23 +367,6 @@ command_result ConnectedClient::handleCommandClientChatClosed(Command &cmd) {
|
||||
return command_result{error::ok};
|
||||
}
|
||||
|
||||
constexpr static auto kDBListQuery{R"(
|
||||
SELECT `clients`.*, `properties`.`value` as `client_description` FROM (
|
||||
SELECT
|
||||
`clients_server`.`client_database_id`,
|
||||
`clients_server`.`client_unique_id`,
|
||||
`clients_server`.`client_nickname`,
|
||||
`clients_server`.`client_ip`,
|
||||
`clients_server`.`client_created`,
|
||||
`clients_server`.`client_last_connected`,
|
||||
`clients_server`.`client_total_connections`,
|
||||
`clients`.`client_login_name` FROM `clients_server`
|
||||
INNER JOIN `clients` ON `clients`.`client_database_id` = `clients_server`.`client_database_id`
|
||||
WHERE `server_id` = :serverId LIMIT :offset, :limit
|
||||
) AS `clients`
|
||||
LEFT JOIN `properties` ON `properties`.serverId = :serverId AND `properties`.key = 'client_description' AND `properties`.`id` = `clients`.`client_database_id`
|
||||
)"};
|
||||
|
||||
command_result ConnectedClient::handleCommandClientDbList(Command &cmd) {
|
||||
CMD_REQ_SERVER;
|
||||
CMD_RESET_IDLE;
|
||||
@ -398,59 +381,32 @@ command_result ConnectedClient::handleCommandClientDbList(Command &cmd) {
|
||||
ts::command_builder result{this->notify_response_command("notifyclientdblist")};
|
||||
result.reserve_bulks(limit);
|
||||
|
||||
size_t command_index{0}, set_index{0};
|
||||
struct CallbackArgument {
|
||||
ts::command_builder& result;
|
||||
bool show_ip{false};
|
||||
size_t command_index{0};
|
||||
};
|
||||
|
||||
auto show_ip = permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_remoteaddress_view, 0));
|
||||
CallbackArgument callback_argument{result};
|
||||
callback_argument.show_ip = permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_remoteaddress_view, 0));
|
||||
serverInstance->databaseHelper()->listDatabaseClients(this->getServerId(), { offset }, { limit }, [](void* ptr_data, const DatabaseClient& client) {
|
||||
auto argument = (CallbackArgument*) ptr_data;
|
||||
|
||||
auto sqlResult = sql::command{this->server->getSql(), kDBListQuery, variable{":serverId", this->getServerId()}, variable{":offset", offset}, variable{":limit", limit}}
|
||||
.query([&](int length, std::string* values, std::string* names) {
|
||||
set_index++;
|
||||
auto bulk = result.bulk(command_index++);
|
||||
bulk.reserve(300);
|
||||
auto bulk = argument->result.bulk(argument->command_index++);
|
||||
bulk.reserve(300);
|
||||
|
||||
auto index{0};
|
||||
try {
|
||||
assert(names[index] == "client_database_id");
|
||||
bulk.put_unchecked("cldbid", values[index++]);
|
||||
bulk.put_unchecked("cldbid", client.client_database_id);
|
||||
bulk.put_unchecked("client_unique_identifier", client.client_unique_id);
|
||||
bulk.put_unchecked("client_nickname", client.client_nickname);
|
||||
bulk.put_unchecked("client_lastip", argument->show_ip ? client.client_ip : "hidden");
|
||||
bulk.put_unchecked("client_lastconnected", client.client_last_connected);
|
||||
bulk.put_unchecked("client_created", client.client_created);
|
||||
bulk.put_unchecked("client_totalconnections", client.client_total_connections);
|
||||
bulk.put_unchecked("client_login_name", client.client_login_name);
|
||||
bulk.put_unchecked("client_description", client.client_description);
|
||||
}, &callback_argument);
|
||||
|
||||
assert(names[index] == "client_unique_id");
|
||||
bulk.put_unchecked("client_unique_identifier", values[index++]);
|
||||
|
||||
assert(names[index] == "client_nickname");
|
||||
bulk.put_unchecked("client_nickname", values[index++]);
|
||||
|
||||
assert(names[index] == "client_ip");
|
||||
bulk.put_unchecked("client_lastip", show_ip ? values[index++] : "hidden");
|
||||
|
||||
assert(names[index] == "client_created");
|
||||
bulk.put_unchecked("client_lastconnected", values[index++]);
|
||||
|
||||
assert(names[index] == "client_last_connected");
|
||||
bulk.put_unchecked("client_created", values[index++]);
|
||||
|
||||
assert(names[index] == "client_total_connections");
|
||||
bulk.put_unchecked("client_totalconnections", values[index++]);
|
||||
|
||||
assert(names[index] == "client_login_name");
|
||||
bulk.put_unchecked("client_login_name", values[index++]);
|
||||
|
||||
assert(names[index] == "client_description");
|
||||
bulk.put_unchecked("client_description", values[index++]);
|
||||
|
||||
assert(index == length);
|
||||
} catch (std::exception& ex) {
|
||||
command_index--;
|
||||
logError(this->getServerId(), "Failed to parse client base properties at index {}: {}. Offset: {} Limit: {} Set: {}",
|
||||
index - 1,
|
||||
ex.what(),
|
||||
offset,
|
||||
limit,
|
||||
set_index - 1
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
if (command_index == 0)
|
||||
if (callback_argument.command_index == 0)
|
||||
return command_result{error::database_empty_result};
|
||||
|
||||
if (cmd.hasParm("count")) {
|
||||
|
@ -1,6 +1,7 @@
|
||||
#include "Properties.h"
|
||||
#include "query/Command.h"
|
||||
#include <algorithm>
|
||||
#include <zstd.h>
|
||||
#include <src/server/QueryServer.h>
|
||||
#include <src/VirtualServerManager.h>
|
||||
#include <src/InstanceHandler.h>
|
||||
@ -834,6 +835,7 @@ command_result QueryClient::handleCommandBindingList(Command& cmd) {
|
||||
|
||||
//TODO with mapping!
|
||||
command_result QueryClient::handleCommandServerSnapshotDeploy(Command& cmd) {
|
||||
#if 0
|
||||
CMD_RESET_IDLE;
|
||||
|
||||
auto start = system_clock::now();
|
||||
@ -903,20 +905,23 @@ command_result QueryClient::handleCommandServerSnapshotDeploy(Command& cmd) {
|
||||
res["time"] = duration_cast<milliseconds>(end - start).count();
|
||||
this->sendCommand(res);
|
||||
return command_result{error::ok};
|
||||
#else
|
||||
return command_result{error::command_not_found};
|
||||
#endif
|
||||
}
|
||||
|
||||
command_result QueryClient::handleCommandServerSnapshotDeployNew(const ts::command_parser &command) {
|
||||
CMD_RESET_IDLE;
|
||||
|
||||
if(this->server) {
|
||||
return command_result{error::not_implemented};
|
||||
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_snapshot_deploy, 1);
|
||||
} else {
|
||||
ACTION_REQUIRES_INSTANCE_PERMISSION(permission::b_virtualserver_snapshot_deploy, 1);
|
||||
}
|
||||
|
||||
std::string error{};
|
||||
auto result = serverInstance->getVoiceServerManager()->deploy_snapshot(error, this->server, command);
|
||||
auto server = this->server;
|
||||
auto result = serverInstance->getVoiceServerManager()->deploy_snapshot(error, server, command);
|
||||
|
||||
using SnapshotDeployResult = VirtualServerManager::SnapshotDeployResult;
|
||||
switch (result) {
|
||||
@ -935,6 +940,12 @@ command_result QueryClient::handleCommandServerSnapshotDeployNew(const ts::comma
|
||||
case SnapshotDeployResult::CUSTOM_ERROR:
|
||||
return command_result{error::vs_critical, error};
|
||||
}
|
||||
|
||||
ts::command_builder notify{""};
|
||||
notify.put_unchecked(0, "virtualserver_port", server->properties()[property::VIRTUALSERVER_PORT].value());
|
||||
notify.put_unchecked(0, "sid", server->getServerId());
|
||||
this->sendCommand(notify, false);
|
||||
|
||||
return command_result{error::ok};
|
||||
}
|
||||
|
||||
@ -943,22 +954,50 @@ command_result QueryClient::handleCommandServerSnapshotCreate(Command& cmd) {
|
||||
CMD_RESET_IDLE;
|
||||
CMD_REQ_SERVER;
|
||||
|
||||
Command result("");
|
||||
Command snapshot_command("");
|
||||
string error;
|
||||
|
||||
int version = cmd[0].has("version") ? cmd[0]["version"] : -1;
|
||||
if(version == -1 && (cmd.hasParm("lagacy") || cmd.hasParm("legacy")))
|
||||
if(version == -1 && (cmd.hasParm("lagacy") || cmd.hasParm("legacy"))) {
|
||||
version = 0;
|
||||
if(!serverInstance->getVoiceServerManager()->createServerSnapshot(result, this->server, version, error))
|
||||
}
|
||||
|
||||
if(!serverInstance->getVoiceServerManager()->createServerSnapshot(snapshot_command, this->server, version, error)) {
|
||||
return command_result{error::vs_critical, error};
|
||||
}
|
||||
|
||||
string data = result.build();
|
||||
auto buildHash = base64::encode(digest::sha1(data));
|
||||
if(version == -1 || version >= 3) {
|
||||
auto build_version = snapshot_command[0]["snapshot_version"].as<int>();
|
||||
snapshot_command.pop_bulk();
|
||||
|
||||
result.push_bulk_front();
|
||||
result[0]["hash"] = buildHash;
|
||||
auto snapshot_data = snapshot_command.build();
|
||||
auto max_compressed_size = ZSTD_compressBound(snapshot_data.size());
|
||||
if(ZSTD_isError(max_compressed_size)) {
|
||||
return command_result{error::vs_critical, "failed to calculate compressed size: " + std::string{ZSTD_getErrorName(max_compressed_size)}};
|
||||
}
|
||||
|
||||
this->sendCommand(result);
|
||||
std::string buffer{};
|
||||
buffer.resize(max_compressed_size);
|
||||
|
||||
auto compressed_size = ZSTD_compress(buffer.data(), buffer.size(), snapshot_data.data(), snapshot_data.length(), 1);
|
||||
if(ZSTD_isError(compressed_size)) {
|
||||
return command_result{error::vs_critical, "failed to compressed snapshot: " + std::string{ZSTD_getErrorName(compressed_size)}};
|
||||
}
|
||||
|
||||
ts::command_builder result{""};
|
||||
result.bulk(0).reserve(100 + compressed_size * 4/3);
|
||||
result.put_unchecked(0, "snapshot_version", build_version);
|
||||
result.put_unchecked(0, "data", base64::encode(std::string_view{buffer.data(), compressed_size}));
|
||||
this->sendCommand(result, false);
|
||||
} else {
|
||||
auto data = snapshot_command.build();
|
||||
auto buildHash = base64::encode(digest::sha1(data));
|
||||
|
||||
snapshot_command.push_bulk_front();
|
||||
snapshot_command[0]["hash"] = buildHash;
|
||||
|
||||
this->sendCommand(snapshot_command);
|
||||
}
|
||||
return command_result{error::ok};
|
||||
}
|
||||
|
||||
|
@ -71,7 +71,7 @@ CryptSetupHandler::CommandResult CryptSetupHandler::handleCommandClientInitIv(co
|
||||
|
||||
std::unique_lock state_lock{client->state_lock};
|
||||
if(client->connectionState() == ConnectionState::CONNECTED) { /* we've a reconnect */
|
||||
auto lastPingResponse = this->connection->ping_handler().last_ping_response();
|
||||
auto lastPingResponse = std::max(this->connection->ping_handler().last_ping_response(), this->connection->ping_handler().last_command_acknowledged());
|
||||
if(std::chrono::system_clock::now() - lastPingResponse < std::chrono::seconds(5)) {
|
||||
logMessage(this->connection->virtual_server_id(), "{} Client initialized session reconnect, but last ping response is not older then 5 seconds ({}). Ignoring attempt",
|
||||
this->connection->log_prefix(),
|
||||
@ -162,6 +162,11 @@ CryptSetupHandler::CommandResult CryptSetupHandler::handleCommandClientInitIv(co
|
||||
this->seed_server = std::string{beta, server_seed_length};
|
||||
}
|
||||
|
||||
auto server_public_key = client->getServer()->publicServerKey();
|
||||
if(server_public_key.empty()) {
|
||||
return ts::command_result{error::vs_critical, "failed to export server public key"};
|
||||
}
|
||||
|
||||
if(this->new_protocol) {
|
||||
//Pre setup
|
||||
//Generate chain
|
||||
@ -193,7 +198,7 @@ CryptSetupHandler::CommandResult CryptSetupHandler::handleCommandClientInitIv(co
|
||||
answer.put_unchecked(0, "time", std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch()).count());
|
||||
answer.put_unchecked(0, "l", base64::encode(crypto_chain));
|
||||
answer.put_unchecked(0, "beta", base64::encode(this->seed_server));
|
||||
answer.put_unchecked(0, "omega", client->getServer()->publicServerKey());
|
||||
answer.put_unchecked(0, "omega", server_public_key);
|
||||
answer.put_unchecked(0, "proof", base64::encode((const char*) sign_buffer, sign_buffer_size));
|
||||
answer.put_unchecked(0, "tvd", "");
|
||||
answer.put_unchecked(0, "root", base64::encode((char*) this->chain_data->public_key, 32));
|
||||
@ -206,11 +211,6 @@ CryptSetupHandler::CommandResult CryptSetupHandler::handleCommandClientInitIv(co
|
||||
} else {
|
||||
debugMessage(this->connection->virtual_server_id(), "{} Got non client 3.1 protocol with build timestamp {}", this->connection->log_prefix(), this->client_protocol_time_, this->client_protocol_time_);
|
||||
|
||||
auto server_public_key = client->getServer()->publicServerKey();
|
||||
if(server_public_key.empty()) {
|
||||
return ts::command_result{error::vs_critical, "failed to export server public key"};
|
||||
}
|
||||
|
||||
{
|
||||
ts::command_builder answer{"initivexpand"};
|
||||
answer.put_unchecked(0, "alpha", base64::encode(this->seed_client));
|
||||
|
@ -4,12 +4,9 @@
|
||||
#include "PacketEncoder.h"
|
||||
|
||||
#include <protocol/buffers.h>
|
||||
#include <protocol/AcknowledgeManager.h>
|
||||
#include <protocol/CompressionHandler.h>
|
||||
#include <protocol/CryptHandler.cpp>
|
||||
|
||||
#include "../../ConnectionStatistics.h"
|
||||
|
||||
using namespace ts;
|
||||
using namespace ts::server::server::udp;
|
||||
|
||||
@ -164,34 +161,40 @@ void PacketEncoder::send_command(const std::string_view &command, bool low, std:
|
||||
packets_tail = &packet->next;
|
||||
}
|
||||
|
||||
|
||||
{
|
||||
std::lock_guard id_lock{this->packet_id_mutex};
|
||||
uint32_t full_id;
|
||||
auto head = packets_head;
|
||||
while(head) {
|
||||
full_id = this->packet_id_manager.generate_full_id(ptype);
|
||||
{
|
||||
uint32_t full_id;
|
||||
auto head = packets_head;
|
||||
while(head) {
|
||||
full_id = this->packet_id_manager.generate_full_id(ptype);
|
||||
|
||||
head->set_packet_id(full_id & 0xFFFFU);
|
||||
head->generation = full_id >> 16U;
|
||||
head = head->next;
|
||||
head->set_packet_id(full_id & 0xFFFFU);
|
||||
head->generation = full_id >> 16U;
|
||||
|
||||
/* loss stats (In order required so we're using the this->packet_id_mutex) */
|
||||
this->packet_statistics_->send_command(head->packet_type(), full_id);
|
||||
|
||||
head = head->next;
|
||||
}
|
||||
}
|
||||
}
|
||||
packets_head->type_and_flags |= head_pflags;
|
||||
|
||||
/* general stats */
|
||||
auto head = packets_head;
|
||||
while(head) {
|
||||
this->callback_connection_stats(this->callback_data, StatisticsCategory::COMMAND, head->packet_length() + 96); /* 96 for the UDP overhead */
|
||||
head = head->next;
|
||||
{
|
||||
auto head = packets_head;
|
||||
while(head) {
|
||||
this->callback_connection_stats(this->callback_data, StatisticsCategory::COMMAND, head->packet_length() + 96); /* 96 for the UDP overhead */
|
||||
head = head->next;
|
||||
}
|
||||
}
|
||||
|
||||
/* loss stats */
|
||||
/* ack handler */
|
||||
{
|
||||
auto head = packets_head;
|
||||
while(head) {
|
||||
auto full_packet_id = (uint32_t) (head->generation << 16U) | head->packet_id();
|
||||
this->packet_statistics_->send_command(head->packet_type(), full_packet_id);
|
||||
|
||||
/* increase a reference for the ack handler */
|
||||
head->ref();
|
||||
@ -230,9 +233,18 @@ void PacketEncoder::encrypt_pending_packets() {
|
||||
return;
|
||||
|
||||
auto packet = packets_head;
|
||||
auto packet_tail = packet;
|
||||
while(packet) {
|
||||
this->prepare_outgoing_packet(packet);
|
||||
packet = packet->next;
|
||||
if(packet)
|
||||
packet_tail = packet;
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard wlock{this->write_queue_mutex};
|
||||
*this->resend_queue_tail = packets_head;
|
||||
this->resend_queue_tail = &packet_tail->next;
|
||||
}
|
||||
}
|
||||
|
||||
@ -267,7 +279,7 @@ bool PacketEncoder::prepare_outgoing_packet(ts::protocol::OutgoingServerPacket *
|
||||
}
|
||||
|
||||
PacketEncoder::BufferPopResult PacketEncoder::pop_write_buffer(protocol::OutgoingServerPacket *&result) {
|
||||
bool need_prepare_packet{false}, more_packets{false};
|
||||
bool need_prepare_packet{false}, more_packets;
|
||||
{
|
||||
std::lock_guard wlock{this->write_queue_mutex};
|
||||
if(this->resend_queue_head) {
|
||||
|
@ -18,13 +18,14 @@ namespace ts::server::server::udp {
|
||||
|
||||
[[nodiscard]] inline std::chrono::milliseconds current_ping() const { return this->current_ping_; }
|
||||
[[nodiscard]] inline std::chrono::system_clock::time_point last_ping_response() const { return this->last_response_; }
|
||||
[[nodiscard]] inline std::chrono::system_clock::time_point last_command_acknowledged() const { return this->last_command_acknowledge_; }
|
||||
|
||||
void* callback_argument{nullptr};
|
||||
callback_send_ping_t callback_send_ping{nullptr};
|
||||
callback_send_recovery_command_t callback_send_recovery_command{nullptr};
|
||||
callback_time_outed_t callback_time_outed{nullptr};
|
||||
private:
|
||||
constexpr static std::chrono::milliseconds kPingRequestInterval{2500};
|
||||
constexpr static std::chrono::milliseconds kPingRequestInterval{1000};
|
||||
constexpr static std::chrono::milliseconds kPingTimeout{15 * 1000};
|
||||
|
||||
constexpr static std::chrono::milliseconds kRecoveryRequestInterval{1000};
|
||||
|
@ -170,11 +170,18 @@ bool SqlDataManager::initialize(std::string& error) {
|
||||
} else {
|
||||
res = sql::command(this->sql(), "UPDATE `general` SET `value`= :value WHERE `key`= :key;", variable{":key", "lock_test"}, variable{":value", "TeaSpeak created by WolverinDEV <3"}).execute();
|
||||
}
|
||||
|
||||
if(!res) {
|
||||
if(res.msg().find("database is locked") != string::npos) error = "database is locked";
|
||||
else error = "Failed to execute lock test! Command result: " + res.fmtStr();
|
||||
return false;
|
||||
}
|
||||
|
||||
res = sql::command{this->sql(), "DELETE FROM `general` WHERE `key` = :key ORDER BY `key` LIMIT -1 OFFSET 1;", variable{":key", "lock_test"}}.execute();
|
||||
if(!res) {
|
||||
error = res.fmtStr();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if(!this->update_permissions(error)) {
|
||||
@ -500,16 +507,17 @@ bool SqlDataManager::update_database(std::string &error) {
|
||||
}
|
||||
|
||||
case 13: {
|
||||
constexpr static std::array<std::string_view, 7> kUpdateCommands{
|
||||
constexpr static std::array<std::string_view, 8> kUpdateCommands{
|
||||
"CREATE INDEX `idx_properties_serverid_id_value` ON `properties` (`serverId`, `id`, `key`);",
|
||||
"CREATE INDEX `idx_properties_serverid_id_type` ON `properties` (`serverId`, `id`, `type`);",
|
||||
|
||||
"CREATE TABLE `groups_v2` (`serverId` INT NOT NULL, `groupId` INTEGER AUTO_INCREMENT NOT NULL PRIMARY KEY, `target` INT, `type` INT, `displayName` VARCHAR(128), `original_group_id` INTEGER);",
|
||||
"CREATE TABLE `groups_v2` (`serverId` INT NOT NULL, `groupId` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, `target` INT, `type` INT, `displayName` VARCHAR(128), `original_group_id` INTEGER);",
|
||||
"INSERT INTO `groups_v2` (`serverId`, `groupId`, `target`, `type`, `displayName`) SELECT `serverId`, `groupId`, `target`, `type`, `displayName` FROM `groups`;",
|
||||
"DROP TABLE `groups`;",
|
||||
"ALTER TABLE `groups_v2` RENAME TO groups;",
|
||||
|
||||
"CREATE INDEX `idx_groups_serverId_groupId` ON `groups` (`server_id`);",
|
||||
"CREATE INDEX `idx_groups_serverid` ON `groups` (`server_id`, `groupId`);"
|
||||
"CREATE INDEX `idx_groups_serverId_groupId` ON `groups` (`serverId`);",
|
||||
"CREATE INDEX `idx_groups_serverid` ON `groups` (`serverId`, `groupId`);"
|
||||
};
|
||||
|
||||
if(!execute_commands(this->sql(), error, kUpdateCommands))
|
||||
|
@ -30,7 +30,7 @@ bool TokenManager::loadTokens() {
|
||||
}
|
||||
|
||||
int TokenManager::loadTokenFromDb(int length, char **values, char **columns) {
|
||||
std::string token, description = "";
|
||||
std::string token, description;
|
||||
auto type = static_cast<TokenType>(0xFF);
|
||||
GroupId group = 0;
|
||||
ChannelId chId = 0;
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -43,21 +43,28 @@ bool relation_parser::parse(std::string &error, group_relations &result, size_t
|
||||
|
||||
bool key_found;
|
||||
while(offset < *relation_end) {
|
||||
ChannelId channel_id;
|
||||
auto begin_bulk = this->command.bulk(offset);
|
||||
if(!begin_bulk.has_key("iid")) {
|
||||
error = "missing iid at character " + std::to_string(begin_bulk.command_character_index());
|
||||
return false;
|
||||
if(begin_bulk.has_key("iid")) {
|
||||
channel_id = begin_bulk.value_as<ChannelId>("iid");
|
||||
} else {
|
||||
/* may be the case for the global assignments (iid may be not send in the first few generations) */
|
||||
channel_id = 0;
|
||||
}
|
||||
|
||||
auto& relations = result[begin_bulk.value_as<ChannelId>("iid")];
|
||||
auto& relations = result[channel_id];
|
||||
auto next_iid = this->command.next_bulk_containing("iid", offset + 1);
|
||||
|
||||
if(next_iid.has_value() && *next_iid < relation_end)
|
||||
size_t end_index;
|
||||
if(next_iid.has_value() && *next_iid < relation_end) {
|
||||
end_index = *next_iid;
|
||||
relations.reserve(*next_iid - offset);
|
||||
else
|
||||
} else {
|
||||
end_index = *relation_end;
|
||||
relations.reserve(*relation_end - offset);
|
||||
}
|
||||
|
||||
while(offset < *next_iid) {
|
||||
while(offset < end_index) {
|
||||
auto relation_data = this->command.bulk(offset++);
|
||||
auto& relation = relations.emplace_back();
|
||||
|
||||
|
@ -9,6 +9,8 @@ using namespace ts::server::snapshots;
|
||||
bool music_bot_parser::parse(std::string &error, music_bot_entry &entry, size_t &index) {
|
||||
auto data = this->command[index];
|
||||
|
||||
entry.properties.register_property_type<property::ClientProperties>();
|
||||
|
||||
size_t entry_index{0};
|
||||
std::string_view key{};
|
||||
std::string value{};
|
||||
@ -23,7 +25,12 @@ bool music_bot_parser::parse(std::string &error, music_bot_entry &entry, size_t
|
||||
} else if(key == "bot_unique_id") {
|
||||
entry.unique_id = value;
|
||||
} else if(key == "bot_owner_id") {
|
||||
entry.bot_owner_id = value;
|
||||
char* end_ptr{nullptr};
|
||||
entry.bot_owner_id = strtoull(value.c_str(), &end_ptr, 10);
|
||||
if (*end_ptr) {
|
||||
error = "failed to parse bot owner database id at character " + std::to_string(data.key_command_character_index(key) + key.length());
|
||||
return false;
|
||||
}
|
||||
} else if(key == "end_bots") {
|
||||
continue;
|
||||
} else {
|
||||
@ -52,6 +59,8 @@ bool music_bot_parser::parse(std::string &error, music_bot_entry &entry, size_t
|
||||
bool playlist_parser::parse(std::string &error, playlist_entry &entry, size_t &index) {
|
||||
auto data = this->command[index];
|
||||
|
||||
entry.properties.register_property_type<property::PlaylistProperties>();
|
||||
|
||||
size_t entry_index{0};
|
||||
std::string_view key{};
|
||||
std::string value{};
|
||||
@ -82,7 +91,7 @@ bool playlist_parser::parse(std::string &error, playlist_entry &entry, size_t &i
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool playlist_song_parser::parse(std::string &error, playlist_song_entry &entry, size_t &index) {
|
||||
|
@ -11,7 +11,7 @@ namespace ts::server::snapshots {
|
||||
ClientDbId database_id{0};
|
||||
|
||||
std::string unique_id{};
|
||||
std::string bot_owner_id{};
|
||||
ClientDbId bot_owner_id{};
|
||||
|
||||
Properties properties{};
|
||||
};
|
||||
|
@ -4,6 +4,10 @@
|
||||
|
||||
#include <Definitions.h>
|
||||
#include <log/LogUtils.h>
|
||||
#include <misc/base64.h>
|
||||
#include <misc/digest.h>
|
||||
|
||||
#include <zstd.h>
|
||||
|
||||
#include "./snapshot.h"
|
||||
#include "./server.h"
|
||||
@ -13,12 +17,33 @@
|
||||
#include "./groups.h"
|
||||
#include "./snapshot_data.h"
|
||||
|
||||
using namespace ts;
|
||||
using namespace ts::server;
|
||||
|
||||
inline bool verify_hash(std::string &error, ServerId server_id, const command_parser &data, size_t hash_offset, const std::string_view& expected_hash) {
|
||||
if(!expected_hash.empty() && !data.has_switch("ignorehash")) {
|
||||
auto payload = data.payload_view(hash_offset);
|
||||
if(auto switches{payload.find(" -")}; switches != std::string::npos) {
|
||||
debugMessage(server_id, "Truncating the snapshot payload after index {} (First switch).", switches);
|
||||
payload = payload.substr(0, switches);
|
||||
}
|
||||
|
||||
auto calculated_hash = base64::encode(digest::sha1(payload));
|
||||
|
||||
/* debugMessage(server_id, "SHA1 for playload: {}", payload); */
|
||||
debugMessage(server_id, "Expected hash: {}, Calculated hash: {}", expected_hash, calculated_hash);
|
||||
if(expected_hash != calculated_hash) {
|
||||
error = "invalid snapshot hash.\nExpected: " + std::string{expected_hash} + "\nCalculated: " + calculated_hash;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool snapshots::parse_snapshot(snapshot_data &result, std::string &error, ServerId server_id, const command_parser &data) {
|
||||
if(data.bulk(0).has_key("version")) {
|
||||
return snapshots::parse_snapshot_ts3(result, error, server_id, data);
|
||||
} else if(data.bulk(1).has_key("snapshot_version")) {
|
||||
} else if(data.bulk(0).has_key("snapshot_version") || (data.bulk_count() > 1 && data.bulk(1).has_key("snapshot_version"))) {
|
||||
/* teaspeak snapshot */
|
||||
return snapshots::parse_snapshot_teaspeak(result, error, server_id, data);
|
||||
} else {
|
||||
@ -27,24 +52,83 @@ bool snapshots::parse_snapshot(snapshot_data &result, std::string &error, Server
|
||||
}
|
||||
}
|
||||
|
||||
bool snapshots::parse_snapshot_teaspeak(snapshot_data &result, std::string &error, ts::ServerId server_id, const ts::command_parser &data) {
|
||||
if(!data.bulk(1).has_key("snapshot_version")) {
|
||||
error = "Missing snapshot version";
|
||||
return false;
|
||||
inline std::unique_ptr<char, void(*)(void*)> decompress_snapshot(std::string& error, size_t& decompressed_length, const std::string& data) {
|
||||
std::unique_ptr<char, void(*)(void*)> result{nullptr, ::free};
|
||||
|
||||
auto uncompressed_length = ZSTD_getFrameContentSize(data.data(), data.length());
|
||||
if(uncompressed_length == ZSTD_CONTENTSIZE_UNKNOWN) {
|
||||
error = "unknown uncompressed size";
|
||||
return result;
|
||||
} else if(uncompressed_length == ZSTD_CONTENTSIZE_ERROR) {
|
||||
error = "failed to calculate uncompressed size";
|
||||
return result;
|
||||
} else if(ZSTD_isError(uncompressed_length)) {
|
||||
error = "failed to calculate uncompressed size: " + std::string{ZSTD_getErrorName(uncompressed_length)};
|
||||
return result;
|
||||
} else if(uncompressed_length > 512 * 1024 * 1028) {
|
||||
error = "uncompressed size exceeds half a gigabyte!";
|
||||
return result;
|
||||
}
|
||||
|
||||
result.reset((char*) ::malloc(uncompressed_length));
|
||||
if(!result) {
|
||||
error = "failed to allocate decompress buffer";
|
||||
return result;
|
||||
}
|
||||
|
||||
decompressed_length = ZSTD_decompress(&*result, uncompressed_length, data.data(), data.length());
|
||||
if(ZSTD_isError(decompressed_length)) {
|
||||
error = "decompress error occurred: " + std::string{ZSTD_getErrorName(decompressed_length)};
|
||||
result = nullptr;
|
||||
return result;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool snapshots::parse_snapshot_teaspeak(snapshot_data &result, std::string &error, ts::ServerId server_id, const ts::command_parser &data) {
|
||||
snapshots::version_t version;
|
||||
if(data.bulk(0).has_key("snapshot_version")) {
|
||||
version = data.bulk(0).value_as<snapshots::version_t>("snapshot_version");
|
||||
} else {
|
||||
version = data.bulk(1).value_as<snapshots::version_t>("snapshot_version");
|
||||
}
|
||||
auto version = data.bulk(1).value_as<snapshots::version_t>("snapshot_version");
|
||||
auto hash = data.bulk(0).value("hash");
|
||||
|
||||
if(version < 1) {
|
||||
error = "snapshot version too old";
|
||||
return false;
|
||||
} else if(version == 2) {
|
||||
auto hash = data.bulk(0).value("hash");
|
||||
|
||||
if(!verify_hash(error, server_id, data, 1, hash))
|
||||
return false;
|
||||
|
||||
/* the actual snapshot begins at index 2 */
|
||||
return snapshots::parse_snapshot_raw(result, error, server_id, data, 2, type::TEASPEAK, version);
|
||||
} else if(version > 2) {
|
||||
auto compressed_data{base64::decode(data.value("data"))};
|
||||
|
||||
size_t decompressed_length;
|
||||
auto decompressed = decompress_snapshot(error, decompressed_length, compressed_data);
|
||||
if(!decompressed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string_view decompressed_data{&*decompressed, decompressed_length};
|
||||
ts::command_parser decompressed_parser{decompressed_data};
|
||||
if(!decompressed_parser.parse(false)) {
|
||||
error = "failed to parse snapshot payload";
|
||||
return false;
|
||||
}
|
||||
|
||||
return snapshots::parse_snapshot_raw(result, error, server_id, decompressed_parser, 0, type::TEASPEAK, version);
|
||||
} else if(version > 3) {
|
||||
error = "snapshot version is too new";
|
||||
return false;
|
||||
} else {
|
||||
error = "invalid snapshot version";
|
||||
return false;
|
||||
}
|
||||
|
||||
/* the actual snapshot begins at index 2 */
|
||||
return snapshots::parse_snapshot_raw(result, error, server_id, data, hash, 2, type::TEASPEAK, version);
|
||||
}
|
||||
|
||||
bool snapshots::parse_snapshot_ts3(snapshot_data &result, std::string &error, ts::ServerId server_id, const ts::command_parser &data) {
|
||||
@ -52,24 +136,42 @@ bool snapshots::parse_snapshot_ts3(snapshot_data &result, std::string &error, ts
|
||||
if(data.bulk(0).has_key("version"))
|
||||
version = data.bulk(0).value_as<snapshots::version_t>("version");
|
||||
|
||||
auto hash = data.bulk(0).value("hash");
|
||||
if(data.bulk(0).has_key("salt")) {
|
||||
error = "TeaSpeak dosn't support encrypted snapshots yet";
|
||||
return false;
|
||||
}
|
||||
|
||||
if(version == 0) {
|
||||
return snapshots::parse_snapshot_raw(result, error, server_id, data, hash, 1, type::TEAMSPEAK, version);
|
||||
auto hash = data.bulk(0).value("hash");
|
||||
if(!verify_hash(error, server_id, data, 1, hash))
|
||||
return false;
|
||||
|
||||
return snapshots::parse_snapshot_raw(result, error, server_id, data, 1, type::TEAMSPEAK, version);
|
||||
} else if(version == 1) {
|
||||
error = "version 1 is an invalid version";
|
||||
return false;
|
||||
} else if(version == 2) {
|
||||
/* compressed data */
|
||||
error = "version 2 isn't currently supported";
|
||||
return false;
|
||||
} else if(version == 3) {
|
||||
error = "version 3 isn't currently supported";
|
||||
return false;
|
||||
} else if(version >= 2 && version <= 3) {
|
||||
std::string compressed_data;
|
||||
if(version == 2) {
|
||||
compressed_data = base64::decode(data.payload_view(1));
|
||||
} else {
|
||||
compressed_data = base64::decode(data.value("data"));
|
||||
}
|
||||
|
||||
size_t decompressed_length;
|
||||
auto decompressed = decompress_snapshot(error, decompressed_length, compressed_data);
|
||||
if(!decompressed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string_view decompressed_data{&*decompressed, decompressed_length};
|
||||
ts::command_parser decompressed_parser{decompressed_data};
|
||||
if(!decompressed_parser.parse(false)) {
|
||||
error = "failed to parse snapshot payload";
|
||||
return false;
|
||||
}
|
||||
|
||||
return snapshots::parse_snapshot_raw(result, error, server_id, decompressed_parser, 0, type::TEAMSPEAK, version);
|
||||
} else {
|
||||
error = "snapshots with version 1-3 are currently supported";
|
||||
return false;
|
||||
@ -77,9 +179,7 @@ bool snapshots::parse_snapshot_ts3(snapshot_data &result, std::string &error, ts
|
||||
}
|
||||
|
||||
bool snapshots::parse_snapshot_raw(snapshot_data &snapshot_data, std::string &error, ServerId server_id, const command_parser &command,
|
||||
const std::string &hash, size_t command_offset, snapshots::type type, snapshots::version_t version) {
|
||||
//TODO: Verify hash
|
||||
|
||||
size_t command_offset, snapshots::type type, snapshots::version_t version) {
|
||||
/* all snapshots start with the virtual server properties */
|
||||
{
|
||||
snapshots::server_parser parser{type, version, command};
|
||||
@ -105,7 +205,6 @@ bool snapshots::parse_snapshot_raw(snapshot_data &snapshot_data, std::string &er
|
||||
return false;
|
||||
}
|
||||
snapshot_data.parsed_channels.reserve(*end_bulk - command_offset);
|
||||
debugMessage(server_id, "Snapshot contains {} channels", *end_bulk - command_offset);
|
||||
|
||||
while(!command.bulk(command_offset).has_key("end_channels")) {
|
||||
auto& entry = snapshot_data.parsed_channels.emplace_back();
|
||||
@ -130,7 +229,6 @@ bool snapshots::parse_snapshot_raw(snapshot_data &snapshot_data, std::string &er
|
||||
return false;
|
||||
}
|
||||
snapshot_data.parsed_channels.reserve(*end_bulk - command_offset);
|
||||
debugMessage(server_id, "Snapshot contains {} clients", *end_bulk - command_offset);
|
||||
|
||||
while(!command.bulk(command_offset).has_key("end_clients")) {
|
||||
auto& entry = snapshot_data.parsed_clients.emplace_back();
|
||||
@ -140,6 +238,93 @@ bool snapshots::parse_snapshot_raw(snapshot_data &snapshot_data, std::string &er
|
||||
command_offset++; /* the "end_clients" token */
|
||||
}
|
||||
|
||||
if(type == snapshots::type::TEASPEAK && version >= 2) {
|
||||
|
||||
bool music_bots_parsed{false},
|
||||
playlist_parsed{false},
|
||||
playlist_songs_parsed{false};
|
||||
|
||||
if(!command.bulk(command_offset).has_key("begin_music")) {
|
||||
error = "missing begin music key";
|
||||
return false;
|
||||
}
|
||||
|
||||
while(!command.bulk(command_offset).has_key("end_music")) {
|
||||
if(command.bulk(command_offset).has_key("begin_bots")) {
|
||||
if(std::exchange(music_bots_parsed, true)) {
|
||||
error = "duplicated music bot list";
|
||||
return false;
|
||||
}
|
||||
|
||||
snapshots::music_bot_parser parser{type, version, command};
|
||||
while(!command.bulk(command_offset).has_key("end_bots")) {
|
||||
auto& bot = snapshot_data.music_bots.emplace_back();
|
||||
|
||||
if(!parser.parse(error, bot, command_offset))
|
||||
return false;
|
||||
|
||||
command_offset++;
|
||||
}
|
||||
} else if(command.bulk(command_offset).has_key("begin_playlist")) {
|
||||
if(std::exchange(playlist_parsed, true)) {
|
||||
error = "duplicated playlist list";
|
||||
return false;
|
||||
}
|
||||
|
||||
snapshots::playlist_parser parser{type, version, command};
|
||||
while(!command.bulk(command_offset).has_key("end_playlist")) {
|
||||
auto& playlist = snapshot_data.playlists.emplace_back();
|
||||
|
||||
if(!parser.parse(error, playlist, command_offset))
|
||||
return false;
|
||||
|
||||
command_offset++;
|
||||
}
|
||||
} else if(command.bulk(command_offset).has_key("begin_playlist_songs")) {
|
||||
if(std::exchange(playlist_songs_parsed, true)) {
|
||||
error = "duplicated playlist songs list";
|
||||
return false;
|
||||
}
|
||||
|
||||
snapshots::playlist_song_parser parser{type, version, command};
|
||||
|
||||
PlaylistId current_playlist_id{0};
|
||||
while(!command.bulk(command_offset).has_key("end_playlist_songs")) {
|
||||
if(snapshot_data.playlist_songs.empty() || command.bulk(command_offset).has_key("song_playlist_id"))
|
||||
current_playlist_id = command.bulk(command_offset).value_as<PlaylistId>("song_playlist_id");
|
||||
|
||||
auto& playlist_song = snapshot_data.playlist_songs.emplace_back();
|
||||
playlist_song.playlist_id = current_playlist_id;
|
||||
|
||||
if(!parser.parse(error, playlist_song, command_offset))
|
||||
return false;
|
||||
|
||||
command_offset++;
|
||||
}
|
||||
} else {
|
||||
__asm__("nop");
|
||||
}
|
||||
command_offset++;
|
||||
}
|
||||
command_offset++;
|
||||
|
||||
/* check if everything has been parsed */
|
||||
if(!music_bots_parsed) {
|
||||
error = "missing music bots";
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!playlist_parsed) {
|
||||
error = "missing playlists";
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!playlist_songs_parsed) {
|
||||
error = "missing playlist songs";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* permissions */
|
||||
{
|
||||
bool server_groups_parsed{false},
|
||||
@ -274,82 +459,14 @@ bool snapshots::parse_snapshot_raw(snapshot_data &snapshot_data, std::string &er
|
||||
}
|
||||
}
|
||||
|
||||
if(type == snapshots::type::TEASPEAK && version >= 2) {
|
||||
|
||||
bool music_bots_parsed{false},
|
||||
playlist_parsed{false},
|
||||
playlist_songs_parsed{false};
|
||||
|
||||
if(!command.bulk(command_offset++).has_key("begin_music")) {
|
||||
error = "missing begin music key";
|
||||
return false;
|
||||
}
|
||||
|
||||
while(!command.bulk(command_offset).has_key("end_music")) {
|
||||
if(command.bulk(command_offset).has_key("begin_bots")) {
|
||||
if(std::exchange(music_bots_parsed, true)) {
|
||||
error = "duplicated music bot list";
|
||||
return false;
|
||||
}
|
||||
|
||||
snapshots::music_bot_parser parser{type, version, command};
|
||||
while(!command.bulk(command_offset).has_key("end_bots")) {
|
||||
auto& bot = snapshot_data.music_bots.emplace_back();
|
||||
|
||||
if(!parser.parse(error, bot, command_offset))
|
||||
return false;
|
||||
}
|
||||
} else if(command.bulk(command_offset).has_key("begin_playlist")) {
|
||||
if(std::exchange(playlist_parsed, true)) {
|
||||
error = "duplicated playlist list";
|
||||
return false;
|
||||
}
|
||||
|
||||
snapshots::playlist_parser parser{type, version, command};
|
||||
while(!command.bulk(command_offset).has_key("end_playlist")) {
|
||||
auto& playlist = snapshot_data.playlists.emplace_back();
|
||||
|
||||
if(!parser.parse(error, playlist, command_offset))
|
||||
return false;
|
||||
}
|
||||
} else if(command.bulk(command_offset).has_key("begin_playlist_songs")) {
|
||||
if(std::exchange(playlist_songs_parsed, true)) {
|
||||
error = "duplicated playlist songs list";
|
||||
return false;
|
||||
}
|
||||
|
||||
snapshots::playlist_song_parser parser{type, version, command};
|
||||
|
||||
PlaylistId current_playlist_id{0};
|
||||
while(!command.bulk(command_offset).has_key("end_playlist_songs")) {
|
||||
if(snapshot_data.playlist_songs.empty() || command.bulk(command_offset).has_key("song_playlist_id"))
|
||||
current_playlist_id = command.bulk(command_offset).value_as<PlaylistId>("song_playlist_id");
|
||||
|
||||
auto& playlist_song = snapshot_data.playlist_songs.emplace_back();
|
||||
playlist_song.playlist_id = current_playlist_id;
|
||||
|
||||
if(!parser.parse(error, playlist_song, command_offset))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* check if everything has been parsed */
|
||||
if(!music_bots_parsed) {
|
||||
error = "missing music bots";
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!playlist_parsed) {
|
||||
error = "missing playlists";
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!playlist_songs_parsed) {
|
||||
error = "missing playlist songs";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
debugMessage(server_id, "Parsed snapshot containing {} channels, {} server groups, {} channel groups, {} clients, {} music bots and {} playlists.",
|
||||
snapshot_data.parsed_channels.size(),
|
||||
snapshot_data.parsed_server_groups.size(),
|
||||
snapshot_data.parsed_channel_groups.size(),
|
||||
snapshot_data.parsed_clients.size(),
|
||||
snapshot_data.music_bots.size(),
|
||||
snapshot_data.playlists.size()
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
@ -143,7 +143,7 @@ bool permission_parser::parse_entry_teaspeak_v1(std::string &error, std::vector<
|
||||
return false;
|
||||
}
|
||||
|
||||
permission::PermissionValue value{};
|
||||
permission::PermissionValue value;
|
||||
{
|
||||
auto value_string = data.value("value", key_found);
|
||||
if(!key_found) {
|
||||
@ -159,7 +159,7 @@ bool permission_parser::parse_entry_teaspeak_v1(std::string &error, std::vector<
|
||||
}
|
||||
}
|
||||
|
||||
permission::PermissionValue granted{};
|
||||
permission::PermissionValue granted;
|
||||
{
|
||||
auto value_string = data.value("grant", key_found);
|
||||
if(!key_found) {
|
||||
|
@ -54,6 +54,6 @@ namespace ts::server::snapshots {
|
||||
extern bool parse_snapshot_ts3(snapshot_data& /* result */, std::string& /* error */, ServerId /* target server id */, const command_parser& /* source */);
|
||||
extern bool parse_snapshot_teaspeak(snapshot_data& /* result */, std::string& /* error */, ServerId /* target server id */, const command_parser& /* source */);
|
||||
|
||||
extern bool parse_snapshot_raw(snapshot_data& /* result */, std::string& /* error */, ServerId /* target server id */, const command_parser& /* source */, const std::string& /* hash */, size_t /* offset */, snapshots::type /* type */, snapshots::version_t /* version */);
|
||||
extern bool parse_snapshot_raw(snapshot_data& /* result */, std::string& /* error */, ServerId /* target server id */, const command_parser& /* source */, size_t /* offset */, snapshots::type /* type */, snapshots::version_t /* version */);
|
||||
|
||||
}
|
@ -29,4 +29,28 @@ namespace ts::server::snapshots {
|
||||
std::deque<snapshots::playlist_entry> playlists{};
|
||||
std::deque<snapshots::playlist_song_entry> playlist_songs{};
|
||||
};
|
||||
|
||||
struct snapshot_mappings {
|
||||
ServerId new_server_id{};
|
||||
|
||||
std::map<ClientDbId, ClientDbId> client_id{};
|
||||
std::map<ChannelId, ChannelId> channel_id{};
|
||||
std::map<ChannelId, ChannelId> channel_group_id{};
|
||||
std::map<ChannelId, ChannelId> server_group_id{};
|
||||
std::map<PlaylistId, PlaylistId> playlist_id{};
|
||||
};
|
||||
|
||||
struct snapshot_deploy_timings {
|
||||
std::chrono::system_clock::time_point initial_timestamp{};
|
||||
std::chrono::system_clock::time_point decode_timestamp{}; /* end decode */
|
||||
std::chrono::system_clock::time_point parse_timestamp{}; /* end parse */
|
||||
std::chrono::system_clock::time_point deploy_timestamp{}; /* else deploy */
|
||||
std::chrono::system_clock::time_point startup_timestamp{}; /* end startup */
|
||||
};
|
||||
|
||||
struct snapshot_deploy {
|
||||
snapshot_deploy_timings timings{};
|
||||
snapshot_mappings mappings{};
|
||||
snapshot_data data{};
|
||||
};
|
||||
}
|
||||
|
2
shared
2
shared
@ -1 +1 @@
|
||||
Subproject commit a6343968450d4e626bcf99ec622b4a325bf8c4ba
|
||||
Subproject commit c3188cd9e5f499f4b5813953d2bc62f56800222f
|
Loading…
x
Reference in New Issue
Block a user