New snapshot system proposal

This commit is contained in:
WolverinDEV 2020-04-11 20:41:09 +02:00
parent 0f1665c97d
commit 483e188c37
12 changed files with 1287 additions and 0 deletions

View File

@ -0,0 +1,60 @@
//
// Created by WolverinDEV on 11/04/2020.
//
#include "channel.h"
using namespace ts::server::snapshots;
bool channel_parser::parse(std::string &error, channel_entry &channel, size_t &offset) {
auto data = this->command.bulk(offset++);
channel.properties.register_property_type<property::ChannelProperties>();
std::optional<ChannelId> channel_id{};
std::optional<ChannelId> parent_channel_id{};
size_t entry_index{0};
std::string_view key{};
std::string value{};
while(data.next_entry(entry_index, key, value)) {
if(key == "begin_channels")
continue;
else if(key == "channel_id") {
char* end_ptr{nullptr};
channel_id = strtoull(value.c_str(), &end_ptr, 10);
if (*end_ptr) {
error = "failed to parse channel id at character " + std::to_string(data.key_command_character_index(key) + key.length());
return false;
}
} else if(key == "channel_pid") {
char* end_ptr{nullptr};
parent_channel_id = strtoull(value.c_str(), &end_ptr, 10);
if (*end_ptr) {
error = "failed to parse channel parent id at character " + std::to_string(data.key_command_character_index(key) + key.length());
return false;
}
} else {
const auto& property = property::find<property::ChannelProperties>(key);
if(property.is_undefined()) {
//TODO: Issue a warning
continue;
}
//TODO: Validate value
channel.properties[property] = value;
}
}
if(!channel_id.has_value()) {
error = "channel entry at character index " + std::to_string(data.command_character_index()) + " misses a channel id";
return false;
}
if(!parent_channel_id.has_value()) {
error = "channel entry at character index " + std::to_string(data.command_character_index()) + " misses a channel parent id";
return false;
}
channel.properties[property::CHANNEL_ID] = *channel_id;
channel.properties[property::CHANNEL_PID] = *parent_channel_id;
return true;
}

View File

@ -0,0 +1,23 @@
#pragma once
#include <Definitions.h>
#include <Properties.h>
#include <chrono>
#include <deque>
#include "./snapshot.h"
namespace ts::server::snapshots {
struct channel_entry {
Properties properties{};
};
class channel_parser : public parser<channel_entry> {
public:
channel_parser(type type_, version_t version, const command_parser& command) : parser{type_, version, command} {}
bool parse(
std::string& /* error */,
channel_entry& /* result */,
size_t& /* offset */) override;
};
}

View File

@ -0,0 +1,108 @@
//
// Created by WolverinDEV on 11/04/2020.
//
#include "client.h"
using namespace ts::server::snapshots;
bool client_parser::parse(std::string &error, client_entry &client, size_t &offset) {
bool key_found;
auto data = this->command.bulk(offset++);
{
auto value_string = data.value("client_id", key_found);
if(!key_found) {
error = "missing id for client entry at character " + std::to_string(data.command_character_index());
return false;
}
char* end_ptr{nullptr};
client.database_id = strtoll(value_string.c_str(), &end_ptr, 10);
if (*end_ptr) {
error = "unparsable id for client entry at character " + std::to_string(data.key_command_character_index("client_id") + 9);
return false;
}
}
{
auto value_string = data.value("client_created", key_found);
if(!key_found) {
error = "missing created timestamp for client entry at character " + std::to_string(data.command_character_index());
return false;
}
char* end_ptr{nullptr};
auto value = strtoll(value_string.c_str(), &end_ptr, 10);
if (*end_ptr) {
error = "unparsable created timestamp for client entry at character " + std::to_string(data.key_command_character_index("client_created") + 14);
return false;
}
client.timestamp_created = std::chrono::system_clock::time_point{} + std::chrono::seconds{value};
}
/* optional */
{
auto value_string = data.value("client_lastconnected", key_found);
if(key_found) {
char* end_ptr{nullptr};
auto value = strtoll(value_string.c_str(), &end_ptr, 10);
if (*end_ptr) {
error = "unparsable last connected timestamp for client entry at character " + std::to_string(data.key_command_character_index("client_lastconnected") + 20);
return false;
}
client.timestamp_last_connected = std::chrono::system_clock::time_point{} + std::chrono::seconds{value};
} else {
client.timestamp_last_connected = std::chrono::system_clock::time_point{};
}
}
/* optional */
{
auto value_string = data.value("client_totalconnections", key_found);
if(key_found) {
char* end_ptr{nullptr};
client.client_total_connections = strtoll(value_string.c_str(), &end_ptr, 10);
if (*end_ptr) {
error = "unparsable total connection count for client entry at character " + std::to_string(data.key_command_character_index("client_totalconnections") + 23);
return false;
}
} else {
client.client_total_connections = 0;
}
}
client.unique_id = data.value("client_unique_id", key_found);
if(!key_found) {
error = "missing unique id for client entry at character " + std::to_string(data.command_character_index());
return false;
}
client.nickname = data.value("client_nickname", key_found);
if(!key_found) {
error = "missing nickname for client entry at character " + std::to_string(data.command_character_index());
return false;
}
client.description = data.value("client_description", key_found);
if(!key_found) {
error = "missing description for client entry at character " + std::to_string(data.command_character_index());
return false;
}
return true;
}
bool client_writer::write(std::string &error, size_t &offset, const client_entry &client) {
auto data = this->command.bulk(offset++);
data.put_unchecked("client_id", client.database_id);
data.put_unchecked("client_unique_id", client.unique_id);
data.put_unchecked("client_nickname", client.nickname);
data.put_unchecked("client_description", client.description);
data.put_unchecked("client_created", std::chrono::floor<std::chrono::seconds>(client.timestamp_created.time_since_epoch()).count());
data.put_unchecked("client_lastconnected", std::chrono::floor<std::chrono::seconds>(client.timestamp_last_connected.time_since_epoch()).count());
data.put_unchecked("client_totalconnections", client.client_total_connections);
if(this->type_ == type::TEAMSPEAK)
data.put_unchecked("client_unread_messages", "0");
return true;
}

View File

@ -0,0 +1,36 @@
#pragma once
#include <Definitions.h>
#include <chrono>
#include <deque>
#include "./snapshot.h"
namespace ts::server::snapshots {
struct client_entry {
ClientDbId database_id;
std::string unique_id;
std::string nickname;
std::string description;
std::chrono::system_clock::time_point timestamp_created;
std::chrono::system_clock::time_point timestamp_last_connected;
size_t client_total_connections;
};
class client_parser : public parser<client_entry> {
public:
client_parser(type type_, version_t version, const command_parser& command) : parser{type_, version, command} {}
bool parse(
std::string& /* error */,
client_entry& /* result */,
size_t& /* offset */) override;
};
class client_writer : public writer<client_entry> {
public:
client_writer(type type_, version_t version, command_builder& command) : writer{type_, version, command} {}
bool write(std::string &, size_t &, const client_entry &) override;
};
}

View File

@ -0,0 +1,374 @@
//
// Created by WolverinDEV on 11/04/2020.
//
#include "./snapshot.h"
#include "./server.h"
#include "./channel.h"
#include "./permission.h"
#include "./client.h"
#include "./groups.h"
#include "../VirtualServerManager.h"
#include <log/LogUtils.h>
using namespace ts;
using namespace ts::server;
using SnapshotType = ts::server::snapshots::type;
using SnapshotVersion = ts::server::snapshots::version_t;
bool VirtualServerManager::deploy_snapshot(std::string &error, ServerId server_id, const command_parser &data) {
if(data.bulk(0).has_key("version")) {
return this->deploy_ts3_snapshot(error, server_id, data);
} else if(data.bulk(1).has_key("snapshot_version")) {
/* teaspeak snapshot */
return this->deploy_teaspeak_snapshot(error, server_id, data);
} else {
/* old TS3 snapshot format */
return this->deploy_ts3_snapshot(error, server_id, data);
}
}
bool VirtualServerManager::deploy_teaspeak_snapshot(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;
}
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) {
error = "snapshot version is too new";
return false;
}
/* the actual snapshot begins at index 2 */
return this->deploy_raw_snapshot(error, server_id, data, hash, 2, SnapshotType::TEASPEAK, version);
}
bool VirtualServerManager::deploy_ts3_snapshot(std::string &error, ts::ServerId server_id, const ts::command_parser &data) {
snapshots::version_t version{0};
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 this->deploy_raw_snapshot(error, server_id, data, hash, 1, SnapshotType::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 {
error = "snapshots with version 1-3 are currently supported";
return false;
}
}
struct parse_client_entry {
snapshots::client_entry parsed_data{};
};
struct parsed_group_entry {
snapshots::group_entry parsed_data{};
};
bool VirtualServerManager::deploy_raw_snapshot(std::string &error, ts::ServerId server_id, const ts::command_parser &command, const std::string& /* hash */, size_t command_offset,
snapshots::type type, snapshots::version_t version) {
snapshots::server_entry parsed_server{};
//TODO: Verify hash
/* all snapshots start with the virtual server properties */
{
snapshots::server_parser parser{type, version, command};
if(!parser.parse(error, parsed_server, command_offset))
return false;
}
std::vector<snapshots::channel_entry> parsed_channels{};
/* afterwards all channels */
{
snapshots::channel_parser parser{type, version, command};
auto data = command.bulk(command_offset);
if(!data.has_key("begin_channels")) {
error = "missing begin channels token at " + std::to_string(data.command_character_index());
return false;
}
auto end_bulk = command.next_bulk_containing("end_channels", command_offset);
if(!end_bulk.has_value()) {
error = "missing end channels token";
return false;
} else if(*end_bulk == command_offset) {
error = "snapshot contains no channels";
return false;
}
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 = parsed_channels.emplace_back();
if(!parser.parse(error, entry, command_offset))
return false;
}
command_offset++; /* the "end_channels" token */
}
std::vector<parse_client_entry> parsed_clients{};
/* after channels all clients */
{
snapshots::client_parser parser{type, version, command};
auto data = command.bulk(command_offset);
if(!data.has_key("begin_clients")) {
error = "missing begin clients token at " + std::to_string(data.command_character_index());
return false;
}
auto end_bulk = command.next_bulk_containing("end_clients", command_offset);
if(!end_bulk.has_value()) {
error = "missing end clients token";
return false;
}
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 = parsed_clients.emplace_back();
if(!parser.parse(error, entry.parsed_data, command_offset))
return false;
}
command_offset++; /* the "end_clients" token */
}
bool server_groups_parsed{false},
channel_groups_parsed{false},
client_permissions_parsed{false},
channel_permissions_parsed{false},
client_channel_permissions_parsed{false};
std::vector<parsed_group_entry> parsed_server_groups{};
snapshots::group_relations parsed_server_group_relations{};
std::vector<parsed_group_entry> parsed_channel_groups{};
snapshots::group_relations parsed_channel_group_relations{};
std::deque<snapshots::permissions_flat_entry> client_permissions{};
std::deque<snapshots::permissions_flat_entry> channel_permissions{};
std::deque<snapshots::permissions_flat_entry> client_channel_permissions{};
/* permissions */
{
if(!command.bulk(command_offset++).has_key("begin_permissions")) {
error = "missing begin permissions key";
return false;
}
snapshots::relation_parser relation_parser{type, version, command};
while(!command.bulk(command_offset).has_key("end_permissions")) {
if(command.bulk(command_offset).has_key("server_groups")) {
if(server_groups_parsed) {
error = "duplicated server group list";
return false;
} else server_groups_parsed = true;
snapshots::group_parser group_parser{type, version, command, "id", permission::teamspeak::GroupType::SERVER};
/* parse all groups */
while(!command.bulk(command_offset).has_key("end_groups")){
auto& group = parsed_server_groups.emplace_back();
if(!group_parser.parse(error, group.parsed_data, command_offset)) /* will consume the end group token */
return false;
command_offset++; /* for the "end_group" token */
}
command_offset++; /* for the "end_groups" token */
/* parse relations */
if(!relation_parser.parse(error, parsed_server_group_relations, command_offset))
return false;
command_offset++; /* for the "end_relations" token */
if(parsed_server_group_relations.size() > 1) {
error = "all group relations should be for channel id 0 but received more than one different channel.";
return false;
} else if(!parsed_server_group_relations.empty() && parsed_server_group_relations.begin()->first != 0) {
error = "all group relations should be for channel id 0 but received it for " + std::to_string(parsed_server_group_relations.begin()->first);
return false;
}
} else if(command.bulk(command_offset).has_key("channel_groups")) {
if(channel_groups_parsed) {
error = "duplicated channel group list";
return false;
} else channel_groups_parsed = true;
snapshots::group_parser group_parser{type, version, command, "id", permission::teamspeak::GroupType::CHANNEL};
/* parse all groups */
while(!command.bulk(command_offset).has_key("end_groups")){
auto& group = parsed_channel_groups.emplace_back();
if(!group_parser.parse(error, group.parsed_data, command_offset))
return false;
command_offset++; /* for the "end_group" token */
}
command_offset++; /* for the "end_groups" token */
/* parse relations */
if(!relation_parser.parse(error, parsed_channel_group_relations, command_offset))
return false;
command_offset++; /* for the "end_relations" token */
} else if(command.bulk(command_offset).has_key("client_flat")) {
/* client permissions */
if(client_permissions_parsed) {
error = "duplicated client permissions list";
return false;
} else client_permissions_parsed = true;
snapshots::flat_parser flat_parser{type, version, command, permission::teamspeak::GroupType::CLIENT};
if(!flat_parser.parse(error, client_permissions, command_offset))
return false;
command_offset++; /* for the "end_flat" token */
} else if(command.bulk(command_offset).has_key("channel_flat")) {
/* channel permissions */
if(channel_permissions_parsed) {
error = "duplicated channel permissions list";
return false;
} else channel_permissions_parsed = true;
snapshots::flat_parser flat_parser{type, version, command, permission::teamspeak::GroupType::CHANNEL};
if(!flat_parser.parse(error, channel_permissions, command_offset))
return false;
command_offset++; /* for the "end_flat" token */
} else if(command.bulk(command_offset).has_key("channel_client_flat")) {
/* channel client permissions */
if(client_channel_permissions_parsed) {
error = "duplicated client channel permissions list";
return false;
} else client_channel_permissions_parsed = true;
snapshots::flat_parser flat_parser{type, version, command, permission::teamspeak::GroupType::CLIENT};
if(!flat_parser.parse(error, client_channel_permissions, command_offset))
return false;
command_offset++; /* for the "end_flat" token */
} else {
command_offset++;
}
}
}
/* check if everything has been parsed */
{
/* basic stuff */
if(!server_groups_parsed) {
error = "missing server groups";
return false;
}
if(!channel_groups_parsed) {
error = "missing channel groups";
return false;
}
if(!client_permissions_parsed) {
error = "missing client permissions";
return false;
}
if(!channel_permissions_parsed) {
error = "missing channel permissions";
return false;
}
if(!client_channel_permissions_parsed) {
error = "missing client channel permissions";
return false;
}
}
std::map<ChannelId, ChannelId> channel_id_mapping{};
std::map<ChannelId, ChannelId> channel_group_id_mapping{};
std::map<ChannelId, ChannelId> server_group_id_mapping{};
/* lets start inserting data to the database */
{
/* channels */
{
/* Assign each channel a new id */
ChannelId current_id{0};
for(auto& channel : parsed_channels) {
const auto new_id = current_id++;
channel_id_mapping[channel.properties[property::CHANNEL_ID]] = new_id;
channel.properties[property::CHANNEL_ID] = new_id;
}
/* Update channel parents */
for(auto& channel : parsed_channels) {
auto pid = channel.properties[property::CHANNEL_PID].as<ChannelId>();
if(pid > 0) {
auto new_id = channel_id_mapping.find(pid);
if(new_id == channel_id_mapping.end()) {
error = "failed to remap channel parent id for channel \"" + channel.properties[property::CHANNEL_NAME].value() + "\" (snapshot/channel tree broken?)";
return false;
}
channel.properties[property::CHANNEL_PID] = new_id->second;
}
}
//TODO: Insert them into the database
}
/* channel permissions */
{
for(auto& entry : channel_permissions) {
auto new_id = channel_id_mapping.find(entry.id1);
if(new_id == channel_id_mapping.end()) {
error = "missing channel id mapping for channel permission entry";
return false;
}
entry.id1 = new_id->second;
}
}
/* server groups */
{
/* TODO: Insert to the database & load result */
}
/* channel groups */
{
/* TODO: Insert to the database & load the result into the mapping */
}
/* client permissions */
{
}
/* register clients in the database */
{
}
/* client channel permissions */
{
for(auto& entry : client_channel_permissions) {
auto new_id = channel_id_mapping.find(entry.id1);
if(new_id == channel_id_mapping.end()) {
error = "missing channel id mapping for client channel permission entry";
return false;
}
entry.id1 = new_id->second;
}
}
}
error = "not implemented";
return false;
}

View File

@ -0,0 +1,97 @@
//
// Created by WolverinDEV on 11/04/2020.
//
#include "groups.h"
using namespace ts::server::snapshots;
bool group_parser::parse(std::string &error, group_entry &group, size_t &offset) {
auto group_data = this->command.bulk(offset);
bool key_found;
{
auto value_string = group_data.value(this->id_key, key_found);
if(!key_found) {
error = "missing id for group entry at character " + std::to_string(group_data.command_character_index());
return false;
}
char* end_ptr{nullptr};
group.group_id = strtoll(value_string.c_str(), &end_ptr, 10);
if (*end_ptr) {
error = "unparsable id for group entry at character " + std::to_string(group_data.key_command_character_index(this->id_key) + this->id_key.length());
return false;
}
}
group.name = group_data.value("name", key_found);
if(!key_found) {
error = "missing name for group entry at character " + std::to_string(group_data.command_character_index());
return false;
}
return this->pparser.parse(error, group.permissions, offset);
}
bool relation_parser::parse(std::string &error, group_relations &result, size_t &offset) {
auto relation_end = this->command.next_bulk_containing("end_relations", offset);
if(!relation_end.has_value()) {
error = "missing end relations token";
return false;
}
bool key_found;
while(offset < *relation_end) {
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;
}
auto& relations = result[begin_bulk.value_as<ChannelId>("iid")];
auto next_iid = this->command.next_bulk_containing("iid", offset + 1);
if(next_iid.has_value() && *next_iid < relation_end)
relations.reserve(*next_iid - offset);
else
relations.reserve(*relation_end - offset);
while(offset < *next_iid) {
auto relation_data = this->command.bulk(offset++);
auto& relation = relations.emplace_back();
{
auto value_string = relation_data.value("cldbid", key_found);
if(!key_found) {
error = "missing client id for group relation entry at character " + std::to_string(relation_data.command_character_index());
return false;
}
char* end_ptr{nullptr};
relation.client_id = strtoll(value_string.c_str(), &end_ptr, 10);
if (*end_ptr) {
error = "unparsable client id for group relation entry at character " + std::to_string(relation_data.key_command_character_index("cldbid") + 4);
return false;
}
}
{
auto value_string = relation_data.value("gid", key_found);
if(!key_found) {
error = "missing group id for group relation entry at character " + std::to_string(relation_data.command_character_index());
return false;
}
char* end_ptr{nullptr};
relation.group_id = strtoll(value_string.c_str(), &end_ptr, 10);
if (*end_ptr) {
error = "unparsable group id for group relation entry at character " + std::to_string(relation_data.key_command_character_index("gid") + 3);
return false;
}
}
}
}
return true;
}

View File

@ -0,0 +1,54 @@
#pragma once
#include <Definitions.h>
#include <chrono>
#include <deque>
#include <utility>
#include "./snapshot.h"
#include "./permission.h"
namespace ts::server::snapshots {
struct group_entry {
GroupId group_id;
std::string name;
std::vector<permission_entry> permissions{};
};
struct group_relation {
ClientDbId client_id;
GroupId group_id;
};
typedef std::map<ChannelId, std::vector<group_relation>> group_relations;
class group_parser : public parser<group_entry> {
public:
group_parser(type type_, version_t version, const command_parser& command, std::string id_key, permission::teamspeak::GroupType target_permission_type)
: parser{type_, version, command}, id_key{std::move(id_key)}, pparser{type_, version, command, {
target_permission_type,
{"end_group"},
false
}} {}
bool parse(
std::string& /* error */,
group_entry& /* result */,
size_t& /* offset */) override;
private:
std::string id_key{};
permission_parser pparser;
};
class relation_parser : public parser<group_relations> {
public:
relation_parser(type type_, version_t version, const command_parser& command) : parser{type_, version, command} {}
bool parse(
std::string& /* error */,
group_relations& /* result */,
size_t& /* offset */) override;
};
}

View File

@ -0,0 +1,305 @@
//
// Created by WolverinDEV on 11/04/2020.
//
#include "permission.h"
using namespace ts::server::snapshots;
permission_parser::permission_parser(ts::server::snapshots::type type_, ts::server::snapshots::version_t version,
const ts::command_parser &command, permission_parser_options options) : parser{type_, version, command}, options{std::move(options)} {
if(type_ == type::TEAMSPEAK) {
this->parser_impl = &permission_parser::parse_entry_teamspeak_v0;
} else if(type_ == type::TEASPEAK) {
if(version >= 1) {
this->parser_impl = &permission_parser::parse_entry_teaspeak_v1;
} else {
/* TeaSpeak has no snapshot version 0. 0 implies a TeamSpeak snapshot */
assert(false);
}
} else {
assert(false);
}
}
bool permission_parser::parse(
std::string &error,
std::vector<permission_entry> &result,
size_t &offset) {
size_t end_offset{(size_t) -1};
{
size_t end_begin_offset{offset + (this->options.ignore_delimiter_at_index_0 ? 1 : 0)};
for(const auto& token : this->options.delimiter) {
auto index = this->command.next_bulk_containing(token, end_begin_offset);
if(index.has_value() && *index < end_offset)
end_offset = *index;
}
if(end_offset == (size_t) -1) {
error = "missing end token";
return false;
}
}
if(end_offset == offset) {
/* no entries at all */
return true;
}
result.reserve((end_offset - offset) * 2); /* reserve some extra space because we might import permissions */
assert(this->type_ == type::TEAMSPEAK || this->type_ == type::TEASPEAK);
while(offset < end_offset) {
if(!(this->*(this->parser_impl))(error, result, this->command[offset]))
return false;
offset++;
}
return true;
}
bool permission_parser::parse_entry_teamspeak_v0(std::string &error, std::vector<permission_entry> &result,
const ts::command_bulk &data) {
bool key_found;
auto original_name = data.value("permid", key_found);
if(!key_found) {
error = "missing id for permission entry at character " + std::to_string(data.command_character_index());
return false;
}
permission::PermissionValue value{};
{
auto value_string = data.value("permvalue", key_found);
if(!key_found) {
error = "missing value for permission entry at character " + std::to_string(data.command_character_index());
return false;
}
char* end_ptr{nullptr};
value = strtoll(value_string.c_str(), &end_ptr, 10);
if (*end_ptr) {
error = "unparsable permission value at index " + std::to_string(data.key_command_character_index("permvalue") + 9);
return false;
}
}
auto flag_skip = data.value("permskip", key_found) == "1";
if(!key_found) {
error = "missing skip flag for permission entry at character " + std::to_string(data.command_character_index());
return false;
}
auto flag_negate = data.value("permnegated", key_found) == "1";
if(!key_found) {
error = "missing skip flag for permission entry at character " + std::to_string(data.command_character_index());
return false;
}
for(const auto& mapped : permission::teamspeak::map_key(original_name, this->options.target_permission_type)) {
auto type = permission::resolvePermissionData(mapped);
if(type == permission::PermissionTypeEntry::unknown)
continue;
permission_entry* entry{nullptr};
for(auto& e : result)
if(e.type == type) {
entry = &e;
break;
}
if(!entry) {
entry = &result.emplace_back();
entry->type = type;
}
entry->value = {value, true};
if(mapped != type->grant_name) {
entry->flag_negate = flag_negate;
entry->flag_skip = flag_skip;
}
}
return true;
}
bool permission_parser::parse_entry_teaspeak_v1(std::string &error, std::vector<permission_entry> &result,
const ts::command_bulk &data) {
bool key_found;
auto permission_name = data.value("perm", key_found);
if(!key_found) {
error = "missing id for permission entry at character " + std::to_string(data.command_character_index());
return false;
}
auto flag_skip = data.value("flag_skip", key_found) == "1";
if(!key_found) {
error = "missing skip flag for permission entry at character " + std::to_string(data.command_character_index());
return false;
}
auto flag_negated = data.value("flag_negated", key_found) == "1";
if(!key_found) {
error = "missing negate flag for permission entry at character " + std::to_string(data.command_character_index());
return false;
}
permission::PermissionValue value{};
{
auto value_string = data.value("value", key_found);
if(!key_found) {
error = "missing value for permission entry at character " + std::to_string(data.command_character_index());
return false;
}
char* end_ptr{nullptr};
value = strtoll(value_string.c_str(), &end_ptr, 10);
if (*end_ptr) {
error = "unparsable permission value at index " + std::to_string(data.key_command_character_index("value") + 5);
return false;
}
}
permission::PermissionValue granted{};
{
auto value_string = data.value("grant", key_found);
if(!key_found) {
error = "missing grant for permission entry at character " + std::to_string(data.command_character_index());
return false;
}
char* end_ptr{nullptr};
granted = strtoll(value_string.c_str(), &end_ptr, 10);
if (*end_ptr) {
error = "unparsable permission granted value at index " + std::to_string(data.key_command_character_index("grant") + 5);
return false;
}
}
auto type = permission::resolvePermissionData(permission_name);
if(type == permission::PermissionTypeEntry::unknown)
return true; /* we just drop unknown permissions */ //TODO: Log this drop
auto& entry = result.emplace_back();
entry.type = type;
entry.flag_skip = flag_skip;
entry.flag_negate = flag_negated;
entry.value = {value, value != permNotGranted};
entry.granted = {granted, granted != permNotGranted};
return true;
}
permission_writer::permission_writer(type type_, version_t version,
ts::command_builder &command, permission::teamspeak::GroupType target_permission_type) : command{command}, type_{type_}, version_{version}, target_permission_type_{target_permission_type} {
if(type_ == type::TEAMSPEAK) {
this->write_impl = &permission_writer::write_entry_teamspeak_v0;
} else if(type_ == type::TEASPEAK) {
if(version >= 1) {
this->write_impl = &permission_writer::write_entry_teaspeak_v1;
} else {
/* TeaSpeak has no snapshot version 0. 0 implies a TeamSpeak snapshot */
assert(false);
}
} else {
assert(false);
}
}
bool permission_writer::write(std::string &error, size_t &offset, const std::deque<permission_entry> &entries) {
this->command.reserve_bulks(entries.size() * 2);
for(auto& entry : entries)
if(!this->write_entry(error, offset, entry))
return false;
return true;
}
bool permission_writer::write_entry(std::string &error, size_t &offset, const ts::server::snapshots::permission_entry &entry) {
return (this->*(this->write_impl))(error, offset, entry);
}
bool permission_writer::write_entry_teamspeak_v0(std::string &error, size_t& offset,
const ts::server::snapshots::permission_entry &entry) {
if(entry.value.has_value) {
for(const auto& name : permission::teamspeak::unmap_key(entry.type->name, this->target_permission_type_)) {
auto bulk = this->command.bulk(offset++);
bulk.put_unchecked("permid", name);
bulk.put_unchecked("permvalue", entry.value.value);
bulk.put_unchecked("permskip", entry.flag_skip);
bulk.put_unchecked("permnegated", entry.flag_negate);
}
}
if(entry.granted.has_value) {
for(const auto& name : permission::teamspeak::unmap_key(entry.type->grant_name, this->target_permission_type_)) {
auto bulk = this->command.bulk(offset++);
bulk.put_unchecked("permid", name);
bulk.put_unchecked("permvalue", entry.granted.value);
bulk.put_unchecked("permskip", "0");
bulk.put_unchecked("permnegated", "0");
}
}
return true;
}
bool permission_writer::write_entry_teaspeak_v1(std::string &error, size_t &offset,
const ts::server::snapshots::permission_entry &entry) {
if(!entry.value.has_value && !entry.granted.has_value)
return true; /* should not happen, but we skip that here */
auto bulk = this->command.bulk(offset++);
bulk.put_unchecked("perm", entry.type->name);
bulk.put_unchecked("value", entry.value.has_value ? entry.value.value : permNotGranted);
bulk.put_unchecked("grant", entry.granted.has_value ? entry.granted.value : permNotGranted);
bulk.put_unchecked("flag_skip", entry.flag_skip);
bulk.put_unchecked("flag_negated", entry.flag_negate);
return true;
}
bool flat_parser::parse(std::string &error, std::deque<permissions_flat_entry> &result, size_t &offset) {
auto flat_end = this->command.next_bulk_containing("end_flat", offset);
if(!flat_end.has_value()) {
error = "missing flat end for " + std::to_string(this->command.bulk(offset).command_character_index());
return false;
}
bool key_found;
while(offset < *flat_end) {
auto flat_data = this->command.bulk(offset);
auto& flat_entry = result.emplace_back();
/* id1 */
{
auto value_string = flat_data.value("id1", key_found);
if(!key_found) {
error = "missing id1 for flat entry at character " + std::to_string(flat_data.command_character_index());
return false;
}
char* end_ptr{nullptr};
flat_entry.id1 = strtoll(value_string.c_str(), &end_ptr, 10);
if (*end_ptr) {
error = "unparsable id1 for flat entry at character " + std::to_string(flat_data.key_command_character_index("id1") + 3);
return false;
}
}
/* id2 */
{
auto value_string = flat_data.value("id2", key_found);
if(!key_found) {
error = "missing id2 for flat entry at character " + std::to_string(flat_data.command_character_index());
return false;
}
char* end_ptr{nullptr};
flat_entry.id2 = strtoll(value_string.c_str(), &end_ptr, 10);
if (*end_ptr) {
error = "unparsable id2 for flat entry at character " + std::to_string(flat_data.key_command_character_index("id2") + 3);
return false;
}
}
if(!this->pparser.parse(error, flat_entry.permissions, offset))
return false;
}
return true;
}

View File

@ -0,0 +1,110 @@
#pragma once
#include <PermissionManager.h>
#include <query/command3.h>
#include "./snapshot.h"
namespace ts::server::snapshots {
struct permission_entry {
std::shared_ptr<permission::PermissionTypeEntry> type{nullptr};
permission::v2::PermissionFlaggedValue value{0, false};
permission::v2::PermissionFlaggedValue granted{0, false};
bool flag_skip{false};
bool flag_negate{false};
};
struct permission_parser_options {
permission::teamspeak::GroupType target_permission_type;
std::vector<std::string> delimiter;
bool ignore_delimiter_at_index_0;
};
class permission_parser : public parser<std::vector<permission_entry>> {
public:
permission_parser(type type_, version_t version, const command_parser& command, permission_parser_options /* options */);
bool parse(
std::string& /* error */,
std::vector<permission_entry>& /* result */,
size_t& /* offset */) override;
private:
typedef bool(permission_parser::*parse_impl_t)(std::string &error, std::vector<permission_entry> &result, const ts::command_bulk &data);
const permission_parser_options options;
parse_impl_t parser_impl;
bool parse_entry_teamspeak_v0(
std::string& /* error */,
std::vector<permission_entry>& /* result */,
const command_bulk& /* entry */
);
bool parse_entry_teaspeak_v1(
std::string& /* error */,
std::vector<permission_entry>& /* result */,
const command_bulk& /* entry */
);
};
class permission_writer {
public:
permission_writer(type type_, version_t version, command_builder& command, permission::teamspeak::GroupType target_permission_type);
bool write(
std::string& /* error */,
size_t& /* offset */,
const std::deque<permission_entry>& /* permissions */);
bool write_entry(
std::string& /* error */,
size_t& /* offset */,
const permission_entry& /* permissions */);
private:
typedef bool(permission_writer::*write_impl_t)(std::string &error, size_t& offset,const permission_entry& /* permissions */);
command_builder& command;
const type type_;
const version_t version_;
const permission::teamspeak::GroupType target_permission_type_;
write_impl_t write_impl;
bool write_entry_teamspeak_v0(
std::string &error,
size_t& offset,
const permission_entry& /* permissions */
);
bool write_entry_teaspeak_v1(
std::string &error,
size_t& offset,
const permission_entry& /* permissions */
);
};
struct permissions_flat_entry {
uint64_t id1;
uint64_t id2;
std::vector<permission_entry> permissions;
};
class flat_parser : public parser<std::deque<permissions_flat_entry>> {
public:
flat_parser(type type_, version_t version, const command_parser& command, permission::teamspeak::GroupType target_permission_type)
: parser{type_, version, command}, pparser{type_, version, command, {
target_permission_type,
{"id1", "id2", "end_flat"}, /* only id1 should be enough, because if id2 changes id1 will be set as well but we just wan't to get sure */
true
}} {}
bool parse(
std::string& /* error */,
std::deque<permissions_flat_entry>& /* result */,
size_t& /* offset */) override;
private:
permission_parser pparser;
};
}

View File

@ -0,0 +1,42 @@
//
// Created by WolverinDEV on 11/04/2020.
//
#include "server.h"
using namespace ts::server::snapshots;
bool server_parser::parse(std::string &error, server_entry &result, size_t &offset) {
auto data = this->command.bulk(offset++);
if(!data.has_key("end_virtualserver")) {
error = "missing virtual server end token at character " + std::to_string(data.command_character_index());
return false;
}
result.properties.register_property_type<property::VirtualServerProperties>();
size_t entry_index{0};
std::string_view key{};
std::string value{};
while(data.next_entry(entry_index, key, value)) {
if(key == "end_virtualserver" ||
key == property::describe(property::VIRTUALSERVER_PORT).name ||
key == property::describe(property::VIRTUALSERVER_HOST).name ||
key == property::describe(property::VIRTUALSERVER_WEB_PORT).name ||
key == property::describe(property::VIRTUALSERVER_WEB_HOST).name ||
key == property::describe(property::VIRTUALSERVER_VERSION).name ||
key == property::describe(property::VIRTUALSERVER_PLATFORM).name)
continue;
const auto& property = property::find<property::VirtualServerProperties>(key);
if(property.is_undefined()) {
//TODO: Issue a warning
continue;
}
//TODO: Validate value?
result.properties[property] = value;
}
return true;
}

View File

@ -0,0 +1,32 @@
#pragma once
#include <Definitions.h>
#include <Properties.h>
#include <chrono>
#include <deque>
#include "./snapshot.h"
namespace ts::server::snapshots {
struct server_entry {
Properties properties{};
};
class server_parser : public parser<server_entry> {
public:
server_parser(type type_, version_t version, const command_parser& command) : parser{type_, version, command} {}
bool parse(
std::string & /* error */,
server_entry & /* result */,
size_t & /* offset */) override;
};
/*
class server_writer : public writer<server_entry> {
public:
server_writer(type type_, version_t version, command_builder& command) : writer{type_, version, command} {}
bool write(std::string &, size_t &, const server_entry &) override;
};
*/
}

View File

@ -0,0 +1,46 @@
#pragma once
#include <cstdint>
#include <query/command3.h>
namespace ts::server::snapshots {
enum struct type {
TEAMSPEAK,
TEASPEAK
};
typedef int32_t version_t;
constexpr version_t unknown_version{-1};
template <typename result_t>
class parser {
public:
parser(type type_, version_t version, const command_parser& command) :
command{command}, type_{type_}, version_{version} {}
virtual bool parse(
std::string& /* error */,
result_t& /* result */,
size_t& /* offset */) = 0;
protected:
const command_parser& command;
const type type_;
const version_t version_;
};
template <typename entry_t>
class writer {
public:
writer(type type_, version_t version, command_builder& command) :
command{command}, type_{type_}, version_{version} {}
virtual bool write(
std::string& /* error */,
size_t& /* offset */,
const entry_t& /* entry */) = 0;
protected:
command_builder& command;
const type type_;
const version_t version_;
};
}