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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user