New snapshot system proposal
This commit is contained in:
parent
0f1665c97d
commit
483e188c37
60
server/src/snapshots/channel.cpp
Normal file
60
server/src/snapshots/channel.cpp
Normal 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;
|
||||
}
|
23
server/src/snapshots/channel.h
Normal file
23
server/src/snapshots/channel.h
Normal 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;
|
||||
};
|
||||
}
|
108
server/src/snapshots/client.cpp
Normal file
108
server/src/snapshots/client.cpp
Normal 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;
|
||||
}
|
36
server/src/snapshots/client.h
Normal file
36
server/src/snapshots/client.h
Normal 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;
|
||||
};
|
||||
}
|
374
server/src/snapshots/deploy.cpp
Normal file
374
server/src/snapshots/deploy.cpp
Normal 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;
|
||||
}
|
97
server/src/snapshots/groups.cpp
Normal file
97
server/src/snapshots/groups.cpp
Normal 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;
|
||||
}
|
54
server/src/snapshots/groups.h
Normal file
54
server/src/snapshots/groups.h
Normal 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;
|
||||
};
|
||||
}
|
305
server/src/snapshots/permission.cpp
Normal file
305
server/src/snapshots/permission.cpp
Normal 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;
|
||||
}
|
110
server/src/snapshots/permission.h
Normal file
110
server/src/snapshots/permission.h
Normal 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;
|
||||
};
|
||||
}
|
42
server/src/snapshots/server.cpp
Normal file
42
server/src/snapshots/server.cpp
Normal 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;
|
||||
}
|
32
server/src/snapshots/server.h
Normal file
32
server/src/snapshots/server.h
Normal 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;
|
||||
};
|
||||
*/
|
||||
}
|
46
server/src/snapshots/snapshot.h
Normal file
46
server/src/snapshots/snapshot.h
Normal 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_;
|
||||
};
|
||||
}
|
Loading…
Reference in New Issue
Block a user